
Node 是一个面向网络而生的平台,它具有事件驱动、无阻塞、单线程等特性,具备良好的可伸缩性,使得它十分轻量,适合在分布式网络中扮演各种各样的角色。
Node 提供了 net、dgram、http、http2、https 等模块,分别用于处理 TCP、UDP、HTTP、HTTPS,适用于服务端和客户端。
TCP 服务在网络应用中十分常见,目前大多数的应用都是基于 TCP 搭建而成的,它的全名为传输控制协议,在 OSI 模型中属于传输层协议,许多应用层协议基于 TCP 构建,典型的 HTTP、SMTP、IMAP 等协议。在这里不讲 TCP 相关知识点了,如果感兴趣可以关注我 计算机网络 这个专栏进行学习。
创建 TCP 服务端
在基本了解 TCP 的工作原理之后,我们可以开始创建一个 TCP 服务器端来接受网络请求,net 模块提供了一个异步网络 API,用于创建基于 stream 的 TCP 或 IPC 服务器和客户端。【相关教程推荐:nodejs视频教程、编程教学】
请看下面这个例子,我们在 server.js 文件中编写以下代码,如下:
import net from "net";
const server = net.createServer((socket) => {
socket.on("data", (data) => {
console.log("监听到客户端的数据:", data.toString());
});
// 监听客户端断开连接事件
socket.on("end", () => {
console.log("客户端断开连接");
});
// 发送数据给客户端
socket.end("over over over\n");
});
// 启动服务
server.listen(3000, () => {
console.log("服务创建成功");
});我们通过 net.createServer(listener) 即可创建一个 TCP 服务器,该函数的参数是里链接事件 connection 的侦听器。
当我们在终端执行该文件时,服务创建成功 输出在了终端。
nodemon .\server.js
在前面我们通过 net.createServer 创建了一个服务端,那么接下来我们使用 net.connect 创建一个客户端进行会话,具体代码如下所示:
import net from "net";
const client = net.connect({ port: 3000 }, () => {
client.write("今晚出去吃饭,收到请 over\n");
});
// 接收服务端的数据
client.on("data", (data) => {
console.log("接收服务端的数据: ", data.toString());
// 断开连接
client.end();
});
// 断开连接
client.on("end", () => {
console.log("断开连接");
});我们这个时候对两个文件执行,如下所示:

接下来我们还有这样的一个例子,具体代码如下图所示:

具体运行结果请看下面的动图:

在客户端我使用 client.write() 发送了多次数据,但是只有 setTimeout 之外的是正常的,setTimeout 里面连续发送的似乎并不是每一次一返回,而是会随机合并返回了,在这里也就出现了粘包了。
TCP 针对网络中的小数据包有一定的优化策略: Negle 算法,如果每次只发送一个字节的内容而不优化,网络中将充满只有极少数有效数据的数据报,将十分浪费网络资源,该算法针对这种情况,要求缓冲区的数据达到一定数量或者一定时间后才将其发出,所以小数据包将会被该算法合并,以此来优化网络。这种优化虽然使网络有效地使用,但是数据有可能被延迟发送。
在 Node 中,由于 TCP 默认启用了 Negle 算法,可以调用 socket.setNoDelay(true) 去掉 Negle 算法,使得 write() 可以立即发送数据到网络中:

关闭 Nagle 算法并不总是有效的,因为其是在服务端完成合并,TCP 接收到数据会先存放于自己的缓冲区中,然后通知应用接收,应用层因为网络或其它的原因若不能及时从 TCP 缓冲区中取出数据,也会造成 TCP 缓冲区中存放多段数据块,就又会形成粘包。
在 Node 中,调用 createServer() 等同于调用 new Server(),具体结果如下图所示:

