Python ZeroMQ编程 网络通信协议详细说明和教程

ZeroMQ概述

ZeroMQ(又名ØMQ,MQ,或zmq)像一个可嵌入的网络库,但其作用就像一个并发框架。

ZeroMQ类似于标准Berkeley套接字,其提供了各种传输工具,如进程内、进程间、TCP和组播中进行原子消息传送的套接字。

可以使用各种模式实现N对N的套接字连接,这些模式包括:发布-订阅、任务分配、请求-应答。

ZeroMQ的速度足够快,因此可充当集群产品的结构。

ZeroMQ的异步I/O模型提供了可扩展的多核应用程序,用异步消息来处理任务

ZeroMQ核心由C语言编写,支持C、C++、java、python等多种编程语言的API,并可运行在大多数操作系统上

总结以下:ØMQ (ZeroMQ) 是一个基于消息队列的多线程网络库,它封装了网络通信、消息队列、线程调度等功能,向上层提供简洁的API,应用程序通过加载库文件,调用API函数来实现高性能网络通信。

ZeroMQ是一个高效的消息传输库,支持许多不同的传输协议和消息模式。与像TCP和HTTP等协议不同,ZeroMQ的目标是为应用程序之间的通信提供一种非常简单和快速的方式。它提供了比HTTP或TCP更高层次的抽象,允许开发人员直接在应用程序之间透明地传递消息,而无需自己构建完整的通信协议。

ZeroMQ提供了简单的API,可用于许多流行的编程语言,包括Python。它可以用于构建分布式系统或多线程应用,可以从中获取更高级别的抽象。

看起来有些抽象,下面我们结合ZeroMQ 的 Python 封装------------ pyzmp,用实例看一下ZeroMQ的三种最基本的工作模式。

安装

安装方法

python 复制代码
pip install pyzmq

查看是否安装成功

python 复制代码
>>> import zmq
>>> print(zmq.__version__)

22.0.3

Request-Reply (请求响应模式)

Request-Reply模式概述:

  • 消息双向的,有来有往。
  • Client请求的消息,Server必须答复给Client。
  • Client在请求后,Server必须回响应,注意:Server不返回响应会报错。
  • Server和Client都可以是1:N的模型。通常把1认为是Server,N认为是Client。
  • 更底层的端点地址是对上层隐藏的,每个请求都隐含回应地址,而应用则不关心它。
  • ZMQ 可以很好的支持路由功能(实现路由功能的组件叫做 Device),把 1:N 扩展为 N:M(只需要加入若干路由节点)。

Client端python实现

python 复制代码
#client.py

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")
socket.send(b"Hello")
#  Get the reply.
message = socket.recv()
print(f"Received reply [ {message} ]")

Server端python实现

python 复制代码
#server.py
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(1)

    #  Send reply back to client
    socket.send(b"World")
  • 启动client.py 首先会打印Connecting to hello world server... 但不会受到任何消息。
  • 然后启动server.py ,客户端收到来自客户端的request: b'Hello'
    = 此时client端收到来自server端的 reply: [ b'World' ]
python 复制代码
python client.py 
Connecting to hello world server...
Received reply [ b'World' ]

python server.py 
Received request: b'Hello'

可以试一下,多运行几个client.py,看看情况是什么样的。

Publish/Subscribe(订阅-发布模式 )

Pub-Subs模式概述:

消息单向,有去无回

一个发布端,多个订阅端;发布端只管产生数据,发布端发布一条消息,可被多个订阅端同时收到。

发布者不必关心订阅者的加入和离开,消息会以 1:N 的方式扩散到每个订阅者。

广播所有client,没有队列缓存,断开连接数据将永远丢失。

如果Publish端开始发布信息时,Subscribe端尚未连接进来,则这些信息会被直接丢弃。

PUB和SUB谁bind谁connect并无严格要求(虽本质并无区别),但仍建议PUB使用bind,SUB使用connect

使用SUB设置一个订阅时,必须使用zmq_setsockopt()对消息进行过滤

这里直接引用官方文档的例子:

发布者:类似于一个天气更新服务器,向订阅者发送天气更新,内容包括邮政编码、温度、湿度等信息

python 复制代码
#Publisher.py
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))

订阅者:它监听发布者更新的数据流,过滤只接收与特定邮政编码相关的天气信息,默认接收接收10条数据

python 复制代码
#Subscribe.py 
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))
)

Push/Pull(流水线模式)

流水线模式概述:

主要用于多任务并行。

消息单向,有去无回。

Push的任何一个消息,始终只会有一个Pull端收到消息。

Push 端还是 Pull 端都可以做 server,bind 到某个地址等待对方访问。

如果有多个PULL端同时连接到PUSH端,则PUSH端会在内部做一个负载均衡,采用平均分配的算法,将所有消息均衡发布到PULL端上。

由三部分组成,Push进行数据推送,work进行数据缓存,Pull进行数据竞争获取处理。

存在一个数据缓存和处理负载,当连接被断开,数据不会丢失,重连后数据继续发送到对端。

ventilator 使用的是 SOCKET_PUSH,将任务分发到 Worker 节点上。Worker 节点上,使用 SOCKET_PULL 从上游接受任务,并使用 SOCKET_PUSH 将结果汇集到 Sink。值得注意的是,任务的分发的时候也同样有一个负载均衡的路由功能,worker 可以随时自由加入,ventilator 可以均衡将任务分发出去。

