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.
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
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
}
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
}
}
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())
}
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()
}
-
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]
-