WebSocket小记

what's the WebSocket

  WebSocket 是一种通信协议,区别于 HTTP 协议,HTTP协议只能实现客户端请求,服务端响应的这种单项通信。而 WebSocket 可以实现客户端与服务端的双向通讯,最大也是最明显的区别就是可以做到服务端主动将消息推送给客户端。

WebSocket 和 普通 HTTP 请求不同点

WebSocket 由服务端主动推送数据到客户端,普通 HTTP 请求需要客户端每次项服务端发送请求后才能得到响应

  1. 长连接:只需要建立一次握手请求后就可以一直得到服务端推送的数据
  2. 数据格式轻量,性能开销小。客户端与服务端进行数据交换时,服务端到客户端的数据包头只有2到10字节,客户端到服务端需要加上另外4字节的掩码。HTTP每次都需要携带完整头部。
  3. 更好的二进制支持,可以发送文本,和二进制数据
  4. 没有同源限制,客户端可以与任意服务器通信
  5. 协议标识符是 ws(如果加密,则是wss),请求的地址就是后端支持 websocket 的 API。

 

实现 WebSocket

WebSocket 连接过程

  客户端发起 HTTP 握手,告诉服务端进行 WebSocket 协议通讯,并告知 WebSocket 协议版本。服务端确认协议版本,升级为 WebSocket 协议。之后如果有数据需要推送,会主动推送给客户端。

WebSocket 握手时的请求头和响应头

Accept-Encoding: gzip, deflate, br
Accept-Language: zh,zh-TW;q=0.9,en-US;q=0.8,en;q=0.7,zh-CN;q=0.6
Cache-Control: no-cache
Connection: Upgrade  # 表示要升级协议
Host: 127.0.0.1:3000
Origin: http://localhost:3000
Pragma: no-cache
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Sec-WebSocket-Key: bwb9SFiJONXhQ/A4pLaXIg==  # 对应服务端响应头的Sec-WebSocket-Accept,由于没有同源限制...客户端随机生成
Sec-WebSocket-Version: 13  # 表示websocket的版本。如果服务端不支持该版本,需要返回一个Sec-WebSocket-Versionheader,里面包含服务端支持的版本号。
Upgrade: websocket  # 要升级协议到websocket协议
请求头字段
Connection: Upgrade
Sec-WebSocket-Accept: 2jrbCWSCPlzPtxarlGTp4Y8XD20= # 用来告知服务器愿意发起一个websocket连接, 值根据客户端请求头的Sec-WebSocket-Key计算出来
Upgrade: websocket
响应头字段

 

WebSocket 连接过程参数

  • WebSocket.onopen: 连接成功后的回调
  • WebSocket.onclose: 连接关闭后的回调
  • WebSocket.onerror: 连接失败后的回调
  • WebSocket.onmessage: 客户端接收到服务端数据的回调
  • webSocket.bufferedAmount: 未发送至服务器的二进制字节数
  • WebSocket.binaryType: 使用二进制的数据类型连接
  • WebSocket.protocol : 服务器选择的下属协议
  • WebSocket.url : WebSocket 的绝对路径
  • WebSocket.readyState: 当前连接状态,对应的四个常量

 

Python 简单实现 WebSocket 服务端

# -*- coding: utf-8 -*-
import base64
import copy
import hashlib
import socket
import struct
import time

from threading import Thread



