但是, 对于同一种通讯协议里的同一个endpoint, 你只能对其执行一次zmq_bind()操作. 这里有个例外, 就是ipc进程间通信. 逻辑上允许另外一个进程去使用之前一个进程已经使用过的ipc endpoint, 但不要滥用这特性: 这只是ZMQ提供给程序崩溃后恢复现场的一种手段, 在正常的代码逻辑中, 不要做这样的事情.
所以看到这里你大概能理解zmq对bind和connect这两个概念的态度: ZMQ努力的将这两个概念之间的差异抹平, 但很遗憾, zmq并没有将这两个操作抽象成一个类似于touch的操作. 但还是请谨记, 在你的网络拓扑中, 让不易变结点去使用zmq_bind(), 让易变结点去使用zmq_connect
zmq socket是分类型的, 不同类型的socket提供了差异化的服务, socket的类型与结点在拓扑中的角色有关, 也影响着消息的出入, 以及缓存策略. 不同类型的socket之间, 有些可以互相连接, 但有些并不能, 这些规则, 以及如何在套路中为各个结点安排合适类型的socket, 都是后续我们将要讲到的内容.
如果从网络通讯的角度来讲, zmq是一个将传统传输层封装起来的网络库. 但从数据传输, 消息传输, 以及消息缓存这个角度来讲, zmq似乎又称得上是一个消息队列库. 总之, zmq是一个优秀的库, 优秀不是指它的实现, 它的性能, 而是它能解决的问题, 它的设计思路.
收发消息在第一章里, 我们接触到了两个有关消息收发的函数, zmq_send()和zmq_recv(), 现在, 我们需要把术语规范一下.
zmq_send()与zmq_recv()是用来传输"数据"的接口. 而"消息"这个术语, 在zmq中有指定含义, 传递消息的接口是zmq_msg_send()与zmq_msg_recv()
当我们说起"数据"的时候, 我们指的是二进制串. 当我们说"消息"的时候, 指提是zmq中的一种特定结构体.
需要额外注意的是, 无论是调用zmq_send()还是zmq_msg_send(), 当调用返回时, 消息并没有真正被发送出去, 更没有被对方收到. 调用返回只代表zmq将你要发送的"消息"或"数据"放进了一个叫"发送缓冲区"的地方. 这是zmq实现收发异步且带缓冲队列的一个设计.
单播传输ZMQ底层封装了三种单播通讯协议, 分别是: 共享内存实现的线程间通讯(inproc), 进程间通信(ipc), 以及TCP/IP协议栈里的TCP协议(tcp). 另外ZMQ底层还封装了两种广播协议: PGM, EPGM. 多播我们在非常后面的章节才会介绍到, 在你了解它之前, 请不要使用多播协议, 即便你是在做一些类似于发布-订阅套路的东西.
对于多数场景来说, 底层协议选用tcp都是没什么问题的. 需要注意的是, zmq中的tcp, 被称为 "无连接的tcp协议", 而之所以起这么一个精神分裂的名字, 是因为zmq允许在对端不存在的情况下, 结点去zmq_connect(). 你大致可以想象zmq做了多少额外工作, 但这些对于你来说, 对于上层应用程序来说, 是透明了, 你不必去关心具体实现.
IPC通讯类似于tcp, 也是"无连接"的, 目前, 这种方式不能在windows上使用, 很遗憾. 并且, 按照惯例, 在使用ipc作为通讯方式时, 我们一般给endpoint加上一个.ipc的后缀. 另外, 在Unix操作系统上, 使用ipc连接还请格外的注意不同进程的权限问题, 特别是从属于两个不同用户的进程.
最后来说一下inproc, 也就是线程间通信, 它只能用于同一进程内的不同线程通讯. 比起tcp和ipc, 这种通讯方式快的飞起. 它与tcp和ipc最大的区别是: 在有客户端调用connect之前, 必须确保已经有一个服务端在对应的endpoint上调用了bind, 这个缺陷可能会在未来的某个版本被修正, 但就目前来讲, 请务必小心注意.
ZMQ对底层封装的通讯协议是有侵入性的很遗憾的是, ZMQ对于其底层封装的网络协议是有侵入性的, 换句话说, 你没法使用ZMQ去实现一个HTTP服务器. HTTP作为一个五层协议, 使用TCP作为传输层协议, 对TCP里的报文格式是有规约限制的, 而ZMQ作为一个封装了TCP的4.5层协议, 其在数据交互时, 已经侵入了TCP的报文格式. 你无法让TCP里的报文既满足HTTP的格式要求, 还满足ZMQ的格式要求.
关心ZMQ到底是如何侵入它封装的通讯协议的, 这个在第三章, 当我们接触到ZMQ_ROUTER_RAW这种socket配置项的时候才会深入讨论, 目前你只需要明白, ZMQ对其底层封装的通讯协议有侵入.
这意味着, 你无法无损的将ZMQ引入到一些现成的项目中. 这很遗憾.
I/O线程我们先前提到过, ZMQ在后台使用独立的线程来实现异步I/O处理. 一般情况下吧, 一个I/O线程就应该足以处理当前进程的所有socket的I/O作业, 但是这个凡事总有个极限情况, 所以总会存在一些很荀的场景, 你需要多开几个I/O线程.
当你创建一个context的时候, ZMQ就在背后创建了一个I/O处理线程. 如果这么一个I/O线程不能满足你的需求, 那么就需要在创建context的时候加一些料, 让ZMQ多创建几个I/O处理线程. 一般有一个简单估算I/O线程数量的方法: 每秒你的程序有几个G字节的吞吐量, 你就开几个I/O线程.