本章先将Java中IO类型和底层的实现方式。

一、Java IO

IO的设计,主要是解决IO相关的操作。按照类型,Java中的IO分为

 

 

从数据传输的方式上,分为字节流和字符流。字节流一次性读取传输一个字节,而字符流则是以字符为单位进行读取传输。

Netty学习(一)

 

  • 文件类型:FileInputStream,FileOutputStream、FileReader、FileWriter
  • 数组类型:ByteArrayInputStream、ByteArrayOutputStream,CharArrayReader, CharArrayWriter
  • 管道操作:PipedInputStream, PipedOutputStream, Pipedreader, PipedWriter
  • 基本数据类型:DataInputStream、DataOutputStream
  • 缓冲操作:BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter
  • 打印:PrintStream、PrintWriter
  • 对象序列化反序列化:ObjectInputStream、ObjectOutputStream
  • 转换:InputStreamReader、OutputStreWriter

 二、BIO, NIO, AIO的区别于联系

阻塞和非阻塞

  • 阻塞操作时,当前线程会处于阻塞状态,无法进行其他任务,只有当满足一定条件时,才继续执行;
  • 非阻塞:非阻塞状态,不会去等待IO操作结束,会立即返回。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。

BIO:同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。

NIO:同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。

AIO:异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理.AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。

三、Java NIO和Netty

直接使用Java NIO的缺点:

  • NIO的类库和API繁杂,需要熟练掌握Selector,ServerSocketChannel、SocketChannel、ByteBuffer等才能很好使用。
  • 可靠性较弱,需要自行维护,工作量大,例如客户端面临断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常码流的处理等问题;
  • JDK NIO的BUG,例如epoll bug,它会导致Selector空轮询,最终导致CPU 100%。

Netty是对Java NIO的封装框架,简化了NIO的使用难度,Netty特性总结如下:

  • API使用简单,开发门槛低
  • 功能强大,预置了多种编解码功能,支持多种主流协议
  • 定制能力强,可以通过ChannelHandler对通信框架进行灵活地扩展
  • 性能高,通过与其他业界主流的NIO框架对比,Netty的综合性能最优
  • 成熟、稳定,Netty修复了已经发现的所有JDK NIO BUG,业务开发人员不需要再为NIO的BUG而烦恼
  • 社区活跃,版本迭代周期短,发现的BUG可以被及时修复,同时更多的新功能会加入
  • 经历了大规模的商业应用考验,质量得到验证。

四、select、poll、epoll之间的区别

select具有O(n)的无差别轮询复杂度,同时处理的流越多,无差别轮询时间就越长。

poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态, 但是它没有最大连接数的限制,原因是它是基于链表来存储的。

epoll可以理解为event poll,是事件驱动(每个事件关联上fd)的。

select:

select本质上是通过设置或者检查存放fd标志位的数据结构来进行下一步处理。这样所带来的缺点是:

1、 单个进程可监视的fd数量被限制,即能监听端口的大小有限。

      一般来说这个数目和系统内存关系很大,具体数目可以cat /proc/sys/fs/file-max察看。32位机默认是1024个。64位机默认是2048.

2、 对socket进行扫描时是线性扫描,即采用轮询的方法,效率较低:

       当套接字比较多的时候,每次select()都要通过遍历FD_SETSIZE个Socket来完成调度,不管哪个Socket是活跃的,都遍历一遍。这会浪费很多CPU时间。如果能给套接字注册某个回调函数,当他们活跃时,自动完成相关操作,那就避免了轮询,这正是epoll与kqueue做的。

3、需要维护一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大

poll:

poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,如果设备就绪则在设备等待队列中加入一项并继续遍历,如果遍历完所有fd后没有发现就绪设备,则挂起当前进程,直到设备就绪或者主动超时,被唤醒后它又要再次遍历fd。这个过程经历了多次无谓的遍历。

它没有最大连接数的限制,原因是它是基于链表来存储的,但是同样有一个缺点:

1、大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义。                   

2、poll还有一个特点是“水平触发”,如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd。

epoll有EPOLLLT和EPOLLET两种触发模式,LT是默认的模式,ET是“高速”模式。epoll的优点:

1、没有最大并发连接的限制,能打开的FD的上限远大于1024(1G的内存上能监听约10万个端口)
2、效率提升,不是轮询的方式,不会随着FD数目的增加效率下降。只有活跃可用的FD才会调用callback函数;

3、用MMP加速内核与用户空间的消息传递。


即Epoll最大的优点就在于它只管你“活跃”的连接,而跟连接总数无关,因此在实际的网络环境中,Epoll的效率就会远远高于select和poll。

EPOLLLT模式下,系统中一旦有大量不需要读写的就绪文件描述符,它们每次调用epoll_wait都会返回,这样会大大降低处理程序检索自己关心的就绪文件描述符的效率.。

采用EPOLLET这种边沿触发模式的话,当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用epoll_wait()时,它不会通知,直到该文件描述符上出现第二次可读写事件才会通知你!!!这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符

相关文章: