梁越

websocket握手过程,和socket的区别

0 人看过

了解两者的区别和用途

如果是写后端的,或者服务器的,肯定都知道socket是什么,套接字,其实就是对TCP和UDP协议封装的接口,相当于是一个库,提供很多函数接口API供我们使用。

我们写的socket后端一般都是基于TCP/IP的,所以socket可以说是属于七层网络中的传输层。

TCP会有握手的过程

平时很多应用软件或者游戏服务器都可以基于socket进行通信,但是当我们要做一个web应用或者想通过浏览器和服务器进行通信的时候,你会发现你根本找不到一个用系统底层socket来通信的方法和例子,也就是说web肯本就不支持直接调用底层socket,这就很奇怪了。

为什么浏览器不支持socket?

浏览器不支持的socket的原因是不安全,本来你的机器上有防火墙来监听机器的每一个网络IO来防止攻击,但是如果你通过socket来和外部建立了通信,这些监听和防控措施就失效了,外部可以通过socket来做一些不为人知的操作,因为socket是无状态的,没有鉴权或者其他鉴别身份的方式。

例如像DDOS的攻击在socket上也会变得更容易进行和更高效

另一个原因是目前javascript也没有实现socket并提供接口

浏览器主流的协议

所以浏览器基本都统一使用http协议,这是在应用层的协议,封装在tcp和ip之上,加入一系列关键字来实现状态。这样的协议更安全了,但是仍然还有个问题,http不是全双工的,也不是基于长连接的,虽然http可以设置关键字keepalive来保证长连接,但是全双工的问题没有解决,服务器无法主动给客户端发送消息,只能由客户端来发起,然后服务端响应。

http为了能实现全双工,可以采用几种方法:

  1. 客户端不停轮询发送请求,服务端有消息时就会响应,这无疑是个比较愚蠢的方法,大部分时间可能都在无效的请求

  2. 客户端发送长轮询,也就是由服务端来负责这个轮询的过程,客户端只发送一个请求,服务端收到之后开始监听消息,有消息时才会response,这样看起来占用的网络带宽少了,但是仍然不够优雅

  3. 使用长连接,这里的长连接是TCP的长连接,不是HTTP的长连接,也就是websocket的实现

在上面第三种情况下websocket就出现了。

websocket和http是同一层的协议,其实socket本来就算是“长”连接,也是全双工,不过http为了场景应用,在关闭网页时就断开连接,设计成了基于短连接的(现在http1.1也设计成默认长连接了)。websocket也是封装于TCP之上的,websocket的握手过程首先也会有TCP握手的过程,然后进行两次Http请求就完成握手。

使用js客户端连接服务器,用wireshark来抓包分析

可以看到上面红色圈出来的是TCP三次握手的过程,后面绿色的两次HTTP请求是websocket独有的握手过程,我们分别看看里面是什么内容

客户端发送给服务端的HTTP请求:

请求

其中包含一些字段

GET / HTTP/1.1\r\n
Host: ip:port\r\n
Connection: Upgrade\r\n
Pragma: no-cache\r\n
Cache-Control: no-cache\r\n
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36\r\n
Upgrade: websocket\r\n
Origin: http://ip:port\r\n
Sec-WebSocket-Version: 13\r\n
Accept-Encoding: gzip, deflate\r\n
Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7,zh-TW;q=0.6\r\n
Cookie: express_sid=s%3ADe4N5jlcztTKSi5AWM9M00KdcO-5oJg3.YZ5eEfmR6D3bwVGHEB0vgAY9YKqIaACjNOvhJpxLaZ8; prefsHttp={}; token=t.BytEywMsCOqLBA9TMecL\r\n
Sec-WebSocket-Key: JZbp68YQb6yjCwxCcSD2mA==\r\n
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits\r\n
\r\n

首先这个请求必须是HTTP请求的GET方式,然后其他一些是HTTP常见的字段,关键的字段在

Connection: Upgrade\r\n
Upgrade: websocket\r\n
Sec-WebSocket-Version: 13\r\n
Sec-WebSocket-Key: JZbp68YQb6yjCwxCcSD2mA==\r\n
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits\r\n
\r\n

Connection表示浏览器通知服务器,如果可以的话,就升级到 WebSocket 协议

Upgrade字段表示将通信协议从HTTP/1.1转向该字段指定的协议websocket

Sec-WebSocket-Version用于指定websocket对应的版本

Sec-WebSocket-Key则是用于握手协议的密钥,是 Base64 编码的16字节随机字符串

Sec-WebSocket-Extensions指定服务器可用的协议层插件,可以用列表的形式,分号分隔

响应

HTTP/1.1 101 Switching Protocols\r\n
Upgrade: websocket\r\n
Connection: Upgrade\r\n
Sec-WebSocket-Accept: zSbvfZu5ePbUoQVwyv+fAcM2T9U=\r\n
Sec-WebSocket-Extensions: permessage-deflate; client_no_context_takeover; server_no_context_takeover\r\n
uWebSockets: 20\r\n

有几个关键字段

Upgrade: websocket\r\n
Connection: Upgrade\r\n
Sec-WebSocket-Accept: zSbvfZu5ePbUoQVwyv+fAcM2T9U=\r\n
Sec-WebSocket-Extensions: permessage-deflate; client_no_context_takeover; server_no_context_takeover\r\n
uWebSockets: 20\r\n

Connection和Upgrade上面解释了

Sec-WebSocket-Accept是服务器在浏览器提供的Sec-WebSocket-Key字符串后面,添加 RFC6456 标准规定的“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”字符串,然后再取 SHA-1 的哈希值,浏览器将对这个值进行验证,以证明确实是目标服务器回应了 WebSocket 请求

server_no_context_takeover是chrome浏览器可以接收的插件字段

uWebSockets是我服务端返回的服务器版本,可以忽略

经过这两个请求后,websocket就握手完成,后面的数据会经过TCP进行传输

所以可以看到,websocket和socket的区别是多了两个http请求验证,websocket和http是类似的协议,但是websocket是长连接