go语言实现tcp

tcp server & tcp client实现

go语言实现tcp_第1张图片

首先就是服务端会监听某个端口号,然后不断轮训是否有客户端连接进来,一旦有客户端连接进来,就委托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快捷键,来选择要实现的接口。
go语言实现tcp_第2张图片

之后就会自动生成接口方法实现
go语言实现tcp_第3张图片

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)
    }
}

go语言实现tcp_第4张图片

你可能感兴趣的:(Go语言,tcp/ip,网络,网络协议)