GO语言实现TCP服务器的示例代码

 更新时间:2023年03月24日 10:56:02   作者:CSGOPHER  
这篇文章主要为大家详细介绍了如何通过GO语言实现TCP服务器,文中的示例代码讲解详细,对我们深入了解Go语言有一定的帮助,需要的可以参考一下

interface/tcp/Handler.go

type Handler interface {
   Handle(ctx context.Context, conn net.Conn)
   Close() error
}

Handler:业务逻辑的处理接口

Handle(ctx context.Context, conn net.Conn) 处理连接

tcp/server.go

type Config struct {
    Address string
}

func ListenAndServeWithSignal(cfg *Config, handler tcp.Handler) error {
    closeChan := make(chan struct{})
    listen, err := net.Listen("tcp", cfg.Address)
    if err != nil {
       return err
   }
    logger.Info("start listen")
    ListenAndServe(listen, handler, closeChan)
    return nil
}

func ListenAndServe(listener net.Listener,
                    handler tcp.Handler,
                    closeChan <-chan struct{}) {
    ctx := context.Background()
    var waitDone sync.WaitGroup
    for true {
        conn, err := listener.Accept()
        if err != nil {
            break
        }
        logger.Info("accept link")
        waitDone.Add(1)
        go func() {
            defer func() {
                waitDone.Done()
            }()
            handler.Handler(ctx, conn)
        }()
    }
    waitDone.Wait()
}

Config:启动tcp服务器的配置

Address:监听地址

ListenAndServe:ctx是上下文,可以传递一些参数。死循环中接收到新连接时,让一个协程去处理连接

如果listener.Accept()出错了就会break跳出来,这时候需要等待已经服务的客户端退出。使用WaitGroup等待客服端退出

func ListenAndServe(listener net.Listener,
                    handler tcp.Handler,
                    closeChan <-chan struct{}) {

    go func() {
       <-closeChan
       logger.Info("shutting down...")
       _ = listener.Close()
       _ = handler.Close()
   }()

    defer func() {
       _ = listener.Close()
       _ = handler.Close()
   }()

    ......
}

listener和handler在退出的时候需要关掉。如果用户直接kill掉了程序,我们也需要关掉listener和handler,这时候要使用closeChan,一旦接收到关闭信号,就执行关闭逻辑

func ListenAndServeWithSignal(cfg *Config, handler tcp.Handler) error {

    closeChan := make(chan struct{})
    sigCh := make(chan os.Signal)
    signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
    go func() {
       sig := <-sigCh
       switch sig {
          case syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
          closeChan <- struct{}{}
      }
   }()
    listen, err := net.Listen("tcp", cfg.Address)
    if err != nil {
       return err
   }
    logger.Info("start listen")
    ListenAndServe(listen, handler, closeChan)
    return nil
}

当系统对程序发送信号时,sigCh会接收到信号

tcp/echo.go

type EchoHandler struct {
   activeConn sync.Map
   closing    atomic.Boolean
}

EchoHandler:

  • activeConn:记录连接
  • closing:是否正在关闭,有并发竞争,使用atomic.Boolean
type EchoClient struct {
   Conn    net.Conn
   Waiting wait.Wait
}

func (c *EchoClient) Close() error {
	c.Waiting.WaitWithTimeout(10 * time.Second)
	_ = c.Conn.Close()
	return nil
}

EchoClient:一个客户端就是一个连接。Close方法关闭客户端连接,超时时间设置为10s

func MakeHandler() *EchoHandler {
	return &EchoHandler{}
}

func (h *EchoHandler) Handle(ctx context.Context, conn net.Conn) {
   // 连接正在关闭,不接收新连接
   if h.closing.Get() {
      _ = conn.Close()
   }

   client := &EchoClient{
      Conn: conn,
   }
   h.activeConn.Store(client, struct{}{})

   reader := bufio.NewReader(conn)
   for {
      msg, err := reader.ReadString('\n')
      if err != nil {
         if err == io.EOF {
            logger.Info("connection close")
            h.activeConn.Delete(client)
         } else {
            logger.Warn(err)
         }
         return
      }
      // 正在处理业务,不要关掉
      client.Waiting.Add(1)
      // 将数据原封不动写回去,测试
      b := []byte(msg)
      _, _ = conn.Write(b)
      client.Waiting.Done()
   }
}

