握手過程中傳送的包里不包含數(shù)據(jù)垂券,三次握手完畢后,客戶端與服務(wù)器才正式開始傳送數(shù)據(jù)。理想狀態(tài)下菇爪,TCP連接一旦建立算芯,在通信雙方中的任何一方主動(dòng)關(guān)閉連接之前,TCP 連接都將被一直保持下去凳宙。斷開連接時(shí)服務(wù)器和客戶端均可以主動(dòng)發(fā)起斷開TCP連接的請求熙揍,斷開過程需要經(jīng)過“四次握手”(過程就不細(xì)寫了,就是服務(wù)器和客戶端交互氏涩,最終確定斷開)
什么是心跳
剛才說到長連接建立連接后届囚,理想狀態(tài)下是不會斷開的,但是由于網(wǎng)絡(luò)問題是尖,可能導(dǎo)致一方斷開后意系,另一方仍然在發(fā)送數(shù)據(jù),或者有些客戶端長時(shí)間不發(fā)送消息饺汹,服務(wù)器還維持這他的客戶端不必要的引用蛔添,增加了服務(wù)器的負(fù)荷。因此我們引入了心跳機(jī)制兜辞。
心跳包之所以叫心跳包是因?yàn)椋核裥奶粯用扛艄潭〞r(shí)間發(fā)一次作郭,以此來告訴服務(wù)器,這個(gè)客戶端還活著弦疮。事實(shí)上這是為了保持長連接夹攒,至于這個(gè)包的內(nèi)容,是沒有什么特別規(guī)定的胁塞,不過一般都是很小的包咏尝,或者只包含包頭的一個(gè)空包。
總的來說啸罢,心跳包主要也就是用于長連接的北嗉欤活和斷線處理。一般的應(yīng)用下扰才,判定時(shí)間在30-40秒比較不錯(cuò)允懂。如果實(shí)在要求高,那就在6-9秒衩匣。
怎么發(fā)送心跳蕾总?
1:輪詢機(jī)制
輪詢:概括來說是服務(wù)端定時(shí)主動(dòng)的與客戶端通信,詢問當(dāng)前的某種狀態(tài)琅捏,客戶端返回狀態(tài)信息生百,客戶端沒有返回,則認(rèn)為客戶端已經(jīng)宕機(jī)柄延,然后服務(wù)端把這個(gè)客戶端的宕機(jī)狀態(tài)保存下來蚀浆,如果客戶端正常,那么保存正常狀態(tài)。如果客戶端宕機(jī)或者返回的是定義的失效狀態(tài)那么當(dāng)前的客戶端狀態(tài)是能夠及時(shí)的監(jiān)控到的市俊,如果客戶端宕機(jī)之后重啟了那么當(dāng)服務(wù)端定時(shí)來輪詢的時(shí)候杨凑,還是可以正常的獲取返回信息,把其狀態(tài)重新更新摆昧。2:心跳機(jī)制
心跳:最終得到的結(jié)果是與輪詢一樣的但是實(shí)現(xiàn)的方式有差別撩满,心跳不是服務(wù)端主動(dòng)去發(fā)信息檢測客戶端狀態(tài),而是在服務(wù)端保存下來所有客戶端的狀態(tài)信息据忘,然后等待客戶端定時(shí)來訪問服務(wù)端,更新自己的當(dāng)前狀態(tài)搞糕,如果客戶端超過指定的時(shí)間沒有來更新狀態(tài)勇吊,則認(rèn)為客戶端已經(jīng)宕機(jī)。
心跳比起輪詢有兩個(gè)優(yōu)勢:
1.避免服務(wù)端的壓力
2.靈活好控制
代碼演示
服務(wù)器端
private ServerManager()
{
IPAddress address = IPAddress.Parse(IP);
listener = new TcpListener(address, Port);
//綁定ip和port窍仰,進(jìn)行偵聽
listener.Start();
Console.WriteLine("開始偵聽");
//異步監(jiān)聽客戶端連接
listener.BeginAcceptTcpClient(OnAccecpt,null);
//開啟定時(shí)器System.Threading.Timer
Timer timer = new Timer(Callback,null, HeartInterval, HeartInterval);
//定時(shí)器System.Timers
//System.Timers.Timer t = new System.Timers.Timer();
//t.Interval = 1000;//定時(shí)器間隔
//t.Elapsed += delegate (object sender, System.Timers.ElapsedEventArgs e){ };
//t.Enabled = true;//開啟定時(shí)器
}
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次心跳超時(shí)汉规,服務(wù)器比客戶端多判斷一次
{
Console.WriteLine(client.userdata.username+ "心跳超時(shí),斷開鏈接");
RemoveClient(client);
}
}
}
//接收客戶端消息驹吮,并進(jìn)行分發(fā)
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;
}
}
客戶端
//接收到服務(wù)器心跳
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)//心跳超時(shí)
{
count++;
lastHeartTime = Time.time;
Debug.Log("心跳超時(shí)一次");
if (count >= 3)//心跳超時(shí)3次
{
count = 0;
Debug.Log("心跳超時(shí)3次,斷線處理");
//斷線處理
client.OnClose();
}
}
}
}