深度解析NIO底层.docx
- 文档编号:987907
- 上传时间:2022-10-15
- 格式:DOCX
- 页数:10
- 大小:1.23MB
深度解析NIO底层.docx
《深度解析NIO底层.docx》由会员分享,可在线阅读,更多相关《深度解析NIO底层.docx(10页珍藏版)》请在冰豆网上搜索。
深度解析NIO底层
深度解析NIO底层
文章首发于公众号:
松花皮蛋的黑板报
作者就职于京东,在稳定性保障、敏捷开发、高级JAVA、微服务架构有深入的理解
一、BIO、NIO的介绍
我们可以分别启动高版本tomcat-8和低版本tomcat-6,然后模拟连接Socketsocket=newSocket(“localhost”,8080)会发现BIO和NIO最明显的区别
1、BIO的应用每来一个请求都会起一个线程去接待。
2、NIO的应用会有一个accepter线程专门负责接待。
所以NIO在做高并发和高吞吐量服务时比BIO更加适合,并且在长链接的情况下,同一时间对CPU的利用率也更高。
传统的BIO也叫”同步阻塞IO”,还有一种叫做”同步非阻塞I/O”
那么问题来了?
我们的NIO底层用的是那种I/O模型呢?
其实是IO多路复用
这里提到了两个概念:
1、select/poll多路由复用器(这里可以先简单的理解成一个队列,有一个单线程在不断的轮询处理队列里的事件)
2、fd类似于一种请求事件(Socket描述符).有一点很重要,这里的select轮询是阻塞的。
刚刚说的I/O复用模型可以说是奠定了NIO的模型基础,但是我们的UNIX对这个模型做了进一步的优化,刚刚的图我们可以发现一个明显的问题,什么问题呢?
select/poll线程像傻瓜一样的顺序轮询fd列表,而且处理的fd也是有限的。
默认是1024个。
这个时候epoll(EventPoll基于事件驱动的select/poll模型)模型就呼之欲出了。
这里的select轮询同样也是阻塞的。
在就绪队列里的数据为空的时候,select内部的监听线程就会阻塞。
所以我们说NIO是一个典型的同步非阻塞的I/O。
而底层的IO模型是采用的epoll方式的多路复用模型。
(在UNIX和Linux下)
总结一下,epoll模型相比select/poll模型的一些优点
select/poll模型
1、每次调用select,都需要把fd集合从用户态拷贝到内核态。
2、每次调用select都需要在内核遍历传递进来的所有fd。
3、select支持的文件描述符数量太小了,默认是1024。
epoll模型
1、初次调用select时,会挂载所有的fd进来,并且没有从用户态到内核态的内存复制,而是通过内核和用户空间mmap同一块内存来实现的。
2、epoll在事件就绪时的触发并没有遍历所有fd,而是遍历就绪态的fd链表,节省了大量的CPU时间。
3、所支持的fd的上限是操作系统的最大文件句柄数,简单理解,也就是可以支持的连接数。
一般来说1GB内存的机器上大约是10W个句柄左右
二、NIO的模型介绍和实现原理
NIO这个概念,早在jdk1.4的时候就支持了,我们完全可以通过jdk中的nio功能模块去实现一个NIO框架的服务。
下面给出一个简单的例子
publicclassNioDemo{
publicstaticvoidmain(String[]args)
{
try{
initServer(9999);
listenSelector();
}catch(IOExceptione){
e.printStackTrace();
}
}
privatestaticSelectorselector;
publicstaticvoidinitServer(intport)throwsIOException{
//init一个通道,并且打开对连接的接收
ServerSocketChannelserverSocketChannel=ServerSocketChannel.open();
//设置为非阻塞
serverSocketChannel.configureBlocking(false);
//绑定端口
serverSocketChannel.socket().bind(newInetSocketAddress(port));
//打开多路复用器,监控监听fd
selector=Selector.open();
//注册监听器,SelectionKey.OP_ACCEPTOP_CONNECTOP_READOP_WRITE
serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
System.out.println("服务端启动成功");
}
publicstaticvoidlistenSelector()throwsIOException{
while(true){
System.out.println("select开始阻塞");
selector.select();
Iterator
System.out.println("获取到新的key");
while(keyIterator.hasNext()){
SelectionKeyselectionKey=keyIterator.next();
//删除已选的key,防止重复处理
keyIterator.remove();
try{
handler(selectionKey,keyIterator);
}catch(IOExceptione){
e.printStackTrace();
}
}
}
}
publicstaticvoidhandler(SelectionKeyselectionKey,Iterator
if(selectionKey.isAcceptable()){
System.out.println("新的客户端连接");
//有新的客户端连接则注册到读就就绪事件
ServerSocketChannelserverSocketChannel=(ServerSocketChannel)selectionKey.channel();
SocketChannelchannel=serverSocketChannel.accept();
channel.configureBlocking(false);
channel.register(selector,SelectionKey.OP_READ);
}elseif(selectionKey.isReadable()){
//通道可读说明可以从buffer里取数据
SocketChannelsocketChannel=(SocketChannel)selectionKey.channel();
ByteBufferbuffer=ByteBuffer.allocate(1024);
intreadData=socketChannel.read(buffer);
if(readData>0){
Stringmsg=newString(buffer.array(),"GBK").trim();
System.out.println("服务端收到消息"+msg);
ByteBufferbyteBuffer=ByteBuffer.wrap("我收到你的消费了".getBytes("UTF-8"));
socketChannel.write(byteBuffer);
}else{
System.out.println("客户端关闭");
selectionKey.cancel();
}
}else{
System.out.println(selectionKey.isValid());
}
}
}
复制代码
当我telnet192.168.0.1019999时,会打印
服务端启动成功
select开始阻塞
获取到新的key
新的客户端连接
select开始阻塞
当我通过浏览器访问时,会打印
服务端启动成功
select开始阻塞
获取到新的key
新的客户端连接
select开始阻塞
获取到新的key
新的客户端连接
select开始阻塞
获取到新的key
新的客户端连接
服务端收到消息GET/HTTP/1.1
Host:
192.168.0.101:
9999
Connection:
keep-alive
Cache-Control:
max-age=0
Upgrade-Insecure-Requests:
1
User-Agent:
Mozilla/5.0(Macintosh;IntelMacOSX10_14_4)
AppleWebKit/537.36(KHTML,likeGecko)Chrome/73.0.3683.103Safari/537.36
Accept:
text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3
Accept-Encoding:
gzip,deflate
Accept-Language:
zh-CN,zh;q=0.9,en;q=0.8
select开始阻塞
获取到新的key
客户端关闭
select开始阻塞
演示完了上面的代码,可以大概的看到他的执行思路是优先注册链接事件,然后监听这个事件,收到事件后处理完成后,又向select注册接下来的读取就绪、写入就绪事件。
我们称这种开发模型为Reactor模型,也叫反应堆模型
事件驱动模型,有一个或多个并发输入源,有一个ServiceHandler,有多个RequestHandlers
三、基于NIO实现的Nettty通信框架
Netty是基于NIO实现的网络通信框架,在rpc框架中被广为应用(dubbo、jsf),同时Netty可以支持多种协议的开发,非常适合高并发的网络编程(弹幕、游戏服务器等)
下面是Netty对JDK原生NIO的一些增强
1、实现了事件分发,和业务执行的线程池隔离。
(也就是我们说的IO线程、工作线程职责剥离)
2、一个NIO服务端处理网络的闪断、客户端的重复接入、客户端的安全认证、消息的编解码、半包读写等情况。
而这些在Netty中都得到了很好的解决。
3、代码编写较复杂,缺少封装,每增加一层处理需要修改的地方有很多,且很难调试。
而Netty实现了PipeLine来实现不同的上下行的Handler。
4、需要具备其他的额外技能做铺垫,例如熟悉Java多线程编程。
这是因为NIO编程涉及到Reactor模式,你必须对多线程和网路编程非常熟悉,才能编写出高质量的NIO程序。
5、Netty在健壮性、功能、性能、可定制性和可扩展性在同类框架中都是首屈一指的。
四、Netty案例演示
这里以弹幕为例,介绍一下Netty在实际项目中的应用。
这里我们使用WebSocket+Netty的方式来实践弹幕的推送,设想一下,如果我们不用Netty能不能实现弹幕系统的功能?
肯定是可以实现的:
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 深度 解析 NIO 底层