func (h *EchoHandler) Close() error {
   logger.Info("handler shutting down...")
   h.closing.Set(true)
   h.activeConn.Range(func(key interface{}, val interface{}) bool {
      client := key.(*EchoClient)
      _ = client.Close()
      return true
   })
   return nil
}

MakeEchoHandler:创建EchoHandler

Handle:处理客户端的连接。

1.连接正在关闭时,不接收新连接

2.存储新连接,value用空结构体

3.使用缓存区接收用户发来的数据,使用\n作为结束的标志

Close:将所有客户端连接关掉

main.go

const configFile string = "redis.conf"

var defaultProperties = &config.ServerProperties{
   Bind: "0.0.0.0",
   Port: 6379,
}

func fileExists(filename string) bool {
   info, err := os.Stat(filename)
   return err == nil && !info.IsDir()
}

func main() {
   logger.Setup(&logger.Settings{
      Path:       "logs",
      Name:       "godis",
      Ext:        "log",
      TimeFormat: "2022-02-02",
   })

   if fileExists(configFile) {
      config.SetupConfig(configFile)
   } else {
      config.Properties = defaultProperties
   }

   err := tcp.ListenAndServeWithSignal(
      &tcp.Config{
         Address: fmt.Sprintf("%s:%d",
            config.Properties.Bind,
            config.Properties.Port),
      },
      EchoHandler.MakeHandler())
   if err != nil {
      logger.Error(err)
   }
}

到此这篇关于GO语言实现TCP服务器的示例代码的文章就介绍到这了,更多相关GO TCP服务器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Go语言设计模式之实现观察者模式解决代码臃肿

    Go语言设计模式之实现观察者模式解决代码臃肿

    今天学习一下用 Go 实现观察者模式,观察者模式主要是用来实现事件驱动编程。事件驱动编程的应用还是挺广的,除了我们都知道的能够用来解耦:用户修改密码后,给用户发短信进行风险提示之类的典型场景,在微服务架构实现最终一致性、实现事件源A + ES
    2022-08-08
  • Go语言命令行参数及cobra使用方法

    Go语言命令行参数及cobra使用方法

    Cobra是关于golang的一个命令行解析库,用它能够快速创建功能强大的 cli应用程序和命令行工具,本文主要介绍了Go语言命令行参数及cobra使用方法,感兴趣的可以了解一下
    2024-01-01
  • gin使用自定义结构绑定表单数据的示例代码

    gin使用自定义结构绑定表单数据的示例代码

    这篇文章主要介绍了gin使用自定义结构绑定表单数据的示例代码,代码简单易懂,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-11-11
  • go如何优雅关闭Graceful Shutdown服务

    go如何优雅关闭Graceful Shutdown服务

    这篇文章主要为大家介绍了go优雅关闭Graceful Shutdown服务详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-05-05
  • Go实现MD5加密的三种方法小结

    Go实现MD5加密的三种方法小结

    本文主要介绍了Go实现MD5加密的三种方法小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-03-03
  • Go1.20 arena新特性示例详解

    Go1.20 arena新特性示例详解

    这篇文章主要为大家介绍了Go1.20 arena新特性示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-11-11
  • 详解如何利用Golang泛型提高编码效率

    详解如何利用Golang泛型提高编码效率

    Golang的泛型已经出来有一段时间了,大家应该或多或少对它有所了解。虽然Golang的泛型在功能上确实比较简单,而且确实可能会增加代码的复杂度,过度使用可能还会降低代码可读性。本文就来介绍一下Golang泛型的相关知识吧
    2023-04-04
  • 一文初探 Goroutine 与 channel基本用法

    一文初探 Goroutine 与 channel基本用法

    这篇文章主要为大家介绍了一文初探 Goroutine 与 channel基本用法详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-02-02
  • Go语言中的字符串处理方法示例详解

    Go语言中的字符串处理方法示例详解

    Go语言的字符串是使用UTF-8编码的。UTF-8是Unicode的实现方式之一。这篇文章主要介绍了Go语言中的字符串处理方法,需要的朋友可以参考下
    2018-10-10
  • Go语言实现热更新具体步骤

    Go语言实现热更新具体步骤

    这篇文章主要为大家介绍了Go语言实现热更新具体步骤详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2024-01-01

最新评论