websocket 是一个长连接协议,全双工通信,主要应用在及时通信:实时聊天,游戏,在线文档等等。

简单示例

客户端

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <input id="input" type="text" />
    <button onclick="send()">Send</button>
    <pre id="output"></pre>

    <script>
        var input = document.getElementById("input");
        var output = document.getElementById("output");

        //调用websocket对象建立连接:
        //参数:ws/wss(加密)://ip:port (字符串)
        var websocket = new WebSocket("ws://xxx.xxx.xxx.xxx:8080/echo");
        console.log(websocket.readyState) // 0 
        // readyState 
        // 0 链接还没有建立(正在建立链接)
        // 1 链接建立成
        // 2 链接正在关闭
        // 3 链接已经关闭

        // 监听链接开启事件
        websocket.onopen = function () {
            output.innerHTML += "Status: " + websocket.readyState + "\n";
            console.log(websocket.readyState)
        }

        // 监听服务端消息推送事件
        websocket.onmessage = function (back) {
            output.innerHTML += "Server: " + back.data + "\n";
            console.log(back.data)
        }

        // 监听连接错误信息
        websocket.onerror = function (evt, e) {
            console.log('Error occured: ' + evt.data);
        };

        //监听连接关闭
        websocket.onclose = function (evt) {
            console.log("Disconnected");
        };

        // 绑定按钮点击事件
        function send() {
            websocket.send(input.value)
            input.value = "";
        }

    </script>

</body>

</html>

服务端

package main

import (
	"fmt"
	"net/http"

	"github.com/gorilla/websocket"
)

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
}

func main() {
	http.HandleFunc("/echo", func(w http.ResponseWriter, r *http.Request) {
		conn, _ := upgrader.Upgrade(w, r, nil)

		for {
             // messageType is either TextMessage == 1 or BinaryMessage == 2
			msgType, msg, err := conn.ReadMessage()
			if err != nil {
				return
			}

			fmt.Printf("%s sent: %s\n", conn.RemoteAddr(), string(msg))

			if err = conn.WriteMessage(msgType, msg); err != nil {
				return
			}
		}
	})

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		http.ServeFile(w, r, "client.html")
	})

	fmt.Println("server is running on :8080...")

	http.ListenAndServe(":8080", nil)
}
go run server.go
http://xxx.xxx.xxx.xxx:8080/

请求头

GET ws://localhost:8080/echo HTTP/1.1
Host: localhost:8080
Origin: http://localhost:8080
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: h8MnjGsXMtnoAcyCAn+V5Q==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

响应头

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: n+hJTwLJbZTrnaXspOIBJZYIDh8=
/echo

因为ws是基于http之上的,所以默认情况下ws的端口是80,安全协议wss端口是443。

WebSocket协议的数据可以是普通的文本数据,也可以是二进制数据,数据量相对较大时,还可以分片多帧进行数据发送与接收。

Sec-WebSocket-KeySec-WebSocket-AcceptSec-WebSocket-AcceptSec-WebSocket-Key258EAFA5-E914-47DA-95CA-C5AB0DC85B11SHA1转成base64字符串

websocket RFC标准 https://www.rfc-editor.org/rfc/rfc6455.txt

数据帧格式

最小单位是帧(frame)
  • 发送端:将消息切割成多个帧,发送给服务端;
  • 服务端:接收消息帧,并将关联的帧重新组装成完整的消息数据;
FIN (Final)RSV1, RSV2, RSV3 (Reserved)OpcodeMaskMask是1Masking-keyPayload length前7 个比特位xxxxxMasking-keyPayload data扩展数据占 x 字节,应用数据占 y 字节扩展数据:应用数据:

如何使用wireshark抓取websocket协议

1、wireshark无法抓取本地服务的包,也就是流量必须经过网卡才行。

2、分析 --> 启用的协议 --> websocket

3、必须在建立ws连接之前就开始抓,这一点的确容易忽略。

websocket

正常的效果如下


可以看到,客户端发给服务端的信息被MASK了,而服务端发给客户端的信息是明文的。

websocket
werwer

发出去的载荷是经过Mask转换的,此时wireshark并不知道如何解析它,而服务端返回的载荷确实明文的

关于心跳检测ping/pong

0x90xA
github.com/gorilla/websocketpingconn.ReadMessage()
github.com/gorilla/websocket