Go语言简单源代码剖析

来源:互联网 发布:淘宝卖家修改评价链接 编辑:程序博客网 时间:2024/06/07 10:14

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 controllersimport (    "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 controllersimport (    "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 controllersimport (    "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 controllersimport (    "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 modelsimport (    "container/list")type EventType intconst (    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}

看完代码的感觉是好的代码真的是太好了,就完全不需要我去注释了…很简单明了,这也算是熟悉一下简单的项目了