体验 Node.js 的 net 模块

news2024/11/24 6:52:13

1. 创建客户端

模拟 http 请求,将接收到的响应体内容原样输出,接收完毕后,关闭连接

1.1 最终效果

接收到的数据:

解析后的数据:

1.2 流程说明

初始化:

  • 创建客户端 const client = net.createConnection(options, cb),其中回调 cb 会在连接成功时触发一次。
  • 注册监听函数 client.on('data', () => {})
  • 发送请求 client.write()

data 事件处理逻辑:

data 事件在每次接收到来自服务端的消息时,都会触发。

第一次接收到响应消息时,解析出响应行、响应头信息。之后每次监听到响应消息,都是剩下的响应体的消息,只要不断拼接到响应体中即可。

每次接收完消息后,都要判断来自服务端的消息是否已经接收完毕了,如果接收完毕了,则需要断开链接。

断开连接:

判断是否需要断开连接,可以借助解析出的响应头的 Content-Length 字段来判断。

  • 「1」服务端返回的响应体消息的总字节数:Content-Length 的值
  • 「2」当前接收到的消息的总字节数:Buffer.from(目前接收到的服务端消息体字符串, 'utf-8').byteLength

一旦 「2」 >= 「1」,那么意味着服务端吐给我们的数据,我们都拿到了,此时需要断开可服务端的连接。

客户端主动断开连接的方法:client.end()

1.3 源码

const net = require("net");
const responseData = {line: null, // 响应行header: null, // 响应头body: "", // 响应体
};
const separator = "\r\n"; // 分隔符

