golang websocket 服務(wù)端

在go中創(chuàng)建websocket服務(wù)

基礎(chǔ)組件 雖然golang官網(wǎng)提供的功能包中有websocket服務(wù)相關(guān)內(nèi)容但部分功能不全所以引用第三方包
包地址 github.com/gorilla/websocket

創(chuàng)建一個(gè)websocket的服務(wù)端

  • websocket服務(wù)其實(shí)就是在http上升級而來許多地方與http相同
package smile

import (
    "errors"
    "log"
    "net/http"
    "sync"
    "time"

    "github.com/gorilla/websocket"
)

const (
    // 允許等待的寫入時(shí)間
    writeWait = 10 * time.Second

    // Time allowed to read the next pong message from the peer.
    pongWait = 60 * time.Second

    // Send pings to peer with this period. Must be less than pongWait.
    pingPeriod = (pongWait * 9) / 10

    // Maximum message size allowed from peer.
    maxMessageSize = 512
)

// 最大的連接ID缺狠,每次連接都加1 處理
var maxConnId int64

// 客戶端讀寫消息
type wsMessage struct {
    // websocket.TextMessage 消息類型
    messageType int
    data        []byte
}

// ws 的所有連接
// 用于廣播
var wsConnAll map[int64]*wsConnection

var upgrader = websocket.Upgrader{
    ReadBufferSize:  1024,
    WriteBufferSize: 1024,
    // 允許所有的CORS 跨域請求,正式環(huán)境可以關(guān)閉
    CheckOrigin: func(r *http.Request) bool {
        return true
    },
}

// 客戶端連接
type wsConnection struct {
    wsSocket *websocket.Conn // 底層websocket
    inChan   chan *wsMessage // 讀隊(duì)列
    outChan  chan *wsMessage // 寫隊(duì)列

    mutex     sync.Mutex // 避免重復(fù)關(guān)閉管道,加鎖處理
    isClosed  bool
    closeChan chan byte // 關(guān)閉通知
    id        int64
}

func wsHandler(resp http.ResponseWriter, req *http.Request) {
    // 應(yīng)答客戶端告知升級連接為websocket
    wsSocket, err := upgrader.Upgrade(resp, req, nil)
    if err != nil {
        log.Println("升級為websocket失敗", err.Error())
        return
    }
    maxConnId++
    // TODO 如果要控制連接數(shù)可以計(jì)算,wsConnAll長度
    // 連接數(shù)保持一定數(shù)量,超過的部分不提供服務(wù)
    wsConn := &wsConnection{
        wsSocket:  wsSocket,
        inChan:    make(chan *wsMessage, 1000),
        outChan:   make(chan *wsMessage, 1000),
        closeChan: make(chan byte),
        isClosed:  false,
        id:        maxConnId,
    }
    wsConnAll[maxConnId] = wsConn
    log.Println("當(dāng)前在線人數(shù)", len(wsConnAll))

    // 處理器,發(fā)送定時(shí)信息,避免意外關(guān)閉
    go wsConn.processLoop()
    // 讀協(xié)程
    go wsConn.wsReadLoop()
    // 寫協(xié)程
    go wsConn.wsWriteLoop()
}

// 處理隊(duì)列中的消息
func (wsConn *wsConnection) processLoop() {
    // 處理消息隊(duì)列中的消息
    // 獲取到消息隊(duì)列中的消息绿满,處理完成后,發(fā)送消息給客戶端
    for {
        msg, err := wsConn.wsRead()
        if err != nil {
            log.Println("獲取消息出現(xiàn)錯(cuò)誤", err.Error())
            break
        }
        log.Println("接收到消息", string(msg.data))
        // 修改以下內(nèi)容把客戶端傳遞的消息傳遞給處理程序
        err = wsConn.wsWrite(msg.messageType, msg.data)
        if err != nil {
            log.Println("發(fā)送消息給客戶端出現(xiàn)錯(cuò)誤", err.Error())
            break
        }
    }
}

// 處理消息隊(duì)列中的消息
func (wsConn *wsConnection) wsReadLoop() {
    // 設(shè)置消息的最大長度
    wsConn.wsSocket.SetReadLimit(maxMessageSize)
    wsConn.wsSocket.SetReadDeadline(time.Now().Add(pongWait))
    for {
        // 讀一個(gè)message
        msgType, data, err := wsConn.wsSocket.ReadMessage()
        if err != nil {
            websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure)
            log.Println("消息讀取出現(xiàn)錯(cuò)誤", err.Error())
            wsConn.close()
            return
        }
        req := &wsMessage{
            msgType,
            data,
        }
        // 放入請求隊(duì)列,消息入棧
        select {
        case wsConn.inChan <- req:
        case <-wsConn.closeChan:
            return
        }
    }
}

