net模块
这个模块不是很常用,这个模块是为HTTP模块打基础的,通过net模块可以对网络请求有一个更加深刻的认识.
首先回顾一下HTTP请求,HTTP模式有两个版本,第一个是传统的版本,客户端和服务器会有三次握手,三次握手完成之后,连接就建立了,建立连接之后,客户端就可以发送一个请求到服务器,服务器就可以给客户端一个响应,完成响应后,通过四次挥手,就关闭了连接,这就是传统的模式,ajax请求都是这种模式.
但是如果一个页面有很多请求,会请求非常多的资源,那么会产生很多握手和挥手,这会浪费响应时间,我们完全可以在非常短的时间内共用一个请求,这就是长连接模式,我们在请求时只需要在消息头加上connection:keep-alive这个属性后,服务器再回复一个connection:keep-alive后,就开始了长连接服务,我们可以用这个连接传输多个数据,不需要再重新建立请求.
net模块能干嘛
net是一个通信模块,利用它我们可以实现进程间通信(IPC),还可以实现网络通信(TCP/IP),我们主要关注网络通信.
TCP/IP是一个比HTTP协议更加底层的协议,通过TCP/IP协议,客户端可以发送任意格式的数据到服务器,服务器也可以主动发送任意格式的数据到客户端,这和HTTP的请求响应不一样.
net模块可以实现很底层的TCP/IP协议.
创建客户端
我们通过net模块下的createConnection方法就可以创建一个客户端,第一个参数为配置对象,第二个是监听函数,参考代码:
javascript
const net = require("net");
net.createConnection(
{
host: "baidu.com", //需要连接的主机,能连接就行,不需要写http协议,这里连接到百度
port: 80, //设置端口号
},
() => {
console.log("连接成功");
}
);这就创建了一个连接,连到百度服务器,端口号80,连接成功后输出,此时就和服务器建立了连接,这个对象会返回一个socket,socket就是一个特殊的文件,他是负责向网卡输送内容,我们可以通过流操作来操纵他,我向这个文件写内容,这个文件就会通过这个连接传送到远程主机,我通过读这个文件就可以读到远程主机回复的内容,很显然,这个socket是一个双工流,可读可写.
javascript
const net = require("net");
const socket = net.createConnection(
{
host: "baidu.com", //需要连接的主机,能连接就行,不需要写http协议,这里连接到百度
port: 80, //设置端口号
},
() => {
console.log("连接成功");
}
);
socket.on("data", (chunk) => {
console.log("来自服务器的消息", chunk);
});运行之后,浏览器没有给我任何消息, 因为我们客户端没有向服务器发送任何请求,他也就不会给我们返回任何消息,我们需要向服务器发送请求.
javascript
const net = require("net");
const socket = net.createConnection(
{
host: "qq.com", //需要连接的主机,能连接就行,不需要写http协议,这里连接到百度
port: 80, //设置端口号
},
() => {
console.log("连接成功");
}
);
socket.on("data", (chunk) => {
console.log("来自服务器的消息", chunk.toString("utf-8"));
});
socket.write("你好");如果我们随便给服务器发送一个请求,这个请求内容并不符合HTTP格式,所以他会给我一个400错误

这是由于这服务器只能识别HTTP协议,当我们发送的数据不是HTTP协议时,就会给我们报错,但是如果这个服务器不是基于HTTP协议的,是基于一个普通的TCP/IP协议,那么他就可以处理这些数据.
我们这里发送一个正式的HTTP请求
HTTP协议的请求格式为:请求行 换行 请求头 换行 换行 请求体
如果不传请求体,那么也需要两个换行来告诉服务器你请求结束了
javascript
const net = require("net");
const socket = net.createConnection(
{
host: "www.baidu.com",
port: 80, //设置端口号
},
() => {
console.log("连接成功");
}
);
socket.on("data", (chunk) => {
console.log("来自服务器的消息", chunk.toString("utf-8"));
});
socket.write(`GET / HTTP/1.1
Host: www.baidu.com
`);
这里百度就会响应给我很多信息,包括了响应头和响应体,响应体就是页面的HTML
我们可以通过socket.end方法来挂断,但问题来了,我需要等他传输完毕之后再挂断,但是流操作是一块一块慢慢传输,我怎么知道他什么时候传输完毕呢?实际上响应体里有一个content-Length,保存了响应体的总字节数

