NIO源码分析(一)

先写个简单的例子

// 开启一个server channel来监听
ServerSocketChannel ssc = ServerSocketChannel.open();
// 开启非阻塞模式
ssc.configureBlocking(false);
ServerSocket socket = ssc.socket();
socket.bind(new InetSocketAddress("localhost",65535));//绑定相应IP及port
//ssc.bind(new InetSocketAddress("localhost",65535));
Selector selector = Selector.open();//开启一个selector
ssc.register(selector,SelectionKey.OP_ACCEPT);//绑定注册事件

while(true){
this.selector.select();//阻塞,只有当至少一个注册的事件发生的时候才会继续.
Set selectKeys = this.selector.selectedKeys();
Iterator it = selectKeys.iterator();
  while (it.hasNext()) {
    SelectionKey key = it.next();
    it.remove();
    // 处理事件. 可以用多线程来处理.
    this.dispatch(key);
  }
} 

OK,我们一句一句来看

第一句

ServerSocketChannel ssc = ServerSocketChannel.open();
public static ServerSocketChannel open() throws IOException {
    return SelectorProvider.provider().openServerSocketChannel();
}

这里会根据操作系统得到不同的provider,由于用的是mac,而且一般服务器以linux为主,所以这边返回的是EPollSelectorProvider

进一步SelectorProviderImpl中:

 public ServerSocketChannel openServerSocketChannel() throws IOException {
    return new ServerSocketChannelImpl(this);
}

ServerSocketChannelImpl(SelectorProvider sp) throws IOException {
    super(sp);//这里面可以不看,就是SelectorProvider的赋值
    this.fd =  Net.serverSocket(true);
    this.fdVal = IOUtil.fdVal(fd);//这2句都是native操作,没有看底层的jvm源码,看函数名应该是新建FD并进行赋值
    this.state = ST_INUSE;
    }

第二句

ssc.configureBlocking(false);

这句不展开了,看名字就知道开启非阻塞模式
底层调用了

static native void configureBlocking(FileDescriptor fd, boolean blocking)
    throws IOException;

第三句

ServerSocket socket = ssc.socket();
socket.bind(new InetSocketAddress("localhost",65535));

看ServerSocketChannelImpl中:

public ServerSocket socket() {
    synchronized (stateLock) {
        if (socket == null)
            socket = ServerSocketAdaptor.create(this);//这里只是新建了一个适配器,本质上还是ssc
        return socket;
    }
 }

具体绑定过程:

public void bind(SocketAddress local, int backlog) throws IOException {
    if (local == null)
        local = new InetSocketAddress(0);
    try {
        ssc.bind(local, backlog);//如上所述,本质上还是ssc
    } catch (Exception x) {
        Net.translateException(x);
    }
}

所以我试了下

ssc.bind(new InetSocketAddress("localhost",65535));

也是可以的

第四句

Selector selector = Selector.open();//开启一个selector

一步一步看:

 public static Selector open() throws IOException {
    return SelectorProvider.provider().openSelector();//这里返回的是EPollSelectorImpl
}

在EPollSelectorProvider中:

public AbstractSelector openSelector() throws IOException {
    return new EPollSelectorImpl(this);
}

第五句

ssc.register(selector, SelectionKey.OP_ACCEPT);

这里一步步跟下去,最终是在AbstractSelectableChannel:

public final SelectionKey register(Selector sel, int ops,
                                   Object att)
    throws ClosedChannelException
{
    if (!isOpen())
        throw new ClosedChannelException();
    if ((ops & ~validOps()) != 0)//下面的代码块中注明,这里只支出accept行为的注册
        throw new IllegalArgumentException();
    synchronized (regLock) {
        if (blocking)
            throw new IllegalBlockingModeException();
        SelectionKey k = findKey(sel);//查找sel是否已经绑定对应的key了,具体代码这里不贴
        if (k != null) {
            k.interestOps(ops);//这句后面再讲,有多处调用到
            k.attach(att);//这里的attachment为null,一般可以用于保存session等作为有状态通讯
        }
        if (k == null) {
            // New registration
            k = ((AbstractSelector)sel).register(this, ops, att);//这句在下面解释
            addKey(k);//这句就是加到保存的SelectionKey[]全局变量
        }
        return k;
    }
}

ServerSocketChannel中:

/*Server-socket channels only support the accepting of new
 * connections, so this method returns {@link SelectionKey#OP_ACCEPT}.*/
public final int validOps() {
    return SelectionKey.OP_ACCEPT;
}

SelectorImpl:

