ZeroMQ指南-第1章-基础(2) 标签: zeromqzeroMQZeroMQZMQzmq 2013-02-17 23:24 3117人阅读
提出假定
我们假定你使用的至少是3.2发行版ØMQ。我们假定你在使用Linux box或类似的东西。我们假定你能读C代码,这是几乎全部示例的默认语言。我们假定当我们写下常量PUSH或SUBSCRIBE时你能够想象到它们实际名称是ZMQ_PUSH或ZMQ_SUBSCRIBE,当编程语言需要时。
获取示例
示例代码位于一个公共GitHub版本库中。获取全部示例的最简单方法是克隆这个版本库:
git clone --depth=1 git://github.com/imatix/zguide.git
然后浏览examples子目录。你会找到各种语言的示例。如果没有你使用的语言,我们鼓励你提交一份翻译。如此,这篇文章才变得这么有用,感谢许多人的努力。所有示例遵循MIT/X11版权。
求则必得
那么我们开始看代码。我们理所当然从HelloWorld示例开始。做一个客户端和服务器。客户端发送“Hello”到服务器,服务器回答“World”。下面是C语言的服务器,打开了一个ØMQ套接字端口5555,读取其中请求,并应答“World”到每个请求:
// // Hello World server // Binds REP socket to tcp://*:5555 // Expects "Hello" from client, replies with "World" // #include <zmq.h> #include <stdio.h> #include <unistd.h> #include <string.h> int main (void) { void *context = zmq_ctx_new (); // Socket to talk to clients void *responder = zmq_socket (context, ZMQ_REP); zmq_bind (responder, "tcp://*:5555"); while (1) { // Wait for next request from client zmq_msg_t request; zmq_msg_init (&request); zmq_msg_recv (&request, responder, 0); printf ("Received Hello\n"); zmq_msg_close (&request); // Do some 'work' sleep (1); // Send reply back to client zmq_msg_t reply; zmq_msg_init_size (&reply, 5); memcpy (zmq_msg_data (&reply), "World", 5); zmq_msg_send (&reply, responder, 0); zmq_msg_close (&reply); } // We never get here but if we did, this would be how we end zmq_close (responder); zmq_ctx_destroy (context); return 0; }
hwserver.c:Hello World server
图2 - 请求-应答
请求-应答套接字是一前一后紧密相连的。客户端先发出zmq_msg_send()然后是zmq_msg_recv(),依次循环(或单次如果必要)。其它任何顺序(例如一行发送两个消息)都将导致send或recv调用返回-1代码。同样的,服务端依次先发出zmq_msg_recv()然后是zmq_msg_send(),频率则因其所需。
ØMQ使用C作为参考语言,同时也是我们在示例中使用的主要语言。如果你在线浏览,示例下方的链接可带你到其它编程语言版本。让我们比较一下服务器代码的C++版本。
// // Hello World server in C++ // Binds REP socket to tcp://*:5555 // Expects "Hello" from client, replies with "World" // #include <zmq.hpp> #include <string> #include <iostream> #include <unistd.h> int main () { // Prepare our context and socket zmq::context_t context (1); zmq::socket_t socket (context, ZMQ_REP); socket.bind ("tcp://*:5555"); while (true) { zmq::message_t request; // Wait for next request from client socket.recv (&request); std::cout << "Received Hello" << std::endl; // Do some 'work' sleep (1); // Send reply back to client zmq::message_t reply (5); memcpy ((void *) reply.data (), "World", 5); socket.send (reply); } return 0; }
hwserver.cpp:Hello World server
你能看到C和C++版的ØMQ API非常类似。某种语言如PHP甚至可以隐藏更多细节,让代码更加容易阅读。
<? php /* * Hello World server * Binds REP socket to tcp://*:5555 * Expects "Hello" from client, replies with "World" * @author Ian Barber <ian(dot)barber(at)gmail(dot)com> */ $context = new ZMQContext(1); // Socket to talk to clients $responder = new ZMQSocket($context, ZMQ::SOCKET_REP); $responder->bind("tcp://*:5555"); while (true) { // Wait for next request from client $request = $responder->recv(); printf ("Received request: [%s]\n", $request); // Do some 'work' sleep (1); // Send reply back to client $responder->send("World"); }
hwserver.php:Hello World server
这是客户端代码:
hwclient: Hello World client in C
// // Hello World client // Connects REQ socket to tcp://localhost:5555 // Sends "Hello" to server, expects "World" back // #include <zmq.h> #include <string.h> #include <stdio.h> #include <unistd.h> int main (void) { void *context = zmq_ctx_new (); // Socket to talk to server printf ("Connecting to hello world server…\n"); 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++) { zmq_msg_t request; zmq_msg_init_size (&request, 5); memcpy (zmq_msg_data (&request), "Hello", 5); printf ("Sending Hello %d…\n", request_nbr); zmq_msg_send (&request, requester, 0); zmq_msg_close (&request); zmq_msg_t reply; zmq_msg_init (&reply); zmq_msg_recv (&reply, requester, 0); printf ("Received World %d\n", request_nbr); zmq_msg_close (&reply); } sleep (2); zmq_close (requester); zmq_ctx_destroy (context); return 0; }
现在这看起来简单的有些不现实,但就像我们已知的,ØMQ套接字拥有超能力。你可以往这个服务器里扔成千上万的客户端,都在同一时间,而它将愉快而迅速的持续工作。好玩的是,可以试试先启动客户端,再启动服务器,看看怎么会仍然有效,想想看这意味着什么。
让我们简要地说明这两个程序实际上做了什么。他们创建一个工作用的ØMQ上下文和一个套接字。别担心这些词什么意思。你会再把它捡起来的。服务器绑定其应答套接字到端口5555。服务器循环等待请求,每个请求都答复一个响应。客户端则发送一个请求并从服务器读取返回的答复。
如果你终止了服务器(Ctrl-C)并将它重启,客户端不会恢复正常。从崩溃的进程中恢复并不是那么容易。做一个可靠的请求-应答流程是复杂的,以至于我们直到第4章——可靠的请求-应答模式才会触及它。
幕后其实发生了很多事情,不过对我们程序员来说更重要的是代码有多精炼而优美,即便是处于沉重负载的情况下,能坚持多长时间不崩溃。这就是请求-应答模式,可能是使用ØMQ的最简单方式。它映射到远程过程调用(RPC)和经典的客户端-服务器模型。
关于字符串的小提醒
除了字节大小,ØMQ并不知道你发送了什么数据。这意味着你有责任将它格式化为安全格式好让应用程序可以再次读取。这对于对象和复合数据类型来说是属于专业函数库的工作,例如Protocol Buffers。但是对字符串你需要多加小心。
在C语言和一些其它语言中,字符串是以null字符结尾的。我们可以发送一个带有额外Null字符的字符串“HELLO”:
zmq_msg_init_data (&request, "Hello",6, NULL, NULL);
但是如果用其它语言发送字符串可能不会包含这个Null字符。举例来说,当使用Python来发送同样的字符串时,我们这么做:
socket.send ("Hello")
那么走到线路上的是一个长度(短字串是一个字节)和一个个字符组成的字符串内容。
图 3 - 一个ØMQ字符串
如果在C程序中读取,你得到的会是一个看起来像是字符串,或许还意外的用起来也是一个字符串(如果碰巧这五个字符发现它们后面跟着一个无辜潜伏着的null),但却不正常的字符串。当客户端和服务器对字符串格式不能取得一致意见时,你会得到怪异的结果。
在C语言中,当你从ØMQ接收到字符串数据时,你无法信任它是安全结尾的。每次读取字符串你都需要分配一个新的多带一个字节的缓冲,复制这个字符串,然后用一个null正确结尾。
那么让我们协定个规则:ØMQ字符串是指定长度的,发送时不带结尾的null。在最简单的情况下(示例中我们会这么做)一个ØMQ字符串简洁的映射到一个ØMQ消息帧,如上图,一个长度和一些字符。
用C语言,这里我们需要做的是接收一个ØMQ字符串然后以合法的C字符串发送给程序。
// Receive 0MQ string from socket and convert into C string static char * s_recv (void *socket) { zmq_msg_t message; zmq_msg_init (&message); int size = zmq_msg_recv (&message, socket, 0); if (size == -1) return NULL; char *string = malloc (size + 1); memcpy (string, zmq_msg_data (&message), size); zmq_msg_close (&message); string [size] = 0; return (string); }
这就是一个很方便的辅助功能,本着复用精神,让我们写一个类似的“s_send”函数用来以正确的ØMQ格式发送字符串,然后把它打包到头文件。
这就产生了zhelpers.h,它让我们用C语言书写更简练而优美的代码。这代码相当长,而且只有C程序员感兴趣,所以没事的时候再看吧。
版本报告
ØMQ已经历了多个版本,比较频繁。如果你碰到了问题,那可能已经在更新的版本中修复了。因此知道自己正用的ØMQ到底是什么版本还是蛮有用的。这是此功能的小程序:
version: ØMQ version reporting in C
// // Report 0MQ version // #include "zhelpers.h" int main (void) { int major, minor, patch; zmq_version (&major, &minor, &patch); printf ("Current 0MQ version is %d.%d.%d\n", major, minor, patch); return EXIT_SUCCESS; }