Go實現基於WebSocket的彈幕服務

来源:https://www.cnblogs.com/clivewang/archive/2018/11/05/9911924.html
-Advertisement-
Play Games

拉模式和推模式 拉模式 1、數據更新頻率低,則大多數請求是無效的 2、線上用戶量多,則服務端的查詢負載高 3、定時輪詢拉取,實時性低 推模式 1、僅在數據更新時才需要推送 2、需要維護大量的線上長連接 3、數據更新後可以立即推送 基於webSocket推送 1、瀏覽器支持的socket編程,輕鬆維持 ...


拉模式和推模式

拉模式

1、數據更新頻率低,則大多數請求是無效的
2、線上用戶量多,則服務端的查詢負載高
3、定時輪詢拉取,實時性低

推模式

1、僅在數據更新時才需要推送
2、需要維護大量的線上長連接
3、數據更新後可以立即推送

基於webSocket推送

1、瀏覽器支持的socket編程,輕鬆維持服務端長連接
2、基於TCP可靠傳輸之上的協議,無需開發者關心通訊細節
3、提供了高度抽象的編程介面,業務開發成本較低

webSocket協議與交互

通訊流程

客戶端->upgrade->服務端
客戶端<-switching<-服務端
客戶端->message->服務端
客戶端<-message<-服務端

實現http服務端

1、webSocket是http協議upgrade而來
2、使用http標準庫快速實現空介面:/ws

webSocket握手

1、使用webSocket.Upgrader完成協議握手,得到webSocket長連接
2、操作webSocket api,讀取客戶端消息,然後原樣發送回去

封裝webSocket

缺乏工程化設計

1、其他代碼模塊,無法直接操作webSocket連接
2、webSocket連接非線程安全,併發讀/寫需要同步手段

隱藏細節,封裝api

1、封裝Connection結構,隱藏webSocket底層連接
2、封裝Connection的api,提供Send/Read/Close等線程安全介面

api原理(channel是線程安全的)

1、SendMessage將消息投遞到out channel
2、ReadMessage從in channel讀取消息

內部原理

1、啟動讀協程,迴圈讀取webSocket,將消息投遞到in channel
2、啟動寫協程,迴圈讀取out channel,將消息寫給webSocket

// server.go
package main

import (
    "net/http"
    "github.com/gorilla/websocket"
    "./impl"
    "time"
)

var (
    upgrader = websocket.Upgrader{
        //允許跨域
        CheckOrigin: func(r *http.Request) bool {
            return true
        },
    }
)

func wsHandler(w http.ResponseWriter, r *http.Request) {
    var (
        wsConn *websocket.Conn
        err error
        conn *impl.Connection
        data []byte
    )

    //Upgrade:websocket
    if wsConn, err = upgrader.Upgrade(w, r, nil); err != nil {
        return
    }
    if conn, err = impl.InitConnection(wsConn); err != nil {
        goto ERR
    }

    go func() {
        var (
            err error
        )
        for {
            if err =conn.WriteMessage([]byte("heartbeat")); err != nil {
                return
            }
            time.Sleep(1 * time.Second)
        }
    }()

    for {
        if data, err = conn.ReadMessage(); err != nil {
            goto ERR
        }
        if err = conn.WriteMessage(data); err != nil {
            goto ERR
        }

    }

    ERR:
        //關閉連接
        conn.Close()
}

func main() {
    //http:localhost:7777/ws
    http.HandleFunc("/ws", wsHandler)
    http.ListenAndServe("0.0.0.0:7777", nil)
}
// connection.go
package impl

import (
    "github.com/gorilla/websocket"
    "sync"
    "github.com/influxdata/platform/kit/errors"
)

var once sync.Once

type Connection struct {
    wsConn *websocket.Conn
    inChan chan []byte
    outChan chan []byte
    closeChan chan byte
    isClosed bool
    mutex sync.Mutex
}

func InitConnection(wsConn *websocket.Conn) (conn *Connection, err error) {
    conn = &Connection{
        wsConn:wsConn,
        inChan:make(chan []byte, 1000),
        outChan:make(chan []byte, 1000),
        closeChan:make(chan byte, 1),
    }

    //啟動讀協程
    go conn.readLoop()

    //啟動寫協程
    go conn.writeLoop()

    return
}

