ZeroMQ(java)Socket之Dealer

好像也有一段时间没有写博客了。。。

ZeroMQ到现在,其实基本的原理也都了解的还算差不多了,剩下的就是一些细节的了。。。

我们知道ZeroMQ中定义了很多类型的Socket,其实这个里面最为简单的就是Dealer类型的socket看了,它基本没有做太多额外的处理。。。

在看具体的Dealer之前,先来看看两个类型,其实是两个工具类,用于维护在pipe上面执行的读写操作。。。

首先是FQ类型,它用于维护Socket与Session之间的pipe,socket在pipe上的读操作:

//用于维护pipe上面的读取操作
public class FQ {

    //  Inbound pipes.
    private final List<Pipe> pipes;  //所有绑定的pipe
    
    private int active;  //激活的pipe的数量,所有激活的pipe都放在最前面
    private int current;  //下一个可以读取的pipe的下标
    private boolean more;  //如果是ture的话,表示只是接受了一部分,还有别的需要接受
    
    //构造函数
    public FQ () {
        active = 0;
        current = 0;
        more = false;
        
        pipes = new ArrayList<Pipe>();   //用于保存所有的关联的pipe
    }
    
    //这里其实很简单,直接保存pipe就好了,并且好更新pipe的数量,这里加进来的pipe一开始是需要放到活动的pipe部分的
    public void attach (Pipe pipe_) {
        pipes.add (pipe_);
        Utils.swap (pipes, active, pipes.size () - 1);  
        active++;
    }
    
    //终止一个pipe,这里还是比较的简单吧,直接去除就好了,不过这里需要更新一些标志位
    public void terminated (Pipe pipe_) {
        final int index = pipes.indexOf (pipe_);
        //当前pipe居然还在活动的部分中,需要现将其放到活动的pipe的最后去,
        if (index < active) {
            active--;
            Utils.swap (pipes, index, active);
            if (current == active)
                current = 0;  //更新current下标
        }
        pipes.remove (pipe_);  //移除
    }

    //激活一个pipe,其实这里是将当前的pipe放到活动的pipe的那里去,这里active数量还要加1
    public void activated (Pipe pipe_) {
        //  Move the pipe to the list of active pipes.
        Utils.swap(pipes, pipes.indexOf (pipe_), active);
        active++;
    }

    //这里从pipe里面读取数据
    public Msg recv(ValueReference<Integer> errno) {
        return recvpipe(errno, null);
    }

    public Msg recvpipe(ValueReference<Integer> errno, ValueReference<Pipe> pipe_) {

        //  Round-robin over the pipes to get the next message.
        while (active > 0) {  //有活动的pipe

            //  Try to fetch new message. If we've already read part of the message
            //  subsequent part should be immediately available.
            Msg msg_ = pipes.get(current).read();  //从当前的current下标读取
            boolean fetched = msg_ != null;  //判断是否有msg读取出来

            //  Note that when message is not fetched, current pipe is deactivated
            //  and replaced by another active pipe. Thus we don't have to increase
            //  the 'current' pointer.
            if (fetched) {
                if (pipe_ != null) {
                    pipe_.set(pipes.get(current));
                }
                more = msg_.has_more();  //判断当前的msg是否还有急需要读的
                if (!more) {  //如果没有more了,那么需要更新current下标
                    current = (current + 1) % active;  //更新current下标
                }
                return msg_;  //返回msg
            }
            
            //到这里了,说明没有读取到数据
            //  Check the atomicity of the message.
            //  If we've already received the first part of the message
            //  we should get the remaining parts without blocking.
            assert (!more);
            
            active--;  //表示当前的活动的pipe数量已经少了一个,
            Utils.swap (pipes, current, active);  //这里其实是将刚刚读取的pipe从活动部分去除
            if (current == active)  //有可能需要更新current下标
                current = 0;
        }

        //  No message is available. Initialise the output parameter
        //  to be a 0-byte message.
        errno.set(ZError.EAGAIN);  //到这里了,说明确实没得数据可以读取了
        return null;
    }

    //用于判断当前的底层的pipe是否有可以读取的数据
    public boolean has_in ()
    {
        //  There are subsequent parts of the partly-read message available.
        if (more)  //如果有标志位的话,那么肯定可以了
            return true;

        //  Note that messing with current doesn't break the fairness of fair
        //  queueing algorithm. If there are no messages available current will
        //  get back to its original value. Otherwise it'll point to the first
        //  pipe holding messages, skipping only pipes with no messages available.
        //在当前的活动pipe部分去找,直到找到有数据可以读取的
        while (active > 0) {
            if (pipes.get(current).check_read ())
                return true;

            //  Deactivate the pipe.
            active--;   //表示当前的这个没有数据可以读取,那么需要将其从活动的pipe部分移除
            Utils.swap (pipes, current, active);
            if (current == active)  //如果已经到了尾部,那么讲current下标设置为最开始
                current = 0;
        }

        return false;
    }

}

