大家好,我是李慢慢。
自动驾驶仿真工程师,有很大一部分工作都是在对接信号。比如把仿真世界的信号发送给算法,也比如把规控算法输出的驾驶指令传递给仿真车辆。这其中信号的传递的是个很讲究的事情,延时性、同步性、实时性和这个信号传递的过程紧密相关。关于通过网络传输信号,此前我一直以为除了用TCP/UDP通信技术外别无他法,直到最近又新接触了一种信号传输方法,就是zmq。然后我就私下试验了下,感觉效果非常好,用起来也比TCP/UDP连接简单,也更自由。在这里简单记录下。
1、zmq简介
ZMQ看起来像是一个嵌入式网络连接库,但实际上是一个并发框架。框架提供的套接字可以满足在多种协议之间传输原子信息,如线程间、进程间、TCP、广播等。可以使用ZMQ构建多对多的连接方式,如扇出、发布-订阅、任务分发、请求-应答等。ZMQ的高速使得它能胜任分布式应用。它的异步I/O机制让你能够构建多核应用程序,完成异步消息处理任务。ZMQ有着多语言支持,并能在几乎所有的操作系统上运行。
2、zmq模块的下载及使用
python中要使用zmq模块,方法非常简单,只需要pip安装一下即可。
pip install pyzmq
安装完后,在命令行输入python回车,测试下能否import zmq成功即可。
接下来,针对zmq提供的三种模式进行信号收发测试。
3、请求-应答模式:Request-Reply
准备以下两段代码。
server.py
import zmq
context = zmq.Context()
socket = context.socket(zmq.REP)
socket.bind("tcp://*:5555")
while True:
message = socket.recv()
print("message got:", message.decode("utf-8"))
socket.send_string("server response!")
client.py
import zmq
context = zmq.Context()
socket = context.socket(zmq.REQ)
socket.connect("tcp://localhost:5555")
while(True):
data = input("input your data:")
if data == 'q':
break
else:
socket.send_string(data)
response = socket.recv()
print("response:", response.decode("utf-8"))
然后打开两个命令行终端,分别运行server.py和client.py,就能在客户端开始输入,与服务器开始聊天了。
这种模式下,不管是服务器端还是客户端,收和发都必须有,否则会报错。另外,如果服务器端断掉,则客户端也会无法继续发送,等服务器端恢复运行后,才能继续在客户端发送消息。
应用场合:
个人感觉,这种模式很像是应用到bilibili视频上的弹幕技术。每个客户端都可以发送弹幕到服务器上,服务器上收到只负责显示,并告诉客户端已经收到,这个时候客户端才能继续发送第二条。
4、发布-订阅模式:Publish-Subscribe
同样的,使用这个模式,最简单的,我们需要创建两个程序。
server.py
import zmq
context = zmq.Context()
socket = context.socket(zmq.PUB)
socket.bind("tcp://127.0.0.1:5000")
while True:
msg = input('input your data:')
if msg != "q":
socket.send_string(msg)
else:
break
client.py
import zmq
context = zmq.Context()
socket = context.socket(zmq.SUB)
socket.connect("tcp://127.0.0.1:5000")
socket.setsockopt_string(zmq.SUBSCRIBE,'')
while True:
messge_got_b = socket.recv()
messge_got = messge_got_b.decode("utf-8")
print("messge_got:", messge_got)
分别打开两个命令行,运行这两段程序,效果如下:
“发布-订阅”模式下,“发布者”绑定一个指定的地址,例如“192.168.10.1:5500”,“订阅者”连接到该地址。
该模式下消息流是单向的,只允许从“发布者”流向“订阅者”。
且“发布者”只管发消息,不理会是否存在“订阅者”。
一个“发布者”可以拥有多个订阅者。
同样的,一个“订阅者”也可订阅多个发布者。
应用场合:
这种发布-订阅模式,比较像现实中的收音机模式,不同的人(客户端)都从同一个频道(服务端)收听音乐的话,如果这个频道总部那边下线了,那么所有人都将收不到这个频道的消息;每一个新接入的客户端,都只能收听到频道当前正在发布的内容;当然每一个客户端都可以选择收听不同服务端的内容。
5、管道模式:Pull-Push
要启用该模式,要至少准备以下三个程序:
client.py
import zmq
context = zmq.Context()
socket = context.socket(zmq.PULL)
socket.bind('tcp://*:5558')
while True:
data = socket.recv()
print(data.decode("utf-8"))
work.py
import zmq
context = zmq.Context()
reciver = context.socket(zmq.PULL)
reciver.connect('tcp://127.0.0.1:5557')
sender = context.socket(zmq.PUSH)
sender.connect('tcp://127.0.0.1:5558')
while True:
data = reciver.recv()
sender.send(data)
server.py
import zmq
context = zmq.Context()
client_socket = context.socket(zmq.PUSH)
client_socket.bind('tcp://*:5557')
while True:
data = input('input your data:')
if data == "q":
break
else:
client_socket.send_string(data)
启动三个命令行终端,分别运行上述三个程序,效果如下。
通过测试发现,当客户端中途掉线退出后,服务端仍然在发送消息,等客户端重新接入,会重新一次性接收到刚刚漏掉的信息。这个大概就是多了个work端的好处吧,相当于多了个历史消息的缓存处。
应用场景:
感人感觉这种模式有点像微 信公 众号的订阅模式,你们(客户端)关注了车路慢慢这个公 众号(服务端),就可以不断的收到我发布的文章,不会遗漏。当然你们可以选择多关注几个公 众号,即多连接几个服务端。有了work这个中间缓存的东西,你们不会漏掉任何一个关注的公 众号的消息。
这个管道模式,我个人是很喜欢的,后面可以集成到openSIL项目中去,到时还会有更多的研究,欢迎持续关注。
这个周末,又水了一文。
瑞斯拜。