Netty学习(一)

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

一、Java IO

Java IO即Java 输入输出系统。在Java IO中,流从概念上来说是一个连续的数据流,既可以从流中读取数据,也可以往流中写数据。IO相关的媒介包括:

  • 文件
  • 管道
  • 网络连接
  • 内存缓存
  • System.in, System.out

IO的设计,主要是解决IO相关的操作。从数据传输的方式上,分为字节流和字符流。字节流一次性读取传输一个字节,而字符流则是以字符为单位进行读取传输。

 

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

 二、什么是NIO

NIO即new IO,是在JDK1.4引入的,NIO和IO有相同的作用和目的,但实现方式不同,NIO主要用到的是块,所以NIO的效率要比IO高很多。

NIO的核心对象包括:

  • Buffer:在NIO中,所有的数据都是用Buffer处理的,它是NIO读写数据的中转池。Buffer实质上是一个数组,通常是一个字节数据,但也可以是其他类型的数组。
  • Channel:是一个对象,可以通过channel读取和写入数据,是IO中流的抽象。但是channel是双向的,也可以是异步读写,并且channel读写必须通过buffer。
  • Selector:是一个对象,可以同时监听多个channel上发生的事件,并且能够根据事件情况决定Channel读写。
// 打开Selector
Selector selector = Selector.open();
// 将channel注册到selector
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

Selector感兴趣的事件有SelectionKey.OP_CONNECT, SelectionKey.OP_ACCEPT, SelectionKey.OP_READ, SelectionKey.OP_WRITE。

SelectionKey表示通道channel在Selector上的注册,事件的传递是通过SelectionKey,也可以通过selectionKey获取注册的channel和对应绑定的selector。

Channel  channel  = selectionKey.channel();
Selector selector = selectionKey.selector(); 

一旦向selector注册一个或者多个通道后,就可以调用重载的select()方法,select()方法会返回读事件已经就绪的那些通道

  • int select():阻塞到至少有一个通道的事件就绪
  • int select(long timeout):与select一样,多个一个超时时间
  • int selectNow():不会阻塞,不管什么通道就绪都立刻返回,如果没有通道可选择,就返回0.

一旦调用了select()方法,它就会返回一个数值,表示一个或多个通道已经就绪,然后你就可以通过调用selector.selectedKeys()方法返回的SelectionKey集合来获得就绪的Channel。某个线程调用select()方法后阻塞了,即使没有通道就绪,也有办法让其从select()方法返回。

  • 让其它线程在调用select方法的对象上调用Selector.wakeup()方法即可,阻塞在select()方法上的线程会立马返回。
  • 如果其它线程调用了wakeup(),但是当前没有线程阻塞在select上,下一个调用select阻塞的线程会被立即唤醒。

三、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()时,它不会通知,直到该文件描述符上出现第二次可读写事件才会通知你!!!这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符

发布了150 篇原创文章 · 获赞 25 · 访问量 16万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章