文章目录
- ZMQ 通信协议小结 🐝
- 前言 🔍
- zmq的三种模型 💦
- 1、Request_Reply模式(请求——应答): REP、 REQ ☎️
- 伪代码
- 应用场景
- 2、Publish-Subscribe模式(发布——订阅): PUB、SUB 🎙
- 伪代码
- 应用场景
- 3、Parallel Pipeline模式(push——pull): PUSH、PULL 🔗
- 伪代码
- 应用场景
ZMQ 通信协议小结 🐝
最近有时间了把这个坑填一填!!!
前言 🔍
- 项目中涉及到 zmq通信协议相关内容,所以将学习、使用过程同步分享
- 通篇以代码分享为主,且本文对底层socket不做过多叙述,以实际应用为准,希望能帮到各位!
- Talk is cheap, Show me the code
zmq的三种模型 💦
1、Request_Reply模式(请求——应答): REP、 REQ ☎️
- 一发一收 无缓存 断开连接数据丢失;
- 生产中也可以一个server对应多个client;
- 双向消息,
REP(server)
端必须recv到REQ(client)
的消息之后,调用send返回,否则通道堵塞; 相同的REQ(client)
端负责send消息到REP(server)
,然后调用recv获取REP(server)
的返回;
伪代码
- server.py
# 1、Request_Reply模式
# server
import zmq
context = zmq.Context()
socket = context.socket(zmq.REP)
socket.bind('tcp://*:5556')
while True:
message = socket.recv()
print(message)
socket.send('server response')
- client.py
# client
import zmq
import sys
context = zmq.Context()
socket = context.socket(zmq.REQ)
socket.connect('tcp://localhost:5556')
while True:
data = raw_input('input your data:')
if data == 'q':
sys.exit()
socket.send(data)
response = socket.recv()
print(response)
应用场景
- 场景说明:
我们定义一个非阻塞
的消息通道, 用作发送特定的Python结构体数据,包含三个文件如下: - Code:
- server.py
import time
import zmq
from data import zmqStruct
context = zmq.Context()
socket = context.socket(zmq.REP)
socket.bind("tcp://*:5656")
while True:
try:
message = socket.recv_pyobj(zmq.NOBLOCK)
print(message)
#time.sleep(1)
socket.send_pyobj('123123123')
except zmq.Again as e:
if e.errno!=zmq.EAGAIN:
print(repr(e))
time.sleep(1)
- client.py
from data import zmqStruct
def zmqREQ():
import zmq
context = zmq.Context()
socket = context.socket(zmq.REQ)
socket.connect("tcp://{}:5656".format('192.168.24.107'))
return socket
sendStruct = zmqStruct()
zmqClient = zmqREQ()
zmqClient.send_pyobj(sendStruct)
print zmqClient.recv_pyobj()
- data.py
class zmqStruct(onject): # 消息结构体
def __init__(self, cmd=0, data=None, desc=''):
self.cmd = cmd
self.data = data
self.desc = desc
2、Publish-Subscribe模式(发布——订阅): PUB、SUB 🎙
- 广播所有client,无缓存,断开连接数据丢失。(当然所有的问题都可以通过增加中间层的方式解决);
- 发布端发布主题topic,订阅端只会收到已订阅的主题topic;
- PUB端发送消息,SUB端接受消息;
- SUB可以注册多个PUB;
- 如果PUB没有任何SUB,那么消息将会被丢弃;
- SUB端消费过慢,消息则堆积到PUB端
- 单工-单向数据传输
伪代码
- server.py
# 2、Publish-Subscribe模式
# server
import zmq
context = zmq.Context()
socket = context.socket(zmq.PUB)
socket.bind("tcp://*:5005")
while True:
msg = input('input your data:').encode('utf-8')
socket.send(msg)
- client.py
# client
import zmq
context = zmq.Context()
socket = context.socket(zmq.SUB)
socket.connect('tcp://127.0.0.1:5005')
# 使用socket.setsockopt()进行过滤
socket.setsockopt(zmq.SUBSCRIBE,b'')
while True:
print(socket.recv_string())
应用场景
- 场景说明:
我们mock-个业务场景,系统分层为maste-slave, master端广播突发事件A
,比如给所有slave进行处理, - Code:
- master.py
import zmq
context = zmq.Context()
socket = context.socket(zmq.PUB)
socket.bind("tcp://*:5005")
while True:
msg = {"event": "突发事件A", "key": "A"}
socket.send_pyobj(msg)
- slave.py
import zmq
context = zmq.Context()
socket = context.socket(zmq.SUB)
socket.connect('tcp://127.0.0.1:5005')
# 使用socket.setsockopt()进行过滤
socket.setsockopt(zmq.SUBSCRIBE,b'')
while True:
msgObj = socket.recv_pyobj(flags=zmq.NOBLOCK)
3、Parallel Pipeline模式(push——pull): PUSH、PULL 🔗
- 管道模式(单工) - 单向通道;
- 可以由三部分组成:push推送数据,work缓存数据,pull竞争数据,断开连接数据不丢失,重连继续发送。work中间件可以去掉;
伪代码
- server.py
# 3、Parallel Pipeline模式
# server
import zmq
context = zmq.Context()
socket = context.socket(zmq.PULL)
socket.bind('tcp://*:5566')
while True:
data = socket.recv()
print(data)
- work.py
# work 无work push 会阻塞掉
import zmq
context = zmq.Context()
recive = context.socket(zmq.PULL)
recive.connect('tcp://127.0.0.1:5565')
sender = context.socket(zmq.PUSH)
sender.connect('tcp://127.0.0.1:5566')
while True:
data = recive.recv()
sender.send(data)
- client.py
# client
import zmq
import time
context = zmq.Context()
socket = context.socket(zmq.PUSH)
socket.bind('tcp://*:5565')
while True:
data = raw_input('input your data:')
socket.send(data)
应用场景
- 场景说明:
我们假定 有一个任务调度器 , 结构为1个 master 对应 10个 slave
, master接受任务,将任务投递给 slave. - Code:
- master.py
import time
import zmq
context = zmq.Context()
socket = context.socket(zmq.PUSH)
socket.bind("tcp://*:5000")
tasks = [i for i in range(100)]
def pub():
# 这个延时 是为了服务端绑定 socket 后会等待200毫秒避免消息丢失; 也是为了保证服客户端环境完备的折中之举
time.sleep(1)
for i in tasks:
socket.send(str(i))
if __name__ == '__main__':
pub()
- slave.py
import time
import threading
from concurrent.futures import ThreadPoolExecutor
import zmq
context = zmq.Context()
socket = context.socket(zmq.PULL)
socket.connect("tcp://127.0.0.1:5000")
threadpool = ThreadPoolExecutor(10)
def submsg():
"""
socket 接受消息使用 `zmq.NOBLOCK` 非阻塞模式来进行,可以保证保证循环体内的其他功能正常使用
:return:
"""
while 1:
try:
msg = socket.recv(flags=zmq.NOBLOCK)
except zmq.Again as e:
if e.errno != zmq.EAGAIN:
print(repr(e))
else:
print '接收到广播消息,线程池投递任务 msg={}'.format(msg)
threadpool.submit(work, msg)
def work(msg):
print '开始工作 参数{}'.format(msg)
time.sleep(2) # 模拟功能执行时间
print '结束工作'
if __name__ == '__main__':
submsg()