java网络编程(三)----同步非阻塞nio及reactor模型

本文介绍了Java NIO的基础,包括缓冲区、通道和选择器,并探讨了NIO与BIO的区别。文章通过示例详细讲解了Reactor模式在NIO中的应用,阐述了多路复用器Selector如何处理网络事件,以及如何通过Selector注册和处理读写事件。同时,对比了不同I/O模型,展示了Java NIO的非阻塞特性。
摘要由CSDN通过智能技术生成

很多刚接触NIO的人,第一眼看到的就是Java相对晦涩的API,比如:Channel,Selector,Socket什么的;然后就是一坨上百行的代码来演示NIO的服务端Demo,所以这里我们人性化地简单介绍一下。

NIO我们一般认为是New I/O(也是官方的叫法),因为它是相对于老的I/O类库新增的(其实在JDK 1.4中就已经被引入了,但这个名词还会继续用很久,即使它们在现在看来已经是“旧”的了,所以也提示我们在命名时,需要好好考虑),做了很大的改变。但民间跟多人称之为Non-block I/O,即非阻塞I/O,因为这样叫,更能体现它的特点。而下文中的NIO,不是指整个新的I/O库,而是非阻塞I/O。

NIO提供了与传统BIO模型中的Socket和ServerSocket相对应的SocketChannel和ServerSocketChannel两种不同的套接字通道实现。
新增的着两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。
对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用NIO的非阻塞模式来开发。


java nio基础实现

  1. 缓冲区 Buffer
    Buffer是一个对象,包含一些要写入或者读出的数据。
    在NIO库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的;在写入数据时,也是写入到缓冲区中。任何时候访问NIO中的数据,都是通过缓冲区进行操作。
    缓冲区实际上是一个数组,并提供了对数据结构化访问以及维护读写位置等信息。
    具体的缓存区有这些:ByteBuffe、CharBuffer、 ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer。他们实现了相同的接口:Buffer。

  2. 通道 Channel
    我们对数据的读取和写入要通过Channel,它就像水管一样,是一个通道。通道不同于流的地方就是通道是双向的,可以用于读、写和同时读写操作。
    底层的操作系统的通道一般都是全双工的,所以全双工的Channel比流能更好的映射底层操作系统的API。
    Channel主要分两大类:

    • SelectableChannel:用户网络读写
    • FileChannel:用于文件操作

    后面代码会涉及的ServerSocketChannel和SocketChannel都是SelectableChannel的子类。

  3. 多路复用器 Selector
    Selector是Java NIO 编程的基础。
    Selector提供选择已经就绪的任务的能力:Selector会不断轮询注册在其上的Channel,如果某个Channel上面发生读或者写事件,这个Channel就处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以获取就绪Channel的集合,进行后续的I/O操作。

    一个Selector可以同时轮询多个Channel,只需要一个线程负责Selector的轮询,就可以接入成千上万的客户端。


nio使用

回忆BIO模型,之所以需要多线程,是因为在进行I/O操作的时候,一是没有办法知道到底能不能写、能不能读,只能”傻等”,即使通过各种估算,算出来操作系统没有能力进行读写,也没法在socket.read()和socket.write()函数中返回,这两个函数无法进行有效的中断。所以除了多开线程另起炉灶,没有好的办法利用CPU。

NIO的读写函数可以立刻返回,这就给了我们不开线程利用CPU的最好机会:如果一个连接不能读写(socket.read()返回0或者socket.write()返回0),我们可以把这件事记下来,记录的方式通常是在Selector上注册标记位,然后切换到其它就绪的连接(channel)继续进行读写。

思考一下在Socket网络通讯中有什么事件:

  1. 在服务器端我们需要接收客户端连接,这里就有一个接收“Accept”事件;
  2. 而客户端连接服务器,连接有一个“Connect”事件
  3. 各自进行读操作时,有一个“read”事件
  4. 各自进行写操作时,有一个“write”事件

而上面我们说到连接被抽象为了Channel,这时我们就可以在多路复用器Selector上注册通道和事件。

  1. 在服务器端:ServerSocketChannel.register(Selector, SelectionKey.OP_ACCEPT);
    将ServerSocketChannel注册在Selector并绑定SelectionKey.OP_ACCEPT事件;
  2. 调用多路复用器Selector.select();
  3. 如果channel已经发生了接收客户端的事件,那么就能被多路复用器select到,然后获取到和客户端连接的Socket,再将这个socket的读写事件注册到Selector上
  4. 同样可以客户端也是将其他事件注册到Selector,然后事件被触发后就可以被select()函数找到
  5. 最后程序不断轮询selector,根据select到的不同事件类型调用对应的handler进行处理。

select()函数调用的是系统底层函数,在Linux 2.6之前是select、poll,2.6之后是epoll,Windows是IOCP。

伪代码如下:

   interface ChannelHandler{
   
      void channelReadable(Channel channel);
      void channelWritable(Channel channel);
   }
   class Channel{
   
     Socket socket;
     Event event;//读,写或者连接
   }

   //IO线程主循环:
   class IoThread extends Thread{
   
   public void run(){
   Channel channel;
   //选择就绪的事件和对应的连接
   while(channel=Selector.select()){
      if(channel.event==accept){
          //触发了accept事件的是新连接,
          //我们需要为这个新连接注册读写事件
         registerNewChannelHandler(channel);
      }
      if(channel.event==write){
          //如果可以写,则执行写事件
         getChannelHandler(channel).channelWritable(channel);
      }
      if(channel.event==read){
      //如果可以读,则执行读事件
          getChannelHandler(channel).channelReadable(channel);
      }
    }
   }
   //所有channel的对应事件处理器
   Ma
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值