欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

nio&netty系列之一nio基础

程序员文章站 2022-03-02 15:28:12
...

 

写在文章初始

 

 

       很久没有读源码了,近来据说netty的源码很漂亮,而且自身对nio的理解其实一直也不到位,所以有天突然心血来潮,决定沉下心,重新学习nio的知识,并通过读netty的源码来加深理解,但是现实生活中,工作,娱乐,心情等各种原因,导致整个过程很漫长,所以我决定一篇一篇的整理我的笔记内容发布到我的博客。算是一个进步过程吧.

 

JAVA NIO基础

 

        NIO是jdk1.4推出的新概念,同JDK1.5版本concurrent并发包,是当前java应用中非常火的两个玩意,前者提升了网络传输编码的性能,后者整个将并发编程的高度提升了一个档次。此文首先从NIO的基础入手(jdk7都有nio2了,得赶紧补旧知识的说),然后再去慢慢深入netty的设计与实现,仅当做我最近一段时间从事netty使用这个工作的一个笔记。

      NIO有三个重要概念:缓冲区(ByteBuffer),通道(Channel),选择器(Selected),通道传输的数据必须是缓冲区对象,通道注册在选择器上,选择器上有四个事件,op_accept(服务器上特有,接受连接事件),op_connection(客户端特有,建立连接事件),op_writer(写事件),op_read(读事件).

     我们先略微介绍下这三个概念,如果对这三个概念需要细究的同学,附件有上传我学习的nio知识的PDF,个人认为已经很详细了.

  1. 缓冲区:首先在NIO中,我们不再以传统的socket的字符流传递数据,而是以块传递数据,是将数据放到缓存区然后推送到通道的。也就是说通道只接受Buffer对象,通常我们使用ByteBuffer.
  2.  通道:字面意思,客户端和服务器端连接的一条通道,用于传输数据,只接受Buffer对象。通道可以注册到选择器上。
  3. 选择器:这个概念较复杂,如果熟悉linux的IO系统的童鞋,那么恭喜,概念是类似的,java的nio的思想应该是来源于linux的异步非阻塞模式select.选择器用于管理所有的通道,是个事件触发模式,当通道就绪的时候,就会触发响应的事件并使对应的key就绪,然后主程序只要获取所有的就绪key(每一个key对应一个唯一的通道),然后做对应的业务逻辑即可。

NIO的过程

      JAVA的NIO模型是借鉴了操作系统,应该可以说是直接借助了操作系统的IO模型,由于本人对NIO的源码未曾仔细深入,且对linux太菜,所以这些资料基本都是从别人的博客资料查询而知。因没自信介绍NIO的模型,所以暂时用生活的一个场景来描述NIO的工作过程。

      餐厅点菜是一个很好的场景,我记得也有人用过。在以前,我们点菜的时候,服务员会等在你旁边记录你要点的菜,有时候你思考的越久,服务员等待的越久,这就是传统的SOCKET,这个服务员在你点菜的过程中被阻塞了。那么现在点菜就不一样了,服务员给你一张菜单和点菜单,你自己写完菜单然后叫服务员即可,这就是NIO模型,那我们就来分析这个新的点菜过程。

      1、客户进入餐厅,餐厅总台指定一个服务员上来迎接,帮你安排座位,给你菜单和点菜单。

      2、服务员去总台服务员那里登记有新客户到来,并且由我服务,客户正在准备点餐。

      3、客户思考今天要吃什么菜,并且记录到点菜单上,让服务员把点菜单放到总台哪边去

      4、总台服务员定时扫描点菜单

      5、将点菜内容发送给厨房。

      6、厨房厨师开始准备指定的餐

      7、总台服务员定时扫描,看那些客户的菜做完了,就通知与客户对应的服务员过来

      8、服务员就把菜送到客户那里。

      这里我们客户初略的概括为,餐厅总台是服务器,客户是客户端,服务员是通道(channel)。对于餐厅而言,服务员收取点餐单是读事件,送餐是写事件,点菜单和餐都是缓冲区(buffer),总台的服务员是选择器(selector),厨房的厨师就是业务逻辑实现者。所以上述六个过程概括为

      1、餐厅与客户创建一个通道,这个通道是指服务员。

      2、然后将此通道注册到selector(总台服务员)上,并为此selector注册了一个读事件(告诉总台服务员客户要准备点餐)

      3、客户写数据到点菜单,并有通道(服务员)交给餐厅总台哪里。

      4、服务器端的选择器selector查看哪边的点菜单写完了,触发服务器的读事件

      5、总台读取缓存区(点菜单)数据发送到业务逻辑那里

      6、业务逻辑(做菜),把做好的菜放到缓冲区(餐盘)

      7、selector扫描哪个客户的数据处理完毕,就通知指定通道

      8、通道将数据传输给客户。

