<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                協議 websocket是個二進制協議,需要先通過Http協議進行握手,從而協商完成從Http協議向websocket協議的轉換。一旦握手結束,當前的TCP連接后續將采用二進制websocket協議進行雙向雙工交互,自此與Http協議無關。 可以通過這篇知乎了解一下websocket協議的基本原理:[《WebSocket 是什么原理?為什么可以實現持久連接?》](https://www.zhihu.com/question/20215561)。 粘包 我們開發過TCP服務的都知道,需要通過協議decode從TCP字節流中解析出一個一個請求,那么websocket又怎么樣呢? websocket以message為單位進行通訊,本身就是一個在TCP層上的一個分包協議,其實并不需要我們再進行粘包處理。但是因為單個message可能很大很大(比如一個視頻文件),那么websocket顯然不適合把一個視頻作為一個message傳輸(中途斷了前功盡棄),所以websocket協議其實是支持1個message分多個frame幀傳輸的。 我們的瀏覽器提供的編程API都是message粒度的,把frame拆幀的細節對開發者隱蔽了,而服務端websocket框架一般也做了同樣的隱藏,會自動幫我們收集所有的frame后拼成messasge再回調,所以結論就是: websocket以message為單位通訊,不需要開發者自己處理粘包問題。 golang實現 golang官方標準庫里有一個websocket的包,但是它提供的就是frame粒度的API,壓根不能用。 不過官方其實已經認可了一個準標準庫實現,它實現了message粒度的API,讓開發者不需要關心websocket協議細節,開發起來非常方便,其文檔地址:https://godoc.org/github.com/gorilla/websocket。 開發websocket服務時,首先要基于http庫對外暴露接口,然后由websocket庫接管TCP連接進行協議升級,然后進行websocket協議的數據交換,所以開發時總是要用到http庫和websocket庫。 上述websocket文檔中對開發websocket服務有明確的注意事項要求,主要是指: 讀和寫API不是并發安全的,需要啟動單個goroutine串行處理。 關閉API是線程安全的,一旦調用則阻塞的讀和寫API會出錯返回,從而終止處理。 在我的實現中,我對websocket進行了封裝,簡化應用層開發的復雜度,主要思路是: 請求和應答都放入管道中排隊。 讀協程阻塞讀websocket,將message放入請求隊列。 寫協程阻塞讀應答channel,將message寫給websocket。 如何處理websocket錯誤和主動關閉websocket呢? 讀/寫協程調用websocket若返回錯誤,那么直接調用websocket的Close關閉連接,協程退出。(此時用戶可能仍舊持有連接對象,繼續向下閱讀!) websocket連接關閉后,用戶通常正阻塞在讀/寫channel上而不知情,所以每個連接配套一個closeChan專門用于喚醒用戶代碼,關閉websocket連接同時關閉closeChan,這會令<-closeChan總是立即返回。 因為上一條設計,所以用戶讀/寫channel時總是select同時監聽channel和closeChan,以便實時感知到websocket連接的關閉。 用戶可以主動關閉連接,websocket連接重復Close沒有影響,而closeChan重復關閉會報錯,所以通過一個上鎖的狀態位判重處理。 描述比較繁瑣,實際并不復雜,看看我的代碼吧: https://github.com/owenliang/go-websocket。 server.go ~~~ package main import ( "errors" "fmt" "net/http" "sync" "time" "github.com/gorilla/websocket" ) // http升級websocket協議的配置 var wsUpgrader = websocket.Upgrader{ // 允許所有CORS跨域請求 CheckOrigin: func(r *http.Request) bool { return true }, } // 客戶端讀寫消息 type wsMessage struct { messageType int data []byte } // 客戶端連接 type wsConnection struct { wsSocket *websocket.Conn // 底層websocket inChan chan *wsMessage // 讀隊列 outChan chan *wsMessage // 寫隊列 mutex sync.Mutex // 避免重復關閉管道 isClosed bool closeChan chan byte // 關閉通知 } func (wsConn *wsConnection) wsReadLoop() { for { // 讀一個message msgType, data, err := wsConn.wsSocket.ReadMessage() if err != nil { goto error } req := &wsMessage{ msgType, data, } // 放入請求隊列 select { case wsConn.inChan <- req: case <-wsConn.closeChan: goto closed } } error: wsConn.wsClose() closed: } func (wsConn *wsConnection) wsWriteLoop() { for { select { // 取一個應答 case msg := <-wsConn.outChan: // 寫給websocket if err := wsConn.wsSocket.WriteMessage(msg.messageType, msg.data); err != nil { goto error } case <-wsConn.closeChan: goto closed } } error: wsConn.wsClose() closed: } func (wsConn *wsConnection) procLoop() { // 啟動一個gouroutine發送心跳 go func() { for { time.Sleep(2 * time.Second) if err := wsConn.wsWrite(websocket.TextMessage, []byte("heartbeat from server")); err != nil { fmt.Println("heartbeat fail") wsConn.wsClose() break } } }() // 這是一個同步處理模型(只是一個例子),如果希望并行處理可以每個請求一個gorutine,注意控制并發goroutine的數量!!! for { msg, err := wsConn.wsRead() if err != nil { fmt.Println("read fail") break } fmt.Println(string(msg.data)) err = wsConn.wsWrite(msg.messageType, msg.data) if err != nil { fmt.Println("write fail") break } } } func wsHandler(resp http.ResponseWriter, req *http.Request) { // 應答客戶端告知升級連接為websocket wsSocket, err := wsUpgrader.Upgrade(resp, req, nil) if err != nil { return } wsConn := &wsConnection{ wsSocket: wsSocket, inChan: make(chan *wsMessage, 1000), outChan: make(chan *wsMessage, 1000), closeChan: make(chan byte), isClosed: false, } // 處理器 go wsConn.procLoop() // 讀協程 go wsConn.wsReadLoop() // 寫協程 go wsConn.wsWriteLoop() } func (wsConn *wsConnection) wsWrite(messageType int, data []byte) error { select { case wsConn.outChan <- &wsMessage{messageType, data}: case <-wsConn.closeChan: return errors.New("websocket closed") } return nil } func (wsConn *wsConnection) wsRead() (*wsMessage, error) { select { case msg := <-wsConn.inChan: return msg, nil case <-wsConn.closeChan: } return nil, errors.New("websocket closed") } func (wsConn *wsConnection) wsClose() { wsConn.wsSocket.Close() wsConn.mutex.Lock() defer wsConn.mutex.Unlock() if !wsConn.isClosed { wsConn.isClosed = true close(wsConn.closeChan) } } func main() { http.HandleFunc("/ws", wsHandler) http.ListenAndServe("0.0.0.0:7777", nil) } ~~~ client.html ~~~ <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script> window.addEventListener("load", function(evt) { var output = document.getElementById("output"); var input = document.getElementById("input"); var ws; var print = function(message) { var d = document.createElement("div"); d.innerHTML = message; output.appendChild(d); }; document.getElementById("open").onclick = function(evt) { if (ws) { return false; } ws = new WebSocket("ws://localhost:7777/ws"); ws.onopen = function(evt) { print("OPEN"); } ws.onclose = function(evt) { print("CLOSE"); ws = null; } ws.onmessage = function(evt) { print("RESPONSE: " + evt.data); } ws.onerror = function(evt) { print("ERROR: " + evt.data); } return false; }; document.getElementById("send").onclick = function(evt) { if (!ws) { return false; } print("SEND: " + input.value); ws.send(input.value); return false; }; document.getElementById("close").onclick = function(evt) { if (!ws) { return false; } ws.close(); return false; }; }); </script> </head> <body> <table> <tr><td valign="top" width="50%"> <p>Click "Open" to create a connection to the server, "Send" to send a message to the server and "Close" to close the connection. You can change the message and send multiple times. </p> <form> <button id="open">Open</button> <button id="close">Close</button> <input id="input" type="text" value="Hello world!"> <button id="send">Send</button> </form> </td><td valign="top" width="50%"> <div id="output"></div> </td></tr></table> </body> </html> ~~~ 首先運行server.go,然后打開client.html頁面,即可體驗所有流程:
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看