这主要的原因它在 Node 源码中有如下定义,所以调用 createServer() 函数实际上调用的是 new Server(),具体代码如下图所示:
function createServer(options, connectionListener) {
return new Server(options, connectionListener);
}该构造函数的定义主要有如下所示:
function Server(options, connectionListener) {
EventEmitter.call(this);
// 注册连接到来时执行的回调
if (typeof options === "function") {
connectionListener = options;
options = {};
this.on("connection", connectionListener);
} else if (options == null || typeof options === "object") {
options = { ...options };
if (typeof connectionListener === "function") {
this.on("connection", connectionListener);
}
}
// 服务器建立的连接数
this._connections = 0;
this[async_id_symbol] = -1;
this._handle = null;
this._usingWorkers = false;
this._workers = [];
this._unref = false;
// 服务器下的所有连接是否允许半连接
this.allowHalfOpen = options.allowHalfOpen || false;
// 有连接时是否注册读事件
this.pauseOnConnect = !!options.pauseOnConnect;
this.noDelay = Boolean(options.noDelay);
// 是否支持keepAlive
this.keepAlive = Boolean(options.keepAlive);
this.keepAliveInitialDelay = ~~(options.keepAliveInitialDelay / 1000);
}
ObjectSetPrototypeOf(Server.prototype, EventEmitter.prototype);
ObjectSetPrototypeOf(Server, EventEmitter);listen
它返回的是一个普通的 JavaScript 对象,接着调用 listen 函数监听端口,listen 方法支持多种使用方式主要有以下这几种方法:
TCP 服务器,而不是需要创建的一个服务器;fd 字段;TCP 服务器,并启动该服务器,如果传入了 host 会对其进行域名解析;该方法的的主要逻辑有如下代码所示:
Server.prototype.listen = function (...args) {
/*
处理入参,根据文档我们知道listen可以接收好几个参数,
假设我们这里是只传了端口号9297
*/
var normalized = normalizeArgs(args);
// normalized = [{port: 9297}, null];
var options = normalized[0];
var cb = normalized[1];
// 第一次listen的时候会创建,如果非空说明已经listen过
if (this._handle) {
throw new errors.Error("ERR_SERVER_ALREADY_LISTEN");
}
// listen成功后执行的回调
var hasCallback = cb !== null;
if (hasCallback) {
// listen成功的回调
this.once("listening", cb);
}
options = options._handle || options.handle || options;
// 第一种情况,传进来的是一个TCP服务器,而不是需要创建一个服务器
if (options instanceof TCP) {
this._handle = options;
this[async_id_symbol] = this._handle.getAsyncId();
listenIncluster(this, null, -1, -1, backlogFromArgs);
return this;
}
// 第二种,传进来一个对象,并且带了fd
if (typeof options.fd === "number" && options.fd >= 0) {
listenIncluster(this, null, null, null, backlogFromArgs, options.fd);
return this;
}
// 创建一个tcp服务器
var backlog;
if (typeof options.port === "number" || typeof options.port === "string") {
backlog = options.backlog || backlogFromArgs;
// 第三种 启动一个TCP服务器,传了host则先进行DNS解析
if (options.host) {
lookupAndListen(
this,
options.port | 0,
options.host,
backlog,
options.exclusive
);
} else {
listenIncluster(
this,
null,
options.port | 0,
4,
backlog,
undefined,
options.exclusive
);
}
return this;
}
};在每种方式的最后丢回调用 listenIncluster 方法,该方法主要做的事情是区分 master 进程 和 worker 进程,采用不同的处理策略:
mastr 进程: 直接调用 server._listen 启动监听;worker 进程: 使用 cluster._getServer 处理传入的 server 对象,修改 server._handle 再调用了 server._listen 启动监听;UDP 又称用户数据包协议,与 TCP 一样同属于网络层传输层。UDP 和 TCP 最大的不同是 UDP 不是面向链接的。
创建 <span style="font-size: 18px;">UDP</span> 服务
创建 UDP 套接字十分简单,UDP 套接字一旦创建,既可以作为客户端发送数据,也可以作为服务端接收数据,下面的代码创建了一个 UDP 套接字,具体代码如下所示:
import dgram from "node:dgram";
const server = dgram.createSocket("udp4");
server.on("error", (err) => {
console.error(`server error:\n${err.stack}`);
server.close();
});
server.on("message", (msg, rinfo) => {
console.log(`server got: ${msg} from ${rinfo.address}:${rinfo.port}`);
});
server.on("listening", () => {
const address = server.address();
console.log(`server listening ${address.address}:${address.port}`);
});
server.bind(3000);该套接字将接收所有网课上 3000 端口上的消息,在绑定完成后,将触发 listening 事件,会终端执行,会输出 server listening 0.0.0.0:3000 字段。
接下来我们创建一个客户端和服务端进行对话,具体代码如下所示:
import dgram from "node:dgram";
import { Buffer } from "node:buffer";
const message = Buffer.from("你个叼毛");
const client = dgram.createSocket("udp4");
client.send(message, 0, message.length, 3000, "localhost", () => {
client.close();
});终端的最终输出结果如下图所示

UDP 广播
在 dgram 模块中,可以使用 socket 端口对象的 setBroadcast 方法来进行数据的广播:
socket.setBroadcast(flag);
flag: 当 flag 为 true 时,UDP 服务器或者客户端可以利用其所用的 socket 端口对象的 send 方法中的地址修改为广播地址。服务端的代码定义在 server.js 文件,具体代码如下所示:
import dgram from "dgram";
const server = dgram.createSocket("udp4");
server.on("message", function (msg, rinfo) {
console.log(
"server got: " + msg + " from " + rinfo.address + ":" + rinfo.port
);
});
server.on("listening", function () {
var address = server.address();
console.log("server listening " + address.address + ":" + address.port);
});
server.bind(3000);客户端的代码定义在 server.js 文件,具体代码如下定义:
import dgram from "dgram";
import { Buffer } from "buffer";
const socket = dgram.createSocket("udp4");
const params = process.argv.splice(2);
socket.bind(function () {
socket.setBroadcast(true);
});
const message = Buffer.from(...params);
socket.send(message, 0, message.length, 3000, "255.255.255.255", () => {
socket.close();
});具体运行效果如下图所示:

更多node相关知识,请访问:nodejs 教程!
以上就是一文浅析Node中的TCP和UDP的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号