NIO与传统socket对比      

那么说到此处,概念仍旧模糊,那么这个NIO与传统的Socket编程的优势体现在哪里?我来比较下传统socket编程的流程和nio编程的流程来分析下孰优孰劣.

 传统socket编程流程

     1、客户端创建一个socket连接到服务器

     2、客户端等待socket建立连接(阻塞)

     3、客户端发起请求

     4、客户端读取数据(阻塞)

     5、结束。

     注意:

     第一步:创建socket建立连接是个阻塞模式,所以说创建socket的成本很高,以前编程我们会使用一个socket连接池,创建一批socket连接放到连接池中去,从连接池获取socket进行操作,操作完成归还socket回连接池,例如数据库连接池是一样的设计。我们通常称之为长连接。

     第三步,客户端读取数据是个阻塞模式,在读取数据的时候我在等待服务器返回数据。如果服务器响应时间为10S,那么我这里要等待10秒才能获取数据。这个很难优化。

     NIO编程流程

     1、客户端创建一个通道(SocketChannel)

     2、创建一个选择器(Selector,如果没有选择器的话)

     3 、通道注册到选择器上,注册事件为OP_CONNECT

     4、客户端选择器触发OP_CONNECT事件。(编码中移除该就绪key)

     5、捕捉到OP_CONNECT后等待连接创建完毕(阻塞)

     6、连接创建完毕,直接发送数据。并且注册当前通道为OP_READ事件。

     7、当服务器端有数据返回的时候,触发通道的OP_READ事件

     8、读取内容。

     注意:
     1、第五步其实也是一个阻塞模式,所以不要频繁的创建通道,一般一个服务器和一个客户端之间只需要一个通道存在,最好是长期存在的。
     两者比较:NIO编程比传统的socket编程从逻辑上看就复杂很多,但是它主要是优化了传统socket编程的第四步,读取数据的时候可以不再是个阻塞模式,而是一个异步模式,在NIO状态下,发送完数据,注册一个OP_READ事件后就不管了,执行接下去的业务逻辑,直到有数据从服务器返回才会触发读取操作。
     疑问二:我客户端就是要同步操作,发送完数据后就是想等待返回数据,那么NIO的优势不是没有了么?

     其实大家如果换位思考,上面我换成服务器端,那么大家思考下,传统socket编程模式下,客户端与服务器端建立socket后,服务器首先也是在等待客户端传输数据过来,这个过程是一直阻塞的,而服务器端此过程完全没必要使用同步,这是很消耗服务器性能的。在NIO模式下,服务器与客户端建立完通道,服务器注册一个OP_READ事件后,服务器的CPU就可以去忙其它事情了,等客户端数据传输过来后,服务器才会触发读事件并处理相应业务逻辑,所以NIO对于服务器的性能提升是很明显的,并且由于NIO使用了块的传输方式,充分的利用了当今操作系统的性能,在传输性能上也会有明显的提升。

      BTW:这是我对传统socket编程和NIO的一些浅薄理解

 JAVA NIO 简单实例

//服务器端类
public class NioService {
       private static final int port = 4444;
       private Selector selector = null;

       // 启动服务
       public void startUp() {
             try {
                   int channels = 0;
                   int nKeys = 0;
                   int currentSelector = 0;

                   // 创建一个通道
                  ServerSocketChannel serverSocketChannel = ServerSocketChannel
                              . open();
                   // 绑定地址,类似 sokcet的绑定
                  serverSocketChannel.socket().bind(
                               new InetSocketAddress(InetAddress.getLocalHost(), 4444));
                   // 必须设置成非同步模式,在要注册到选择器的前提下,否则在注册的时候会报异常
                  serverSocketChannel.configureBlocking( false);
                   // 使用Selector
                   selector = Selector.open();
                   // 注册一个感兴趣的事件,这里是OP_ACCEPT事件.
                  SelectionKey s = serverSocketChannel.register(selector ,
                              SelectionKey. OP_ACCEPT);
                  System. out.println("服务器启动成功" );
                  listen();

            } catch (IOException e) {
                   // TODO Auto-generated catch block
                  e.printStackTrace();
            }
      }

