整体流程分析
上一篇文章,通过源码分析了netty的启动过程,整体上来说,其实就是通过创建boss\worker线程池,然后注册channel,以及绑定端口等,然后就开始轮训的方式,接受客户端的请求。而请求中主要包含 连接、读、写 三种不同的事件,本篇我们主要围绕这三种事件进行源码分析。
EventLoop eventLoop = new NioEventLoopGroup().next();
eventLoop.execute(() -> {
System.out.println("hello workd");
});
接收请求-accepct过程
判断当前线程是不是NIO线程,如果不是的启动一个NIO线程
private void execute(Runnable task, boolean immediate) {
// 当前线程是否是NIO 线程
boolean inEventLoop = inEventLoop();
// 添加到任务队列中
addTask(task);
// 是否是nio线程 ,不是的话
if (!inEventLoop) {
// 开启一个线程
startThread();
}
}
启动一个线程。先CAS修改启动状态。使用CAS其实可以提升整体的并发度。可以看到如果线程执行启动失败的话,会采用finally进行CAS修改成原来的状态
// NIO 线程 只会启动一次,启动成功后 直接从taskQueue 获取数据
private void startThread() {
// state = ST_NOT_STARTED; 初始值
if (state == ST_NOT_STARTED) {
// CAS 状态 更新 not_started -> st_started 代表线程启动
if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
// 初始化值为false
boolean success = false;
try {
// 做真正的NIO启动
doStartThread();
success = true;
} finally {
if (!success) {
STATE_UPDATER.compareAndSet(this, ST_STARTED, ST_NOT_STARTED);
}
}
}
}
}
doStartThread(); 利用线程池创建一个任务。
// 只会创建一次
private void doStartThread() {
assert thread == null;
// 如下就是NIO线程
executor.execute(new Runnable() {
@Override
public void run() {
thread = Thread.currentThread();
// 核心 ⭐️
SingleThreadEventExecutor.this.run();
success = true;
这个run方法 ,其实就是select(), 轮训查询事件。这里包括IO任务 普通任务,定时任务等。
// io任务 普通任务 定时任务 网络通信
// eventLoop
@Override
protected void run() {
int selectCnt = 0;
for (;;) {
try {
int strategy;
try {
// hasTasks() 普通任务 或者 定时任务
// 1.如果存在普通任务 selectNow 不阻塞
// 如果没有普通任务就阻塞
strategy = selectStrategy.calculateStrategy(selectNowSupplier, hasTasks());
switch (strategy) {
case SelectStrategy.CONTINUE:
continue;
case SelectStrategy.BUSY_WAIT:
// fall-through to SELECT since the busy-wait is not supported with NIO
case SelectStrategy.SELECT:
long curDeadlineNanos = nextScheduledTaskDeadlineNanos();
if (curDeadlineNanos == -1L) {
curDeadlineNanos = NONE; // nothing on the calendar
}
nextWakeupNanos.set(curDeadlineNanos);
try {
if (!hasTasks()) {
strategy = select(curDeadlineNanos);
}
} finally {
// This update is just to help block unnecessary selector wakeups
// so use of lazySet is ok (no race condition)
nextWakeupNanos.lazySet(AWAKE);
}
// fall through
default:
}
}
selectCnt++;
cancelledKeys = 0;
needsToSelectAgain = false;
final int ioRatio = this.ioRatio;
boolean ranTasks;
// 执行IO
if (ioRatio == 100) {
try {
if (strategy > 0) {
// 如何执行IO任务
processSelectedKeys();
}
} finally {
// 执行普通任务
// Ensure we always run tasks.
ranTasks = runAllTasks();
}
} else if (strategy > 0) {
final long ioStartTime = System.nanoTime();
try {
processSelectedKeys();
} finally {
// Ensure we always run tasks.
final long ioTime = System.nanoTime() - ioStartTime;
ranTasks = runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
} else {
// 没有IO任务,执行其他所有的任务
ranTasks = runAllTasks(0); // This will run the minimum number of tasks
}
}
}
}
由于我们的关注点在于客户端和服务端IO事件的交互上,所以只关系 accecpt\read\write 事件
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
// 校验处理 检查该selectionKey是否有效,如果无效,则关闭channel
if (!k.isValid()) {}
// 该方法主要是对SelectionKey k进行了检查,有如下几种不同的情况
//
// 1)OP_ACCEPT,接受客户端连接
//
// 2)OP_READ, 可读事件, 即 Channel 中收到了新数据可供上层读取。
//
// 3)OP_WRITE, 可写事件, 即上层可以向 Channel 写入数据。
//
// 4)OP_CONNECT, 连接建立事件, 即 TCP 连接已经建立, Channel 处于 active 状态。
// 不同事件 处理不同功能
try {
int readyOps = k.readyOps();
// We first need to call finishConnect() before try to trigger a read(...) or write(...) as otherwise
// the NIO JDK channel implementation may throw a NotYetConnectedException.
// 连接
// 需要移除OP_CONNECT 否则可能出现理会返回不会有任何阻塞
if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
// remove OP_CONNECT as otherwise Selector.select(..) will always return without blocking
// See https://github.com/netty/netty/issues/924
int ops = k.interestOps();
ops &= ~SelectionKey.OP_CONNECT;
k.interestOps(ops);
unsafe.finishConnect();
}
// 核心事件 判断 以及处理
// Process OP_WRITE first as we may be able to write some queued buffers and so free memory.
// 将缓冲区中的数据发送出去,如果缓冲区数据都发送完成,清除之前关注的OP_WRITE标记
if ((readyOps & SelectionKey.OP_WRITE) != 0) {
// Call forceFlush which will also take care of clear the OP_WRITE once there is nothing left to write
unsafe.forceFlush();
}
// Also check for readOps of 0 to workaround possible JDK bug which may otherwise lead
// to a spin loop
// 读 或者 接收
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
unsafe.read();
}
} catch (CancelledKeyException ignored) {
unsafe.close(unsafe.voidPromise());
}
}
public void read() {
assert eventLoop().inEventLoop();
final ChannelConfig config = config();
final ChannelPipeline pipeline = pipeline();
final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
allocHandle.reset(config);
boolean closed = false;
Throwable exception = null;
try {
try {
do {// ⭐️ 创建一个SocketChannel对象
int localRead = doReadMessages(readBuf);
if (localRead == 0) {
break;
}
if (localRead < 0) {
closed = true;
break;
}
allocHandle.incMessagesRead(localRead);
} while (continueReading(allocHandle));
} catch (Throwable t) {
exception = t;
}
int size = readBuf.size();
// 遍历处理
for (int i = 0; i < size; i ++) {
readPending = false;
// 消费过程
// readBuf.get(i) 获取nioSocketChannel
// 触发pipiline的Read功能
pipeline.fireChannelRead(readBuf.get(i));
}
readBuf.clear();
allocHandle.readComplete();
pipeline.fireChannelReadComplete();
if (exception != null) {
closed = closeOnReadError(exception);
pipeline.fireExceptionCaught(exception);
}
}
}
}
1.创建原生的socketChannel, 这里会调用父类设置一些基础的信息,并且设置当前为一个readChannel
protected int doReadMessages(List<Object> buf) throws Exception {
// 创建原生的socketChannel
// 这里其实要是看是否是阻塞的.
SocketChannel ch = SocketUtils.accept(javaChannel());
try {
if (ch != null) {
// 创建了NIOScoektChannel 将SocketChannel 存储到NIOSocketChannel
// 将原始的sovketchannel 设置为非阻塞
// ServerSOcketChannel设置父类
// 添加到list中
buf.add(new NioSocketChannel(this, ch));
return 1;
}
} catch (Throwable t) {
logger.warn("Failed to create a new channel from an accepted socket.", t);
try {
ch.close();
} catch (Throwable t2) {
logger.warn("Failed to close a socket.", t2);
}
}
return 0;
}
由于在初始化服务端的时候,往pipeline中添加一个ServerBootstrapAcceptor对象,当出发accpect事件,就会调用read方法。
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(final Channel ch) {
final ChannelPipeline pipeline = ch.pipeline();
ChannelHandler handler = config.handler();
if (handler != null) {
pipeline.addLast(handler);
}
// 这是一个接入器,专门接受新请求,把新的请求扔给某个事件循环器
// 看到这里,我们发现其实init只是初始化了一些基本的配置和属性,
// 以及在pipeline上加入了一个接入器,用来专门接受新连接,并没有启动服务.
ch.eventLoop().execute(new Runnable() {
@Override
public void run() {
pipeline.addLast(new ServerBootstrapAcceptor(
ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs,
extensions));
}
});
}
});
调用read方法就会注册一个read NIO线程,这个线程是worker线程池的线程。用来处理read事件。
public void channelRead(ChannelHandlerContext ctx, Object msg) {
final Channel child = (Channel) msg;
child.pipeline().addLast(childHandler);
setChannelOptions(child, childOptions, logger);
setAttributes(child, childAttrs);
if (!extensions.isEmpty()) {
for (ChannelInitializerExtension extension : extensions) {
try {
extension.postInitializeServerChildChannel(child);
} catch (Exception e) {
logger.warn("Exception thrown from postInitializeServerChildChannel", e);
}
}
}
try {
// 核心注册方法
childGroup.register(child).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (!future.isSuccess()) {
forceClose(child, future.cause());
}
}
});
} catch (Throwable t) {
forceClose(child, t);
}
}
worker线程-read过程
public final void read() {
// 1.获取配置信息
final ChannelConfig config = config();
if (shouldBreakReadReady(config)) {
clearReadPending();
return;
}
// 2.交给pipeline
final ChannelPipeline pipeline = pipeline();
// 创建分配器
final ByteBufAllocator allocator = config.getAllocator();
// 服务端接收数据的大小
final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
allocHandle.reset(config);
ByteBuf byteBuf = null;
boolean close = false;
try {
// 循环的原因
// bytebuf 大小可能不够 需要循环处理
do {
// 接收数据 自适应 相对于ByteBuf
//0.自适应 如果数据量大,扩容 如果数据量小 缩小
//1.决定ByteBuf 用的是直接内存 还是堆内存
byteBuf = allocHandle.allocate(allocator);
//doReadBytes 通过时机IO读区数据
allocHandle.lastBytesRead(doReadBytes(byteBuf));
if (allocHandle.lastBytesRead() <= 0) {// 读区完毕
// nothing was read. release the buffer.
byteBuf.release(); // 释放
byteBuf = null;
close = allocHandle.lastBytesRead() < 0;
if (close) { // 关闭
// There is nothing left to read as we received an EOF.
readPending = false;
}
break;
}
allocHandle.incMessagesRead(1);
readPending = false;
pipeline.fireChannelRead(byteBuf);
byteBuf = null;
} while (allocHandle.continueReading());
// do while 是判断当前是否读区完数据
allocHandle.readComplete();
pipeline.fireChannelReadComplete();
if (close) {
closeOnRead(pipeline);
}
} catch (Throwable t) {
handleReadException(pipeline, byteBuf, t, close, allocHandle);
} finally {
}
}
}
这里涉及到几个点那就是在读区数据的时候。
- 堆内存和直接内存
- 自动扩缩容的处理(根据读区缓冲区大小 是否读区完数据调整)
worker线程-write过程
protected void flush0() {
// 状态判断
if (inFlush0) {
// Avoid re-entrance
return;
}
final ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
if (outboundBuffer == null || outboundBuffer.isEmpty()) {
return;
}
inFlush0 = true;
// Mark all pending write requests as failure if the channel is inactive.
//
if (!isActive()) {
try {
// Check if we need to generate the exception at all.
if (!outboundBuffer.isEmpty()) {
if (isOpen()) {
outboundBuffer.failFlushed(new NotYetConnectedException(), true);
} else {
// Do not trigger channelWritabilityChanged because the channel is closed already.
outboundBuffer.failFlushed(newClosedChannelException(initialCloseCause, "flush0()"), false);
}
}
} finally {
inFlush0 = false;
}
return;
}
try {
// 核心 ⭐️
doWrite(outboundBuffer);
} catch (Throwable t) {
handleWriteError(t);
} finally {
inFlush0 = false;
}
}
总结
整体的过程代码还是比较多的,这里只是主要介绍了接收客户端请求的处理逻辑,对于读和写的具体代码没有展示。但是我们可以从一个宏观的角度去理解。
1.接收请求,其实就是boss线程开辟一个NIO线程处理客户端的连接,将获取到的连接,通过注册一个worker线程进行后续的读和写操作。
2.对于读操作,可以利用直接内存进行提升整体的读取数据的性能,减少数据拷贝的次数。以及进行动态的扩容实现数据的快速读取。
3.对于写操作,需要经历几个过程。数据是存储在outboundBuffer缓冲中的。
而对于直接内存来说,有两种选择,一种是池化的直接内存,另一种就是保存在线程中的ThreadLocalDirectBuffer 直接内存对象。
而数据会添加到链表中,经过addMessage() unflushEntry 状态到 addFlush的flushEntry状态。