今天我們繼續來看Melody,hub,Session三個業務主體,它們struct當中值得關心的部分。
用前三篇的分析,可以充分了解到,Melody,hub,Session是『1』對『1』對『多』的關係。
而每個Session,又有個變數記著它們最上面的Melody的關係。
// Session wrapper around websocket connections.
type Session struct {
Request *http.Request
Keys map[string]interface{}
conn *websocket.Conn
output chan *envelope
melody *Melody
open bool
rwmutex *sync.RWMutex
}
type hub struct {
sessions map[*Session]bool
broadcast chan *envelope
register chan *Session
unregister chan *Session
exit chan *envelope
open bool
rwmutex *sync.RWMutex
}
type Melody struct {
Config *Config
Upgrader *websocket.Upgrader
messageHandler handleMessageFunc
messageHandlerBinary handleMessageFunc
messageSentHandler handleMessageFunc
messageSentHandlerBinary handleMessageFunc
errorHandler handleErrorFunc
closeHandler handleCloseFunc
connectHandler handleSessionFunc
disconnectHandler handleSessionFunc
pongHandler handleSessionFunc
hub *hub
}
輔以package裡面的source code(到了要看code的時候喔),筆者來逐一解釋:
首先說的是內部許多function型態變數的部分,後續會有一篇專門介紹event trigger,打算專門介紹這部分,如此的設計可說是非常非常好用,細節暫且之後再介紹。
不過還是有個重點,綁定function的變數放在Melody,而不是放在hub或Session,則代表著是Melody這個業務會負責提供package外面的使用事宜,提供外面怎麼樣的使用方式,是Melody來決定的。
messageHandler handleMessageFunc
messageHandlerBinary handleMessageFunc
messageSentHandler handleMessageFunc
messageSentHandlerBinary handleMessageFunc
errorHandler handleErrorFunc
closeHandler handleCloseFunc
connectHandler handleSessionFunc
disconnectHandler handleSessionFunc
pongHandler handleSessionFunc
對外部接洽業務處理的struct通常會有個大寫的初始化method,不一定要叫New(),或者會有個屬於這個package的全域變數,在這個package第一次被import的時候,自動初始化一個預設的struct給外面使用的概念。
func New() *Melody {
upgrader := &websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool { return true },
}
hub := newHub()
go hub.run()
return &Melody{
Config: newConfig(),
Upgrader: upgrader,
messageHandler: func(*Session, []byte) {},
messageHandlerBinary: func(*Session, []byte) {},
messageSentHandler: func(*Session, []byte) {},
messageSentHandlerBinary: func(*Session, []byte) {},
errorHandler: func(*Session, error) {},
closeHandler: nil,
connectHandler: func(*Session) {},
disconnectHandler: func(*Session) {},
pongHandler: func(*Session) {},
hub: hub,
}
}
hub 一眼就看到許多channel型態的變數,看到這樣子的設計,要馬上想到什麼呢?
答案是這個package應該會開出許多goroutine,而hub會負責處理這些goroutine的某些業務,最不濟也是充當中繼者的角色。
尤其是看到sessions和rwmutex兩種變數,如果沒有多個gorutine同時操作同一個map,何必須要讀寫鎖去控制呢,或者你會覺得這個讀寫鎖型態的變數,是拿來怎麼做使用的?
sessions map[*Session]bool
rwmutex *sync.RWMutex
如果還不知道為什麼看到channel,就要聯想到多個goroutine的朋友們,筆者在這邊做個小小的提醒,channel的收和送是不可能在同一個goroutine內完成的,往channel塞東西,沒有從channel取出的話,程式永遠卡在那邊,既然卡在送的動作,又怎麼可能會執行到收的動作呢?
或許你會想說buffer channel的話就不會馬上卡住了,但是這樣設計話......老實說很怪啦。
回過頭來,channel的變數命名,也很常拿來代表它們的業務處理動作,所以在這裡就可以知道hub有這4項主要的業務處理,分別是broadcast、register、unregister、exit。
broadcast chan *envelope
register chan *Session
unregister chan *Session
exit chan *envelope
Session 反而沒什麼好說的,好幾個指標的型態,換句話說就是別人的業務主體,然後session有它們的聯絡方式,有跟那些業務主體有關聯,不過除了*Melody以外,都是別的package的事情。
變數output可能會稍為讓人有點在意,因為hub有個channel變數也是同樣的型態,所以這個業務處理,可以推測是需要Session和hub要一起配合才處理得來。
output chan *envelope
以上觀察melody的業務主體struct,從正面來看差不多分析完了,簡明的三個struct就可以告訴要使用package的開發者,非常非常多的資訊,大大寫出來的東西就是不一樣,下一篇筆者幫大家挑選ㄧ些內部的method,我們試著用這些method來理解struct的業務處理設計。