這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
簡介:本文描述了使用Go語言實現的、適應於Go語言並行存取模型的一種支援多工網路通訊協定處理架構,並提供了架構的代碼實現。作者將這種架構用於網路遊戲伺服器中的協議處理,但也可用於其他領域。
應用背景:在網路遊戲伺服器設計中,一般都會遇到協議多工情境。比如登入伺服器和玩家用戶端之間有1:N的多個TCP串連;登入伺服器和遊戲伺服器之間是1:1的TCP串連。玩家登入遊戲的大致流程是這樣的:
- 玩家串連登入伺服器
- 登入伺服器向資料庫請求玩家資料
- 登入伺服器擷取到玩家資料,把玩家資料轉寄給遊戲伺服器進行載入包括建立玩家對象等
- 登入伺服器擷取到載入成功回應後,通知玩家用戶端可以進入遊戲世界
在3和4中,因為登入伺服器和遊戲伺服器通常只有一個TCP串連,所有玩家資料都是通過這個串連進行傳輸,所以需要從協議包中區分出是哪個玩家的資料。通常這個區分的依據可以是玩家的角色名稱,但是也可以更通用一些,用一個數字ID來區分,這樣就把協議包的分發處理和協議包中與遊戲邏輯有關的內容分離開來。
協議說明:通常網遊的網路通訊協定都是報文的形式,即使底層是使用TCP,也會用一些方法把資料拆分成一個個的報文(本文中稱為協議包)。因此,本文也基於這一假設,但是對於具體的協議包格式,本文沒有特別限制,只是要求協議包中能夠容納一個32位元組的ID。協議包的處理大概可以分為以下兩種類型。其他更複雜的會話可以由以下兩種類型組合而成。
- 發送一個資料包並等待回應。比如登入伺服器等待遊戲伺服器載入玩家資料的結果通知。
- 發送一個資料包,不需要回應。比如遊戲伺服器載入玩家資料後,給登入伺服器發送結果通知。
架構說明:Go語言是一種支援高並發的程式設計語言,它支援高並發的方式是大量輕量級的goroutine並發執行。在每個goroutine中的操作基本上都是同步阻塞的,這樣可以極大地簡化程式邏輯,使得代碼清晰易讀,容易維護。基於這點,本文實現的架構的調用介面也是使用同步方式的。
- 如果一個協議包需要等待回應,就在調用函數上阻塞等待。這個調用的簽名為:
func (p *Connection) Query(data[]byte) ([]byte, error)
注意:data的控制權會轉交給架構,因此函數調用後不能修改data的內容。
- 如果發送一個協議包是對於接收到的某個協議包的回應,則調用:
func(p *Connection) Reply(query, answer []byte) error
注意:answer的控制權會轉交給架構,因此函數調用後不能修改answer的內容。
- 如果一個協議包不需要回應,就直接調用發送函數:
func(p *Connection) Write(data []byte) error
注意:data的控制權會轉交給架構,因此函數調用後不能修改data的內容。
- 調用者需要實現的介面:
- Socket。用於協議包的收發。基本上是net.TCPConn的簡單封裝,在頭部加上一個協議包的長度。
- DataHandler。用於協議處理,即沒有通過Query返回的協議包會分發給此介面處理。
- ErrorHandler。用於錯誤處理。當斷線時,會調用此介面。
- IdentityHandler。用於讀取和設定會話ID。
5. 關於goroutine安全的說明:
ErrorHandler和DataHandler的函數實現中不能直接調用(*Connection).Close,否則會導致死結。
匯出類型、函數和介面:
type Connectionfunc NewConnection(conn Socket, maxcount int, dh DataHandler, ih IdentityHandler, eh ErrorHandler) *Connectionfunc (p *Connection) Start()func (p *Connection) Close()func (p *Connection) Query(data []byte) (res []byte, err error)func (p *Connection) Reply(query, answer []byte) errorfunc (p *Connection) Write(data []byte) errortype Socket interface {Read() ([]byte, error)Write([]byte) errorClose()}type DataHandler interface {Process([]byte)}type ErrorHandler interface {OnError(error)}type IdentityHandler interface {GetIdentity([]byte) uint32SetIdentity([]byte, uint32)}
完整的代碼實現:
package multiplexerimport ("errors""sync""sync/atomic")var (ERR_EXIT = errors.New("exit"))type Socket interface {Read() ([]byte, error)Write([]byte) errorClose()}type DataHandler interface {Process([]byte)}type ErrorHandler interface {OnError(error)}type IdentityHandler interface {GetIdentity([]byte) uint32SetIdentity([]byte, uint32)}type Connection struct {conn Socketwg sync.WaitGroupmutex sync.Mutexapplicants map[uint32]chan []bytechexit chan boolchsend chan []bytechch chan chan []bytedh DataHandlerih IdentityHandlereh ErrorHandleridentity uint32}func NewConnection(conn Socket, maxcount int, dh DataHandler, ih IdentityHandler, eh ErrorHandler) *Connection {count := maxcountif count < 1024 {count = 1024}chch := make(chan chan []byte, count)for i := 0; i < count; i++ {chch <- make(chan []byte, 1)}return &Connection{conn: conn,applicants: make(map[uint32]chan []byte, count),chsend: make(chan []byte, count),chexit: make(chan bool),chch: chch,dh: dh,ih: ih,eh: eh,}}func (p *Connection) Start() {p.wg.Add(2)go func() {defer p.wg.Done()p.recv()}()go func() {defer p.wg.Done()p.send()}()}func (p *Connection) Close() {close(p.chexit)p.conn.Close()p.wg.Wait()}func (p *Connection) Query(data []byte) (res []byte, err error) {var ch chan []byteselect {case <-p.chexit:return nil, ERR_EXITcase ch = <-p.chch:defer func() {p.chch <- ch}()}id := p.newIdentity()p.ih.SetIdentity(data, id)p.addApplicant(id, ch)defer func() {if err != nil {p.popApplicant(id)}}()if err := p.Write(data); err != nil {return nil, err}select {case <-p.chexit:return nil, ERR_EXITcase res = <-ch:break}return res, nil}func (p *Connection) Reply(query, answer []byte) error {// put back the identity attached to the queryid := p.ih.GetIdentity(query)p.ih.SetIdentity(answer, id)return p.Write(answer)}func (p *Connection) Write(data []byte) error {select {case <-p.chexit:return ERR_EXITcase p.chsend <- data:break}return nil}func (p *Connection) send() {for {select {case <-p.chexit:returncase data := <-p.chsend:if p.conn.Write(data) != nil {return}}}}func (p *Connection) recv() (err error) {defer func() {if err != nil {select {case <-p.chexit:err = nildefault:p.eh.OnError(err)}}}()for {select {case <-p.chexit:return nildefault:break}data, err := p.conn.Read()if err != nil {return err}if id := p.ih.GetIdentity(data); id > 0 {ch, ok := p.popApplicant(id)if ok {ch <- datacontinue}}p.dh.Process(data)}return nil}func (p *Connection) newIdentity() uint32 {return atomic.AddUint32(&p.identity, 1)}func (p *Connection) addApplicant(identity uint32, ch chan []byte) {p.mutex.Lock()defer p.mutex.Unlock()p.applicants[identity] = ch}func (p *Connection) popApplicant(identity uint32) (chan []byte, bool) {p.mutex.Lock()defer p.mutex.Unlock()ch, ok := p.applicants[identity]if !ok {return nil, false}delete(p.applicants, identity)return ch, true}