Zeromq 源码全解析(2)

在开始前,建议先阅读一遍Zeromq中文指南

https://github.com/anjuke/zguide-cn
目的是学习基本的使用方法,以及面对高扩展需求时,Zeromq官方的解决方案
有些代码示例接口已经改变,但是不妨碍对Zeromq的理解与使用.

关于各APi的介绍会在源代码目录和网页中分别有介绍
代码中路径为
libzmq\doc
网页地址为
http://api.zeromq.org/
内容一致

经过简单的学习不难得出以下几个函数调用会启动zeromq的结论
接下来也是通过对这几个函数的来进行探索

接收端

void* zmq_ctx_new()
void* zmq_socket (void* ctx_, int type_)
int zmq_bind (void* s_, const char* addr_)
int zmq_recv (void* s_, void* buf_, size_t len_, int flags_)

发送端

void* zmq_ctx_new()
void* zmq_socket (void* ctx_, int type_)
int zmq_connect (void *s_, const char *addr_)

从接收端开始

zmq_ctx_new()

API介绍

libzmq\doc\zmq_ctx_new.txt
http://api.zeromq.org/master:zmq-ctx-new

zmq_ctx_new函数创建了zeromq的上下文环境,
从介绍中可以了解到zmq_ctx_new创建了Zeromq的上下文ctx_t,而且ctx_t是线程安全的,并且可以安全的在线程间传递

作为Zeromq的环境初始化接口,我们当然需要从这里开始探索Zeromq的整体设计

src/ctx.hpp

ctx_t 继承自 thread_ctx_t

thread_ctx_t
提供了以下功能的设置接口
set (int option_, const void *optval_, size_t optvallen_)
get (int option_, const void *optval_, size_t optvallen_)
ZMQ_THREAD_SCHED_POLICY 线程调度策略
ZMQ_THREAD_AFFINITY_CPU_ADD 绑定cpu核心
ZMQ_THREAD_AFFINITY_CPU_REMOVE 移除cpu核心绑定
ZMQ_THREAD_PRIORITY 线程优先级
ZMQ_THREAD_NAME_PREFIX 线程字符串别名
也提供了线程启动函数来进行线程的启动
start_thread (thread_t &thread_,thread_fn *tfn_,void *arg_,const char *name_) const

ctx_t 这个类复杂度较高,拥有很多函数,如果一一分析,不仅抓不到重点,而且让人一下接受几十个函数,并理清之间的关系,容易让人怀疑人生

void *zmq_ctx_new (void)
{
    // 首先是网络环境的初始化,分别是PGM和WINDOWS下的
    if (!zmq::initialize_network ()) {
        return NULL;
    }

	
	//直接创建ctx_t指针,而构造函数执行了一些数值初始化
	//当前 ctx_t 的状态
	//_tag (ZMQ_CTX_TAG_VALUE_GOOD),
	//启动标记
    //_starting (true),
	//当前是否处于关闭状态
    //_terminating (false),
	// 回收线程
    //_reaper (NULL),
	//同时socket最大打开数
    //_max_sockets (clipped_maxsocket (ZMQ_MAX_SOCKETS_DFLT)),
	//同时消息的最大数目
    //_max_msgsz (INT_MAX),
	//io线程数量
    //_io_thread_count (ZMQ_IO_THREADS_DFLT),
	// 该上下文是否永远不会终止
    //_blocky (true),
	//是否支持ipv6
    //_ipv6 (false),
	//是否使用零拷贝消息解析功能
    //_zero_copy (true)	
    zmq::ctx_t *ctx = new (std::nothrow) zmq::ctx_t;
    if (ctx) {
        if (!ctx->valid ()) {
            delete ctx;
            return NULL;
        }
    }
    return ctx;
}

相当于真的只做了初始化工作,而我们简单翻阅ctx_t的函数可以得到如下信息:
ctx_t有一个start_thread函数,肯定是后续的函数调用中进行的启动,让我们继续往下走