还蛮简单的吧,基本上注释都已经说的比较清楚了。。。首先有一个list,用于维护当前所有的pipe,然后有一个active下标,然后还有一个current下标:


这里在Active前面的pipe就是当前活动的pipe,也就是可能有数据可以读取的pipe,current就是下一个要读取的pipe的下标。。。应该够简单的吧,这里还通过current的移动,实现在pipe上面数据的循环读取。。。防止只在某一个pipe上读取数据。。。、

然后还有另外一个工具类,LB,它与LQ相反,它是用于维护写操作的,不知道为啥取这两个奇怪的名字,囧:

//用于维护pipe上的写操作
public class LB {

    //  List of outbound pipes.
    private final List<Pipe> pipes;  //用于保存所有关联的pipe
    
    private int active;  //当前所有的活动pipe的下标,前面的都是活动的
    private int current;  //current下标
    private boolean more;  //是否还有数据要写
    private boolean dropping;  //如果是true的话,那么丢弃当前的数据
    
    public LB() {
        active = 0;
        current = 0;
        more = false;
        dropping = false;
        
        pipes = new ArrayList<Pipe>();  //创建arraylist
    }

    public void attach (Pipe pipe_)  {
        pipes.add (pipe_);  //将当前的pipe放到列表
        activated (pipe_);  //将其移动到活动的pipe部分,也就是active前面
    }

    //终止一个pipe
    public void terminated(Pipe pipe_) {
        int index = pipes.indexOf (pipe_);  //获取这个pipe的下标

        //  If we are in the middle of multipart message and current pipe
        //  have disconnected, we have to drop the remainder of the message.
        if (index == current && more)  //如果就是当前的current,而且还有数据,那么将dropping标志位设置为true
            dropping = true;

        //  Remove the pipe from the list; adjust number of active pipes
        //  accordingly.
        if (index < active) {  //如果在active部分,那么这里还要更新一下
            active--;
            Utils.swap (pipes, index, active);
            if (current == active)
                current = 0;
        }
        pipes.remove (pipe_);

    }
    //将这个pipe移动到active部分,而且增加active计数
    public void activated(Pipe pipe_) {
        //  Move the pipe to the list of active pipes.
        Utils.swap (pipes, pipes.indexOf (pipe_), active);
        active++;
    }

    //用于向pipe写数据
    public boolean send(Msg msg_, ValueReference<Integer> errno) {
        //  Drop the message if required. If we are at the end of the message
        //  switch back to non-dropping mode.
        if (dropping) {  //如果有dropping标志位的话,那么直接丢弃就好了

            more = msg_.has_more();
            dropping = more;

            msg_.close();
            return true;
        }

        while (active > 0) {  //如果有底层的pipe可以写
            if (pipes.get(current).write (msg_))  //想当前的current的pipe写
                break;  //写进去了,那么直接break就好了

            assert (!more);
            active--;  //这里表示这个pipe已经满了,那么需要将其从active部分移除
            if (current < active)
                Utils.swap (pipes, current, active);
            else
                current = 0;
        }

        //  If there are no pipes we cannot send the message.
        if (active == 0) {  //实在没有pipe可以写了
            errno.set(ZError.EAGAIN);
            return false;
        }

        //  If it's final part of the message we can fluch it downstream and
        //  continue round-robinning (load balance).
        more = msg_.has_more();  //判断当前数据是否还有接下来要写的
        if (!more) {//如果没有的话,那么flush,而且要更新current
            pipes.get(current).flush ();
            if (active > 1)
                current = (current + 1) % active;
        }

        return true;
    }

    //用于看底层的pipe有没有哪一个可以写
    public boolean has_out() {
        //  If one part of the message was already written we can definitely
        //  write the rest of the message.
        if (more)
            return true;

        //遍历,直到找到可以写的pipe,这里还要将无法写的pipe从队列里面移除以及更新current下标
        while (active > 0) {

            //  Check whether a pipe has room for another message.
            if (pipes.get(current).check_write ())
                return true;

            //  Deactivate the pipe.
            active--;
            Utils.swap (pipes, current, active);
            if (current == active)
                current = 0;
        }

        return false;
    }
}

其实实现跟FQ基本一样,也就是这个是写操作。。。。。


好了,接下来来看看具体的Dealer类型的实现吧:

//这个应该最简单的socket类型了
public class Dealer extends SocketBase {
     
