”Connection reset by peer“引發的思考


  閑來無事,把之前寫的一個游戲服務器框架(《一個java頁游服務器框架》),部署到阿里雲服務器上,測試運行了下,結果看到后台log中打印出了“Connection reset by peer”。出於好奇疑問就查了一下相關資料,網上說一般有這幾種:

  ①:服務器的並發連接數超過了其承載量,服務器會將其中一些連接Down掉;

  ②:客戶關掉了瀏覽器,而服務器還在給客戶端發送數據;

  ③:瀏覽器端按了Stop

  但是這幾種都可以排除,剛搭建的服務器,就我測試連接了下不可能超過負載,而且我這是tcp長連接與瀏覽器沒半點關系。后來問了一些大神才知道,一個連接如果長時間不用防火牆或者路由器就會給你斷開,於是就會打印如上異常。現實中的解決方法就是,客戶端定時向服務器發送心跳包。才知道心跳包原來還有這個作用,說來甚是慚愧啊。

  說到了心跳包自然又想到了另一個功能。客戶端斷電等一些列強制連接斷開的情況,可以通過心跳包機制,讓服務器端及時知道鏈接斷開,從而及時清除這些無法使用的鏈接屍體。反過來說,如果沒有心跳包,那么客戶端強制斷開,服務器是無法及時捕獲到鏈接斷開的事件的。然而在我測試下,強制關閉客戶端,服務器端是會拋出異常的(我的這個框架使用了mina,客戶端強制斷開后,會調用對應的exceptionCaught方法拋出異常,然后又調用sessionClosed關閉連接)。既然服務器會拋出異常,自然我們不用通過心跳包,服務器端也能及時知道客戶端連接斷開的情況。

  懷着強烈的好奇心就做了如下測試,由於java網絡編程有bio和nio以及最新的aio,對於AIO,成產環境中我還沒有接觸到又用這個的,所以暫不做測試,就針對BIO和NIO做測試

      (以下代碼比較簡單,就不做解釋了。僅僅用於測試我的疑惑,某些不合理的地方也就不必在意了)

  BIO測試程序:

    服務器端測試代碼:

public class BIOServer {

	public static void main(String[] args) throws UnsupportedEncodingException {
		byte[] datas="hello".getBytes("UTF-8");
		try {
			ServerSocket ss = new ServerSocket(810);
			while(true){
				Socket s = ss.accept();
				OutputStream os = s.getOutputStream();
				os.write(datas);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}

}

    客戶端程序:

public class Client {

	
	public static void main(String[] args) {
		try {
			Socket s = new Socket("localhost", 810);
			InputStream is = s.getInputStream();
			byte[] buf = new byte[1024];
			int len=0;
			while((len=is.read(buf))!=-1){
				System.out.println(new String(buf,0,len,"utf-8"));
			}
			
			System.in.read();
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}

    先運行服務器端,然后運行客戶端收到服務器端發來的hello,然后強制關閉客戶端,服務器端沒有任何反應,這說明BIO下強制關閉客戶端,服務器端是沒法及時捕獲的。當然在連接斷開的情況下,服務器端對此連接進行讀寫,就會拋出異常。

  NIO測試程序:

    服務器端:

public class NioServer {

	public static void main(String[] args)throws Exception {
		
		Selector selector = Selector.open();
		ServerSocketChannel serverChannel = ServerSocketChannel.open();
		serverChannel.configureBlocking(false);
		serverChannel.socket().bind(new InetSocketAddress(810));
		serverChannel.register(selector, SelectionKey.OP_ACCEPT);
		while(true){
			int l = selector.select();
			if(l==0){
				continue;
			}
			Set<SelectionKey> sets = selector.selectedKeys();
			for(SelectionKey key :sets){
				if(key.isAcceptable()){
					ServerSocketChannel tChannel = (ServerSocketChannel) key.channel();
					SocketChannel clientChannel = tChannel.accept();
					clientChannel.configureBlocking(false);
					clientChannel.register(selector, SelectionKey.OP_READ);
				}else if(key.isReadable()){
					SocketChannel clientChannel = (SocketChannel) key.channel();
					ByteBuffer buf = ByteBuffer.allocate(1024);
					int len = clientChannel.read(buf);
					if(len==-1){
						System.out.println("客戶端斷開了");
					}else{
						buf.flip();
						System.out.println(new String(buf.array(),0,len,"utf-8"));
					}
				}
				sets.remove(key);
			}
		}
	}

}

    客戶端:

public class Client {

	
	public static void main(String[] args) {
		try {
			Socket s = new Socket("localhost", 810);
			OutputStream os = s.getOutputStream();
			byte[] datas="hello".getBytes("UTF-8");
			os.write(datas);
			System.in.read();
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}

  運行服務器端,然后運行客戶端,可以看到服務器端打印出客戶端發來的“hello”,然后強制關閉客戶端,服務器端就回拋出如下異常:

  

 

  通過以上測試,可以知道:BIO下,客戶端強制斷開,服務器端是沒法及時知道的;NIO下,客戶端強制斷開,服務器端可以及時知道並拋出異常。有此差異肯定是因為BIO和nio使用了不同的網絡模型。NIO有一個selector,我想也就是這個selector的底層對每一個連接會有檢查,所以能及時知道客戶端連接斷開事件。我的有服務器框架就是基於mina,而mina使用的nio,所以對於強制關閉客戶端,我的服務器能及時捕獲到並拋出異常這個疑惑,也就算徹底明確了。

 

  

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM