tcp server & tcp client实现
首先就是服务端会监听某个端口号,然后不断轮训是否有客户端连接进来,一旦有客户端连接进来,就委托Handler处理这个连接的所有请求。
服务端的代码实现:
package tcp
import (
"context"
"gopro/interface/tcp"
"gopro/lib/logger"
"net"
"os"
"os/signal"
"sync"
"syscall"
)
type Config struct {
Address string
}
func ListenAndServeWithSignal(cfg *Config,
handler tcp.Handler) error {
closeChan := make(chan struct{})
sigChan := make(chan os.Signal)
signal.Notify(sigChan, syscall.SIGHUP, syscall.SIGILL, syscall.SIGQUIT, syscall.SIGTERM)
// 异步监听系统信号,监听到关闭信号,就是用closeChan 发送一个关闭信息,让另一个异步线程感知到并做资源释放
go func() {
sig := <-sigChan
switch sig {
case syscall.SIGHUP, syscall.SIGILL, syscall.SIGQUIT, syscall.SIGTERM:
logger.Warn(sig)
closeChan <- struct{}{}
}
}()
// 创建tcp连接
listen, err := net.Listen("tcp", cfg.Address)
if err != nil {
return err
}
ListenerAndServe(listen, handler, closeChan)
return nil
}
func ListenerAndServe(listener net.Listener,
handler tcp.Handler,
closeChan <-chan struct{}) {
// 监听closeChan通道, 比如调用Kill -9 或者系统杀死程序时 关闭资源
go func() {
<-closeChan
logger.Info("shutting down 关闭资源")
_ = listener.Close()
_ = handler.Close()
}()
// 关闭资源
defer func() {
logger.Info("关闭资源")
_ = listener.Close()
_ = handler.Close()
}()
wg := sync.WaitGroup{}
ctx := context.Background()
for {
// 循环监听多个客户端的存在.支持同时处理多个client的连接
conn, err := listener.Accept()
if err != nil {
break
}
logger.Info("accept link")
wg.Add(1)
go func() {
// 为了防止在Handler中发生panic, 所以在defer中进行wait的减少
defer func() {
wg.Done()
}()
handler.Handle(ctx, conn)
}()
}
// 因为Server可能会服务多个client的连接,所以某个连接挂了的时候需要等待一下,等其他几个连接处理完
wg.Wait()
}
tcp handler实现
当我们手写了一个结构体的时候,需要实现一个接口,我们可以通过Ctrl+i快捷键,来选择要实现的接口。
之后就会自动生成接口方法实现
Handler代码实现
package tcp
import (
"bufio"
"context"
"gopro/lib/logger"
"gopro/lib/sync/atomic"
"gopro/lib/sync/wait"
"io"
"net"
"sync"
"time"
)
type EchoClient struct {
Conn net.Conn
Waiting wait.Wait
}
func (e *EchoClient) Close() error {
// 等10s在关闭连接
e.Waiting.WaitWithTimeout(10 * time.Second)
_ = e.Conn.Close()
return nil
}
type EchoHandler struct {
// map 当set用,用于存储client
activeConn sync.Map
// handler的状态
closing atomic.Boolean
}
func MakeHandler() *EchoHandler {
return &EchoHandler{}
}
func (handler *EchoHandler) Handle(ctx context.Context, conn net.Conn) {
if handler.closing.Get() {
_ = conn.Close()
return
}
logger.Info("create EchoClient")
client := &EchoClient{Conn: conn}
// 存储客户端
handler.activeConn.Store(client, struct{}{})
// 创建一个缓存buffer,读取内容
reader := bufio.NewReader(conn)
// 循环处理客户端的请求
for {
msg, err := reader.ReadString('\n')
if err != nil {
if err == io.EOF {
logger.Info("connection close")
// 代表读取到了终点了
handler.activeConn.Delete(client)
} else {
logger.Warn(err)
}
return
}
// 该客户端增加一个干活的请求
client.Waiting.Add(1)
// 处理连接
bytes := []byte(msg)
_, _ = client.Conn.Write(bytes)
// 该客户端完成一个干活的请求
client.Waiting.Done()
}
}
func (handler *EchoHandler) Close() error {
// 设置关闭状态
handler.closing.Set(true)
// 关闭handler中的连接资源
handler.activeConn.Range(func(key, value interface{}) bool {
// 因为key是空接口,需要强转类型
client := key.(*EchoClient)
_ = client.Close()
// 代表继续处理
return true
})
return nil
}
main实现
package main
import (
"fmt"
"gopro/config"
"gopro/lib/logger"
"gopro/tcp"
"os"
)
const configFile string = "redis.conf"
var defaultProperties = &config.ServerProperties{
Bind: "0.0.0.0",
Port: 6379,
}
func fileExist(filename string) bool {
stat, err := os.Stat(filename)
return err == nil && !stat.IsDir()
}
func main() {
logger.Setup(&logger.Settings{
Path: "logs",
Name: "gopro",
Ext: "log",
TimeFormat: "2022-10-01",
})
if fileExist(configFile) {
config.SetupConfig(configFile)
} else {
config.Properties = defaultProperties
}
err := tcp.ListenAndServeWithSignal(&tcp.Config{
Address: fmt.Sprintf("%s:%d", config.Properties.Bind, config.Properties.Port),
},
tcp.MakeHandler(),
)
if err != nil {
logger.Error(err)
}
}