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结束。


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

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();
			}
		}
		
	}

}
 
  
 
  
 
  
 
 

你可能感兴趣的:(Java,网络编程)