彻底理解Netty,这一篇文章就够了
Netty到底是什么
从HTTP说起
有了Netty,你可以实现自己的HTTP服务器,FTP服务器,UDP服务器,RPC服务器,WebSocket服务器,Redis的Proxy服务器,MySQL的Proxy服务器等等。
我们回顾一下传统的HTTP服务器的原理
1、创建一个ServerSocket,监听并绑定一个端口
2、一系列客户端来请求这个端口
3、服务器使用Accept,获得一个来自客户端的Socket连接对象
4、启动一个新线程处理连接
4.1、读Socket,得到字节流
4.2、解码协议,得到Http请求对象
4.3、处理Http请求,得到一个结果,封装成一个HttpResponse对象
4.4、编码协议,将结果序列化字节流 写Socket,将字节流发给客户端
5、继续循环步骤3
HTTP服务器之所以称为HTTP服务器,是因为编码解码协议是HTTP协议,如果协议是Redis协议,那它就成了Redis服务器,如果协议是WebSocket,那它就成了WebSocket服务器,等等。 使用Netty你就可以定制编解码协议,实现自己的特定协议的服务器。
NIO
上面是一个传统处理http的服务器,但是在高并发的环境下,线程数量会比较多,System load也会比较高,于是就有了NIO。
他并不是Java独有的概念,NIO代表的一个词汇叫着IO多路复用。它是由操作系统提供的系统调用,早期这个操作系统调用的名字是select,但是性能低下,后来渐渐演化成了Linux下的epoll和Mac里的kqueue。我们一般就说是epoll,因为没有人拿苹果电脑作为服务器使用对外提供服务。而Netty就是基于Java NIO技术封装的一套框架。为什么要封装,因为原生的Java NIO使用起来没那么方便,而且还有臭名昭著的bug,Netty把它封装之后,提供了一个易于操作的使用模式和接口,用户使用起来也就便捷多了。
说NIO之前先说一下BIO(Blocking IO),如何理解这个Blocking呢?
客户端监听(Listen)时,Accept是阻塞的,只有新连接来了,Accept才会返回,主线程才能继
读写socket时,Read是阻塞的,只有请求消息来了,Read才能返回,子线程才能继续处理
读写socket时,Write是阻塞的,只有客户端把消息收了,Write才能返回,子线程才能继续读取下一个请求
传统的BIO模式下,从头到尾的所有线程都是阻塞的,这些线程就干等着,占用系统的资源,什么事也不干。
那么NIO是怎么做到非阻塞的呢。它用的是事件机制。它可以用一个线程把Accept,读写操作,请求处理的逻辑全干了。如果什么事都没得做,它也不会死循环,它会将线程休眠起来,直到下一个事件来了再继续干活,这样的一个线程称之为NIO线程。用伪代码表示:
while true {
events = takeEvents(fds) // 获取事件,如果没有事件,线程就休眠
for event in events { if event.isAcceptable {
doAccept() // 新链接来了
} elif event.isReadable {
request = doRead() // 读消息
if request.isComplete() {
doProcess()
}
} elif event.isWriteable {
doWrite() // 写消息
}
}
}
Reactor线程模型
Reactor单线程模型
一个NIO线程+一个accept线程:

Reactor多线程模型

Reactor主从模型
主从Reactor多线程:多个acceptor的NIO线程池用于接受客户端的连接

