Skip to main content
Version: 2.8.x(Latest)

Advanced Development

For short connections, each data send and receive operation closes the connection, resulting in relatively simple connection handling logic but lower communication efficiency. In most TCP communication scenarios, long connection operations are often used, with an asynchronous full-duplex TCP communication mode, meaning all communication is entirely asynchronous. In such scenarios, the gtcp.Conn link object may simultaneously be in multiple read and write operations (data read and write operations of the gtcp.Conn object are concurrency-safe), hence SendRecv operations will logically fail. Because after sending data in the same logical operation, immediately retrieving data might result in receiving results from other write operations.

Both the server and client need to encapsulate the use of the Recv* method to obtain data in an independent asynchronous loop and handle data through switch...case... (fully responsible for reading data in one goroutine), forwarding business processing based on the request data.

tip

That is, Send*/Recv* methods are concurrency-safe, but data should be sent in one go. Because asynchronous concurrent writing is supported, the gtcp.Conn object is implemented without any buffering.

Example

We'll illustrate how to implement asynchronous full-duplex communication in a program with a complete example, found at: https://github.com/gogf/gf/v2/tree/master/.example/net/gtcp/pkg_operations/common

  1. types/types.go

Define the data format for communication so we can use SendPkg/RecvPkg methods for communication.

For simplification of test code complexity, we'll use the JSON data format to pass data here. In some scenarios where the message package size is strictly considered, the Data field can be encapsulated and parsed in binary as needed. Also, note that even using JSON data format, the Act field often defines constants for implementation, and uint8 type suffices for most cases to reduce message package size. Here, we use strings for demonstration purposes.

package types


type Msg struct {
Act string // Operation
Data string // Data
}
  1. funcs/funcs.go

Custom definitions for sending/receiving data in defined formats, facilitating data structure encoding/parsing.

package funcs


import (
"encoding/json"
"fmt"
"github.com/gogf/gf/v2/net/gtcp"
"github.com/gogf/gf/.example/net/gtcp/pkg_operations/common/types"
)


// Custom format message package sending
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)
}


// Custom format message package receiving
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
}
}
  1. gtcp_common_server.go

Communication server. In this example, the server does not actively disconnect, but sends a doexit message to the client after 10 seconds, notifying the client to disconnect to end the example.

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()
// Test message, let the client exit voluntarily after 10 seconds
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())
}
  1. gtcp_common_client.go

Communication client. As you can see, the code structure is similar to the server, with data retrieval independently residing within a for loop. Each business logic sends message packages directly using the SendPkg method.

Heartbeat messages are typically implemented with a gtimer timer. In this example, the client sends heartbeat messages to the server every 1 second and sends a hello test message to the server after 3 seconds. These are implemented using the gtimer timer, which is common in TCP communication.

After 10 seconds of client connection, the server sends a doexit message to the client, prompting the client to disconnect, ending the long connection.

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()
// Heartbeat message
gtimer.SetInterval(time.Second, func() {
if err := funcs.SendPkg(conn, "heartbeat"); err != nil {
panic(err)
}
})
// Test message, send hello message to server after 3 seconds
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()
}
  1. After execution

    • Server output result

        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
    • Client output result

        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]