GOLANG探測HTTP連線斷開
考慮基於HTTP的RPC,或者HTTP伺服器主動通知客戶端的機制,就是HTTP Long-Polling,意思就是客戶端發起一個長連線,伺服器阻塞忍住不響應直到:
- 超時,比如5秒後,我們給客戶端響應一個keepalive,意思是現在還沒有啥事,請繼續polling。
- 拿到結果,這個可能是任何時候,比如300毫秒、1100毫秒、2300毫秒拿到一個事件,響應給客戶端,實現了有事件非同步通知。
這樣客戶端和伺服器之間RPC的效率就非常高,只有在有事件時才會通知。但是,實際上還有一種情況需要處理:
- 當客戶端斷開連線,比如客戶端設定了3秒鐘TCP請求超時,或者因為客戶端Crash時OS回收了FD等等,這個時候伺服器應該要終止polling事務,停止獲取事件。因為如果這個時候獲取了事件,那麼如何處理這個事件?只能丟棄,如果客戶端再次發起請求,就拿不到這個事件了。
問題就來了,如何在HTTP Handler中探測客戶端斷開?例如:
var incoming chan []byte
http.HandleFunc("/polling", func(w http.ResponseWriter, r *http.Request) {
select {
case b := <- incoming:
w.Write(b)
case <-time.After(5 * time.Second):
w.Write("keepalive")
// how to detect TCP disconnect event?
}
})
可能有以下方式:
- 讀取
r.Body
,如果發現斷開應該會有錯誤。 - 有朋友用reflect或hijack取到底層的TCPConn,然後Peek。
- 將
w
轉換成http.CloseNotifier
,在TCP連線關閉時拿到事件。
r.Body Read
這種方式是不靠譜的,假設沒有Body內容,直接讀取檢測是否有error:
nn,err := io.Copy(ioutil.Discard, r.Body)
實際上返回的是nn=0
和err=nil
,也就是沒有Body,沒有錯誤。因為這個讀取的含義是指Request結束。
如果讀取完Body後再讀呢?收到的是io.EOF
,在沒有傳送Response之前,Request已經結束了,所以就是io.EOF
,並不能檢測到底層TCP斷開。
Peek TcpConn
使用reflect獲取底層的TCPConn物件,是知道w http.ResponseWriter
實際上是http.response
:
// A response represents the server side of an HTTP response.
type response struct {
conn *conn
它有個Field就是conn
,再轉成TCPConn
就可以Peek。
這樣做的風險就是,不同的GOLANG版本,可能會對底層實現進行變更,在升級時會有風險。
Reflect方式始終不是最好的。
另外,還有一種方式,就是用http hijack方式,這種方式雖然是http庫提供的介面,但是很多地方註釋都說hijack需要特殊處理,因此也不是最好的方式。參考When to use hijack。
Close Notifier
在GO1.1提供了http.CloseNotifier
介面,參考Close Notifier,但是也注意會有一些問題,參考net/http: CloseNotifier fails to fire when underlying connection is gone。用法如下:
var incoming chan []byte
http.HandleFunc("/polling", func(w http.ResponseWriter, r *http.Request) {
select {
case <- w.(http.CloseNotifier).CloseNotify():
fmt.Println("connection closed")
}
})
實際上,超時機制始終是需要的,加上之前的邏輯,考慮context.Context
取消事件,http-long polling
的完整實現應該是:
func polling(ctx context.Context, incoming chan []byte) {
http.HandleFunc("/polling", func(w http.ResponseWriter, r *http.Request) {
select {
case <- ctx.Done():
fmt.Println("system quit")
case b := <- incoming:
w.Write(b)
case <-time.After(5 * time.Second):
w.Write("keepalive")
case <- w.(http.CloseNotifier).CloseNotify():
fmt.Println("connection closed")
}
})
}
相關文章
- 關於檢測TCP連線斷開TCP
- 滲透&&探測 (如何斷開隔壁妹紙的無線網路)
- Socket連線和Http連線HTTP
- java操作Oracle 方式一 ( 連線-》操作-》斷開連線 )JavaOracle
- 保持ssh的連線不斷開
- 1204 斷開式連線
- HTTP 持久連線HTTP
- HTTP長連線HTTP
- HTTP連線池HTTP
- golang tcp連線池GolangTCP
- ADO 資料庫連線斷開重連資料庫
- Http持久連線與HttpClient連線池HTTPclient
- http的長連線和短連線HTTP
- 網路連線斷開如何解決 win10系統網路連線經常斷開Win10
- cmd 工具 ssh 保持連線不斷開
- VMRC控制檯的連線已斷開..正在嘗試重新連線
- http Socket長連線HTTP
- Golang SQL連線池梳理GolangSQL
- vmware中控制檯的連線已斷開…正在嘗試重新連線
- WebSocket斷線重連Web
- 熱升級如何不斷開舊連線
- java連線池解決連線中斷Java
- HTTP非持續連線和持續連線HTTP
- HTTP長連線和短連線原理淺析HTTP
- golang連線MySQL時候的連線池設定GolangMySql
- 圖解 HTTP 連線管理圖解HTTP
- HTTP 長連線問題HTTP
- HTTP長連線、短連線究竟是什麼?HTTP
- Http和Socket連線區別HTTP
- 連線Http Server的程式碼HTTPServer
- 【問題記錄】—SignalR連線斷線重連SignalR
- golang開發:類庫篇(二) Redis連線池的使用GolangRedis
- golang 幾種字串的連線方式Golang字串
- TCP-ip建立連線時3次握手,斷開連線時4次揮手TCP
- 俄羅斯成功斷開全球網際網路連線
- ? 抓包分析 TCP 建立和斷開連線的流程TCP
- 記一次websocket的自動斷開連線Web
- 使用secureCRT連線伺服器,防超時斷開Securecrt伺服器