第一章 基础知识
解释zmq:
1、 像有路由的邮箱 很快
2、 更小巧,更简单
代码与代码之间必须要有交流,像人脑一样亿万个神经元相互发射消息
互联方式:
很多IETF规范,例如HTTP,但只考虑大型服务器和C/S
集中:UDP,TCP,专有协议,HTTP和WebSocket
分布: Skype, BT
Fixing the World:
- 如何将任何地方的任何代码连接起来
- 包装在人们容易理解和使用尽可能简单的构件中
模式一:请求应答模式REQ-REP
对应于RPC(远程过程调用)和C/S模型
REQ-REP步调一致(一次交流):
Server 先 zmq_recv 后 zmq_send
Client 先 zmq_send 后 zmq_recv
服务器中断后,重启服务器,客户端无法恢复正常。
示例1-1 “Hello World” 服务器
coding=gbk
import zmq
import time
准备上下文
context = zmq.Context()
与客户端交流的套接字
socket = context.socket(zmq.REP)
将REP套接字绑定到tcp://127.0.0.1:55555
socket.bind(“tcp://127.0.0.1:5555”)
while True:
# 等待来自客户端的请求
message = socket.recv()
print(“Received request: %s” % message)
time.sleep(1)
# 给客户端发送回复
socket.send(b"World")
示例1-1 “Hello World” 客户端
coding=gbk
import zmq
准备上下文
context = zmq.Context()
print(“Connecting to hello world server…”)
与服务器交流的套接字
socket = context.socket(zmq.REQ)
将REQ套接字连接到tcp://127.0.0.1:55555
socket.connect(“tcp://127.0.0.1:5555”)
for i in range(10):
print(“Sending request %s …” % i)
socket.send(b"Hello")
message = socket.recv()
print(“Received reply %s [ %s ]” % (i, message))
补充:
上下文:context = zmq.Context()
套接字:socket = context.socket(zmq.**)
ZMQ对发送的数据只知字节大小,不知内容
复杂的数据类型和对象 有专门的库 如(协议缓冲区)
字符串:
C:字符串用空字节终止,“Hello” H e l l o 0 (6)
Python:“Hello” H e l l o (5)
zmq字符串中:“指定长度” ,在线路上发送时,不含结尾空字符。
C语言使用zmq传递字符串需要包含”zhelpers.h“头文件
版本报告:示例version.zmq
模式二:发布订阅模式PUB-SUB
示例: Weather update PUB
coding=gbk
import zmq
import random
import time
from random import randrange
context = zmq.Context()
publisher = context.socket(zmq.PUB)
publisher.bind(“tcp://127.0.0.1:5555”)
while True:
# 邮编 温度 湿度
zipcode = random.randrange(1, 100000)
temperature = random.randrange(-80, 135)
humidity = random.randrange(10, 60)
time.sleep(1)
publisher.send_string("%i %i %i" % (zipcode, temperature, humidity))
示例: Weather update SUB
coding=gbk
import zmq
import sys
context = zmq.Context()
subscriber = context.socket(zmq.SUB)
print(“Collecting updates from weather server…”)
subscriber.connect(“tcp://127.0.0.1:5555”)
订阅者订阅所有主题
subscriber.setsockopt_string(zmq.SUBSCRIBE, ‘’)
while True:
message = subscriber.recv_string()
zipcode, temperature, humidity = message.split()
print("%s %s %s" % (zipcode, temperature, humidity))
SUB收集针对邮编的1000个update后计算avg,退出
单向的数据发布
使用SUB套接字必须用zmq_setsockopt()和SUBSCRIBE()设置一个订阅,订阅者可以设置许多订阅。
PUB-SUB 异步
bind PUB 服务端(发布方)只能Send
connect SUB 客户端(订阅方)只能Recv
Slow joiner(慢木匠,慢连接):
顺序:SUB连接到端点,并接收。PUB绑定端点,立即发出消息。
启动SUB,稍后在启动PUB,依然总会错过PUB的第一条消息。原因是,订阅者SUB连接到PUB时,PUB可能已经将消息发出去了。
类似的,TCP建立连接,需花费几毫秒握手。解决慢连接的问题。
采用休眠的方法既缓慢又脆弱。
示例代码同步问题的替代方法是PUB消息无限,SUB不关心启动前发生了什么。
重启PUB端任意次,SUB端将继续工作。
补充
1、 一个SUB可以连接多个PUB,每次使用一个connect()调用。数据将交错(公平排队)。没有一个发布者可以淹没其他发布者。
2、 一个PUB没有SUB,会简单丢弃所有消息
3、 使用TCP且SUB慢速,PUB处消息会排队,用“高水位线“针对此情况,保护SUB
4、 ZMQ 3.x开始(tcp或ipc)过滤器发生在PUB
epgm 过滤器发生在SUB
ZMQ 2.x 所有协议 过滤器都在SUB
模式三 PULL-PUSH
并行任务发生器:产生100个任务,每个任务包含一条消息(休眠毫秒数)
并行任务工人 :接受一个消息,按消息里的秒数休眠,然后发出完成信号
并行任务接收器:收集100个结果消息,计算整体时间
示例并行任务发生器:
coding=gbk
import zmq
import random
import time
context = zmq.Context()
sender = context.socket(zmq.PUSH)
sender.bind(“tcp://127.0.0.1:5555”)
sink = context.socket(zmq.PUSH)
sink.connect(“tcp://127.0.0.1:5556”)
print("Press Enter when the workers are ready: ")
_ = input()
print(“Sending tasks to workers…”)
sink.send(b’0’)
random.seed()
total_msec = 0
for task_nbr in range(100):
workload = random.randint(1, 100)
total_msec += workload
sender.send_string(u'%i' % workload)
print(“Total expected cost: %s msec” % total_msec)
time.sleep(1)
示例并行任务工人:
coding=gbk
import zmq
import time
import sys
context = zmq.Context()
receiver = context.socket(zmq.PULL)
receiver.connect(“tcp://127.0.0.1:5555”)
sender = context.socket(zmq.PUSH)
sender.connect(“tcp://127.0.0.1:5556”)
while True:
s = receiver.recv()
sys.stdout.write(’.’)
sys.stdout.flush()
time.sleep(int(s)*0.001)
sender.send(b'')
示例并行任务接收器:
coding=gbk
import zmq
import sys
import time
context = zmq.Context()
receiver = context.socket(zmq.PULL)
receiver.bind(“tcp://127.0.0.1:5556”)
s = receiver.recv()
tstart = time.time()
for task_nbr in range(100):
s = receiver.recv()
if task_nbr % 10 == 0:
sys.stdout.write(’:’)
else:
sys.stdout.write(’.’)
sys.stdout.flush()
tend = time.time()
print("Total elapsed time: %d msec" % ((tend - tstart)*1000))
1、 工人向上连接发生器,向下连接接收器
2、 同步开始,同批次所有工人启动运行。
3、 发生器PUSH任务,均匀分配给工人,批处理前,工人都已连接(负载均衡)
4、 接收器PULL均匀收集工人的结果。(公平排队)
PULL-PUSH模式也会慢连接,影响负载均衡,某个工人连接的早,抓取的消息多。
用zmq编程
1、 zmq是一个api,有许多可能
2、 漂亮的代码风格
3、 边编程,边测试
4、 出bug时,代码分片段测试
5、 抽象(类,方法)不要只复制,要修改
获得正确的上下文
1、 创建一个上下文
2、 创建套接字
C语言中,进程中只创建并使用一个上下文
使用fork()时:主进程先执行zmq_ctx_new(),在fork(),子进程会获得自己的上下文,子进程干活,父进程管理。
执行彻底的退出:
C语言,要手动释放内存。Python:不用,内存自动释放。
多线程:不要在多线程中使用同一个套接字 (复杂)。