判斷對方是否斷開連接:
一、方法層面的實現:
1,使用輸入流的read方法:
輸入流的read(byte[] ,int ,int) 方法,表示從當前的通道中讀取數據,具體讀取到的數據有返回的int值決定;這里的返回值和拋出的異常很重要,如果拋出IOException異常,很明顯連接已經斷開;
返回值說明:
針對於基於tcp/ip協議的socket連接說明:
如果沒有設置socket的soTimeout屬性,那么該方法將是一個阻塞方法,可以通過設置socket的soTimeout屬性,讓read方法退出。
注意:read方法如果timeout將以拋出socketTimeoutException異常;
客戶端:
如果對方斷開連接,客戶端的read方法將返回-1;
服務器端:
如果對方斷開連接,服務器端的read方法將拋出IOException異常;
提示:
建議使用這種方式,netty底層源碼就是使用的這種方式實現的;
2,使用socket 的sendUrgentData(int) :
注意:不建議使用此種方式,因為使用該方式有很多不可預測的情況;
通常情況是:接收端沒有開啟socket的SO_OOBINLINE屬性,那么使用sendUrgentData(int)測試連接時,在發送17次(windows測試數據)時會發生異常;而如果接收端開啟socket的SO_OOBINLINE屬性,那么接收端就會收到發送的數據,從而導致真實數據的混亂;
socket sendUrgentData(int) 17次異常說明;
對於17次發送異常,在一片文章中有看到,說:如果接收端沒有開啟socket的SO_OOBINLINE的屬性(當然這也是想把該方法用於心跳檢測的必須條件),那么接收端將拋棄接收到的這個數據,也不會向發送端返回對應的ack值;但是,發送端卻會占用一個tcp/ip的發送窗口,一直等待接收端的返回,這里肯定等待不到,就會一直占用窗口;而一個tcp/ip基於平台只有8或16個窗口,於是,在第17次發送數據時拋出異常了;
意思,作者是懂的,但真正的底層實現卻不太清楚;提供tcp/ip窗口詳解連接:
https://technet.microsoft.com/zh-cn/library/2007.01.cableguy.aspx
二、協議層面的實現:
通過自定義的心跳機制,這也是最常用的方式之一;
示例實現:
定義了一個抽象的通道處理類,提供遠端斷開連接的判斷;
import java.io.IOException; import java.io.InputStream; import java.net.SocketTimeoutException; /** * 提供基於tcp/ip協議連接的斷開判斷實現 * @author pineapple food * */ public abstract class AbstractChannelHandler { /* * 連接關閉標志 */ private boolean closed; /** * 嘗試從輸入流中讀取數據,具體讀取數據的數量,依據返回的int值作為依據; * @param buf 緩存區 * @param index 起始地址 * @param length 讀取長度 * @return 值大於零,表示讀取到返回值表示大小的數據;等於0: 表示未讀取到數據; 等於-1:表示遠端已關閉連接; */ public int tryReadMsg(byte[] buf ,int index ,int length) { int result = 0; try { result = inputStream().read(buf, index, length); } catch (SocketTimeoutException e) { result = 0; } catch (IOException e) { result = -1; setClosed(); } return result; } protected void setClosed() { closed = true; } /** * 連接是否關閉 * @return false 未關閉; true 關閉 */ public boolean isClosed() { return closed; } /** * 向遠端連接發送心跳數據; */ protected void sendHeartbeat() {} /** * 通過協議層的心跳發送,判斷遠端連接是否關閉; * @return */ protected boolean isClosedBySendHeartbeat() { return false; } /** * 設置socket 輸入流的讀取超時時間,用於設置{@link tryReadMsg(byte[] ,int ,int)} 方法的timeout時間 * 否則將一直阻塞; */ abstract protected void setSoTimeout(); /** * 返回與該通道相關聯的輸入流; * @return */ abstract protected InputStream inputStream() throws IOException; }
通道處理類,繼承與上述抽象類,方便斷開連接的判斷
import java.io.IOException; import java.io.InputStream; import java.net.Socket; import java.net.SocketException; public class ChannelHandler extends AbstractChannelHandler{ private Socket socket; private InputStream is; public ChannelHandler(Socket socket) { this.socket = socket; setSoTimeout(); } @Override protected InputStream inputStream() throws IOException { if(is == null) { is = socket.getInputStream(); } return is; } @Override protected void setSoTimeout() { try { socket.setSoTimeout(1000); } catch (SocketException e) { } } }
demo,親測可用:
static public void test() throws IOException { String host = "127.0.0.1"; Socket socket = new Socket(); socket.connect(new InetSocketAddress(host, 6800)); ChannelHandler channelHandler = new ChannelHandler(socket); byte[] b = new byte[20]; while(true) { int n = channelHandler.tryReadMsg(b, 0, b.length); if(n == -1 || channelHandler.isClosed()) { System.out.println("___________ connection is closed"); break; } if(n > 0) { byte[] data = new byte[n]; System.arraycopy(b, 0, data, 0, n); System.out.println(n + " : receive data = "+new String(data)); } } }