52. Realization of Heart Rate

Keywords: socket

In the working mode of multi-client accessing the server at the same time, the first thing is to ensure the normal operation of the server. Therefore, it is very important for Server to ensure that the connection is disconnected in time after establishing communication with Client. Otherwise, it is a terrible waste of server resources that multiple clients occupy the connection for a long time without closing. This will greatly reduce the number of serveable clients on the server.
Therefore, for short and long connections, according to business needs, supporting different processing mechanisms.

Short connection

Generally, once a connection is established, data is transmitted immediately. When the data is transferred, the connection is closed. The server sets the connection time according to the need. Over time, even if the client timed out. Close the connection immediately.

Long connection

After the connection is established, the data is transmitted, then the connection is maintained, and then the data is transmitted again. Until the connection is closed.

socket reads and writes can set the blocking time through 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 

If you make a short connection, set ReadDeadline directly on the Server-side connection. When the time limit you set arrives, the Server will not receive the message no matter whether the client is still delivering it or not. And the connection has been closed.

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
        }

        //Set Short Connection (10 seconds)
        conn.SetReadDeadline(time.Now().Add(time.Duration(10)*time.Second))

        Log(conn.RemoteAddr().String(), "connect success!")
        ...
    }
}

That's all right. In this code, the connection terminates whenever the time limit in 10 seconds arrives.
This is easy to do. What we want to focus on is how to achieve timeout control in long connections.
Depending on business needs, the client may need to maintain the connection for a long time. But the server can't keep it indefinitely. This requires a mechanism to determine that the client does not need to connect (e.g. the client has dropped) if the server does not get the client's data for more than a certain length of time.
To do this, we need a heartbeat mechanism. In a limited time, the client sends a specified message to the server so that the server knows that the client is still alive.

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("The server receives it.", 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("The server receives it.", msg)
        time.Sleep(2 * time.Second)
    }

}

This client code achieves two identical information sending frequencies to the server. Between the two frequencies, we rested the operation for 12 seconds.
Then, our corresponding mechanism on the server side is like this.

func HeartBeating(conn net.Conn, bytes chan byte, timeout int) {
    select {
    case fk := <- bytes:
        Log(conn.RemoteAddr().String(), "heartbeat:The first", 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 lengthens timeout for a period of time each time heartbeat data is received. If no heartbeat data is received, the connection closes after 5 seconds.
Server-side complete code example

/**
* 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
        }

        //Set Short Connection (10 seconds)
        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)
        //Heartbeat timing
        go HeartBeating(conn, message, 4)
        //Check whether there is data in each time
        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(), "heartbeat:The first", 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
}

Client complete code example

/**
* 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("The server receives it.", 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("The server receives it.", msg)
        time.Sleep(2 * time.Second)
    }

}
func Log(v ...interface{}) {
    fmt.Println(v...)
    return
}

The server runs like this



The server only receives the first round-robin data

According to the principle of 5 seconds, the server shuts down the connection within 12 seconds of the first cycle of the client.

The client runs like this



The client has no idea whether the server has closed the connection or continues to send the second loop.

In this way, the server really achieves the goal of not occupying too much resources. But it's not friendly enough for the client. The friendly way is to shake hands three times for each message exchange.
Of course, the specific adoption needs to be determined by business scenario analysis.

Posted by SundayDriver on Fri, 24 May 2019 12:51:26 -0700