使用Netty创建客户端的时候, 和创建服务端类似, 也需要经过创建-初始化-注册这三步, 最后一步也最重要就是连接操作, io.netty.bootstrap.Bootstrap#connect(java.net.SocketAddress)
创建就是创建NioSocketChannel, 同时也会创建unsafe,pipeline,config等. 还会设置一个感兴趣的SelectionKey.OP_READ 读事件属性, 此时也仅仅是把OP_READ保存到一个属性上.
初始化就是给channel设置一些option和attribute.
注册就是将channel注册到对应的NioEventLoop的selector上.
这个连接操作会调用到JDK的channel.connect方法, 然而连接操作实际就是向服务端发启TCP三次握手, 握手是有网络延时的, 而Netty是非阻塞的网络框架. Netty调用connect方法, 发起连接之后就返回了, 但是它会向channel中注册一个感兴趣的连接事件.
源码位置: io.netty.channel.socket.nio.NioSocketChannel#doConnect
等到三次握手完成之后, Netty客户端就会监听到连接事件. (Netty底层会有一个IO线程, 一直轮询着外部的事件)
源码位置: io.netty.channel.nio.NioEventLoop#processSelectedKey(java.nio.channels.SelectionKey, io.netty.channel.nio.AbstractNioChannel)
io.netty.channel.DefaultChannelPipeline.HeadContext#channelActive
io.netty.channel.DefaultChannelPipeline.HeadContext#readIfIsAutoRead
io.netty.channel.DefaultChannelPipeline.HeadContext#read
io.netty.channel.nio.AbstractNioChannel#doBeginRead
protected void doBeginRead() throws Exception {
final SelectionKey selectionKey = this.selectionKey;
if (!selectionKey.isValid()) {
return;
}
readPending = true;
final int interestOps = selectionKey.interestOps();
if ((interestOps & readInterestOp) == 0) {
// 设置之前保存起来的事件, 例如OP_READ
selectionKey.interestOps(interestOps | readInterestOp);
}
}
再经过以上四个主要方法, 最后就会设置感兴趣的OP_READ事件了. 这样客户端才可以读取数据.
【总结】
客户端在向服务器发起连接请求的时候, 由于网络等原因, 连接不会马上成功, Netty是非阻塞框架. 因此在发起连接之后就返回了, 同时设置一个感兴趣的OPCONNECT事件, 等三次握手成功之后, Netty监听到OPCONNECT事件, 然后才会把设置之前的OP_READ事件, 这个时候客户端才可以读取网络数据.