上一篇讲到netty和服务器建立连接的所有过程,接着上一篇的结尾,看代码
private static void doConnect(
final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise connectPromise) {
final Channel channel = connectPromise.channel();
标记: channel.eventLoop().execute(new Runnable() {
@Override
public void run() {
if (localAddress == null) {
channel.connect(remoteAddress, connectPromise);
} else {
channel.connect(remoteAddress, localAddress, connectPromise);
}
connectPromise.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
}
});
}
看标记处,此处处直接加入队列一个任务执行连接,好找到我们的管道( channel 即是 NioSocketChannel ), eventLoop() 返回的就是 NioEventLoopGroup ,进入,在它的父类找到这个方法
@Override
public void execute(Runnable command) {
next().execute(command);
}
上一篇已经讲过这个类next()方法获取的是NioEventLoop,那么这个类就是负责整个读写事件分发的最重要的一个类了,跟进
@Override
public void execute(Runnable task) {
if (task == null) {
throw new NullPointerException("task");
}
//判断当前处理任务的线程是否启动
boolean inEventLoop = inEventLoop();
//没有启动的话开启线程,启动的话直接加入任务
if (inEventLoop) {
addTask(task);
} else {
标记: startThread();
addTask(task);
//加入线程池关闭了,移除任务,抛拒绝任务的异常
if (isShutdown() && removeTask(task)) {
reject();
}
}
//唤醒线程继续处理任务
if (!addTaskWakesUp && wakesUpForTask(task)) { wakeup(inEventLoop); } }
看标记处现在最需要关心的就是启动线程了,
private void doStartThread() {
assert thread == null;
executor.execute(new Runnable() {
@Override
public void run() {
thread = Thread.currentThread();
//线程是否被打断
if (interrupted) {
thread.interrupt();
}
boolean success = false;
//更新最后一次启动的时间
updateLastExecutionTime(); try {
//执行当前的run方法,进入这个方法
SingleThreadEventExecutor.this.run();
success = true;
最后在NioEventLoop中发现最终处理任务的方法,如下代码
protected void run() {
for (;;) {
try {
switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
case SelectStrategy.CONTINUE:
continue;
case SelectStrategy.SELECT:
select(wakenUp.getAndSet(false));
if (wakenUp.get()) {
selector.wakeup();
}
default:
// fallthrough
}
cancelledKeys = 0;
needsToSelectAgain = false;
final int ioRatio = this.ioRatio;
if (ioRatio == 100) {
try {
标记: processSelectedKeys();
} finally {
// Ensure we always run tasks.
runAllTasks();
}
} else {
final long ioStartTime = System.nanoTime();
try {
标记: processSelectedKeys();
} finally { // Ensure we always run tasks.
final long ioTime = System.nanoTime() - ioStartTime;
runAllTasks(ioTime * (100 - ioRatio) / ioRatio); } } }
catch (Throwable t) {
handleLoopException(t); }
// Always handle shutdown even if the loop processing threw an exception. try {
if (isShuttingDown()) {
closeAll();
if (confirmShutdown()) { return; } } }
catch (Throwable t)
{ handleLoopException(t); } } }
看标记处,其他的方法都是一些无关紧要的判断,真正开始读写消息了
private void processSelectedKeysPlain(Set<SelectionKey> selectedKeys) {
//selectedKeys集合是第一篇当中连接之前,注册的一些key值,nio通信基础,不熟悉的小伙伴请自行查找
if (selectedKeys.isEmpty()) {
return;
}
Iterator<SelectionKey> i = selectedKeys.iterator();
//死循环,不断监测通道信息,是否可读,可写
for (;;) {
final SelectionKey k = i.next();
final Object a = k.attachment();
i.remove();
//a肯定属于AbstractNioChannel
if (a instanceof AbstractNioChannel)
{ 标记:
processSelectedKey(k, (AbstractNioChannel) a);
}
else {
@SuppressWarnings("unchecked")
NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a; processSelectedKey(k, task);
}
if (!i.hasNext()) { break; }
if (needsToSelectAgain) {
selectAgain(); selectedKeys = selector.selectedKeys();
// Create the iterator again to avoid ConcurrentModificationException
if (selectedKeys.isEmpty()) { break; }
else {
i = selectedKeys.iterator();
}
}
}
}
不容易啊,最终终于找到最核心的方法
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
if (!k.isValid()) {
final EventLoop eventLoop;
try {
eventLoop = ch.eventLoop();
} catch (Throwable ignored) {
return;
}
if (eventLoop != this || eventLoop == null) {
return;
}
// close the channel if the key is not valid anymore
unsafe.close(unsafe.voidPromise());
return;
}
try {
int readyOps = k.readyOps();
//已经连接上,nio收到这个通知
if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
int ops = k.interestOps();
ops &= ~SelectionKey.OP_CONNECT;
k.interestOps(ops);
unsafe.finishConnect();
}
//已经可以写的时候收到这个通知
if ((readyOps & SelectionKey.OP_WRITE) != 0) {
ch.unsafe().forceFlush();
}
//最后可以读的时候收到这个通知
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
unsafe.read();
}
} catch (CancelledKeyException ignored) {
unsafe.close(unsafe.voidPromise());
}
}
终于找到入口点,unsafe又是什么鬼,曹曹曹.....,我想说整这么多类真的好吗,真正这相当于一个中介啊,搞了半天真正执行方法的是unsafe,什么鬼啊
public interface NioUnsafe extends Unsafe {
/**
* Return underlying {@link SelectableChannel}
*/
SelectableChannel ch();
/**
* Finish connect
*/
void finishConnect();
/**
* Read from underlying {@link SelectableChannel}
*/
void read();
void forceFlush();
}
一个接口,好,通道里面持有这个接口,再次进入NioSocketChannel
protected AbstractNioUnsafe newUnsafe() {
return new NioSocketChannelUnsafe();
}
最后居然是NioSocketChannelUnsafe类,隐藏够深的,我只能说写的真好
真正的读终于开始了 public final void read() {
//获取配置文件 config = new NioSocketChannelConfig(this, socket.socket());
final ChannelConfig config = config();
//得到当前的管道
final ChannelPipeline pipeline = pipeline();
//allocator 是AdaptiveRecvByteBufAllocator
//ByteBuffer的封装类最终靠他解析字节并返回给客户端的
final ByteBufAllocator allocator = config.getAllocator();
//allocHandle 最终是通过FixedRecvByteBufAllocator获取的
标记1: final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
allocHandle.reset(config);
ByteBuf byteBuf = null;
boolean close = false;
/循环开始读
try {
do
{
标记2: byteBuf = allocHandle.allocate(allocator);
/向byteBuf里填充读取数据
标记3: allocHandle.lastBytesRead(doReadBytes(byteBuf));
if (allocHandle.lastBytesRead() <= 0)
{ // nothing was read. release the buffer.
byteBuf.release();
byteBuf = null;
close = allocHandle.lastBytesRead() < 0;
break;
}
allocHandle.incMessagesRead(1);
readPending = false;
标记:最重要的方法开始分发管道了 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();
}
}
}
}
看标记1怎么获取到handler的 ,最终是获得 HandleImpl 这个类
public Handle newHandle() {
return new HandleImpl(bufferSize);
}
那么标记2的allocHandle.allocate(allocator);默认申请直接内存2048个字节的ByteBuf ,不了解java的nio的请自行百度ByteBuf的用法。
看标记3,我们进入doReadBytes(byteBuf)的方法
protected int doReadBytes(ByteBuf byteBuf)
throws Exception {
//妈的刚转到NioSocketChannel又转回NioSocketChannelUnsafe
final RecvByteBufAllocator.Handle allocHandle =unsafe().recvBufAllocHandle();
//byteBuf还有多少空间可写,即position离limit的距离,写入HandleImpl的attemptedBytesRead属性中,做记录
allocHandle.attemptedBytesRead(byteBuf.writableBytes());
//真正的读取
return byteBuf.writeBytes(javaChannel(), allocHandle.attemptedBytesRead());
}
好了现在该返回上一个方法了,即allocHandle.lastBytesRead(doReadBytes(byteBuf));方法,进入该方法
public final void lastBytesRead(int bytes) {
//最后一次读取的字节的个数
lastBytesRead = bytes;
if (bytes > 0) {
//总共读取的字节数
totalBytesRead += bytes;
}
}
这个方法的主要作用是记录从流中读了多少数据,那么现在思路清晰多了,HandleImpl类就是用来做记录用的一个辅助类
//如果读取的的字节为0的话,那么释放byteBuf
if (allocHandle.lastBytesRead() <= 0) {
// nothing was read. release the buffer.
byteBuf.release();
byteBuf = null;
close = allocHandle.lastBytesRead() < 0;
break;
}
//将读的次数加1
allocHandle.incMessagesRead(1);
readPending = false;
//调用通道将读取的数据传入
pipeline.fireChannelRead(byteBuf);
byteBuf = null;
//如果仍然可读继续读
}
while (allocHandle.continueReading());
allocHandle.readComplete();
pipeline.fireChannelReadComplete();
if (close)
{
closeOnRead(pipeline);
}
接下来找到我们的DefaultChannelPipeline类
public final ChannelPipeline fireChannelRead(Object msg) {
AbstractChannelHandlerContext.invokeChannelRead(head, msg);
return this;
}
继续,AbstractChannelHandlerContext又是什么鬼呢,还记得上一篇讲的吗,这是个管道封装类,管道即我们添加的
ch.pipeline().addLast("encoder", new Encoder());
ch.pipeline().addLast("decoder", new Decoder());
ch.pipeline().addLast("handler",new ClientHandler(NettyCore.this));
这些类通通是继承ChannelInboundHandlerAdapter或ChannelOutboundHandlerAdapter的类,先看一下怎么添加的
private void addLast0(AbstractChannelHandlerContext newCtx) {
AbstractChannelHandlerContext prev = tail.prev;
newCtx.prev = prev;
newCtx.next = tail;
prev.next = newCtx;
tail.prev = newCtx;
}
以队列的方式插入到tail的前面和 head的后面,那么这两个又是干啥的呢
protected DefaultChannelPipeline(Channel channel) {
this.channel = ObjectUtil.checkNotNull(channel, "channel");
succeededFuture = new SucceededChannelFuture(channel, null);
voidPromise = new VoidChannelPromise(channel, true);
tail = new TailContext(this);
head = new HeadContext(this);
head.next = tail;
tail.prev = head;
}
初始化时创建,将tail放在head的后面,这是addFirst方法
private void addFirst0(AbstractChannelHandlerContext newCtx) {
AbstractChannelHandlerContext nextCtx = head.next;
newCtx.prev = head;
newCtx.next = nextCtx;
head.next = newCtx;
nextCtx.prev = newCtx;
}
将newCtx插入到队列head的后面,继续
//第一个参数是head头,第二参数是byteBuf
static void invokeChannelRead
(final AbstractChannelHandlerContext next, Object msg)
{
//判断管道是否可用
final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
EventExecutor executor = next.executor();
if (executor.inEventLoop())
{ next.invokeChannelRead(m);
} else {
executor.execute(new Runnable() {
@Override
public void run() {
next.invokeChannelRead(m); }
});
}
}
饶了这么一大圈终于到了传消息了
private void invokeChannelRead(Object msg) {
if (invokeHandler()) {
try {
标记1: ((ChannelInboundHandler) handler()).channelRead(this, msg);
} catch (Throwable t)
{ notifyHandlerException(t); } }
else { 标记2: fireChannelRead(msg); } }
进入标记2的方法先从队列头开始读
public ChannelHandlerContext fireChannelRead(final Object msg) {
invokeChannelRead(findContextInbound(), msg);
return this;
}
findContextInbound又是什么鬼呢?
private AbstractChannelHandlerContext findContextInbound() {
AbstractChannelHandlerContext ctx = this;
do {
ctx = ctx.next;
} while (!ctx.inbound);
return ctx;
}
重点终于来了
//读管道我们必须继承ChannelInboundHandler
private static boolean isInbound(ChannelHandler handler) {
return handler instanceof ChannelInboundHandler;
}
//写管道我必须继承ChannelOutboundHandler
private static boolean isOutbound(ChannelHandler handler)
{ return handler instanceof ChannelOutboundHandler;
}
现在思路已经很清晰了,从head开始调用invokeChannelRead-》fireChannelRead-》invokeChannelRead-》调用队列下一个fireChannelRead,以此形
成循环,知道调用队列最后一个tail的时候invokeHandler()为true走标记1,此时结束一次读取。现在整个读取就形成了一条链。
总结:tcp缓冲区一有数据就会发送读信号开始读取,每次读到数据就开始链式分发,交给管道处理,管道处理得到我们想要的数据,直到读够数据为止,返回数据给客户端。