Golang 心跳的實現
轉自:
https://blog.csdn.net/lengyuezuixue/article/details/79235850
在多客戶端同時訪問伺服器的工作模式下,首先要保證伺服器的執行正常。因此,Server和Client建立通訊後,確保連線的及時斷開就非常重要。否則,多個客戶端長時間佔用著連線不關閉,是非常可怕的伺服器資源浪費。會使得伺服器可服務的客戶端數量大幅度減少。
因此,針對短連結和長連線,根據業務的需求,配套不同的處理機制。
短連線
一般建立完連線,就立刻傳輸資料。傳輸完資料,連線就關閉。服務端根據需要,設定連線的時長。超過時間長度,就算客戶端超時。立刻關閉連線。
長連線
建立連線後,傳輸資料,然後要保持連線,然後再次傳輸資料。直到連線關閉。
socket讀寫可以通過 SetDeadline、SetReadDeadline、SetWriteDeadline設定阻塞的時間。
- func (*IPConn) SetDeadline
- func (c *IPConn) SetDeadline(t time.Time) error
-
- func (*IPConn) SetReadDeadline
- func (c *IPConn) SetReadDeadline(t time.Time) error
-
- func (*IPConn) SetWriteDeadline
- func (c *IPConn) SetWriteDeadline(t time.Time) error
如果做短連線,直接在Server端的連線上設定SetReadDeadline。當你設定的時限到達,無論客戶端是否還在繼續傳遞訊息,服務端都不會再接收。並且已經關閉連線。
- func main() {
- server := ":7373"
- netListen, err := net.Listen("tcp", server)
- if err != nil{
- Log("connect error: ", err)
- os.Exit(1)
- }
- Log("Waiting for Client ...")
- for{
- conn, err := netListen.Accept()
- if err != nil{
- Log(conn.RemoteAddr().String(), "Fatal error: ", err)
- continue
- }
-
- //設定短連線(10秒)
- conn.SetReadDeadline(time.Now().Add(time.Duration(10)*time.Second))
-
- Log(conn.RemoteAddr().String(), "connect success!")
- ...
- }
- }
這就可以了。在這段程式碼中,每當10秒中的時限一道,連線就終止了。
根據業務需要,客戶端可能需要長時間保持連線。但是服務端不能無限制的保持。這就需要一個機制,如果超過某個時間長度,服務端沒有獲得客戶端的資料,就判定客戶端已經不需要連線了(比如客戶端掛掉了)。
做到這個,需要一個心跳機制。在限定的時間內,客戶端給服務端傳送一個指定的訊息,以便服務端知道客戶端還活著。
- func sender(conn *net.TCPConn) {
- for i := 0; i < 10; i++{
- words := strconv.Itoa(i)+" Hello I'm MyHeartbeat Client."
- msg, err := conn.Write([]byte(words))
- if err != nil {
- Log(conn.RemoteAddr().String(), "Fatal error: ", err)
- os.Exit(1)
- }
- Log("服務端接收了", msg)
- time.Sleep(2 * time.Second)
- }
- for i := 0; i < 2 ; i++ {
- time.Sleep(12 * time.Second)
- }
- for i := 0; i < 10; i++{
- words := strconv.Itoa(i)+" Hi I'm MyHeartbeat Client."
- msg, err := conn.Write([]byte(words))
- if err != nil {
- Log(conn.RemoteAddr().String(), "Fatal error: ", err)
- os.Exit(1)
- }
- Log("服務端接收了", msg)
- time.Sleep(2 * time.Second)
- }
-
- }
這段客戶端程式碼,實現了兩個相同的資訊傳送頻率給服務端。兩個頻率中間,我們讓執行休息了12秒。然後,我們在服務端的對應機制是這樣的。
- func HeartBeating(conn net.Conn, bytes chan byte, timeout int) {
- select {
- case fk := <- bytes:
- Log(conn.RemoteAddr().String(), "心跳:第", string(fk), "times")
- conn.SetDeadline(time.Now().Add(time.Duration(timeout) * time.Second))
- break
-
- case <- time.After(5 * time.Second):
- Log("conn dead now")
- conn.Close()
- }
- }
每次接收到心跳資料就 SetDeadline 延長一個時間段 timeout。如果沒有接到心跳資料,5秒後連線關閉。
服務端完整程式碼示例
- /**
- * MyHeartbeatServer
- * @Author: Jian Junbo
- * @Email: junbojian@qq.com
- * @Create: 2017/9/16 14:02
- * Copyright (c) 2017 Jian Junbo All rights reserved.
- *
- * Description:
- */
- package main
-
- import (
- "net"
- "fmt"
- "os"
- "time"
- )
-
- func main() {
- server := ":7373"
- netListen, err := net.Listen("tcp", server)
- if err != nil{
- Log("connect error: ", err)
- os.Exit(1)
- }
- Log("Waiting for Client ...")
- for{
- conn, err := netListen.Accept()
- if err != nil{
- Log(conn.RemoteAddr().String(), "Fatal error: ", err)
- continue
- }
-
- //設定短連線(10秒)
- conn.SetReadDeadline(time.Now().Add(time.Duration(10)*time.Second))
-
- Log(conn.RemoteAddr().String(), "connect success!")
- go handleConnection(conn)
-
- }
- }
- func handleConnection(conn net.Conn) {
- buffer := make([]byte, 1024)
- for {
- n, err := conn.Read(buffer)
- if err != nil {
- Log(conn.RemoteAddr().String(), " Fatal error: ", err)
- return
- }
-
- Data := buffer[:n]
- message := make(chan byte)
- //心跳計時
- go HeartBeating(conn, message, 4)
- //檢測每次是否有資料傳入
- go GravelChannel(Data, message)
-
- Log(time.Now().Format("2006-01-02 15:04:05.0000000"), conn.RemoteAddr().String(), string(buffer[:n]))
- }
-
- defer conn.Close()
- }
- func GravelChannel(bytes []byte, mess chan byte) {
- for _, v := range bytes{
- mess <- v
- }
- close(mess)
- }
- func HeartBeating(conn net.Conn, bytes chan byte, timeout int) {
- select {
- case fk := <- bytes:
- Log(conn.RemoteAddr().String(), "心跳:第", string(fk), "times")
- conn.SetDeadline(time.Now().Add(time.Duration(timeout) * time.Second))
- break
-
- case <- time.After(5 * time.Second):
- Log("conn dead now")
- conn.Close()
- }
- }
- func Log(v ...interface{}) {
- fmt.Println(v...)
- return
- }
客戶端完整程式碼示例
- /**
- * MyHeartbeatClient
- * @Author: Jian Junbo
- * @Email: junbojian@qq.com
- * @Create: 2017/9/16 14:21
- * Copyright (c) 2017 Jian Junbo All rights reserved.
- *
- * Description:
- */
- package main
-
- import (
- "net"
- "fmt"
- "os"
- "strconv"
- "time"
- )
-
- func main() {
- server := "127.0.0.1:7373"
-
- tcpAddr, err := net.ResolveTCPAddr("tcp4",server)
- if err != nil{
- Log(os.Stderr,"Fatal error:",err.Error())
- os.Exit(1)
- }
- conn, err := net.DialTCP("tcp",nil,tcpAddr)
- if err != nil{
- Log("Fatal error:",err.Error())
- os.Exit(1)
- }
- Log(conn.RemoteAddr().String(), "connection succcess!")
-
- sender(conn)
- Log("send over")
- }
- func sender(conn *net.TCPConn) {
- for i := 0; i < 10; i++{
- words := strconv.Itoa(i)+" Hello I'm MyHeartbeat Client."
- msg, err := conn.Write([]byte(words))
- if err != nil {
- Log(conn.RemoteAddr().String(), "Fatal error: ", err)
- os.Exit(1)
- }
- Log("服務端接收了", msg)
- time.Sleep(2 * time.Second)
- }
- for i := 0; i < 2 ; i++ {
- time.Sleep(12 * time.Second)
- }
- for i := 0; i < 10; i++{
- words := strconv.Itoa(i)+" Hi I'm MyHeartbeat Client."
- msg, err := conn.Write([]byte(words))
- if err != nil {
- Log(conn.RemoteAddr().String(), "Fatal error: ", err)
- os.Exit(1)
- }
- Log("服務端接收了", msg)
- time.Sleep(2 * time.Second)
- }
-
- }
- func Log(v ...interface{}) {
- fmt.Println(v...)
- return
- }
參考:
https://www.yuque.com/docs/share/ef732d9e-f488-4e7e-8d64-43a1c18872ea
相關文章
- 聊聊心跳機制及netty心跳實現Netty
- 微信小程式實現WebSocket心跳重連微信小程式Web
- Golang可重入鎖的實現Golang
- Golang 的 goroutine 是如何實現的?Golang
- golang reflect 實現原理Golang
- C++ 實現Golang裡的deferC++Golang
- Golang 限流器的使用和實現Golang
- golang 中 sync.Mutex 的實現GolangMutex
- Golang 實現 RabbitMQ 的死信佇列GolangMQ佇列
- 【Go】Golang實現gRPC的Proxy的原理GolangRPC
- golang實現單例模式Golang單例模式
- 計數排序 -- GoLang實現排序Golang
- golang實現稀疏陣列Golang陣列
- Golang實現ForkJoin小文Golang
- golang如何實現單例Golang單例
- 利用 Watermill 實現 Golang CQRSGolang
- 層級時間輪的 Golang 實現Golang
- golang實現抓取IP地址的蜘蛛程式Golang
- 常見的Golang設計模式實現?Golang設計模式
- golang 中,非對稱加密的實現Golang加密
- 200行golang 實現的區塊鏈Golang區塊鏈
- Golang 實現 RabbitMQ 的延遲佇列GolangMQ佇列
- Golang 實現 Redis(6): 實現 pipeline 模式的 redis 客戶端GolangRedis模式客戶端
- Golang 學習——如何判斷 Golang 介面是否實現?Golang
- Golang 實現 Redis(5): 使用跳錶實現 SortedSetGolangRedis
- 高效 實現長連線保活:手把手教你實現 自適應的心跳保活機制
- Golang 中字典的 Comma Ok 是如何實現的Golang
- Golang 連線池的幾種實現案例Golang
- golang 中,對稱加密的程式碼實現Golang加密
- 關於Golang中的依賴注入實現Golang依賴注入
- golang洗牌演算法實現Golang演算法
- Golang實現氣泡排序法Golang排序
- golang 進度條功能實現Golang
- Golang協程池(workpool)實現Golang
- golang實現子程式通訊Golang
- Golang實現PHP常用函式GolangPHP函式
- golang關鍵字select的三個例子, time.After模擬socket/心跳超時Golang
- golang的fmt包String(),Error(),Format(),GoString()的介面實現GolangErrorORM