安装 pip install pyzmq-18.0.1
1. 请求应答模式(Request-Reply)(rep 和 req)
消息双向的,有来有往,req端请求的消息,rep端必须答复给req端
2. 订阅发布模式 (pub 和 sub)
消息单向的,有去无回的。可按照发布端可发布制定主题的消息,订阅端可订阅喜欢的主题,订阅端只会收到自己已经订阅的主题。发布端发布一条消息,可被多个订阅端同事收到。
3. push pull模式
消息单向的,也是有去无回的。push的任何一个消息,始终只会有一个pull端收到消息.
后续的代理模式和路由模式等都是在三种基本模式上面的扩展或变异。
1.请求回应模型。由请求端发起请求,并等待回应端回应请求。从请求端来看,一定是一对对收发配对的;
反之,在回应端一定是发收对。请求端和回应端都可以是1:N的模型。通常把1认为是server,N认为是Client。
0MQ可以很好的支持路由功能(实现路由功能的组件叫做Device),把1:N扩展为N:M(只需要加入若干路由节点)。
从这个模型看,更底层的端点地址是对上层隐藏的。每个请求都隐含回应地址,而应用则不关心它。
2.发布订阅模型。这个模型里,发布端是单向只发送数据的,且不关心是否把全部的信息都发送给订阅者。
如果发布端开始发布信息的时候,订阅端尚未连接上,这些信息直接丢弃。
不过一旦订阅端连接上来,中间会保证没有信息丢失。
同样,订阅端则只负责接收,而不能反馈。
如果发布端和订阅端需要交互(比如要确认订阅者是否已经连接上),则使用额外的socket采用请求回应模型满足这个需求。
3.管道模型。这个模型里,管道是单向的,从PUSH端单向的向PULL端单向的推送数据流。
server为REP模式,等待消息,client为REQ模式,向server请求消息。
一个最简单的例子:
server.py
import zmq
context = zmq.Context()
socket = context.socket(zmq.REP)
socket.bind("tcp://*:5555")
message = socket.recv()
print "message from client:", message
# Send reply back to client
socket.send("World")
client.py
import zmq
context = zmq.Context()
print "Connecting to server..."
socket = context.socket(zmq.REQ)
socket.connect ("tcp://localhost:5555")
socket.send ("Hello")
message = socket.recv()
print "Received reply: ", message
一个线程中如果有多个sokect,同时需要收发数据时,zmq提供polling sockets实现,不用在send()或者recv()时阻塞socket。
下面是一个在recv()端接受信息的poller()轮询接受代码。其中socks = dict(poller.poll(1000))中的1000位延迟时间。
#!/usr/bin/python
# -*- coding: utf-8
import zmq
import random
import time
from multiprocessing import Process
def server_push(port="5556"):
context = zmq.Context()
socket = context.socket(zmq.PUSH)
socket.bind("tcp://*:%s" % port)
print "Running server on port: ", port
# serves only 5 request and dies
for reqnum in range(10):
if reqnum < 6:
socket.send("Continue")
else:
socket.send("Exit")
break
time.sleep (1)
def server_pub(port="5558"):
context = zmq.Context()
socket = context.socket(zmq.PUB)
socket.bind("tcp://*:%s" % port)
publisher_id = random.randrange(0,9999)
print "Running server on port: ", port
# serves only 5 request and dies
for reqnum in range(10):
# Wait for next request from client
topic = random.randrange(8,10)
messagedata = "server#%s" % publisher_id
print "%s %s" % (topic, messagedata)
socket.send("%d %s" % (topic, messagedata))
time.sleep(1)
def client(port_push, port_sub):
context = zmq.Context()
socket_pull = context.socket(zmq.PULL)
socket_pull.connect ("tcp://localhost:%s" % port_push)
print "Connected to server with port %s" % port_push
socket_sub = context.socket(zmq.SUB)
socket_sub.connect ("tcp://localhost:%s" % port_sub)
socket_sub.setsockopt(zmq.SUBSCRIBE, "9")
#zmq.SUBSCRIBE创建一个消息过滤标志,订阅以9为前缀的消息
print "Connected to publisher with port %s" % port_sub
# 初始化Poller
poller = zmq.Poller()
poller.register(socket_pull, zmq.POLLIN)
poller.register(socket_sub, zmq.POLLIN)
# Work on requests from both server and publisher
#如果设置为POLLIN则刷新recv,与之对应的是POLLOUT刷新send发送事件,也可以同时设置两个标志
should_continue = True
while should_continue:
socks = dict(poller.poll())
if socket_pull in socks and socks[socket_pull] == zmq.POLLIN:
message = socket_pull.recv()
print "Recieved control command: %s" % message
if message == "Exit":
print "Recieved exit command, client will stop recieving messages"
should_continue = False
if socket_sub in socks and socks[socket_sub] == zmq.POLLIN:
string = socket_sub.recv()
topic, messagedata = string.split()
#Python split()通过指定分隔符对字符串进行切片,如果参数num 有指定值,则仅分隔 num 个子字符串
#str.split(str="", num=string.count(str)).
# str -- 分隔符,默认为所有的空字符,包括空格、换行(\n)、制表符(\t)等,num -- 分割次数
print "Processing ... ", topic, messagedata
if __name__ == "__main__":
# Now we can run a few servers
server_push_port = "5556"
server_pub_port = "5558"
Process(target=server_push, args=(server_push_port,)).start()
Process(target=server_pub, args=(server_pub_port,)).start()
Process(target=client, args=(server_push_port,server_pub_port,)).start()
结果:
Running server on port: 5556
Running server on port: 5558
8 server#5230
Connected to server with port 5556
Connected to publisher with port 5558
Recieved control command: Continue
8 server#5230
Recieved control command: Continue
9 server#5230
Processing ... 9 server#5230
Recieved control command: Continue
9 server#5230
Processing ... 9 server#5230
Recieved control command: Continue
9 server#5230
Processing ... 9 server#5230
Recieved control command: Continue
9 server#5230
Processing ... 9 server#5230
Recieved control command: Continue
9 server#5230
Processing ... 9 server#5230
Recieved control command: Exit
Recieved exit command, client will stop recieving messages
8 server#5230
9 server#5230
9 server#5230
Process finished with exit code 0
from zmq.auth.certs import create_certificates
创建证书
public_key, private_key = create_certificates(tmp_key_dir, id)
from zmq.auth.ioloop import IOLoopAuthenticator
IO循环验证程序
from zmq.auth.certs import load_certificate
server_secret_file = os.path.join(secret_keys_dir, 'beeswarm_server.pri')
server_public, server_secret = load_certificate(server_secret_file)
drone_data_inbound = beeswarm.shared.zmq_context.socket(zmq.PULL)
drone_data_inbound.curve_secretkey = server_secret
drone_data_inbound.curve_publickey = server_public
drone_data_inbound.curve_server = True
drone_data_inbound.bind('tcp://*:{0}'.format(self.config['network']['zmq_port']))
from gevent import Greenlet
import zmq.green as zmq
from zmq.auth.certs import create_certificates
def init
context = beeswarm.shared.zmq_context
self.config_commands = context.socket(zmq.REP)
self.enabled = True
def run
self.config_commands.bind(SocketNames.CONFIG_COMMANDS.value)
poller = zmq.Poller()
poller.register(self.config_commands, zmq.POLLIN)
while self.enabled:
socks = dict(poller.poll(500))
if self.config_commands in socks and socks[self.config_commands] == zmq.POLLIN:
self._handle_commands()
def _handle_commands(self):
msg = self.config_commands.recv()
if ' ' in msg:
cmd, data = msg.split(' ', 1)
else:
cmd = msg
logger.debug('Received command: {0}'.format(cmd))
if cmd == Messages.SET_CONFIG_ITEM.value: #SET 重新设置配置文件
self._handle_command_set(data)
self.config_commands.send('{0} {1}'.format(Messages.OK.value, '{}'))
elif cmd == Messages.GET_CONFIG_ITEM.value: #GET 获取配置文件某KEY值
value = self._handle_command_get(data)
self.config_commands.send('{0} {1}'.format(Messages.OK.value, value))
elif cmd == Messages.GET_ZMQ_KEYS.value: #返回客户端证书
self._handle_command_getkeys(data)
elif cmd == Messages.DELETE_ZMQ_KEYS.value: #删除客户端证书
self._remove_zmq_keys(data)
self.config_commands.send('{0} {1}'.format(Messages.OK.value, '{}'))
else:
logger.error('Unknown command received: {0}'.format(cmd))
self.config_commands.send(Messages.FAIL.value)
def _handle_command_set(self, data):
new_config = json.loads(data)
self.config.update(new_config)
self._save_config_file()
def _handle_command_get(self, data):
# example: 'network,host' will lookup self.config['network']['host']
#示例:“network,host”将查找self.config[“network”][“host”]
keys = data.split(',')
value = self._retrieve_nested_config(keys, self.config)
return value
def _retrieve_nested_config(self, keys, dict):
if keys[0] in dict:
if len(keys) == 1:
return dict[keys[0]]
else:
return self._retrieve_nested_config(keys[1:], dict[keys[0]])
def _handle_command_getkeys(self, name):
private_key, publickey = self._get_zmq_keys(name)
self.config_commands.send(Messages.OK.value + ' ' + json.dumps({'public_key': publickey,
'private_key': private_key}))
def _save_config_file(self):
with open(self.config_file, 'w+') as config_file:
config_file.write(json.dumps(self.config, indent=4))
def _get_zmq_keys(self, id):
cert_path = os.path.join(self.work_dir, 'certificates')
public_keys = os.path.join(cert_path, 'public_keys')
private_keys = os.path.join(cert_path, 'private_keys')
public_key_path = os.path.join(public_keys, '{0}.pub'.format(id))
private_key_path = os.path.join(private_keys, '{0}.pri'.format(id))
if not os.path.isfile(public_key_path) or not os.path.isfile(private_key_path):
logging.debug('Generating ZMQ keys for: {0}.'.format(id))
for _path in [cert_path, public_keys, private_keys]:
if not os.path.isdir(_path):
os.mkdir(_path)
tmp_key_dir = tempfile.mkdtemp()
try:
public_key, private_key = create_certificates(tmp_key_dir, id)
# the final location for keys
#钥匙的最终位置
shutil.move(public_key, public_key_path)
shutil.move(private_key, private_key_path)
finally:
shutil.rmtree(tmp_key_dir)
# return copy of keys
#返回密钥副本
return open(private_key_path, "r").readlines(), open(public_key_path, "r").readlines()
def _remove_zmq_keys(self, id):
cert_path = os.path.join(self.work_dir, 'certificates')
public_keys = os.path.join(cert_path, 'public_keys')
private_keys = os.path.join(cert_path, 'private_keys')
public_key_path = os.path.join(public_keys, '{0}.pub'.format(id))
private_key_path = os.path.join(private_keys, '{0}.pri'.format(id))
for _file in [public_key_path, private_key_path]:
if os.path.isfile(_file):
os.remove(_file)
def message_proxy(self, work_dir):
"""
drone_data_inboud is for data comming from drones
drone_data_outbound is for commands to the drones, topic must either be a drone ID or all for sending a broadcast message to all drones
无人机内部数据用于无人机的数据混合。
无人机数据输出用于向无人机发送命令,主题必须是无人机ID或全部用于向所有无人机发送广播消息
"""
public_keys_dir = os.path.join(work_dir, 'certificates', 'public_keys')
secret_keys_dir = os.path.join(work_dir, 'certificates', 'private_keys')
# start and configure auth worker
#启动并配置身份验证工作程序
auth = IOLoopAuthenticator()
auth.start()
auth.allow('127.0.0.1')
auth.configure_curve(domain='*', location=public_keys_dir)
# external interfaces for communicating with drones
#与无人机通信的外部接口
#服务器证书
server_secret_file = os.path.join(secret_keys_dir, 'beeswarm_server.pri')
#获取公钥和密钥
server_public, server_secret = load_certificate(server_secret_file)
drone_data_inbound = beeswarm.shared.zmq_context.socket(zmq.PULL)
drone_data_inbound.curve_secretkey = server_secret
drone_data_inbound.curve_publickey = server_public
drone_data_inbound.curve_server = True
drone_data_inbound.bind('tcp://*:{0}'.format(self.config['network']['zmq_port']))
drone_data_outbound = beeswarm.shared.zmq_context.socket(zmq.PUB)
drone_data_outbound.curve_secretkey = server_secret
drone_data_outbound.curve_publickey = server_public
drone_data_outbound.curve_server = True
drone_data_outbound.bind('tcp://*:{0}'.format(self.config['network']['zmq_command_port']))
# internal interfaces
# all inbound session data from drones will be replayed on this socket
#内部接口,来自无人机的所有入站会话数据将在此套接字上重播
drone_data_socket = beeswarm.shared.zmq_context.socket(zmq.PUB)
drone_data_socket.bind(SocketNames.DRONE_DATA.value)
# all commands received on this will be published on the external interface
#在此上接收到的所有命令都将在外部接口上发布
drone_command_socket = beeswarm.shared.zmq_context.socket(zmq.PULL)
drone_command_socket.bind(SocketNames.DRONE_COMMANDS.value)
poller = zmq.Poller()
poller.register(drone_data_inbound, zmq.POLLIN)
poller.register(drone_command_socket, zmq.POLLIN)
while True:
# .recv() gives no context switch - why not? using poller with timeout instead
#recv()不提供上下文切换-为什么不?将轮询器与超时一起使用
socks = dict(poller.poll(100))
gevent.sleep()
if drone_command_socket in socks and socks[drone_command_socket] == zmq.POLLIN:
data = drone_command_socket.recv()
drone_id, _ = data.split(' ', 1)
logger.debug("Sending drone command to: {0}".format(drone_id))
# pub socket takes care of filtering
#pub socket负责过滤
drone_data_outbound.send(data)
elif drone_data_inbound in socks and socks[drone_data_inbound] == zmq.POLLIN:
raw_msg = drone_data_inbound.recv()
split_data = raw_msg.split(' ', 2)
if len(split_data) == 3:
topic, drone_id, data = split_data
else:
data = None
topic, drone_id, = split_data
logger.debug("Received {0} message from {1}.".format(topic, drone_id))
# relay message on internal socket
#内部插座上的中继消息
drone_data_socket.send(raw_msg)