编者注:Netty是Java领域有名的开源网络库,特点是高性能和高扩展性,因此很多流行的框架都是基于它来构建的,比如我们熟知的Dubbo、Rocketmq、Hadoop等,针对高性能RPC,一般都是基于Netty来构建,比如soft-bolt。总之一句话,Java小伙伴们需要且有必要学会使用Netty并理解其实现原理。

关于Netty的入门讲解可参考:Netty 入门,这一篇文章就够了

Netty的连接处理就是IO事件的处理,IO事件包括读事件、ACCEPT事件、写事件和OP_CONNECT事件。

IO事件的处理是结合ChanelPipeline来做的,一个IO事件到来,首先进行数据的读写操作,然后交给ChannelPipeline进行后续处理,ChannelPipeline中包含了channelHandler链(head + 自定义channelHandler + tail)。

使用channelPipeline和channelHandler机制,起到了解耦和可扩展的作用。一个IO事件的处理,包含了多个处理流程,这些处理流程正好对应channelPipeline中的channelHandler。如果对数据处理有新的需求,那么就新增channelHandler添加到channelPipeline中,这样实现很6,以后自己写代码可以参考。

说到这里,一般为了满足扩展性要求,常用2种模式:

  • 方法模板模式:模板中定义了各个主流程,并且留下对应hook方法,便于扩展。
  • 责任链模式:串行模式,可以动态添加链数量和对应回调方法。

netty的channelHandlerchannelPipeline可以理解成就是责任链模式,通过动态增加channelHandler可达到复用和高扩展性目的。

了解netty连接处理机制之前需要了解下NioEventLoop模型,其中处理连接事件的架构图如下:

对应的处理逻辑源码为:

// 处理各种IO事件
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe(); try {
int readyOps = k.readyOps();
if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
// OP_CONNECT事件,client连接上客户端时触发的事件
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) {
// 注意,这里读事件和ACCEPT事件对应的unsafe实例是不一样的
// 读事件 -> NioByteUnsafe, ACCEPT事件 -> NioMessageUnsafe
unsafe.read();
}
} catch (CancelledKeyException ignored) {
unsafe.close(unsafe.voidPromise());
}
}

从上面代码来看,事件主要分为3种,分别是OP_CONNECT事件、写事件和读事件(也包括ACCEPT事件)。下面分为3部分展开:

ACCEPT事件

// NioMessageUnsafe
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 {
do {
// 调用java socket的accept方法,接收请求
int localRead = doReadMessages(readBuf);
// 增加统计计数
allocHandle.incMessagesRead(localRead);
} while (allocHandle.continueReading());
} catch (Throwable t) {
exception = t;
} // readBuf中存的是NioChannel
int size = readBuf.size();
for (int i = 0; i < size; i ++) {
readPending = false;
// 触发fireChannelRead
pipeline.fireChannelRead(readBuf.get(i));
}
readBuf.clear();
allocHandle.readComplete();
pipeline.fireChannelReadComplete();
}

连接建立好之后就该连接的channel注册到workGroup中某个NIOEventLoop的selector中,注册操作是在fireChannelRead中完成的,这一块逻辑就在ServerBootstrapAcceptor.channelRead中。

// ServerBootstrapAcceptor
public void channelRead(ChannelHandlerContext ctx, Object msg) {
final Channel child = (Channel) msg; // 设置channel的pipeline handler,及channel属性
child.pipeline().addLast(childHandler);
setChannelOptions(child, childOptions, logger); for (Entry<AttributeKey<?>, Object> e: childAttrs) {
child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
} try {
// 将channel注册到childGroup中的Selector上
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);
}
}

READ事件

// NioByteUnsafe
public final void read() {
final ChannelConfig config = config();
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 {
byteBuf = allocHandle.allocate(allocator);
// 从channel中读取数据,存放到byteBuf中
allocHandle.lastBytesRead(doReadBytes(byteBuf)); allocHandle.incMessagesRead(1);
readPending = false; // 触发fireChannelRead
pipeline.fireChannelRead(byteBuf);
byteBuf = null;
} while (allocHandle.continueReading()); // 触发fireChannelReadComplete,如果在fireChannelReadComplete中执行了ChannelHandlerContext.flush,则响应结果返回给客户端
allocHandle.readComplete();
// 触发fireChannelReadComplete
pipeline.fireChannelReadComplete(); if (close) {
closeOnRead(pipeline);
}
} catch (Throwable t) {
if (!readPending && !config.isAutoRead()) {
removeReadOp();
}
}
}

写事件

正常情况下一般是不会注册写事件的,如果Socket发送缓冲区中没有空闲内存时,再写入会导致阻塞,此时可以注册写事件,当有空闲内存(或者可用字节数大于等于其低水位标记)时,再响应写事件,并触发对应回调。

if ((readyOps & SelectionKey.OP_WRITE) != 0) {
// 写事件,从flush操作来看,虽然之前没有向socket缓冲区写数据,但是已经写入到
// 了chnanel的outboundBuffer中,flush操作是将数据从outboundBuffer写入到
// socket缓冲区
ch.unsafe().forceFlush();
}

CONNECT事件

该事件是client触发的,由主动建立连接这一侧触发的。

