java nio socketChannel read返回值代表的意思

当socketChannel为阻塞方式时(默认就是阻塞方式)read函数,不会返回0,阻塞方式的socketChannel,若没有数据可读,或者缓冲区满了,就会阻塞,直到满足读的条件,所以一般阻塞方式的read是比较简单的,不过阻塞方式的socketChannel的问题也是显而易见的。这里我结合基于NIO 写ftp服务器调试过程中碰到的问题,总结一下非阻塞场景下的read碰到的问题。注意:这里的场景都是基于客户端以阻塞socket的方式发送数据。

1、read什么时候返回-1

read返回-1说明客户端的数据发送完毕,并且主动的close socket。所以在这种场景下,(服务器程序)你需要关闭socketChannel并且取消key,最好是退出当前函数。注意,这个时候服务端要是继续使用该socketChannel进行读操作的话,就会抛出“远程主机强迫关闭一个现有的连接”的IO异常。

2、read什么时候返回0

其实read返回0有3种情况,一是某一时刻socketChannel中当前(注意是当前)没有数据可以读,这时会返回0,其次是bytebuffer的position等于limit了,即bytebuffer的remaining等于0,这个时候也会返回0,最后一种情况就是客户端的数据发送完毕了(注意看后面的程序里有这样子的代码,这个时候客户端想获取服务端的反馈调用了recv函数,若服务端继续read,这个时候就会返回0。

总结:当客户端发送的是文件,而且大小未知的情况,服务端如何判断对方已经发送完毕。如单纯的判断是否等于0,可能会导致客户端发送的数据不完整。所以,这里加了一个检测0出现次数的判断,来判断客户端是否确实是数据发送完毕了,当然这个方法是比较笨拙的方法,大家若有更好的方法,期待大家给我答案。

网上也有类似的建议,比如自定义协议,在数据头部带上文件大小等。

注意:这里有一个问题就是通过这种while循环读取的方式,实际上它只有一次NIO事件通知,而且在这个处理过程中,其他事件就得不到及时处理,除非while结束。



更好的方式是整个体系中只有一个大的bytebuffer封装体,然后每次使用都从这个封装体中去取然后手工释放也就有些人说的“memcache”。

你说这句话的时候已经说明连入门级还没到。多个线程中不同的KEY读取的数据在同一个bytebuffer中交叉存放,然后如果分配给每个具体的处理程序?“手工释放”这种搞笑的话也说得出来?如何手工?用手去操作内存?除了因为理屈词穷而想出这种混乱思维,你说的有些人在哪里见到这种使用方式?

对sk.interestOps(SelectionKey.OP_READ);的两点你说明什么?说明你去看了底层的操作?底层如何操作了?只说明你根本没有能力看懂sk.interestOps(SelectionKey.OP_READ);的作用。

对于非阻塞IO的读操作(写相反),一次select只是通知载协议栈的读操作有数据可读,至于这次读到的数据是多少,是否是一次完整的交互数据,selector并不关心。如果客户端在发送了1024字节的数据,这次只读到512字节,那么余下的512只能等下次select,除非是阻塞方式可以等到一直读完。

那么到底是重新注册READ事件还是sk.interestOps(SelectionKey.OP_READ);,如果重新注册当然可以读到下面的数据,但是你就无法和上次的那部份数据合并,因为多个KEY的不完整数据同时存在,你不知道要合并到哪个现有上去,所以用sk.interestOps(SelectionKey.OP_READ);的意思其实就是用同一个KEY重新注册,下次读到的余下的数据合并到上次这个KEY的部分数据上,代表同一客户端的一次完整的发送。

对于同一次交互中比较大的数据,必须使用sk.interestOps(SelectionKey.OP_READ);来多次读取。无论你出于什么原因只要你知道他的作用就不可能说它不必要。就象阻塞方法中我们while((len = in.read(buf)) > 0){....};如果你说while不必要的话,那只能说明你根本不懂IO操作。

理论上即使对方发两个字节的数据,一次select也可能只读到第一个字节,除非在第一次select时,已经读到的数据中包含中约定好的结束标记或已经知道的长度。而楼主的代码只是将读到的数据打印出来,既没有判断是否已经读到结束标记,也没判断已经读到的数据长度是否达到多少而不需再读,那么sk.interestOps(SelectionKey.OP_READ);来读下次数据怎么会不必要?只要你懂这段代码的意思就不可能说出这样的话。说出这样的话只能代表你不懂

服务端的代码(客户端发送的数据大小未知)

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片

  1.   
  2. import java.io.IOException;  
  3. import java.nio.ByteBuffer;  
  4. import java.nio.channels.SelectionKey;  
  5. import java.nio.channels.Selector;  
  6. import java.nio.channels.SocketChannel;  
  7.   
  8. import com.myftpnio.server.FtpNioServer;  
  9.   
  10. public class ClientHandler implements NioHandler {  
  11.   
  12.     private SocketChannel sc;  
  13.     @SuppressWarnings("unused")  
  14.     private Selector selector;  
  15.     private ByteBuffer buf = ByteBuffer.allocate(1024);  
  16.     private long sum = 0;  
  17.     private static int count_zore = 0;  
  18.     public ClientHandler(SocketChannel sc, Selector selector) {  
  19.         this.sc = sc;  
  20.         this.selector = selector;  
  21.     }  
  22.       
  23.     @Override  
  24.     public void execute(SelectionKey key) {  
  25.         // TODO Auto-generated method stub  
  26.           
  27.         if (key.isReadable()) {  
  28.               
  29.             try {  
  30.                 while(true) {  
  31.                     buf.clear();  
  32.                     int n = sc.read(buf);  
  33.                     if (n > 0) {  
  34.                         sum += n;  
  35.                         System.out.println("sum=" + sum + " n=" + n + " " + FtpNioServer.ByteBufferToString(buf));  
  36.                     } else if (n == 0) {  
  37.                         if (count_zore++ < FtpNioServer.MAX) {  
  38.                             continue;  //这里表明还需要读取数据
  39.                         } else {  
  40.                             key.interestOps(SelectionKey.OP_WRITE);  
  41.                             break;  //这里表明已经读取到了所有需要的数据
  42.                         }  
  43.                     } else if (n == -1) {  
  44.                         System.out.println("client close connect");  
  45.                         sc.close();  
  46.                         key.cancel();  
  47.                         return;  
  48.                     }  
  49.                 }  
  50.             } catch (IOException e) {  
  51.                 //处理捕获到的IO异常  
  52.                 System.out.println(e.getMessage());  
  53.                 try {  
  54.                     sc.close();  
  55.                 } catch (IOException e1) {  
  56.                     // TODO Auto-generated catch block  
  57.                     e1.printStackTrace();  
  58.                 }  
  59.                 key.cancel();  
  60.                 return;  
  61.             }  
  62.         }  
  63.           
  64.         if (key.isWritable()) {  
  65.             try {  
  66.                 String ret = "hello " + sc.socket().getRemoteSocketAddress().toString();  
  67.                 ByteBuffer send = ByteBuffer.wrap(ret.getBytes());  
  68.                 sc.write(send);  
  69.                   
  70.                 key.cancel();  
  71.                 sc.close();  
  72.                   
  73.                 FtpNioServer.connum--;  
  74.                 count_zore = 0;  
  75.             } catch (Exception e) {  
  76.                 e.printStackTrace();  
  77.             }  
  78.         }  
  79.           
  80.     }  
  81.   
  82. }  
  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值