       private void listen() throws IOException {
             int nKeys = 0;
             while (true ) {
                   // 阻塞线程,至到有就绪通道,返回值为有多少key已经就绪
                  nKeys = selector.select();
                   //获取就绪key的集合
                   Set selectedKeys = selector.selectedKeys();
                   for (Iterator keys = selectedKeys.iterator(); keys.hasNext();) {
                        SelectionKey key = (SelectionKey) keys.next();
                         //将就绪的key从就绪key中移除掉,否则每次获取都是就绪的,而非更新的就绪key
                        keys.remove();
                         if (key.isAcceptable()) {
                              SocketChannel socketChannel= ((ServerSocketChannel) key.channel()).accept().socket()
                              .getChannel();
                              socketChannel.configureBlocking( false);
                              socketChannel.register( selector,
                                                      SelectionKey. OP_READ);
                        } else if (key.isReadable()) {
                              Socket socket = ((SocketChannel) key.channel()).socket();
                              SocketChannel sc = socket.getChannel();
                              sc.configureBlocking( false);
                              ByteBuffer bb = ByteBuffer. allocate(1024);
                              sc.read(bb);
                              System. out.println("客户端接受到的数据为:" +new String(bb.array()));
                              sc.register( selector,
                                                      SelectionKey. OP_WRITE);
                        } else if (key.isWritable()) {
                              Socket socket = ((SocketChannel) key.channel()).socket();
                              SocketChannel sc = socket.getChannel();
                              ByteBuffer bb = ByteBuffer. wrap("hello world again!"
                                          .getBytes());
                              sc.write(bb);
                              sc.configureBlocking( false);
                              sc.register( selector,
                                                      SelectionKey. OP_READ);
                        }
                  }
            }
      }

       public static void main(String[] ben) {
            NioService ns = new NioService();
            ns.startUp();
      }

 

//客户端类
public class NioClient {
       public static void main(String[] ben) throws IOException{
             int selectNum;
            
            Selector selector = Selector. open();
            SocketChannel socketChannel = SocketChannel. open();
            socketChannel.configureBlocking( false);
            socketChannel.register(selector, SelectionKey. OP_CONNECT);
            socketChannel.connect( new InetSocketAddress(InetAddress.getLocalHost(), 4444));
            
            
             while(true ){
                  selectNum = selector.select();
                   if(selectNum>0){
                        Set<SelectionKey> keys= selector.selectedKeys();
                         for(Iterator<SelectionKey> it = keys.iterator();it.hasNext();){
                              SelectionKey key = it.next();
                              it.remove();
                               if(key.isConnectable()){
                                    SocketChannel channel = ((SocketChannel)key.channel());
                                    channel.configureBlocking( false);
                                     // 判断此通道上是否正在进行连接操作。 
                          // 完成套接字通道的连接过程。 
                          if (channel.isConnectionPending()) { 
                              channel.finishConnect();
                              channel.write(ByteBuffer. wrap("client connection over!".getBytes()));
                                          channel.register(selector, SelectionKey.OP_WRITE );
                          }
                              } else if (key.isWritable()){
                                    key.channel().configureBlocking( false);
                                    ((SocketChannel)key.channel()).write(ByteBuffer. wrap("hello,i'm from client!".getBytes()));
                                    key.channel().register(selector, SelectionKey.OP_READ );
                                     //客户端线程去处理其它业务逻辑了
                                    System. out.println("cc" );
                                    System. out.println("cc" );
                                    System. out.println("cc" );
                                    System. out.println("cc" );
                                    System. out.println("cc" );
                                    System. out.println("cc" );
                              } else if (key.isReadable()){
                                    ByteBuffer bb = ByteBuffer.allocate(1024);
                                    ((SocketChannel)key.channel()).read(bb);
                                    System. out.println(new String(bb.array()));
                                    key.channel().configureBlocking( false);
//                                  key.channel().register(selector, SelectionKey.OP_WRITE);
                                     //本次模拟的是一次读取过程,所以读完后就关闭
                                    key.channel().close();
                                    selector.close();
                              }
                              
                        }
                  }
            }
      }

}

 

 

 

 

 

 

 

 

相关标签: nio