在上面的掮客代码示例中, 核心代码就是zmq_poll对两个socket的监听, 以及while(1)循环. ZMQ将这两坨操作统一封装到了一个函数中, 省得大家每次都要写boring code.
int zmq_proxy (const void *frontend, const void *backend, const void *capture);参数frontend与backend分别是与客户端相连的socket和与服务端相连的socket. 在使用zmq_proxy函数之前, 这两个socket必须被正确配置好, 该调用connect就调用connect, 该调用bind就调用bind. 简单来讲, zmq_proxy负责把frontend与backend之间的数据互相递送给对方. 而如果仅仅是单纯的递送的话, 第三个参数capture就应当被置为NULL, 而如果还想监听一下数据, 那么就再创建一个socket, 并将其值传递给capture, 这样, frontend与backend之间的数据都会有一份拷贝被送到capture上的socket.
当我们用zmq_proxy重写上面的掮客代码的话, 代码会非常简洁, 会变成这样:
#include <zmq.h> #include "zmq_helper.h" int main(void) { void * context = zmq_ctx_new(); void * socket_for_client = zmq_socket(context, ZMQ_ROUTER); void * socket_for_server = zmq_socket(context, ZMQ_DEALER); zmq_bind(socket_for_client, "tcp://*:5559"); zmq_bind(socket_for_server, "tcp://*:5560"); zmq_proxy(socket_for_client, socket_for_server, NULL); zmq_close(socket_for_client); zmq_close(socket_for_server); zmq_ctx_destroy(context); return 0; } 桥接技巧桥接是服务器后端的一种常用技巧. 所谓的桥接有点类似于掮客, 但是解决问题的侧重点不一样. 掮客主要解决了三个问题:
降低网络连接数量. 从N*M降低到 (N+M)*X
向客户端与服务端屏蔽彼此的具体实现, 隐藏彼此的具体细节.
缓冲
而桥接解决的问题的侧重点主要在:
向通信的一方, 屏蔽另一方的具体实现.
这种设计思路常用于后台服务的接口层. 接口层一方面连接着后端内部局域网, 另外一方面对公提供服务. 这种服务可以是请求-回应式的服务, 也可以是发布-订阅式的服务(显然发布方在后端内部的局域网里). 这个时候接口层其实就完成了桥接的工作.
其实这种应用场景里, 把这种技巧称为桥接并不是很合适. 因为桥接是一个计算机网络中硬件层的术语, 最初是用于线缆过长信号衰减时, 在线缆末端再加一个信号放大器之类的设备, 为通信续命用的.
原版ZMQ文档在这里提出bridging这个术语, 也只是为了说明一下, zmq_proxy的适用场景不仅局限于做掮客, 而是应该在理解上更宽泛一点, zmq_proxy函数就是互相传递两个socket之间数据函数, 仅此而已, 而具体这个函数能应用在什么样的场景下, 掮客与桥接场景均可以使用, 但绝不局限于此. 写代码思维要活.
妥善的处理错误ZMQ库对待错误, 或者叫异常, 的设计哲学是: 见光死. 前文中写的多数示例代码, 都没有认真的检查ZMQ库函数调用的返回值, 也没有关心它们执行失败后会发生什么. 一般情况下, 这些函数都能正常工作, 但凡事总有个万一, 万一创建socket失败了, 万一bind或connect调用失败了, 会发生什么?
按照见光死的字面意思: 按我们上面写代码的风格, 一旦出错, 程序就挂掉退出了.
所以正确使用ZMQ库的姿势是: 生产环境运行的代码, 务必为每一个ZMQ库函数的调用检查返回值, 考虑调用失败的情况. ZMQ库函数的设计也继续了POSIX接口风格里的一些设计, 这些设计包括:
创建对象的接口, 在失败时一般返回NULL
处理数据的接口, 正常情况下将返回处理的数据的字节数. 失败情况下将返回-1
其它一般性的函数, 成功时返回0, 失败时返回-1
当调用失败发生时, 具体的错误码存放在errno中, 或zmq_errno()中
有关错误的详情描述信息, 通过zmq_strerror()可能获得
真正健壮的代码, 应该像下面这样写, 是的, 它很啰嗦, 但它很健壮:
// ... void * context = zmq_ctx_new(); assert(context); void * socket = zmq_socket(context, ZMQ_REP); assert(socket); int rc = zmq_bind(socket, "tcp://*:5555"); if(rc == -1) { printf("E: bind failed: %s\n", strerror(errno)); return -1; } // ...有两个比较例外的情况需要你注意一下:
处理ZMQ_DONTWAIT的函数返回-1时, 一般情况下不是一个致命错误, 不应当导致程序退出. 比如在收包函数里带上这个标志, 那么语义只是说"没数据可收", 是的, 收包函数会返回-1, 并且会置error值为EAGAIN, 但这并不代表程序发生了不可逆转的错误.
当一个线程调用zmq_ctx_destroy()时, 如果此时有其它线程在忙, 比如在写数据或者收数据什么的, 那么这会直接导致这些在干活的线程, 调用的这些阻塞式接口函数返回-1, 并且errno被置为ETERM. 这种情况在实际编码过程中不应当出现.