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
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
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.