if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
// OP_CONNECT事件,client连接上客户端时触发的事件
int ops = k.interestOps();
ops &= ~SelectionKey.OP_CONNECT;
k.interestOps(ops); // 触发finishConnect事件,其中就包括fireChannelActive事件,如果有自定义的handler有channelActive方法,则会触发
unsafe.finishConnect();
}

推荐阅读

欢迎小伙伴关注【TopCoder】阅读更多精彩好文。

Netty连接处理那些事的更多相关文章

  1. 聊聊iOS中网络编程长连接的那些事

    1.长连接在iOS开发中的应用 常见的短连接应用场景:一般的App的网络请求都是基于Http1.0进行的,使用的是NSURLConnection.NSURLSession或者是AFNetworking ...

  2. Netty的那些”锁”事

    Netty锁事的五个关键点: ① 在意锁的对象和范围  --> 减少粒度 ②  注意锁的对象本身大小   --> 减少空间占用 ③ 注意锁的速度 --> 提高速度 ④不同场景选择不同 ...

  3. SQL SERVER 无法正常连接的那些事

    1.确保sqlserver服务正常运行. >一般可以从两个地方控制服务,一是系统自带的服务管理器,最快捷的方式是运行“services.msc”,二是使用sqlserver自带的“SQL Ser ...

  4. Netty自带连接池的使用

    一.类介绍1.ChannelPool——连接池接口 2.SimpleChannelPool——实现ChannelPool接口,简单的连接池实现 3.FixedChannelPool——继承Simple ...

  5. 新手入门:目前为止最透彻的的Netty高性能原理和框架架构解析

    1.引言 Netty 是一个广受欢迎的异步事件驱动的Java开源网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端. 本文基于 Netty 4.1 展开介绍相关理论模型,使用场景,基本组件 ...

  6. Netty 源码 NioEventLoop(三)执行流程

    Netty 源码 NioEventLoop(三)执行流程 Netty 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html) 上文提到在启动 N ...

  7. Netty In Action中文版 - 第三章:Netty核心概念

            在这一章我们将讨论Netty的10个核心类.清楚了解他们的结构对使用Netty非常实用.可能有一些不会再工作中用到.可是也有一些非经常常使用也非常核心,你会遇到. Bootstrap ...

  8. netty的调优-及-献上写过注释的源码工程

    Netty能干什么? Http服务器 使用Netty可以编写一个 Http服务器, 就像tomcat那样,能接受用户发送的http请求, , 只不过没有实现Servelt规范, 但是它也能解析携带的参 ...

  9. Netty高性能原理和框架架构解析

    1.引言 Netty 是一个广受欢迎的异步事件驱动的Java开源网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端. 本文基于 Netty 4.1 展开介绍相关理论模型,使用场景,基本组件 ...

随机推荐

  1. reduce方法应用技巧

    定义和用法 reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值. 注意: reduce() 对于空数组是不会执行回调函数的. 浏览器支持 方法 Chro ...

  2. 我又不是你的谁--java instanceof操作符用法揭秘

    背景故事 <曾经最美>是朱铭捷演唱的一首歌曲,由陈佳明填词,叶良俊谱曲,是电视剧<水晶之恋>的主题曲.歌曲时长4分28秒. 歌曲歌词: 看不穿你的眼睛 藏有多少悲和喜 像冰雪细 ...

  3. CSS3新单位vw、vh、vmin、vmax使用详解

    像 px.em 这样的长度单位大家肯定都很熟悉,前者为绝对单位,后者为相对单位.CSS3 又引入了新单位:vw.vh.vmin.vmax.下面对它们做个详细介绍. 新单位也成为视窗单位,视窗(View ...

  4. Hackers' Crackdown UVA - 11825

    Miracle Corporations has a number of system services running in a distributed computer system which ...

  5. web安全之php中常见的INI文件配置

    php.ini 在 PHP 启动时被读取.对于服务器模块版本的 PHP,仅在 web 服务器启动时读取 一次.对于 CGI 和 CLI 版本,每次调用都会读取. * Apache web 服务器在启动 ...

  6. libevent::事件::定时器

    #include <cstdio> #include <errno.h> #include <sys/types.h> #include <event.h&g ...

  7. Knative 实战:基于 Kafka 实现消息推送

    作者 | 元毅 阿里云智能事业群高级开发工程师 导读:当前在 Knative 中已经提供了对 Kafka 事件源的支持,那么如何基于 Kafka 实现消息推送呢?本文作者将以阿里云 Kafka 产品为 ...

  8. windows 抓hash获取管理员密码

    webshell 找能执行权限的目录上传 C:\Windows\System32\config\sam 内有windows 密码 利用工具把密码抓出来 samcopy 直接抓取 GetHASHES.e ...

  9. python学习-多线程并发

    1.线程与进程 通俗解释: 对于操作系统来说,一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程,打开一个记事本就启动了一个记事本进程,打开两个记事本就启动了两个记事本进 ...

  10. 史上最轻松入门之Spring Batch - 轻量级批处理框架实践

    从 MariaDB 一张表内读 10 万条记录,经处理后写到 MongoDB . Batch 任务模型 具体实现 1.新建 Spring Boot 应用,依赖如下: <!-- Web 应用 -- ...