java nio之数据读写时无限循环分析与解决

    写这片文章是因为自己昨天刚解决了一个十个月前碰到的问题!当时苦于网上高手无人回答,也苦于自己当时没有时间去钻研为什么

   问题是:通过网上实例以及java网络编程这本书写java nio简单的测试服务器时发现,都是注册读写事件后然后分别处理相应的事件

就行了,这样本没错,可对于后续对注册事件的操作,再也没有看到与之相关实例与说明。然而,就这样完事以后,服务器端要么是

无限读事件与写事件有效,要么是客户端接收的数据是重复的好几条,在高并发时甚至上百上千。


   一般网络通讯程序是,客户端请求,然后服务端回复响应,即key.isReadable()时,服务器端接收请求,key.isWriteable()时,服

务端返回响应!一般我们在处理完接收后,我们就注册写事件,然后有写事件时处理写事件,正常网上的实例都是这样写的,可是这

样的话,客户端第二次以后发送时,服务器端再也没有进入到读事件中,还写事件回复的数据也很怪异,返回很多重复的数据。

再改进一点的是,写事件完事以后,又注册一个读事件,这样又可以读了,然后读了以后再注册写,这样反复重复,的确,能很好的

解决问题了。可是是因为什么原因呢?当时我是这样解决的,凑合用着,但是不知道为什么!

    昨天偶尔想起这个问题,然后查看mina源码时,发现其只注册过读事件,而对写事件并没有注册过,只是发现有一些

key.interestOps(SelectionKey.OP_WRITE)的操作,不是很明白,后来查看帮忙文档,知道这是管道当前感兴趣的事件时突然有些明

白了,后来又查看jdk源码,发现,每当重复注册时,都会检测事件是否已经注册过,如果已经注册过的事件,会把当前感兴趣的事

件切换成现在感兴趣的事件,这样所有的都明白了。

    即:管道注册事件,可以有四种事件,它可以注册所有的事件,但是管道每次只能对一种事件有效,即注册读事件时,此时只对读事

件有效,注册写事件时,此时只对写事件有效,这样上面的笨方法来回注册刚好达到这种效果,多的只是每次注册时的一次判断是否

已经注册过。当然我们也有更好的方法,那就是通过key.interestOps方法来切换当前感兴趣的事件,这样就可以避免每次注册时都

需要判断了。

    以上把问题与解决方法说明,现在附一下网上经常看到的服务器端实例!只是做了一些简单的修改。另外客户端网上很多也用非阻塞

实现的,我感觉没有必要,因为非阻塞只是为了解决高并发的问题。下面附上的客户端是我用socket写的一个简单实例!

 

 

//服务器
package com.netease.nio;

import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.channels.Selector;
import java.nio.channels.SelectionKey;

import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.CharBuffer;

import java.util.Iterator;

import java.io.IOException;

//基于非阻塞的服务器实现
public class NioServer {
 
 private static int DEFAULT_PORT = 512;
 private static int DEFAULT_BUFFERSIZE = 1023;
 private static String DEFAULT_CHARSET = "UTF-8";
 
 private ServerSocketChannel ss;
 private Selector selector;
 
 private ByteBuffer buffer;
 private Charset charset;
 private CharsetDecoder decoder;
 private int port;
 public NioServer() throws IOException{
  
  this.port = DEFAULT_PORT;
  this.ss = null;
  this.selector = Selector.open();
  this.buffer = ByteBuffer.allocate(DEFAULT_BUFFERSIZE);
  this.charset = Charset.forName(DEFAULT_CHARSET);
  this.decoder = this.charset.newDecoder();
  
 }
 
