美文网首页
go的websocket实现

go的websocket实现

作者: 小ocean | 来源:发表于2021-04-12 11:48 被阅读0次

1、WebSocket 是什么?

一种协议,链接客服端和服务端的协议,

2、WebSocket 优势

  1. WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。
  2. 在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
  3. 浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。
  4. 你可以通过 send() 方法来向服务器发送数据,并通过 onmessage 事件来接收服务器返回的数据
  5. websocket分为握手和数据传输阶段,即进行了HTTP握手 + 双工的TCP连接

3、WebSocket 的握手

这里参考地址:https://www.cnblogs.com/yjf512/archive/2013/02/18/2915171.html

客户端发送消息:
    GET /chat HTTP/1.1
    Host: server.example.com
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
    Origin: http://example.com
    Sec-WebSocket-Version: 13

服务端返回消息:
    HTTP/1.1 101 Switching Protocols
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

4、WebSocket传输协议

websocket的数据传输使用的协议是:


image.png

参数的具体说明在这:
FIN:1位,用来表明这是一个消息的最后的消息片断,当然第一个消息片断也可能是最后的一个消息片断;

RSV1, RSV2, RSV3: 分别都是1位,如果双方之间没有约定自定义协议,那么这几位的值都必须为0,否则必须断掉WebSocket连接;

Opcode:4位操作码,定义有效负载数据,如果收到了一个未知的操作码,连接也必须断掉,以下是定义的操作码:
* %x0 表示连续消息片断
* %x1 表示文本消息片断
* %x2 表未二进制消息片断
* %x3-7 为将来的非控制消息片断保留的操作码
* %x8 表示连接关闭
* %x9 表示心跳检查的ping
* %xA 表示心跳检查的pong
* %xB-F 为将来的控制消息片断的保留操作码

Mask:1位,定义传输的数据是否有加掩码,如果设置为1,掩码键必须放在masking-key区域,客户端发送给服务端的所有消息,此位的值都是1;

Payload length: 传输数据的长度,以字节的形式表示:7位、7+16位、或者7+64位。如果这个值以字节表示是0-125这个范围,那这个值就表示传输数据的长度;如果这个值是126,则随后的两个字节表示的是一个16进制无符号数,用来表示传输数据的长度;如果这个值是127,则随后的是8个字节表示的一个64位无符合数,这个数用来表示传输数据的长度。多字节长度的数量是以网络字节的顺序表示。负载数据的长度为扩展数据及应用数据之和,扩展数据的长度可能为0,因而此时负载数据的长度就为应用数据的长度。

Masking-key:0或4个字节,客户端发送给服务端的数据,都是通过内嵌的一个32位值作为掩码的;掩码键只有在掩码位设置为1的时候存在。
Payload data: (x+y)位,负载数据为扩展数据及应用数据长度之和。
Extension data:x位,如果客户端与服务端之间没有特殊约定,那么扩展数据的长度始终为0,任何的扩展都必须指定扩展数据的长度,或者长度的计算方式,以及在握手时如何确定正确的握手方式。如果存在扩展数据,则扩展数据就会包括在负载数据的长度之内。

Application data:y位,任意的应用数据,放在扩展数据之后,应用数据的长度=负载数据的长度-扩展数据的长度。

go 中具体代码实现

服务端

package main

import (
    "sync"

    "202104_01_test/server_websocket"
)

var (
    wg sync.WaitGroup
)

func init() {
    // 设置WaitGroup需要等待的数量,只要有一个服务器出现错误都停止服务器
    wg.Add(1)
}

func main() {
    //有对应的HTML前端页面:C:\Users\Administrator\Desktop\sort\tmp\web_socakte.html
    server_websocket.StartServer(&wg, "127.0.0.1:7777")

    // 阻塞等待,以免main线程退出
    wg.Wait()
}

package server_websocket

import (
    "fmt"
    "net/http"
    "sync"
    "time"

    "202104_01_test/impl"
    "github.com/gorilla/websocket"
    "public.com/Framework/logMgr"
)

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

    //  w.Write([]byte("hello"))
    wsConn *websocket.Conn
    err    error
    conn   *impl.Connection
    data   []byte
)

func wsHandler(w http.ResponseWriter, r *http.Request) {
    // 完成ws协议的握手操作
    // 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(10 * 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 StartServer(wg *sync.WaitGroup, listen string) {
    defer wg.Done()

    // 开启监听
    msg := fmt.Sprintf("server_websocket begins to listen on:%s...", listen)
    fmt.Println(msg)
    logMgr.InfoLog(msg, "")

    http.HandleFunc("/", wsHandler)
    err := http.ListenAndServe(listen, nil)
    if err != nil {
        panic(fmt.Sprintf("server_websocket.ListenAndServe, err:%v", err))
    }
}

// 停止服务器
func StopServer() {
    fmt.Println("WebSocketServer is closing.")
}

//参考:https://blog.csdn.net/dodod2012/article/details/81744526

package impl

import (
    "errors"
    "fmt"
    "github.com/gorilla/websocket"
    "sync"
)

type Connection struct {
    wsConnect *websocket.Conn
    inChan    chan []byte
    outChan   chan []byte
    closeChan chan byte

    mutex    sync.Mutex // 对closeChan关闭上锁
    isClosed bool       // 防止closeChan被关闭多次
}