由于响应头在最前面,第一次传输是肯定可以传输完毕的,我们可以解析请求头,对content-Length属性进行解析,这个属性保存了响应体的总字节数,我们写一个完整的实例
javascript
const net = require("net");
const socket = net.createConnection(
{
host: "www.baidu.com",
port: 80, //设置端口号
},
() => {
console.log("连接成功");
}
);
let isFirst = true;
let allBody = "";
let length = 0;
socket.on("data", (chunk) => {
if (isFirst) {//是否是第一次接收
parseResponse(chunk.toString("utf-8"));
isFirst = !isFirst;
if (Buffer.from(allBody, "utf-8").byteLength >= length) {
console.log(allBody);
socket.end();
return;
}
} else {
// console.log(Buffer.from(allBody, "utf-8").byteLength, length);
allBody += chunk.toString("utf-8");
if (Buffer.from(allBody, "utf-8").byteLength >= length) {
console.log(allBody);
socket.end();
return;
}
}
});
socket.write(`GET / HTTP/1.1
`);
function parseResponse(response) {
const index = response.indexOf("\r\n\r\n");
const head = response.substring(0, index);
const headArr = head.split("\r\n").map((item) => {
return item.split(":");
});
length = parseInt(
headArr.find((item) => {
return item[0] == "Content-Length";
})[1]
);
const body = response.substring(index + 2);
allBody += body.trimStart();
}创建服务器
net不仅可以作为客户端向服务器发送请求,还可以作为服务器来响应其他客户端的请求
net使用createServer方法来创建服务器,返回一个server对象
server对象提供了server.lesten(port)用来监听port端口
server有一个listening方法,可以通过on绑定,服务器开始监听端口时会触发listening方法
server有一个很重要的connection事件,当一个客户端连接到服务器的时候会触发connection事件,触发这个事件时会给我们传入一个socket参数
javascript
const net = require("net");
const server = net.createServer();
server.listen(9527); //此时就开始监听9527端口了
server.on("listening", () => {
console.log("开始监听");
});
server.on("connection", (socket) => {
console.log("有连接");
});运行代码时,开始监听

我们使用postman建立连接
<HTML><HEAD><META SENDING REQUEST...T/HTML HTTP-EQUIVCONTENT- A HTTP-EQUIV-"X-UA-COMPATIBLE" CHARSET UTF-8"><META HT TENT-"ALWAYS" CONTENT"IEEDGE,CHROM CANCEL LOR" NAME-REFERRER><META CONTENT三"征FFFFF">META NAME-"DESCRIPTION" CONTENT二"全球领 先的中文搜索引擎,致力于让网民更便捷地获取信息,找到所求.百度超过千 亿的中文网页数据库,可以瞬间找到相关的搜索结果."><LINK REL-"SHORTCUT ICON" HREF三"/FAVICON.ICO" TYPE:"IMAGE X-ICON" /><LINK REL-"SEARCH" TYPE-"APPLICATION/ --> 

可以看见,postman发送请求后,建立了连接,触发了connection方法
这里触发了两次,说明有两次连接,这是因为浏览器或者postman在连接之前都会发送一个测试连接,测试联通状态,这就导致产生了两次连接
建立连接后产生的socket只针对本次连接,一个服务器会接受很多的请求,每一次请求都会生成一个socket,这个socket就是用来和客户端通信的,所以服务器端是可以产生很多个socket,服务器向里面写内容,相当于响应到客户端,读取socket的内容就相当于接收客户端的信息,我们可以来测试.
javascript
const net = require("net");
const server = net.createServer();
server.listen(9527); //此时就开始监听9527端口了
server.on("listening", () => {
console.log("开始监听");
});
server.on("connection", (socket) => {
socket.on("data", (chunk) => {
console.log(chunk.toString("utf-8")); //可以获取响应信息
socket.write(`HTTP/1.1 200 OK
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div>你好</div>
</body>
</html>`); //向客户端响应
socket.end();
});
socket.on("close", () => {
console.log("连接关闭"); //当我们从客户端关闭请求时,连接关闭
});
});
我们就可以请求这个服务器来获得响应,当我们请求的时候,就可以拿到一个完整的网页了.
我们可以尝试给他响应一张图片
javascript
const net = require("net");
const fs = require("fs");
const path = require("path");
const server = net.createServer();
server.listen(9527); //此时就开始监听9527端口了
server.on("listening", () => {
console.log("开始监听");
});
server.on("connection", (socket) => {
socket.on("data", async (chunk) => {
console.log(chunk.toString("utf-8")); //可以获取响应信息
const imgBuffer = await fs.promises.readFile(
path.resolve(__dirname, "./cat.webp")
);
const headBuffer = Buffer.from(
`HTTP/1.1 200 OK
Content-Type: image/webp
`,
"utf-8"
);
socket.write(Buffer.concat([headBuffer, imgBuffer])); //向客户端响应
socket.end();
});
socket.on("close", () => {
console.log("连接关闭"); //当我们从客户端关闭请求时,连接关闭
});
});
这我们就实现了对图片的响应,这就实现了对图片的响应,这里就从底层实现了一个http模块