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的超時或者取消機制進行信號的傳遞,具體實現再也不贅述了。

相關文章
相關標籤/搜索