体验 Node.js 的 net 模块
admin
2024-03-10 04:54:19
0

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道面试题,并对每个问题作出了回答和解析。

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

部分文档展示:



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

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

相关内容

热门资讯

西安甲康甲状腺江勇医生科普:甲... 西安甲康甲状腺医院介绍甲亢和甲减是甲状腺功能异常的两种常见疾病,二者因甲状腺激素分泌水平的截然相反,...
FXTRADING 财经看点:... 美国最新公布的1月份生产者端价格数据,再次把市场的注意力拉回到通胀本身。数据显示,上游价格压力并未明...
心向暖阳・守护安宁 —— 甘肃... 2026年1月20日11:30,“‘宁’心守护,爱不‘终’断”团队成员来到甘肃省人民医院康复理疗科进...
原创 该... 全球能源市场,风云突变!哈梅内伊遇害的消息如同重磅炸弹,在伊朗各地引发连环爆炸,局势瞬间升至沸点。随...
开春别乱补!遵循 “春生” 规... 春节刚过,大鱼大肉的油腻饮食让肠胃不堪重负,紧接着春暖花开,阳气升腾却也湿气渐重,很多人会出现疲劳乏...
原创 金... 就在大家觉得金价已经高得吓人的时候,华尔街的投行们却给出了更惊人的预测:摩根大通说,到今年年底,金价...
商务部:调整对加拿大反歧视措施 加拿大政府宣布,部分调整对自中国进口钢铝产品采取的加征关税等限制措施。对此,中国也宣布,对原产于加拿...
原创 今... 就在刚刚过去的这个周末,全球黄金市场炸开了锅。 2026年3月1日,国际金价像坐上了火箭,伦敦现货黄...
侃股:巴菲特超级收益率靠的是持... 巴菲特60年收益率超6万倍,靠的是持续不断的价值投资。实际上,投资者如果能够做到每年约20%的投资收...
尺素金声|分量重、成色足,上市... 惠民消费券、以旧换新补贴、有奖发票……今年春节,一系列民生红利增添过节底气,资本市场派发的“红包”也...
从股价大涨到实控人等三方被同时... 每经记者|蔡鼎 每经编辑|张益铭 2023年的A股市场,捷荣技术(SZ002855,股价16.40...
80后夫妻卖充电桩,要IPO了 星星充电品牌运营公司、来自江苏的万帮数字能源股份有限公司(简称“星星充电”)在港交所递交招股书,拟在...
刚刚开盘,直线大跳水!石油、黄... 这个周末,中东地区战火重燃,成为全球金融市场的“黑天鹅”事件,目前投资者正密切关注伊朗遭袭事件可能对...
原创 卡... 去年年底的卡塔尔世界杯成为了全球关注的焦点,而一张卡塔尔小王子的表情包更是迅速在网络上刷屏,这张看似...
周末黄金定价权转移:链上黄金成... 传统金融市场的交易时间限制,正让区块链黄金资产迎来关键定价窗口。前瑞信首席投资官、现流动性基础设施公...
老铺黄金2026年首次调价,整... 老铺黄金迎来今年首次调价。 2月28日,记者获悉,老铺黄金迎来年内第一次调价,此次调价幅度在20%至...
中东局势剧变!“赌”涨黄金潮起... 来源:滚动播报 (来源:北京商报) 这个周末,全球资本市场的神经骤然绷紧。 中东局势的突然升级如同一...
“沪七条”落地实施 上海外环外... 中新网上海3月1日电 (张践)近日,上海发布楼市新政“沪七条”,通过降低购房门槛、放宽购房套数限制、...
原创 中... 哎呀,特朗普这家伙又回来了,2025年1月20日他第二次宣誓就职总统,就跟第一次似的,马上就开始折腾...
湖南南新制药股份有限公司202... 本公司董事会及全体董事保证本公告内容不存在任何虚假记载、误导性陈述或者重大遗漏,并对其内容的真实性、...