关于 WebSocket 和 HTTP 区别的思考以及一个最简单的 WebSocket 的客户端和服务器实现

简介: 关于 WebSocket 和 HTTP 区别的思考以及一个最简单的 WebSocket 的客户端和服务器实现

笔者之前与一位同事研究了 Cypress 的 visit 方法,其源码实现最终是调用了 WebSocket 向 visit 参数里指定的 website 通行并获取数据,见下图变量 ev.data 的值。



我这位同事的研究成果,通过 Joplin 笔记记录如下如下。


于是笔者心里有一个疑问,为什么 Cypress 的 visit 方法选择了 WebSocket 作为与目标网站的通信技术呢?为什么不直接走 HTTP 协议,比如用 ES6 原生支持的 fetch 去访问目标网站呢?


要回答这个问题,我们先要理解到底什么是 WebSocket,以及它与 HTTP 相比较的优缺点。


诚然,WebSocket 可以在用户的浏览器和服务器之间打开交互式通信会话,浏览器可以向服务器发送消息并接收事件驱动的响应,而无需通过轮询服务器的方式以获得响应。



WebSocket 基于 TCP 连接,在服务器和浏览器间提供了全双工通信功能,即服务器可以主动推送数据到浏览器端,而这在 HTTP 协议中是不可能实现的,HTTP 协议只支持浏览器到服务器端的 Request - Response 方式,即浏览器客户端如果想查询服务器端是否有最新的事件发生,则只能采取低效的轮询方式进行。


举个例子,当用户向服务器发送请求时,该请求以 HTTP 或 HTTPS 的形式发送,服务器收到请求后向客户端发送响应,每个请求都与相应的响应相关联,发送响应后连接关闭,每个 HTTP 或 HTTPS 请求每次都会建立与服务器的新连接,并且在获得响应后,连接会自行终止。



笔者注:HTTP 请求头部的 Connection: keep-alive 字段,可以实现连接重用的需求吗?





当启用 Keep-Alive 时,客户端和服务器同意为后续请求或响应保持连接打开。


默认情况下,HTTP 连接在数据事务结束时关闭。 这意味着客户端创建一个新连接来请求页面的每个文件,服务器在发送数据后关闭这些 TCP 连接。


但是,如果服务器需要同时响应多个 HTTP 请求并为每个新的 TCP 连接提供一个文件,则站点页面的加载时间将会增加。 这可能会导致糟糕的用户体验。


为了克服这个问题,网站所有者需要启用 Keep-Alive 标头来限制新连接的数量。


通过打开 Keep-Alive 连接标头,客户端可以通过单个 TCP 连接下载所有内容,例如 JavaScript、CSS、图像和视频,而不是为每个文件发送不同的请求。


这是一张演示 Keep-Alive 工作原理的图片:




问题:启用 Keep-Alive 头部字段后,重用的是 HTTP 连接,还是 TCP 连接?


WebSocket 并不是将 HTTP 的设计完全推翻重建,而是在 HTTP 的基础上增添了一些逻辑来,管理客户端和服务器端的流。这些流的内容也是 HTTP 请求和响应,保留了旧语义,只是编码和打包方式不同。



了解了理论知识后,我们动手开发一套最简单的 WebSocket 服务器端和客户端实现。


WebSocket 服务器端实现


var app = require('express')();
var server = require('http').Server(app);
var io = require('socket.io')(server);
var defaultPort = 3001;
var port = process.env.PORT || defaultPort;
var i = 0;
console.log("Server is listening on port: " + defaultPort);
server.listen(port);
io.on('connection', function (socket) {
  console.log("connect comming from client: " + socket.id);
  socket.emit('messages_jerry', { hello: 'world greeting from Server!' });
  socket.on('messages', function (data) {
    console.log("data received from Client:" + JSON.stringify(data,2,2));
  });
});


代码实现包含了4个关键点:


  1. 服务器监听在默认的 3001 端口上。


  1. 一旦 WebSocket 客户端有发送到 3001 端口上的连接请求时,代码第 12 行的 on 监听函数触发,监听的事件名称为 connection,然后在监听函数的实现体里,打印出客户端连接的 id 值。


  1. 服务器端接收了客户端的链接后,向客户端通过第 15 行的 emit 方法,发送一个 messages_jerry 的事件,以及一个 JSON 对象作为事件负载。


  1. 第 17 行服务器端监听在 messages 事件上的监听函数触发时,说明接收到了从客户端发送过来的事件,在监听函数里打印出客户端传递过来的数据。




