zoukankan      html  css  js  c++  java
  • Title

     一、含义

      WebSocket 是一种在单个TCP连接上进行全双工通讯的协议。

        WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输

    二、Websocket 产生背景

      很多网站为了实现推送技术,所用的技术都是轮询

      轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。

      传统的模式的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。

      比较新的技术去做轮询的效果是Comet

      Comet 是一种用于web的推送技术,能使服务器实时地将更新的信息传送到客户端,而无须客户端发出请求,目前有两种实现方式,长轮询iframe流

      长轮询 是在打开一条连接以后保持,等待服务器推送来数据再关闭的方式。

      iframe流 方式是在页面中插入一个隐藏的iframe,利用其src属性在服务器和客户端之间创建一条长链接,服务器向iframe传输数据(通常是HTML,内有负责插入信息的javascript),来实时更新页面。

      Comet技术虽然可以双向通信,但依然需要反复发出请求。

        Comet中,普遍采用的长链接,也会消耗服务器资源。

     

      Websocket使用和 HTTP 相同的 TCP 端口,可以绕过大多数防火墙的限制。

      默认情况下,Websocket协议使用80端口;运行在TLS之上时,默认使用443端口。

      WebSocket 是独立的、创建在 TCP 上的协议。

      Websocket 通过 HTTP/1.1 协议的101状态码进行握手。

      为了创建Websocket连接,需要通过浏览器发出请求,之后服务器进行回应,这个过程通常称为“握手”(handshaking)。

     三、Python编写Socket服务端

     1.客户端
    客户端:浏览器(必须要有socket包或者类库,一般自带,个别浏览器没有,所以websocket有局限性)
      <script type="text/javascript">
        # 创建连接
        # 发送消息
        # 接收验证消息 下面的一句话做了上面的三件事
        var socket = new WebSocket("ws://127.0.0.1:8002/xxoo"); 
    			
        # 与服务器端连接成功后,自动执行 
        socket.onopen = function () {
    
        };
    			
        # 服务器端向客户端发送数据时,自动执行
        socket.onmessage = function (event) {
    
        };
      </script>
    
    2.服务端
    import socket
     
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind(('127.0.0.1', 8002))
    sock.listen(5)
    # 获取客户端socket对象
    conn, address = sock.accept()
    # 获取客户端的【握手】信息
    data = conn.recv(1024)
    ...
    ...
    ...
    conn.send('响应【握手】信息')
    

       请求握手时,浏览器发来的请求信息

    GET /chatsocket HTTP/1.1
    Host: 127.0.0.1:8002
    Connection: Upgrade
    Pragma: no-cache
    Cache-Control: no-cache
    Upgrade: websocket
    Origin: http://localhost:63342
    Sec-WebSocket-Version: 13
    Sec-WebSocket-Key: mnwFxiOlctXFN/DeMt1Amg==
    Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

    请求和响应的【握手】信息需要遵循规则:

    • 从请求【握手】信息中提取 Sec-WebSocket-Key
    • 利用magic_string 和 Sec-WebSocket-Key 进行hmac1加密,再进行base64加密
    • 将加密结果响应给客户端

    注:magic string为:258EAFA5-E914-47DA-95CA-C5AB0DC85B11

        其中 Sec-WebSocket-Key: mnwFxiOlctXFN/DeMt1Amg== 是浏览器给服务端的一个随机字符串(mnwFxiOlctXFN/DeMt1Amg==),服务端需要给这个随机字符串进行加密,然后在给浏览器send数据的时候带上这个加密后的随机字符串,如果浏览器可以解密,那么就是说服务端支持websocket,进而进行建立连接通信。

      提取Sec-WebSocket-Key值并加密:

    import socket
    import base64
    import hashlib
     
    def get_headers(data):
        """
        将请求头格式化成字典
        :param data:
        :return:
        """
        header_dict = {}
        data = str(data, encoding='utf-8')
     
        for i in data.split('
    '):
            print(i)
        header, body = data.split('
    
    ', 1)
        header_list = header.split('
    ')
        for i in range(0, len(header_list)):
            if i == 0:
                if len(header_list[i].split(' ')) == 3:
                    header_dict['method'], header_dict['url'], header_dict['protocol'] = header_list[i].split(' ')
            else:
                k, v = header_list[i].split(':', 1)
                header_dict[k] = v.strip()
        return header_dict
     
     
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind(('127.0.0.1', 8002))
    sock.listen(5)
     
    conn, address = sock.accept()
    data = conn.recv(1024)
    headers = get_headers(data) # 提取请求头信息
    # 对请求头中的sec-websocket-key进行加密
    response_tpl = "HTTP/1.1 101 Switching Protocols
    " 
          "Upgrade:websocket
    " 
          "Connection: Upgrade
    " 
          "Sec-WebSocket-Accept: %s
    " 
          "WebSocket-Location: ws://%s%s
    
    "
    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'))
    ...
    ...
    ...

      此外还有服务端需要实现封包解包的功能,而客户端浏览器的JS已经帮我们实现了封包解包了。

      封包解包详见:武沛齐

    网络编程:

      day31--多进程和多线程

      day32--同步锁、死锁递归锁、Event对象

      day33--IO模型

      day34--selectors、队列、生产者和消费者模型

     
  • 相关阅读:
    几种不同风格的Toast
    [置顶] 如何访问web文件夹之外的文件
    30天自制操作系统笔记(九十)
    tomcat install on Linux
    共享内存使用的基本思路和接口
    30天自制操作系统笔记(九十)——源码
    storm单机版安装配置
    新安装XAMPP,phpMyAdmin错误:#1045
    TI-Davinci开发系列之二使用CCS5.2TI Simulator模拟环境调试DSP程序
    ffmpeg的logo, delogo滤镜参数设置
  • 原文地址:https://www.cnblogs.com/guotianbao/p/8052555.html
Copyright © 2011-2022 走看看