浅析即时通讯开发中HTTP与WebSocket的关系

为什么要用WebSocket来替代HTTP

上一篇中提到WebSocket的目的就是解决传统Web网络传输中的双向通信的问题,HTTP1.1默认使用持久连接(persistent connection),在一个TCP连接上也可以传输多个Request/Response消息对,但是HTTP的基本模型还是一个Request对应一个Response。

以IM聊天系统为例,客户端要向服务器传送数据,同时服务器也需要实时的向客户端传送信息,一个聊天系统就是典型的双向通信。

要实现Web端双向通信,一般会使用这样几种解决方案:

    1)轮询(polling):轮询就会造成对网络和通信双方的资源的浪费,且非实时;
    2)长轮询:客户端发送一个超时时间很长的Request,服务器hold住这个连接,在有新数据到达时返回Response,相比#1,占用的网络带宽少了,其他类似;
    3)长连接:其实有些人对长连接的概念是模糊不清的,我这里讲的其实是HTTP的长连接(1)。如果你使用Socket来建立TCP的长连接(2),那么,这个长连接(2)跟我们这里要讨论的WebSocket是一样的,实际上TCP长连接就是WebSocket的基础,但是如果是HTTP的长连接,本质上还是Request/Response消息对,仍然会造成资源的浪费、实时性不强等问题。

WebSocket协议基础

WebSocket的目的是取代HTTP在双向通信场景下的使用,而且它的实现方式有些也是基于HTTP的(WS的默认端口是80和443)。现有的网络环境(客户端、服务器、网络中间人、代理等)对HTTP都有很好的支持,所以这样做可以充分利用现有的HTTP的基础设施,有点向下兼容的意味。

简单来讲,WS协议有两部分组成:握手和数据传输。

出于兼容性的考虑,WS的握手使用HTTP来实现(此文档中提到未来有可能会使用专用的端口和方法来实现握手),客户端的握手消息就是一个「普通的,带有Upgrade头的,HTTP Request消息」。

扫描二维码关注公众号,回复: 14487269 查看本文章

可以看到,前两行跟HTTP的Request的起始行一模一样,而真正在WS的握手过程中起到作用的是下面几个header域:

    1)Upgrade:upgrade是HTTP1.1中用于定义转换协议的header域。它表示,如果服务器支持的话,客户端希望使用现有的「网络层」已经建立好的这个「连接(此处是TCP连接)」,切换到另外一个「应用层」(此处是WebSocket)协议;
    2)Connection:HTTP1.1中规定Upgrade只能应用在「直接连接」中,所以带有Upgrade头的HTTP1.1消息必须含有Connection头,因为Connection头的意义就是,任何接收到此消息的人(往往是代理服务器)都要在转发此消息之前处理掉Connection中指定的域(不转发Upgrade域)。如果客户端和服务器之间是通过代理连接的,那么在发送这个握手消息之前首先要发送CONNECT消息来建立直接连接;即时通讯聊天软件app开发可以加蔚可云的v:weikeyun24咨询


    3)Sec-WebSocket-*:第7行标识了客户端支持的子协议的列表(关于子协议会在下面介绍),第8行标识了客户端支持的WS协议的版本列表,第5行用来发送给服务器使用(服务器会使用此字段组装成另一个key值放在握手返回信息里发送客户端);
    4)Origin:作安全使用,防止跨站攻击,浏览器一般会使用这个来标识原始域。


如果服务器接受了这个请求,可能会发送如下这样的返回信息,这是一个标准的HTTP的Response消息。101表示服务器收到了客户端切换协议的请求,并且同意切换到此协议。

在握手之前,客户端首先要先建立连接,一个客户端对于一个相同的目标地址(通常是域名或者IP地址,不是资源地址)同一时刻只能有一个处于CONNECTING状态(就是正在建立连接)的连接。

从建立连接到发送握手消息这个过程大致是这样的:

    1)客户端检查输入的Uri是否合法。
    2)客户端判断,如果当前已有指向此目标地址(IP地址)的连接(A)仍处于CONNECTING状态,需要等待这个连接(A)建立成功,或者建立失败之后才能继续建立新的连接:
      - PS:如果当前连接是处于代理的网络环境中,无法判断IP地址是否相同,则认为每一个Host地址为一个单独的目标地址,同时客户端应当限制同时处于CONNECTING状态的连接数;
      - PPS:这样可以防止一部分的DDOS攻击;
      - PPPS:客户端并不限制同时处于「已成功」状态的连接数,但是如果一个客户端「持有大量已成功状态的连接的」,服务器或许会拒绝此客户端请求的新连接。
    3)如果客户端处于一个代理环境中,它首先要请求它的代理来建立一个到达目标地址的TCP连接:
    例如,如果客户端处于代理环境中,它想要连接某目标地址的80端口,它可能要收现发送以下消息:
           CONNECT example.com:80 HTTP/1.1
           Host: example.com
    如果客户端没有处于代理环境中,它就要首先建立一个到达目标地址的直接的TCP连接。


如果上一步中的TCP连接建立失败,则此WebSocket连接失败。如果协议是wss,则在上一步建立的TCP连接之上,使用TSL发送握手信息。如果失败,则此WebSocket连接失败;如果成功,则以后的所有数据都要通过此TSL通道进行发送。

客户端握手信息的要求:

    握手必须是RFC2616中定义的Request消息
    此Request消息的方法必须是GET,HTTP版本必须大于1.1 :
      - 以下是某WS的Uri对应的Request消息:
      - ws://example.com/chat
      - GET /chat HTTP/1.1
    此Request消息中Request-URI部分(RFC2616中的概念)所定义的资型必须和WS协议的Uri中定义的资源相同。
    此Request消息中必须含有Host头域,其内容必须和WS的Uri中定义的相同。
    此Request消息必须包含Upgrade头域,其内容必须包含websocket关键字。
    此Request消息必须包含Connection头域,其内容必须包含Upgrade指令。
    此Request消息必须包含Sec-WebSocket-Key头域,其内容是一个Base64编码的16位随机字符。
    如果客户端是浏览器,此Request消息必须包含Origin头域,其内容是参考RFC6454。
    此Request消息必须包含Sec-WebSocket-Version头域,在此协议中定义的版本号是13。
    此Request消息可能包含Sec-WebSocket-Protocol头域,其意义如上文中所述。
    此Request消息可能包含Sec-WebSocket-Extensions头域,客户端和服务器可以使用此header来进行一些功能的扩展。
    此Request消息可能包含任何合法的头域。如RFC2616中定义的那些。

接收到Response握手消息之后:

    如果返回的返回码不是101,则按照RFC2616进行处理。如果是101,进行下一步,开始解析header域,所有header域的值不区分大小写;
    判断是否含有Upgrade头,且内容包含websocket;
    判断是否含有Connection头,且内容包含Upgrade;
    判断是否含有Sec-WebSocket-Accept头,其内容在下面介绍;
    如果含有Sec-WebSocket-Extensions头,要判断是否之前的Request握手带有此内容,如果没有,则连接失败;
    如果含有Sec-WebSocket-Protocol头,要判断是否之前的Request握手带有此协议,如果没有,则连接失败。

猜你喜欢

转载自blog.csdn.net/wecloud1314/article/details/126366479