1. 程式人生 > >網路通訊之檢測遠端連線是否斷開連線

網路通訊之檢測遠端連線是否斷開連線

判斷對方是否斷開連線:

一、方法層面的實現:

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