WebSocket 客户端实现


// #!/usr/bin/env node
const io = require('socket.io-client');
var socket = io.connect('http://localhost:3001');
socket.on('messages_jerry', function (data) {
    console.log("data sent from Server:" + JSON.stringify(data,2,2));
    socket.emit('messages', { my: 'data sent from Client' });
  });
socket.on('connect', function (socket2) {
    console.log('Connection with Server established!');
        socket.emit('messages', 'Client has established connection with Server');
});


代码的关键点:


  1. 客户端通过 connect 方法向 WebSocket 服务器发起连接请求


  1. 连接成功建立后,客户端第 10 行的 on 监听函数触发,该函数监听在 connect 事件上,会在 Web Socket 连接成功建立后自动触发。


  1. 客户端在第 12 行调用 emit 向服务器发送一个 messages 事件。


  1. 客户端监听在 messages_jerry 的监听函数触发,说明服务器端有数据到达。使用第 6 行的 console.log 语句打印出这个数据。


  1. 在第 7 行代码,客户端调用 emit,向服务器端发送一个请求,通知服务器自己已经收到了服务器发送过来的数据。




使用命令行 node wsServer.js 启动服务器端,看到如下输出:




新开一个命令行窗口,使用 node wsClient.js 启动客户端,能看到客户端打印出的成功建立连接,以及从服务器端发送过来的数据:



切换回服务器端,红色高亮的内容,就是客户端与服务器端建立连接之后,服务器端新打印出的数据:



回到本文开头抛出的问题:


问题1


为什么 Cypress 的 visit 方法选择了 WebSocket 作为与目标网站的通信技术呢?为什么不直接走 HTTP 协议,比如用 ES6 原生支持的 fetch 去访问目标网站呢?


笔者猜测,是不是因为 Cypress 里某些 API,比如 cy.XXX 需要利用到 WebSocket 这种全双工通信的特性才能够充分发挥作用?



问题2


那么问题又来了,在我们 cy.visit('http://xxx.com') 的代码里,如果说最终 Cypress 通过 WebSocket 协议向 http://xxx.com 发送数据报,但是 http://xxx.com 不支持 WebSocket 怎么办?就像本文前一部分介绍的例子一样,WebSocket 需要客户端和服务器端同时支持才行。



那么会不会 cy.visit 和 visit 参数里指定的 webSite 之间,还存在着一个中间层?




问题3


WebSocket Connection,HTTP Connection,TCP connection,这三者的区别和联系是什么?


参考资料


相关文章
|
17天前
|
开发者
HTTP状态码是由网页服务器返回的三位数字响应代码,用于表示请求的处理结果和状态
HTTP状态码是由网页服务器返回的三位数字响应代码,用于表示请求的处理结果和状态
23 1
|
1月前
|
缓存 数据安全/隐私保护 UED
代理服务器在HTTP请求中的应用:Ruby实例
代理服务器在HTTP请求中的应用:Ruby实例
|
1月前
|
开发者 Python
深入解析Python `httpx`源码,探索现代HTTP客户端的秘密!
深入解析Python `httpx`源码,探索现代HTTP客户端的秘密!
71 1
|
1月前
|
前端开发 网络协议 物联网
Django Web:搭建Websocket服务器(入门篇)
Django Web:搭建Websocket服务器(入门篇)
38 1
|
2月前
|
存储 运维 Java
函数计算产品使用问题之如何使用Python的requests库向HTTP服务器发送GET请求
阿里云Serverless 应用引擎(SAE)提供了完整的微服务应用生命周期管理能力,包括应用部署、服务治理、开发运维、资源管理等功能,并通过扩展功能支持多环境管理、API Gateway、事件驱动等高级应用场景,帮助企业快速构建、部署、运维和扩展微服务架构,实现Serverless化的应用部署与运维模式。以下是对SAE产品使用合集的概述,包括应用管理、服务治理、开发运维、资源管理等方面。
100 8
|
1月前
|
Linux Python
【Azure 应用服务】Azure App Service For Linux 上实现 Python Flask Web Socket 项目 Http/Https
【Azure 应用服务】Azure App Service For Linux 上实现 Python Flask Web Socket 项目 Http/Https
|
1月前
|
移动开发 网络协议 编译器
实战案例3:C语言实现的HTTP服务器
实战案例3:C语言实现的HTTP服务器
46 0
|
2月前
|
Go 开发者
golang的http客户端封装
golang的http客户端封装
31 0
|
Web App开发 前端开发

热门文章

最新文章