开发进阶

    无论服务端还是客户端,都需要在独立的异步循环中封装使用Recv*方法获取数据并通过switch...case...处理数据(仅在一个goroutine中全权负责读取数据),根据请求数据进行业务处理的转发。

    也就是说,Send*/Recv*方法是并发安全的,但是发送数据时要一次性发送。由于支持异步并发写,gtcp.Conn对象不带任何缓冲实现。

    使用示例

    我们通过一个完成的示例来说明一下如何在程序中实现异步全双工通信,完成示例代码位于:https://github.com/gogf/gf/v2/tree/master/.example/net/gtcp/pkg_operations/common

    1. types/types.go

      定义通信的数据格式,随后我们可以使用SendPkg/RecvPkg方法来通信。

      考虑到简化测试代码复杂度,因此这里使用JSON数据格式来传递数据。在一些对于消息包大小比较严格的场景中,数据字段可以自行按照二进制进行封装解析设计。此外,需要注意的是,即使使用JSON数据格式,其中的Act字段往往定义常量来实现,大部分场景中使用uint8类型即可,以减小消息包大小,这里偷一下懒,直接使用字符串,以便演示。

    1. funcs/funcs.go

      自定义数据格式的发送/获取定义,便于数据结构编码/解析。

      ``` package funcs

    1. import (
    2. "encoding/json"
    3. "fmt"
    4. "github.com/gogf/gf/v2/net/gtcp"
    5. "github.com/gogf/gf/.example/net/gtcp/pkg_operations/common/types"
    6. )
    7. // 自定义格式发送消息包
    8. func SendPkg(conn *gtcp.Conn, act string, data...string) error {
    9. s := ""
    10. if len(data) > 0 {
    11. s = data[0]
    12. }
    13. msg, err := json.Marshal(types.Msg{
    14. Act : act,
    15. Data : s,
    16. })
    17. if err != nil {
    18. panic(err)
    19. }
    20. return conn.SendPkg(msg)
    21. }
    22. // 自定义格式接收消息包
    23. func RecvPkg(conn *gtcp.Conn) (msg *types.Msg, err error) {
    24. return nil, err
    25. } else {
    26. msg = &types.Msg{}
    27. err = json.Unmarshal(data, msg)
    28. if err != nil {
    29. }
    30. return msg, err
    31. }
    32. }
    33. ```
    1. gtcp_common_server.go

      通信服务端。在该示例中,服务端并不主动断开连接,而是在10秒后向客户端发送doexit消息,通知客户端主动断开连接,以结束示例。

      ``` package main

    1. 通信客户端,可以看到代码结构和服务端差不多,数据获取独立处于for循环中,每个业务逻辑发送消息包时直接使用SendPkg方法进行发送。

      心跳消息常用gtimer定时器实现,在该示例中,客户端每隔1秒主动向服务端发送心跳消息,在3秒后向服务端发送hello测试消息。这些都是由gtimer定时器实现的,定时器在TCP通信中比较常见。

      客户端连接10秒后,服务端会给客户端发送doexit消息,客户端收到该消息后便主动断开连接,长连接结束。

      ``` package main

    1. import (
    2. "github.com/gogf/gf/v2/net/gtcp"
    3. "github.com/gogf/gf/v2/os/glog"
    4. "github.com/gogf/gf/v2/os/gtimer"
    5. "github.com/gogf/gf/.example/net/gtcp/pkg_operations/common/funcs"
    6. "github.com/gogf/gf/.example/net/gtcp/pkg_operations/common/types"
    7. "time"
    8. )
    9. func main() {
    10. conn, err := gtcp.NewConn("127.0.0.1:8999")
    11. if err != nil {
    12. panic(err)
    13. }
    14. defer conn.Close()
    15. // 心跳消息
    16. gtimer.SetInterval(time.Second, func() {
    17. if err := funcs.SendPkg(conn, "heartbeat"); err != nil {
    18. panic(err)
    19. })
    20. // 测试消息, 3秒后向服务端发送hello消息
    21. gtimer.SetTimeout(3*time.Second, func() {
    22. if err := funcs.SendPkg(conn, "hello", "My name's John!"); err != nil {
    23. panic(err)
    24. }
    25. })
    26. for {
    27. if err != nil {
    28. if err.Error() == "EOF" {
    29. glog.Println("server closed")
    30. }
    31. break
    32. }
    33. switch msg.Act {
    34. case "hello": onServerHello(conn, msg)
    35. case "doexit": onServerDoExit(conn, msg)
    36. case "heartbeat": onServerHeartBeat(conn, msg)
    37. default:
    38. glog.Errorf("invalid message: %v", msg)
    39. break
    40. }
    41. }
    42. }
    43. func onServerHello(conn *gtcp.Conn, msg *types.Msg) {
    44. glog.Printf("hello response message from [%s]: %s", conn.RemoteAddr().String(), msg.Data)
    45. }
    46. func onServerHeartBeat(conn *gtcp.Conn, msg *types.Msg) {
    47. glog.Printf("heartbeat from [%s]", conn.RemoteAddr().String())
    48. }
    49. func onServerDoExit(conn *gtcp.Conn, msg *types.Msg) {
    50. glog.Printf("exit command from [%s]", conn.RemoteAddr().String())
    51. conn.Close()
    52. }
    53. ```
    1. 执行后

      • 服务端输出结果

        1. 2019-05-03 14:59:22.698 exit command from [127.0.0.1:8999]