握手過程中傳送的包里不包含數據,三次握手完畢后,客戶端與服務器才正式開始傳送數據。理想狀態下,TCP連接一旦建立,在通信雙方中的任何一方主動關閉連接之前,TCP 連接都將被一直保持下去。斷開連接時服務器和客戶端均可以主動發起斷開TCP連接的請求,斷開過程需要經過“四次握手”(過程就不細寫了,就是服務器和客戶端交互,最終確定斷開)
什么是心跳
剛才說到長連接建立連接后,理想狀態下是不會斷開的,但是由于網絡問題,可能導致一方斷開后,另一方仍然在發送數據,或者有些客戶端長時間不發送消息,服務器還維持這他的客戶端不必要的引用,增加了服務器的負荷。因此我們引入了心跳機制。
心跳包之所以叫心跳包是因為:它像心跳一樣每隔固定時間發一次,以此來告訴服務器,這個客戶端還活著。事實上這是為了保持長連接,至于這個包的內容,是沒有什么特別規定的,不過一般都是很小的包,或者只包含包頭的一個空包。
總的來說,心跳包主要也就是用于長連接的保活和斷線處理。一般的應用下,判定時間在30-40秒比較不錯。如果實在要求高,那就在6-9秒。
怎么發送心跳?
1:輪詢機制
輪詢:概括來說是服務端定時主動的與客戶端通信,詢問當前的某種狀態,客戶端返回狀態信息,客戶端沒有返回,則認為客戶端已經宕機,然后服務端把這個客戶端的宕機狀態保存下來,如果客戶端正常,那么保存正常狀態。如果客戶端宕機或者返回的是定義的失效狀態那么當前的客戶端狀態是能夠及時的監控到的,如果客戶端宕機之后重啟了那么當服務端定時來輪詢的時候,還是可以正常的獲取返回信息,把其狀態重新更新。2:心跳機制
心跳:最終得到的結果是與輪詢一樣的但是實現的方式有差別,心跳不是服務端主動去發信息檢測客戶端狀態,而是在服務端保存下來所有客戶端的狀態信息,然后等待客戶端定時來訪問服務端,更新自己的當前狀態,如果客戶端超過指定的時間沒有來更新狀態,則認為客戶端已經宕機。
心跳比起輪詢有兩個優勢:
1.避免服務端的壓力
2.靈活好控制
代碼演示
服務器端
private ServerManager()
{
IPAddress address = IPAddress.Parse(IP);
listener = new TcpListener(address, Port);
//綁定ip和port,進行偵聽
listener.Start();
Console.WriteLine("開始偵聽");
//異步監聽客戶端連接
listener.BeginAcceptTcpClient(OnAccecpt,null);
//開啟定時器System.Threading.Timer
Timer timer = new Timer(Callback,null, HeartInterval, HeartInterval);
//定時器System.Timers
//System.Timers.Timer t = new System.Timers.Timer();
//t.Interval = 1000;//定時器間隔
//t.Elapsed += delegate (object sender, System.Timers.ElapsedEventArgs e){ };
//t.Enabled = true;//開啟定時器
}
void Callback(object state)
{
List<string> keys = new List<string>();
foreach (var item in dic.Keys)
{
keys.Add(item);
}
for (int i = 0; i < keys.Count; i++)
{
SocketClient client = dic[keys[i]];
client.SendMessage(Protocol.HeartBeat);
client.timeOut++;
if(client.timeOut>5)//4次心跳超時,服務器比客戶端多判斷一次
{
Console.WriteLine(client.userdata.username+ "心跳超時,斷開鏈接");
RemoveClient(client);
}
}
}
//接收客戶端消息,并進行分發
public void OnMessage(SocketClient client,int protocol,string msg)
{
switch (protocol)
{
case Protocol.Login_CMD:
break;
case Protocol.HeartBeat:
//接收到心跳,重置timeOut
client.timeOut = 0;
break;
default:
break;
}
}
客戶端
//接收到服務器心跳
void OnHeatBeat()
{
lastHeartTime = Time.time;
count = 0;
//返回心跳
SendMessage(Protocol.HeartBeat);
}
float lastHeartTime = 0;
int count = 0;
void CheckHeat()
{
if(client.online && client.isloggin)
{
if (Time.time - lastHeartTime > 5)//心跳超時
{
count++;
lastHeartTime = Time.time;
Debug.Log("心跳超時一次");
if (count >= 3)//心跳超時3次
{
count = 0;
Debug.Log("心跳超時3次,斷線處理");
//斷線處理
client.OnClose();
}
}
}
}