提问-应答
让我们从简单的代码开始,一段传统的Hello World程序。我们会创建一个客户端和一个服务端,客户端发送Hello给服务端,服务端返回World。
Server
#include <stdio.h> #include <zmq.h> #include <string.h> #include <unistd.h> #define buffersize 4096 int main(int argc, char* argv[]) { // [0]创建对象 void* ctx = zmq_ctx_new(); void* server = zmq_socket(ctx, ZMQ_REP); // [1]绑定到7766端口 zmq_bind(server, "tcp://*:7766"); char buffer[256] = { 0 }; printf("%s ", "server listen 7766 ...."); int i = 0; // [2]收发数据,先收再发 while (1) { memset(buffer, 0, 256); zmq_recv(server, buffer, 256, 0); printf("recv: %s ", buffer); sleep(2); char szMsg[1024] = {0}; snprintf(szMsg, sizeof(szMsg), " ack : %3d ", i++); zmq_send(server, szMsg, strlen(szMsg), 0); printf("send: %s ", szMsg); } zmq_close(server); zmq_ctx_destroy(ctx); return 0; }
Client:
#include <zmq.h> #include "stdio.h" #include <string.h> #include <unistd.h> int main(int argc, char * argv[]) { // [0]创建对象 void* ctx = zmq_ctx_new(); void* server = zmq_socket(ctx, ZMQ_REQ); // [1]连接7766端口 zmq_connect(server, "tcp://localhost:7766"); char buffer[256] = { 0 }; printf("%s ", "client connect 7766 ..."); int i = 0; // [2]先发数据再收 while (1) { char szMsg[1024] = {0}; snprintf(szMsg, sizeof(szMsg), "hello world : %3d", i++); printf("send: %s ", szMsg); if (zmq_send(server, szMsg, strlen(szMsg), 0) < 0) { fprintf(stderr, "send message faild "); } sleep(2); memset(buffer, 0, 256); zmq_recv(server, buffer, 256, 0); printf("recv: %s ", buffer); } zmq_close(server); zmq_ctx_destroy(ctx); return 0; }
使用REQ-REP套接字发送和接受消息是需要遵循一定规律的。客户端首先使用zmq_send()发送消息,再用zmq_recv()接收,如此循环。如果打乱了这个顺序(如连续发送两次)则会报错。类似地,服务端必须先进行接收,后进行发送。
我们看看运行结果:
我们首先运行client再运行server:
zf@eappsvr-0:~/ds/zmq/test/req_rep> ./send client connect 7766 ... send: hello world : 0 recv: ack : 0 zf@eappsvr-0:~/ds/zmq/test/req_rep> ./recv server listen 7766 .... recv: hello world : 0 send: ack : 0
我们可以看到即使客户端先运行,也可以正常工作的,这就是zmq神奇的地方。
让我简单介绍一下这两段程序到底做了什么。首先,他们创建了一个ZMQ上下文,然后是一个套接字。不要被这些陌生的名词吓到,后面我们都会讲到。服务端将REP套接字绑定到5555端口上,并开始等待请求,发出应答,如此循环。客户端则是发送请求并等待服务端的应答。
这些代码背后其实发生了很多很多事情,但是程序员完全不必理会这些,只要知道这些代码短小精悍,极少出错,耐高压。这种通信模式我们称之为请求-应答模式,是ZMQ最直接的一种应用。
下面我们试着在客户端连续发送两次:
// [2]先发数据再收 while (1) { char szMsg[1024] = {0}; snprintf(szMsg, sizeof(szMsg), "hello world : %3d", i++); printf("send: %s ", szMsg); if (zmq_send(server, szMsg, strlen(szMsg), 0) < 0) { fprintf(stderr, "send message faild "); } if (zmq_send(server, szMsg, strlen(szMsg), 0) < 0) { fprintf(stderr, "send message faild "); } sleep(2); memset(buffer, 0, 256); zmq_recv(server, buffer, 256, 0); printf("recv: %s ", buffer); }
// client client connect 7766 ... send: hello world : 0 send message faild recv: ack : 0 // server server listen 7766 .... recv: hello world : 0 send: ack : 0
可以看到,第二次发送会失败,提问应答模式,严格控制着发送时序,必须发送-接收-发送-接收,打乱的话,会失败。
关于字符串
你可能注意到了我们上面的例子里, 其实客户端与服务端互相传输的数据里, 并没有包含C风格字符串最后一位的' '.strlen并不会计算结尾的‘ ’。
我们修改一下服务端buffer的初始值5:
// [2]收发数据,先收再发 while (1) { memset(buffer, 5, 256); zmq_recv(server, buffer, 256, 0); printf("recv: %s ", buffer); sleep(2); char szMsg[1024] = {0}; snprintf(szMsg, sizeof(szMsg), " ack : %3d ", i++); zmq_send(server, szMsg, strlen(szMsg), 0); printf("send: %s ", szMsg); }
server listen 7766 .... recv: hello world : 0 XshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellXshellsend: ack : 0
我们可以看到,接收数据就不是我们预想中的值,因为初始值为5,且接收的数据末尾非0,解析字符串就会出错。
对于zmq来说,数据都是字节序列而已, 如何解释这些字节序列, 是使用者的责任. 比如上面, 我们需要在每次接收数据的时候记录接收的数据的大小, 并且在buffer中为接收到的数据之后的一个字节赋值为0, 即人为的把接收到的数据解释为字符串。