ZMQ Getting Started


前言

ZMQ(ØMQ、ZeroMQ, 0MQ)看起来像是一套嵌入式的网络链接库,但工作起来更像是一个并发式的框架。它提供的套接字可以在多种协议中传输消息,如线程间、进程间、TCP、广播等。你可以使用套接字构建多对多的连接模式,如扇出、发布-订阅、任务分发、请求-应答等。ZMQ的快速足以胜任集群应用产品。它的异步I/O机制让你能够构建多核应用程序,完成异步消息处理任务。ZMQ有着多语言支持,并能在几乎所有的操作系统上运行。ZMQ是iMatix公司的产品,以LGPL开源协议发布。

zmq本体是用C++实现的,提供C语言头文件。https://github.com/zeromq/libzmq
网站https://zeromq.org/get-started/的其它实现是对zmq接口的封装,需要依赖zmq库。


一、提问-回答 模式

client通过REQ socket发送Hello,server通过REP socket接收Hello并返回World
在这里插入图片描述
此例中,bind放在了server端,connect在client端,在zmq中其位置可以互换。server和client程序启动顺序无关,client先启动后会阻塞等待server启动。

  1. 一个进程只需要一个zmq context就可以了
  2. 一个zmq socket可多次bind
  3. zmq socket有严格的配对关系,只有特定类型的socket之间才可通讯 https://libzmq.readthedocs.io/en/latest/zmq_socket.html
  4. bind 和 connect是对等关系,位置可以互换
//  Hello World server
#include <zmq.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>

int main (void)
{
    //  Socket to talk to clients
    void *context = zmq_ctx_new ();
    void *responder = zmq_socket (context, ZMQ_REP);
    int rc = zmq_bind (responder, "tcp://*:5555");
    assert (rc == 0);
    // 同一个socket可多次bind
    rc = zmq_bind(responder, "tcp://*:60000");
    assert(rc == 0);
    rc = zmq_bind(responder, "ipc://socket_type");
    assert(rc == 0);

    while (1) {
        char buffer [10];
        zmq_recv (responder, buffer, 10, 0);
        printf ("Received Hello\n");
        sleep (1);          //  Do some 'work'
        zmq_send (responder, "World", 5, 0);
    }
    zmq_close(responder);
    zmq_ctx_destroy(context);
    return 0;
}
//  Hello World client
#include <zmq.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>

int main (void)
{
    printf ("Connecting to hello world server...\n");
    void *context = zmq_ctx_new ();
    void *requester = zmq_socket (context, ZMQ_REQ);
    zmq_connect (requester, "tcp://localhost:5555");

    int request_nbr;
    for (request_nbr = 0; request_nbr != 10; request_nbr++) {
        char buffer [10];
        printf ("Sending Hello %d...\n", request_nbr);
        zmq_send (requester, "Hello", 5, 0);
        zmq_recv (requester, buffer, 10, 0);
        printf ("Received World %d\n", request_nbr);
    }
    zmq_close (requester);
    zmq_ctx_destroy (context);
    return 0;
}

client也可connect多次,具体内部实现不详

二、发布-订阅 模式

如下图,ZMQ 的 PUB-SUB是一对多的模式:
在这里插入图片描述

  1. bind一个发送方,多个接收方可接到相同的消息。即便没有sub,pub也一直再发。
  2. 可通过zmq_setsockopt设置多个filter,但如果不设置zmq_setsockopt则收不到任何数据。如果filter=“”,则接收所有数据
  3. ZMQ_SUB通过filter匹配接收信息的前缀
  4. ZMQ_PUB只能zmq_send; ZMQ_SUB只能zmq_recv
//  Weather update server
//  Binds PUB socket to tcp://*:5556
//  Publishes random weather updates
#include "zmq.h"
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <assert.h>
#include <time.h>
#include <stdlib.h>

#define randof(num) (int)((float)(num)*random()/(RAND_MAX+1.0))

int main (void)
{
    //  Prepare our context and publisher
    void *context = zmq_ctx_new ();
    void *publisher = zmq_socket (context, ZMQ_PUB);
    int rc = zmq_bind (publisher, "tcp://*:5556");
    assert (rc == 0);

    //  Initialize random number generator
    srandom ((unsigned) time (NULL));
    while (1) {
        //  Get values that will fool the boss
        int zipcode, temperature, relhumidity;
        zipcode     = randof (100000);
        temperature = randof (215) - 80;
        relhumidity = randof (50) + 10;

        //  Send message to all subscribers
        char update [20];
        sprintf (update, "%05d %d %d", zipcode, temperature, relhumidity);
        zmq_send(publisher, update, sizeof(update), 0);
    }
    zmq_close (publisher);
    zmq_ctx_destroy (context);
    return 0;
}
//  Weather update client
//  Connects SUB socket to tcp://localhost:5556
//  Collects weather updates and finds avg temp in zipcode

