[golang] WebSocket

442 阅读2分钟

WebSocket 是 HTML5 的重要特性,它实现了基于浏览器的远程 socket,它使浏览器和服务器可以进行全双工通信(全双工通信又称为双向同时通信,即通信的双方可以同时发送和接收信息的信息交互方式)。

在 WebSocket 出现之前,为了实现即时通信,采用的技术都是“轮询”,使得浏览器需要对服务器不断发出 HTTP Request,这样会占用大量带宽。

WebSocket 采用了一些特殊的报头,使得浏览器和服务器只需要做一个握手的动作,就可以在浏览器和服务器之间建立一条连接通道(保持在活动状态)。可以使用 JavaScript 来向连接写入或从中接收数据,就像在使用一个常规的 TCP Socket 一样,解决了 Web 实时化的问题。

WebSocket URL 的起始输入是 ws:// 或是 wss://(在 SSL 上)。一个带有特定报头的 HTTP 握手被发送到了服务器端,接着在服务器端或是客户端就可以通过 JavaScript 来使用某种套接口(socket),这一套接口可被用来通过事件句柄异步地接收数据。

image.png

WebSocket 原理

WebSocket 的协议颇为简单,在第一次 handshake 通过以后,连接便建立成功。其后的通讯数据都是以 \x00 开头,以 \xFF 结尾。在客户端,这个是透明的,WebSocket 组件会自动将原始数据“掐头去尾”。

image.png

请求中的 Sec-WebSocket-Key 是随机的,这是一个经过 base64 编码后的数据。服务器端接收到这个请求之后需要把这个字符串连接上一个固定的字符串。

f7cb4ezEAl6C3wRaU6JORA==258EAFA5-E914-47DA-95CA-C5AB0DC85B11

对该字符串先用 sha1 安全散列算法计算出二进制的值,然后用 base64 对其进行编码,即可以得到握手后的字符串。

rE91AJhfC+6JdVcVXOGJEADEJdQ=

将之作为响应头 Sec-WebSocket-Accept 的值反馈给客户端。

go 实现 WebSocket

client

<html>

<head></head>

<body>
    <script type="text/javascript">
        var sock = null;
        var wsuri = "ws://127.0.0.1:1234";

        window.onload = function () {

            console.log("onload");

            sock = new WebSocket(wsuri);

            sock.onopen = function () {
                console.log("connected to " + wsuri);
            }

            sock.onclose = function (e) {
                console.log("connection closed (" + e.code + ")");
            }

            sock.onmessage = function (e) {
                console.log("message received: " + e.data);
            }
        };

        function send() {
            var msg = document.getElementById('message').value;
            sock.send(msg);
        };
    </script>
    <h1>WebSocket Echo Test</h1>
    <form>
        <p>
            Message: <input id="message" type="text" value="Hello, world!">
        </p>
    </form>
    <button onclick="send();">Send Message</button>
</body>

</html>

当握手成功后,会触发 WebSocket 对象的 onopen 事件,告诉客户端连接已经成功建立。客户端一共绑定了四个事件。

  1. onopen 建立连接后触发;
  2. onmessage 收到消息后触发;
  3. onerror 发生错误时触发;
  4. onclose 关闭连接时触发;

server

func main() {
    http.Handle("/", websocket.Handler(Echo))

    if err := http.ListenAndServe(":1234", nil); err != nil {
        log.Fatal("ListenAndServe:", err)
    }
}

func Echo(ws *websocket.Conn) {
    var err error

    for {
        var reply string

        if err = websocket.Message.Receive(ws, &reply); err != nil {
            fmt.Println("Can't receive")
            break
        }

        fmt.Println("Received back from client: " + reply)

        msg := "Received:  " + reply
        fmt.Println("Sending to client: " + msg)

        if err = websocket.Message.Send(ws, msg); err != nil {
            fmt.Println("Can't send")
            break
        }
    }
}