void *zmq_socket (void *ctx_, int type_)

http://api.zeromq.org/master:zmq-socket
libzmq\doc\zmq_socket.txt

void *zmq_socket (void *ctx_, int type_)
{
	//空指针检查,以及ctx_t检查
    if (!ctx_ || !(static_cast<zmq::ctx_t *> (ctx_))->check_tag ()) {
        errno = EFAULT;
        return NULL;
    }	
    zmq::ctx_t *ctx = static_cast<zmq::ctx_t *> (ctx_);
	//通过 type_ 创建了具体对象指针 并以基类 socket_base_t 形式返回
    zmq::socket_base_t *s = ctx->create_socket (type_);
    return (void *) s;
}

再看

zmq::socket_base_t *zmq::ctx_t::create_socket (int type_)
{
	//后续需要对_empty_slots进行操作,进行上锁
    scoped_lock_t locker (_slot_sync);
	//如果未启动当前 ctx 则进行启动
    if (unlikely (_starting)) {
		//来了来了,在该函数中,我们念念不忘的 start_thread 启动了
        if (!start ())
            return NULL;
    }
    
	// 一旦调用了zmq_ctx_term,将不能创建新套接字
    if (_terminating) {
        errno = ETERM;
        return NULL;
    }

	//如果当前已达到套接字上限,返回错误
    if (_empty_slots.empty ()) {
        errno = EMFILE;
        return NULL;
    }

    // 选择索引
    uint32_t slot = _empty_slots.back ();
    _empty_slots.pop_back ();

    //  生成唯一id
    int sid = (static_cast<int> (max_socket_id.add (1))) + 1;

    // 创建套接字,并注册在其身上的mailbox
    socket_base_t *s = socket_base_t::create (type_, this, slot, sid);
    if (!s) {
        _empty_slots.push_back (slot);
        return NULL;
    }
	//该 ctx_t 上的 socket_base_t 数组
    _sockets.push_back (s);
	//该 ctx_t 上的 i_mailbox 数组
    _slots[slot] = s->get_mailbox ();

    return s;
}

//启动当前ctx
bool zmq::ctx_t::start ()
{
	//对数组中的 mailboxes 进行初始化,增加回收线程
    _opt_sync.lock ();	
    const int term_and_reaper_threads_count = 2;	
    const int mazmq = _max_sockets;	
    const int ios = _io_thread_count;
    _opt_sync.unlock ();
	
	
    int slot_count = mazmq + ios + term_and_reaper_threads_count;
    try {
		//vector 重设 capacity 上限
        _slots.reserve (slot_count);
        _empty_slots.reserve (slot_count - term_and_reaper_threads_count);
    }
    catch (const std::bad_alloc &) {
        errno = ENOMEM;
        return false;
    }
	//设置当前大小.
	//吐槽一下,一顿分析下来我竟然忘了 _slots 容器中装的是什么了,又回去看了一下,改成 _mailbox_slots应该会好一点
    _slots.resize (term_and_reaper_threads_count);

    //  Initialise the infrastructure for zmq_ctx_term thread.
	// 将关闭线程的 mailbox 绑定到 ctx 上
    _slots[term_tid] = &_term_mailbox;


	//创建回收线程并启动
    _reaper = new (std::nothrow) reaper_t (this, reaper_tid);
    if (!_reaper) {
        errno = ENOMEM;
        goto fail_cleanup_slots;
    }
    if (!_reaper->get_mailbox ()->valid ())
        goto fail_cleanup_reaper;
    _slots[reaper_tid] = _reaper->get_mailbox ();
    _reaper->start ();

	//创建指定数量的io线程启动且注册,当然包括其mailbox
    _slots.resize (slot_count, NULL);

    for (int i = term_and_reaper_threads_count;
         i != ios + term_and_reaper_threads_count; i++) {
        io_thread_t *io_thread = new (std::nothrow) io_thread_t (this, i);
        if (!io_thread) {
            errno = ENOMEM;
            goto fail_cleanup_reaper;
        }
        if (!io_thread->get_mailbox ()->valid ()) {
            delete io_thread;
            goto fail_cleanup_reaper;
        }
        _io_threads.push_back (io_thread);
        _slots[i] = io_thread->get_mailbox ();
        //io_thread 会使用 ctx_t 上的start_thread来启动成员函数 worker_routine ,进而启动当前平台下的io接口的
        //loop(), 再接下来就是经典的 reactor 模式, 从响应的fd中,找到对应的 poll_entry_t ,
        //通过判断响应的事件来调用挂接在io_thread上的对象的 in_event 或者 out_event 函数
        io_thread->start ();
    }

    //  In the unused part of the slot array, create a list of empty slots.
	
	//将可以分配的索引放入可用索引vector中.
    for (int32_t i = static_cast<int32_t> (_slots.size ()) - 1;
         i >= static_cast<int32_t> (ios) + term_and_reaper_threads_count; i--) {
        _empty_slots.push_back (i);
    }
	
	//启动完毕
    _starting = false;
    return true;

fail_cleanup_reaper:
    _reaper->stop ();
    delete _reaper;
    _reaper = NULL;

fail_cleanup_slots:
    _slots.clear ();
    return false;
}

