一.HTTP与TCP的关系
- HTTP属于应用层协议,主要解决如何包装数据;
- 在传输层使用TCP协议,主要解决数据如何在网络中传输;
- 在网络层使用IP协议,主要解决网络路由和寻址问题;
- HTTP把TCP分割好的各种数据包封装到IP包里面传送给接收方。
二.短连接、长连接、websocket、postmessage作用
1.短连接:(占用较多内存和带宽)。
在HTTP/1.0中,默认使用的是短连接。浏览器和服务器每进行一次HTTP操作,就建立一次连接,但服务器向发送响应数据后,将关闭TCP,中断连接。WEB网站的http服务一般都用短链接,因为长连接对于服务端来说会耗费一定的资源,而像WEB网站这么频繁的成千上万甚至上亿客户端的连接用短连接会更省一些资源;并发量大,但每个用户无需频繁操作情况下,使用短连接更好。
2.长连接:(省时间,少带宽)。
但从 HTTP/1.1起,默认使用长连接。会在响应头有加入这行代码: Connection:keep-alive;TCP连接在发送后将仍然保持打开状态,浏览器可以继续通过相同的连接发送请求,即把多个HTTP请求合并为一个。也就是说,在一个HTTP连接中,可以发送多个Request,接收多个Response。但是请记住 Request = Response
, 在HTTP中永远是这样,也就是说一个request只能有一个response。而且这个response也是被动的,不能主动发起。数据库的连接用长连接, 如果用短连接频繁的通信会造成socket错误,而且频繁的socket 创建也是对资源的浪费。
3.WebSocket:(无需循环等待(长轮询),CPU和内存资源不以客户端数量衡量,而是以客户端事件数衡量。)
WebSocket是HTML5出的协议,IE10以上浏览器支持,跟HTTP协议没有关系。只是借用了HTTP的协议来完成一部分握手。HTTP是不支持持久连接的(长连接,循环连接的不算),Websocket是一个持久化的协议;
4.PostMessage:可以安全地实现跨源通信。IE8以上支持。主窗口通过postMessage
API向异域的窗口发送数据,在异域的页面脚本中始终监听message
事件,当获取主窗口数据时处理数据或者以同样的方式返回数据从而实现跨窗口的异域通讯。
三.Websocket握手过程(参考:https://www.cnblogs.com/oshyn/p/3574497.html)
Websocket协议定义为ws和wss协议,分别为普通请求和基于SSL的安全传输,占用端口与http协议系统,ws为80端口,wss为443端口。
ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ]
wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ]
其中port是可选项,query前接“?”。
1.当建立一个Websocket连接时,连接状态是CONNECTING,客户端通过HTTP请求与WebSocket服务端协商升级协议,需要提供host、port、resource-name和一个是否是安全连接的标记,也就是一个WebSocket URI,采用的是标准的HTTP报文格式,且只支持GET
方法。
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
这个升级的HTTP请求头中的字段顺序是可以随便的。与普通HTTP请求相比多了一些字段。
- Sec-WebSocket-Protocol:字段表示客户端可以接受的子协议类型,也就是在Websocket协议上的应用层协议类型。上面可以看到客户端支持chat和superchat两个应用层协议,当服务器接受到这个字段后要从中选出一个协议返回给客户端。
- Upgrade:告诉服务器这个HTTP连接是升级的Websocket连接。
- Connection:告知服务器当前请求连接是升级的。
- Origin:该字段是用来防止客户端浏览器使用脚本进行未授权的跨源攻击,这个字段在WebSocket协议中非常重要。服务器要根据这个字段判断是否接受客户端的Socket连接。可以返回一个HTTP错误状态码来拒绝连接。
- Sec-WebSocket-Key:为了表示服务器同意和客户端进行Socket连接,服务器端需要使用客户端发送的这个Key进行校验,然后返回一个校验过的字符串给客户端,客户端验证通过后才能正式建立Socket连接。服务器验证方法是:首先进行 Key + 全局唯一标示符(GUID)“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”连接起来,然后将连接起来的字符串使用SHA-1哈希加密,再进行base64加密,将得到的字符串返回给客户端作为握手依据。其中GUID是一个对于不识别WebSocket的网络端点不可能使用的字符串。
发送请求的要求(排查错误的方法/连接失败的原因):
- 请求的WebSocket URI必须要是定义的有效的URI。
- 如果客户端已经有一个WebSocket连接到远程服务器端,不论是否是同一个服务器,客户端必须要等待上一个连接关闭后才能发送新的连接请求,也就是同一客户端一次只能存在一个WebSocket连接。如果想同一个服务器有多个连接,客户端必须要串行化进行。如果客户端检测到多个到不同服务器的连接,应该限制一个最大连接数,在web浏览器中应该设定最多可以打开的标签页的数目。这样可以防止到远程服务器的DDOS攻击,但这是对到多个服务器的连接,如果是到同一个服务器连接,并没有数目限制。
- 如果使用了代理服务器,那么客户端建立连接的时候需要告知代理服务器向目标服务器打开TCP连接。
- 如果连接没有打开,一定是某一方出现错误,此时客户端必须要关闭再次连接的尝试。
- 连接建立后,握手必须要是一个有效的HTTP请求
- 请求的方式必须是GET,HTTP协议的版本至少是1.1
- Upgrade字段必须包含而且必须是"websocket",Connection字段必须内容必须是“Upgrade”
- Sec-Websocket-Version必须,而且必须是13
2.服务器返回响应头,客户端验证通过后将建立连接,此时状态为OPEN。
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat
Sec-WebSocket-Accept是
根据客户端请求首部的Sec-WebSocket-Key
计算出来的:
计算公式为:
- 将
Sec-WebSocket-Key
跟258EAFA5-E914-47DA-95CA-C5AB0DC85B11
拼接。 - 通过SHA1计算出摘要,并转成base64字符串。
3.判断服务器返回的响应头是否同意握手的依据:
- 首行返回的是HTTP/1.1协议版本和状态码101,表示变换协议(Switching Protocol)
- Upgrade 和 Connection:这两个字段是服务器返回的告知客户端同意使用升级并使用websocket协议,用来完善HTTP升级响应
- Sec-WebSocket-Accept:服务器端将加密处理后的握手Key通过这个字段返回给客户端表示服务器同意握手建立连接。
- Sec-Websocket-Procotol:服务器选择的一个应用层协议。
浏览器解析响应头字段,如果Sec-WebSocket-Accept字段的信息符合要求就会建立连接,同时就可以发送WebSocket的数据帧了。如果该字段不符合要求或者为空或者HTTP状态码不为101,就不会建立连接。
服务器端响应步骤:
- 解析握手请求头:获取握手依据Key并进行处理,检测HTTP的GET请求和版本是否准确,Host字段是否有权限,Upgrade字段中websocket是一个与大小写无关的ASCII字符串,Connection字段是一个大小写无关的"Upgrade"ASCII字符串,Websocket协议版本必须为13,其他的关于Origin、Protocol和Extensions可选。
- 发送握手响应头:检测是否是wss协议连接,如果是就是用TLS握手连接,否则就是普通连接。服务器可以添加额外的验证信息到客户端进行验证。当进行一系列验证之后,服务器必须返回一个有效的HTTP响应头。响应头中每一行一个字段,结束必须为“\r\n”,使用的ABNF语法。
除了上述必要头字段之外,其他的HTTP协议定义的字段都可以使用,如Set-Cookie等。
同样类似AJAX的是,WebSocket
对象也有一个readyState
属性,用来表示对象实例当前所处的链接状态,有四个值:
- 0:表示正在连接中(CONNECTING);
- 1:表示连接成功,可以通信(OPEN);
- 2:表示连接正在关闭(CLOSING);
- 3:表示连接已经关闭或打开连接失败(CLOSED);
四.PostMessage(跨源通信)
以上这些跨域技术都只适用于客户端请求异域服务端资源的情景。而除此之外,有时候我们还需要在异域的两个客户端之间共享数据,例如页面与内嵌iframe窗口通讯,页面与新打开异域页面通讯。
任何域都可以通过postMessage
发送跨域信息,因此只要有消息通过postMessage
发送过来,脚本都会接收并进行处理。如何鉴别发送至页面的信息呢?答案是通过 message
事件监听函数的事件对象,我们称它为event
,该对象有三个属性:
- data:值为其他window传递过来的对象;
- origin:值为消息发送方窗口的域名;
- source:值为对发送消息的窗口对象的引用;
很显然的,我们应该着重检测event
对象的origin
属性,建立一个白名单对origin
属性进行检测通常是一个明智的做法。
-------------------------------<本节完>------------------------------------------