//API
func (conn *Connection) ReadMessage() (data []byte, err error) {
    select {
    case data = <- conn.inChan:
    case <- conn.closeChan:
        err = errors.New("connection is closed")
    }
    return
}

func (conn *Connection) WriteMessage(data []byte) (err error) {
    select {
    case conn.outChan <- data:
    case <- conn.closeChan:
        err = errors.New("connection is closed")
    }
    return
}

func (conn *Connection) Close() {
    // 線程安全的close,可重入
    conn.wsConn.Close()
    conn.mutex.Lock()
    if !conn.isClosed {
        close(conn.closeChan)
        conn.isClosed = true
    }
    conn.mutex.Unlock()
}

//內部實現
func (conn *Connection) readLoop() {
    var (
        data []byte
        err error
    )
    for {
        if _, data, err = conn.wsConn.ReadMessage(); err != nil {
            goto ERR
        }

        //阻塞在這裡,等待inChan有空位置
        //但是如果writeLoop連接關閉了,這邊無法得知
        //conn.inChan <- data

        select {
        case conn.inChan <- data:
        case <-conn.closeChan:
            //closeChan關閉的時候,會進入此分支
            goto ERR
        }
    }
    ERR:
        conn.Close()
}

func (conn *Connection) writeLoop() {
    var (
        data []byte
        err error
    )
    for {
        select {
        case data = <- conn.outChan:
        case <- conn.closeChan:
            goto ERR

        }

        if err = conn.wsConn.WriteMessage(websocket.TextMessage, data); err != nil {
            goto ERR
        }
        conn.outChan <- data
    }
    ERR:
        conn.Close()
}

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 添加商品部分原理和添加商品類別是一樣的,不過要比商品類別複雜,因為商品的屬性有很多,對應的資料庫中的欄位也就多了,添加商品還有個選項是上傳圖片,這一小塊內容會在下一篇博客中單獨說明,因為這涉及到一個知識點,就是Struts2實現文件上傳功能。其他廢話不多說了,現在開始完善添加商品部分的代碼: 1.  ...
  • 匿名函數基本格式: func= lambda i : ret # i 是形參,ret 是返回值 func() #調用匿名函數 內置函數: 1.reverse(註意,都是返回的貼帶起,如果想看內容,就要用for方法) 2.slice,format 3.bytes,bytearray # 切片 —— 字 ...
  • 代碼: 明顯的錯誤: 應改成 import java.util.*; 沒有理解java的基本概念 ...
  • 100-199 信息性狀態碼 100 continue 請繼續 101 switching protocols 切換協議,返回upgraded頭 200-299 成功狀態碼 200 ok 201 created 創建資源 202 accepted 請求已經接收到,不保證完成 203 non-auth... ...
  • 前面介紹while迴圈時,有個名叫year的整型變數頻繁出現,並且它是控制迴圈進出的關鍵要素。不管哪一種while寫法,都存在三處與year有關的操作,分別是“year = 0”、“year<limit”、“year++”。第一個“year = 0”用來給該變數初始賦值,第二個“year<limit ...
  • 多線程 unique_lock的使用 unique_lock的特點: 1,靈活。可以在創建unique_lock的實例時,不鎖,然後手動調用lock_a.lock()函數,或者std::lock(lock_a, …),來上鎖。當unique_lock的實例被析構時,會自動調用unlock函數,釋放鎖 ...
  • 前言 在 "上一篇" 中我們學習了結構型模式的解釋器模式(Interpreter Pattern)和迭代器模式(Iterator Pattern)。本篇則來學習下行為型模式的兩個模式,訪問者模式(Visitor Pattern)和中介者模式(Mediator Pattern)。 訪問者模式 簡介 訪 ...
  • 前言 每一種該語言在某些極限情況下的表現一般都不太一樣,那麼我常用的Java語言,在達到100萬個併發連接情況下,會怎麼樣呢,有些好奇,更有些期盼。 這次使用經常使用的順手的 netty NIO框架(netty 3.6.5.Final),封裝的很好,介面很全面,就像它現在的功能變數名稱 netty.io , ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...