The gtcp module offers a connection pool feature, implemented by the gtcp.PoolConn object, where the connection pool’s cached fixed lifespan is 600 seconds and internally implements a reconnect mechanism during data transmission. The connection pool is particularly suitable for scenarios involving frequent short connection operations and high connection concurrency. Next, we’ll demonstrate the capabilities of the connection pool using two examples.

Usage:

  1. import "github.com/gogf/gf/v2/net/gtcp"

API Documentation:

https://pkg.go.dev/github.com/gogf/gf/v2/net/gtcp

  1. type PoolConn
  2. func NewPoolConn(addr string, timeout ...int) (*PoolConn, error)
  3. func (c *PoolConn) Close() error
  4. func (c *PoolConn) Recv(length int, retry ...Retry) ([]byte, error)
  5. func (c *PoolConn) RecvLine(retry ...Retry) ([]byte, error)
  6. func (c *PoolConn) RecvPkg(option ...PkgOption) ([]byte, error)
  7. func (c *PoolConn) RecvPkgWithTimeout(timeout time.Duration, option ...PkgOption) ([]byte, error)
  8. func (c *PoolConn) RecvWithTimeout(length int, timeout time.Duration, retry ...Retry) (data []byte, err error)
  9. func (c *PoolConn) Send(data []byte, retry ...Retry) error
  10. func (c *PoolConn) SendPkg(data []byte, option ...PkgOption) (err error)
  11. func (c *PoolConn) SendPkgWithTimeout(data []byte, timeout time.Duration, option ...PkgOption) error
  12. func (c *PoolConn) SendRecv(data []byte, receive int, retry ...Retry) ([]byte, error)
  13. func (c *PoolConn) SendRecvPkg(data []byte, option ...PkgOption) ([]byte, error)
  14. func (c *PoolConn) SendRecvPkgWithTimeout(data []byte, timeout time.Duration, option ...PkgOption) ([]byte, error)
  15. func (c *PoolConn) SendRecvWithTimeout(data []byte, receive int, timeout time.Duration, retry ...Retry) ([]byte, error)
  16. func (c *PoolConn) SendWithTimeout(data []byte, timeout time.Duration, retry ...Retry) error

Since gtcp.PoolConn inherits from gtcp.Conn, methods of gtcp.Conn can also be used.

Example 1, Basic Usage

  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. "github.com/gogf/gf/v2/net/gtcp"
  6. "github.com/gogf/gf/v2/os/glog"
  7. "github.com/gogf/gf/v2/os/gtime"
  8. )
  9. func main() {
  10. // Server
  11. go gtcp.NewServer("127.0.0.1:8999", func(conn *gtcp.Conn) {
  12. defer conn.Close()
  13. for {
  14. data, err := conn.Recv(-1)
  15. if len(data) > 0 {
  16. if err := conn.Send(append([]byte("> "), data...)); err != nil {
  17. fmt.Println(err)
  18. }
  19. }
  20. if err != nil {
  21. break
  22. }
  23. }
  24. }).Run()
  25. time.Sleep(time.Second)
  26. // Client
  27. for {
  28. if conn, err := gtcp.NewPoolConn("127.0.0.1:8999"); err == nil {
  29. if b, err := conn.SendRecv([]byte(gtime.Datetime()), -1); err == nil {
  30. fmt.Println(string(b), conn.LocalAddr(), conn.RemoteAddr())
  31. } else {
  32. fmt.Println(err)
  33. }
  34. conn.Close()
  35. } else {
  36. glog.Error(err)
  37. }
  38. time.Sleep(time.Second)
  39. }
  40. }

In this example, the Server runs asynchronously using a new goroutine, while the Client runs on the main goroutine. The Server is an echo server, while the Client sends the current time to the Server every second, after which it’s echoed back by the Server, and the connection port information of both sides is printed on the Client side.

After execution, the output is as follows:

  1. > 2018-07-11 23:29:54 127.0.0.1:55876 127.0.0.1:8999
  2. > 2018-07-11 23:29:55 127.0.0.1:55876 127.0.0.1:8999
  3. > 2018-07-11 23:29:56 127.0.0.1:55876 127.0.0.1:8999
  4. > 2018-07-11 23:29:57 127.0.0.1:55876 127.0.0.1:8999
  5. > 2018-07-11 23:29:58 127.0.0.1:55876 127.0.0.1:8999
  6. ...

You can see that the Client’s port remains unchanged, and each time you get the same gtcp.Conn object through gtcp.NewConn("127.0.0.1:8999"). Additionally, each time conn.Close() does not truly close the connection but rather returns the object to the pool for reuse.

Example 2, Connection Disconnection Scenario

This example demonstrates how to deal with invalid connection objects when the server side closes the connection.

  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. "github.com/gogf/gf/v2/net/gtcp"
  6. "github.com/gogf/gf/v2/os/glog"
  7. "github.com/gogf/gf/v2/os/gtime"
  8. )
  9. func main() {
  10. // Server
  11. go gtcp.NewServer("127.0.0.1:8999", func(conn *gtcp.Conn) {
  12. defer conn.Close()
  13. for {
  14. data, err := conn.Recv(-1)
  15. if len(data) > 0 {
  16. if err := conn.Send(append([]byte("> "), data...)); err != nil {
  17. fmt.Println(err)
  18. }
  19. }
  20. if err != nil {
  21. break
  22. }
  23. return
  24. }
  25. }).Run()
  26. time.Sleep(time.Second)
  27. // Client
  28. for {
  29. if conn, err := gtcp.NewPoolConn("127.0.0.1:8999"); err == nil {
  30. if b, err := conn.SendRecv([]byte(gtime.Datetime()), -1); err == nil {
  31. fmt.Println(string(b), conn.LocalAddr(), conn.RemoteAddr())
  32. } else {
  33. fmt.Println(err)
  34. }
  35. conn.Close()
  36. } else {
  37. glog.Error(err)
  38. }
  39. time.Sleep(time.Second)
  40. }
  41. }

After execution, the output is as follows:

  1. > 2018-07-20 12:56:15 127.0.0.1:59368 127.0.0.1:8999
  2. EOF
  3. > 2018-07-20 12:56:17 127.0.0.1:59376 127.0.0.1:8999
  4. EOF
  5. > 2018-07-20 12:56:19 127.0.0.1:59378 127.0.0.1:8999
  6. EOF
  7. ...

In this example, the Server closes the connection after processing each request. The Client, after sending the first request, due to the IO reuse feature of the connection pool, will obtain the same connection object for the next request. As the Server connection has been actively closed, the second request write succeeds (actually hasn’t been successfully sent to the Server, needing another read operation to detect the link error), but the read fails (EOF indicates the target connection is closed). Therefore, at this point, when the Client executes Close, it will destroy the connection operation object instead of further reusing it. The next time it gets a connection object through gtcp.NewPoolConn, the Client will establish a new connection with the Server for data communication. So you see, the Client’s port is constantly changing because the gtcp.Conn object is a new connection object, and the previous connection object has been destroyed.

The IO reuse of connection objects involves very subtle changes in connection states. Since point-to-point network communication itself is a complex environment, the state of connection objects may change passively at any time. Therefore, when using the gtcp connection pool feature, it is crucial to pay attention to the reconstruction mechanism of the connection object when a communication error occurs. As soon as an error occurs, immediately discard (Close) the object (gtcp.PoolConn) and rebuild (gtcp.NewPoolConn).