NioSocketChannel
public class NioSocketChannel extends AbstractNioByteChannel implements io.netty.channel.socket.SocketChannel {
private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider();
private static SocketChannel newSocket(SelectorProvider provider) {
try {
return provider.openSocketChannel();
} catch (IOException e) {
throw new ChannelException("Failed to open a socket.", e);
}
}
public NioSocketChannel() {
this(DEFAULT_SELECTOR_PROVIDER);
}
public NioSocketChannel(SelectorProvider provider) {
this(newSocket(provider));
}
public NioSocketChannel(SocketChannel socket) {
this(null, socket);
}
public NioSocketChannel(Channel parent, SocketChannel socket) {
super(parent, socket);
config = new NioSocketChannelConfig(this, socket.socket());
}
从上面代码中可以看出 NioSocketChannel 封装了 Nio 中的 SocketChannel。
SocketChannel 是通过 SelectorProvider.provider().openSocketChannel() 创建的。
@Override
public ServerSocketChannel parent() {
return (ServerSocketChannel) super.parent();
}
NioSocketChannel.parent() 是 NioServerSocketChannel。
如果是 NioServerSocketChannel.parent() 为 null。
@Override
protected SocketChannel javaChannel() {
return (SocketChannel) super.javaChannel();
}
javaChannel() 获取的是 Nio 的 SocketChannel。
@Override
public boolean isActive() {
SocketChannel ch = javaChannel();
return ch.isOpen() && ch.isConnected();
}
查看 SocketChannel 是否是open,并且是已经连接状态。
@Override
public boolean isOutputShutdown() {
return javaChannel().socket().isOutputShutdown() || !isActive();
}
查看 SocketChannel 写是否关闭。
调用 SocketChannel.shutdownOutput() 可用关闭写操作。
@Override
public boolean isInputShutdown() {
return javaChannel().socket().isInputShutdown() || !isActive();
}
查看 SocketChannel 读是否关闭。
调用 SocketChannel.shutdownInput() 可用关闭读操作。
@Override
public boolean isShutdown() {
Socket socket = javaChannel().socket();
return socket.isInputShutdown() && socket.isOutputShutdown() || !isActive();
}
@Override
public boolean isActive() {
SocketChannel ch = javaChannel();
return ch.isOpen() && ch.isConnected();
}
判断SocketChannel 是否是Shutdown 状态:如果 SocketChannel 不支持读写 或者不是打开和连接状态
@Override
public InetSocketAddress localAddress() {
return (InetSocketAddress) super.localAddress();
}
@Override
public InetSocketAddress remoteAddress() {
return (InetSocketAddress) super.remoteAddress();
}
获取 Socket 的本地地址和远端地址
@Override
protected void doBind(SocketAddress localAddress) throws Exception {
doBind0(localAddress);
}
private void doBind0(SocketAddress localAddress) throws Exception {
if (PlatformDependent.javaVersion() >= 7) {
SocketUtils.bind(javaChannel(), localAddress);
} else {
SocketUtils.bind(javaChannel().socket(), localAddress);
}
}
调用 SocketChannel 进行绑定 IP 地址。
@Override
protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
if (localAddress != null) {
doBind0(localAddress);
}
boolean success = false;
try {
boolean connected = SocketUtils.connect(javaChannel(), remoteAddress);
if (!connected) {
selectionKey().interestOps(SelectionKey.OP_CONNECT);
}
success = true;
return connected;
} finally {
if (!success) {
doClose();
}
}
}
客户端向服务端发起连接操作。
1、首先绑定 本地IP
2、然后发起连接,返回连接的结果true、false。
3、如果未连接成功,则向 Selector 上注册 OP_CONNECT 事件。
连接结果有 3 种可能
1、连接成功,返回true
2、连接未成功,返回false,表示已经发送连接请求还未收到连接成功的状态。
3、连接网络异常,在 finally 中关闭该连接。
@Override
protected void doDisconnect() throws Exception {
doClose();
}
@Override
protected void doClose() throws Exception {
super.doClose();
javaChannel().close();
}
关闭 SocketChannel 连接。
@Override
protected int doReadBytes(ByteBuf byteBuf) throws Exception {
final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
allocHandle.attemptedBytesRead(byteBuf.writableBytes());
return byteBuf.writeBytes(javaChannel(), allocHandle.attemptedBytesRead());
}
从 SocketChannel 中读取数据到 byteBuf 中
@Override
protected int doWriteBytes(ByteBuf buf) throws Exception {
final int expectedWrittenBytes = buf.readableBytes();
return buf.readBytes(javaChannel(), expectedWrittenBytes);
}
buf 数据写入到 SocketChannel 中
@Override
protected long doWriteFileRegion(FileRegion region) throws Exception {
final long position = region.transferred();
return region.transferTo(javaChannel(), position);
}
NioSocketChannel 除了支持写 ByteBuf ,还支持写 FileRegion类型。
FileRegion 是使用 FileChannel.transferTo() 直接写 Socket的,使用的零拷贝。
@Override
protected void doWrite(ChannelOutboundBuffer in) throws Exception {
SocketChannel ch = javaChannel();
int writeSpinCount = config().getWriteSpinCount();
do {
// 1、写缓存待写入的字节数为0,则取消写事件
if (in.isEmpty()) {
// All written so clear OP_WRITE
clearOpWrite();
// Directly return here so incompleteWrite(...) is not called.
return;
}
// Ensure the pending writes are made of ByteBufs only.
int maxBytesPerGatheringWrite = ((NioSocketChannelConfig) config).getMaxBytesPerGatheringWrite();
ByteBuffer[] nioBuffers = in.nioBuffers(1024, maxBytesPerGatheringWrite);
// 2、获取 ChannelOutboundBuffer 中 ByteBuf 的数量
int nioBufferCnt = in.nioBufferCount();
// Always us nioBuffers() to workaround data-corruption.
// See https://github.com/netty/netty/issues/2761
switch (nioBufferCnt) {
case 0:
// 3、如果nioBuffers个数为0,可能是其他内容的数据,比如FileRegion,则调用父类AbstractNioByteChannel的doWriter方法
writeSpinCount -= doWrite0(in);
break;
case 1: {
// 4、如果只有一个ByteBuffer,则不需要使用nio的 gathering write。
// Zero length buffers are not added to nioBuffers by ChannelOutboundBuffer, so there is no need
// to check if the total size of all the buffers is non-zero.
ByteBuffer buffer = nioBuffers[0];
int attemptedBytes = buffer.remaining();
final int localWrittenBytes = ch.write(buffer);
if (localWrittenBytes <= 0) {
incompleteWrite(true);
return;
}
adjustMaxBytesPerGatheringWrite(attemptedBytes, localWrittenBytes, maxBytesPerGatheringWrite);
in.removeBytes(localWrittenBytes);
--writeSpinCount;
break;
}
default: {
// 5、如果有多个 ByteBuf 则使用 Nio 的 gather 聚合写入
// to check if the total size of all the buffers is non-zero.
// We limit the max amount to int above so cast is safe
long attemptedBytes = in.nioBufferSize();
final long localWrittenBytes = ch.write(nioBuffers, 0, nioBufferCnt);
if (localWrittenBytes <= 0) {
incompleteWrite(true);
return;
}
// Casting to int is safe because we limit the total amount of data in the nioBuffers to int above.
adjustMaxBytesPerGatheringWrite((int) attemptedBytes, (int) localWrittenBytes,
maxBytesPerGatheringWrite);
in.removeBytes(localWrittenBytes);
--writeSpinCount;
break;
}
}
} while (writeSpinCount > 0);
incompleteWrite(writeSpinCount < 0);
}
1、判断 ChannelOutboundBuffer 中的链表数据是否为空,如果为空,则取消注册在 Selector 上的写事件。
2、判断 ChannelOutboundBuffer 中待写的 ByteBuf 数量。
3、ByteBuf 个数为0,可能待写入的类型不是 ByteBuf,而是 FileRegion 类型的。则调用 AbstractNioByteChannel.doWrite0() 尝试写入 FileRegion 类型的数据。FileRegion 类型的数据以后文章在分析。
4、如果 ByteBuf 个数为1,则不使用 Nio 的 gather 聚合 Buffer 写入,而是直接写入就可以。
5、如果 ByteBuf 个数大于1,则使用 Nio 的gather 聚合 Buffer 写入,一次就可以多去多个 Buffer 的数据。
NIO 知识点 Gather 和 Scatter
聚集(gather)写入Channel是指在写操作时将多个buffer的数据写入同一个Channel,因此,Channel 将多个Buffer中的数据“聚集(gather)”后发送到Channel。
分散(scatter)从Channel中读取是指在读操作时将读取的数据写入多个buffer中。因此,Channel将从Channel中读取的数据“分散(scatter)”到多个Buffer中。
NioSocketChannelConfig
private final class NioSocketChannelConfig extends DefaultSocketChannelConfig {
private volatile int maxBytesPerGatheringWrite = Integer.MAX_VALUE;
private NioSocketChannelConfig(NioSocketChannel channel, Socket javaSocket) {
super(channel, javaSocket);
calculateMaxBytesPerGatheringWrite();
}
@Override
protected void autoReadCleared() {
clearReadPending();
}
@Override
public NioSocketChannelConfig setSendBufferSize(int sendBufferSize) {
super.setSendBufferSize(sendBufferSize);
calculateMaxBytesPerGatheringWrite();
return this;
}
@Override
public boolean setOption(ChannelOption option, T value) {
if (PlatformDependent.javaVersion() >= 7 && option instanceof NioChannelOption) {
return NioChannelOption.setOption(jdkChannel(), (NioChannelOption) option, value);
}
return super.setOption(option, value);
}
@Override
public T getOption(ChannelOption option) {
if (PlatformDependent.javaVersion() >= 7 && option instanceof NioChannelOption) {
return NioChannelOption.getOption(jdkChannel(), (NioChannelOption) option);
}
return super.getOption(option);
}
@SuppressWarnings("unchecked")
@Override
public Map, Object> getOptions() {
if (PlatformDependent.javaVersion() >= 7) {
return getOptions(super.getOptions(), NioChannelOption.getOptions(jdkChannel()));
}
return super.getOptions();
}
void setMaxBytesPerGatheringWrite(int maxBytesPerGatheringWrite) {
this.maxBytesPerGatheringWrite = maxBytesPerGatheringWrite;
}
int getMaxBytesPerGatheringWrite() {
return maxBytesPerGatheringWrite;
}
private void calculateMaxBytesPerGatheringWrite() {
// Multiply by 2 to give some extra space in case the OS can process write data faster than we can provide.
int newSendBufferSize = getSendBufferSize() << 1;
if (newSendBufferSize > 0) {
setMaxBytesPerGatheringWrite(getSendBufferSize() << 1);
}
}
private SocketChannel jdkChannel() {
return ((NioSocketChannel) channel).javaChannel();
}
}
NioSocketChannelConfig 是 NioSocketChannel 的配置类。
1、设置 Socket 参数
2、设置gather 最大写入数据
设置 Socket 参数
首先调用 DefaultSocketChannelConfig.setOption() 进行设置
@Override
public boolean setOption(ChannelOption option, T value) {
validate(option, value);
if (option == SO_RCVBUF) {
setReceiveBufferSize((Integer) value);
} else if (option == SO_SNDBUF) {
setSendBufferSize((Integer) value);
} else if (option == TCP_NODELAY) {
setTcpNoDelay((Boolean) value);
} else if (option == SO_KEEPALIVE) {
setKeepAlive((Boolean) value);
} else if (option == SO_REUSEADDR) {
setReuseAddress((Boolean) value);
} else if (option == SO_LINGER) {
setSoLinger((Integer) value);
} else if (option == IP_TOS) {
setTrafficClass((Integer) value);
} else if (option == ALLOW_HALF_CLOSURE) {
setAllowHalfClosure((Boolean) value);
} else {
return super.setOption(option, value);
}
return true;
}
这些参数都是 socket 参数,具体详见 socket 参数说明。
DefaultChannelConfig.setOption()
@Override
@SuppressWarnings("deprecation")
public boolean setOption(ChannelOption option, T value) {
validate(option, value);
if (option == CONNECT_TIMEOUT_MILLIS) {
setConnectTimeoutMillis((Integer) value);
} else if (option == MAX_MESSAGES_PER_READ) {
setMaxMessagesPerRead((Integer) value);
} else if (option == WRITE_SPIN_COUNT) {
setWriteSpinCount((Integer) value);
} else if (option == ALLOCATOR) {
setAllocator((ByteBufAllocator) value);
} else if (option == RCVBUF_ALLOCATOR) {
setRecvByteBufAllocator((RecvByteBufAllocator) value);
} else if (option == AUTO_READ) {
setAutoRead((Boolean) value);
} else if (option == AUTO_CLOSE) {
setAutoClose((Boolean) value);
} else if (option == WRITE_BUFFER_HIGH_WATER_MARK) {
setWriteBufferHighWaterMark((Integer) value);
} else if (option == WRITE_BUFFER_LOW_WATER_MARK) {
setWriteBufferLowWaterMark((Integer) value);
} else if (option == WRITE_BUFFER_WATER_MARK) {
setWriteBufferWaterMark((WriteBufferWaterMark) value);
} else if (option == MESSAGE_SIZE_ESTIMATOR) {
setMessageSizeEstimator((MessageSizeEstimator) value);
} else if (option == SINGLE_EVENTEXECUTOR_PER_GROUP) {
setPinEventExecutorPerGroup((Boolean) value);
} else {
return false;
}
return true;
}