Netty可以基于如上三种模型进行灵活的配置。
总结
Netty是建立在NIO基础之上,Netty在NIO之上又提供了更高层次的抽象。
在Netty里面,Accept连接可以使用单独的线程池去处理,读写操作又是另外的线程池来处理。
Accept连接和读写操作也可以使用同一个线程池来进行处理。而请求处理逻辑既可以使用单独的线程池进行处理,也可以跟放在读写线程一块处理。线程池中的每一个线程都是NIO线程。用户可以根据实际情况进行组装,构造出满足系统需求的高性能并发模型。
为什么选择Netty
如果不用netty,使用原生JDK的话,有如下问题:
1、API复杂
2、对多线程很熟悉:因为NIO涉及到Reactor模式
3、高可用的话:需要出路断连重连、半包读写、失败缓存等问题
4、JDK NIO的bug
而Netty来说,他的api简单、性能高而且社区活跃(dubbo、rocketmq等都使用了它)
什么是TCP 粘包/拆包
现象
先看如下代码,这个代码是使用netty在client端重复写100次数据给server端,ByteBuf是netty的一个字节容器,里面存放是的需要发送的数据
public class FirstClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) {
for (int i = 0; i < 1000; i++) {
ByteBuf buffer = getByteBuf(ctx);
ctx.channel().writeAndFlush(buffer);
}
}
private ByteBuf getByteBuf(ChannelHandlerContext ctx) {
byte[] bytes = "需要更多资料加群:586446657".getBytes(Charset.forName("utf-8"));
ByteBuf buffer = ctx.alloc().buffer();
buffer.writeBytes(bytes);
return buffer;
}
}
从client端读取到的数据为:

从服务端的控制台输出可以看出,存在三种类型的输出
一种是正常的字符串输出。
一种是多个字符串“粘”在了一起,我们定义这种 ByteBuf 为粘包。
一种是一个字符串被“拆”开,形成一个破碎的包,我们定义这种 ByteBuf 为半包。
透过现象分析原因
应用层面使用了Netty,但是对于操作系统来说,只认TCP协议,尽管我们的应用层是按照 ByteBuf 为 单位来发送数据,server按照Bytebuf读取,但是到了底层操作系统仍然是按照字节流发送数据,因此,数据到了服务端,也是按照字节流的方式读入,然后到了 Netty 应用层面,重新拼装成 ByteBuf,而这里的 ByteBuf 与客户端按顺序发送的 ByteBuf 可能是不对等的。因此,我们需要在客户端根据自定义协议来组装我们应用层的数据包,然后在服务端根据我们的应用层的协议来组装数据包,这个过程通常在服务端称为拆包,而在客户端称为粘包。
拆包和粘包是相对的,一端粘了包,另外一端就需要将粘过的包拆开,发送端将三个数据包粘成两个 TCP 数据包发送到接收端,接收端就需要根据应用协议将两个数据包重新组装成三个数据包。
如何解决
在没有 Netty 的情况下,用户如果自己需要拆包,基本原理就是不断从 TCP 缓冲区中读取数据,每次读取完都需要判断是否是一个完整的数据包 如果当前读取的数据不足以拼接成一个完整的业务数据包,那就保留该数据,继续从 TCP 缓冲区中读取,直到得到一个完整的数据包。 如果当前读到的数据加上已经读取的数据足够拼接成一个数据包,那就将已经读取的数据拼接上本次读取的数据,构成一个完整的业务数据包传递到业务逻辑,多余的数据仍然保留,以便和下次读到的数据尝试拼接。
而在Netty中,已经造好了许多类型的拆包器,我们直接用就好:

选好拆包器后,在代码中client段和server端将拆包器加入到chanelPipeline之中就好了:
如上实例中:
客户端:
ch.pipeline().addLast(new FixedLengthFrameDecoder(31));
服务端:
ch.pipeline().addLast(new FixedLengthFrameDecoder(31));

