Netty连接处理那些事
编者注: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的channelHandler的channelPipeline可以理解成就是责任链模式,通过动态增加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();
}
推荐阅读
- Netty 入门,这一篇文章就够了
- Java常见几种动态代理的对比
- 程序员必看| mockito原理浅析
- Eureka 原理分析
- MQ初窥门径【面试必看的Kafka和RocketMQ存储区别】
- java lambda 深入浅出
欢迎小伙伴关注【TopCoder】阅读更多精彩好文。
Netty连接处理那些事的更多相关文章
- 聊聊iOS中网络编程长连接的那些事
1.长连接在iOS开发中的应用 常见的短连接应用场景:一般的App的网络请求都是基于Http1.0进行的,使用的是NSURLConnection.NSURLSession或者是AFNetworking ...
- Netty的那些”锁”事
Netty锁事的五个关键点: ① 在意锁的对象和范围 --> 减少粒度 ② 注意锁的对象本身大小 --> 减少空间占用 ③ 注意锁的速度 --> 提高速度 ④不同场景选择不同 ...
- SQL SERVER 无法正常连接的那些事
1.确保sqlserver服务正常运行. >一般可以从两个地方控制服务,一是系统自带的服务管理器,最快捷的方式是运行“services.msc”,二是使用sqlserver自带的“SQL Ser ...
- Netty自带连接池的使用
一.类介绍1.ChannelPool——连接池接口 2.SimpleChannelPool——实现ChannelPool接口,简单的连接池实现 3.FixedChannelPool——继承Simple ...
- 新手入门:目前为止最透彻的的Netty高性能原理和框架架构解析
1.引言 Netty 是一个广受欢迎的异步事件驱动的Java开源网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端. 本文基于 Netty 4.1 展开介绍相关理论模型,使用场景,基本组件 ...
- Netty 源码 NioEventLoop(三)执行流程
Netty 源码 NioEventLoop(三)执行流程 Netty 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html) 上文提到在启动 N ...
- Netty In Action中文版 - 第三章:Netty核心概念
在这一章我们将讨论Netty的10个核心类.清楚了解他们的结构对使用Netty非常实用.可能有一些不会再工作中用到.可是也有一些非经常常使用也非常核心,你会遇到. Bootstrap ...
- netty的调优-及-献上写过注释的源码工程
Netty能干什么? Http服务器 使用Netty可以编写一个 Http服务器, 就像tomcat那样,能接受用户发送的http请求, , 只不过没有实现Servelt规范, 但是它也能解析携带的参 ...
- Netty高性能原理和框架架构解析
1.引言 Netty 是一个广受欢迎的异步事件驱动的Java开源网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端. 本文基于 Netty 4.1 展开介绍相关理论模型,使用场景,基本组件 ...
随机推荐
- TCP/IP协议介绍
一.什么是TCP/IP TCP/IP是一类协议系统,它是用于网络通信的一套协议集合 TCP/IP是供已连接因特网的计算机进行通信的通信协议 TCP/IP指传输控制协议/网际协议 TCP/IP定义了电子 ...
- java架构之路-(Redis专题)Redis的高性能和持久化
上次我们简单的说了一下我们的redis的安装和使用,这次我们来说说redis为什么那么快和持久化数据 在我们现有的redis中(5.0.*之前的版本),Redis都是单线程的,那么单线程的Redis为 ...
- WPF写圆形头像,带消息提醒图标
<DockPanel VerticalAlignment="Top" HorizontalAlignment="Stretch" Margin=" ...
- eclipse常用快捷键即项目操作
快捷键: 1.代码提示:Alt+/ 2.撤销上一步操作:Ctrl+z:取消撤销:Ctrl+y: 3.如何注销一整段代码?☞▲第一种注释方法是每行代码前加//:先选中,然后按Ctrl+/:取消注销方法一 ...
- Spring Security 入门(一)
当你看到这篇文章时,我猜你肯定是碰到令人苦恼的问题了,我希望本文能让你有所收获. 本人几个月前还是 Spring 小白,几个月走来,看了 Spring,Spring boot,到这次的 Spring ...
- 机器学习:weka中Evaluation类源码解析及输出AUC及交叉验证介绍
在机器学习分类结果的评估中,ROC曲线下的面积AOC是一个非常重要的指标.下面是调用weka类,输出AOC的源码: try { // 1.读入数据集 Instances data = new Inst ...
- Git推送到多个远程仓库
Git推送到多个远程仓库 Grey 原文地址 准备工作 在码云和Github上分别新建两个不包括任何文件的空仓库(若是两个已经有文件的仓库,请参见关联已经存在的项目) https://github.c ...
- “无处不在” 的系统核心服务 —— ActivityManagerService 启动流程解析
本文基于 Android 9.0 , 代码仓库地址 : android_9.0.0_r45 系列文章目录: Java 世界的盘古和女娲 -- Zygote Zygote 家的大儿子 -- System ...
- 打python&adb组合拳,实现微信读书永久免费读
用过“微信读书”的朋友都知道,如果我们想阅读全本的付费书籍,除了购买整本(使用书币)外,还可以使用无限卡.可无论是购买全书还是无限卡,归根结底都是要花银子的. 除此之外,还有一种方式——用阅读时长兑换 ...
- MyBatisCodeHelper-Pro插件破解
MyBatisCodeHelper-Pro: MyBatisCodeHelper-Pro是IDEA下的一个插件,功能类似mybatis plugin. 但是是收费的,我们可以对他进行破解 转载出处:h ...