protected final SelectionKey register(AbstractSelectableChannel ch,
                                      int ops,
                                      Object attachment)
{
    if (!(ch instanceof SelChImpl))
        throw new IllegalSelectorException();
    SelectionKeyImpl k = new SelectionKeyImpl((SelChImpl)ch, this);//新建key,这个ch就是最初的ssc
    k.attach(attachment);
    synchronized (publicKeys) {
        implRegister(k);//主要是这个
    }
    k.interestOps(ops);
    return k;
}

再看EPollSelectorImpl:

protected void implRegister(SelectionKeyImpl ski) {
    if (closed)
        throw new ClosedSelectorException();
    SelChImpl ch = ski.channel;
    fdToKey.put(Integer.valueOf(ch.getFDVal()), ski);
    pollWrapper.add(ch); //这里要见最底下的图, 核心代码updateList.add(new Updator(channel, EPOLL_CTL_ADD));这个码updateList在下面interestOps会用到
    keys.add(ski);
}

再来说多处用到的k.interestOps(ops);
SelectionKeyImpl:

SelectionKey nioInterestOps(int ops) {      // package-private
    if ((ops & ~channel().validOps()) != 0)
        throw new IllegalArgumentException();
    channel.translateAndSetInterestOps(ops, this);
//这句
    interestOps = ops;
    return this;
}

SocketChannelImpl,这里讲SelectionKey对应的操作转为EPOLL对应的事件类型,具体可以查看EPOLL相关文档:

public void translateAndSetInterestOps(int ops, SelectionKeyImpl sk) {
    int newOps = 0;
    if ((ops & SelectionKey.OP_READ) != 0)
        newOps |= PollArrayWrapper.POLLIN;
    if ((ops & SelectionKey.OP_WRITE) != 0)
        newOps |= PollArrayWrapper.POLLOUT;
    if ((ops & SelectionKey.OP_CONNECT) != 0)
        newOps |= PollArrayWrapper.POLLCONN;
    sk.selector.putEventOps(sk, newOps);
}

跟踪下去EPollSelectorImpl,中间代码不贴了:

void putEventOps(SelectionKeyImpl sk, int ops) {
    if (closed)
        throw new ClosedSelectorException();
    pollWrapper.setInterest(sk.channel, ops);
}

继续EPollArrayWrapper,这里将注册事件及对应的channel保存到一个updateList中:

void setInterest(SelChImpl channel, int mask) {
    synchronized (updateList) {
        // if the previous pending operation is to add this file descriptor
        // to epoll then update its event set
        if (updateList.size() > 0) {
            Updator last = updateList.getLast();//防止已经加过了的channel直接更新事件
            if (last.channel == channel && last.opcode == EPOLL_CTL_ADD) {
                last.events = mask;
                return;
            }
        }

        // update existing registration
        updateList.add(new Updator(channel, EPOLL_CTL_MOD, mask));
    }
}

到这里为止其实初始化的工作都做好了,接下来

第六句

this.selector.select();

还是去掉中间代码,直接看核心的EPollSelectImpl:

protected int doSelect(long timeout) throws IOException
{
    if (closed)
        throw new ClosedSelectorException();
    processDeregisterQueue();//处理已经取消的key
    try {
        begin();
        pollWrapper.poll(timeout);//核心代码看下面
    } finally {
        end();
    }
    processDeregisterQueue();
    int numKeysUpdated = updateSelectedKeys(); //核心代码,见下面
    if (pollWrapper.interrupted()) { //中断处理,暂时不看
        // Clear the wakeup pipe
        pollWrapper.putEventOps(pollWrapper.interruptedIndex(), 0);
        synchronized (interruptLock) {
            pollWrapper.clearInterrupted();
            IOUtil.drain(fd0);
            interruptTriggered = false;
        }
    }
    return numKeysUpdated;
}

EPollArrayWrapper:

int poll(long timeout) throws IOException {
    updateRegistrations();  //核心代码更新前面updateList中的操作到native
    updated = epollWait(pollArrayAddress, NUM_EPOLLEVENTS, timeout, epfd); //核心代码,执行native的epollWait方法,等待注册事件上来
    for (int i=0; i

最后:

Set selectKeys = this.selector.selectedKeys();
Iterator it = selectKeys.iterator();
  while (it.hasNext()) {
    SelectionKey key = it.next();
    it.remove();
    // 处理事件. 可以用多线程来处理.
    this.dispatch(key);
  }
} 

这里就很简单了,selectedKeys()已经在上一步处理好了,这里只是拿出来做相应的业务操作

这样一个服务端的启动操作就完成了,后面还要注册SocketChannel的读写事件,就到以后再分析。
Epoll相关内容以前写过,就不写了。

整个epoll的类关系图:

NIO源码分析(一)_第1张图片
epoll.png

你可能感兴趣的:(NIO源码分析(一))