TCP協議的`KeepAlive`機制與`HeartBeat`心跳包
* HeartBeat心跳包
很多應用層協議都有HeartBeat機制,通常是客戶端每隔一小段時間向服務器發送一個數據包,通知服務器自己仍然在線,并傳輸一些可能必要的數據。使用心跳包的典型協議是IM,比如QQ/MSN/飛信等協議。
心跳包之所以叫心跳包是因為:它像心跳一樣每隔固定時間發一次,以此來告訴服務器,這個客戶端還活著。事實上這是為了保持長連接,至于這個包的內容,是沒有什么特別規定的,不過一般都是很小的包,或者只包含包頭的一個空包。
在TCP的機制里面,本身是存在有心跳包的機制的,也就是TCP的選項:SO\_KEEPALIVE。系統默認是設置的2小時的心跳頻率。但是它檢查不到機器斷電、網線拔出、防火墻這些斷線。而且邏輯層處理斷線可能也不是那么好處理。一般,如果只是用于保活還是可以的。
心跳包一般來說都是在邏輯層發送空的echo包來實現的。下一個定時器,在一定時間間隔下發送一個空包給客戶端,然后客戶端反饋一個同樣的空包回來,服務器如果在一定時間內收不到客戶端發送過來的反饋包,那就只有認定說掉線了。
其實,要判定掉線,只需要send或者recv一下,如果結果為零,則為掉線。但是,在長連接下,有可能很長一段時間都沒有數據往來。
理論上說,這個連接是一直保持連接的,但是實際情況中,如果中間節點出現什么故障是難以知道的。更要命的是,有的節點(防火墻)會自動把一定時間之內沒有數據交互的連接給斷掉。在這個時候,就需要我們的心跳包了,用于維持長連接,保活。
在獲知了斷線之后,服務器邏輯可能需要做一些事情,比如斷線后的數據清理呀,重新連接呀……當然,這個自然是要由邏輯層根據需求去做了。
總的來說,心跳包主要也就是用于長連接的保活和斷線處理。一般的應用下,判定時間在30-40秒比較不錯。如果實在要求高,那就在6-9秒。
* TCP協議的`KeepAlive`機制
TCP的IP傳輸層的兩個主要協議是UDP和TCP,其中UDP是無連接的、面向packet的,而TCP協議是有連接、面向流的協議。
TCP的`KeepAlive`機制,首先它貌似默認是不打開的,要用`setsockopt`將`SOL_SOCKET.SO_KEEPALIVE`設置為1才是打開,并且可以設置三個參數`tcp_keepalive_time/tcp_keepalive_probes/tcp_keepalive_intvl`,分別表示連接閑置多久開始發`keepalive`的ack包、發幾個ack包不回復才當對方死了、兩個ack包之間間隔多.
在測試的時候用`Ubuntu Server 10.04`下面默認值是7200秒(2個小時,要不要這么蛋疼啊!)、9次、75秒。
于是連接就了有一個超時時間窗口,如果連接之間沒有通信,這個時間窗口會逐漸減小,當它減小到零的時候,TCP協議會向對方發一個帶有ACK標志的空數據包(KeepAlive探針),對方在收到ACK包以后,如果連接一切正常,應該回復一個ACK;如果連接出現錯誤了(例如對方重啟了,連接狀態丟失),則應當回復一個RST;如果對方沒有回復,服務器每隔intvl的時間再發ACK,如果連續probes個包都被無視了,說明連接被斷開了。
在http早期,每個http請求都要求打開一個tpc socket連接,并且使用一次之后就斷開這個tcp連接。
使用`keep-alive`可以改善這種狀態,即在一次TCP連接中可以持續發送多份數據而不會斷開連接。通過使用`keep-alive`機制,可以減少tcp連接建立次數,也意味著可以減少`TIME_WAIT`狀態連接,以此提高性能和提高httpd服務器的吞吐率(更少的tcp連接意味著更少的系統內核調用,socket的`accept()`和`close()`調用)。
但是,`keep-alive`并不是免費的午餐,長時間的tcp連接容易導致系統資源無效占用。配置不當的`keep-alive`,有時比重復利用連接帶來的損失還更大。所以,正確地設置`keep-alive timeout`時間非常重要。
使用`http keep-alvie`,可以減少服務端`TIME_WAIT`數量(因為由服務端httpd守護進程主動關閉連接)。道理很簡單,相較而言,啟用`keep-alive`,建立的tcp連接更少了,自然要被關閉的tcp連接也相應更少了。
使用啟用`keepalive`的不同。另外,`http keepalive`是客戶端瀏覽器與服務端httpd守護進程協作的結果,所以,我們另外安排篇幅介紹不同瀏覽器的各種情況對`keep-alive`的利用。
[](https://github.com/KeKe-Li/data-structures-questions/blob/master/src/images/106.jpg)
- Golang基礎
- Go中new與make的區別
- Golang中除了加Mutex鎖以外還有哪些方式安全讀寫共享變量
- 無緩沖Chan的發送和接收是否同步
- Golang并發機制以及它所使用的CSP并發模型.
- Golang中常用的并發模型
- Go中對nil的Slice和空Slice的處理是一致的嗎
- 協程和線程和進程的區別
- Golang的內存模型中為什么小對象多了會造成GC壓力
- Go中數據競爭問題怎么解決
- 什么是channel,為什么它可以做到線程安全
- Golang垃圾回收算法
- GC的觸發條件
- Go的GPM如何調度
- 并發編程概念是什么
- Go語言的棧空間管理是怎么樣的
- Goroutine和Channel的作用分別是什么
- 怎么查看Goroutine的數量
- Go中的鎖有哪些
- 怎么限制Goroutine的數量
- Channel是同步的還是異步的
- Goroutine和線程的區別
- Go的Struct能不能比較
- Go的defer原理是什么
- Go的select可以用于什么
- Context包的用途是什么
- Go主協程如何等其余協程完再操作
- Go的Slice如何擴容
- Go中的map如何實現順序讀取
- Go中CAS是怎么回事
- Go中的逃逸分析是什么
- Go值接收者和指針接收者的區別
- Go的對象在內存中是怎樣分配的
- 棧的內存是怎么分配的
- 堆內存管理怎么分配的
- 在Go函數中為什么會發生內存泄露
- G0的作用
- Go中的鎖如何實現
- Go中的channel的實現
- 棧的內存是怎么分配的2
- 堆內存管理怎么分配的2
- Go中的map的實現
- Go中的http包的實現原理
- Goroutine發生了泄漏如何檢測
- Go函數返回局部變量的指針是否安全
- Go中兩個Nil可能不相等嗎
- Goroutine和KernelThread之間是什么關系
- 為何GPM調度要有P
- 如何在goroutine執行一半就退出協程
- Mysql基礎
- Mysql索引用的是什么算法
- Mysql事務的基本要素
- Mysql的存儲引擎
- Mysql事務隔離級別
- Mysql高可用方案有哪些
- Mysql中utf8和utf8mb4區別
- Mysql中樂觀鎖和悲觀鎖區別
- Mysql索引主要是哪些
- Mysql聯合索引最左匹配原則
- 聚簇索引和非聚簇索引區別
- 如何查詢一個字段是否命中了索引
- Mysql中查詢數據什么情況下不會命中索引
- Mysql中的MVCC是什么
- Mvcc和Redolog和Undolog以及Binlog有什么不同
- Mysql讀寫分離以及主從同步
- InnoDB的關鍵特性
- Mysql如何保證一致性和持久性
- 為什么選擇B+樹作為索引結構
- InnoDB的行鎖模式
- 哈希(hash)比樹(tree)更快,索引結構為什么要設計成樹型
- 為什么索引的key長度不能太長
- Mysql的數據如何恢復到任意時間點
- Mysql為什么加了索引可以加快查詢
- Explain命令有什么用
- Redis基礎
- Redis的數據結構及使用場景
- Redis持久化的幾種方式
- Redis的LRU具體實現
- 單線程的Redis為什么快
- Redis的數據過期策略
- 如何解決Redis緩存雪崩問題
- 如何解決Redis緩存穿透問題
- Redis并發競爭key如何解決
- Redis的主從模式和哨兵模式和集群模式區別
- Redis有序集合zset底層怎么實現的
- 跳表的查詢過程是怎么樣的,查詢和插入的時間復雜度
- 網絡協議基礎
- TCP和UDP有什么區別
- TCP中三次握手和四次揮手
- TCP的LISTEN狀態是什么
- 常見的HTTP狀態碼有哪些
- 301和302有什么區別
- 504和500有什么區別
- HTTPS和HTTP有什么區別
- Quic有什么優點相比Http2
- Grpc的優缺點
- Get和Post區別
- Unicode和ASCII以及Utf8的區別
- Cookie與Session異同
- Client如何實現長連接
- Http1和Http2和Grpc之間的區別是什么
- Tcp中的拆包和粘包是怎么回事
- TFO的原理是什么
- TIME_WAIT的作用
- 網絡的性能指標有哪些