ZeroMQ(java)Socket之Dealer

来源:互联网 发布:乐创圣衣神话淘宝 编辑:程序博客网 时间:2024/04/29 11:28

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

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 0