class WsServer:
    def __init__(self):
        self.users = set() # 用于存放连接的客户端

    def get_headers(self, data):
        '''将请求头转换为字典'''
        header_dict = {}
        data = str(data, encoding="utf-8")
        header, body = data.split("\r\n\r\n", 1)
        header_list = header.split("\r\n")
        # print("---" * 22, body)
        for idx, v in enumerate(header_list):
            if idx == 0:
                if len(v.split(" ")) == 3:
                    header_dict['method'], header_dict['url'], header_dict['protocol'] = v.split(" ")
            else:
                key, value = v.split(":", 1)
                header_dict[key] = value.strip()
        return header_dict

    # 等待用户连接
    def acce(self):
        conn, addr = sock.accept()
        # print("conn from ", conn, addr)
        self.users.add(conn)
        # 获取握手消息,magic string ,sha1加密
        # 发送给客户端
        data = conn.recv(1024)
        print("websocket client data: %s" % data)
        headers = self.get_headers(data)

        # 对请求头中的sec-websocket-key进行加密
        response_tpl = "HTTP/1.1 101 Switching Protocols\r\n" \
                       "Upgrade:websocket\r\n" \
                       "Connection: Upgrade\r\n" \
                       "Sec-WebSocket-Accept: %s\r\n" \
                       "WebSocket-Location: ws://%s%s\r\n\r\n"

        magic_string = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
        value = headers['Sec-WebSocket-Key'] + magic_string
        ac = base64.b64encode(hashlib.sha1(value.encode('utf-8')).digest())
        response_str = response_tpl % (ac.decode('utf-8'), headers['Host'], headers['url'])

        # 此处可加入校验
        
        conn.send(bytes(response_str, encoding='utf-8'), )
        logger.info('Websocket shake hand success: %s' % conn)


    def send_msg(self, msg_bytes):
        """
        WebSocket服务端向客户端发送消息
        :param conn: 客户端连接到服务器端的socket对象,即: conn,address = socket.accept()
        :param msg_bytes: 向客户端发送的字节
        :return:
        """
        token = b"\x81"  # 接收的第一字节,一般都是x81不变
        length = len(msg_bytes)
        if length < 126:
            token += struct.pack("B", length)
        elif length <= 0xFFFF:
            token += struct.pack("!BH", 126, length)
        else:
            token += struct.pack("!BQ", 127, length)

        msg = token + msg_bytes

        for conn in self.users:
            # 如果出错就是客户端断开连接
            try:
                conn.send(msg)
            except Exception as e:
                print("%s 连接关闭: %s" % (conn, e))
                # 删除断开连接的记录
                self.users.remove(conn)


    # 循环等待客户端建立连接
    def th(self):
        while True:
            self.acce()



sock = socket.socket()
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(("0.0.0.0", 3002)) # 监听3002端口
sock.listen(5)

WS = WsServer()

Thread(target=WS.th).start()
View Code

 

HTML 中使用 JS 实现 WebSocket 客户端

<!DOCTYPE HTML>
<html>
   <head>
   <meta charset="utf-8">
   <title>websocket通信客户端</title>
       <script type="text/javascript">
         function WebSocketTest()
         {
            if ("WebSocket" in window)
            {
               // 打开一个 web socket
               var ws = new WebSocket("ws://10.10.6.91:5678");
               
               // 连接建立后的回调函数
               ws.onopen = function()
               {
               // Web Socket 已连接上,使用 send() 方法发送数据
                  ws.send("admin:123456");
                  alert("正在发送:admin:123456");
               };
               
               // 接收到服务器消息后的回调函数
               ws.onmessage = function (evt) 
               { 
                  var received_msg = evt.data;
                  if (received_msg.indexOf("sorry") == -1) {
                    alert("收到消息:"+received_msg);
                  }
                  
               };
               
               // 连接关闭后的回调函数
               ws.onclose = function()
               { 
                  // 关闭 websocket
                  alert("连接已关闭..."); 
               };
            }
            else
            {
               // 浏览器不支持 WebSocket
               alert("您的浏览器不支持 WebSocket!");
            }
         }
         
      </script>
   </head>

   <body onload="WebSocketTest()">

   </body>
</html>
View Code

 

WebSocket 添加握手自定义参数

  由于 WebSocket 没有修改请求头的方法,所以可在 url 后面接问号然后添加自定义参数

举个例子

var token='dcvuahsdnfajw12kjfasfsdf34'
var  ws = new WebSocket("ws://" + url?token + "/webSocketServer");

 

心跳

  待更新...

 

 

                

posted @ 2020-04-21 19:24  ''竹先森゜  阅读(1650)  评论(0编辑  收藏  举报