go语言实现tcp

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

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,您想了解关于使用 Go 语言实现 TCP 断线重连的方法。在 Go 中,可以使用 net 包来实现 TCP 连接和断开连接的操作。在实现 TCP 断线重连时,可以通过实现一个循环来等待连接并且处理连接中断的情况。 以下是一个简单的示例代码,用于演示如何在 Go 中实现 TCP 断线重连: ```go package main import ( "fmt" "net" "time" ) func main() { conn, err := net.Dial("tcp", "localhost:8000") if err != nil { fmt.Println("Connection error:", err) return } for { if _, err := conn.Write([]byte("Hello, world!")); err != nil { fmt.Println("Write error:", err) conn.Close() break } time.Sleep(time.Second) buffer := make([]byte, 1024) if _, err := conn.Read(buffer); err != nil { fmt.Println("Read error:", err) conn.Close() break } fmt.Println(string(buffer)) } } ``` 在这个例子中,我们使用 net.Dial 来连接本地的一个 TCP 服务器。也可以替换为实际的服务器端地址。然后,我们通过一个简单的循环来处理连接中断并且尝试重新连接。在循环中,我们使用 conn.Write 来发送数据,如果发送失败,则关闭连接来断线重连。然后我们使用 conn.Read 来读取数据,如果读取失败,则关闭连接并且重新连接。循环会一直执行,直到应用程序被结束为止。 请注意,上面的示例代码仅用于演示如何在 Go 中实现 TCP 断线重连。在实际的应用程序中,你需要警惕一些细节问题,例如最大连接失败次数等等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值