转载请使用原文连接: https://www.gitdig.com/go-tcpserver-graceful-shutdown/
工做须要快速写了个tcpserver
的框架,有效代码差很少 100 行左右,写篇文章分享下实现思路, 顺便解释一下如何实现相似网络服务的Graceful Shutdown
功能。git
首先,什么是框架?简单总结一下就是将变化交给用户,将不变交给本身,即面向接口编程。因此要实现一个简单的框架,第一步就是明确接口,对全部可能的变化进行接口级别的抽象。画一张简图说明下:github
就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
的概念早就有了,只是在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
的超时或者取消机制进行信号的传递,具体实现再也不赘述了。