Go語言簡單原始碼剖析
阿新 • • 發佈:2019-02-01
https://github.com/beego/samples/tree/master/WebIM
專案在github上面的地址
是beego提供的一個應用小專案,只是對本人第一次剖析go語言的一個小記錄,也算自己剖析程式碼的思路記錄,希望對他人也能有所幫助
(1)閱讀readme文件,知曉專案大致內容,專案用到的各種配置檔案
(2)檢視main包檔案WebIM.go
import (
"github.com/astaxie/beego"
"github.com/beego/i18n"
"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()
}
main檔案包含了三個引用包,一般main檔案裡的函式都被封裝的很好,但是先看main檔案,就可以知曉程式執行順序,再一點一點的分析每個函式的作用
(2)
beego.Router()函式:註冊路由,Router 函式的兩個引數函式,第一個是路徑,第二個是 Controller 的指標。路由就是告訴 beego,當用戶來請求的時候,該如何去呼叫相應的 Controller。
PS:由於我主要是想搞明白聊天室的工作原理,所以對於beego工具的運作方法暫且不細細去看,重點是controllers
(3)
app.go
先看開發文件瞭解功能:供使用者選擇技術和使用者名稱的歡迎頁面
package controllers
import (
"strings"
"github.com/astaxie/beego"
"github.com/beego/i18n"
)
var langTypes []string //支援的語言,從配置檔案獲取
func init() {
// Initialize language type list.
langTypes = strings.Split(beego.AppConfig.String("lang_types"), "|")
// Load locale files according to language 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
}
}
}
// baseController represents base router for all other app routers.
//基本控制器為所有其他的app路由提供了基本
// It implemented some methods for the same implementation;
// thus, it will be embedded into other routers.
type baseController struct {
beego.Controller // Embed struct that has stub implementation of the interface.
i18n.Locale // For i18n usage when process data and render template.
}
// Prepare implemented Prepare() method for baseController.
// It's used for language option check and setting.
func (this *baseController) Prepare() {
// Reset language option.
this.Lang = "" // This field is from i18n.Locale.
// 1. Get language information from 'Accept-Language'.
al := this.Ctx.Request.Header.Get("Accept-Language")
if len(al) > 4 {
al = al[:5] // Only compare first 5 letters.
if i18n.IsExist(al) {
this.Lang = al
}
}
// 2. Default language is English.
if len(this.Lang) == 0 {
this.Lang = "en-US"
}
// Set template level language option.
this.Data["Lang"] = this.Lang
}
// AppController handles the welcome screen that allows user to pick a technology and username.
type AppController struct {
baseController // Embed to use methods that are implemented in baseController.
}
// Get implemented Get() method for AppController.
func (this *AppController) Get() {
this.TplName = "welcome.html"
}
//用於解決post請求
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
}
chatroom.go:資料管理相關的函式
package controllers
import (
"container/list"
"time"
"github.com/astaxie/beego"
"github.com/gorilla/websocket"
"samples/WebIM/models"
)
type Subscription struct {
Archive []models.Event // All the events from the archive.
New <-chan models.Event // New events coming in.
}
func newEvent(ep models.EventType, user, msg string) models.Event {
return models.Event{ep, user, int(time.Now().Unix()), msg}
}
func Join(user string, ws *websocket.Conn) {
subscribe <- Subscriber{Name: user, Conn: ws}
//上線,記錄使用者名稱和連線
}
func Leave(user string) {
unsubscribe <- user
//下線
}
type Subscriber struct {
Name string
Conn *websocket.Conn // Only for WebSocket users; otherwise nil.
}
var (
// Channel for new join users.
subscribe = make(chan Subscriber, 10)
// Channel for exit users.
unsubscribe = make(chan string, 10)
// Send events here to publish them.
publish = make(chan models.Event, 10)
// Long polling waiting list.
waitingList = list.New()
subscribers = list.New()
)
// This function handles all incoming chan messages.
func chatroom() {
for {
select {
case sub := <-subscribe:
if !isUserExist(subscribers, sub.Name) {
subscribers.PushBack(sub) // Add user to the end of list.
// Publish a JOIN event.
publish <- newEvent(models.EVENT_JOIN, sub.Name, "")
beego.Info("New user:", sub.Name, ";WebSocket:", sub.Conn != nil)
} else {
beego.Info("Old user:", sub.Name, ";WebSocket:", sub.Conn != nil)
}
case event := <-publish:
// Notify waiting list.
for ch := waitingList.Back(); ch != nil; ch = ch.Prev() {
ch.Value.(chan bool) <- true
waitingList.Remove(ch)
}
broadcastWebSocket(event)
models.NewArchive(event)
if event.Type == models.EVENT_MESSAGE {
beego.Info("Message from", event.User, ";Content:", event.Content)
}
case unsub := <-unsubscribe:
for sub := subscribers.Front(); sub != nil; sub = sub.Next() {
if sub.Value.(Subscriber).Name == unsub {
subscribers.Remove(sub)
// Clone connection.
ws := sub.Value.(Subscriber).Conn
if ws != nil {
ws.Close()
beego.Error("WebSocket closed:", unsub)
}
publish <- newEvent(models.EVENT_LEAVE, unsub, "") // Publish a LEAVE event.
break
}
}
}
}
}
func init() {
go chatroom()
}
func isUserExist(subscribers *list.List, user string) bool {
for sub := subscribers.Front(); sub != nil; sub = sub.Next() {
if sub.Value.(Subscriber).Name == user {
return true
}
}
return false
}
longpolling.go:長輪詢模式的控制器和方法
package controllers
import (
"samples/WebIM/models"
)
// LongPollingController handles long polling requests.
type LongPollingController struct {
baseController
}
// Join method handles GET requests for LongPollingController.
func (this *LongPollingController) Join() {
// Safe check.
uname := this.GetString("uname")
if len(uname) == 0 {
this.Redirect("/", 302)
return
}
// Join chat room.
Join(uname, nil)
this.TplName = "longpolling.html"
this.Data["IsLongPolling"] = true
this.Data["UserName"] = uname
}
// Post method handles receive messages requests for LongPollingController.
func (this *LongPollingController) Post() {
this.TplName = "longpolling.html"
uname := this.GetString("uname")
content := this.GetString("content")
if len(uname) == 0 || len(content) == 0 {
return
}
publish <- newEvent(models.EVENT_MESSAGE, uname, content)
}
// Fetch method handles fetch archives requests for LongPollingController.
func (this *LongPollingController) Fetch() {
lastReceived, err := this.GetInt("lastReceived")
if err != nil {
return
}
events := models.GetEvents(int(lastReceived))
if len(events) > 0 {
this.Data["json"] = events
this.ServeJSON()
return
}
// Wait for new message(s).
ch := make(chan bool)
waitingList.PushBack(ch)
<-ch
this.Data["json"] = models.GetEvents(int(lastReceived))
this.ServeJSON()
}
websocket.go:資料管理相關的函式
package controllers
import (
"encoding/json"
"net/http"
"github.com/astaxie/beego"
"github.com/gorilla/websocket"
"samples/WebIM/models"
)
// WebSocketController handles WebSocket requests.
type WebSocketController struct {
baseController
}
// Get method handles GET requests for WebSocketController.
func (this *WebSocketController) Get() {
// Safe check.
uname := this.GetString("uname")
if len(uname) == 0 {
this.Redirect("/", 302)
return
}
this.TplName = "websocket.html"
this.Data["IsWebSocket"] = true
this.Data["UserName"] = uname
}
// Join method handles WebSocket requests for WebSocketController.
func (this *WebSocketController) Join() {
uname := this.GetString("uname")
if len(uname) == 0 {
this.Redirect("/", 302)
return
}
// Upgrade from http request to 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
}
// Join chat room.
Join(uname, ws)
defer Leave(uname)
// Message receive loop.
for {
_, p, err := ws.ReadMessage()
if err != nil {
return
}
publish <- newEvent(models.EVENT_MESSAGE, uname, string(p))
}
}
// broadcastWebSocket broadcasts messages to WebSocket users.
func broadcastWebSocket(event models.Event) {
data, err := json.Marshal(event)
if err != nil {
beego.Error("Fail to marshal event:", err)
return
}
for sub := subscribers.Front(); sub != nil; sub = sub.Next() {
// Immediately send event to WebSocket users.
ws := sub.Value.(Subscriber).Conn
if ws != nil {
if ws.WriteMessage(websocket.TextMessage, data) != nil {
// User disconnected.
unsubscribe <- sub.Value.(Subscriber).Name
}
}
}
}
archive.go:操作資料相關的函式
package models
import (
"container/list"
)
type EventType int
const (
EVENT_JOIN = iota
EVENT_LEAVE
EVENT_MESSAGE
)
type Event struct {
Type EventType // JOIN, LEAVE, MESSAGE
User string
Timestamp int // Unix timestamp (secs)
Content string
}
const archiveSize = 20
// Event archives.
var archive = list.New()
// NewArchive saves new event to archive list.
func NewArchive(event Event) {
if archive.Len() >= archiveSize {
archive.Remove(archive.Front())
}
archive.PushBack(event)
}
// GetEvents returns all events after lastReceived.
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
}
看完程式碼的感覺是好的程式碼真的是太好了,就完全不需要我去註釋了…很簡單明瞭,這也算是熟悉一下簡單的專案了