一、背景简介
文章背景: 近一年前发在公司技术博客上的文章,虽然公司技术氛围相当不错,交流的人毕竟比开放环境还是要少的。发在这里主要希望能和更多对zmq源码感兴趣的同学交流,毕竟感觉自己还有很多了解有偏差的地方。
这里真的是简介:zmq自称史上最快消息队列,其野心是“成为标准网络协议栈的一部分,之后进入 Linux 内核”,剩下背景内容需要的同学请自行寻找啦~
二、本文目的
zmq作为一个c++写成的功能强大的传输层,设计思想和代码本身的学习价值不言而喻!另外zmq的设计者所写的guild融入了其多年分布式系统设计经验,对照着guild里面的示例,从示例中的用户层面开始到zmq内部实现的完整流程正好是实际工作中很多需求实现的良好技术范例。了解zmq内部的一些实现是可以直接提高工作效率的哦。
本文针对那些对zmq基本应用已经有所了解,想进一步探究zmq内部实现奥秘的同学,抛砖引玉,希望能够引发大家关于zmq源码的相关讨论。文章用的是zmq 4.0.1的源码做参照,有兴趣的同学可以对照着看。
三、开始啦
本文分析内容基本限定在zmq提供的进程内通信功能上,尽量不涉及主要流程中无关的类和函数,实现基本功能的对象大部分不展开。(zmq的源码完全深度展开后,真是千头万绪,分而治之是我学习zmq的策略!哈哈,我会告诉你写文章的时候进一步深入的分析我也没理出来吗)进程内通信是zmq guild中吹嘘的一大功能,有哪些特点我这边就不重复吹嘘了。这部分功能在源码中涉及的内容不特别深,但涉及的面并不窄,先学习这部分内容主要是为了先和zmq源码混个脸熟。
先最简单的说一下zmq处理进程内通信的原理,如下图:
图 1
这是啥?其实就是线程之间通过两个队列来交互,对于每个线程来说都是通过其中一个队列发消息给对方,从另一个队列中读取对方发送的消息。这两个队列根据源码用的名称,我称之为pipe,后面会反复提到。zmq所做的就是把pipe绑定到对应的线程上,然后在send和recv的时候通过pipe来发出、获取信息。是的,就是这么简单~
那么我们就来看看具体的源码实现吧!这里通过一个我写的示例程序的运转流程来展开源码的工作流程。进程内通信实验程序:
#include "zmq.h"
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <pthread.h>
static void* client_func(void* context)
{
sleep(5);
void* client = zmq_socket(context, ZMQ_REQ);
zmq_connect(client, "inproc://hello");
sleep(30);
while(1)
{
char buffer[10];
printf("Client: Sending Hello...\n");
zmq_send (client, "Hello", 5, 0);
zmq_recv (client, buffer, 10, 0);
printf("Client: Received World!\n");
}
zmq_close(client);
return NULL;
}
int main()
{
void* context = zmq_ctx_new();
void* responder = zmq_socket(context, ZMQ_REP);
int rc = zmq_bind(responder, "inproc://hello");
assert(rc == 0);
pthread_t client;
pthread_create (&client, NULL, client_func, context);
while (1)
{
char buffer[10];
zmq_recv(responder, buffer, 10, 0);
printf("I am responder server! Recv Hello\n");
sleep(1);
zmq_send(responder, "World", 5, 0);
}
zmq_close(responder);
zmq_ctx_destroy(context);
return 0;
}
没错!又是低小下、俗气烂大街的 hello world。加了两个sleep是为了跟着流程分析zmq源码的时候代码有明确的执行顺序和明显的阻塞,也就某些程度上掩盖了zmq的一个重要特性,这个重要特性留在以后专门分析吧。从main函数的 void* context = zmq_ctx_new(); 开始。
zmq_ctx_new()这个函数可以轻易从源码中看出其实就是调用了ctx_t类的构造函数,因此这里就讲这个上下文类及其构造函数。
class ctx_t
{
public:
ctx_t ();
// 创建socket都是调用ctx中的函数哦
zmq::socket_base_t *create_socket (int type_);
// 发送“命令”给目标线程
void send_command (uint32_t tid_, const command_t &command_);
// 管理zmq中很重要的概念“endpoint”的函数
int register_endpoint (const char *addr_,
endpoint_t &endpoint_);
void unregister_endpoints (zmq::socket_base_t *socket_);
endpoint_t find_endpoint (const char *addr_);
~ctx_t ();
private:
uint32_t tag;
// 进程内所有的socket都能在此找到
typedef array_t <socket_base_t> sockets_t;
sockets_t sockets;
// 还没用到的slot在此集合
typedef std::vector <uint32_t> empty_slots_t;
empty_slots_t empty_slots;
bool starting;
bool terminating;
// slot相关参数需要同步保护的,这个就是互斥锁
mutex_t slot_sync;
// 指向进程内所有“mailbox”的东西就是这些slot
uint32_t slot_count;
mailbox_t **slots;
// 进程内所有“endpoint”在此
typedef std::map <std::string, endpoint_t> endpoints_t;
endpoints_t endpoints;
// 访问endpoint是需要同步的
mutex_t endpoints_sync;
// 这货其实是个产生唯一id的东西,一会见
static atomic_counter_t max_socket_id;
// 最大socket数量
int max_sockets;
// I/O 线程的数量,这类线程暂时被我黑掉了。。。
int io_thread_count;
// Is IPv6 enabled on this context?
bool ipv6;
// ctx选项也是需要同步保护的
mutex_t opt_sync;
// 复制构造函数、等号在此私有化限制,想乱用也不行啦
ctx_t (const ctx_t&);
const ctx_t &operator = (const ctx_t&);
。。。//这是?
}
ctx才这么点内容?好吧,其实我把一些和今天主题无关的以及特别讨厌的家伙都发配到最后那个。。。里面去了。剩下的东西大部分很快就会展现他们的作用,我简单注释了一下。下面看构造函数吧:
zmq::ctx_t::ctx_t () :
tag (ZMQ_CTX_TAG_VALUE_GOOD), // 这货就是证明自己身份用的
starting (true), // 表明第一次创建socket时需要初始化
terminating (false),
reaper (NULL),
slot_count (0),
slots (NULL),
max_sockets (ZMQ_MAX_SOCKETS_DFLT),
io_thread_count (ZMQ_IO_THREADS_DFLT),
ipv6 (false)
{
#ifdef HAVE_FO