首页/文章/ 详情

浅析Python用zmq进行通信的3大模式?

1年前浏览699

大家好,我是李慢慢。

自动驾驶仿真工程师,有很大一部分工作都是在对接信号比如把仿真世界的信号发送给算法,也比如把规控算法输出的驾驶指令传递给仿真车辆。这其中信号的传递的是个很讲究的事情,延时性、同步性、实时性和这个信号传递的过程紧密相关。关于通过网络传输信号,此前我一直以为除了用TCP/UDP通信技术外别无他法,直到最近又新接触了一种信号传输方法,就是zmq。然后我就私下试验了下,感觉效果非常好,用起来也比TCP/UDP连接简单,也更自由。在这里简单记录下。


1、zmq简介

ZMQ看起来像是一个嵌入式网络连接库,但实际上是一个并发框架。框架提供的套接字可以满足在多种协议之间传输原子信息,如线程间、进程间、TCP、广播等。可以使用ZMQ构建多对多的连接方式,如扇出、发布-订阅、任务分发、请求-应答等。ZMQ的高速使得它能胜任分布式应用。它的异步I/O机制让你能够构建多核应用程序,完成异步消息处理任务。ZMQ有着多语言支持,并能在几乎所有的操作系统上运行。

官网:https://zeromq.org

2、zmq模块的下载及使用

python中要使用zmq模块,方法非常简单,只需要pip安装一下即可。

    pip install pyzmq

    安装完后,在命令行输入python回车,测试下能否import zmq成功即可。

    接下来,针对zmq提供的三种模式进行信号收发测试。

    3、请求-应答模式:Request-Reply

    说到“请求-应答”模式,不得不说的就是它的消息流动模型。消息流动模型指的是该模式下,必须严格遵守“一问一答”的方式。
    客户端发出消息后,若没有收到回复,再发出第二条消息时就会抛出异常。
    同样的,对于服务端也是,在没有接收到消息前,不允许发出消息。
    基于此构成“一问一答”的响应模式,模式如下所示:

    准备以下两段代码。

    server.py

      import zmqcontext = 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 zmqcontext = 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 zmqcontext = 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 zmqcontext = 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

            “管道模式”一般用于任务分发与结果收集,由一个任务发生器来产生任务,“公平”的派发到其管辖下的所有 worker,完成后再由结果收集器来回收任务的执行结果。
            整体流程比较好理解,worker 连接到任务发生器上,等待任务的产生,完成后将结果发送至结果收集器。如果要以客户端服务端的概念来区分,这里的任务发生器与结果收集器是服务端,而 worker 是客户端。
            前面说到了这里任务的派发是“公平的”,因为内部采用了 LRU 的算法来找到最近最久未工作的闲置 worker。但是公平在这里是相对的,当任务发生器启动后,第一个连接到它的 worker 会在一瞬间承受整个任务发生器产生的 tasks。
            总结来说由三部分组成,push 进行数据推送,work 进行数据缓存,pull 进行数据竞争获取处理。区别于 Publish-Subscribe 存在一个数据缓存和处理负载。
            当连接被断开,数据不会丢失,重连后数据继续发送到对端。

            要启用该模式,要至少准备以下三个程序:

            client.py

              import zmqcontext = zmq.Context()socket = context.socket(zmq.PULL)socket.bind('tcp://*:5558')while True:    data = socket.recv()    print(data.decode("utf-8"))

              work.py

                import zmqcontext = 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 zmqcontext = 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项目中去,到时还会有更多的研究,欢迎持续关注。

                  这个周末,又水了一文。

                  瑞斯拜。

                  来源:车路慢慢
                  python通信自动驾驶试验
                  著作权归作者所有,欢迎分享,未经许可,不得转载
                  首次发布时间:2023-06-22
                  最近编辑:1年前
                  李慢慢
                  硕士 自动驾驶仿真工程师一枚
                  获赞 11粉丝 63文章 122课程 0
                  点赞
                  收藏
                  未登录
                  还没有评论
                  课程
                  培训
                  服务
                  行家
                  VIP会员 学习 福利任务 兑换礼品
                  下载APP
                  联系我们
                  帮助与反馈