好像也有一段时间没有写博客了。。。
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线程,当然不是说它不需要执行命令,而是它直接依赖用户线程。。。