前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >理解websocket的原理

理解websocket的原理

作者头像
theanarkh
发布2020-06-28 15:55:26
1.8K0
发布2020-06-28 15:55:26
举报
文章被收录于专栏:原创分享原创分享

首先我们看一下websocket的出现背景,我们知道http系列协议是建立在tcp上的,理论上,他是可以可以双向通信的。但是http1.1之前,服务器没有实现推送的功能。每次都是客户端请求,服务器响应。下面看一下http协议关于请求处理的发展。

  1. http1.0的时候,一个http请求的生命周期是客户端发起请求,服务器响应,断开连接。但是我们知道tcp协议的缺点就是,三次握手需要时间,再加上慢开始等特性,每一个http请求都这样的话,效率就很低。
  2. http1.1的时候,默认开启了长连接(客户端请求中设置了keep-alive头),服务器处理一个请求后,不会立刻关闭连接,而是会等待一定的时间。如果没有请求才关闭连接。这样浏览器不仅可以在一个tcp连接中,不断地发送请求(服务器也会限制一个连接上可以处理的请求阈值),甚至可以一次发很多个请求。这就是http1.1的管道化(pipeline)技术。但是他也有个问题,因为对于基于http协议的客户端来说,虽然他可以发很多请求出去,但是当一个请求对于的回包回来时,他却无法分辨是属于哪个请求的。所以回包只能按请求顺序返回,这就引来了另一个问题-线头阻塞(Head-of-Link Blocking)。并且http1.1虽然支持长连接,但是他不支持服务端推送(push)的能力。如果服务器有数据要给客户端,也只能等着客户端来取(pull)。
  3. 来到了http2.0,不仅实现了服务器推送,还使用了帧(iframe),流(stream)等技术解决了线头阻塞的问题,http2.0在一个tcp连接中,可以同时发送多个http请求,每个请求是一个流,一个流可以分成很多帧,有了标记号,服务器可以随便发送回包,客户端收到后,根据标记,重新组装就可以。 以上是http协议的关于请求的一些发展,而websocket就服务端推送提供了另外一种解决方案。他本质上是在tcp协议上封装的另一种应用层协议(websocket协议)。因为他是基于tcp的,所以服务端推送自然不是什么难题。但是在实现上,他并不是直接连接一个tcp连接,然后在上面传输基于websocket协议的数据包。他涉及到一个协议升级(交换)的过程。我们看看这个过程。

1 客户端发送协议升级的请求。在http请求加上下面的http头

代码语言:javascript
复制
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Sec-WebSocket-Key: k1kbxGRqGIBD5Y/LdIFwGQ==
Sec-WebSocket-Version: 13
Upgrade: websocket

2 服务器如果支持websocket协议的话,会返回101状态码表示同意协议升级,并且支持各种配置(如果服务器不支持某些功能或版本,或告诉客户端,客户端可以再次发送协议升级的请求)。服务会返回形如下面的http头(可以参考websocket协议)。

代码语言:javascript
复制
Connection: Upgrade
Sec-WebSocket-Accept: y73KZR4t+hqD6KKYbkx2tULfBsQ=
Upgrade: websocket

3 这样就完成了协议的升级,后续的数据通信,就是基于tcp连接之上,使用websocket协议封装的数据包。

下面我们通过wireshark来了解这个过程。首先我们启动一个服务器(ip:192.168.8.226)。

代码语言:javascript
复制
var http = require('http');
var fs = require('fs');
const WebSocket = require('ws');
// 如果在浏览器控制台进行测试,可以不起http服务器
const server = http.createServer(options,function(req,res){
    res.end(fs.readFileSync(`${__dirname}/websocket.html`));
}).listen(11111);

const wss = new WebSocket.Server({ server }); 
wss.on('connection', function connection(ws) {
  ws.on('message', function(message) {
    ws.send(message);
  });

  ws.send('get it');
});

我们可以直接在浏览器控制台进行测试

