编者注: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模型,其中处理连接事件的架构图如下:

对应的处理逻辑源码为:

  1. // 处理各种IO事件
  2. private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
  3. final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
  4. try {
  5. int readyOps = k.readyOps();
  6. if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
  7. // OP_CONNECT事件,client连接上客户端时触发的事件
  8. int ops = k.interestOps();
  9. ops &= ~SelectionKey.OP_CONNECT;
  10. k.interestOps(ops);
  11. unsafe.finishConnect();
  12. }
  13. if ((readyOps & SelectionKey.OP_WRITE) != 0) {
  14. ch.unsafe().forceFlush();
  15. }
  16. if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
  17. // 注意,这里读事件和ACCEPT事件对应的unsafe实例是不一样的
  18. // 读事件 -> NioByteUnsafe, ACCEPT事件 -> NioMessageUnsafe
  19. unsafe.read();
  20. }
  21. } catch (CancelledKeyException ignored) {
  22. unsafe.close(unsafe.voidPromise());
  23. }
  24. }

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

ACCEPT事件

  1. // NioMessageUnsafe
  2. public void read() {
  3. assert eventLoop().inEventLoop();
  4. final ChannelConfig config = config();
  5. final ChannelPipeline pipeline = pipeline();
  6. final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
  7. allocHandle.reset(config);
  8. boolean closed = false;
  9. Throwable exception = null;
  10. try {
  11. do {
  12. // 调用java socket的accept方法,接收请求
  13. int localRead = doReadMessages(readBuf);
  14. // 增加统计计数
  15. allocHandle.incMessagesRead(localRead);
  16. } while (allocHandle.continueReading());
  17. } catch (Throwable t) {
  18. exception = t;
  19. }
  20. // readBuf中存的是NioChannel
  21. int size = readBuf.size();
  22. for (int i = 0; i < size; i ++) {
  23. readPending = false;
  24. // 触发fireChannelRead
  25. pipeline.fireChannelRead(readBuf.get(i));
  26. }
  27. readBuf.clear();
  28. allocHandle.readComplete();
  29. pipeline.fireChannelReadComplete();
  30. }

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

  1. // ServerBootstrapAcceptor
  2. public void channelRead(ChannelHandlerContext ctx, Object msg) {
  3. final Channel child = (Channel) msg;
  4. // 设置channel的pipeline handler,及channel属性
  5. child.pipeline().addLast(childHandler);
  6. setChannelOptions(child, childOptions, logger);
  7. for (Entry<AttributeKey<?>, Object> e: childAttrs) {
  8. child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
  9. }
  10. try {
  11. // 将channel注册到childGroup中的Selector上
  12. childGroup.register(child).addListener(new ChannelFutureListener() {
  13. @Override
  14. public void operationComplete(ChannelFuture future) throws Exception {
  15. if (!future.isSuccess()) {
  16. forceClose(child, future.cause());
  17. }
  18. }
  19. });
  20. } catch (Throwable t) {
  21. forceClose(child, t);
  22. }
  23. }

READ事件

  1. // NioByteUnsafe
  2. public final void read() {
  3. final ChannelConfig config = config();
  4. final ChannelPipeline pipeline = pipeline();
  5. final ByteBufAllocator allocator = config.getAllocator();
  6. final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
  7. allocHandle.reset(config);
  8. ByteBuf byteBuf = null;
  9. boolean close = false;
  10. try {
  11. do {
  12. byteBuf = allocHandle.allocate(allocator);
  13. // 从channel中读取数据,存放到byteBuf中
  14. allocHandle.lastBytesRead(doReadBytes(byteBuf));
  15. allocHandle.incMessagesRead(1);
  16. readPending = false;
  17. // 触发fireChannelRead
  18. pipeline.fireChannelRead(byteBuf);
  19. byteBuf = null;
  20. } while (allocHandle.continueReading());
  21. // 触发fireChannelReadComplete,如果在fireChannelReadComplete中执行了ChannelHandlerContext.flush,则响应结果返回给客户端
  22. allocHandle.readComplete();
  23. // 触发fireChannelReadComplete
  24. pipeline.fireChannelReadComplete();
  25. if (close) {
  26. closeOnRead(pipeline);
  27. }
  28. } catch (Throwable t) {
  29. if (!readPending && !config.isAutoRead()) {
  30. removeReadOp();
  31. }
  32. }
  33. }

写事件

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

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

CONNECT事件

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

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

推荐阅读

欢迎小伙伴关注【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. java 远程方法调用(RMI)

    什么是RMI? 维基百科:一种用于实现远程过程调用的应用程序编程接口.它使客户机上运行的程序可以调用远程服务器上的对象. 什么是序列化及反序列化 (1)序列化:把对象转换为字节序列的过程称为对象的序列 ...

  2. python编程基础之三十四

    面向对象:三大特征:封装,继承,多态 封装:隐藏对象的实现过程,对外仅仅公开接口,控制在程序中的读取和修改的访问级别 类,函数都是一种封装 属性私有化:当类里面的属性不想被外部访问,可以将这些属性设置 ...

  3. 面试常考各类排序算法总结.(c#)

    前言 面试以及考试过程中必会出现一道排序算法面试题,为了加深对排序算法的理解,在此我对各种排序算法做个总结归纳. 1.冒泡排序算法(BubbleSort) 1.1 算法描述 (1)比较相邻的元素.如果 ...

  4. 【NOIP2015】子串

    题目描述 有两个仅包含小写英文字母的字符串 A 和 B.现在要从字符串 A 中取出 k 个互不重叠的非空子串,然后把这 k 个子串按照其在字符串 A 中出现的顺序依次连接起来得到一 个新的字符串,请问 ...

  5. 在集成环境中修改配置文件php.ini不生效的解决办法

    配置完成之后一定要重启服务,如果重启服务还是不生效. 就去看一下phpinfo(),找到Loaded Configuration File选项,这个才是真正读取的配置文件

  6. 1046 Shortest Distance (20 分)

    1046 Shortest Distance (20 分) The task is really simple: given N exits on a highway which forms a si ...

  7. 小白学 Python(4):变量基础操作

    人生苦短,我选Python 引言 前文传送门 小白学 Python(1):开篇 小白学 Python(2):基础数据类型(上) 小白学 Python(3):基础数据类型(下) 前面的文章中,我们介绍了 ...

  8. [JZOJ5817] 【NOIP提高A组模拟2018.8.15】 抄代码

    Description J 君是机房的红太阳,每次模拟她总是 AK 虐场.然而在 NOIP2117 中,居然出现了另一位 AK 的选手 C 君! 这引起了组委会的怀疑,组委会认为 C 君有抄袭 J 君 ...

  9. 控制反转和依赖注入(对IOC,DI理解+案例)

    理解 控制反转说的官方一点就是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度.其实就是一种设计思想,大概思想就是把设计好的对象交给容器控制,而不是在你内部直接控制. 依赖注入是控制反 ...

  10. Spring Boot提供RESTful接口时的错误处理实践

    使用Spring Boot开发微服务的过程中,我们会使用别人提供的接口,也会设计接口给别人使用,这时候微服务应用之间的协作就需要有一定的规范. 基于rpc协议,我们一般有两种思路:(1)提供服务的应用 ...