本文介绍Java BIO(同步阻塞IO),伪异步IO,NIO(非阻塞IO),AIO(异步IO)这四种IO的情况,并对不同IO模型作比较。

目录

1.BIO

2.伪异步IO

3.NIO

4.AIO

5.四种IO比较

6.BIO\伪异步IO\NIO\AIO源码下载

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实现,所以没有最大连接句柄的限制。(题外话,这里说的eopllselect是说的linux下的IO复用,和selectepoll一样,清楚流程概念请直接看源码)。

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(一)引题的更多相关文章

  1. [重点]delphi 实现 根据给定的标题去《中国青年报》网上电子报数据中查找匹配的内容,并从该内容中取出引题、正题、副题、作者和正文。

    项目要求:根据给定的标题去<中国青年报>网上电子报数据中查找匹配的内容,并从该内容中取出引题.正题.作者和正文. unit Unit1; interface uses Winapi.Win ...

  2. Netty(二)入门

    在上篇<Netty(一)引题>中,分别对AIO,BIO,PIO,NIO进行了简单的阐述,并写了简单的demo.但是这里说的简单,我也只能呵呵了,特别是NIO.AIO(我全手打的,好麻烦). ...

  3. iOS 与 惯性滚动

    注:以下所有例子均 只 在 iOS 的微信中测试过,但对于饿了么APP的内置浏览器同样适用(两者使用相同内核) 引题 工作中常常有需要显示大量信息的情况,列表超出一屏就涉及到滚动的问题.例如 - va ...

  4. arguments 对象的老历史

    引题:为什么 JavaScript 中的 arguments 对象不是数组 http://www.zhihu.com/question/50803453 JavaScript 1.0 1995 年, ...

  5. java内存分配和String类型的深度解析

    [尊重原创文章出自:http://my.oschina.net/xiaohui249/blog/170013] 摘要 从整体上介绍java内存的概念.构成以及分配机制,在此基础上深度解析java中的S ...

  6. 【网站国际化必备】Asp.Net MVC 集成Paypal(贝宝)快速结账 支付接口 ,附源码demo

    开篇先给大家讲段历史故事,博主是湖北襄阳人.襄阳物华天宝,人杰地灵,曾用名襄樊.在2800多年的历史文化中出现了一代名相诸葛亮(卧龙),三国名士庞统(凤雏),魏晋隐士司马徽(水镜先生),唐代大诗人孟浩 ...

  7. 我心中的核心组件~MSMQ与Redis队列

    回到目录 这个文章其实是我心中的核心组件的第七回,确实在时间上有些滞后了,但内容并不滞后!本文MSMQ只是个引题,我确实不太想说它,它是微软自己集成的一套消息队列,寄宿在Window服务里,稳定性十在 ...

  8. 你知道require是什么吗?

    引题 用过node的同学应该都知道require是用来加载模块的,那你是否存在如下的疑问呢? 1. require(path)是如何依据path找到对应module呢? 2. 为何在模块定义中,一定要 ...

  9. [置顶] Ajax程序:处理异步调用中的异常(使用Asp.Net Ajax内建的异常处理方法)

    无论在Window应用程序,还是Web应用程序以对用户友好的方式显示运行时的异常都是很有必要,尤其对于可能有很多不确定因素导致异常的Web应用程序;在传统的Web开发中,处理异常的方式——设计专门一个 ...

随机推荐

  1. Android开发学习之路-PopupWindow和仿QQ左滑删除

    这周作业,要做一个类似QQ的左滑删除效果的ListView,因为不想给每个item都放一个按钮,所以决定用PopupWindow,这里记录一下 先放一下效果图: 先说明一下这里面的问题: ①没有做到像 ...

  2. android 手把手教您自定义ViewGroup(一)

    1.概述 在写代码之前,我必须得问几个问题: 1.ViewGroup的职责是啥? ViewGroup相当于一个放置View的容器,并且我们在写布局xml的时候,会告诉容器(凡是以layout为开头的属 ...

  3. Maven配置Nexus私服

    官方文档:http://books.sonatype.com/nexus-book/3.0/reference/maven.html#maven-sect-single-group 1,下载安装 首先 ...

  4. .net 网络编程

    1.首先说下计算机网络中的TCP/IP参考模型 TCP/IP把网络分为5层,每一层负责完成不同的功能 1)应用层:传输报文,提供各种网络应用,有FTP.SMTP.HTTP等协议 2)运输层:传输报文段 ...

  5. 基于ReactCSSTransitionGroup实现react-router过渡动画

      此前,我使用了react-router库来完成单页应用的路由,从而实现组件之间的切换能力.然而,默认页面的切换是非常生硬的,为了让页面切换更加缓和与舒适,通常的方案就是过渡动画. 这里我调研了2种 ...

  6. 查看Validate Subscription 的结果

    Sql Server Replication Monitor 提供一个feature,能够verify Replication的 Publication 和 Subscription 的数据同步sta ...

  7. 调用Child Package

    使用Execute Package Task,能够在一个package中调用并执行其他package,被调用的Package称作 Child Package,Execute Package Task ...

  8. html5多出来的字自动隐藏并显示...

  9. Entity Framework Code First执行SQL语句、视图及存储过程

    1.Entity Framework Code First查询视图 Entity Framework Code First目前还没有特别针对View操作的方法,但对于可更新的视图,可以采用与Table ...

  10. Unity3D研究院之Prefab里面的Prefab关联问题

    最近在做UI部分中遇到了这样的问题,就是Prefab里面预制了Prefab.可是在Unity里面一旦Prefab预制了Prefab那么内部的Prefab就失去关联.导致与如果要改内部的Prefab需要 ...