Netty(一)引题
本文介绍Java BIO(同步阻塞IO),伪异步IO,NIO(非阻塞IO),AIO(异步IO)这四种IO的情况,并对不同IO模型作比较。
目录
1.BIO
2.伪异步IO
3.NIO
4.AIO
5.四种IO比较
1.BIO
采用BIO通信模型的服务器,通常由一个独立的Acceptor线程负责监听客户端的连接,它接收到客户端连接请求后为每个客户端创建一个新的线程进程链路连接处理,处理完后,通过输出流返回应答给客户端,线程销毁。
该模型最大的问题性能问题,当客户端并发访问增加后,服务端线程增加,当线程数膨胀后,系统的性能下降,随着并发量增大,系统会发生线程堆栈溢出、创建新线程失败等问题,最终导致线程宕机或者僵死,不能对外提供服务。而且开线程有很大的开销,影响服务器性能。
源码在src/main/java/NIOInduction/BIO下,分为客户端和服务端,简单的网络、线程的处理。
2.伪异步IO
为了解决同步阻塞IO面临的一个链接需要一个线程处理情况,现在引入了“池”的概念,加入了线程池。
当有新的客户端连接的时候,将客户端的Socket封装为Task(java的Runnable接口实现了)投递到后端线程池中进行处理。由于线程池可以设置消息队列的大小和最大线程数,因此它的资源是可控的,无论多少个客户端并发访问,都不会导致资源的耗尽和宕机。
伪异步IO通讯框架采用了线程池的实现,因此避免了为每个请求都创建一个独立的线程造成的线程资源耗尽问题。但是由于它的底层的通信依然采用的同步阻塞模型,因此无法从根本上解决问题。
java输出流InputStream:当对socket的输入流进行读操作时,它会一直阻塞下去,直到发生以下三种事件。
- 有数据可读;
- 可用数据已经读取完毕;
- 发生空指针或者IO异常。
这意味着当对方发数据请求或者应答消息缓慢(网络传输慢)时,读取写入流一方的通讯线程将长时间阻塞,如果对方要100s才有消息发生完成,读取的一方的IO线程也会将同步阻塞100s,在此时间里,其他接入消息只能在消息队列中排队。
java输入流OutputStream:当调用OutputStream的write方法写输出流的时候,它将会呗阻塞,直到所有要发送的字节全部写入完毕,或者发生异常。搞过TCP/IP的都晓得,当消息的接收方处理缓慢的时候,将不能及时从TCP缓冲区读取数据,这将导致发送方的TCP window size不断减小,直到为0,双方处于keep-alive状态,消息发送方就不能再将TCP缓冲区写入数据,这时采用同步阻塞的IO,write操作将会无限期阻塞,直到tcp window size大于0或者发生IO异常。
源码在src/main/java/NIOInduction/PseudoAsynchronousIO下,分为客户端和服务端。客户端和BIO的客户端一样,服务端加入了线程池ExecutorService,相关构造函数请读者自行查阅。
3.NIO
NIO库,是在JDK1.4中引入的,NIO弥补了同步阻塞IO的不足。在所有的数据,NIO都是用缓冲区处理掉的(Buffer),任何时候访问NIO中的数据,都是通过缓冲区进行操作。缓冲区实际就是一个数组。Java NIO的基础是多路复用器Selector,简单来说,selector会不断的轮询注册在其上的Channel(通道,全双工的),如果某个Channel上有新的TCP连接接入、读写事件,这个Channel会处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以获取就绪的select集合,进行后续的IO操作。
一个多路复用器可以同时轮询多个Channel,而且由于jdk使用了epoll替代了select实现,所以没有最大连接句柄的限制。(题外话,这里说的eopll、select是说的linux下的IO复用,和select、epoll一样,清楚流程概念请直接看源码)。
NIO服务端序列图
1.打开ServerSocketChannel,用于监听客户端的连接,它是所有客户端连接的父管道。
ServerSocketChannel accptorSvr = ServerSocketChannel.open();
2.绑定监听端口,设置连接为非阻塞模式。
acceptorSvr.socket().bind(
new InetSocketAddress(InetAddress.getByName("IP"),port));
acceptorSvr.configureBlocking(false);
3.创建Reactor线程,创建多路复用器并启动线程。
Selector selectot = Selector.open();
new Thread(new RectorTask()).start();
4.将SelectSocketChannel注册到Reactor线程的多路复用器selector上,监听accept事件。
SelectionKey key = acceptorSvr.register(selector,SelectionKey.OP_ACCEPT,ioHandler);
5.多路复用器在线程run方法中无线循环里轮询准备就绪的key。
int num = selector.select();
Set selectkeys = selector.selectedKeys();
Iterator it = selectkeys.iterator();
while(it.hasNext)
{
SelectionKey key = (SelectionKey)it.next;
/* deal with IO event */
}
6.多路复用监听到有新的用户接入,处理新的接入请求,完成TCP三次握手,建立物理连接。
SocketChannel sc = ssc.accept();
7.设置客户端链路为非阻塞模式
sc.configureBlocking(false);
sc.socket().setReuseAddress(true);
...
8.将新接入的客户端连接注册到Reactor线程的多路复用器上,监听读操作,用来读取客户端发送的网络消息。
SelectionKey key = sc.register(selector,SelectionKey.OP_READ,ioHangler);
9.异步读取客户端请求消息到缓冲区
int readNumber = channel.read(receivedBuffer);
10.对bytebuffer进行编解码,如果有半包消息指针reset,继续读取后续的报文,将解码成功的消息封装成task,投递到业务线程池中,进行业务逻辑处理。
Object message = null;
while (buffer.hasRemain()){
byteBuffer.mark();
Object message = decode(byteBuffer);
if(message==null){
byteBuffer.reset();
break;
}
messageList.add(message);
}
if(!byteBuffer.hasRemain()){
byteBuffer.clear();
}
else byteBuffer.compact();
if(messageList!=null & !messageList.isEmpty()) {
for(Object messageF:messageList)
handleTask(messageE);
}
11.将pojo对象encode成bytebuffer,调用SocketChannel的异步write接口,将消息异步发送到客户端。
socketChannel.wite(buffer);
注意:如果发送区TCP缓冲区满了,会导致写半包,此时,需要注册写操作位,循环写,直到整个包消息写入TCP缓冲区。
NIO客户端序列图(大多数和服务端类似)
1.打开SocketChannel,绑定客户端本地地址(可选,默认系统会随机分配一个可用的本地地址)
SocketChannel clientChannel = SocketChannel.open();
2.设置SocketChannel为非阻塞模式,同时设置连接的TCP参数。
SocketChannel.configureBlocking(false);
socket.setReuseAddress(true);
socket.setReceiveBufferSize(BUFFER_SIZE);
socket.setSendBufferSize(BUFFER_SIZE);
3.异步连接服务器。
boolean connected = clientChannel.connect(new InetSocketAdress("ip",port));
4.判断是否连接成功,如果成功,则直接注册读状态位到多路复用器中,如果没成功(异步连接,返回false,说明客户端已经已经发送sync包,服务端没有返回ack包,物理连接还没建立——关于ack、sync包,请读者自行查阅TCP/IP中的TCP的三次握手,四次分手的过程)
if(connect)
clientChannel.register(selector,SelectionKey.OP_READ,ioHandler);
else
clientChannel.register(selector,SelectionKey.OP_CONNECT,ioHandler);
5.向Reactor线程的多路复用器注册OP_CONNECT状态位,监听服务器的TCP ACK应答。
clientChannel.register(selector,SelectionKey.OP_CONNECT,ioHandler);
6.创建Reactor线程,创建多路复用器并启动线程。
Selector selectot = Selector.open();
new Thread(new RectorTask()).start();
7.多路复用器在线程run方法中无线循环里轮询准备就绪的key。
int num = selector.select();
Set selectkeys = selector.selectedKeys();
Iterator it = selectkeys.iterator();
while(it.hasNext)
{
SelectionKey key = (SelectionKey)it.next;
/* deal with IO event */
}
8.接收connect事件进行处理
if(key.isConnectable())
//handlerConnect();
9.判断连接结果,如果连接成功,注册读事件到多路复用器
if(channel.finishConnect())
registerRead();
10.注册读事件到多路复用器
clientChannel.register(selector,SelectionKey.OP_READ,ioHandler);
11.异步读取客户端请求消息到缓冲区
int readNumber = channel.read(receivedBuffer);
12.对bytebuffer进行编解码,如果有半包消息指针reset,继续读取后续的报文,将解码成功的消息封装成task,投递到业务线程池中,进行业务逻辑处理。
Object message = null;
while (buffer.hasRemain()){
byteBuffer.mark();
Object message = decode(byteBuffer);
if(message==null){
byteBuffer.reset();
break;
}
messageList.add(message);
}
if(!byteBuffer.hasRemain()){
byteBuffer.clear();
}
else byteBuffer.compact();
if(messageList!=null & !messageList.isEmpty()) {
for(Object messageF:messageList)
handleTask(messageE);
}
13.将pojo对象encode成bytebuffer,调用SocketChannel的异步write接口,将消息异步发送到客户端。
socketChannel.wite(buffer);
注:以上的客户端和服务端过程,了解就行,上层的代码不一定这样写的,具体参考能运行的代码。
源码在src/main/java/NIOInduction/NIO下,分为客户端和服务端。
4.AIO
NIO2.0中引入了新的异步通道的概念,并提供了异步文件通道h额异步套接字通道的实现。
异步通道提供2种方式获取操作结果:
- 通过java.util.concurrent.Futurn类来表示异步操作的结果;
- 在执行异步操作的时候传入一个java.nio.channels.
CompletionHandler接口的实现类作为操作完成的回溯。
NIO2.0的异步套接字通道,对应UNIX网络编程中的事件驱动IO(AIO),它不需要通过多路复用器(Selector)对注册的通道进行轮询操作。
源码在src/main/java/NIOInduction/AIO下,分为客户端和服务端。
5.四种IO比较
6.BIO\伪异步IO\NIO\AIO源码下载
GitHub地址:https://github.com/orange1438/Netty_Course
作者:orange1438
出处:http://www.cnblogs.com/orange1438/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
Netty(一)引题的更多相关文章
- [重点]delphi 实现 根据给定的标题去《中国青年报》网上电子报数据中查找匹配的内容,并从该内容中取出引题、正题、副题、作者和正文。
项目要求:根据给定的标题去<中国青年报>网上电子报数据中查找匹配的内容,并从该内容中取出引题.正题.作者和正文. unit Unit1; interface uses Winapi.Win ...
- Netty(二)入门
在上篇<Netty(一)引题>中,分别对AIO,BIO,PIO,NIO进行了简单的阐述,并写了简单的demo.但是这里说的简单,我也只能呵呵了,特别是NIO.AIO(我全手打的,好麻烦). ...
- iOS 与 惯性滚动
注:以下所有例子均 只 在 iOS 的微信中测试过,但对于饿了么APP的内置浏览器同样适用(两者使用相同内核) 引题 工作中常常有需要显示大量信息的情况,列表超出一屏就涉及到滚动的问题.例如 - va ...
- arguments 对象的老历史
引题:为什么 JavaScript 中的 arguments 对象不是数组 http://www.zhihu.com/question/50803453 JavaScript 1.0 1995 年, ...
- java内存分配和String类型的深度解析
[尊重原创文章出自:http://my.oschina.net/xiaohui249/blog/170013] 摘要 从整体上介绍java内存的概念.构成以及分配机制,在此基础上深度解析java中的S ...
- 【网站国际化必备】Asp.Net MVC 集成Paypal(贝宝)快速结账 支付接口 ,附源码demo
开篇先给大家讲段历史故事,博主是湖北襄阳人.襄阳物华天宝,人杰地灵,曾用名襄樊.在2800多年的历史文化中出现了一代名相诸葛亮(卧龙),三国名士庞统(凤雏),魏晋隐士司马徽(水镜先生),唐代大诗人孟浩 ...
- 我心中的核心组件~MSMQ与Redis队列
回到目录 这个文章其实是我心中的核心组件的第七回,确实在时间上有些滞后了,但内容并不滞后!本文MSMQ只是个引题,我确实不太想说它,它是微软自己集成的一套消息队列,寄宿在Window服务里,稳定性十在 ...
- 你知道require是什么吗?
引题 用过node的同学应该都知道require是用来加载模块的,那你是否存在如下的疑问呢? 1. require(path)是如何依据path找到对应module呢? 2. 为何在模块定义中,一定要 ...
- [置顶] Ajax程序:处理异步调用中的异常(使用Asp.Net Ajax内建的异常处理方法)
无论在Window应用程序,还是Web应用程序以对用户友好的方式显示运行时的异常都是很有必要,尤其对于可能有很多不确定因素导致异常的Web应用程序;在传统的Web开发中,处理异常的方式——设计专门一个 ...
随机推荐
- 王宝强新片P2P风波持续发酵,互金真的前途未卜?
王宝强离婚风波还未完全结束,一波未平一波又起,新片又引来话题爆点,其自导自演的电影<大闹天竺>陷P2P平台集资的新闻占据各大媒体头条. 该P2P平台为湖北武汉一家P2P互联网金融理财平台& ...
- EditText的小细节
EditText获取焦点时,右边图片的改变例如: <item android:state_window_focused="false" android:drawable=&q ...
- 移动web app开发必备 - 异步队列 Deferred
背景 移动web app开发,异步代码是时常的事,比如有常见的异步操作: Ajax(XMLHttpRequest) Image Tag,Script Tag,iframe(原理类似) setTimeo ...
- lintcode 落单的数(位操作)
题目1 落单的数 给出2*n + 1 个的数字,除其中一个数字之外其他每个数字均出现两次,找到这个数字. 链接:http://www.lintcode.com/zh-cn/problem/single ...
- Javascript正则构造函数与正则表达字面量&&常用正则表达式
本文不讨论正则表达式入门,即如何使用正则匹配.讨论的是两种创建正则表达式的优劣和一些细节,最后给出一些常用正则匹配表达式. Javascript中的正则表达式也是对象,我们可以使用两种方法创建正则表达 ...
- 窥探Swift之数组与字典
说到数组和字典,只要是编过程的小伙伴并不陌生.在Swift中的数组与字典也有着一些让人眼前一亮的特性,今天的博客就来窥探一下Swift中的Array和Dictionary.还是沿袭之前的风格,在介绍S ...
- Objective-C中的深拷贝和浅拷贝
在Objective-C中对象之间的拷贝分为浅拷贝和深拷贝.说白了,对非容器类的浅拷贝就是拷贝对象的地址,对象里面存的内容仍然是一份,没有新的内存被分配.对非容器类的深拷贝就是重写分配一块内存,然后把 ...
- 快刀斩乱麻之 Katana
Katana-武士刀,寓意:快.准.狠! 按照常规,我们一般编写的 ASP.NET 应用程序会部署在 IIS 上(有点傻的描述),在 ASP.NET 应用程序中,我们会大量使用 HttpContext ...
- Windows Server 2012 为什么没有“磁盘清理”选项了?
用习惯了客户端版的Windows,对于磁盘清理想必大家都不会陌生,他具有安全.快捷.准确.集中化的删除系统中的临时文件.管理系统还原卷影副本.添加删除程序的快捷入口等便捷功能,而在Server版的Wi ...
- MongoDB的CRUD操作
1. 前言 在上一篇文章中,我们介绍了MongoDB.现在,我们来看下如何在MongoDB中进行常规的CRUD操作.毕竟,作为一个存储系统,它的基本功能就是对数据进行增删改查操作. MongoDB中的 ...