Push/Pull模式还是蛮常用的,这里我们主要测试一下它的负载均衡。

Ventilator

python 复制代码
# ventilator.py
import zmq
import time

context = zmq.Context()
socket = context.socket(zmq.PUSH)
socket.bind("tcp://*:5557")

while True:
    socket.send(b"test")
    print("已发送")
    time.sleep(1)
worker

# worker.py
import zmq

context = zmq.Context()

recive = context.socket(zmq.PULL)
recive.connect('tcp://127.0.0.1:5557')

sender = context.socket(zmq.PUSH)
sender.connect('tcp://127.0.0.1:5558')

while True:
    data = recive.recv()
    print("work1 正在转发...")
    sender.send(data)

sink

python 复制代码
# sink.py
import zmq
import sys

context = zmq.Context()
socket = context.socket(zmq.PULL)
socket.bind("tcp://*:5558")

while True:
    response = socket.recv()
    print("response: %s" % response)

打开4个Terminal,分别运行

bash 复制代码
python sink.py
python worker.py
python worker.py
python ventilator.py

简易程序版本

三、使用ZeroMQ进行通信

下面我们来看一个简单的例子,演示如何使用ZeroMQ在两个Python程序之间传递消息:

这是第一个Python程序,它会发送消息:

python 复制代码
import zmq

context = zmq.Context()
socket = context.socket(zmq.PUSH)
socket.bind("tcp://127.0.0.1:5555")

socket.send(b"Hello World")

这是第二个Python程序,它将接收该消息:

python 复制代码
import zmq

context = zmq.Context()
socket = context.socket(zmq.PULL)
socket.connect("tcp://127.0.0.1:5555")

message = socket.recv()
print(message)

在这个例子中,第一个程序创建了一个ZeroMQ上下文对象,并使用tcp://127.0.0.1:5555地址创建一个PUSH套接字。它发送一条消息,其内容为Hello World。

第二个程序也创建了一个ZeroMQ上下文对象,但它使用connect()方法连接了同一地址的PULL套接字。然后它调用recv()方法,等待来自第一个程序的消息,并在接收到消息后将其打印出来。

四、消息类型

ZeroMQ支持多种不同类型的消息,这些类型具有不同的功能和行为。下面是几种常见的消息类型:

REQ/REP

这种消息模式是简单的请求和响应模式。发送方使用REQ套接字发送请求,接收方使用REP套接字接收请求并发送响应。

PUB/SUB

这种消息模式是发布和订阅模式。发布者使用PUB套接字发布消息,订阅者使用SUB套接字订阅消息。

PUSH/PULL

这种消息模式是分发工作模式。PUSH套接字将任务分配给多个工作者并在它们完成任务后收集结果,而PULL套接字接收任务并完成它们并将结果返回。

五、使用不同的消息模式

下面是一个演示如何使用不同ZeroMQ消息模式的简单例子:

这是一个发布者程序:

python 复制代码
import zmq
import time

context = zmq.Context()
socket = context.socket(zmq.PUB)
socket.bind("tcp://127.0.0.1:5555")

while True:
    time.sleep(1)
    socket.send(b"A new message from publisher!")

这是订阅者程序:

python 复制代码
import zmq

context = zmq.Context()
socket = context.socket(zmq.SUB)
socket.connect("tcp://127.0.0.1:5555")
socket.setsockopt(zmq.SUBSCRIBE, b"")

while True:
    message = socket.recv()
    print(message)

在这个例子中,发布者程序创建了一个PUB套接字,并绑定到地址tcp://127.0.0.1:5555。它使用无限循环向订阅者发送消息。

订阅者程序创建一个SUB套接字,并连接到相同的地址。它使用setsockopt()设置SUBSCRIBE选项,这意味着它将接收所有发布者发送的消息。然后它使用recv()方法等待接收消息,并在接收到消息时将其打印出来。

这些例子可以使您熟悉PythonZMQ和ZeroMQ的基础知识。使用ZeroMQ可以非常方便地在应用程序之间发送消息,可以让您构建分布式系统,提高应用程序的可靠性和效率。

总结

消息模型可以根据需要组合使用,后续的代理模式和路由模式等都是在三种基本模式上面的扩展或变异。继续探索,请移步官方文档

Github: https://github.com/zeromq/pyzmq

Docs: https://zeromq.github.io/pyzmq/

Guide: http://zguide.zeromq.org/py:all

pypi: https://pypi.org/project/pyzmq/

相关推荐
数据智能老司机2 小时前
精通 Python 设计模式——分布式系统模式
python·设计模式·架构
数据智能老司机3 小时前
精通 Python 设计模式——并发与异步模式
python·设计模式·编程语言
数据智能老司机3 小时前
精通 Python 设计模式——测试模式
python·设计模式·架构
数据智能老司机3 小时前
精通 Python 设计模式——性能模式
python·设计模式·架构
c8i3 小时前
drf初步梳理
python·django
每日AI新事件3 小时前
python的异步函数
python
BingoGo3 小时前
PHP 如何利用 Opcache 来实现保护源码
后端·php
这里有鱼汤4 小时前
miniQMT下载历史行情数据太慢怎么办?一招提速10倍!
前端·python
databook13 小时前
Manim实现脉冲闪烁特效
后端·python·动效
程序设计实验室14 小时前
2025年了,在 Django 之外,Python Web 框架还能怎么选?
python