Golang 心跳的實現

大囚長發表於2019-02-01

轉自:
https://blog.csdn.net/lengyuezuixue/article/details/79235850

    在多客戶端同時訪問伺服器的工作模式下,首先要保證伺服器的執行正常。因此,Server和Client建立通訊後,確保連線的及時斷開就非常重要。否則,多個客戶端長時間佔用著連線不關閉,是非常可怕的伺服器資源浪費。會使得伺服器可服務的客戶端數量大幅度減少。

   因此,針對短連結和長連線,根據業務的需求,配套不同的處理機制。

短連線

    一般建立完連線,就立刻傳輸資料。傳輸完資料,連線就關閉。服務端根據需要,設定連線的時長。超過時間長度,就算客戶端超時。立刻關閉連線。

長連線

    建立連線後,傳輸資料,然後要保持連線,然後再次傳輸資料。直到連線關閉。

 

   socket讀寫可以通過 SetDeadline、SetReadDeadline、SetWriteDeadline設定阻塞的時間。

  1. func (*IPConn) SetDeadline
  2. func (c *IPConn) SetDeadline(t time.Time) error
  3. func (*IPConn) SetReadDeadline
  4. func (c *IPConn) SetReadDeadline(t time.Time) error
  5. func (*IPConn) SetWriteDeadline
  6. func (c *IPConn) SetWriteDeadline(t time.Time) error

   如果做短連線,直接在Server端的連線上設定SetReadDeadline。當你設定的時限到達,無論客戶端是否還在繼續傳遞訊息,服務端都不會再接收。並且已經關閉連線。

  1. func main() {
  2. server := ":7373"
  3. netListen, err := net.Listen("tcp", server)
  4. if err != nil{
  5. Log("connect error: ", err)
  6. os.Exit(1)
  7. }
  8. Log("Waiting for Client ...")
  9. for{
  10. conn, err := netListen.Accept()
  11. if err != nil{
  12. Log(conn.RemoteAddr().String(), "Fatal error: ", err)
  13. continue
  14. }
  15. //設定短連線(10秒)
  16. conn.SetReadDeadline(time.Now().Add(time.Duration(10)*time.Second))
  17. Log(conn.RemoteAddr().String(), "connect success!")
  18. ...
  19. }
  20. }

 

   這就可以了。在這段程式碼中,每當10秒中的時限一道,連線就終止了。

 

   根據業務需要,客戶端可能需要長時間保持連線。但是服務端不能無限制的保持。這就需要一個機制,如果超過某個時間長度,服務端沒有獲得客戶端的資料,就判定客戶端已經不需要連線了(比如客戶端掛掉了)。

   做到這個,需要一個心跳機制。在限定的時間內,客戶端給服務端傳送一個指定的訊息,以便服務端知道客戶端還活著。

  1. func sender(conn *net.TCPConn) {
  2. for i := 0; i < 10; i++{
  3. words := strconv.Itoa(i)+" Hello I'm MyHeartbeat Client."
  4. msg, err := conn.Write([]byte(words))
  5. if err != nil {
  6. Log(conn.RemoteAddr().String(), "Fatal error: ", err)
  7. os.Exit(1)
  8. }
  9. Log("服務端接收了", msg)
  10. time.Sleep(2 * time.Second)
  11. }
  12. for i := 0; i < 2 ; i++ {
  13. time.Sleep(12 * time.Second)
  14. }
  15. for i := 0; i < 10; i++{
  16. words := strconv.Itoa(i)+" Hi I'm MyHeartbeat Client."
  17. msg, err := conn.Write([]byte(words))
  18. if err != nil {
  19. Log(conn.RemoteAddr().String(), "Fatal error: ", err)
  20. os.Exit(1)
  21. }
  22. Log("服務端接收了", msg)
  23. time.Sleep(2 * time.Second)
  24. }
  25. }

   這段客戶端程式碼,實現了兩個相同的資訊傳送頻率給服務端。兩個頻率中間,我們讓執行休息了12秒。然後,我們在服務端的對應機制是這樣的。

 

  1. func HeartBeating(conn net.Conn, bytes chan byte, timeout int) {
  2. select {
  3. case fk := <- bytes:
  4. Log(conn.RemoteAddr().String(), "心跳:第", string(fk), "times")
  5. conn.SetDeadline(time.Now().Add(time.Duration(timeout) * time.Second))
  6. break
  7. case <- time.After(5 * time.Second):
  8. Log("conn dead now")
  9. conn.Close()
  10. }
  11. }

   每次接收到心跳資料就 SetDeadline 延長一個時間段 timeout。如果沒有接到心跳資料,5秒後連線關閉。

 