// 發(fā)送消息給客戶端
func (wsConn *wsConnection) wsWriteLoop() {
    ticker := time.NewTicker(pingPeriod)
    defer func() {
        ticker.Stop()
    }()
    for {
        select {
        // 取一個(gè)應(yīng)答
        case msg := <-wsConn.outChan:
            // 寫給websocket
            if err := wsConn.wsSocket.WriteMessage(msg.messageType, msg.data); err != nil {
                log.Println("發(fā)送消息給客戶端發(fā)生錯(cuò)誤", err.Error())
                // 切斷服務(wù)
                wsConn.close()
                return
            }
        case <-wsConn.closeChan:
            // 獲取到關(guān)閉通知
            return
        case <-ticker.C:
            // 出現(xiàn)超時(shí)情況
            wsConn.wsSocket.SetWriteDeadline(time.Now().Add(writeWait))
            if err := wsConn.wsSocket.WriteMessage(websocket.PingMessage, nil); err != nil {
                return
            }
        }
    }
}

// 寫入消息到隊(duì)列中
func (wsConn *wsConnection) wsWrite(messageType int, data []byte) error {
    select {
    case wsConn.outChan <- &wsMessage{messageType, data}:
    case <-wsConn.closeChan:
        return errors.New("連接已經(jīng)關(guān)閉")
    }
    return nil
}

// 讀取消息隊(duì)列中的消息
func (wsConn *wsConnection) wsRead() (*wsMessage, error) {
    select {
    case msg := <-wsConn.inChan:
        // 獲取到消息隊(duì)列中的消息
        return msg, nil
    case <-wsConn.closeChan:

    }
    return nil, errors.New("連接已經(jīng)關(guān)閉")
}

// 關(guān)閉連接
func (wsConn *wsConnection) close() {
    log.Println("關(guān)閉連接被調(diào)用了")
    wsConn.wsSocket.Close()
    wsConn.mutex.Lock()
    defer wsConn.mutex.Unlock()
    if wsConn.isClosed == false {
        wsConn.isClosed = true
        // 刪除這個(gè)連接的變量
        delete(wsConnAll, wsConn.id)
        close(wsConn.closeChan)
    }
}

// 對所有用戶進(jìn)行廣播
func broadcastUsers(messageType int, data string) {
    for _, ws := range wsConnAll {
        ws.wsWrite(messageType, []byte(data))
    }
}

// 啟動程序
func StartWebsocket(addrPort string) {
    wsConnAll = make(map[int64]*wsConnection)
    http.HandleFunc("/ws", wsHandler)
    http.ListenAndServe(addrPort, nil)
}

啟動調(diào)用

// broadcastUsers 這個(gè)方法是對所有連接進(jìn)行廣播
smile.StartWebsocket("0.0.0.0:8080")
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市追葡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖宜肉,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件匀钧,死亡現(xiàn)場離奇詭異,居然都是意外死亡谬返,警方通過查閱死者的電腦和手機(jī)之斯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來遣铝,“玉大人佑刷,你說我怎么就攤上這事∧鹫ǎ” “怎么了瘫絮?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長填硕。 經(jīng)常有香客問我麦萤,道長,這世上最難降的妖魔是什么廷支? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任频鉴,我火速辦了婚禮,結(jié)果婚禮上恋拍,老公的妹妹穿的比我還像新娘垛孔。我一直安慰自己,他們只是感情好施敢,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布周荐。 她就那樣靜靜地躺著,像睡著了一般僵娃。 火紅的嫁衣襯著肌膚如雪概作。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天讯榕,我揣著相機(jī)與錄音,去河邊找鬼匙睹。 笑死愚屁,一個(gè)胖子當(dāng)著我的面吹牛痕檬,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播梦谜,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼丘跌,長吁一口氣:“原來是場噩夢啊……” “哼袭景!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起闭树,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤耸棒,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后报辱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體榆纽,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年捏肢,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片饥侵。...
    茶點(diǎn)故事閱讀 38,789評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡鸵赫,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出躏升,到底是詐尸還是另有隱情辩棒,我是刑警寧澤,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布膨疏,位于F島的核電站一睁,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏佃却。R本人自食惡果不足惜者吁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望饲帅。 院中可真熱鬧复凳,春花似錦、人聲如沸灶泵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽赦邻。三九已至髓棋,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間惶洲,已是汗流浹背按声。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留湃鹊,地道東北人儒喊。 一個(gè)月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像币呵,于是被迫代替她去往敵國和親怀愧。 傳聞我的和親對象是個(gè)殘疾皇子侨颈,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評論 2 351

推薦閱讀更多精彩內(nèi)容