 //处理key
 public void handlerKey(SelectionKey key) throws IOException{
  
  if(key.isAcceptable()){
   
   System.out.println("one client coming");
   //取出对应的服务器通道
   ServerSocketChannel server = (ServerSocketChannel)key.channel();
   //对应socketchannel
   SocketChannel channel = server.accept();
   channel.configureBlocking(false);
   //可读
   channel.register(selector, SelectionKey.OP_READ);
  }else if(key.isReadable()){
   
    SocketChannel channel = (SocketChannel) key.channel();
   
             int count = channel.read(this.buffer);
             System.out.println("开始read");
             if (count > 0)
              {
              //把极限设为位置,把位置设为0,这样读取buffer时,刚好读取其已经读取多少内容的buffer
              this.buffer.flip();
              
               CharBuffer charBuffer = decoder.decode(this.buffer);
               String message = charBuffer.toString();
               //服务端接收客户端来的数据
               System.out.println("server:" + message);
            //   NioSession data = new NioSession(message);
              
             //切换为写事件
               key.interestOps(SelectionKey.OP_WRITE);
      //        channel.register(selector, SelectionKey.OP_WRITE);
               key.attach(message);
              
             }
             else
             { //客户已经断开
                 channel.close();
             }
             //把极限设为容量,再把位置设为0。
      //       this.buffer.clear();//清空缓冲区,为下一次接收数据做准备
            
             //极限不变,再把位置设为0,这样跟初始化时buffer是一样的
             this.buffer.rewind();
  }else if(key.isWritable() && key.isValid()){
   
   SocketChannel channel = (SocketChannel) key.channel();
  // NioSession handle = (NioSession) key.attachment();//取出处理者
   String message = "re:"+((String)key.attachment());
  // handle.setGreeting(message);
            ByteBuffer block = ByteBuffer.wrap(message.getBytes());
            channel.write(block);
          //切换为读事件
            key.interestOps(SelectionKey.OP_READ);
            //此时重新注册一个读事件时,当调用此方法时,register当检测读事件已经存在时,
    //        channel.register(selector,SelectionKey.OP_READ);
  }
 }
 
 public void listen() throws IOException
    { //服务器开始监听端口,提供服务
       ServerSocket socket;
       ss = ServerSocketChannel.open(); // 打开通道
       socket = ss.socket();   //得到与通到相关的socket对象
       socket.bind(new InetSocketAddress(port));   //将scoket榜定在制定的端口上
       //配置通到使用非阻塞模式,在非阻塞模式下,可以编写多道程序同时避免使用复杂的多线程
       ss.configureBlocking(false);   
       ss.register(selector, SelectionKey.OP_ACCEPT);
       try
        {
           while(true)
            {//     与通常的程序不同,这里使用channel.accpet()接受客户端连接请求,而不是在socket对象上调用accept(),

这里在调用accept()方法时如果通道配置为非阻塞模式,那么accept()方法立即返回null,并不阻塞
               this.selector.select();
               Iterator iter = this.selector.selectedKeys().iterator();
               while(iter.hasNext())
                {
                   SelectionKey key = (SelectionKey)iter.next();
                   iter.remove();
                   this.handlerKey(key);
                  
               }
           }
       }
       catch(IOException ex)
        {
           ex.printStackTrace();
           this.ss.close();
       }
   }

 public static void main(String[] args) throws IOException
    {
       System.out.println("服务器启动 ");
       NioServer server = new NioServer();
       server.listen(); //服务器开始监听端口,提供服务
   }

 
}

//客户端

package com.netease.nio;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.InetSocketAddress;
import java.nio.CharBuffer;

import java.util.Scanner;

//nio 连接服务器
public class NioClient {

 
 public static void main(String[] args) throws IOException{
  
  String host = "127.0.0.1";
  int port = 512;
  
  Socket socket = new Socket(host,port);
  System.out.println("客户端起动");
  
  while(true){
   
   Scanner in = new Scanner(System.in);
   
   String message = in.nextLine();
   
   PrintWriter pw = new PrintWriter(socket.getOutputStream());
   
   pw.write(message);
   
   pw.flush();
   
   InputStreamReader reader = new InputStreamReader(socket.getInputStream());
   
   CharBuffer buf = CharBuffer.allocate(100);
   reader.read(buf);
   
   buf.flip();
   System.out.println("client:"+String.valueOf(buf));
   
   if(message.equals("quit")){
    
    break;
   }
   buf.clear();
   
   
  }
  
  socket.close();
  
 }
}


以上有什么问题,请指出,谢谢!

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章