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
}
就可以捕獲異常,使伺服器程式不會因為某一個客戶端的斷開而中斷.