本文主要分析服务端新连接的接入过程,主要分为以下 2 个各步骤:

  1. select 操作;
  2. processSelectedKeys 操作。

1. select 操作

  在分析 select 操作前,先要回顾一下 NioEventLoop 的 run()方法及其父类 SingleThreadEventExecutor 的 execute(Runnable task)方法。

@Override
public void execute(Runnable task) {
if (task == null) {
throw new NullPointerException("task");
}
//判断是否是 netty 线程添加的任务
boolean inEventLoop = inEventLoop();
if (inEventLoop) {
addTask(task);//是的话直接添加任务到队列
} else {
//不是的话,可能要创建 netty 线程
startThread();
//然后添加任务到队列
addTask(task);
if (isShutdown() && removeTask(task)) {
reject();
}
}
// 构造方法中将 addTaskWakesUp 置 false
// wakesUpForTask(task) 直接返回 true
if (!addTaskWakesUp && wakesUpForTask(task)) {
// 所以 wakeup()方法肯定会被调用
wakeup(inEventLoop);
}
}

  

  这里的 wakeup(boolean inEventLoop)方法分析 NioEventLoop 中的:

@Override
protected void wakeup(boolean inEventLoop) {
// 如果是非 Netty 线程添加任务,那么 wakenUp.compareAndSet(false ,true)
// 成功的话,就会调用 selector 的 wakeup()方法
if (!inEventLoop && wakenUp.compareAndSet(false, true)) {
// 回顾一下 selector 的 wakeup()方法
// 1. 当 selector 在执行 select 操作时,调用它的 wakeup()方法,
// 那么当前的 select 操作会立刻返回已就绪的事件集
// 2. 如果提前调用 selector 的 wakeup()方法(一次或者多次都是一样的)
// 那么下一次的 select 操作会直接返回(应该是没有就绪事件)
// 关于 selector 的 wakeup()方法,可以看一下文末的参考资料
selector.wakeup();
}
}

  接下来再 分析以下 NioEventLoop 中的 run()方法:

protected void run() {
for (;;) {
try {
// 计算 select 策略,当前有任务时,会进行一次 selectNow 操作返回就绪的 key 个数(大于等于 0)
// SelectStrategy.CONTINUE 值是 -2,SelectStrategy.SELECT 的值是 -1
// (这里的 SelectStrategy.CONTINUE 感觉不会匹配到)
// 显然 switch 中没有匹配项,直接跳出 switch
// 无任务时,则直接返回 SelectStrategy.SELECT
switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
case SelectStrategy.CONTINUE:
continue;
case SelectStrategy.SELECT:
//当没有可处理的任务时,直接进行 select 操作
// wakenUp.getAndSet(false) 返回的是 oldValue,由于默认值是 false
// 所以第一次返回的是 false,需要注意的是,只有在这个地方才将 wakenUp 置为 false
select(wakenUp.getAndSet(false)); if (wakenUp.get()) {
selector.wakeup();
}
default:
// fallthrough
}
cancelledKeys = 0;
needsToSelectAgain = false;
final int ioRatio = this.ioRatio;
//根据比例来处理 IO 事件和任务
if (ioRatio == 100) {
//...
} else {
final long ioStartTime = System.nanoTime();
try {
processSelectedKeys();
} finally {
// Ensure we always run tasks.
// 计算出处理 IO 事件的时间,然后根据比例算出执行任务的时间
final long ioTime = System.nanoTime() - ioStartTime;
runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
}
} catch (Throwable t) {
handleLoopException(t);
}
//...
}
}

  wakenUp 是用来减少调用 selector 的 wakeup()方法,关于 wakeup()方法的实现细节,参考文末的资料。接下来分析一下 key 的处理:

private void processSelectedKeysOptimized(SelectionKey[] selectedKeys) {
for (int i = 0;; i ++) {
final SelectionKey k = selectedKeys[i];
if (k == null) {
break;
}
// null out entry in the array to allow to have it GC'ed once the Channel close
// See https://github.com/netty/netty/issues/2363
// 置为 null,参考上面一行的注释
selectedKeys[i] = null; // 在前面 AbstractNioChannel 中的 doRegister()方法中,注册的时候传入的参数是 this
// 接入连接时,这个 a 就是 AbstractNioChannel 对象
final Object a = k.attachment(); if (a instanceof AbstractNioChannel) {
processSelectedKey(k, (AbstractNioChannel) a);
} else {
//这里暂时不熟悉
@SuppressWarnings("unchecked")
NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
processSelectedKey(k, task);
}
//...
}
}

2. processSelectedKeys 操作

  接下来分析一下 processSelectedKey()方法:

private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
//取出 channel 对应的 unsafe
final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
if (!k.isValid()) {
//... 当 key 无效时,关闭 channel 等操作
} 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.
if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
// 连接就绪事件只需要处理一次就行了,否则后续的 select()操作会一直立刻返回
// 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);
// 最后调用 SocketChannel 的 finishConnect()方法
unsafe.finishConnect();
} // Process OP_WRITE first as we may be able to write some queued buffers and so free memory.
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
ch.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) {
// read 和 accept
unsafe.read();
if (!ch.isOpen()) {
// Connection already closed - no need to handle write.
return;
}
}
} catch (CancelledKeyException ignored) {
unsafe.close(unsafe.voidPromise());
}
}

  由于新连接的接入是一个就绪的 ACCEPT 事件,所以分析一下 unsafe.read(),这个 unsafe 对象是创建服务端 Channel 时创建的,是一个 NioMessageUnsafe 对象,它的 read()方法中有一行:

localRead = doReadMessages(readBuf);

  该方法的实现选择 NioServerSocketChannel 中的:

