# 1\. 什么是心跳
其實簡單的說就是:客戶端隔一段時間就給服務端發送消息,用來告訴服務端這個連接沒有斷,是正常的,從而維護長連接的持久性。
如果不加心跳包,有的服務器節點(防火墻)會自動把一定時間之內沒有數據交互的連接給斷掉;而且這中間指不定會有什么亂七八糟的比如機器斷電、網線拔出這些幺蛾子出現導致客戶端斷線。
但是類似斷網這種**極端情況**導致客戶端斷開連接,服務端是不知道的。因為客戶端在正常情況下主動斷開會向服務端發送一個tcp的fin包。
> 什么是FIN包?
> FIN包表示發送端已經達到數據末尾,也就是說雙方的數據傳送完成,沒有數據可以傳送了。
> 發送FIN標志 位的TCP數據包后,連接將被斷開。
> 這個標志的數據包也經常被用于進行端口掃描。
然而極端情況下,客戶端沒有機會發這個包,就會導致服務端并不知道客戶端斷掉了。
# 2\. 長連接必須加心跳
長連接,只要是長連接,長時間不通訊肯定會被防火墻干掉然后斷開,服務在非可控情況下斷開是非常不好的情況;所以長連接無論如何都要加上心跳包。
# 3\. 心跳實例
接下來以workerman建立websocket連接為實例。
## 3.1 前端JavaScript代碼
在onopen連接建立的時候,定義一個定時器,每10秒鐘發送一個包,包的內容隨意。
~~~
var ws = new WebSocket("ws://www.goozp.com");
//連接websocket
ws.onopen = function () {
setInterval(function () {
ws.send('Hello!');
}, 10000)
};
~~~
一般發送心跳包的間隔在60秒以內。
## 3.2 Workerman中處理斷線
此處參考:[官方文檔:心跳](http://doc.workerman.net/315282 "官方文檔:心跳")
可以先定義一些常量在之后用到,方便配置:
~~~
define('HEARTBEAT_TIME', 30); // 定義一個心跳間隔30秒
define('CHECK_HEARTBEAT_TIME', 1); // 檢查連接的間隔時間
~~~
當接收到信息時,我們就記錄下接收到信息的時間:
~~~
$worker->onMessage = function($connection, $msg) {
// 給connection臨時設置一個lastMessageTime屬性,用來記錄上次收到消息的時間
$connection->lastMessageTime = time();
// TODO 其它業務邏輯...
};
~~~
在進程啟動后設置一個定時器,每隔一段時間遍歷一遍當前worker的所有連接
~~~
// 進程啟動后設置一個每秒運行一次的定時器
$worker->onWorkerStart = function($worker) {
Timer::add(CHECK_HEARTBEAT_TIME, function()use($worker){
$time_now = time();
foreach($worker->connections as $connection) {
// 有可能該connection還沒收到過消息,則lastMessageTime設置為當前時間
if (empty($connection->lastMessageTime)) {
$connection->lastMessageTime = $time_now;
continue;
}
// 上次通訊時間間隔大于心跳間隔,則認為客戶端已經下線,關閉連接
if ($time_now - $connection->lastMessageTime > HEARTBEAT_TIME) {
$connection->close();
}
}
});
};
~~~
這樣,結合前端的心跳包,我們就可以做到維持連接的長久,以及踢出設置時間段內未使用的廢棄連接。
>好文轉載,此章節轉載地址:[咖啡與代碼](https://www.goozp.com/category/workerman) -(原作者如有要求,會立馬下架此章節)