Python ZeroMQ入门

通常需要用到ZeroMQ的人应该已经具备比较深的编程知识,所以有个问题就是目前能找到的大多数入门教程对小白不够友好。

所以我最近在学的时候,顺便做些记录。

就不摆概念定义了,百度一下就有。而且刚刚学可能也看不懂,容易打击信心,所以直接从例子开始吧。

接下来会用三个例子,介绍最基本的模式和用法。

第一个例子

我们想实现Hello World这个经典功能。但是ZeroMQ不是一个编程语言,是一个传输工具。所以我们想实现的功能是:A给B发送一个消息,B收到消息返回给A一些回答。也就是客户端和服务器干的事情。

客户端发送“Hello”到服务器,然后服务器用“World”来应答。

下面是Hello world服务器(hwserver.py):

# -*-coding:utf-8-*-
import time
import zmq

context = zmq.Context()
socket = context.socket(zmq.REP)
socket.bind("tcp://*:5555")

while True:
    #  Wait for next request from client

    message = socket.recv()

    print("Received request: %s" % message)

    #  Do some 'work'
    time.sleep(0.5)

    #  Send reply back to client
    socket.send(b"World")

把REP套接字绑定到tcp://*:5555上,就像是安装一个电话机在这里,这样别人就可以给它打电话传输信息了。

while True让服务器会一直运行,等待客户端的消息,接收到之后,发送消息“World”。

字符串前面b代表这是一个bytes对象,下同。

下面是客户端(hwclient.py):

# -*-coding:utf-8-*-

import zmq

context = zmq.Context()

#  Socket to talk to server
print("Connecting to hello world server...")
socket = context.socket(zmq.REQ)
socket.connect("tcp://localhost:5555")

#  Do 10 requests, waiting each time for a response
for request in range(10):
    print("Sending request %s ..." % request)
    socket.send(b"Hello")

    #  Get the reply.
    message = socket.recv()
    print("Received reply %s [ %s ]" % (request, message))

客户端取连接服务器的位置,相当于给之前安装的电话机打电话。

然后测试,发送了10次消息,每次都是发送的“Hello”。

然后接受服务器返回的信息,打印出来。


首先打开服务器,即运行hwserver.py :

Python ZeroMQ入门_第1张图片

服务器正在等待。

然后运行客户端,即hwclient.py :

Python ZeroMQ入门_第2张图片

可以看到客户端发送了10次消息,然后每次都收到回复“World”。

而服务器:

Python ZeroMQ入门_第3张图片

可以看到服务器接收到了10次消息“Hello”。并且还在运行,等待。此时如果再运行客户端,服务器就会再多出10条消息。

Python ZeroMQ入门_第4张图片

第二个例子:

这是一个经典模式,即单向的数据发布。一台服务器把更新推到一组客户端。

下面我们要做的事情就是:模拟天气预报,服务器更新天气信息,并且把天气信息告诉每一个订阅电台收听天气预报的人。

下面是天气更新服务器(wuserver.py):

# -*-coding:utf-8-*-

#   Weather update server
#   Binds PUB socket to tcp://*:5556
#   Publishes random weather updates
#

import zmq
from random import randrange


context = zmq.Context()
socket = context.socket(zmq.PUB)
socket.bind("tcp://*:5556")

while True:
    zipcode = randrange(1, 100000)
    temperature = randrange(-80, 135)
    relhumidity = randrange(10, 60)

    socket.send_string("%i %i %i" % (zipcode, temperature, relhumidity))

用随机数来得到模拟的天气信息。

PUB代表publish发布,将PUB套接字绑到tcp://*:5556

得到天气信息之后,发送。

然后是天气客户端(wuclient.py):

# -*-coding:utf-8-*-

#   Weather update client
#   Connects SUB socket to tcp://localhost:5556
#   Collects weather updates and finds avg temp in zipcode
#

import sys
import zmq


#  Socket to talk to server
context = zmq.Context()
socket = context.socket(zmq.SUB)

print("Collecting updates from weather server...")
socket.connect("tcp://localhost:5556")

# Subscribe to zipcode, default is NYC, 10001
zip_filter = sys.argv[1] if len(sys.argv) > 1 else "10001"

# Python 2 - ascii bytes to unicode str
if isinstance(zip_filter, bytes):
    zip_filter = zip_filter.decode('ascii')
socket.setsockopt_string(zmq.SUBSCRIBE, zip_filter)

# Process 5 updates
total_temp = 0
for update_nbr in range(5):
    string = socket.recv_string()
    zipcode, temperature, relhumidity = string.split()
    total_temp += int(temperature)

print("Average temperature for zipcode '%s' was %dF" % (
      zip_filter, total_temp / (update_nbr+1))
)

SUB 就是subscribe,订阅。要与天气预报电台连接上。

zip_filter就是你要找出特定地区的相关信息所用到的筛选器。默认是10001。

如果是bytes类型的,就把它用decode函数编程str类型数据。

然后非常重要的一点是,要用setsockopt_string和SUBSCRIBE来设置一个订阅。就像你看油管,要点击“订阅”键才能订阅成功。

如果漏掉这一步,不会得到任何信息。

然后更新5个信息,取平均值,打印出来。


首先我们运行天气服务器(wuserver.py):

Python ZeroMQ入门_第5张图片

可以看到天气服务器开始运行。

然后我们运行客户端(wuclient.py):

Python ZeroMQ入门_第6张图片

