Heim > Artikel > Web-Frontend > Eine kurze Analyse von TCP und UDP in Node
Node
ist eine netzwerkorientierte Plattform, die nicht blockiert, Single-Threaded ist und eine gute Skalierbarkeit aufweist, wodurch sie für die Verteilung verschiedener Rollen geeignet ist das Netzwerk. 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
stellt net
, dgram
, http
, http2
, bereit https
und andere Module werden für die Verarbeitung von TCP
, UDP
, HTTP
, HTTPS
usw. verwendet der Server und der Client.
TCP
-Dienste sind in Netzwerkanwendungen sehr verbreitet. Die meisten aktuellen Anwendungen basieren auf TCP
Der vollständige Name lautet Transmission Control Protocol, ein Transportschichtprotokoll im OSI
-Modell. Viele Anwendungsschichtprotokolle basieren auf TCP
>HTTP, SMTP
, IMAP
und andere Protokolle. Ich werde hier nicht über TCP
-bezogenes Wissen sprechen. Wenn Sie interessiert sind, können Sie mir folgen Computernetzwerk Lernen Sie diese Kolumne. 🎜TCP-Server erstellen🎜🎜Nachdem wir ein grundlegendes Verständnis davon haben, wie TCP
funktioniert, können wir beginnen Erstellen eines TCP
-Servers zum Akzeptieren von Netzwerkanforderungen. Das net
-Modul stellt eine asynchrone Netzwerk-API
zum Erstellen von Streams
bereit TCP
oder IPC
Server und Client. [Empfohlene verwandte Tutorials: nodejs-Video-Tutorial, Programmierunterricht】🎜🎜Bitte schauen Sie sich das Beispiel unten an, wir befinden uns in server.js code> Schreiben Sie den folgenden Code wie folgt in die Datei: 🎜<pre class="brush:js;toolbar:false;">function createServer(options, connectionListener) {
return new Server(options, connectionListener);
}</pre>🎜Wir können einen <code>TCP
-Server über net.createServer(listener)
erstellen Der Listener des Link-Ereignisses Connection
. 🎜🎜Wenn wir die Datei im Terminal ausführen, wird Dienst erfolgreich erstellt
im Terminal ausgegeben. 🎜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);🎜 Zuvor haben wir einen Server über
net.createServer
erstellt, dann verwenden wir net.connect
, um einen Client für die Sitzung zu erstellen. Der spezifische Code lautet wie folgt: 🎜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; } };🎜 Wir führen zu diesem Zeitpunkt die beiden Dateien aus, wie unten gezeigt:🎜🎜🎜🎜Als nächstes haben wir ein solches Beispiel, der spezifische Code ist wie folgt:🎜🎜🎜🎜Bitte sehen Sie sich die Animation unten für die spezifischen Laufergebnisse an:🎜🎜🎜🎜Auf dem Client verwende ich
client .write() hat Daten mehrmals gesendet, aber nur die anderen als <code>setTimeout
sind normal. Das kontinuierliche Senden in setTimeout
scheint nicht jedes Mal zurückzukehren nach dem Zufallsprinzip zusammengeführt und zurückgegeben, und Sticky-Pakete werden hier angezeigt. 🎜🎜TCP
verfügt über bestimmte Optimierungsstrategien für kleine Datenpakete im Netzwerk: Negle
-Algorithmus Wenn jeweils nur ein Byte Inhalt ohne Optimierung gesendet wird, wird das Netzwerk A Ein Datagramm, das nur mit einer sehr kleinen Menge gültiger Daten gefüllt ist, stellt eine Verschwendung von Netzwerkressourcen dar. In dieser Situation erfordert dieser Algorithmus, dass der Puffer eine bestimmte Datenmenge oder einen bestimmten Zeitraum erreicht, bevor er gesendet wird, also kleine Datenpakete werden durch diesen Algorithmus zusammengeführt, um das Netzwerk zu optimieren. Obwohl diese Optimierung eine effiziente Nutzung des Netzwerks ermöglicht, kann es beim Senden der Daten zu Verzögerungen kommen. 🎜🎜In Node
können Sie socket.setNoDelay(true)
aufrufen, da TCP
standardmäßig den Negle
-Algorithmus aktiviert > Entfernen Sie den Negle
-Algorithmus, damit write()
sofort Daten an das Netzwerk senden kann: 🎜🎜🎜🎜Schließen Nagle
Der Algorithmus ist nicht immer effektiv, weil er der ist Der Server schließt die Zusammenführung ab. Die von TCP
empfangenen Daten werden zunächst in seinem eigenen Puffer gespeichert und dann der Anwendung mitgeteilt, dass sie sie empfangen kann, wenn die Anwendungsschicht die Daten nicht von TCP Das Abrufen von Daten aus dem Puffer führt auch dazu, dass mehrere Datenblöcke im TCP-Puffer gespeichert werden, was zu Sticky-Paketen führt. 🎜<h2><strong>TCP-Prinzip</strong></h2>🎜In <code>Node
entspricht der Aufruf von createServer()
dem Aufruf von new Server()
, die spezifischen Ergebnisse sind in der folgenden Abbildung dargestellt: 🎜🎜🎜🎜这主要的原因它在 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 教程!
Das obige ist der detaillierte Inhalt vonEine kurze Analyse von TCP und UDP in Node. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!