代码语言:javascript
复制
var ws = new WebSocket("ws://192.168.8.226:11111");
// 连接上后执行
ws.send(11)

这时候,我们看看wireshark的包。

首先看前面三条记录,这是tcp三次握手的数据包。这个我们都了解了,就不展示。接着看第四条记录。展开后如下。

我们看到建立tcp连接后,浏览器发了一个http请求,并带上了几个websocket的数据包。接着看下面的一条。

服务返回了同意升级协议或者说交换协议。从服务器代码中我们看到,在建立连接的时候我们给浏览器推送了一个get it的字符串。继续看上面的记录。

这就是服务器给浏览器推送的基于websocket协议的数据包。具体每个字段什么意思,参考websocket协议就可以。继续往下看一条记录是针对服务器推送的数据包的一个tcp的ack。最后我们可以顺便看看最后三条写着keep-alive的记录。这就是之前文章里讲到的tcp层的keep-alive。因为我们一直没有数据传输,所以tcp层会间歇性地发送探测包。我们可以看看探测包的结构。

有一个字节的探测数据。如果这时候我们发送一个数据包给服务器,又是怎样的呢。

白色背景的三条数据,分别是浏览器发送给服务器的数据,服务器推送回来的数据。tcp的ack。我们发现,服务器给浏览器推送的时候,浏览器会发送ack,但是浏览器给服务器发送的时候,服务器貌似没有返回ack。下面我们看看为什么。首先我们看浏览器发出的包。

再看看服务器给浏览器推送的数据包。

我们发现服务器(tcp)推送消息的时候把ack也带上了。而不是发送两个tcp包。这就是tcp的机制。tcp不会对每个包都发ack,他会累积确认(发ack),以减少网络的包,但是他也需要保证尽快地回复ack,否则就会导致客户端触发超时重传。tcp什么时候发送确认呢?比如需要发送数据的时候,或者超过一定时间没有收到数据包,或者累积的确认数量达到阈值等。既然研究了tcp,我们不妨多研究点,我们看一下,如果这时候关闭服务器会怎样。

服务器会发送一个重置包给浏览器,告诉他需要断开连接。继续,如果是浏览器自己调用close去关闭连接会怎样。

我们看到websocket首先会发送一个FIN包给服务器,然后服务器也会返回一个FIN包,然后才开始真正的四次挥手过程。并且四次挥手的第一个fin包是服务器发的。

我们再来看看安全版本的websocket。我们启动一个https服务器。

代码语言:javascript
复制
var https = require('https');
var fs = require('fs');
const WebSocket = require('ws');

var options = {
    key: fs.readFileSync('./server-key.pem'),
    ca: [fs.readFileSync('./ca-cert.pem')],
    cert: fs.readFileSync('./server-cert.pem')
};

const server = https.createServer(options,function(req,res){
    res.end(fs.readFileSync(`${__dirname}/websocket.html`));
}).listen(11111);

const wss = new WebSocket.Server({ server });

wss.on('connection', function connection(ws) {
  ws.on('message', function(message) {
    ws.send(message);
  });
});

然后在浏览器控制台执行。

代码语言:javascript
复制
var ws = new WebSocket("wss://192.168.8.226:11111");
ws.sned(11);

然后来看看wireshark。

首先建立tcp连接,然后建立tls连接。后续的数据通信就可以基于加密来进行了。不再重复。后续分析tls协议的时候再分析。

经过一系列的分析,我们对websocket协议应该有了更多的了解,最后再说一个关于websocket的点。我们发现如果在websocket连接上,一直不通信的话,websocket连接所维持的时间是依赖tcp实现的。因为我们发现tcp层会一直发送探测包。达到阈值之后,连接就会被断开。所以我们想维持websocket连接的话,需要自己去发送心跳包,比如ping,pong。

总结:本文分析了websocket的基本原理,但不涉及协议的内容,如需了解协议的内容,可以参考rfc文档。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-06-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 编程杂技 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档