在多客戶端同時訪問服務(wù)器的工作模式下,首先要保證服務(wù)端的運(yùn)行正常。因此,Server在和Client建立通訊后,確保連接的及時斷開就非常重要。否則,多個客戶端長時間占用著連接不關(guān)閉,是非??膳碌姆?wù)器資源浪費(fèi)。會使得服務(wù)器可服務(wù)的客戶端數(shù)量大幅度減少。
因此,針對短連接和長連接,根據(jù)業(yè)務(wù)的需要,配套不同的處理機(jī)制。
短連接
一般建立完連接,就立刻傳輸數(shù)據(jù)。傳輸完數(shù)據(jù),連接就關(guān)閉。服務(wù)端根據(jù)需要,設(shè)定連接的時長。超過時間長度,就算客戶端超時。立刻關(guān)閉連接。
長連接
建立連接后,傳輸數(shù)據(jù),然后要保持連接,然后再次傳輸數(shù)據(jù)。直到連接關(guān)閉。
socket 讀寫可以通過 SetDeadline、SetReadDeadline、SetWriteDeadline設(shè)置阻塞的時間。
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 端的連接上設(shè)置SetReadDeadline。當(dāng)你設(shè)置的時限到達(dá),無論客戶端是否還在繼續(xù)傳遞信息,服務(wù)端都不會再接收。并且已經(jīng)關(guān)閉連接。
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
}
//設(shè)置短連接(10秒)
conn.SetReadDeadline(time.Now().Add(time.Duration(10)*time.Second))
Log(conn.RemoteAddr().String(), "connect success!")
...
}
}
這就可以了。在這段代碼中,每當(dāng)10秒中的時限一到,連接就終止了。
這個很容易做到。而我們重點要講的是,在長連接中如何做到超時控制。
根據(jù)業(yè)務(wù)需要,客戶端可能需要長時間保持連接。但是服務(wù)端不能無限制的保持。這就需要一個機(jī)制,如果超過某個時間長度,服務(wù)端沒有獲得客戶端的數(shù)據(jù),就判定客戶端已經(jīng)不需要連接了(比如客戶端掛掉了)。
做到這個,需要一個心跳機(jī)制。在限定的時間內(nèi),客戶端給服務(wù)端發(fā)送一個指定的消息,以便服務(wù)端知道客戶端還活著。
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("服務(wù)端接收了", 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("服務(wù)端接收了", msg)
time.Sleep(2 * time.Second)
}
}
這段客戶端代碼,實現(xiàn)了兩個相同的信息發(fā)送頻率給服務(wù)端。兩個頻率中間,我們讓運(yùn)行休息了12秒。
然后,我們在服務(wù)端的對應(yīng)機(jī)制是這樣的。
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()
}
}
每次接收到心跳數(shù)據(jù)就 SetDeadline 延長一個時間段 timeout。如果沒有接到心跳數(shù)據(jù),5秒后連接關(guān)閉。
服務(wù)端完整代碼示例
/**
* 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
}
//設(shè)置短連接(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)
//檢測每次是否有數(shù)據(jù)傳入
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("服務(wù)端接收了", 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("服務(wù)端接收了", msg)
time.Sleep(2 * time.Second)
}
}
func Log(v ...interface{}) {
fmt.Println(v...)
return
}
服務(wù)端運(yùn)行效果是這樣的

服務(wù)端根據(jù)5秒原則,在客戶端第一個循環(huán)間歇的12秒的時間內(nèi),關(guān)閉了連接。
客戶端運(yùn)行效果是這樣的

這樣做服務(wù)端確實達(dá)到了不占用過多資源的目的。但是對客戶端來說不夠友好。友好的方式,是每次消息往來都執(zhí)行三次握手的模式。
當(dāng)然,具體是否采用,需要根據(jù)業(yè)務(wù)場景分析確定。