前言:
前一篇中我们介绍了NioServerSocketChannel,NioServerSocketChannel主要负责两件事:绑定(bind)到本地port,作为一个Endpoint;监听客户端连接事件,将获取到的连接注册到EventLoop中。
那么数据的读写呢?NioServerSocketChannel不负责数据的读写,那么数据读写都交由NioSocketChannel来负责。
本文主要讲解NioSocketChannel 处理连接(connect)、读(read)事件。
1.NioSocketChannel类结构图
同样,直接开启Diagrams视角。
与前面NioServerSocketChannel有很多相同的接口和抽象类,AbstractChannel、AbstractNioChannel、SocketChannel等前一篇文章中,都已经有说明,本文不再赘述。
2.AbstractNioByteChannel解析
public abstract class AbstractNioByteChannel extends AbstractNioChannel {
// 构造方法,OP_READ代表NioSocketChannel关注的事件,在注册EventLoop时会将监听事件也传入
protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {
super(parent, ch, SelectionKey.OP_READ);
}
// 同之前的分析一样,都包含一个NioByteUnsafe
protected class NioByteUnsafe extends AbstractNioUnsafe {
// 读取对端传入的数据
@Override
public final void read() {
final ChannelConfig config = config();
// 如果输入流已经关闭,则移除OP_READ监听
if (shouldBreakReadReady(config)) {
clearReadPending();
return;
}
final ChannelPipeline pipeline = pipeline();
final ByteBufAllocator allocator = config.getAllocator();
final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
allocHandle.reset(config);
ByteBuf byteBuf = null;
boolean close = false;
try {
do {
// 从ByteBufAllocator分配器中获取ByteBuf
byteBuf = allocHandle.allocate(allocator);
// doReadBytes()方法负责从channel中读取数据,交由子类NioSocketChannel实现
allocHandle.lastBytesRead(doReadBytes(byteBuf));
if (allocHandle.lastBytesRead() <= 0) {
// 没有可读数据,则释放ByteBuf
byteBuf.release();
byteBuf = null;
close = allocHandle.lastBytesRead() < 0;
if (close) {
readPending = false;
}
break;
}
allocHandle.incMessagesRead(1);
readPending = false;
// 获取到可读数据后,触发ChannelPipeline的读事件
pipeline.fireChannelRead(byteBuf);
byteBuf = null;
} while (allocHandle.continueReading());
allocHandle.readComplete();
pipeline.fireChannelReadComplete();
if (close) {
closeOnRead(pipeline);
}
} catch (Throwable t) {
handleReadException(pipeline, byteBuf, t, close, allocHandle);
} finally {
if (!readPending && !config.isAutoRead()) {
removeReadOp();
}
}
}
}
...
// 还有一系列的write方法,不是本文关注的重点呢,下一篇会介绍
protected final int doWrite0(ChannelOutboundBuffer in) throws Exception {}
}
可以看到,AbstractNioByteChannel还是做了很多事情的,读写模板方法都在这里了。
读方法在NioByteUnsafe中,主要做了两件事情:从channel中读取数据到ByteBuf中;将读取到的ByteBuf交由ChannelPipeline处理,触发器read事件,进而触发其ChannelHandler.read方法
3.NioSocketChannel解析
public class NioSocketChannel extends AbstractNioByteChannel implements io.netty.channel.socket.SocketChannel {
// 用于创建SocketChannel的默认SelectorProvider
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);
}
}
// 连接到远端
protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
if (localAddress != null) {
doBind0(localAddress);
}
boolean success = false;
try {
// 调用原生的socketChannel.connect(remoteAddress),还是NIO那一套
boolean connected = SocketUtils.connect(javaChannel(), remoteAddress);
if (!connected) {
selectionKey().interestOps(SelectionKey.OP_CONNECT);
}
success = true;
return connected;
} finally {
if (!success) {
doClose();
}
}
}
// 从channel中读取数据
// 接上面AbstractNioByteChannel.read方法,主要实现由以下来进行
protected int doReadBytes(ByteBuf byteBuf) throws Exception {
final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
allocHandle.attemptedBytesRead(byteBuf.writableBytes());
// 直接调用ByteBuf.writeBytes方法,将SocketChannel的可读数据写入到ByteBuf中
return byteBuf.writeBytes(javaChannel(), allocHandle.attemptedBytesRead());
}
/** 输入输出流状态判断
* 主要还是调用原生的Socket来判断
*/
@Override
public boolean isActive() {
SocketChannel ch = javaChannel();
return ch.isOpen() && ch.isConnected();
}
@Override
public boolean isOutputShutdown() {
return javaChannel().socket().isOutputShutdown() || !isActive();
}
@Override
public boolean isInputShutdown() {
return javaChannel().socket().isInputShutdown() || !isActive();
}
// NioSocketChannelUnsafe基本没啥方法了,可以忽略
private final class NioSocketChannelUnsafe extends NioByteUnsafe {
// 将当前channel的关注事件从EventLoop中移除
protected Executor prepareToClose() {}
}
}
读事件本身比较简单,就是将数据从Channel中读取到ByteBuf中。
总结:
有了前面文章的分析之后,再来分析NioSocketChannel就比较简单了。
老规矩,通过两张时序图来说明下NioSocketChannel 连接(connect)、读取数据的整个过程
1.NioSocketChannel连接远端过程
连接远端的起点肯定就是Bootstrap.connect(ip,port)方法了。时序图如下所示:
最终的实现连接的的方法还是调用NioSocketChannel.doConnect()
public class NioSocketChannel extends AbstractNioByteChannel implements io.netty.channel.socket.SocketChannel {
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();
}
}
}
}
2.NioSocketChannel接收(read)消息
客户端从哪里接收消息呢,有了之前分析NioServerSocketChannel的经验,我们就知道,是从EventLoop中。时序图如下:
最关键的解析Channel中的输入流信息到ByteBuf中,该动作是由NioSocketChannel来执行的
public class NioSocketChannel extends AbstractNioByteChannel implements io.netty.channel.socket.SocketChannel {
protected int doReadBytes(ByteBuf byteBuf) throws Exception {
final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
allocHandle.attemptedBytesRead(byteBuf.writableBytes());
return byteBuf.writeBytes(javaChannel(), allocHandle.attemptedBytesRead());
}
}
后续会将ByteBuf交由一系列自定义的ChannelHandler来操作。