net 与 dns
本教程共 76 篇 · 第 31 篇 · 更新于 2026-07-25 · 约 5 分钟阅读
31. net 与 dns
本节目标:用 net 模块做 TCP 通信,用 dns 模块做域名解析。
HTTP 服务已经写过好几轮了,但 HTTP 本质上只是 TCP 上的一个应用层协议。如果你想做长连接游戏服务器、自定义二进制协议,或者单纯想理解 HTTP 底下在发生什么,就得跟 net 模块打交道。这一章还会顺路讲 dns,毕竟几乎所有网络请求的第一步都是把域名换成 IP。
TCP 服务器与客户端
net 模块让你直接操作 TCP 连接。它比 http 更底层,没有请求头、状态码这些概念,只有纯粹的字节流。
启动一个 TCP 服务器
import net from 'node:net';
const server = net.createServer(socket => {
console.log(`Client connected from ${socket.remoteAddress}:${socket.remotePort}`);
socket.on('data', data => {
console.log('Received:', data.toString());
socket.write(`Echo: ${data}`);
});
socket.on('end', () => {
console.log('Client disconnected');
});
socket.on('error', err => {
console.error('Socket error:', err.message);
});
});
server.listen(8080, () => {
console.log('TCP server listening on port 8080');
});
socket 对象既是可读流也是可写流。data 事件收到的默认是 Buffer,你可以用 socket.setEncoding('utf8') 让它直接传字符串。
测试这个服务器最简单的方式是用系统自带的 telnet 或 nc:
# Windows 没有 nc 的话可以用 PowerShell 的 telnet
nc localhost 8080
Hello
# 会收到 Echo: Hello
TCP 客户端
import net from 'node:net';
const client = net.createConnection({ port: 8080 }, () => {
console.log('Connected to server');
client.write('Hello from client');
});
client.setEncoding('utf8');
client.on('data', data => {
console.log('Server says:', data);
});
client.on('end', () => {
console.log('Server closed connection');
});
一个简单聊天室
TCP 的长连接特性天然适合做聊天。思路很简单:服务器维护一个客户端列表,收到消息时广播给其他人。
import net from 'node:net';
const clients = new Set();
const server = net.createServer(socket => {
socket.setEncoding('utf8');
clients.add(socket);
socket.write('Welcome to the chat room!\n');
socket.on('data', data => {
const msg = `[${socket.remotePort}] ${data}`;
for (const client of clients) {
if (client !== socket) {
client.write(msg);
}
}
});
socket.on('end', () => {
clients.delete(socket);
});
socket.on('error', () => {
clients.delete(socket);
});
});
server.listen(8080, () => {
console.log('Chat server on port 8080');
});
开两个终端连上去,一个人打字,另一个人能实时收到。这就是 WebSocket 出现之前,聊天室最常见的实现方式。
Tip
net模块还可以创建 IPC(进程间通信)服务器,通过 Unix Domain Socket 或 Windows 命名管道实现同机高速通信。如果你在做父子进程协作,比 TCP 回环地址更快、更安全。
DNS 查询
dns 模块负责域名解析。它有两套 API:回调式的传统 API,以及基于 Promise 的现代 API。新项目建议直接用 dns/promises。
基础查询
import dns from 'node:dns/promises';
const result = await dns.lookup('example.com');
console.log(result.address); // IP 地址
console.log(result.family); // 4 (IPv4) 或 6 (IPv6)
dns.lookup() 走的是操作系统底层的解析逻辑,会读取 /etc/hosts 和本地 DNS 缓存。如果你想绕开操作系统,直接向 DNS 服务器发问,用 resolve4 或 resolve6:
const addresses = await dns.resolve4('google.com');
console.log(addresses); // ['142.250.80.46', ...]
const ipv6 = await dns.resolve6('google.com');
console.log(ipv6);
其他记录类型
// MX 记录(邮件交换)
const mx = await dns.resolveMx('gmail.com');
console.log(mx); // [{ exchange: 'gmail-smtp-in.l.google.com', priority: 5 }, ...]
// TXT 记录
const txt = await dns.resolveTxt('google.com');
console.log(txt);
// 反向解析:IP 转域名
const hostnames = await dns.reverse('8.8.8.8');
console.log(hostnames); // ['dns.google']
自定义 DNS 服务器
有时候你想指定用哪个 DNS 服务器,而不是系统默认的。可以创建一个独立的 Resolver:
import dns from 'node:dns/promises';
const resolver = new dns.Resolver();
resolver.setServers(['8.8.8.8', '1.1.1.1']); // Google + Cloudflare
const result = await resolver.resolve4('example.com');
console.log(result);
缓存与性能
DNS 查询看起来轻,但在高并发场景下,重复的域名解析会拖慢请求。简单缓存可以这样写:
import dns from 'node:dns/promises';
const cache = new Map();
async function cachedLookup(hostname) {
if (cache.has(hostname)) {
return cache.get(hostname);
}
const result = await dns.lookup(hostname);
cache.set(hostname, result);
return result;
}
生产环境建议用更成熟的方案,比如底层库自带的 DNS 缓存,或者在应用层用 LRU Cache 控制内存上限。
net 与 http 的关系
看完 net 你可能会好奇:HTTP 和 TCP 到底差多少?其实 http 模块就是基于 net 封装的。当你调用 http.createServer 时,Node.js 在底层还是开了一个 TCP 服务器,只不过在收到数据后多了一层 HTTP 协议解析:拆请求行、解析头部、按 Content-Length 分 body。
如果你不需要这些 HTTP 语义——比如做个游戏后端、物联网网关、RPC 框架——直接用 net 反而更轻量。