Netty
Java 的AIO
重要的三个类:AsynchronousServerSocketChannel(服务端)、AsynchronousSocketChannel(客户端)和CompletionHandler(用户处理器)
CompletionHandler接口实现应用程序向操作系统发起IO操作
Buffer的基本用法就是:
初始化(allocate
)–> 写入数据(read / put
)–> 转换为写出模式(flip
)–> 写出数据(get
)–> 转换为写入模式(compact
)–> 写入数据(read / put
)…
五种IO通信模型:
-
阻塞IO
-
非阻塞IO
-
多路复用IO(selector 复用器)重点
本质和阻塞IO类似,相较于阻塞IO更适合用于处理多连接高并发的情形,常见于:Java NIO、Nginx;
-
信号驱动IO
伪异步
- 异步IO
适合用于高性能高并发应用
同步和异步
同步和异步是指CPU时间片的利用,主要看请求发起方对结果的获取是主动还是被动的;
发起结果获取请求后,
同步阻塞:一直等待应答
同步非阻塞:可以先处理其他事情,但要不断轮询结果
异步阻塞:等待系统通知结果,但是同时自己也一直在等通知
异步非阻塞:处理其他事情,系统处理完成后通知自己
BIO (Blocking IO)
NIO (Non-Blocking IO)
NIO其实也是阻塞的:只不过他阻塞的时间段只在于数据从内核态到用户态的拷贝过程,是面向缓存区来设计的
BIO是面向流的,NIO是面向缓冲区的;
面向流:数据在一个流上,直来直去的,中间不间断顺序也不能乱,从头到尾都必须一直等待;
面向缓冲区:数据将被装到一个缓冲区里,就像先翻一个桶在哪里,当水龙头里面的水来了之后,就会自动装到这个桶里,这里就有些复 杂的情况了:1.当水太少,装不满桶,不够用;2.对自己需要的水量预估不足或者额外多了一些水进来,最后我们都不能得到我们想要的结果,最终我们只能不都拿的去检查水桶里的水是否足够,这其实花费了更多的开销
Java 1.7 的NIO2 才真正实现了异步IO,即AIO
Mina & Netty
都是韩国大神Trustin Lee的作品
对比:两者有很多特性都一样
Mina依赖于Apache
Netty依赖于JBoss
一般使用中两者性能相差无几;
概念
Netty三大核心组件:缓冲区Buffer、选择器Selector、通道Channel
- Buffer
position:指定下一个将要读取或者写入的位置,初始值为0,又put()或get()自动维护
limit:指定还有多少数据需要读取或者还有多少空间能够被写入
capacity:缓冲区的容量,即底层数组的大小;
flip():重设缓冲区,将当前位置设为限制值,然后将当前位置设为0;
hasRemaining():查看当前位置和限制值之间是否有元素;
remaining():返回剩余元素个数;
alloctate():初始化一个指定大小的缓冲区;
wrap():将现有数组包装为缓冲区;
slice():分片,截取缓冲区的一个窗口,方便操作,大小为从postion到limit;
asReadOnlyBuffer():只读缓冲区;
allocateDirect():直接缓冲区,尽量避免中间copy操作,使得IO速度更快;
MappedByteBuffer:内存映射
- Selector
selector是注册各种IO事件的地方,当这些事件发生之后,它就会告知我们发生的所有事件;
每个事件有对应的selectionKey ;
selectionKey 对应了事件发生的通道(Channel);
通过selectionKey 拿到Channel我们就可以进行数据读写等操作;
NIO编程基本就是分三步:
-
向Slector注册感兴趣的事件;
-
从Selector中读取感兴趣的事件;
-
处理事件
- Channel
它是一个对象,通过它可以读取或者写入数据;当然所有数据都是通过Buffer对象来处理,我们永远不会将字节直接写进Channel,而是先写进缓冲区,读取也是同理;
使用NIO读取分为三步:
- 从Stream中获取Channel;
- 创建Buffer;
- 将数据从Channel读取到Buffer;
类似的,写入也是三步:
- 从Stream中获取Channel;
- 创建Buffer;
- 将数据从Buffer写入到Channel;
- 多路复用
主要包括四种:select、poll、epoll、kqueue
对于java而言,windows下仅支持select一种,这种技术特别适合高并发场景(1ms上千请求);其他场景这一设计模式并不能发挥优势;
文件描述:FileDescriptor,IO通过这个列来操作文件
IOUtil.makePipe()
Netty和NIO
Netty采用NIO而非AIO的理由
- Netty不看重windows上的使用,在Linux上,AIO底层仍使用epoll,没有很好的实现AIO, 性能上么有明显优势;
- Netty采用Reactor模型,而AIO采用Proactor;
- AIO有个缺点就是:接收数据需要预先分配缓存,而NIO是需要接收时才分配缓存,因此对于对连接数量非常大但流量小的情况,AIO浪费了很多内存;
PipeLine是Netty的职责链,用来存放各种handler;
context 用于封装hander,方便操作;
每个hander都在单独线程执行;
handelr主要分为inbound(入站)和outbound(出站),执行顺序遵循:inbound先add的先执行,而outbound先add后执行。
Bootstrap
Bootstrap是一个用来初始化Netty的工厂类;
-
handler()和childHandler()的主要区别是,handler()是发生在初始化的时候,childHandler()是发生在客户端连接之后。
也就是说,如果需要在客户端连接前的请求进行handler处理,则需要配置handler(),如果是处理客户端连接之后的handler,则需要配置在childHandler()。
-
对于ChannelOption.SO_BACKLOG的解释
服务器端TCP内核模块维护有2个队列,我们称之为A,B吧
客户端向服务端connect的时候,发送带有SYN标志的包(第一次握手)
服务端收到客户端发来的SYN时,向客户端发送SYN ACK 确认(第二次握手)
此时TCP内核模块把客户端连接加入到A队列中,然后服务器收到客户端发来的ACK时(第三次握手)
TCP没和模块把客户端连接从A队列移到B队列,连接完成,应用程序的accept会返回
也就是说accept从B队列中取出完成三次握手的连接
A队列和B队列的长度之和是backlog,当A,B队列的长之和大于backlog时,新连接将会被TCP内核拒绝
所以,如果backlog过小,可能会出现accept速度跟不上,A.B 队列满了,导致新客户端无法连接,
要注意的是,backlog对程序支持的连接数并无影响,backlog影响的只是还没有被accept 取出的连接
上一篇: C#无边框控制窗体移动
推荐阅读
-
Netty源码分析 (三)----- 服务端启动源码分析
-
netty源码解析(4.0)-28 ByteBuf内存池:PooledByteBufAllocator-把一切组装起来
-
spring+netty服务器搭建的方法
-
Netty浅析
-
netty源码解解析(4.0)-23 ByteBuf内存管理:分配和释放
-
Netty源码分析之ChannelPipeline(二)—ChannelHandler的添加与删除
-
从BIO到Netty的演变
-
Netty解决粘包和拆包问题的四种方案
-
Netty源码分析 (十)----- 拆包器之LineBasedFrameDecoder
-
史诗级最强教科书式“NIO与Netty编程”