#include "zmq.h"
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <assert.h>
#include <time.h>
#include <stdlib.h>
int main (int argc, char *argv [])
{
    //  Socket to talk to server
    printf ("Collecting updates from weather server...\n");
    void *context = zmq_ctx_new ();
    void *subscriber = zmq_socket (context, ZMQ_SUB);
    int rc = zmq_connect (subscriber, "tcp://localhost:5556");
    assert (rc == 0);

    //  Subscribe to zipcode, default is NYC, 10001
    const char *filter = (argc > 1)? argv [1]: "10001 ";
    rc = zmq_setsockopt (subscriber, ZMQ_SUBSCRIBE,
                         filter, strlen (filter));
    assert (rc == 0);

    //  Process 100 updates
    int update_nbr;
    long total_temp = 0;
    char *string = (char*)malloc(BUFSIZ);
    for (update_nbr = 0; update_nbr < 100; update_nbr++) {
        int rx_size = zmq_recv(subscriber, string, BUFSIZ, 0);
        assert(rx_size >= 0);
        string[rx_size] = '\0';
        int zipcode, temperature, relhumidity;
        sscanf (string, "%d %d %d",
            &zipcode, &temperature, &relhumidity);
        total_temp += temperature;
    }
    printf ("Average temperature for zipcode '%s' was %dF\n",
        filter, (int) (total_temp / update_nbr));

    zmq_close (subscriber);
    zmq_ctx_destroy (context);
    return 0;
}

三、Divide and Conquer

  • ventilator产生可被并行处理的任务
  • 一系列的worker可以用于处理任务
  • sink收集worker的结果
    在这里插入图片描述
    现实中workers运行在超快的设备上,也许是在GPUs上解数学运算。

关于zmq context

  1. 一个进程只需有一个zmq context实例即可,如果有两个则会分离zmq实例。技术上说它是个容器。
  2. 如果使用fork,在子进程的开头调用一次zmq_ctx_new()。
  3. 使用zmq_send/recv可避免 直接使用zmq_msg_t 对象
  4. 使用zmq_msg_recv就需要zmq_msgclose
  5. zmq_ctx_new; zmq_socket; zmq_close; zmq_ctx_destroy;
  6. First, do not try to use the same socket from multiple threads. Please don’t explain why you think this would be excellent fun, just please don’t do it.

基础用法

利用zmq_msg_t,通过zmq_msg_recv接收并获取、处理数据,最后通过zmq_send发送

        zmq_msg_t message;
        zmq_msg_init(&message);
        int size = zmq_msg_recv(&message, response, 0);
        char* str = (char*)malloc(size + 100);
        memcpy(str, zmq_msg_data(&message), size);
        static int num = 0;
        snprintf(str+size, 100, " servid: %d, response: %d", pid, num++);
        zmq_send(response, str, strlen(str), 0);
        free(str);
        zmq_msg_close(&message);

ref

If you copy/paste a lot of code, you’re going to copy/paste errors, too.

https://github.com/zeromq/libzmq

ZGUIDE: https://zguide.zeromq.org/docs/chapter1/

https://wizardforcel.gitbooks.io/zmq-guide/content/chapter1.html

API: https://libzmq.readthedocs.io/en/latest/

