参考:https://www.cnblogs.com/songwenjie/p/8575579.html
demo:https://blog.csdn.net/WangMapleWang/article/details/87009945
首先抓包看下websocket请求
Websocket 握手
Request:
GET ws://localhost:8080/imWebSocket/test2 HTTP/1.1
Host: localhost:8080
Upgrade: websocket
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Mobile Safari/537.36
Origin: http://localhost:8080
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: JSESSIONID=DF854DB505EE3452FCBCF6284FF21725
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: jKmwUQ7MtDb456lQIUy3ew==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Response:
HTTP/1.1 101
Upgrade: websocket
Connection: upgrade
Sec-WebSocket-Accept: udwnrhEtaHeeHPCxCideCLz3DGM=
Sec-WebSocket-Extensions: permessage-deflate;client_max_window_bits=15
Date: Wed, 16 Jan 2019 02:15:08 GMT
WebSocket的连接都基于HTTP请求,那怎么区分这次请求是HTTP还是WebSocket呢?
Connection: Upgrade 表示要升级协议
Upgrade: websocket 升级到websocket
客户端想把HTTP协议转为WebSocket,通过更新头字段(Upgrade header)向服务器指定传输协议类型。这个就是Websocket的核心,告诉 Apache 、 Nginx 等服务器:注意啦,我发起的是Websocket协议。此时HTTP连接会被基于TCP/IP连接的WebSocket连接所取代。
WebSocket连接默认使用和HTTP(80)或者HTTPS(443)一样的端口,同样,你可以像部署Web服务一样使用其它端口。
Sec-WebSocket-Version: 13:表示websocket的版本。如果服务端不支持该版本,需要返回一个Sec-WebSocket-Versionheader,里面包含服务端支持的版本号。
WebSocket的连接是怎么握手验证的?
客户端请求中的Sec-WebSocket-Key是随机的,服务器端会用这些数据来构造出一个SHA-1的信息摘要。把Sec-WebSocket-Key加上一个魔幻字符串258EAFA5-E914-47DA-95CA-C5AB0DC85B11。使用 SHA-1 加密,之后进行 BASE-64编码,将结果作为 Sec-WebSocket-Accept 头的值,返回给客户端。客户端同样算法校验正确性完成握手。
def ws_accept_key(ws_key):
"""calc the Sec-WebSocket-Accept key by Sec-WebSocket-key
come from client, the return value used for handshake
:ws_key: Sec-WebSocket-Key come from client
:returns: Sec-WebSocket-Accept
"""
import hashlib
import base64
try:
magic = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
sha1 = hashlib.sha1()
sha1.update(ws_key + magic)
return base64.b64encode(sha1.digest())
except Exception as e:
return None
print 'udwnrhEtaHeeHPCxCideCLz3DGM='.__eq__(ws_accept_key('jKmwUQ7MtDb456lQIUy3ew=='))
wireshark抓包分析
几个概念:
SYN:同步比特,建立连接。
ACK:确认比特,置1表示这是一个确认的TCP包,0则不是。
PSH:推送比特,当发送端PSH=1时,接收端应尽快交付给应用进程。
Sequence number: 序列号
Acknowledgment number:确认号
tcp的三次握手:
第一次握手:
传输层Transmission Control Protocol(TCP),SYN置为1,客户端向服务端发送连接请求包。
第二次握手:
服务端接收到客户端的syn=1的连接请求,向客户端发送syn=1,确认号ack+1的报文
第三次握手
客户端验证服务端返回的报文,确认序列号(seq+1),标志位ack=1。确认成功再次想服务端发送ack=1的确认包表示链接成功,可以发送数据了
TCP Window
一般指的是TCP Receive Window,在TCP连接两端都有的缓冲区用于暂时保存到来的数据。在这个缓冲区中的数据会被发送到应用程序中, 为新到来的数据腾出空间. 如果这个缓冲满了, 那么数据的接收方会警告发送方在缓冲去清空之前已经不能在收取更多的数据了,
设备会在TCP Header信息中通知对方当前它的TCPWindows的大小.
在上图的Window size value 是12743, 这个包的发送方告诉连接的另一端: 他的TCP Receive Buffer是12743个字节. 标准TCP Window Size的最大值是65535.
TCP连接的任意一段都有自己的TCP Receive Window. 所以在任何时候, 这两个window的大小都可能会不一样. 比如说一个Web Server通常发送数据给用户, 而不是从用户那里获得数据. 基于这个原因, Web Server不需要像普通用户需要那么大的TCP Window. 所以Web Server会告知它的receive window是8192字节, 而客户端的window却是65535字节.
如何影响性能?
在一个文件传输的时候, 数据从一台机器流向另一台. 数据接收方需要阻止它的TCP Window降为0, 意味着window填满了。如果一个TCP Window变为0了, 或者接近0了, 这就会警告数据发送方没有更多空间来接受更多数据了。文件传输会停止, 直到收到一个update说buffer已经清空了。
TCP长连消息的顺序
在一次长连接中,服务器怎么知道消息的顺序呢?
这就涉及到tcp的序列号(Sequence Number)和确认号(Acknowledgment Number)
每一端的序列号都是从0开始,三次握手中是无数据包传输的但是接收的包中包含SYN或FIN标志位,故序列号也在原有值上+1
keep-alive
keep-alive 的原理就是 TCP 内嵌的一个心跳包。
以服务器端为例,如果当前 server 端检测到超过一定时间(默认是 7,200,000 milliseconds ,也就是 2 个小时)没有数据传输,那么会 向client 端发送一个 keep-alive packet (该 keep-alive packet 就是 ACK 和当前 TCP 序列号减一的组合),此时 client 端应该为以下三种情况之一:
-
client 端仍然存在,网络连接状况良好。此时 client 端会返回一个 ACK 。 server 端接收到 ACK 后重置计时器,在 2 小时后再发送探测。如果 2 小时内连接上有数据传输,那么在该时间基础上向后推延 2 个小时。
-
客户端异常关闭,或是网络断开。在这两种情况下, client 端都不会响应。服务器没有收到对其发出探测的响应,并且在一定时间(系统默认为
1000 ms )后重复发送 keep-alive packet ,并且重复发送一定次数( 2000 XP 2003 系统默认为 5 次 , Vista 后的系统默认为 10 次)。 -
客户端曾经崩溃,但已经重启。这种情况下,服务器将会收到对其存活探测的响应,但该响应是一个复位,从而引起服务器对连接的终止。