English | 中文
tnet 是一个提供了多种模式以满足不同业务需求的网络库,它解决了以下业务诉求:
- 支持更多的连接数(百万级别)
- 更高的性能(QPS, 时延)
- 更少的内存(相较于 go/net 只需要 ~10% 的内存)
- 易用性(和 go/net 提供的接口保持一致)
已支持的特性:
- TCP, UDP
- IPv4, IPv6
- 提供阻塞和非阻塞读写 API
- 提供零拷贝读写 API
- 提供批量收发包能力 (readv/writev)
- 支持服务端和客户端开发
- 支持 Linux / Mac OS
- 支持 TLS
- 支持 WebSocket
对于 tnet
网络库本身而言,它提供了两类使用方法:
- 用户 handler 不在 poller 协程中
- 用户 handler 在 poller 协程中
如下图所示:
tnet
默认提供第一类使用方法,第二类使用方法通过 tnet.WithNonBlocking(true)
来打开(见 examples
文件夹下的具体例子)
在这两类使用方法基础之上,用户在 handler 中通过选择是否使用业务协程池来进一步分化出更细粒度的模式,具体而言,
- 在第一类中不使用业务协程池对应“类 Go/net 模式”,特点:
协程数 = poller 个数 + 活跃连接数,适用于网络 IO 与 CPU 处理均衡的业务场景,缺点是不支持连接多路复用,同一个连接上无法支持业务的并发处理(IO 处理部分无法并发)
对于第二类 handler 在 poller 协程中的场景,handler 函数通常可以划分为以下两种模式:
- IO 处理和业务分离
- IO 处理和业务合并
- 在第二类中使用业务协程池对应“IO 处理与业务分离模式”,特点:
协程数 = poller 个数 + 数据包的并发处理数,适用于 CPU 密集型场景
- 在第二类中不使用业务协程池对应“IO 处理与业务合并模式”,特点:
协程数 = poller 个数,但是场景苛刻,经典的使用场景为网关场景,这种场景下大部分逻辑只是在做请求的转发,每个请求处理的时间耗时很低,不会出现阻塞,适用于此模式
tnet.WithTCPKeepAlive
设置了连接探活的时间间隔,默认值为 15s,设置为 0 时可以关闭连接探活
// WithTCPKeepAlive sets the tcp keep alive interval.
func WithTCPKeepAlive(keepAlive time.Duration) Option {
return Option{func(op *options) {
op.tcpKeepAlive = keepAlive
}}
}
tnet.WithTCPIdleTimeout
设置了连接的空闲超时时间,空闲时间超过给定值时会自动断开连接
// WithTCPIdleTimeout sets the idle timeout to close tcp connection.
func WithTCPIdleTimeout(idleTimeout time.Duration) Option {
return Option{func(op *options) {
op.tcpIdleTimeout = idleTimeout
}}
}
tnet.WithOnTCPOpened
可以设置 TCP 连接刚建立时需要进行的操作
// WithOnTCPOpened registers the OnTCPOpened method that is fired when connection is established.
func WithOnTCPOpened(onTCPOpened OnTCPOpened) Option {
return Option{func(op *options) {
op.onTCPOpened = onTCPOpened
}}
}
tnet.WithOnTCPClosed
可以设置 TCP 连接断开时需要进行的操作
// WithOnTCPClosed registers the OnTCPClosed method that is fired when tcp connection is closed.
func WithOnTCPClosed(onTCPClosed OnTCPClosed) Option {
return Option{func(op *options) {
op.onTCPClosed = onTCPClosed
}}
}
tnet.WithTCPFlushWrite(true)
使用户可以直接在当前业务协程中及时完成发包:
// WithTCPFlushWrite sets whether use flush write for TCP
// connection or not. Default is notify.
func WithTCPFlushWrite(flush bool) Option {
return Option{func(op *options) {
op.flushWrite = flush
}}
}
以分离模式为例,启用了 TCPFlushWrite 后的流程图如下所示:
tnet.WithOnUDPClosed
可以设置 UDP 关闭时所需要进行的操作
// WithOnUDPClosed registers the OnUDPClosed method that is fired when udp connection is closed.
func WithOnUDPClosed(onUDPClosed OnUDPClosed) Option {
return Option{func(op *options) {
op.onUDPClosed = onUDPClosed
}}
}
tnet.WithMaxUDPPacketSize
设置了 UDP 包的最大长度
// WithMaxUDPPacketSize sets maximal UDP packet size when receiving UDP packets.
func WithMaxUDPPacketSize(size int) Option {
return Option{func(op *options) {
op.maxUDPPacketSize = size
}}
}
tnet.WithNonBlocking
可以设置阻塞/非阻塞模式,也是控制 IO 处理是否在 Poller 协程内的一个选项,默认为阻塞模式,IO 处理不在 Poller 协程内
// WithNonBlocking set conn/packconn to nonblocking mode
func WithNonBlocking(nonblock bool) Option {
return Option{func(op *options) {
op.nonblocking = nonblock
}}
}
tnet.Conn
接口在 net.Conn
基础之上进行了扩展
- 包含的四个零拷贝 API 如下:
// Peek returns the next n bytes without advancing the reader. It waits until it has
// read at least n bytes or error occurs such as connection closed or read timeout.
// The bytes stop being valid at the next ReadN or Release call.
// Zero-Copy API.
Peek(n int) ([]byte, error)
// Next returns the next n bytes with advancing the reader, It waits until it has
// read at least n bytes or error occurs such as connection closed or read timeout.
// The bytes stop being valid at the next ReadN or Release call.
// Zero-Copy API.
Next(n int) ([]byte, error)
// Skip the next n bytes and advance the reader. It waits until the underlayer has at
// least n bytes or error occurs such as connection closed or read timeout.
// Zero-Copy API.
Skip(n int) error
// Release releases underlayer buffer when using Peek() and Skip() Zero-Copy APIs.
Release()
- Peek:读取给定数量的字节,但是不移动下层 Linked Buffer 的 读指针,返回的 byte slice 直接引用自 Linked Buffer,此时要求这部分数据在使用完之前不能被释放掉
- Skip:移动下层 Linked Buffer 的 读指针,使其跳过给定数量的字节,通常与 Peek 合用
- Next:等价于先调用 Peek,然后调用 Skip,返回的 byte slice 会在 Release 的调用后失效
- Release:释放掉已经读过的部分,通常在使用完 byte slice 后使用,在调用安全 API Read/ReadN 时会自动调用 Release 来释放已读空间
tnet.Conn
提供了Writev
,用来依次写出多个数据块,比如包头和包体,不需要手动进行数据包的拼接,使用例子见examples/tcp/classical/main.go
// Writev provides multiple data slice write in order.
Writev(p ...[]byte) (int, error)
tnet.Conn
还提供了感知连接状态的方法:IsActive
// IsActive checks whether the connection is active or not.
IsActive() bool
tnet.Conn
提供了SetMetaData/GetMetadata
用于存储/读取用户在连接上的私有数据:
// SetMetaData sets meta data. Through this method, users can bind some custom data to a connection.
SetMetaData(m interface{})
// GetMetaData gets meta data.
GetMetaData() interface{}
- tRPC-Go
tRPC-Go 已经集成 tnet