Netty 的零拷贝
传统意义的拷贝
是在发送数据的时候,传统的实现方式是:
1. `File.read(bytes)`
2. `Socket.send(bytes)`
这种方式需要四次数据拷贝和四次上下文切换:
1. 数据从磁盘读取到内核的read buffer
2. 数据从内核缓冲区拷贝到用户缓冲区
3. 数据从用户缓冲区拷贝到内核的socket buffer
4. 数据从内核的socket buffer拷贝到网卡接口(硬件)的缓冲区
零拷贝的概念
明显上面的第二步和第三步是没有必要的,通过java的FileChannel.transferTo方法,可以避免上面两次多余的拷贝(当然这需要底层操作系统支持)
1. 调用transferTo,数据从文件由DMA引擎拷贝到内核read buffer
2. 接着DMA从内核read buffer将数据拷贝到网卡接口buffer
上面的两次操作都不需要CPU参与,所以就达到了零拷贝。
Netty中的零拷贝
主要体现在三个方面:
1、bytebuffer
Netty发送和接收消息主要使用bytebuffer,bytebuffer使用对外内存(DirectMemory)直接进行Socket读写。
原因:如果使用传统的堆内存进行Socket读写,JVM会将堆内存buffer拷贝一份到直接内存中然后再写入socket,多了一次缓冲区的内存拷贝。DirectMemory中可以直接通过DMA发送到网卡接口
2、Composite Buffers
传统的ByteBuffer,如果需要将两个ByteBuffer中的数据组合到一起,我们需要首先创建一个size=size1+size2大小的新的数组,然后将两个数组中的数据拷贝到新的数组中。但是使用Netty提供的组合ByteBuf,就可以避免这样的操作,因为CompositeByteBuf并没有真正将多个Buffer组合起来,而是保存了它们的引用,从而避免了数据的拷贝,实现了零拷贝。
3、对于FileChannel.transferTo的使用
Netty中使用了FileChannel的transferTo方法,该方法依赖于操作系统实现零拷贝。
Netty 内部执行流程
服务端:


1、创建ServerBootStrap实例
2、设置并绑定Reactor线程池:EventLoopGroup,EventLoop就是处理所有注册到本线程的Selector上面的Channel
3、设置并绑定服务端的channel
4、5、创建处理网络事件的ChannelPipeline和handler,网络时间以流的形式在其中流转,handler完成多数的功能定制:比如编解码 SSl安全认证
6、绑定并启动监听端口
7、当轮训到准备就绪的channel后,由Reactor线程:NioEventLoop执行pipline中的方法,最终调度并执行channelHandler
8、说到这里顺便给大家推荐一个Java的交流学习社区:586446657,里面不仅可以交流讨论,还有面试经验分享以及免费的资料下载,包括Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。相信对于已经工作和遇到技术瓶颈的码友,在这里会有你需要的内容。
客户端


