现在的位置: 首页 > 综合 > 正文

java网络编程自学(三)-TCP/IP+NIO

2018年01月23日 ⁄ 综合 ⁄ 共 3320字 ⁄ 字号 评论关闭

       在java中可以基于java.nio.channels中的Channel和Selector的相关类来实现TCP/IP+NIO方式的系统间通信。

用于系统间通信依靠SocketChannel和ServerSocketChannel,SocketChannel用于建立连接,监听事件及操作读写,ServerSocketChannel用于监听端口及监听连接事件,可通过Selector来获取是否有要处理的事件。基于这两个类实现客户端的关键代码如下:

      

SocketChannel channel=SocketChannel.open();
       //设置为非阻塞模式,使用selector必须设置为非阻塞模式。
       channel.configureBlocking(false);
       //对于非阻塞模式,立刻返回false,表示连接正在建立
       channel.connect(addr);
       Selector selector=Selector.open();
       //向channel注册selector以及感兴趣的连接事件
       channel.register(selector, SelectionKey.OP_CONNECT);
       //阻塞至有感兴趣的IO事件发生,或达到超时时间(TIME_OUT)。
       //如果希望一直等待至有感兴趣的IO事件发生,可调用无参数的select()方法,
       //如果希望不阻塞直接返回目前是否有感兴趣的事件发生,可调用selectNow()方法
       int nKeys=selector.select(TIME_OUT);
       SelectionKey skey=null;
       ByteBuffer byteBuffer=null;
       //大于0,表明有感兴趣的IO事件发生
       if(nKeys>0){
           Set<SelectionKey> keys=selector.selectedKeys();
           for(SelectionKey key : keys){
              //对于发生连接的事件
              if(key.isConnectable()){
                  SocketChannel sc=(SocketChannel)key.channel();
                  sc.configureBlocking(false);
                  //注册感兴趣的IO读事件,通常不直接注册写事件,在发送缓存区未满的
                  //情况下一直是可写的,因此如果注册了写事件,而又不用写数据,很容易
                  //造成CPU消耗100%的现象(??)
                  skey=sc.register(selector, SelectionKey.OP_READ);
                  sc.finishConnect();//完成连接的建立
              }
              //channel可读
              else if(key.isReadable()){
                  ByteBuffer buffer=ByteBuffer.allocate(1024);
                  SocketChannel sc=(SocketChannel)key.channel();
                  int readBytes=0;
                  try{
                     int ret=0;
                     try{
                         //读取目前可读取的数据,sc.read返回的为成功读取到ByteBuffer
                         //中的字节数,此步为阻塞操作,值可能为0;当读到数据末尾时,返回-1
                         while((ret=sc.read(buffer))>0){
                            readBytes+=ret;
                         }
                     }finally{
                         buffer.flip();
                     }
                  }finally{
                     if(buffer!=null){
                         buffer.clear();
                     }
                  }
              }
              //如果可写入channel
              else if(key.isWritable()){
                  //取消对OP——WRITE事件的注册
                  key.interestOps(key.interestOps()&(~SelectionKey.OP_WRITE));
                  SocketChannel sc=(SocketChannel)key.channel();
                  //此步为阻塞操作,直到写入操作系统发送缓冲区或网络IO出现异常,
                  //返回的为成功写入的字节数,当操作系统的发送缓冲区已满,此处返回0.
                  int writtenedSize=sc.write(byteBuffer);
                  //如未写入,则继续注册感兴趣的OP_WRITE事件
                  if(writtenedSize==0){
                     key.interestOps(key.interestOps()|SelectionKey.OP_WRITE);
                  }
                  /*对于要写入的channel,可直接调用channel.write来完成,只有在写入未成功时,
                  //才需要注册OP_WRITE事件
                  int wSize=channel.write(byteBuffer);
                  if(wSize==0){
                     key.interestOps(key.interestOps()|SelectionKey.OP_WRITE);
                  }*/
              }
           }
           selector.selectedKeys().clear();
       }

从上述代码中,NIO是Reactor模式的实现,通过注册感兴趣的事件及扫描是否有感兴趣的事件发生,从而执行相应的动作。

服务器端代码如下:

     

 ServerSocketChannel ssc=ServerSocketChannel.open();
       ServerSocket serverSocket=ssc.socket();
       serverSocket.bind(new InetSocketAddress(PORT));
       ssc.configureBlocking(false);
       Selector selector=Selector.open();
       //向channel注册selector以及感兴趣的连接事件
       ssc.register(selector, SelectionKey.OP_ACCEPT);
       //……类似客户端进行轮询,添加一个对key.
        /*
       SelectionKey key=null;
       if(key.isAcceptable()){
           ServerSocketChannelserver=(ServerSocketChannel)key.channel();
          SocketChannelsc=server.accept();
          if(sc==null){
              continue;
          }
           sc.configureBlocking(false);
           sc.register(selector, SelectionKey.OP_READ);
       }
       */

上面代码描述了java Tcp/Ip+NIO实现网络通信,其中涉及到很多Java NIO的知识。

比如:channel,Buffer,selector。Java 标准IO中基于流进行数据交互,java NIO中给出了许多Buffer,基于数据块进行操作,通过channel(通道)进行交互。Channel同时支持异步的读和写。且主要与buffer进行数据读写。

Selector也是Java NIO提供的类,能够监听网络IO中Channel的事件,检查Channel是否准备好可读/可写,以及连接(connect),accept。一个Selector可支持监听多个Channel的事件。这样NIO中可以一个线程可以支持多个请求发送。

同样,考虑客户端发送多个请求,NIO方式相比BIO可以做到不阻塞,因此客户端可以采用连接复用的方式,即每个SocketChannel在发送消息后,不用等待响应可继续发送其他消息。这样可以降低连接池带来的资源争抢问题,对系统性能有帮助。

对于服务器接受多个连接请求,通常采用的是有一个线程来监听连接事件,另一个线程来监听网络流的读写事件。

 当有网络流的读写事件时,在放入一个线程池中处理。这种方式比TCP/IP+BIO的好处是在于可接受很多连接,而这些连接只有在有真正的与服务器进行(请求)交互才会创建线程进行处理。这网上被称为:一请求一连接。

当连接数不多,或者连接数很多而且连接上的请求比较频繁是,TCP/IP NIO方式不会带相比TCP/IP+BIO理论上不会有太大的优势。

综上,TCP/IP+NIO对于高访问量的系统来说,服务器端可以支撑更多的连接。

 

本人菜鸟,边学边写。如果有错误,欢迎指正。Thanks!

 

抱歉!评论已关闭.