服務端完整程式碼示例

  1. /**
  2. * MyHeartbeatServer
  3. * @Author: Jian Junbo
  4. * @Email: junbojian@qq.com
  5. * @Create: 2017/9/16 14:02
  6. * Copyright (c) 2017 Jian Junbo All rights reserved.
  7. *
  8. * Description:
  9. */
  10. package main
  11. import (
  12. "net"
  13. "fmt"
  14. "os"
  15. "time"
  16. )
  17. func main() {
  18. server := ":7373"
  19. netListen, err := net.Listen("tcp", server)
  20. if err != nil{
  21. Log("connect error: ", err)
  22. os.Exit(1)
  23. }
  24. Log("Waiting for Client ...")
  25. for{
  26. conn, err := netListen.Accept()
  27. if err != nil{
  28. Log(conn.RemoteAddr().String(), "Fatal error: ", err)
  29. continue
  30. }
  31. //設定短連線(10秒)
  32. conn.SetReadDeadline(time.Now().Add(time.Duration(10)*time.Second))
  33. Log(conn.RemoteAddr().String(), "connect success!")
  34. go handleConnection(conn)
  35. }
  36. }
  37. func handleConnection(conn net.Conn) {
  38. buffer := make([]byte, 1024)
  39. for {
  40. n, err := conn.Read(buffer)
  41. if err != nil {
  42. Log(conn.RemoteAddr().String(), " Fatal error: ", err)
  43. return
  44. }
  45. Data := buffer[:n]
  46. message := make(chan byte)
  47. //心跳計時
  48. go HeartBeating(conn, message, 4)
  49. //檢測每次是否有資料傳入
  50. go GravelChannel(Data, message)
  51. Log(time.Now().Format("2006-01-02 15:04:05.0000000"), conn.RemoteAddr().String(), string(buffer[:n]))
  52. }
  53. defer conn.Close()
  54. }
  55. func GravelChannel(bytes []byte, mess chan byte) {
  56. for _, v := range bytes{
  57. mess <- v
  58. }
  59. close(mess)
  60. }
  61. func HeartBeating(conn net.Conn, bytes chan byte, timeout int) {
  62. select {
  63. case fk := <- bytes:
  64. Log(conn.RemoteAddr().String(), "心跳:第", string(fk), "times")
  65. conn.SetDeadline(time.Now().Add(time.Duration(timeout) * time.Second))
  66. break
  67. case <- time.After(5 * time.Second):
  68. Log("conn dead now")
  69. conn.Close()
  70. }
  71. }
  72. func Log(v ...interface{}) {
  73. fmt.Println(v...)
  74. return
  75. }

客戶端完整程式碼示例

 

  1. /**
  2. * MyHeartbeatClient
  3. * @Author: Jian Junbo
  4. * @Email: junbojian@qq.com
  5. * @Create: 2017/9/16 14:21
  6. * Copyright (c) 2017 Jian Junbo All rights reserved.
  7. *
  8. * Description:
  9. */
  10. package main
  11. import (
  12. "net"
  13. "fmt"
  14. "os"
  15. "strconv"
  16. "time"
  17. )
  18. func main() {
  19. server := "127.0.0.1:7373"
  20. tcpAddr, err := net.ResolveTCPAddr("tcp4",server)
  21. if err != nil{
  22. Log(os.Stderr,"Fatal error:",err.Error())
  23. os.Exit(1)
  24. }
  25. conn, err := net.DialTCP("tcp",nil,tcpAddr)
  26. if err != nil{
  27. Log("Fatal error:",err.Error())
  28. os.Exit(1)
  29. }
  30. Log(conn.RemoteAddr().String(), "connection succcess!")
  31. sender(conn)
  32. Log("send over")
  33. }
  34. func sender(conn *net.TCPConn) {
  35. for i := 0; i < 10; i++{
  36. words := strconv.Itoa(i)+" Hello I'm MyHeartbeat Client."
  37. msg, err := conn.Write([]byte(words))
  38. if err != nil {
  39. Log(conn.RemoteAddr().String(), "Fatal error: ", err)
  40. os.Exit(1)
  41. }
  42. Log("服務端接收了", msg)
  43. time.Sleep(2 * time.Second)
  44. }
  45. for i := 0; i < 2 ; i++ {
  46. time.Sleep(12 * time.Second)
  47. }
  48. for i := 0; i < 10; i++{
  49. words := strconv.Itoa(i)+" Hi I'm MyHeartbeat Client."
  50. msg, err := conn.Write([]byte(words))
  51. if err != nil {
  52. Log(conn.RemoteAddr().String(), "Fatal error: ", err)
  53. os.Exit(1)
  54. }
  55. Log("服務端接收了", msg)
  56. time.Sleep(2 * time.Second)
  57. }
  58. }
  59. func Log(v ...interface{}) {
  60. fmt.Println(v...)
  61. return
  62. }

參考:

https://www.yuque.com/docs/share/ef732d9e-f488-4e7e-8d64-43a1c18872ea

相關文章