日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Golang Beego框架之WebIM例子分析

發布時間:2023/12/20 编程问答 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Golang Beego框架之WebIM例子分析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

beego框架算是golang比較成熟的一個框架了,最近看了下這個框架其中的一個在線聊天室的例子,覺得還是有很多可以學習借鑒的地方,所以就總結下。

這個例子的源碼在這里,該例子配合bee工具可以很簡單的進行運行。

首先看下這個項目的結構:

標準的beego框架,各個文件夾包含了不同的功能。

然后我們從main.go(這里是WebIM.go)看起:

package mainimport ("github.com/astaxie/beego""github.com/beego/i18n""github.com/beego/samples/WebIM/controllers" )const (APP_VER = "0.1.1.0227" )func main() {beego.Info(beego.BConfig.AppName, APP_VER)// Register routers.beego.Router("/", &controllers.AppController{})// Indicate AppController.Join method to handle POST requests.beego.Router("/join", &controllers.AppController{}, "post:Join")// Long polling.beego.Router("/lp", &controllers.LongPollingController{}, "get:Join")beego.Router("/lp/post", &controllers.LongPollingController{})beego.Router("/lp/fetch", &controllers.LongPollingController{}, "get:Fetch")// WebSocket.beego.Router("/ws", &controllers.WebSocketController{})beego.Router("/ws/join", &controllers.WebSocketController{}, "get:Join")// Register template functions.beego.AddFuncMap("i18n", i18n.Tr)beego.Run() }

雖然是從該文件看起,但是并非是從該文件開始執行的,而是import中的package中的init方法執行的,有關init方法的執行順序可以看這里,好了,先收回來看這個文件,WebIM.go中主要是定義了路由,那我們來看下路由的細節:

請求/會跳轉到controllers.AppController對應的Get方法,該方法直接返回welcome.html頁面,頁面如下:

然后在此處輸入用戶名并選擇使用的連接技術,用戶名很簡單就是用戶ID,使用的技術這里采用長輪詢或者WebSocket的方式,稍后會專門談談這兩種方式。

點擊‘進入聊天室’后,就會請求/join,同時會攜帶兩個參數,一個是用戶名,另外一個參數是連接的方式(技術),該請求為post請求,根據beego.Router("/join", &controllers.AppController{}, "post:Join")可知,該請求會被AppController的Join方法:

func (this *AppController) Join() {// Get form value.uname := this.GetString("uname")tech := this.GetString("tech")// Check valid.if len(uname) == 0 {this.Redirect("/", 302)return}switch tech {case "longpolling":this.Redirect("/lp?uname="+uname, 302)case "websocket":this.Redirect("/ws?uname="+uname, 302)default:this.Redirect("/", 302)}// Usually put return after redirect.return }

該方法首先獲取到post請求的兩個參數,然后判斷用戶名是否為空,若為空重新跳回歡迎頁面,如不為空,開始判斷當前連接方式(技術)是什么,根據不同分別跳轉/lp?uname=uname和/ws?uname=uname,然后WebIM.go中的路由路由就會根據本次請求的方式分別請求不同的控制器,/lp將會請求controllers.LongPollingController{}的Join方法,/ws將會請求controllers.WebSocketController{}默認的Get方法,這兩種請求的方式返回的前端頁面是相同的,但是后臺的處理是不同的。

那么,我們先區分一下這兩種獲取數據的方式有什么不同,首先是websocket的方式,websocket實現了服務器端主動向客戶端瀏覽器進行消息的推送,實現了消息的同步,實質上是建立了一條socket連接,通過該鏈接進行通信,而長輪詢則依舊是采用http的方式去請求數據,區別于輪詢的方式,輪詢是客戶端瀏覽器每隔一段時間發起一次http請求,而長輪詢則是發起一次請求,然后服務器端將該連接保持住(hold,暫時不拒絕也不響應),等到服務器端到達指定的情況(收到另一個消息)將該消息作為內容進行響應,客戶端收到響應后再次發起長輪詢請求,這樣就能保證每次有新的消息到達的時候都能及時的收到響應。

在這個WebIM中是通過chan來維持長輪詢的,在介紹維持長輪詢這塊之前,我們按次序來看下在這個例子中使用的數據結構models:

archive.go

package modelsimport ("container/list" )//用int類型重新定義客戶端產生的事件類型 type EventType int//三種事件類型:加入、離開、消息 const (EVENT_JOIN = iotaEVENT_LEAVEEVENT_MESSAGE )//定義事件結構(事件類型、用戶名、事件、內容) type Event struct {Type EventType // JOIN, LEAVE, MESSAGEUser stringTimestamp int // Unix timestamp (secs)Content string }//用來保存服務器上能夠保存的消息記錄,保存最新的20條 const archiveSize = 20// 事件歸檔保存 var archive = list.New()// 將一個新的事件保存在archive中,若事件的個數已經大于等于20則刪除第一個,只保留最新的20個 func NewArchive(event Event) {if archive.Len() >= archiveSize {archive.Remove(archive.Front())}archive.PushBack(event) }// 根據傳過來的時間戳返回該時間戳之后的所有事件消息 func GetEvents(lastReceived int) []Event {events := make([]Event, 0, archive.Len())for event := archive.Front(); event != nil; event = event.Next() {e := event.Value.(Event)if e.Timestamp > int(lastReceived) {events = append(events, e)}}return events }

看完了models才想起來上面還說到WebIM.go并不是程序執行的其實,而應該是import包時調用的init方法,由于此處導入了github.com/beego/samples/WebIM/controllerspackage,所以我們來看下controllers中的init方法。

在app.go中的init方法如下:

func init() {// 從配置文件中獲取語言類型列表langTypes = strings.Split(beego.AppConfig.String("lang_types"), "|")// 根據語言類型加載語言環境文件for _, lang := range langTypes {beego.Trace("Loading language: " + lang)if err := i18n.SetMessage(lang, "conf/"+"locale_"+lang+".ini"); err != nil {beego.Error("Fail to set message file:", err)return}} }

在chatroom.go中init方法如下:

func init() {go chatroom() }

該方法直接啟動一個goroutine,讓其獨立運行,下面我們就來看下這個chatroom方法的作用。

首先在chatroom.go中定義了一系列的變量,如:subscribe、unsubscribe、publish、waitingList、subscribers,其中subscribe、unsubscribe、publish都是chan類型,三個變量代表的含義分別是:訂閱者,未訂閱者,以及要進行發布的消息,之所以設置為緩沖為10的chan,這是為了應對同時多個客戶端發起的請求事件。watingList表示的是當前等待長輪詢的list,該list中存儲的類型為無緩沖的chan類型,也就是通過這樣一個無緩沖的chan類型來保證了長輪詢的“保持(hold)”:當有長輪詢請求到達時,該list添加一個空的chan,然后從該ch中讀取數據,由于剛才是添加的空的chan,所以這里直接從chan中讀取數據自然不能讀取到,那么執行到此處的無緩沖的chan自然就會阻塞,這也就是長輪詢可以保持住的原因!

那么,我們在看下當事件觸發的情況下,長輪詢返回響應的情況:

// Notify waiting list.for ch := waitingList.Back(); ch != nil; ch = ch.Prev() {ch.Value.(chan bool) <- truewaitingList.Remove(ch)}

當有事件觸發時,此處循環waitingList,往list中的每個chan填入元素true,那么這時在上面使用chan讀取數據來保持長輪詢的部分將會被激活(啟動),此時也將返回響應信息,完成一次長輪詢的過程,進而開始下次的長輪詢。此處使用chan來保持長輪詢還是設計的很巧妙的!

我們再看下使用websocket方式來進行通信的場景。

wobsocket和longpolling共用了較多的數據,如所有的事件存檔,當有新的websocket用戶加入時,會調用如下函數:

websocket.go

// Join method handles WebSocket requests for WebSocketController. //當有新的用戶通過websocket方式加入時,調用執行該函數 func (this *WebSocketController) Join() {//獲取加入的用戶的用戶名并進行是否為空的校驗uname := this.GetString("uname")if len(uname) == 0 {this.Redirect("/", 302)return}// 從http請求升級到WebSocket。ws, err := websocket.Upgrade(this.Ctx.ResponseWriter, this.Ctx.Request, nil, 1024, 1024)if _, ok := err.(websocket.HandshakeError); ok {http.Error(this.Ctx.ResponseWriter, "Not a websocket handshake", 400)return} else if err != nil {beego.Error("Cannot setup WebSocket connection:", err)return}// 將該請求轉換成的websocket聯通用戶名一起加入chatroomJoin(uname, ws)defer Leave(uname)// 循環從websocket中讀取數據,無數據時阻塞,有數據到達時往publish chan中添加事件,從而引起其他事件的響應for {_, p, err := ws.ReadMessage()if err != nil {return}publish <- newEvent(models.EVENT_MESSAGE, uname, string(p))} }

websocket.go中還有如下函數:

func broadcastWebSocket(event models.Event) {//將要進行廣播的事件json格式化data, err := json.Marshal(event)if err != nil {beego.Error("Fail to marshal event:", err)return}//循環遍歷通過websocket方式加入聊天室的用戶,廣播該事件(單條)for sub := subscribers.Front(); sub != nil; sub = sub.Next() {// Immediately send event to WebSocket users.ws := sub.Value.(Subscriber).Conn//若是通過longpolling方式加入的,則ws為nilif ws != nil {//如下是將事件消息寫入websocket中,若寫入失敗(返回err)則證明客戶端已關閉websocket,此時從訂閱列表中將該用戶刪除if ws.WriteMessage(websocket.TextMessage, data) != nil {// User disconnected.unsubscribe <- sub.Value.(Subscriber).Name}}} }

在chatroom.go中源碼中有一段如下:

chatroom.go

//當有新的事件消息到達時,執行如下 case event := <-publish:// Notify waiting list.for ch := waitingList.Back(); ch != nil; ch = ch.Prev() {ch.Value.(chan bool) <- truewaitingList.Remove(ch)}broadcastWebSocket(event)models.NewArchive(event)if event.Type == models.EVENT_MESSAGE {beego.Info("Message from", event.User, ";Content:", event.Content)}

此時,我發現了如下的問題,這里的chatroom實際上已經是一個單獨的goroutine,即就是此處的添加消息事件和長輪詢的響應處理函數是兩個goroutine,所以這里就存在了同步的問題,我們先來看下這里有的兩個goroutine的執行情況:

從圖中可以看出,當chatroom的goroutine中收到事件消息時,首先是激活了長輪詢的等待,然后依次是廣播事件、添加事件消息到消息歸檔中,而在另一個goroutine中激活了長輪詢的等待之后立刻就會去獲取lastReceived之后新的事件消息,這里就有可能產生不同步,即就是事件消息在chatroom的goroutine中還未加入事件消息歸檔,這里就開始去獲取,這樣的話當然是不能獲取到消息,也就是返回的消息響應為空!雖然這樣理解但是并非每次都是這樣,而且即便此時此刻返回的響應消息為空,依舊不會影響消息的獲取,因為當前的請求返回空之后,立刻回發起一次新的fetch請求,這次請求的lastReceived依舊是上次請求的timestamp,所以消息不會遺漏,但是,消息事件響應的靈敏度就不夠高,需要發起兩次請求。

為了測試,我添加了如下的代碼,用來模擬chatroom的goroutine運行較慢的情況:

// Notify waiting list.for ch := waitingList.Front(); ch != nil; ch = waitingList.Front() {ch.Value.(chan bool) <- truewaitingList.Remove(ch)}time.Sleep(time.Second * 2)broadcastWebSocket(event)models.NewArchive(event)

如下是收到空的事件消息的情況:

從請求中也可以看出,當返回響應消息為空時,下次請求的lastRecived依舊為上次請求的時間戳,所以消息并不會丟失。

關于兩個goroutine的運行差不多就這樣了,在這個例子中,源碼的作者有這樣一處小失誤,代碼如下:

// Notify waiting list.for ch := waitingList.Back(); ch != nil; ch = ch.Prev() {ch.Value.(chan bool) <- truewaitingList.Remove(ch)}

本來是想要循環遍歷所有的長輪詢的等待隊列,遍歷之后刪除節點,但是由于循環條件出錯,并不能達到預想的情況,而是遺漏了很多的節點。修改如下:

// Notify waiting list.for ch := waitingList.Front(); ch != nil; ch = waitingList.Front() {ch.Value.(chan bool) <- truewaitingList.Remove(ch)}

關于前端是如何請求以及后臺模板的響應這里并沒有介紹,還有前端的websocket以及長輪詢自動發起的請求還需要自己去體會。。。

好了,就這些了,算是對這個例子的一點認識!!!

總結

以上是生活随笔為你收集整理的Golang Beego框架之WebIM例子分析的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。