总结
以上就是我对Netty相关知识整理,如果有不同的见解,欢迎讨论!
彻底理解Netty,这一篇文章就够了的更多相关文章
- Android:学习AIDL,这一篇文章就够了(下)
前言 上一篇博文介绍了关于AIDL是什么,为什么我们需要AIDL,AIDL的语法以及如何使用AIDL等方面的知识,这一篇博文将顺着上一篇的思路往下走,接着介绍关于AIDL的一些更加深入的知识.强烈建议 ...
- (转载)Android:学习AIDL,这一篇文章就够了(下)
前言 上一篇博文介绍了关于AIDL是什么,为什么我们需要AIDL,AIDL的语法以及如何使用AIDL等方面的知识,这一篇博文将顺着上一篇的思路往下走,接着介绍关于AIDL的一些更加深入的知识.强烈建议 ...
- (转载)Android:学习AIDL,这一篇文章就够了(上)
前言 在决定用这个标题之前甚是忐忑,主要是担心自己对AIDL的理解不够深入,到时候大家看了之后说——你这是什么玩意儿,就这么点东西就敢说够了?简直是坐井观天不知所谓——那样就很尴尬了.不过又转念一想, ...
- (转) TensorFlow深度学习,一篇文章就够了
TensorFlow深度学习,一篇文章就够了 2016/09/22 · IT技术 · TensorFlow, 深度学习 分享到:6 原文出处: 我爱计算机 (@tobe迪豪 ) 作者: 陈迪 ...
- 面试题-关于Java线程池一篇文章就够了
在Java面试中,线程池相关知识,虽不能说是必问提,但出现的频次也是非常高的.同时又鉴于公众号"程序新视界"的读者后台留言让写一篇关于Java线程池的文章,于是就有本篇内容,本篇将 ...
- Android:学习AIDL,这一篇文章就够了(上)
前言 在决定用这个标题之前甚是忐忑,主要是担心自己对AIDL的理解不够深入,到时候大家看了之后说——你这是什么玩意儿,就这么点东西就敢说够了?简直是坐井观天不知所谓——那样就很尴尬了.不过又转念一想, ...
- 真的,Kafka 入门一篇文章就够了
初识 Kafka 什么是 Kafka Kafka 是由 Linkedin 公司开发的,它是一个分布式的,支持多分区.多副本,基于 Zookeeper 的分布式消息流平台,它同时也是一款开源的基于发布订 ...
- TensorFlow深度学习,一篇文章就够了
http://blog.jobbole.com/105602/ 作者: 陈迪豪,就职小米科技,深度学习工程师,TensorFlow代码提交者. TensorFlow深度学习框架 Google不仅是大数 ...
- go包管理速通,一篇文章就够了,再也不用担心因为不会导包被辞退
前言 最近在看一些go语言相关的书,发现了一个有意思的事情:其中一本书最新印刷的版本是2017年3月,而golang包管理的后起之秀go module伴随go1.11于2018年8月诞生--因此,书里 ...
随机推荐
- 10.Oracle Golden Date(ogg)的搭建和管理
一. GoldenGate 概述 GoldenGate现在是业内成熟的数据容灾与复制产品:GoldenGate是一种基于日志的结构化数据复制方式,它通过解析源数据库在线日志或归档日志获得数据的增删改变 ...
- MISCONF Redis is configured to save RDB snapshots, but it is currently not able to persist on disk. Commands that may modify the data set are disabled, because this instance is configured to report e
早上来到公司,线上的项目报错: Error in execution; nested exception is io.lettuce.core.RedisCommandExecutionExcepti ...
- 新唐N32905U1DN开发教程:MDK环境下工程搭建(官方提供的固件库)(转)
前言:新唐N32905U1DN单片机不支持用户将程序烧录到内部FLASH,必须外挂FLASH存储用户程序,可以是SPI FLASH.NAND FLASH.SD卡.用户采用裸机开发的方式可使用官方提供的 ...
- Axure RP 9 Beta 开放下载(更新激活密钥和汉化包)
2018年9月9号,7月9号来厦门入职,已经两个月了.这两个月的生活状态真心不好,一方面工作很忙(刚工作是这样?),虽然工资还可以,但总感觉性价比很低,自已对这份工作不够热爱也许.另一方面,来到新城市 ...
- [EMSE'17] A Correlation Study between Automated Program Repair and Test-Suite Metrics
Basic Information Authors: Jooyong Yi, Shin Hwei Tan, Sergey Mechtaev, Marcel Böhme, Abhik Roychoudh ...
- 【Py-Github】根据条件筛选Github repo的例子
条件: language:python commits:>100 contributors:>2 stars:>5 fork:0 实现: from github import Git ...
- AWS事故总结,几招教你规避风险
版权声明:本文由王煜奕原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/90687001488360802 来源:腾云阁 ht ...
- 终端命令行编译运行C#程序HelloWorld
终端命令行编译运行C#程序HelloWorld 今天忽然有人问我,刚学习java程序的时候,一般老师都会建议我们从配置环境变量开始从命令行编译运行程序,那么学习c#的时候基本是直接安装visual s ...
- Louvain 算法原理
Louvain算法是一种基于图数据的社区发现算法,算法的优化目标为最大化整个数据的模块度,模块度的计算如下: 其中m为图中边的总数量,k_i表示所有指向节点i的连边权重之和,k_j同理.A_{i,j} ...
- ubuntu百度云下载大文件
一.实验环境 ubuntu16.04 + 百度在线云盘 二.下载小文件步骤 小文件直接点击右侧的下载按钮即可,弹出文件保存对话框 三.大文件下载步骤 大文件使用如上方式下载时提示,请使用网盘客户端下载 ...