第三个例子:

假设我们有一个很艰巨的任务,如果我们能把这个任务分成很多小任务,并且把它们分配给各个工人同时来完成的话,就会提高效率。

为了完成这样一个模型,我们需要三个部分:

  • 一台发生器,用于产生可以并行执行的任务。
  • 一组工人,用于处理任务。
  • 一个接收器,用于收集工作进程返回的结果。

流水线如图所示:

è¿éåå¾çæè¿°

接下来我们挨个实现每个部分:

下面是并行任务发生器(taskvent.py):

# -*-coding:utf-8-*-

# Task ventilator
# Binds PUSH socket to tcp://localhost:5557
# Sends batch of tasks to workers via that socket
#
# Author: Lev Givon 

import zmq
import random
import time

try:
    raw_input
except NameError:
    # Python 3
    raw_input = input

context = zmq.Context()

# Socket to send messages on
sender = context.socket(zmq.PUSH)
sender.bind("tcp://*:5557")

# Socket with direct access to the sink: used to syncronize start of batch
sink = context.socket(zmq.PUSH)
sink.connect("tcp://localhost:5558")

print("Press Enter when the workers are ready: ")
_ = raw_input()
print("Sending tasks to workers...")

# The first message is "0" and signals start of batch
sink.send(b'0')

# Initialize random number generator
random.seed()

# Send 100 tasks
total_msec = 0
for task_nbr in range(100):

    # Random workload from 1 to 100 msecs
    workload = random.randint(1, 100)
    total_msec += workload

    sender.send_string(u'%i' % workload)

print("Total expected cost: %s msec" % total_msec)

# Give 0MQ time to deliver
time.sleep(1)

PUSH是用于发送的,PULL是用于接收的。

我们把sender绑到tcp://*:5557上,sender用于发送每个任务。

把sink绑到tcp://localhost:5558上,用于同时开始处理一批任务。

sink发送一个0过去,作为开始处理的信号。

然后随机产生一百个数字,每个数字相当于每个任务所需要的时间,然后把任务信息用sender分发下去。

并打印出依次完成任务需要的总时间。

 

下面是并行任务工人(taskwork.py):

# -*-coding:utf-8-*-

# Task worker
# Connects PULL socket to tcp://localhost:5557
# Collects workloads from ventilator via that socket
# Connects PUSH socket to tcp://localhost:5558
# Sends results to sink via that socket
#
# Author: Lev Givon 

import sys
import time
import zmq


context = zmq.Context()

# Socket to receive messages on
receiver = context.socket(zmq.PULL)
receiver.connect("tcp://localhost:5557")

# Socket to send messages to
sender = context.socket(zmq.PUSH)
sender.connect("tcp://localhost:5558")

# Process tasks forever
while True:
    s = receiver.recv()

    # Simple progress indicator for the viewer
    sys.stdout.write('.')
    sys.stdout.flush()

    # Do the work
    time.sleep(int(s)*0.001)

    # Send results to sink
    sender.send(b'')

receiver用于接收5557上的消息,也就是之钱并行任务发生器中sender发出来的任务。

这里的sender用于向5558传送消息。

while True一直等待消息。

s从为从5557收到的消息,也就是每一个任务。

用sys.stdout来显示完成进度。

因为s代表的是每个任务耗时,所以Do the work部分就是sleep对应的毫秒数,方便最后用time函数来计算实际总运行时间。

再用sender把结果传送给5558上的sink。但由于这里没有结果,所以是空字符串。

 

下面是并行任务接收器(tasksink.py):

# -*-coding:utf-8-*-

# Task sink
# Binds PULL socket to tcp://localhost:5558
# Collects results from workers via that socket
#
# Author: Lev Givon 

import sys
import time
import zmq


context = zmq.Context()

# Socket to receive messages on
receiver = context.socket(zmq.PULL)
receiver.bind("tcp://*:5558")

# Wait for start of batch
s = receiver.recv()

# Start our clock now
tstart = time.time()

# Process 100 confirmations
for task_nbr in range(100):
    s = receiver.recv()
    if task_nbr % 10 == 0:
        sys.stdout.write(':')
    else:
        sys.stdout.write('.')
    sys.stdout.flush()

# Calculate and report duration of batch
tend = time.time()
print("Total elapsed time: %d msec" % ((tend-tstart)*1000))

receiver用于接收5558上的消息也就是任务接收器中的sender传输过来的信息,当然,我们已经知道,是空字符串而已。

但是别忘了,不全是空字符串,因为第一步中传输了一个0给5558的sink,用于开始批处理任务。这就是for循环之外的s所接收到的信息,接收到开始信号之后,用tstart代表开始工作的时间,然后接收所有任务的结果,也就是100条信息。

最后用tend代表结束时的时间,就可以得到并行任务的总耗时。


下面执行上述代码,看看结果:

首先运行并行任务发生器(taskevent.py):

Python ZeroMQ入门_第7张图片

等待工人就位。

按下回车键之后:

Python ZeroMQ入门_第8张图片

 

然后运行并行任务工人(taskwork.py):

Python ZeroMQ入门_第9张图片

Python ZeroMQ入门_第10张图片

任务理论总耗时为5秒左右。

最后运行并行任务接收器(tasksink.py):

实际总耗时为36毫秒,可以看到确实是所有任务确实是并行的。

速度快了140倍。

 

 

 

未完待续。。。

 

你可能感兴趣的:(ZeroMQ)