.net core 和 WPF 開發升訊威線上客服系統:怎樣實現拔網線也不丟訊息的高可靠通訊(附視訊)

2022-05-26 21:01:20

本系列文章詳細介紹使用 .net core 和 WPF 開發 升訊威線上客服與行銷系統 的過程。本產品已經成熟穩定並投入商用。
私有化部署免費下載:https://docs.shengxunwei.com/Post/f7bc8496-14ee-4a53-07b4-08d8e3da6269/553293a8-dfa1-4282-bc3f-96c6c623fc9a


客服系統開發過程中,最讓我意外的是對 TCP/IP 協定的認識。過去一直認為 TCP/IP 是可靠的連線,加上過去開發的軟體網路環境比較穩定,很少在這個問題上糾結。

直到客服系統的客戶越來越多,才重新讓我認識了基於 TCP/IP 協定的軟體應該如何設計開發。

有許多客戶做的是外貿業務,伺服器部署在海外,比如香港、韓國、美國等,有些客服之前用基於網頁的客服系統,最為困擾的問題就是丟訊息!而使用我的客服系統,做到了100%穩定,不丟客戶不丟訊息。

本文將分幾個部分,詳細介紹基於 TCP/IP 協定開發時,應該如何考慮複雜網路環境下的訊息傳輸。


演示網路中斷,直接禁用網路卡,或者手機進入飛航模式,也不丟訊息,不出異常。

視訊地址:https://v.youku.com/v_show/id_XNTEwNzQ5Mzg2OA==.html



TCP 報文的確認機制

首先我們回顧一下 TCP 協定,TCP 報文格式一般如下所示:
其中的 ACK ,表示對報文是否送達的一個迴應。

ACK是TCP檔頭中的標誌和欄位。 傳送一個訊息至少需要一個檔頭,再加上所有較低層的內容。

下圖則顯示了 TCP 通訊時,使用者端和伺服器端之間報文傳送的過程。
從圖中可以看到,發出的訊息,和迴應的訊息,都會有一個編號,如:#1、#2
在ACK報文迴應時,它回附帶上所收到的報文的編號,那麼傳送端只需根據收到的ACK報文中的編號,就能判定報文是否送達,已經所送達的封包。如果在一定時間內,沒有收到迴應的ACK訊息,則傳送端會在一定時間內重新嘗試傳送。

通過 C# 實現拔網線也不丟訊息的高可靠通訊

基於 TCP 協定自有的訊息確認機制,我們在上層應用中實現可靠的通訊就比較簡單了。底層通訊相關的類已經幫我們實現好了可靠的 TCP 傳輸,一旦出現網路異常,我們在上層都能夠收到相應的通知。

使用者端自身網路異常

這種情況最好處理。因為使用者端程式異常退出會直接引發 ConnectionReset 的 Socket 異常。我們只需要在伺服器端捕獲這個異常進行處理即可:

 public bool Send(byte[] data)
        {
            // 連線已經斷開了
            try
            {
                _networkStream.Write(data, 0, data.Length);
            }
            catch (Exception ex)
            {
                OnDisconnected(ex);
                return false;
            }

            return true;
        }

網路鏈路異常

對於這種情況,我們只需要檢測 Socket 物件的 Connected 屬性。
但是需要特別注意:Socket 物件的 Connected 屬性獲取從 Socket 最後一個 i/o 操作到的的連線狀態。 當它返回時 false , Socket 要麼從未連線,要麼不再處於連線狀態。當 Socket 從另一個執行緒斷開連線時,它可能會在操作中止後返回。
如果需要確定連線的當前狀態,請發出非阻止的零位元組傳送呼叫。 如果呼叫成功返回或引發 WAEWOULDBLOCK 錯誤程式碼 (10035) ,則通訊端仍處於連線狀態;否則,將不再連線通訊端。

我們可以通過實現一個定時心跳,來對網路鏈路進行檢測:

_heartbeatTimer = new Timer((state) =>
            {
                HeartbeatMessage heartbeatMessage = new HeartbeatMessage();
                Send(heartbeatMessage);

            }, null, 3000, 3000);

在定時器傳送心跳時,如果網路鏈路中斷,我們可以收到以下訊息:

 private void _socketClient_Disconnected(object sender, EventArgs e)
        {
            if (_heartbeatTimer != null)
                _heartbeatTimer.Dispose();

            if (_socketClient != null)
            {
                _socketClient.Close();
                _socketClient = null;
            }
        }

只需針對 Disconnected 事件,進行處理,將兩端的狀態,置於等待即可。


請存取:https://kf.shengxunwei.com