Golang 心跳的實現
在多客戶端同時訪問伺服器 的工作模式下,首先要保證伺服器的執行正常。因此,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, byteschan byte , timeoutint ) {
-
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:[email protected]
-
* @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 , messchan byte ) {
-
for _, v :=range bytes{
-
mess <- v
-
}
-
close (mess)
-
}
-
func HeartBeating (conn net.Conn, byteschan byte , timeoutint ) {
-
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:[email protected]
-
* @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