    public static class DealerSession extends SessionBase {  //这里其实对Session也没有扩展,就是直接用SessionBase
        public DealerSession(IOThread io_thread_, boolean connect_,
            SocketBase socket_, final Options options_,
            final Address addr_) {
            super(io_thread_, connect_, socket_, options_, addr_);
        }
    }
    
    //  Messages are fair-queued from inbound pipes. And load-balanced to
    //  the outbound pipes.
    private final FQ fq;  //用于维护读取操作
    private final LB lb;   //用于维护写操作

    //  Have we prefetched a message.
    private boolean prefetched;   //是否有预读取的msg
      
    private Msg prefetched_msg;   //指向预读取的数据 

    //构造函数,第一个参数是其所属的CTX,第二个参数是tid,这里其实并没有生存在某个IO线程中,所以这个ID并没有与任何IO线程关联,因为生存在用户线程
    public Dealer(Ctx parent_, int tid_, int sid_) {
        super(parent_, tid_, sid_);
        
        prefetched = false;
        options.type = ZMQ.ZMQ_DEALER;  //当前socket的类型
        
        fq = new FQ();
        lb = new LB();
        //  TODO: Uncomment the following line when DEALER will become true DEALER
        //  rather than generic dealer socket.
        //  If the socket is closing we can drop all the outbound requests. There'll
        //  be noone to receive the replies anyway.
        //  options.delay_on_close = false;
            
        options.recv_identity = true;   
    }


    @Override
    protected void xattach_pipe(Pipe pipe_, boolean icanhasall_) {
        assert (pipe_ != null);
        fq.attach (pipe_);  //放到维护读取的
        lb.attach (pipe_);  //放到维护写的
    }
    
    @Override
    protected boolean xsend(Msg msg_)
    {
        return lb.send(msg_, errno);  //直接调用lb来发送 msg,这里会遍历的底层活动的pipe,直接直到将msg写到pipe为止
    }

    @Override
    protected Msg xrecv() {
        return xxrecv();
    }

    private Msg xxrecv()
    {
        Msg msg_ = null;
        //  If there is a prefetched message, return it.
        if (prefetched) {  //如果有预接收的msg,那么直接返回它就行了
            msg_ = prefetched_msg;
            prefetched = false;
            prefetched_msg = null;
            return msg_;
        }

        //  DEALER socket doesn't use identities. We can safely drop it and 
        while (true) {  //这里不断的从底层的pipe里面读取数据
            msg_ = fq.recv(errno);  //调用fq来接收,其实是从底层的活动的pipe里面去接收数据,它会遍历底层所有活动的pipe,直到接收到数据为止
            if (msg_ == null)  //确实没有数据
                return null;
            if ((msg_.flags() & Msg.identity) == 0)  //dealer发送的msg没有标志位
                break;
        }
        return msg_;
    }

    @Override
    //用于判断底层的pipe是否有可以读取的msg
    protected boolean xhas_in ()
    {
        //  We may already have a message pre-fetched.
        if (prefetched)  //已经有预接收的数据了
            return true;

        //  Try to read the next message to the pre-fetch buffer.
        prefetched_msg = xxrecv();  //这里接收到预接收
        if (prefetched_msg == null)
            return false;
        prefetched = true;
        return true;
    }

    @Override
    //检查是否有pipe可以写数据
    protected boolean xhas_out () {
        return lb.has_out ();
    }

    @Override
    //当底层的pipe有数据可以读取的时候,这个其实将其放到的pipe列表的活动pipe部分,这个方法会在socketBase里面调用,当pipe接收到session传上来的数据的时候
    protected void xread_activated (Pipe pipe_) {
        fq.activated (pipe_);
    }

    @Override
    //将这个pipe放到活动部分,也就是可以向这个pipe里面写数据了
    protected void xwrite_activated (Pipe pipe_) {
        lb.activated (pipe_);
    }


    @Override
    //用于终止一个关联的pipe
    protected void xterminated(Pipe pipe_) {
        fq.terminated (pipe_);
        lb.terminated (pipe_);
    }

}

好吧,dealer的实现真的是太简单了,它继承自SocketBase,也就扩展了几个读写操作,其实具体的还是上面的两个工具类型对象完成的。。。

这里好像直接跳过SocketBase的实现直接来看具体的Socket类型的实现有点不太好,。。。不过其实也都蛮简单的。。。就不细说了。。。

这里有一点需要提醒的就是,在一般情况下对象都会关联一个IO线程,用于执行命令啥的,不过Socket并没有关联某个具体的IO线程,当然不是说它不需要执行命令,而是它直接依赖用户线程。。。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值