前前言
這個類經過我的正式投入使用啊,發現不對勁,這樣做可能會導致線程死鎖
比如你dispatch一個event,然后在這個回調里把那個事件的偵聽給remove掉了,那么就會導致線程死鎖(這個問題找了好久啊,剛剛調試的時候才發現了)
還有就是獲取func的引用的問題,golang那半c半java的語法,我改用了新的方法
源碼已經修改!
前言:
呀,學Go語言兩周了,感覺上手挺快的,golang雖然和c語言很想,但是避免掉了很多指針相關的東東,所以學起來特別輕松。
但是途中坎坷頗多啊,資料是少之又少啊,搜索引擎都搜不到啥東西,唯有golang.org和github的ebook上才有比較完整的資料,加了個golang群么,大牛都潛水不說話,然后我瞎了,只能自己琢磨。
也是自己鬧着玩吧,最近想寫一個服務器(目前已經能同步移動了),我也想學一門后端語言,於是就選了google的go語言了,聽說並發性能挺好的。
然而,golang這語言貌似都是被用作web服務開發了的,群里一般都是在討論web開發的問題,而我是做socket開發的,當然,golang里面不叫socket。
工作比較忙,只有下班后那點時間來學golang了,學得比較基礎,大牛就當走走場好了。
正題:
我是一名頁游前端開發人員,當然我是as3開發者,對as3的觀察者模式-事件機制,那是太依賴了,而golang里面原生並不提供這種機制。
golang有的只是十分相似的goroutine,也就是底層支持的並發機制,然后線程間通訊就是channel,好比於as3中的Event,當然不能直接比較,要封裝過。
觀察者模式就是指一對多的依賴關系,生產者分派消息,消費者全都能收到消息(全局觀察模式),這樣,可以降低模塊間的耦合度,我們要做的,就是來管理這三者。
然后,用golang來實現這一設計模式是很簡單的,我僅用了一百多行,就簡單地實現了,直接看代碼吧:
1 package tbs 2 3 import ( 4 //"fmt" 5 "unsafe" 6 ) 7 8 type Dispatcher struct { 9 listeners map[string]*EventChain 10 } 11 12 type EventChain struct { 13 chs []chan *Event 14 callbacks []*EventCallback 15 } 16 17 func createEventChain() *EventChain { 18 return &EventChain{chs: []chan *Event{}, callbacks: []*EventCallback{}} 19 } 20 21 type Event struct { 22 eventName string 23 Params map[string]interface{} 24 } 25 26 func CreateEvent(eventName string, params map[string]interface{}) *Event { 27 return &Event{eventName: eventName, Params: params} 28 } 29 30 type EventCallback func(*Event) 31 32 var _instance *Dispatcher 33 34 func SharedDispatcher() *Dispatcher { 35 if _instance == nil { 36 _instance = &Dispatcher{} 37 _instance.Init() 38 } 39 40 return _instance 41 } 42 43 func (this *Dispatcher) Init() { 44 this.listeners = make(map[string]*EventChain) 45 } 46 47 func (this *Dispatcher) AddEventListener(eventName string, callback *EventCallback) { 48 eventChain, ok := this.listeners[eventName] 49 if !ok { 50 eventChain = createEventChain() 51 this.listeners[eventName] = eventChain 52 } 53 54 exist := false 55 //fmt.Println("add len:", len(eventChain.callbacks)) 56 for _, item := range eventChain.callbacks { 57 a := *(*int)(unsafe.Pointer(item)) 58 b := *(*int)(unsafe.Pointer(callback)) 59 //fmt.Println("add", a, b) 60 if a == b { 61 exist = true 62 break 63 } 64 } 65 66 if exist { 67 return 68 } 69 70 ch := make(chan *Event) 71 72 eventChain.chs = append(eventChain.chs[:], ch) 73 eventChain.callbacks = append(eventChain.callbacks[:], callback) 74 75 go this.handler(eventName, ch, callback) 76 } 77 78 func (this *Dispatcher) handler(eventName string, ch chan *Event, callback *EventCallback) { 79 //fmt.Printf("add listener: %s\n", eventName) 80 //fmt.Println("chan: ", ch) 81 for { 82 event := <-ch 83 //fmt.Println("event out:", eventName, event, ch) 84 if event == nil { 85 break 86 } 87 go (*callback)(event) 88 } 89 } 90 91 func (this *Dispatcher) RemoveEventListener(eventName string, callback *EventCallback) { 92 eventChain, ok := this.listeners[eventName] 93 if !ok { 94 return 95 } 96 97 var ch chan *Event 98 exist := false 99 key := 0 100 for k, item := range eventChain.callbacks { 101 a := *(*int)(unsafe.Pointer(item)) 102 b := *(*int)(unsafe.Pointer(callback)) 103 //fmt.Println("remove", a, b) 104 if a == b { 105 exist = true 106 ch = eventChain.chs[k] 107 key = k 108 break 109 } 110 } 111 112 if exist { 113 //fmt.Printf("remove listener: %s\n", eventName) 114 //fmt.Println("chan: ", ch) 115 ch <- nil 116 117 eventChain.chs = append(eventChain.chs[:key], eventChain.chs[key+1:]...) 118 eventChain.callbacks = append(eventChain.callbacks[:key], eventChain.callbacks[key+1:]...) 119 //fmt.Println(len(eventChain.chs)) 120 } 121 } 122 123 func (this *Dispatcher) DispatchEvent(event *Event) { 124 eventChain, ok := this.listeners[event.eventName] 125 if ok { 126 ////fmt.Printf("dispatch event: %s\n", event.eventName) 127 for _, chEvent := range eventChain.chs { 128 chEvent <- event 129 } 130 } 131 }
這個類里定義了三個結構,Dispatcher:分派器主類,Event:事件類,EventChain:事件鏈類
如果你要使用這個類,那你只要那Dispatcher的單例方法:
SharedDispatcher()
來進行操作好了
要創建Event,你是要使用創建方法
CreateEvent(eventNamestring,paramsmap[string]interface{})
來創建
當然,demo還得貼上
1 package main 2 3 import ( 4 "fmt" 5 "tbs" 6 "time" 7 ) 8 9 type MClass struct { 10 dispatcher tbs.Dispatcher 11 } 12 13 func main() { 14 mc := &MClass{} 15 mc.Start() 16 } 17 18 func (this *MClass) Start() { 19 //獲取分派器單例 20 dispatcher := tbs.SharedDispatcher() 21 22 //添加監聽1 23 var fun1 tbs.EventCallback = this.onTest 24 dispatcher.AddEventListener("test", &fun1) 25 26 //再添加監聽2 27 var fun2 tbs.EventCallback = this.onTest2 28 dispatcher.AddEventListener("test", &fun2) 29 30 //隨便弄個事件攜帶的參數,我把參數定義為一個map 31 params := make(map[string]interface{}) 32 params["id"] = 1000 33 //創建一個事件對象 34 event := tbs.CreateEvent("test", params) 35 //把事件分派出去 36 dispatcher.DispatchEvent(event) 37 38 //移除監聽1 39 dispatcher.RemoveEventListener("test", &fun1) 40 41 //再把事件分派出去一次 42 dispatcher.DispatchEvent(event) 43 44 //因為主線程不會等子線程而直接關閉進程,這樣會看不到效果,所以我在這里加了阻塞式延時 45 time.Sleep(time.Second * 1) 46 } 47 48 //回調出得到的就是一個event對象了 49 func (this *MClass) onTest(event *tbs.Event) { 50 fmt.Println("onTest", event.Params["id"]) 51 } 52 53 func (this *MClass) onTest2(event *tbs.Event) { 54 fmt.Println("onTest2", event.Params["id"]) 55 }
輸出結果:
add listener: test add listener: test dispatch event: test onTest 1000 remove listener: test dispatch event: test onTest2 1000 onTest2 1000 成功: 進程退出代碼 0.
哈哈,成功地運行了。
demo你面的注釋已經非常詳盡了,看不懂就在下面問我好了!
昨晚我拿他來封裝了一下golang的socket,改成了事件驅動,耦合度瞬間降低了很多。
1 func onServerStarted(event *tbs.Event) { 2 fmt.Println("server started.") 3 } 4 5 func onAccept(event *tbs.Event) { 6 socket := (event.Params["socket"]).(*tbs.Socket) 7 8 fmt.Printf("client[#%d] connect on:%s\n", socket.Sign, socket.Conn.RemoteAddr().String()) 9 } 10 11 func onData(event *tbs.Event) { 12 socket := (event.Params["socket"]).(*tbs.Socket) 13 bytes := (event.Params["bytes"]).([]byte) 14 15 fmt.Printf("[#%d]:", socket.Sign) 16 fmt.Println(bytes) 17 } 18 19 func onClosed(event *tbs.Event) { 20 socket := (event.Params["socket"]).(*tbs.Socket) 21 fmt.Printf("[#%d] closed\n", socket.Sign) 22 }
我還是模仿了as3提供的socket,看如上四個回調,只要監聽並開啟了serversocket,那么我只要坐等這四個回調來處理游戲的邏輯即可,每個socket都綁有累加的Sign作為標識。
總結:
golang是一門不錯的語言,特別靈活,反射也很方便,應該會火吧,希望國內能有更多golang的開發者社區能建立起來吧!
貼上我自己的博客地址:http://blog.codeforever.net/