创建NioSocketChannel其实和创建服务端的NioServerSocketChannel类似,从上一篇文章的new NioSocketChannel(this, ch)这里进入,主要做了两件事:
1、调用父类构造函数AbstractNioByteChannel(p,ch,op_read)
1)设置configureBlocking(false),并且把传进来的OP_READ事件保存起来(保存了还没有注册)
2)create id,unsafe,pipeline
2、新建new NioSocketChannelConfig()
主要是调用了这个函数setTcpNoDelay(true)
源码如下:
public NioSocketChannel(Channel parent, SocketChannel socket) {
super(parent, socket);
config = new NioSocketChannelConfig(this, socket.socket());
}
是不是和创建服务端NioServerSocketChannel很像呢?
在这里,和服务端不同的是,客户端是直接调用new产生NioSocketChannel,而服务端是通过反射的方式(为什么会是这样设计呢?new出来的对象我们无法访问其中的私有属性,但是通过反射出来的对象我们可以通过setAccessible()方法来访问其中的私有属性,前面我们要优化keySet,所以需要用到反射获取一些field?参考)。
一、调用父类构造函数AbstractNioByteChannel(p,ch,op_read)
先看调用父类的构造方法:
protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {
super(parent, ch, SelectionKey.OP_READ);
}
这里注意了,直接产生了一个OP_READ事件进去。因为这个是客户端的,所以客户端只用读事件,如果是服务端,那就是OP_ACCEPT事件。然后继续进入:
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
super(parent);
this.ch = ch;
this.readInterestOp = readInterestOp;
try {
ch.configureBlocking(false);
} catch (IOException e) {
try {
ch.close();
} catch (IOException e2) {
if (logger.isWarnEnabled()) {
logger.warn(
"Failed to close a partially initialized socket.", e2);
}
}
throw new ChannelException("Failed to enter non-blocking mode.", e);
}
}
this.ch = ch(这个ch就是之前我们javaChannel.accept()得到的channel);把我们之前创建的jdk的socketChannel绑定到我们的这个服务端SocketChannel这里,并且把刚刚创建的readInterestOps(对读事件感兴趣)事件也传入了进来。
下面有一个ch.configureBlocking(false)就是设置非阻塞模式。
我们还要继续从super(parent);进入:
protected AbstractChannel(Channel parent) {
this.parent = parent;
id = newId();
unsafe = newUnsafe();
pipeline = newChannelPipeline();
}
如果是客户端的NioSocketChannel,会有一个parent,就是我们服务端NioServerSocketChannel
id不用说了,这个unsafe就是NioSocketChannel用来负责处理底层该数据的读写。NioServerSocketChannel的实现是NioMessageUnsafe,而NioSocketChannel的实现是NioByteUnsafe,因为服务端处理的事件是连接时间,而客户端处理的是读事件,所以有所不同。
pipeline就是处理逻辑链,后面的文章会继续深入。
二、新建new NioSocketChannelConfig()
这个congfigure就是设置tcp的一些连接参数。
从这个地方config = new NioSocketChannelConfig(this, socket.socket());进入:
public DefaultSocketChannelConfig(SocketChannel channel, Socket javaSocket) {
super(channel);
if (javaSocket == null) {
throw new NullPointerException("javaSocket");
}
this.javaSocket = javaSocket;
// Enable TCP_NODELAY by default if possible.
if (PlatformDependent.canEnableTcpNoDelayByDefault()) {
try {
setTcpNoDelay(true);
} catch (Exception e) {
// Ignore.
}
}
}
找到setTcpNoDelay这个方法进去
@Override
public SocketChannelConfig setTcpNoDelay(boolean tcpNoDelay) {
try {
javaSocket.setTcpNoDelay(tcpNoDelay);
} catch (SocketException e) {
throw new ChannelException(e);
}
return this;
}
这里就是调用jdk底层不使用Nagle算法。关于这个算法:为了提高吞吐量,把小数据包集合成大数据包一起发过去。
但是这样会提高延时,netty默认设置了禁用此算法,可以降低延时。
看这个方法PlatformDependent.canEnableTcpNoDelayByDefault()的实现:
public static boolean canEnableTcpNoDelayByDefault() {
return CAN_ENABLE_TCP_NODELAY_BY_DEFAULT;
}
private static final boolean CAN_ENABLE_TCP_NODELAY_BY_DEFAULT = !isAndroid();
也就是,当不是安卓的时候,就设置优化。另外一个细节就是,netty居然也支持安卓。