zoukankan      html  css  js  c++  java
  • WebSocket原理及技术简介

    摘要:

    WebSocket用于在Web浏览器和服务器之间进行任意的双向数据传输的一种技术。WebSocket协议基于TCP协议实现,包含初始的握手过程,以及后续的多次数据帧双向传输过程。其目的是在WebSocket应用和WebSocket服务器进行频繁双向通信时,可以使服务器避免打开多个HTTP连接进行工作来节约资源,提高了工作效率和资源利用率。

    一、WebSocket诞生需求

    互联网发展的早期,网站上只是一些静态展示页面。用户请求(Request)网站页面,网站回复(Response)页面内容给用户浏览器。因为需求简单,所以也没有很复杂的协议过程。这种形式的Request/Response交互流程如下图所示:

    WebSocket原理及技术简介_Java

     随着互联网技术的发展,带宽逐步提高,用户数也越来越庞大。对互联网的呈现内容提出了要求,随之出现了动态页面技术,对同一个页面,页面的某些部分对不同的访问用户,呈现的内容不同。相关的实现技术有CGI、ASP、PHP、JSP等。由于访问量的增加,WEB服务器同时处理的用户数也达到了万(10K)以上级别,这就是C10K问题:"The C10K problem"。为了缓解服务器压力,每次Request/Response后连接(TCP连接)继续保持,以及对同一个TCP连接,多次复用Request/Response的方法(也称为Pipeline)也提了出来。这就是HTTP/1.1协议中长连接的主要内容。

        伴随移动互联网的发展,大量移动终端和其上的APP应用接入网络,HTML5技术也提了出来,以便支持WEB上的音视频播放、实时游戏、实时聊天等。催生了这样一个需求,当服务器有更新时,需要立即将数据发送给客户端,这就是基于服务器端的推送技术。

        WEBSOCKET之前的解决方法大概这么几种: 1)轮询:客户端设置一个时间间隔,时间到以后,向服务器发送request询问有无新数据,服务器立即返回response,如果有更新则携带更新的数据。2)长连接(long poll): 和轮询相似,但是为阻塞模式的轮询,客户端请求新的数据request, 服务器会阻塞请求,直到有新数据后才返回response给客户端;然后客户端再重复此过程。这两种方式的特点,不断的建立HTTP连接,然后发送请求request,之后服务器等待处理。服务端体现的是一种被动性,同时这种处理方式,非常耗费网络带宽和服务器资源。

        服务器向客户端推送更新时,因为被动性,对低延迟的应用体验不好;因为request/response的交互方式,对网络带宽和服务器带来了额外的负担(例如多次请求的HTTP头部, TCP连接复用会导致的Head-of-Line Blocking线头阻塞[2]等)。如果在单一的TCP连接中,使用双向通信(全双工通信)就能很好的解决此问题。这就是WebSocket技术的缘由。

    二、WebSocket技术及协议

    (一)WebSocket技术的优点有:

    1)通过第一次HTTP Request建立了连接之后,后续的数据交换都不用再重新发送HTTP Request,节省了带宽资源;

    2) WebSocket的连接是双向通信的连接,在同一个TCP连接上,既可以发送,也可以接收;

    3)具有多路复用的功能(multiplexing),也即几个不同的URI可以复用同一个WebSocket连接。这些特点非常类似TCP连接,但是因为它借用了HTTP协议的一些概念,所以被称为了WebSocket。

    (二)WebSocket协议

    WebSocket看成是一种类似TCP/IP的socket技术;此socket在Web应用中实现,并获得了和TCP/IP通信一样灵活方便的全双向通信功能。

    WebSocket协议由RFC 6455定义。协议分为两个部分: 握手阶段和数据通信阶段。

    WebSocket为应用层协议,其定义在TCP/IP协议栈之上。WebSocket连接服务器的URI以"ws"或者"wss"开头。ws开头的默认TCP端口为80,wss开头的默认端口为443。

    1、握手阶段

    客户端和服务器建立TCP连接之后,客户端发送握手请求,随后服务器发送握手响应即完成握手阶段。如下图所示:

    WebSocket原理及技术简介_Java

    客户端握手请求类似如下:

    #首先看一个标准的websocket请求头
    
    GET /chat HTTP/1.1
    Upgrade: websocket
    Connection: Upgrade
    Host: 127.0.0.1:8001
    Origin: http://127.0.0.1:8001
    Sec-WebSocket-Key: hj0eNqbhE/A0GkBXDRrYYw==
    Sec-WebSocket-Version: 13

    可以看到使用http1.1 协议上面是标准的http的请求信息

    但是熟悉http的小伙伴可以明显看出这里多出了一些信息。用于实现协议升级

    Upgrade: websocket
    Connection: Upgrade
    origin:xxxx
    Sec-WebSocket-Key: hj0eNqbhE/A0GkBXDRrYYw==
    Sec-WebSocket-Version: 13

    upgrade websocket用于告诉服务器此连接需要升级到websocket。

    而下面的Sec-WebSocket-Key是客户端也就是浏览器或者其他终端随机生成一组16位的随机base64编码的字节串。

    最后Sec-WebSocket-Version就是当前使用协议的版本号了。

    服务器在接受到上面的请求之后,会返回一个response 头包完成握手。

    HTTP/1.1 101 Switching Protocols
    Content-Length: 0
    Upgrade: websocket
    Sec-Websocket-Accept: ZEs+c+VBk8Aj01+wJGN7Y15796g=
    Server: TornadoServer/4.5.1
    Connection: Upgrade
    Date: Wed, 21 Jun 2017 03:29:14 GMT

    由Sec-Websocket-Accept的key完成校验。 我贴一个生成的Sec-Websocket-Accept的代码大家感受一下

    import socket, base64, hashlib
    
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind(('127.0.0.1', 9527))
    sock.listen(5)
    # 获取客户端socket对象
    conn, address = sock.accept()
    # 获取客户端的【握手】信息
    data = conn.recv(1024)
    print(data)
    """
    GET /ws HTTP/1.1
    
    Host: 127.0.0.1:9527
    
    Connection: Upgrade
    
    Pragma: no-cache
    
    Cache-Control: no-cache
    
    User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36
    
    Upgrade: websocket
    
    Origin: http://localhost:63342
    
    Sec-WebSocket-Version: 13
    
    Accept-Encoding: gzip, deflate, br
    
    Accept-Language: zh-CN,zh;q=0.9
    
    Cookie: session=a6f96c20-c59e-4f33-84d9-c664a2f29dfc
    
    Sec-WebSocket-Key: MAZZU5DPIxWmhk/UWL2+BA==
    
    Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
    
    
    """
    # 以下动作是有websockethandler完成的
    # magic string为:258EAFA5-E914-47DA-95CA-C5AB0DC85B11
    
    
    def get_headers(data):
        header_dict = {}
        header_str = data.decode("utf8")
        for i in header_str.split("
    "):
            if str(i).startswith("Sec-WebSocket-Key"):
                header_dict["Sec-WebSocket-Key"] = i.split(":")[1].strip()
    
        return header_dict
    
    
    headers = get_headers(data)  # 提取请求头信息
    
    magic_string = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
    #Sec-WebSocket-Key: MAZZU5DPIxWmhk/UWL2+BA==
    value = headers['Sec-WebSocket-Key'] + magic_string    #字符串类型
    ac = base64.b64encode(hashlib.sha1(value.encode('utf-8')).digest())
    
    # 对请求头中的sec-websocket-key进行加密
    response_tpl = "HTTP/1.1 101 Switching Protocols
    " 
                   "Upgrade:websocket
    " 
                   "Connection: Upgrade
    " 
                   "Sec-WebSocket-Accept: %s
    " 
                   "WebSocket-Location: ws://127.0.0.1:9527
    
    "
    print(ac.decode('utf-8'))
    response_str = response_tpl % (ac.decode('utf-8'))
    # 响应【握手】信息
    conn.send(response_str.encode("utf8"))
    #
    while True:
        msg = conn.recv(8096)
        print(msg)
  • 相关阅读:
    C/C++ 构造函数不能是虚函数
    C/C++ STL迭代器失效
    Linux fork函数
    算法和数据结构 限流算法
    数据库 redis底层实现
    C/C++ 虚析构函数
    万物皆可 Serverless 之使用云函数 SCF 快速部署验证码识别接口
    万物皆可 Serverless 之使用云函数 SCF+COS 免费运营微信公众号
    腾讯云云函数 SCF 日志检索最佳实践
    江娱互动「世界争霸」产品迁移至腾讯云云函数的实践
  • 原文地址:https://www.cnblogs.com/fengchong/p/10273998.html
Copyright © 2011-2022 走看看