Netty知识整理
2023-01-11 22:43:08 0 举报
AI智能生成
登录查看完整内容
整理netty相关的知识点,包括IO模型,粘包拆包,编解码,ByteBuf等
作者其他创作
大纲/内容
本地IO和网络IO
源IP
源端口
目标IP
目标端口
四要素
1.Socket起源于Unix,而Unix/Linux基本思想之一就是“一切皆文件”,也称为文件描述符
2.既然一切都是文件,那么就可以把对Socket的操作就是对“open—write/read—close”模式的一种实现
3.Socket是对TCP/IP协议的封装,Socket本身不是协议,通过Socket才能使用TCP/IP协议
socket
应用进程从发起 IO 系统调用,至内核返回成功标识,这整个期间是处于阻塞状态的。
同步阻塞IO模型
应用进程可以将 Socket 设置为非阻塞,这样应用进程在发起 IO 系统调用后,会立刻返回。应用进程可以轮询的发起 IO 系统调用,直到内核返回成功标识。
同步非阻塞IO模型
可以将多个应用进程的 Socket 注册到一个 Select(多路复用器)上,然后使用一个进程来监听该 Select(该操作会阻塞),Select 会监听所有注册进来的Socket。只要有一个 Socket 的数据准备好,就会返回该Socket
IO复用模型
可以为 Socket 开启信号驱动 IO 功能,应用进程需向内核注册一个信号处理程序,该操作并立即返回。当内核中有数据准备好,会发送一个信号给应用进程,应用进程便可以在信号处理程序中发起 IO 系统调用,来完成数据读取了。
信息驱动IO模型
应用进程发起 IO 系统调用后,会立即返回。当内核中数据完全准备后,并且也复制到了用户空间,会产生一个信号来通知应用进程。
异步非阻塞IO模型
unix五种IO模型
ServerSocket serverSocket = new ServerSocket(8080);
使用ServerSocket创建一个socket,然后用循环一直阻塞接收到的消息
BIO
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
SocketChannel socketChannel = serverSocketChannel.accept();,这里不会阻塞,在处理业务的时候还是会阻塞的
selector = Selector.open(); ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
将多个channel注册到selecter上,selecter一直循环到有消息时,通知channel进行读写操作
在连接个数较小,连接的活跃度较高情况下,效率高于epoll
select的跨平台做的很好,几乎每个平台都支持;
优点
单个进程能够监视的文件描述符的数量存在最大限制;
select()所维护的存储大量文件描述符的数据结构,随着文件描述符数量的增长,其在用户态和内核的地址空间的复制所引发的开销也会线性增长;
由于网络响应时间的延迟使得大量TCP连接处于非活跃状态,但调用select()还是会对所有的socket进行一次线性扫描 ,会造成一定的开销;
缺点
select/poll模型
支持的FD上限为系统定义的进程打开的最大FD个数,与系统内存相关;
IO效率不随FD数目增加而线性下降(FD中有大量的空闲连接),它只会对\"活跃\"的socket进行操作(根据每个fd上面的callback函数实现)。
epoll返回时已经明确的知道哪个sokcet fd发生了事件,不用再一个个比对,提高了效率。
在连接数量较小,或者连接活跃度都比较高的情况下,epoll的效率可能比不上select。
epoll只有Linux2.6以上才有实现 ,而其他平台都没有,跨平台性存在缺陷
epoll模型
NIO
AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(8080));
在实现accept方法时,会调用使用实现CompletionHandler接口内的completed或者是failed,整个过程都不是阻塞的
AIO
java 四种IO模型
首先客户端访问服务端,在服务端这边首先是使用Reactor监听accept事件和read事件,当有连接过来,就交给acceptor处理accept事件,当触发read事件,同时accept或把read事件交给handler处理
单Reactor单线程模型
首先客户端访问服务端,在服务端这边首先是使用Reactor监听accept事件和read事件,当有连接过来,就交给acceptor处理accept事件,当触发read事件,同时accept或把read事件交给线程池中的handler处理
单Reactor多线程模型
首先客户端访问服务端,在服务端这边首先是使用Reactor监听accept事件和read事件,当有连接过来,就交给acceptor处理accept事件,当触发read事件,交给多个subReactor处理,选中自己selector对应的handler处理read事件
主从Reactor多线程模型
Reactor模型
Proactorinitiator创建ProactorProactorinitiator创建handlerProactor通过内核提供的Asynchronous Operation processor把Proactor和handler注册到内核
启动初始化
当IO操作完成是内核通知ProactorProactor根据不同的IO事件回调不同的handler完成业务处理
客户端调用
Proactor模型
Reactor 是在事件发生时就通知事先注册的事件(读写在应用程序线程中处理完成);
Proactor 是在事件发生时基于异步 I/O 完成读写操作(由内核完成),待 I/O 操作完成后才回调应用程序的处理器来进行业务处理;
Reactor 模式是基于同步非阻塞 I/O 的;
Proactor 模式基于异步非阻塞 I/O
两者之间的区别
Reactor模型和Proactor模型
对比java的byteBuffer,做了优化
当创建时,为0,读取一段数据后,读指针后移
读指针readerIndex
当创建时为0,写数据时,写多少后移多少
写指针writerIndex
创建的容量
容量 capacity
已读的字节,可以通过resetReaderInder()重置读指针
废弃字节
读指针到写指针的范围
可读字节
写指针到容量的范围
可写字节
容量到数字最大值范围
当容量小于4M时,每次扩列容量翻倍
当容量大于等于4M时,每次扩容添加4M
扩大到2^64为止
扩容规则
可扩容字节
byteBuf
数据从客户端传输到服务端,可能会把多个小的数据包封装成一个大包发送
粘包
数据从客户端传输到服务,可能会把一个大的数据包拆成多个小数据包发送
拆包
粘包拆包
MessageToByteEncoder:将消息编码为字节
将消息对象转换成字节或其他序列形式在网络上传输,有两个抽象类可以继承
对应解码器中的LengthFieldBasedFrameDecoder
LengthFieldPrepender
编码
继承抽象类ByteToMessageDecoder,作用是通过ByteBuf去传递数据,我们把这种把ByteBuf(原始数据流) -> ByteBuf(用户数据) 称之为一次解码器
固定长度解码器
FixedLengthFrameDecoder
发送信息加上换行符\
LineBasedFrameDecoder
自定义分隔符解码器
DelimiterBasedFrameDecoder
采用大端序列还是小端
byteOrder
lengthFieldLength
需要调整的长度数,最后和长度的值相加为你要得到的值的长度
lengthAdjustment
为true时,length字段的值=length字段的长度+Content的长度。为false时,length字段的值=Content的长度
lengthIncludesLengthFieldLength
LengthFieldBasedFrameDecoder
netty提供四种的解码器
解码
将数据帧 转为正确的byte数组我们称之为一次解码,将byte数据流转换为java对象,称之为二次解码,那么在netty中的二次解码器都要去继承MessageToMessageDecoder类
二次编码解码
netty编码解码
一般情况下Java对象的声明周期都比Java虚拟机的要短,实际应用中我们希望在JVM停止运行之后能够持久化指定的对象,这时候就需要把对象进行序列化之后保存。
需要把Java对象通过网络进行传输的时候。因为数据只能够以二进制的形式在网络中进行传输,因此当把对象通过网络发送出去之前需要先序列化成二进制数据,在接收端读到二进制数据之后反序列化成Java对象。这是针对网络传输来说的,因为我们讲的是Netty,一个网络通信的框架,所以序列化也是针对网络传输来说的
使用作用
序列化的对象必须实现Serializable接口
尽量定义serialVersionUID,如果没有定义,系统在编码的时候会自动附一个值,对于应用是不可见的。数据修改后,可能这个值不同导致序列化失败
java序列化
将对象序列化成xml格式,由此可以看出,这类序列化消息体比较大,不利用高并发的网络应用
xml序列化
将数据序列化成json对象
json序列化
dubbo默认就是使用Hassian序列化
使用HessianInput和HessianOutput调用
hassian序列化
可以使用数据库的结构使用
avro序列化
谷歌提供的一个序列化方案,它是一个灵活高效的用于序列化数据的协议,相比较XML和JSON格式,protobuf更小、更快、更便捷
protobuf中正数使用varint算法,负数使用zigzag算法
速度快,编码解码方式简单,采用ProtocolBuffer自身的框架代码和编译器共同完成
序列化的数据量体积小:独特的编码方式,如Varint,Zigzag算法,采用T-L-V的数据存储方式
protobuf序列化
对性能要求不高的场景,可以采用基于XML的soap协议
基于前后端分离,或者独立的对外的api服务,选用json是比较好的,对于调试,可读性都很不错
选型建议
序列化和反序列化
1,各个子系统在不同的服务器上,通过注解实现远程依赖注入
2,通过注解将远程的服务中的接口通过网络传输得到服务类
3,使用序列化和反序列化将传输内容得到原始数据流
4,使用编码解码得到原始数据
5,通过反射得到具体的方法
6,利用spring的启动流程,将反射出来的bean注入到spring容器
7,接口调用的时候,即可远程方法的调用过程
基于netty实现RPC的原理
1,得到一个selector
2,得到一个serverSocketChannel
3,设置channel为非阻塞
4,关联selector和channel的关系,并没有关注事件
5,绑定端口
6,给channel注册accept事件
7,客户端连接过来之后,通过selector获取channel
8,通过accept获取socketchannel
9,给selector上监听op_read事件
对比的NIO流程
netty整体实现是通过NIO实现的,所以,整体的原理同上流程,封装了一层,对外提供简明的api
在每个NioEventLoop中都会有一个executor的执行器,因为NioEventLoop其实本身不是线程,只是里面有一个线程的变量,而这个执行器会创建一个线程,然后去执行任务
我们会生成多个boss和work的NioEventLoop对象,所以我们处理选择那个work和boss就需要使用选择器选择一个来处理任务,当然,这里boss其实只会使用到一个
创建的NioEventLoop这个对象,这个对象的构造函数中创建了很多的东西,比如多路复用器,也对应到我们之前说的每一个线程都有一个多路复用器,任务队列,任务都会放在这个队列中,然后run方法就是不停的从这个队列中获取任务执行
线程,就是刚才说的执行器会创建一个线程,并且会赋值给这个成员变量,刚初始化的时候是null
保存执行器,每一个NioEventLoop都有一个执行器
实例化NioServerSocketChannel实例,其实就是实例化ServerSocketChannel和设置channel为非阻塞,同时绑定一个pipeline
总结
main线程
注册空事件
绑定了端口和ip
最终注册了ACCEPT事件
selector.select()方法阻塞线程
客户端连接过来,获取到的SocketChannel封装成NIOSocketChannel
选择一个Work线程去处理,这个是由ServerBootstrapAcceptor类中的channelRead方法处理的
boss线程
注册Read事件
调用pipeline的handler上的channelRead方法,获取客户端的数据
通过ctx.writeAndFlush方法给客户端回写数据。
work线程
NioEventLopGroup
创建NioServerSocketChannel对象的时候,就创建了一个Pipeline,在获取到客户端的连接之后,把SocketChannel封装成NioSocketChannel,在它的构造函数中也创建一个Pipeline
1.channelRegistered: channel被注册到EventLoop时调用; register0>pipeline.fireChannelRegistered()
2.channelActive:channel以激活; register0>pipeline.fireChannelActive()
3.channelRead:从当前channel的对端读取消息; processSelectedKey()>unsafe.read()>pipeline.fireChannelRead(byteBuf)
4.channelReadComplete:消息读取完执行;processSelectedKey()>unsafe.read()>pipeline.fireChannelReadComplete(byteBuf)
5.exceptionCaught:处理异常;从unsafe.read()异常>handleReadException>pipeline.fireExceptionCaught(cause)
6.channelInactive:channel结束生命周期;从unsafe.read()异常>handleReadException>pipeline.fireChannelInactive()
7.channelUnregistered:把channel从EventLoop中注销;pipeline.fireChannelInactive()>pipeline.fireChannelUnregistered()
handle的生命周期
数据结构
pipeLine原理
对空轮训的死循环操作,使用的了512次的检查,如果出现会清除码,重新分配
selector结构优化,对原有的hashset数据结构转换,转换成数组,查询效率增加
线程优化
FastThreadLocalThread
内存泄露处理
FastThreadLocal
对NIO的优化
netty源码
编写代码更加简明,bug出现少
解决了粘包拆包,序列化等api
对比NIO
mina是apach维护,而且3.x会有很大的重构,api不兼容
netty的开发迭代迅速,api更加简洁优秀
mina将内核和一些特性的联系过于紧密,使得用户在不需要这些特性的时候无法脱离,相比之下性能会有所下降
netty比mina使用更加简单快速
对比mina
netty和其他框架的对比
Netty知识总结
0 条评论
回复 删除
下一页