轉載請使用原文連接: 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
的超時或者取消機制進行信號的傳遞,具體實現再也不贅述了。