一、回顾NIO中的server
下面是我在学习nio时,写的selctor版本的服务端,具体代码如下:
public static void nioSelectorServer() throws Exception{
//1。创建Selector
Selector selector = Selector.open();
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false);//默认是true,设置为false就是非阻塞模式(下面的accept方法就会变成非阻塞)
//2。建立selector 和 channel 的联系(注册)
//SelectionKey 就是将来事件发生后,通过它可以知道事件和哪个channel的事件
SelectionKey ssckey = ssc.register(selector, 0, null);
// key 只关注 accept 事件
ssckey.interestOps(SelectionKey.OP_ACCEPT);
log.info("register key:{}",ssckey);
ssc.bind(new InetSocketAddress(8080));
while (true) {
//3.select当啊,没有事件发生,线程阻塞。有事件发生,线程才会恢复运行
//在事件未处理时,select不会阻塞。( 处理有两种方法:key.cancel() 和 key.chaneel().accept() )
selector.select();
//4.处理事件,selectedKeys 内部包含了所有发生的事件
Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
while (iter.hasNext()) {
SelectionKey key = iter.next();
//!!!处理key时,要从selectedKey集合中删除,否则下厨处理可能抛出空指针(至于为什么可以看image/1.png)
iter.remove();
log.info("key: {}",key);
//5.区分事件类型
if (key.isAcceptable()) {
//如果下面三行取消,whilet(true)代码块就会一直运行,打印上面的log
ServerSocketChannel channel = (ServerSocketChannel)key.channel();
SocketChannel sc = channel.accept();//处理事件
sc.configureBlocking(false);
SelectionKey scKey = sc.register(selector, 0, null);
scKey.interestOps(SelectionKey.OP_READ);
log.info("{}",sc);
}else if (key.isReadable()) {
try {
SocketChannel channel = (SocketChannel) key.channel();//拿到触发事件的channel
ByteBuffer buffer = ByteBuffer.allocate(16);
int read = channel.read(buffer);//如果是正常断开,read方法返回值是-1
if (read == -1){
key.cancel();//!!!!
}else {
buffer.flip();
System.out.println(Charset.defaultCharset().decode(buffer));
}
}catch (IOException e) {
e.printStackTrace();
//!!!因为客户端断开了,因此需要将key取消(从selector 的 keys 集合中真正删除key)
key.cancel();
}
}
}
}
}
从上述代码中,我们可以看到几个重要的步骤:
二、Bind源码分析
1.大体观摩
下面是我编写的netty版本server端的demo
咱可以重点看一下doBind
方法中调用的下面这两个方法,注意线程是怎么从main线程变成Nio线程的
2.initAndRegister方法
2.1 initAndRegister
方法可以大体上看成下面两个部分,上面是init,下面是register
2.2 init相关步骤
2.2.1往下追channelFactory.newChannel()
的底层,发现是init其实就是通过反射的方式调用构造方法:
2.2.2上面这个初始化的方法其实会调用到NioServerSocketChannel
的构造方法中去,下面是其构造方法的部分源码:
从中可以得知nio原生的ServerSocketChannel.open
操作是在此执行的。
2.2.3回到initAndRegister
方法,咱们在看看其调用的init()做了什么事情?
答:主要就是给NioServerSocketChanel添加了一个handler处理器
小结:init相关操作主要做了两件事:
1.创建出了NioServerSocketChannel对象,ServerSocketChannel.open()
2.为NioServerSocketChannel对象添加了一个handler处理器,等待后续调用执行
2.3 register相关步骤
2.3.1回到initAndRegister
方法,咱继续看 config().group().register(channel);
方法的底层,需要追很深哦
可以发现在这里面发生了线程的切换,注意切换为Nio线程执行时,main线程会继续往下执行,这里先提一嘴留个影响。
2.3.2继续追register0,首先看其内部调用的doRegister
从中可以得知nio原生的ServerSocketChannel的register方法
在此执行
2.3.3继续看register0中调用的 pipeline.invokeHandlerAddedIfNeeded()
的源码
这个方法其实就是执行了2.2.3步骤中添加的handler,为ServerSocketChannel添加acceptHandler,当accept事件发生后建立链接
2.3.4继续看register0中调用的 safeSetSuccess(promise)
这个方法的作用是给promise对象赋值,好让doBind方法中的main线程继续执行后续逻辑
小结:register相关操作主要做了3件事:
1.将serverSocketChannel绑定到了Selector上
2.执行了前面init方法中准备好的handler,目的是给NioServerSocketChannel添加一个关注accept事件的handler
3.当前面两部都完成后,让dobind方法中的main线程继续执行doBind0
3.doBind0方法
上面2.3.4可以得知执行到这一步时NioServerSocket以及绑定了一个Accept事件
dobind0其底层调用到了AbstractChannel中的bind方法
3.1dobind方法底层
底层相当于就是执行nio原生的ServerSocketChannel的bind方法
3.2invokeLater方法底层
它会去执行每个handler的active方法,重点是看head头节点的active
从其底层可以看出是调用到了nio的原生方法selectionKey.interestOps
,让selector关注到accept事件
小结:dobind0主要做了2件事:
1.执行ServerSocketChannel的bind方法
,绑定端口号
2.执行selectionKey.interestOps
,关注Accept事件