W3Cschool
恭喜您成為首批注冊(cè)用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
針對(duì)于短連接而言,每一次發(fā)送接收數(shù)據(jù)即關(guān)閉連接,連接的處理邏輯比較簡(jiǎn)單,當(dāng)然通信效率也會(huì)比較低。在大多數(shù)的?TCP
?通信場(chǎng)景中,往往是使用長(zhǎng)連接操作,并采用異步全雙工的?TCP
?通信模式,即所有的通信完全是異步。在這種場(chǎng)景下,?gtcp.Conn
?鏈接對(duì)象可能同時(shí)處于多個(gè)讀寫操作(?gtcp.Conn
?對(duì)象的數(shù)據(jù)讀寫操作是并發(fā)安全的),因此?SendRecv
?操作在邏輯上將會(huì)失效。因?yàn)楫?dāng)你在同一邏輯操作中發(fā)送完畢數(shù)據(jù)之后,隨后立即獲取數(shù)據(jù)有可能得到的是其他寫操作的結(jié)果。
無論服務(wù)端還是客戶端,都需要在獨(dú)立的異步循環(huán)中封裝使用?Recv*
?方法獲取數(shù)據(jù)并通過?switch...case...
?處理數(shù)據(jù)(僅在一個(gè)?goroutine
?中全權(quán)負(fù)責(zé)讀取數(shù)據(jù)),根據(jù)請(qǐng)求數(shù)據(jù)進(jìn)行業(yè)務(wù)處理的轉(zhuǎn)發(fā)。
也就是說,?Send*/Recv*
?方法是并發(fā)安全的,但是發(fā)送數(shù)據(jù)時(shí)要一次性發(fā)送。由于支持異步并發(fā)寫,?gtcp.Conn
?對(duì)象不帶任何緩沖實(shí)現(xiàn)。
我們通過一個(gè)完成的示例來說明一下如何在程序中實(shí)現(xiàn)異步全雙工通信,完成示例代碼位于:https://github.com/gogf/gf/v2/tree/master/.example/net/gtcp/pkg_operations/common
types/types.go
?定義通信的數(shù)據(jù)格式,隨后我們可以使用?SendPkg/RecvPkg
?方法來通信。
考慮到簡(jiǎn)化測(cè)試代碼復(fù)雜度,因此這里使用?JSON
?數(shù)據(jù)格式來傳遞數(shù)據(jù)。在一些對(duì)于消息包大小比較嚴(yán)格的場(chǎng)景中,數(shù)據(jù)字段可以自行按照二進(jìn)制進(jìn)行封裝解析設(shè)計(jì)。此外,需要注意的是,即使使用?JSON
?數(shù)據(jù)格式,其中的?Act
?字段往往定義常量來實(shí)現(xiàn),大部分場(chǎng)景中使用?uint8
?類型即可,以減小消息包大小,這里偷一下懶,直接使用字符串,以便演示。
package types
type Msg struct {
Act string // 操作
Data string // 數(shù)據(jù)
}
funcs/funcs.go
?自定義數(shù)據(jù)格式的發(fā)送/獲取定義,便于數(shù)據(jù)結(jié)構(gòu)編碼/解析。
package funcs
import (
"encoding/json"
"fmt"
"github.com/gogf/gf/v2/net/gtcp"
"github.com/gogf/gf/.example/net/gtcp/pkg_operations/common/types"
)
// 自定義格式發(fā)送消息包
func SendPkg(conn *gtcp.Conn, act string, data...string) error {
s := ""
if len(data) > 0 {
s = data[0]
}
msg, err := json.Marshal(types.Msg{
Act : act,
Data : s,
})
if err != nil {
panic(err)
}
return conn.SendPkg(msg)
}
// 自定義格式接收消息包
func RecvPkg(conn *gtcp.Conn) (msg *types.Msg, err error) {
if data, err := conn.RecvPkg(); err != nil {
return nil, err
} else {
msg = &types.Msg{}
err = json.Unmarshal(data, msg)
if err != nil {
return nil, fmt.Errorf("invalid package structure: %s", err.Error())
}
return msg, err
}
}
gtcp_common_server.go
?通信服務(wù)端。在該示例中,服務(wù)端并不主動(dòng)斷開連接,而是在10秒后向客戶端發(fā)送?doexit
?消息,通知客戶端主動(dòng)斷開連接,以結(jié)束示例。
package main
import (
"github.com/gogf/gf/v2/net/gtcp"
"github.com/gogf/gf/v2/os/glog"
"github.com/gogf/gf/v2/os/gtimer"
"github.com/gogf/gf/.example/net/gtcp/pkg_operations/common/funcs"
"github.com/gogf/gf/.example/net/gtcp/pkg_operations/common/types"
"time"
)
func main() {
gtcp.NewServer("127.0.0.1:8999", func(conn *gtcp.Conn) {
defer conn.Close()
// 測(cè)試消息, 10秒后讓客戶端主動(dòng)退出
gtimer.SetTimeout(10*time.Second, func() {
funcs.SendPkg(conn, "doexit")
})
for {
msg, err := funcs.RecvPkg(conn)
if err != nil {
if err.Error() == "EOF" {
glog.Println("client closed")
}
break
}
switch msg.Act {
case "hello": onClientHello(conn, msg)
case "heartbeat": onClientHeartBeat(conn, msg)
default:
glog.Errorf("invalid message: %v", msg)
break
}
}
}).Run()
}
func onClientHello(conn *gtcp.Conn, msg *types.Msg) {
glog.Printf("hello message from [%s]: %s", conn.RemoteAddr().String(), msg.Data)
funcs.SendPkg(conn, msg.Act, "Nice to meet you!")
}
func onClientHeartBeat(conn *gtcp.Conn, msg *types.Msg) {
glog.Printf("heartbeat from [%s]", conn.RemoteAddr().String())
}
gtcp_common_client.go
?通信客戶端,可以看到代碼結(jié)構(gòu)和服務(wù)端差不多,數(shù)據(jù)獲取獨(dú)立處于?for
?循環(huán)中,每個(gè)業(yè)務(wù)邏輯發(fā)送消息包時(shí)直接使用?SendPkg
?方法進(jìn)行發(fā)送。
心跳消息常用?gtimer
?定時(shí)器實(shí)現(xiàn),在該示例中,客戶端每隔1秒主動(dòng)向服務(wù)端發(fā)送心跳消息,在3秒后向服務(wù)端發(fā)送?hello
?測(cè)試消息。這些都是由?gtimer
?定時(shí)器實(shí)現(xiàn)的,定時(shí)器在?TCP
?通信中比較常見。
客戶端連接10秒后,服務(wù)端會(huì)給客戶端發(fā)送?doexit
?消息,客戶端收到該消息后便主動(dòng)斷開連接,長(zhǎng)連接結(jié)束。
package main
import (
"github.com/gogf/gf/v2/net/gtcp"
"github.com/gogf/gf/v2/os/glog"
"github.com/gogf/gf/v2/os/gtimer"
"github.com/gogf/gf/.example/net/gtcp/pkg_operations/common/funcs"
"github.com/gogf/gf/.example/net/gtcp/pkg_operations/common/types"
"time"
)
func main() {
conn, err := gtcp.NewConn("127.0.0.1:8999")
if err != nil {
panic(err)
}
defer conn.Close()
// 心跳消息
gtimer.SetInterval(time.Second, func() {
if err := funcs.SendPkg(conn, "heartbeat"); err != nil {
panic(err)
}
})
// 測(cè)試消息, 3秒后向服務(wù)端發(fā)送hello消息
gtimer.SetTimeout(3*time.Second, func() {
if err := funcs.SendPkg(conn, "hello", "My name's John!"); err != nil {
panic(err)
}
})
for {
msg, err := funcs.RecvPkg(conn)
if err != nil {
if err.Error() == "EOF" {
glog.Println("server closed")
}
break
}
switch msg.Act {
case "hello": onServerHello(conn, msg)
case "doexit": onServerDoExit(conn, msg)
case "heartbeat": onServerHeartBeat(conn, msg)
default:
glog.Errorf("invalid message: %v", msg)
break
}
}
}
func onServerHello(conn *gtcp.Conn, msg *types.Msg) {
glog.Printf("hello response message from [%s]: %s", conn.RemoteAddr().String(), msg.Data)
}
func onServerHeartBeat(conn *gtcp.Conn, msg *types.Msg) {
glog.Printf("heartbeat from [%s]", conn.RemoteAddr().String())
}
func onServerDoExit(conn *gtcp.Conn, msg *types.Msg) {
glog.Printf("exit command from [%s]", conn.RemoteAddr().String())
conn.Close()
}
服務(wù)端輸出結(jié)果
2019-05-03 14:59:13.732 heartbeat from [127.0.0.1:51220]
2019-05-03 14:59:14.732 heartbeat from [127.0.0.1:51220]
2019-05-03 14:59:15.733 heartbeat from [127.0.0.1:51220]
2019-05-03 14:59:15.733 hello message from [127.0.0.1:51220]: My name's John!
2019-05-03 14:59:16.731 heartbeat from [127.0.0.1:51220]
2019-05-03 14:59:17.733 heartbeat from [127.0.0.1:51220]
2019-05-03 14:59:18.731 heartbeat from [127.0.0.1:51220]
2019-05-03 14:59:19.730 heartbeat from [127.0.0.1:51220]
2019-05-03 14:59:20.732 heartbeat from [127.0.0.1:51220]
2019-05-03 14:59:21.732 heartbeat from [127.0.0.1:51220]
2019-05-03 14:59:22.698 client closed
客戶端輸出結(jié)果
2019-05-03 14:59:15.733 hello response message from [127.0.0.1:8999]: Nice to meet you!
2019-05-03 14:59:22.698 exit command from [127.0.0.1:8999]
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: