GoFrame 連接對(duì)象-通信開發(fā)進(jìn)階

2022-04-15 13:47 更新

開發(fā)進(jìn)階

針對(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()
}

  • 執(zhí)行后

服務(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]


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)