import time from typing import Any, Optional import numpy as np import zmq from agent.schema import RobotObsShape from dataclasses import dataclass, field class LowLevelRobotConnectionConfig: host: str = field(default="localhost") port: str = field(default="15558") timeout: int = field(default=1000) max_retries: int = field(default=5) delay: float = field(default=0.5) controller: Optional[Any] = field(default_factory=lambda: None) DOC = ''' controller 需要实现 get_obs 和 act 方法。例如: class Controller: def __init__(self, rgb, right_cam_img, right_state): self.rgb = rgb self.right_cam_img = right_cam_img self.right_state = right_state def get_obs(self) -> dict: """ { "rgb" np.ndarray (480, 640, 3), np.uint8 "right_cam_img" np.ndarray (360, 480, 3), np.uint8 "right_state" np.ndarray (7,), np.uint8 } Returns: _type_: _description_ """ return { "rgb": self.rgb, "right_cam_img": self.right_cam_img, "right_state": self.right_state, } def act(self, actions: np.ndarray) -> None: print(f"Executing...") print(f"Executed action: {actions}") ''' class LowLevelRobotConnection: def __init__(self, config): self.host = config.host self.port = config.port self.addr = f"tcp://{self.host}:{self.port}" # self.timeout = config.timeout self.max_retries = config.max_retries self.delay = config.delay self.is_connected = False self.controller = config.controller def _connect(self): print(f"INFO: Robot service connected to server at port {self.port}") self.context = zmq.Context() # 新建上下文 self.socket = self.context.socket(zmq.REQ) # 新建套接字 self.socket.connect(self.addr) self.is_connected = True def _close(self): # if self.context is not None: # self.context.term() print("INFO: context terminated") if self.socket is not None: self.socket.close() print("INFO: socket closed") self.is_connected = False def send_obs(self, obs: bytes) -> bool: fired = False for _ in range(self.max_retries): try: if not self.is_connected: self._connect() fired = True print("INFO: send observation") self.socket.send(obs) break except zmq.Again: print("ERROR: Timeout") self._close() if not fired: print("ERROR: Failed to fire observation to server") self._close() return False print("INFO: observation fired") return True def get_actions(self) -> Optional[np.ndarray]: for _ in range(self.max_retries): try: if not self.is_connected: self._connect() print("INFO: send action request") message = self.socket.recv(copy=False) print(f"INFO: recerved msg size: {len(message)}") if len(message) != RobotObsShape.TOTAL_ACTIONS_SIZE: print( f"ERROR: Invalid message size as {len(message)}, required {RobotObsShape.TOTAL_ACTIONS_SIZE} bytes" ) continue actions = np.frombuffer(message.buffer, dtype=np.float32).reshape( (RobotObsShape.ACTIONS_SHAPE) ) print("INFO: received action") break except zmq.Again: print("ERROR: Timeout") self._close() if actions is None: print("ERROR: Failed to retrieve action from server") return None return actions def run(self) -> None: while True: user_input = input("Press <Enter> to start, <q> to quit.") obs = self.get_obs() if not self.send_obs(obs): print("ERROR: Failed to send observation") continue actions = self.get_actions() if actions is None: print("ERROR: Failed to retrieve action from server") continue self.act(actions) time.sleep(self.delay) def step(self) -> None: obs = self.get_obs() if obs is None: print("ERROR: Failed to retrieve image") return if not self.send_obs(obs): print("ERROR: Failed to send observation") return actions = self.get_actions() if actions is None: print("ERROR: Failed to retrieve action from server") return self.act(actions) def get_obs(self) -> bytes: """获取观察的内容。头部、腕部图像、关节角度和灵巧手状态。 Returns: bytes: 字节流 """ obs = self.controller.get_obs() # head_image = np.zeros(RobotObsShape.HEAD_IMAGE_SHAPE, dtype=np.uint8) # wrist_image = np.zeros(RobotObsShape.WRIST_IMAGE_SHAPE, dtype=np.uint8) # state = np.zeros(RobotObsShape.STATE_SHAPE, dtype=np.float32) obs = ( obs["rgb"].tobytes() + obs["right_cam_img"].tobytes() + obs["right_state"].tobytes() ) return obs def act(self, actions: np.ndarray) -> None: """执行六个策略预测的动作。 Args: actions (np.ndarray): 形状为 (6, 7) """ assert actions.shape == RobotObsShape.ACTIONS_SHAPE, ( f"Expected actions shape {RobotObsShape.ACTIONS_SHAPE}, got {actions.shape}" ) print(f"INFO: actions: {actions}") if __name__ == "__main__": class Controller: def get_obs(self) -> dict: return { "rgb": np.zeros(RobotObsShape.HEAD_IMAGE_SHAPE, dtype=np.uint8), "right_cam_img": np.zeros( RobotObsShape.WRIST_IMAGE_SHAPE, dtype=np.uint8 ), "right_state": np.zeros(RobotObsShape.STATE_SHAPE, dtype=np.float32), } config = dict( host="wujingdp.xyz", port=15558, timeout=2, max_retries=5, delay=0.5, controller=Controller(), ) r = LowLevelRobotConnection(config) r.run() 全文注释
07-18
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值