1. 程式人生 > >tcp nio 遠端主機強迫關閉了一個現有的連線

tcp nio 遠端主機強迫關閉了一個現有的連線

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class NIOServer implements Runnable {
	
	/*標識數字*/
	private  int flag = 0;
	/*緩衝區大小*/
	private  int BLOCK = 4096;
	/*接受資料緩衝區*/
	private  ByteBuffer sendbuffer = ByteBuffer.allocate(BLOCK);
	/*傳送資料緩衝區*/
	private  ByteBuffer receivebuffer = ByteBuffer.allocate(BLOCK);
	private  Selector selector;

	public NIOServer(int port){
		try{
		// 開啟伺服器套接字通道
			ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
		// 伺服器配置為非阻塞
			serverSocketChannel.configureBlocking(false);
		// 檢索與此通道關聯的伺服器套接字
			ServerSocket serverSocket = serverSocketChannel.socket();
		// 進行服務的繫結
			serverSocket.bind(new InetSocketAddress(port));
		// 通過open()方法找到Selector
			selector = Selector.open();
		// 註冊到selector,等待連線
			serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
			System.out.println("Server Start----8888:");
		}catch(Exception e){
			e.printStackTrace();
		}
	}


	// 監聽
	public void run() {
		try {
			while (true) {
				// 選擇一組鍵,並且相應的通道已經開啟
				selector.select();
				// 返回此選擇器的已選擇鍵集。
				Set<SelectionKey> selectionKeys = selector.selectedKeys();
				Iterator<SelectionKey> iterator = selectionKeys.iterator();
				while (iterator.hasNext()) {
					SelectionKey selectionKey = iterator.next();
					iterator.remove();
					try{
						handleKey(selectionKey);
				
					}catch(IOException e){
						e.printStackTrace();
						//selectionKey.cancel();
						break;
					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	// 處理請求
	private void handleKey(SelectionKey selectionKey) throws IOException {
		// 接受請求
		ServerSocketChannel server = null;
		SocketChannel client = null;
		SocketAddress clientaddr = null;
		String receiveText;
		String sendText;
		int count=0;
	
		// 測試此鍵的通道是否已準備好接受新的套接字連線。
		
		if (selectionKey.isAcceptable()) {
			// 返回為之建立此鍵的通道。
			server = (ServerSocketChannel) selectionKey.channel();
			// 接受到此通道套接字的連線。
			// 此方法返回的套接字通道(如果有)將處於阻塞模式。
			client = server.accept();
			clientaddr=client.socket().getRemoteSocketAddress();
			System.out.printf("+++++++++++伺服器端接受客戶端[%s]連線!+++++++++++ \n",clientaddr);
			// 配置為非阻塞
			client.configureBlocking(false);
			// 註冊到selector,等待連線
			client.register(selector, SelectionKey.OP_READ);
		} else if (selectionKey.isReadable()) {
			
				// 返回為之建立此鍵的通道。
				client = (SocketChannel) selectionKey.channel();
				clientaddr=client.socket().getRemoteSocketAddress();
				//將緩衝區清空以備下次讀取
				receivebuffer.clear();
				
				//讀取伺服器傳送來的資料到緩衝區中
				count = client.read(receivebuffer);	
				if (count > 0) {
					receiveText = new String( receivebuffer.array(),0,count);
					System.out.printf("伺服器端接受客戶端[%s]資料:\n%s",clientaddr,receiveText);
					client.register(selector, SelectionKey.OP_WRITE);
				}
			
		} else if (selectionKey.isWritable()) {
			//將緩衝區清空以備下次寫入
			sendbuffer.clear();
			// 返回為之建立此鍵的通道。
			client = (SocketChannel) selectionKey.channel();
			sendText="message from server--" + flag++;
			//向緩衝區中輸入資料
			sendbuffer.put(sendText.getBytes());
			 //將緩衝區各標誌復位,因為向裡面put了資料標誌被改變要想從中讀取資料發向伺服器,就要復位
			sendbuffer.flip();
			//輸出到通道
			client.write(sendbuffer);
			System.out.println("伺服器端向客戶端傳送資料--:"+sendText);
			client.register(selector, SelectionKey.OP_READ);
		}
	}

	/**
	 * @param args
	 * @throws IOException
	 */
	public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
		int port = 8888;
		NIOServer server = new NIOServer(port);
		server.run();
	}
}

上面是從網上摘錄的Java NIO TCP 的一個server程式,用客戶端的NIO TCP與之連線的時候,只要客戶端斷開連線,伺服器端就會報出“遠端主機強迫關閉了一個現有的連線”的錯誤,並且中斷伺服器端程式。經過查閱之後發現,得知OP_READ 事件不僅僅只有可讀時才觸發,當channel中資料讀完遠端的另一端被關閉有一個錯誤的pending都會觸發OP_READ事件"!

解決辦法:在selectionKey.isReadable()中加入錯誤捕捉機制,即:

 if (selectionKey.isReadable()) {
try {
// 返回為之建立此鍵的通道。
client = (SocketChannel) selectionKey.channel();
clientaddr=client.socket().getRemoteSocketAddress();
//將緩衝區清空以備下次讀取
receivebuffer.clear();
//讀取伺服器傳送來的資料到緩衝區中
count = client.read(receivebuffer);
if (count > 0) {
receiveText = new String( receivebuffer.array(),0,count);
System.out.printf("伺服器端接受客戶端[%s]資料:\n%s",clientaddr,receiveText);
client.register(selector, SelectionKey.OP_WRITE);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
selectionKey.cancel();  //取消selectionKey
}

 就可以捕獲異常,使伺服器程式不會因為某一個客戶端的斷開而中斷.