前言
千言万语zeromq
zeromq(0mq,或者zmq)看起来像一个嵌入式的网络库文件,但其功能如同一个并发框架;
它提供了能够携带原子消息的套接字,可以实现进程内部,进程间,tcp以及多播通信;你
可以使用诸如fan-out,pub-sub,任务分发以及请求响应等模式来连接套接口N-to-N;
对于集群软件而言,它的效率非常高;它的异步IO模式可以提供可扩展的多核应用;同时,
其提供多种语言的API,可以运行在绝大多数的操作系统上;zeromq是imatix公司开发的,
是一款LGPLv3 的开源软件;
开始
先从hello world 这个程序开始来,服务端监听5555端口,当它接收到客户端的hello时,
将会回复一个world给客户端;
请求-响应 对套接字;客户端先调用 zmq_send() 然后调用 zmq_recv(),类似的服务端调用
zmq_recv(),然后调用zmq_send()函数;下列为其参考语言:
//
// 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>
#ifndef _WIN32
#include <unistd.h>
#else
#include <windows.h>
#define sleep(n) Sleep(n)
#endif
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
可以看到zeromq的语言非常类似c和c++语言,使用java或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 in Java
// Binds REP socket to tcp://*:5555
// Expects "Hello" from client, replies with "World"
//
import org.zeromq.ZMQ;
public class hwserver {
public static void main(String[] args) throws Exception {
ZMQ.Context context = ZMQ.context(1);
// Socket to talk to clients
ZMQ.Socket responder = context.socket(ZMQ.REP);
responder.bind("tcp://*:5555");
while (!Thread.currentThread().isInterrupted()) {
// Wait for next request from the client
byte[] request = responder.recv(0);
System.out.println("Received Hello");
// Do some 'work'
Thread.sleep(1000);
// Send reply back to client
String reply = "World";
responder.send(reply.getBytes(), 0);
}
responder.close();
context.term();
}
}
hwserver.java
c++客户端程序如下所示:
// Hello World client
#include <zmq.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
int main (void)
{
printf ("Connecting to hello world server…\n");
void *context = zmq_ctx_new ();
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++) {
char buffer [10];
printf ("Sending Hello %d…\n", request_nbr);
zmq_send (requester, "Hello", 5, 0);
zmq_recv (requester, buffer, 10, 0);
printf ("Received World %d\n", request_nbr);
}
zmq_close (requester);
zmq_ctx_destroy (context);
return 0;
}
这看起来非常的简单,但是zeromq还有更强大的功能;你可以一次性的向服务端发数以万计
的客户端,它仍然会快速的处理;试着启动客户端然后启动服务端,来看看他是如何工作的;
然后再思考下;
如果你杀掉一个服务端,然后重启他,客户端将不会完全恢复;从当掉的进程恢复不是一件
容易的事情;创建一个可靠的请求-响应工作流是非常复杂的,不过第四章的可靠的请求-响应
模式可以解决他;
这种场景下背后发生的事情总是会引起我们的注意,例如如何让代码更简洁,如何让代码更不容易
当掉,即使在高负荷情况下;这就是请求-响应模式,或许是使用zeromq的最简单的方式,
其类同于rpc调用或者客户端/服务端;
如果你不告诉zeromq消息大小,zeromq是不会知道这些数据的;这就意味着,你必须安全的格式化
数据,以便应用程序可以读取;对于对象或者复杂的数据来说这确实是一个工作量,就像Protocol Buffers
但是对于字符串来说,你得小心点;
在c语言中,字符串是以null来结束的;我们可以发送一个“hello”外加一个null字符;
zmq_send (requester, "Hello", 6, 0);
然而,如果你用其他语言发送,可能不会包含一个null字符,例如我们用phython,
socket.send ("Hello")
发送的字符将只有5个;
如果你用c语言读取,你可能获取一个像字符串的东东,如果你幸运的话,可能收到的如发送的一样;
如果客户端和服务端没有对此字符串格式达成协议,你很可能获得一个很尴尬的结果;
如果你从zeromq(C语言)接收字符串,你可能无法相信它是安全终止结束的;每一次读取字符串,
会为额外的字符申请空间,复制字符串,并在该字符串后添加null字符;
我们制定一个这样的规则,zeromq字符串是由描述长度的,不需要后缀null字符的;按照最简单的例子
zeromq字符串可以映射为一个消息帧,如同上面描述的长度和字符;
c语言的代码如下所示:
// Receive ZeroMQ string from socket and convert into C string
// Chops string at 255 chars, if it's longer
static char *
s_recv (void *socket) {
char buffer [256];
int size = zmq_recv (socket, buffer, 255, 0);
if (size == -1)
return NULL;
if (size > 255)
size = 255;
buffer [size] = 0;
return strdup (buffer);
}
这种方式符合恰当的重复使用的精神,然后我们可以写出zeromq的send函数;这就是zhelpers.h
文件,这可以让我们用c语音编写更短的zeromq的程序;不过,这确实有相当大的代码量,不过
对于c程序员而言,就只当消遣了; 、
版本控制
由于 zeromq的版本更新也较为频繁;如果你遇到bug,可先在较新版本中查看是否已经修正,所以你最好知道
你在使用哪些版本;
#include <zmq.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 0;
}