Go 编程:tcpserver & graceful shutdown

转载请使用原文连接: https://www.gitdig.com/go-tcpserver-graceful-shutdown/

工做须要快速写了个tcpserver的框架,有效代码差很少 100 行左右,写篇文章分享下实现思路, 顺便解释一下如何实现相似网络服务的Graceful Shutdown功能。git

首先,什么是框架?简单总结一下就是将变化交给用户,将不变交给本身,即面向接口编程。因此要实现一个简单的框架,第一步就是明确接口,对全部可能的变化进行接口级别的抽象。画一张简图说明下:github

图片描述

tcpserver 框架的实现

tcpserver框架程序而言,变化的部分很是有限:编程

  • 监听类型
  • 监听地址
  • 客户端链接处理

那么,就能够将这三部分做为tcpserver的可选参数传递进去。因此,在还没有实现以前, README 文档中的 Quick Start 部分能够写起来了。网络

import "github.com/x-mod/tcpserver"

srv := tcpserver.NewServer(
    tcpserver.Network("tcp"),
    tcpserver.Address(":8080"),
    tcpserver.Handler(MyHandler),
)

从这个简单的建立函数可知,tcpserver.Handler选项传入的是一个函数对象。既然是一个函数对象,就须要对其类型进行定义。一般而言,在函数类型定义时必需要注意的是参数问题,即参数不能过于复杂,同时必要的参数一个不能少。我这样定义接口函数:框架

type ConnectionHandler func(context.Context, net.Conn) error

函数签名中最重要的参数是 net.Conn 参数,不论使用方如何实现,这个参数是必须的。因此做为独立的参数,直接定义。另一个参数,上下文参数context.Context。上下文的特色与用途,无需多说了。经过它,能够传递各类变量信息与上层的中断信号。这样也就解决了参数定义不足的问题。tcp

如今,再看看 tcpserver.NewServer 还缺乏什么?显然就是启动函数与关闭函数了。参考 http.Server 的处理方式。在使用上,能够这样定义操做函数:ide

func (srv *Server) Serve(ctx context.Context) error {
    //TODO
    return nil
}

func (srv *Server) Close() {
    //TODO
}

具体实现,能够直接参考个人项目代码tcpserver函数

接下来简单说下,相似服务端程序如何实现Graceful Shutdown功能。ui

Graceful Shutdown 功能的实现

Graceful Shutdown的概念早就有了,只是在Go语言早期的版本中没有受到重视。好像是context包被移入系统包开始,Graceful Shutdown就开始被重视起来。为何是从context包被移入系统包开始受到重视,还真是有必定联系。spa

什么是Graceful Shutdown,不妨看看官方在net/http包中的说明:

Shutdown gracefully shuts down the server without interrupting any active connections. Shutdown works by first closing all open listeners, then closing all idle connections, and then waiting indefinitely for connections to return to idle and then shut down. If the provided context expires before the shutdown is complete, Shutdown returns the context's error, otherwise it returns any error returned from closing the Server's underlying Listener(s).

Graceful Shutdown的具体操做步骤以下:

  • 首先关闭全部监听接口
  • 再关闭全部闲置链接
  • 等待全部运行链接处理完成
  • 上下文超时信号的处理

这样关闭服务程序的好处很明显,固然也有弊端:万一个别链接没法完成,程序可能就会一直等待。因此客户端处理逻辑上须要考虑这种异常状况,经过超时机制进行规避。

对于tcpserver而言,一样须要提供Graceful Shutdown功能。看看怎么来实现:

经过上节中 Server.Close 函数来触发 Graceful Shutdown。收到触发信号后,首先关闭监听,再等待全部客户端链接完成。具体程序实现以下:

//Close tcpserver waiting all connections finished
func (srv *Server) Close() {
    //触发关闭信号
    close(srv.closed)
    //等待客户端连完成
    srv.wgroup.Wait()
}

//Serve tcpserver serving
func (srv *Server) Serve(ctx context.Context) error {
    ln, err := net.Listen(srv.network, srv.address)//开启监听
    ...
    for {
        select {
        case <-ctx.Done():
            return ctx.Err()
        case <-srv.closed: //收到关闭信号
            log.Println("tcpserver is closing ...")
            return ln.Close()//关闭监听
        ...
    }
    ...
}

贴一小段代码方便你们阅读,具体代码请直接参考实现: server.go。为何context很重要,由于除了本身定义chan做为信号触发机制之外,还能够经过context的超时或者取消机制进行信号的传递,具体实现再也不赘述了。

相关文章
相关标签/搜索