基础
- 客户端可以在服务端没有监听端口 (bind)时,直接创建连接,这会在服务端开始监听端口时自动连接并传输消息
- 已经创建过的连接,如果中途中断,会自动重连
- 一个服务端 socket 可以监听多个不同的地址,端口不同甚至协议不同
- socket 内部有 IO 线程,send 函数返回时,消息并不一定已经发出了,有可能还在队列里等待
- 每创建一个 context 对象都会隐式创建一个 IO 线程
- C 需要库中处理字符串时 不/不应该 发送空终止字符,所以需要在接收字符串数据后,手动追加空终止字符
- zmq_send/zmq_recv 和 zmq_msg_send/zmq_msg_recv,主要区别在于是否需要调用者提供 msg 结构体对象,前者不需要,会自动将数据封装到一个默认的 msg 对象中
- 使用 ZMQ_SNDMORE 参数发送多块消息体,当对端接收到数据时,其实肯定已经接收到了所有消息体,但仍旧需要调用多次 get_socketopt + zmq_recv 接口,读取出来所有消息体数据
- 使用 zmq_poll(理解参考 linux poll 或 python 协程 异步 IO)相关接口,可以实现同时处理多个 socket 的 send/recv
上下文 Context 对象
- 程序一般都以创建 context 开始
- 一个进程通常应该创建并使用一个 context 对象
- 也有创建多个 context 对象的场景,这相当于拥有多个独立的 zqm 实例,并且要注意销毁所有 context 对象
请求-响应 模式(REQ-REP)
- 服务端一般使用 响应 REP 类型的 socket
- 客户端一般使用 请求 REQ 类型的 socket
- REQ 必须遵守 send-recv 配对规则,先请求并且得到响应后,再发起下一个请求,打乱顺序会报错;REP 同样但它先 recv 再 send
- 理论上可以有 N 多个客户端连接到服务端,同时连接都没问题
- 这个模式和常见的 CS/BS 模式很类似
- 多个 REQ 连接到一个 REP 时,从逻辑上来看 REP 端的消息处理流程是同步的,必须要等到当前已接收消息处理完毕并回复对端后,才能去接收处理下一个 REQ 请求,其他 REQ 请求都在阻塞
- 一个 REQ 可以连接到多个 REP 对端,请求会被负载均衡得发送到多个 REP 端
- REQ 和 REP 在概念上可以认为是基于 Router-Dealer 实现的,内部自动处理了信封,上层应用不会看到信封(包括空帧),只会看到数据
- 当 REQ 直接与 REP 连接时,底层/内部使用的信封是最简单的格式,没有 地址标识帧,只有空帧+数据帧
- 当 REQ 和 REP 中间加入了 Router-Dealer 作为代理层时,REP 收到的数据包含了 地址标识帧 + 空帧 + 数据帧
- REP 会认为从第一帧开始直到空帧为止都是信封帧,所以当 REQ 直接与 REP 连接时即便没有地址帧,也需要有空帧,这是为了兼容 Router-Dealer 作为代理时的情况
- REP 在收到消息时,会保存此消息的信封,在回复消息时会自动在把信封加在数据帧前面;由于 REP 必须遵守 请求-响应 配对规则,所以在回复消息时肯定是知道需要使用的信封的
Router-Dealer
- 多用于在 REQ 和 REP 中间作为代理层: REQ(可以是多个) <--> Router(一个) <--> Deader(一个) <--> REP(可以是多个)
- 可以成对得出现多个 Router-Dealer 来实现多层代理
- 异步的,调用完 recv/send 接口之后不用必须调用反向的 send/recv 接口
- 需要显式处理信封,信封用于标识请求来源,同时回复消息时也需要用信封来标识回复到哪一个具体的请求端
- 信封可以嵌套,用于表示数据被多个 Router 转发
- 需要自己借助信封处理 路由规则(消息发送到哪些对端)
- Router 在收到消息时,会跟踪每一个 TCP 连接,并自动在消息前面追加 地址标识帧,以此告诉上层应用此数据的来源
- Router 在发送消息时,根据 地址标识帧 判断需要使用哪个 TCP 连接,发送消息给正确的目标,但真正发送消息前,会移除掉 地址标识帧
- Dealer 在逻辑上几乎不处理任何东西,只会接收和发送所有帧,不管是信封帧还是数据帧,不会擅自增加或删除任意帧,就像是一个异步的收发中转站
- Router 只关心 地址标识帧,它不知道也不关心后面的帧,包括空帧
- 当业务层直接使用 Router 时,可以自己处理 地址标识帧,换句话说,可以更灵活得控制消息的路由规则
- 当业务层直接使用 Dealer 与 REP 连接时,Deader 应该按照如下规则实现:Deader 发送消息时,必须先发送一个空帧(send-more),来模拟 REQ 的消息信封,否则 REP 会丢弃掉没有空帧的消息;在接收数据时,应该判断第一帧是否是空帧,是的话跳过并取得数据帧,不是的话则丢弃掉整个消息(包括数据帧)
- 类似的,当业务层直接使用 Dealer 与 REQ 连接时,Deader 应该按照如下规则实现:Deader 在接收到的消息时,应该判断第一帧是否是空帧,是的话跳过并取得数据帧,不是的话则丢弃掉整个消息(包括数据帧);在发送消息时,同样需要先发送空帧
- Router 一般会自动为一个新的连接生成标识,也就是 地址标识帧,但如果对端使用 setsockopt 明确设置了标识,Router 则不再自动生成
发布-订阅 模式(PUB-SUB)
- 没有订阅者的话,发布者会把所有消息都丢弃掉
- 订阅者可以订阅多个发布者,消息将会以轮次获取的方式公平得从每个发布者处获取
- 如果订阅者使用 TCP 创建连接,但处理消息比较慢,发布者会缓存一定量的消息
- 在不同版本和连接协议下,filtering 消息过滤这一操作可能在发布者一侧,也可能在订阅者一侧
- 一个消息会被所有订阅者获取到
- 订阅者必须要设置 SUBSCRIBE 否则收不到任何数据
- 订阅者可以设置多个 SUBSCRIBES,匹配到 任何一个 都会收到数据
- 消息的读写都是异步的
推送-拉取(PUSH-PULL)(分布式/并行处理模型/管道模式)
- 当出现 一对多 的情况时,数据会保证 公平队列(PULL) 和 负载均衡(PUSH)
- 一个消息只会被一个 PULL 端获取到