当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结束。
服务端的代码(客户端发送的数据大小未知)
<pre name="code" class="java">package com.myftpnio.handler;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import com.myftpnio.server.FtpNioServer;
public class ClientHandler implements NioHandler {
private SocketChannel sc;
@SuppressWarnings("unused")
private Selector selector;
private ByteBuffer buf = ByteBuffer.allocate(1024);
private long sum = 0;
private static int count_zore = 0;
public ClientHandler(SocketChannel sc, Selector selector) {
this.sc = sc;
this.selector = selector;
}
@Override
public void execute(SelectionKey key) {
// TODO Auto-generated method stub
if (key.isReadable()) {
try {
while(true) {
buf.clear();
int n = sc.read(buf);
if (n > 0) {
sum += n;
System.out.println("sum=" + sum + " n=" + n + " " + FtpNioServer.ByteBufferToString(buf));
} else if (n == 0) {
if (count_zore++ < FtpNioServer.MAX) {
continue;
} else {
key.interestOps(SelectionKey.OP_WRITE);
break;
}
} else if (n == -1) {
System.out.println("client close connect");
sc.close();
key.cancel();
return;
}
}
} catch (IOException e) {
//处理捕获到的IO异常
System.out.println(e.getMessage());
try {
sc.close();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
key.cancel();
return;
}
}
if (key.isWritable()) {
try {
String ret = "hello " + sc.socket().getRemoteSocketAddress().toString();
ByteBuffer send = ByteBuffer.wrap(ret.getBytes());
sc.write(send);
key.cancel();
sc.close();
FtpNioServer.connum--;
count_zore = 0;
} catch (Exception e) {
e.printStackTrace();
}
}
}
}