再看 socket_base_t 对象的创建过程

典型的工厂模式,隐藏构造时的细节,用type来获取不同的目标对象
截取部分分析

zmq::socket_base_t *zmq::socket_base_t::create (int type_,
                                                class ctx_t *parent_,
                                                uint32_t tid_,
                                                int sid_)
{
    socket_base_t *s = NULL;
    switch (type_) {
        case ZMQ_REP:
			//可以跟着几个对象的构造进行查看,3个参数的传入其实是给基类 socket_base_t 所使用初始化
			//再根据Zeromq类结构图不难看出,不同的type_只是生成了不同socket_base_t的子类对象
            s = new (std::nothrow) rep_t (parent_, tid_, sid_);
            break;
			//其他构造方法
			.....
        default:
            errno = EINVAL;
            return NULL;
    }

    alloc_assert (s);

    if (s->_mailbox == NULL) {
        s->_destroyed = true;
        LIBZMQ_DELETE (s);
        return NULL;
    }

    return s;
}

再看 socket_base_t 的构造函数

zmq::socket_base_t::socket_base_t (ctx_t *parent_,
                                   uint32_t tid_,
                                   int sid_,
                                   bool thread_safe_) :
    //调用 own_t 的构造函数,用于维护对象的生命周期
    own_t (parent_, tid_),
    _tag (0xbaddecaf),
    _ctx_terminated (false),
    _destroyed (false),
    _poller (NULL),
    _handle (static_cast<poller_t::handle_t> (NULL)),
    _last_tsc (0),
    _ticks (0),
    _rcvmore (false),
    _monitor_socket (NULL),
    _monitor_events (0),
    _thread_safe (thread_safe_),
    _reaper_signaler (NULL),
    _sync (),
    _monitor_sync ()
{
    options.socket_id = sid_;
    options.ipv6 = (parent_->get (ZMQ_IPV6) != 0);
    options.linger.store (parent_->get (ZMQ_BLOCKY) ? -1 : 0);
    options.zero_copy = parent_->get (ZMQ_ZERO_COPY_RECV) != 0;
    
	//根据线程安全选项来决定是否生成线程安全的 mailbox 对象
    if (_thread_safe) {
        _mailbox = new (std::nothrow) mailbox_safe_t (&_sync);
        zmq_assert (_mailbox);
    } else {
        mailbox_t *m = new (std::nothrow) mailbox_t ();
        zmq_assert (m);

        if (m->get_fd () != retired_fd)
            _mailbox = m;
        else {
            LIBZMQ_DELETE (m);
            _mailbox = NULL;
        }
    }
}

可以这么看 zmq_socket 在 ctx 上插入了一个 socket_base_t 对象并将指针抛出来,由 own_t 来维护生命周期