// 创建客户端
const client = net.createConnection({port: 80, // HTTP 协议,默认端口 80host: "www.baidu.com", // default val => 'localhost'},() => {// 连接成功之后的回调console.log("连接成功~");}
);

// 发送请求
client.write(`GET / HTTP/1.1
Connection: keep-alive
Host: www.baidu.com

`);

// 监听响应
client.on("data", (chunk) => {console.log("chunk => ", chunk.toString("utf-8"));if (!responseData.line) { // 第一次收到的响应消息// 解析第一次接收到的 chunk 获取到响应行、响应头以及响应体的部分信息parseResponse(chunk.toString("utf-8"));} else { // 非第一次接收到的响应消息responseData.body += chunk.toString("utf-8");}isOver();
});

// 监听断开
client.on("close", () => {console.log("连接断开~");
});

/**
 * 解析响应消息
 * @param {String} response 响应消息
 */
function parseResponse(response) {const lineEndIndex = response.indexOf(separator); // => 响应行的结束位置const headerEndIndex = response.indexOf(separator + separator); // => 响应头的结束位置const lineStr = response.slice(0, lineEndIndex);const headerStr = response.slice(lineEndIndex + 2, headerEndIndex);const bodyStr = response.slice(headerEndIndex + 4);const lineArr = lineStr.split(" ");const headerArr = headerStr.split(separator);// 响应行responseData.line = {HTTPVersion: lineArr[0], // => 协议版本StatusCode: lineArr[1], // => 状态码ReasonPhrase: lineArr[2], // => 状态码描述};// 响应头responseData.header = headerArr.map((it) => {const keyEndIndex = it.indexOf(": "),key = it.slice(0, keyEndIndex),val = it.slice(keyEndIndex + 2);return [key, val];}).reduce((a, b) => {a[b[0]] = b[1];return a;}, {});// 响应体responseData.body = bodyStr;
}

/**
 * 判断来自服务器的消息是否已经接收完毕
 */
function isOver() {const contentLength = +responseData.header["Content-Length"],curLen = Buffer.from(responseData.body).byteLength;// 消息接收完毕if (curLen >= contentLength) {client.end(); // 关闭连接}
} 

主要解决几个问题:

  • 如何创建客户端,建立与服务端的链接
  • 如何使用客户端发送 HTTP 请求
  • 如何拿到服务端返回的 HTTP 响应数据
  • 如何判断服务端响应的内容是否都接收完毕,并在接收完毕之后,关闭连接

1.4 补充

响应消息中,有些字段是重复的,暂时还不理解这些重复的 key 是干啥的,使用上述逻辑处理的最终结果是,后者覆盖前者。

2. 创建服务端

模拟 HTTP 服务器,使用浏览器访问该服务,得到一个静态资源

2.1 最终效果

http://localhost:2155/ 使用浏览器访问本地搭建的一个服务,可以获取到我们返回的静态资源。

2.2 流程说明

初始化:

  • 创建服务端:const localServer = net.createServer()
  • 监听 2155 端口:localServer.listen(2155, () => {})* 注意:回调函数仅会在客户端连接 2155 端口成功时触发一次。
  • 监听来自客户端的连接请求:localServer.on("connection", (socket) => {})* 注意:每次有客户端连接,都会触发 connection 事件。每个客户端都对应一个 socket,它们之间是相互独立的。

处理 socket:

  • 注册监听事件:socket.on("data", (chunk) => {}) 注册 data 事件,每次接收到来自客户端的数据时触发
  • 注册 end 事件,连接断开时触发:socket.on('end', () => {})

准备响应的数据:

  • 响应头
const headBuffer = Buffer.from(
`HTTP/1.1 200 OK
Content-Type: image/jpeg

`, "utf-8"); 
  • 响应体,读取静态文件资源(buffer 格式)稍后作为 响应体 返回:const bodyBuffer = await fs.promises.readFile(path.resolve(__dirname, './xxx'))

响应数据:

  • 拼接响应头和响应体,并返回给客户端:socket.write(Buffer.concat([headBuffer, bodyBuffer]))

断开连接:

socket.end()

2.3 源码

const net = require("net");
const path = require("path");
const fs = require("fs");
const localServer = net.createServer();

localServer.listen(2155, () => {console.log("开始监听 2155 端口");
}); // => 监听 2155 端口

localServer.on("connection", (socket) => {console.log("有客户端连接到该服务了");socket.on("data", async (chunk) => {console.log("接收到来自客户端的数据:", chunk.toString("utf-8"));const headBuffer = Buffer.from(`HTTP/1.1 200 OK
Content-Type: image/jpeg

`,"utf-8");// 读取本地头像文件 avatar.jpegconst filename = path.resolve(__dirname, "./avatar.jpeg");// const filename = path.resolve(__dirname, "./index.html");const bodyBuffer = await fs.promises.readFile(filename);socket.write(Buffer.concat([headBuffer, bodyBuffer]));socket.end();});socket.on("end", () => {console.log("连接关闭了");});
}); 

最后

整理了一套《前端大厂面试宝典》,包含了HTML、CSS、JavaScript、HTTP、TCP协议、浏览器、VUE、React、数据结构和算法,一共201道面试题,并对每个问题作出了回答和解析。

有需要的小伙伴,可以点击文末卡片领取这份文档,无偿分享

部分文档展示:



文章篇幅有限,后面的内容就不一一展示了

有需要的小伙伴,可以点下方卡片免费领取

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/53592.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

美国高防服务器到底好不好用

对于对安全性配置有较高要求的用户来说,一般的独立服务器似乎并不能满足其需求。他们一般会选择带有DDoS或CC攻击防御功能或者流量清洗功能的高防服务器。而本文所要介绍的美国高防服务器也是众多海内外用户比较青睐的一大产品选择。那么美国高防服务器到底好不好用…

设计原则和设计模式

一:软件设计原则 1.单一职责原则: 有且只有一个原因引起类的变化(类或者接口的职责单一化) 2.里氏替换原则: 子类可以扩展父类的功能,但不能改变父类原有的功能 3.依赖倒置原则: 1.高层模块不应该依赖于底层模块&#xff0c…

机器学习2普通knn算法

文章目录KNN算法-k近邻算法(k-Nearest Neighbors)前言KNN优点:快捷键:一、普通knn算法是什么?解析:二、普通knn算法基础1.分布解析2.使用pycharm函数封装的形式运行KNN算法3.使用scikit-learn中的knn三、普…

《Redis基础篇》带你走进Redis的世界 ~ ⭐必看必看⭐

文章目录1. NoSQL数据库简介1.1 技术发展1.1.1. Web1.0时代1.1.2 Web2.0时代1.1.3. 解决CPU及内存压力(采用分布式)1.1.4. 解决IO压力1.2. NoSQL数据库1.2.1. NoSQL数据库概述1.2.2 NoSQL适用场景1.2.3 NoSQL不适用场景1.2.4 Memcache1.2.5 Redis1.2.6. MongoDB1.3. 行式存储数…

新的datax启动方式(直接使用java命令启动)

一开始用的python启动 (很烦 还得有python环境 但是本着项目上线可以给客户装个python环境 也就用了) 后续需要做个客户端 开始呢下载了源码直接再源码中启动(但是多个任务都在一个jvm中 我很抵触这样做) 又研究研究python启动的源码 发现最后还是调用…

Lerna入门与实战

一、Lerna简介 1.1 lerna背景 维护过多个package项目的同学可能都会遇到一个问题:package是放在一个仓库里维护还是放在多个仓库里单独维护。当package数量较少的时候,多个仓库维护不会有太大问题,但package数量逐渐增多时,一些…

数据驱动运营成功案例——内蒙古国大药房

国药控股国大药房内蒙古有限公司(以下简称“内蒙古国大药房”),自2003年成立以来,销售额以每年35%以上的速度增长,现有直营门近400家,分布覆盖呼和浩特、包头、呼伦贝尔、乌兰察布、巴彦淖尔、赤峰、满洲里…

新加坡国立大学尤洋:我的四个选择,本质的喜欢催动长久的坚持丨青源专栏...

为了启发青年学者思考职业发展,激发科研灵感,智源社区推出青源专栏,定期邀请青源会员分享他们的研究思考和科研感悟。新加坡国立大学计算机系校长青年教授、青源会会员尤洋分享了他在高性能计算研究、创业经历以及在新加坡的生活所感。从求学…

006. 分割回文串

1.题目链接: 131. 分割回文串 2.解题思路: 2.1.题目要求: 给一个字符串 s ,要求把 s 分割成一些子串,并使每个子串都是 回文串。 回文串的概念:正反顺序都一样的字符串。 举例: 输入&#…

IC模拟版图工程师高薪进阶之路,三年实现年薪30w+

模拟版图是模拟IC设计岗位的一种,随着国内半导体行业的不断发展,岗位的需求也越来越多。而每个芯片最终能够付诸于生产都离不开模拟版图设计师的功劳,所以目前芯片产业对于这类人才需求越来越大。 什么是模拟版图设计工程师? 模…

BSA牛血清白蛋白修饰Fe3O4纳米颗粒 BSA-MION

产品名称:BSA牛血清白蛋白修饰Fe3O4纳米颗粒 英文名称:BSA-MION 用途:科研 状态:固体/粉末/溶液 产品规格:1g/5g/10g 保存:冷藏 储藏条件:-20℃ 储存时间:1年 牛血清中的简单蛋白&am…

2022年第十一届认证杯数学中国数学建模国际赛小美赛:D题野生动物贸易是否应该长期禁止建模方案及代码实现

更新进展 (1)2022-12-2 9:00 发布预售 (2)2022-12-2 10:40 发布建模思路、代码以及参考文献 (3)待更新内容:进一步对全部数据分析 1 题目 野生动物市场被怀疑是当前疫情和2002年SARS疫情的…

blender中的灯光和相机

灯光环境光灯光基础灯光基本属性 EV渲染器日光面光天空盒IES灯光 (灯光遮罩)自发光移动摄像机切换摄像机摄像机属性环境光 在渲染视图中,没有任何光时,物体只受环境光的影响 灯光基础 1 四种灯光,点光,…

安卓期末大作业——仿番茄免费小说APP

《移动应用开发实践》实践报告 APP名称: 番茄免费小说 要求: 格式:宋体,小四号字;首行缩进;行距:1.5倍。 每人独立完成Android App的设计与开发App必须包含SOLite数据库操作 一、所调查的Ap…

Android -- 每日一问:能讲讲 Android 的 Handler 机制吗?

典型回答 讲清楚Android中的消息机制,先表述一下和Handler相关的一些类: Message:消息分为硬件产生的消息(如按钮、触摸)和软件生成的消息; MessageQueue:消息队列的主要功能向消息池投递消息(MessageQueue.enqueueMe…

疫情下为什么年轻、未婚女性更容易拿到offer

最近发现一个奇怪的现象,为什么会奇怪。大家别着急,容我慢慢道来。由于疫情开始后,也是大家开始躺平后,企业在有意、无意的疯狂裁员,导致人心惶惶,大家都不敢轻易跳槽了。甚至有的人很庆幸,有份…

一文弄懂JUnit5相关注解

JUnit5 JUnit是Java生态系统中最流行的单元测试框架之一。JUnit5版本包含许多令人兴奋的创新,其目标是支持Java8和更高版本中的新功能,并支持多种不同风格的测试。 Maven依赖 启动JUnit5.x.0非常简单;我们只需要将以下依赖项添加到pom.xml…

昆石网络 VOS3000虚拟运营支撑系统任意文件读取漏洞

漏洞描述: 昆石网络 VOS3000虚拟运营支撑系统 通过 %c0%ae%c0%ae 等字符绕过检测,可导致任意文件读取漏洞。 漏洞利用条件: 对⽤户查看或下载的⽂件没有限制或者限制绕过,就可以查看或下载任意⽂件 漏洞影响范围: VOS3000 漏…

阿里云Redis性能压力测试(二十)

文章目录1.云Redis性能压力测试2.安装redis-banchmark压测工具3.压测两节点的Redis集群4.压测四节点的Redis集群4.1.扩容集群为四节点4.2.压力测试5.压测结果对比1.云Redis性能压力测试 我们当前Redis集群是2个节点2个分片,使用redis-benchmark工具对Redis集群进行…

轨迹预测——day 57 基于车道交叉和考虑驾驶方式的终点生成模型的前目标车辆轨迹预测

Trajectory Prediction of Preceding Target Vehicles Based on Lane Crossing and Final Points Generation Model Considering Driving Styles导读II.问题表述与系统架构A. Trajectory Prediction for PTVs(preceding target vehicles)B. Position and…