您好,欢迎访问一九零五行业门户网

Netty是什么?Netty相关知识的深入解析

本篇文章给大家带来的内容是关于netty是什么?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是什么?netty相关知识的深入解析的详细内容。
其它类似信息

推荐信息