func InitConnection(wsConn *websocket.Conn) (conn *Connection, err error) {
    conn = &Connection{
        wsConnect: wsConn,
        inChan:    make(chan []byte, 1000),
        outChan:   make(chan []byte, 1000),
        closeChan: make(chan byte, 1),
    }
    // 启动读协程
    go conn.readLoop()
    // 启动写协程
    go conn.writeLoop()
    return
}

func (conn *Connection) ReadMessage() (data []byte, err error) {

    select {
    case data = <-conn.inChan:
    case <-conn.closeChan:
        err = errors.New("connection is closeed")
    }
    return
}

func (conn *Connection) WriteMessage(data []byte) (err error) {

    select {
    case conn.outChan <- data:
    case <-conn.closeChan:
        err = errors.New("connection is closeed")
    }
    return
}

func (conn *Connection) Close() {
    // 线程安全,可多次调用
    err := conn.wsConnect.Close()
    if err != nil {
        panic(fmt.Sprintf("server_websocket.Close, err:%v", err))
    }
    // 利用标记,让closeChan只关闭一次
    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.wsConnect.ReadMessage(); err != nil {
            goto ERR
        }

        fmt.Printf("ReadMessage=%v\n ", string(data))
        //阻塞在这里,等待inChan有空闲位置
        select {
        case conn.inChan <- data:
        case <-conn.closeChan: // closeChan 感知 conn断开
            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.wsConnect.WriteMessage(websocket.TextMessage, data); err != nil {
            goto ERR
        }
    }

ERR:
    conn.Close()
}

客服端


<!DOCTYPE html>
<html>
<head>
    <title>go websocket</title>
    <meta charset="utf-8" />  
</head>
<body>
    <script type="text/javascript">
        var wsUri ="ws://127.0.0.1:7777/ws"; 
        var output;  
        
        function init() { 
            output = document.getElementById("output"); 
            testWebSocket(); 
        }  
     
        function testWebSocket() { 
            websocket = new WebSocket(wsUri); 
            websocket.onopen = function(evt) { 
                onOpen(evt) 
            }; 
            websocket.onclose = function(evt) { 
                onClose(evt) 
            }; 
            websocket.onmessage = function(evt) { 
                onMessage(evt) 
            }; 
            websocket.onerror = function(evt) { 
                onError(evt) 
            }; 
        }  
     
        function onOpen(evt) { 
            writeToScreen("CONNECTED"); 
           // doSend("WebSocket rocks"); 
        }  
     
        function onClose(evt) { 
            writeToScreen("DISCONNECTED"); 
        }  
     
        function onMessage(evt) { 
            writeToScreen('<span style="color: blue;">RESPONSE: '+ evt.data+'</span>'); 
           // websocket.close(); 
        }  
     
        function onError(evt) { 
            writeToScreen('<span style="color: red;">ERROR:</span> '+ evt.data); 
        }  
     
        function doSend(message) { 
            writeToScreen("SENT: " + message);  
            websocket.send(message); 
        }  
     
        function writeToScreen(message) { 
            var pre = document.createElement("p"); 
            pre.style.wordWrap = "break-word"; 
            pre.innerHTML = message; 
            output.appendChild(pre); 
        }  
     
        window.addEventListener("load", init, false);  
        function sendBtnClick(){
            var msg = document.getElementById("input").value;
            doSend(msg);
            document.getElementById("input").value = '';
        }
        function closeBtnClick(){
            websocket.close(); 
        }
    </script>
    <h2>WebSocket Test</h2>  
    <input type="text" id="input"></input>
    <button onclick="sendBtnClick()" >send</button>
    <button onclick="closeBtnClick()" >close</button>
    <div id="output"></div>     
    
</body>

仔细看就会发现服务端实现HTTP链接函数:upgrader.Upgrade

#验证、组装参数...
#返回数据
    if _, err = netConn.Write(p); err != nil {
        netConn.Close()
        return nil, err
    }

相关文章

  • go+gin+websocket实现轮询

    go+gin+webSocket实现轮询 js[^webSocket实时获取系统时间] go router ser...

  • 在 go 中实现 websocket 服务

    目标:了解 websocket ,能够使用 golang 来实现 websocket 服务 要求:了解 go 基本...

  • go实现websocket

    用go实现一个简单的websocke的简单demo 1、服务端 2、客户端 3运行的效果

  • go websocket 问题(Hijacker)

    go websocket 问题(Hijacker) 在写websocket包的时候发现一个比较有趣问题!go 使用...

  • Go实现Websocket消息推送

    1.websocket 简介 以往浏览器要获取服务端数据,都是通过发送 HTTP 请求,然后等待服务端回应的。也就...

  • Go语言实现的WebSocket

    最终的效果如下Web端上传的信息Web端得到的打印的信息 WebSocket协议是基于TCP的一种新的网络协议。它...

  • go

    我们使用golang构建websocket,支持高性能、高并发,websocket不用多介绍,go自带原生webs...

  • go websocket

    获得相应包支持:go get golang.org/x/net/websocket 解释:(1)http.Hand...

  • Go实现基于WebSocket的弹幕服务

    拉模式和推模式 拉模式 1、数据更新频率低,则大多数请求是无效的2、在线用户量多,则服务端的查询负载高3、定时轮询...

  • 2022-05-21 Springboot 系列 (10) -

    Springboot+WebSocket(一) | 使用 Spring 封装的 WebSocket 实现 JSON...

网友评论

      本文标题:go的websocket实现

      本文链接:https://www.haomeiwen.com/subject/ejiqlltx.html