再看

zmq_bind (_responder, "tcp://*:9000");


int zmq_bind (void *s_, const char *addr_)
{
	//转换成 socket_base_t 指针
    zmq::socket_base_t *s = as_socket_base_t (s_);
    if (!s)
        return -1;
	//进行地址的解析和绑定地址
    return s->bind (addr_);
}

函数非常长,主要是因为该函数是进行地址解析,还需要根据不同的协议执行不同的函数调用操作
同样,我们暂时只对其中一种模式进行分析

int zmq::socket_base_t::bind (const char *endpoint_uri_)
{
	//根据线程安全拍段进行上锁准备
    scoped_optional_lock_t sync_lock (_thread_safe ? &_sync : NULL);

    if (unlikely (_ctx_terminated)) {
        errno = ETERM;
        return -1;
    }

	// 执行可能存在的被挂起的命令
    int rc = process_commands (0, false);
    if (unlikely (rc != 0)) {
        return -1;
    }

   //以://为分割对传入的协议和地址端口进行分片
   //并对传入协议进行检查
    std::string protocol;
    std::string address;
    if (parse_uri (endpoint_uri_, protocol, address)
        || check_protocol (protocol)) {
        return -1;
    }
	
	
	....


	//以下传输方式需要在io线程中进行,所以我们选择一个io线程
    io_thread_t *io_thread = choose_io_thread (options.affinity);
    if (!io_thread) {
        errno = EMTHREAD;
        return -1;
    }

    if (protocol == protocol_name::tcp) {
		//创建tcp 监听对象
        tcp_listener_t *listener =
          new (std::nothrow) tcp_listener_t (io_thread, this, options);
        alloc_assert (listener);
		//设置地址
        rc = listener->set_local_address (address.c_str ());
        if (rc != 0) {
            LIBZMQ_DELETE (listener);
            event_bind_failed (make_unconnected_bind_endpoint_pair (address),
                               zmq_errno ());
            return -1;
        }

        // Save last endpoint URI
        listener->get_local_address (_last_endpoint);
		//将节点插入子树中,
        add_endpoint (make_unconnected_bind_endpoint_pair (_last_endpoint),
                      static_cast<own_t *> (listener), NULL);
        options.connected = true;
        return 0;
    }
	...

    zmq_assert (false);
    return -1;
}

//传入绑定的CPU下标
zmq::io_thread_t *zmq::ctx_t::choose_io_thread (uint64_t affinity_)
{
    if (_io_threads.empty ())
        return NULL;

    //根据cpu偏好以及当前的io压力来选择压力最小的io线程并返回
    int min_load = -1;
    io_thread_t *selected_io_thread = NULL;
    for (io_threads_t::size_type i = 0; i != _io_threads.size (); i++) {
        if (!affinity_ || (affinity_ & (uint64_t (1) << i))) {
            int load = _io_threads[i]->get_load ();
            if (selected_io_thread == NULL || load < min_load) {
                min_load = load;
                selected_io_thread = _io_threads[i];
            }
        }
    }
    return selected_io_thread;
}


void zmq::socket_base_t::add_endpoint (
  const endpoint_uri_pair_t &endpoint_pair_, own_t *endpoint_, pipe_t *pipe_)
{
	//将新节点插入endpoint_
    launch_child (endpoint_);
	
	//插入ctx
    _endpoints.ZMQ_MAP_INSERT_OR_EMPLACE (endpoint_pair_.identifier (),
                                          endpoint_pipe_t (endpoint_, pipe_));

    if (pipe_ != NULL)
        pipe_->set_endpoint_pair (endpoint_pair_);
}

void zmq::own_t::launch_child (own_t *object_)
{
    //  插入
    object_->set_owner (this);

    //  向object_所属的io线程发送plug消息,在执行process_plug
    send_plug (object_);

    //  设置object_归属权
    send_own (this, object_);
}

<
  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值