@Override
protected int doReadMessages(List<Object> buf) throws Exception {
// 接入连接
SocketChannel ch = javaChannel().accept();
try {
if (ch != null) {
// 向 Object 列表中加入封装后的 NioSocketChannel 对象
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;
}

  回到 NioMessageUnsafe 的 read()方法,doReadMessages()方法返回了 NioSocketChannel 列表,接下来的一行:

pipeline.fireChannelRead(readBuf.get(i));

  就会调用服务端 Channel 中的 handler 链,首先是用户添加的 handler,最后会找到前面说过的 ServerBootstrapAcceptor,它的 channelRead()方法中的 msg 参数实际上是 Channel 对象。

  接下来对这个客户端的 Channel 的处理与服务端 Channel 的处理过程基本类似。

Netty 服务端:新连接接入的更多相关文章

  1. Netty是如何处理新连接接入事件的?

    更多技术分享可关注我 前言 前面的分析从Netty服务端启动过程入手,一路走到了Netty的心脏——NioEventLoop,又总结了Netty的异步API和设计原理,现在回到Netty服务端本身,看 ...

  2. Netty服务端NioEventLoop启动及新连接接入处理

    一 Netty服务端NioEventLoop的启动 Netty服务端创建.初始化完成后,再向Selector上注册时,会将服务端Channel与NioEventLoop绑定,绑定之后,一方面会将服务端 ...

  3. Netty 学习(八):新连接接入源码说明

    Netty 学习(八):新连接接入源码说明 作者: Grey 原文地址: 博客园:Netty 学习(八):新连接接入源码说明 CSDN:Netty 学习(八):新连接接入源码说明 新连接的接入分为3个 ...

  4. Netty 服务端启动过程

    在 Netty 中创建 1 个 NioServerSocketChannel 在指定的端口监听客户端连接,这个过程主要有以下  个步骤: 创建 NioServerSocketChannel 初始化并注 ...

  5. Netty 服务端创建

    参考:http://blog.csdn.net/suifeng3051/article/details/28861883?utm_source=tuicool&utm_medium=refer ...

  6. Netty服务端Channel的创建与初始化

    Netty创建服务端Channel时,从服务端 ServerBootstrap 类的 bind 方法进入,下图是创建服务端Channel的函数调用链.在后续代码中通过反射的方式创建服务端Channel ...

  7. Netty源码 新连接处理

    上文我们阐述了Netty的Reactor模型.在Reactor模型的第二阶段,Netty会处理各种io事件.对于客户端的各种请求就是在这个阶段去处理的.本文便来分析一个新的连接是如何被处理的. 代码的 ...

  8. netty服务端启动--ServerBootstrap源码解析

    netty服务端启动--ServerBootstrap源码解析 前面的第一篇文章中,我以spark中的netty客户端的创建为切入点,分析了netty的客户端引导类Bootstrap的参数设置以及启动 ...

  9. Netty之旅三:Netty服务端启动源码分析,一梭子带走!

    Netty服务端启动流程源码分析 前记 哈喽,自从上篇<Netty之旅二:口口相传的高性能Netty到底是什么?>后,迟迟两周才开启今天的Netty源码系列.源码分析的第一篇文章,下一篇我 ...

随机推荐

  1. 怎么让html中 还是显示<button> 而不进行编译

    toTXT(str) {    var RexStr = /\<|\>|\"|\'|\&| | /g    str = str.replace(RexStr,       ...

  2. apk文件二维码微信无法识别 APP在微信中二维码扫描无法下载的解决方案

    现在微信分享的功能很多,从分享的链接下载apk安卓包是很正常的,但是微信不让下载apk包,只能通过浏览器来下载,但是这要给用户一个提示吧,不然用户不知道 下面我们来实现,引导用户通过浏览器来下载apk ...

  3. 在kali linux上安装VMware tool

    在安全圈的门口徘徊了一年,一直不知道该如何入门,现在决定先从kali 入手.有同样兴趣的伙伴欢迎一起. 但是刚在VMware上安好系统就遇到了一个大麻烦,看了很多书,还有教程但总是遇到这样那样的问题. ...

  4. week4_1

    ---恢复内容开始--- _________________________________列表生成式_____________________ a = [a*2 for a in range(10) ...

  5. 解读——angeltoken钱包

    Angeltoken可不可靠,这是每一个会员都会考虑的问题.有风险意识很重要,但是,更重要的是,怎么才能规避风险,最大限度的安全投资呢? AngelToken值得我们每一个想要改变自己处境的平凡人,认 ...

  6. Android NDK pthreads详细使用

    这个pthread.h文件可以在NDK环境里创建子线程,并对线程能够做出互斥所.等待.销毁等控制. 写这个博客的原因是我要写如何使用FFmpeg播放视频,因为同时需要播放音频和视频所以需要开启线程,并 ...

  7. Object.keys方法之详解

    在实际开发中,我们有时需要知道对象的所有属性,原生js给我们提供了一个很好的方法:Object.keys(),该方法返回一个数组 传入对象,返回属性名 var obj = {'a':'123','b' ...

  8. 软工作业(JAVA)

    github传送门:https://github.com/hhg52516/WC.git 项目要求 wc.exe 是一个常见的工具,它能统计文本文件的字符数.单词数和行数.这个项目要求写一个命令行程序 ...

  9. Unity3d KeyCode 键盘各种键值详情

    KeyCode :KeyCode是由Event.keyCode返回的.这些直接映射到键盘上的物理键. 值        对应键 Backspace     退格键 Delete      Delete ...

  10. Visual Studio+VAssistX自动添加注释,函数头注释,文件头注释

    转载:http://blog.csdn.net/xzytl60937234/article/details/70455777 在VAssistX中为C++提供了比较规范注释模板,用这个注释模板为编写的 ...