TypeScript 网络编程从零到一:net 模块全解析(入门专属)

TypeScript 网络编程从零到一:net 模块全解析

很多刚接触网络编程的同学都会有一个困惑:B 站、论坛上的教程全是 C++/Java,语法晦涩、环境折腾,还没写通一个 TCP 连接就先被劝退了。

但其实,TypeScript + Node.js 是新手入门网络编程的最优选择 :语法友好、跨平台、不用折腾复杂的编译环境,Node.js 内置的 net 模块封装了完整的 TCP 网络通信能力,API 设计简洁易懂,写通一个客户端 - 服务端通信只需要十几行代码。

这篇博客会从零开始,带你吃透 net 模块的所有核心能力,从基础概念到完整实战,再到新手必踩的坑,全程只讲 TypeScript,无额外技术栈干扰,看完就能独立写出完整的 TCP 网络程序。


一、前置环境准备(一步到位,零踩坑)

1. 必须安装的环境

  • Node.js:LTS 长期支持版(官网直接下载,下一步安装即可,自带 npm)

  • TypeScript

    :全局安装,终端执行:

    bash 复制代码
    npm install -g typescript @types/node
  • 编辑器:VSCode(代码提示、终端集成,对 TS 新手最友好)

2. 验证环境是否正常

终端执行以下 3 条命令,能正常输出版本号即环境正常:

bash 复制代码
node -v
npm -v
tsc -v

3. 项目初始化 & TS 配置

  1. 新建一个空文件夹,用 VSCode 打开

  2. 终端执行 tsc --init,自动生成 tsconfig.json 配置文件

  3. 复制代码
    tsconfig.json

    里的内容替换为以下极简可用配置(新手零报错):

    json 复制代码
    {
      "compilerOptions": {
        "target": "ES6",
        "module": "CommonJS",
        "outDir": "./dist",
        "rootDir": "./src",
        "esModuleInterop": true,
        "strict": true,
        "skipLibCheck": true
      },
      "include": ["src/**/*"]
    }
  4. 新建 src 文件夹,我们所有的代码都放在这里面。

4. 最稳的 TS 代码运行方式(新手必看)

很多新手第一步就栽在运行命令上,这里给你 2 种零报错、兼容所有系统的运行方式,优先用第一种。

方式 1:先编译,后运行(最稳,无任何依赖)

TS 代码不能直接被 Node.js 执行,必须先编译成 JS,再运行,分 2 步执行,避开所有语法坑:

  1. 编译 TS 代码:tsc src/你的文件名.ts
  2. 运行编译后的 JS 文件:node dist/你的文件名.js
方式 2:ts-node 直接运行(省事,开发首选)

全局安装 ts-node 后,可跳过手动编译,直接运行 TS 代码:

  1. 全局安装:npm install -g ts-node
  2. 直接运行:ts-node src/你的文件名.ts

二、核心概念大白话扫盲(先懂逻辑,再写代码)

网络编程的本质,就是两个运行在不同设备(或同一设备)上的程序,通过网络互相收发数据。我们用「打电话」的类比,一次性搞懂所有核心概念,再也不会被术语绕晕。

术语 大白话解释 打电话类比
TCP 传输控制协议,一种可靠、面向连接的网络通信标准,保证数据不丢失、不重复、按顺序到达 正规的有线电话,必须对方接通了才能说话,不会漏话、不会串线
服务端(Server) 被动等待连接的程序,一直监听一个固定的端口,等待客户端连接 守在固定电话旁的人,等着别人打电话进来
客户端(Client) 主动发起连接的程序,知道服务端的地址和端口,主动发起通信 主动拨打电话的人
Socket 网络通信的「通道」,服务端和客户端建立连接后,会生成一个专属的 socket,所有数据收发都通过这个 socket 完成 电话线,连接两端的通话通道
端口(Port) 设备上的「门牌号」,范围 0-65535,一个端口只能被一个程序占用,客户端通过端口找到对应的服务端 固定电话的号码,拨对号码才能找到对应的人
IP 地址 设备在网络中的「身份证号」,局域网 / 公网中唯一标识一台设备 你家的地址,快递(数据)能精准送到

我们接下来所有的代码,都是围绕「服务端监听端口 → 客户端主动连接 → 两端通过 socket 收发数据」这个核心逻辑展开的。


三、net 模块核心 API 全解析(逐行讲透,细节拉满)

net 是 Node.js 内置的 TCP 网络编程模块,无需额外安装,直接导入即可使用。注意:TS 中必须用 import \* as net from 'net' 导入,不能用 import net from 'net',否则会报「no default export」错误,这是新手第一大坑。

我们把 API 分为「服务端 API」和「客户端 API」两部分,逐个拆解,每个 API 都讲清作用、参数、示例和触发时机。

1. 服务端核心 API

1.1 net.createServer([options], connectionListener)

作用:创建一个 TCP 服务端实例,是所有服务端代码的入口。

  • 参数 1 options(可选):服务端配置,新手入门几乎不用,默认即可
  • 参数 2 connectionListener(必填) :回调函数,每当有新的客户端连接成功时,就会执行一次
  • 回调函数的参数 socket:核心中的核心,代表「和当前客户端的专属连接通道」,所有和这个客户端的数据收发、断开操作,都通过这个 socket 完成
  • 返回值net.Server 实例,也就是我们创建的服务端

基础示例

typescript 复制代码
import * as net from 'net';

// 创建TCP服务端
const server = net.createServer((socket: net.Socket) => {
  // 有新客户端连接时,这里就会执行
  console.log(`新客户端连接成功!客户端地址:${socket.remoteAddress}:${socket.remotePort}`);
});
1.2 server.listen(port, [host], [backlog], [callback])

作用 :让服务端开始监听指定的端口,等待客户端连接。服务端必须调用 listen 后,才能接收客户端连接

  • 参数 1 port(必填):要监听的端口号,范围 1024-65535(1024 以下是系统保留端口)
  • 参数 2 host(可选) :要监听的 IP 地址,默认 0.0.0.0(监听本机所有网卡地址,局域网内其他设备也能连接)
  • 参数 3 backlog(可选):最大等待连接数,新手不用管,默认即可
  • 参数 4 callback(可选) :回调函数,监听成功后,仅执行一次,用来提示服务端启动完成

新手必懂的误区纠正

listen 不是「每隔几秒刷新一次」,而是让服务端进入「持续阻塞监听」状态,只要服务端不关闭,就会一直守在这个端口,等待客户端连接,不会自动停止。

基础示例

typescript 复制代码
// 监听3000端口
server.listen(3000, '0.0.0.0', () => {
  console.log('✅ TCP服务端启动成功,正在监听3000端口...');
});
1.3 server.close([callback])

作用:关闭服务端,停止接收新的客户端连接,已经建立的连接不会被强制断开。

  • 参数 callback(可选):服务端完全关闭后执行的回调函数

示例

typescript 复制代码
// 10秒后关闭服务端
setTimeout(() => {
  server.close(() => {
    console.log('❌ 服务端已关闭,不再接收新连接');
  });
}, 10000);
1.4 服务端常用事件监听 server.on('事件名', 回调)

和 DOM 的事件监听逻辑一致,当服务端发生对应事件时,会自动执行回调函数。

事件名 触发时机 常用场景
listening 服务端成功调用 listen,开始监听端口时 打印服务端启动成功日志
connection 有新的客户端连接成功时 和 createServer 的回调函数作用一致,管理新连接
close 服务端完全关闭时 打印关闭日志,清理资源
error 服务端发生错误时(最常见:端口被占用) 捕获错误,提示用户更换端口

完整的服务端事件示例

typescript 复制代码
// 监听启动成功
server.on('listening', () => {
  console.log('服务端开始监听端口');
});

// 监听新连接
server.on('connection', (socket) => {
  console.log('有新客户端接入');
});

// 监听关闭
server.on('close', () => {
  console.log('服务端已关闭');
});

// 监听错误(必加!否则端口被占用会直接崩溃)
server.on('error', (err: Error) => {
  console.error('服务端出错:', err.message);
  // 端口被占用时的提示
  if (err.message.includes('EADDRINUSE')) {
    console.error('端口3000已被占用,请更换端口或关闭占用程序');
  }
});

2. Socket 核心 API(服务端 & 客户端通用)

socket 是网络通信的核心,无论是服务端接收到的连接,还是客户端创建的连接,都是 net.Socket 实例,所有数据收发、连接管理都通过它完成,API 完全通用。

2.1 数据收发 API
API 作用 参数说明
socket.write(data, [encoding], [callback]) 向对方发送数据 data:要发送的内容,支持字符串 / Buffer;encoding:编码,默认 utf8;callback:发送成功后的回调
socket.on('data', (data: Buffer) => {}) 监听对方发来的数据 回调函数的参数 data 是 Buffer 类型,必须用 .toString() 转成字符串才能正常读取
socket.end([data], [encoding], [callback]) 主动关闭当前连接 可选参数 data:关闭前先发送这段数据,再断开连接

核心示例

typescript 复制代码
// 监听对方发来的数据
socket.on('data', (data: Buffer) => {
  // 把Buffer转成字符串
  const message = data.toString();
  console.log('收到对方消息:', message);

  // 给对方回消息
  socket.write(`我已收到你的消息:${message}`);

  // 回复后,主动断开连接
  socket.end();
});
2.2 连接状态 & 事件监听

socket 的所有状态变化,都通过事件监听来处理,这是 TCP 通信的核心逻辑。

事件名 触发时机 常用场景
connect 连接成功建立时 客户端专用,连接成功后立刻给服务端发消息
data 收到对方发来的数据时 核心,处理对方发送的内容
end 对方主动关闭连接时 打印断开提示,清理资源
close 连接完全关闭时 打印连接断开日志
error 连接发生错误时 捕获错误,避免程序崩溃
timeout 连接超时时 断开长时间无响应的连接,释放资源
2.3 常用属性
属性 含义 常用场景
socket.remoteAddress 对方的 IP 地址 区分不同的客户端,打印日志
socket.remotePort 对方的端口号 区分不同的客户端
socket.localAddress 本地的 IP 地址 调试网络配置
socket.localPort 本地的端口号 调试网络配置
socket.connected 布尔值,当前连接是否处于活跃状态 发送数据前判断连接是否正常

3. 客户端核心 API

3.1 net.createConnection(options, [connectListener])

作用:创建一个 TCP 客户端实例,主动向服务端发起连接。

  • 参数 1 options(必填)

    :连接配置,核心参数如下

    参数 含义 必填 默认值
    port 要连接的服务端端口号
    host 要连接的服务端 IP 地址 127.0.0.1(本机)
  • 参数 2 connectListener(可选) :回调函数,和服务端连接成功后,仅执行一次

  • 返回值net.Socket 实例,也就是客户端的连接通道,后续所有数据收发都通过它完成

基础示例

typescript 复制代码
import * as net from 'net';

// 主动连接本机3000端口的服务端
const client = net.createConnection({ port: 3000, host: '127.0.0.1' }, () => {
  // 连接成功后执行
  console.log('✅ 成功连接到服务端!');
  // 连接成功后,立刻给服务端发消息
  client.write('你好服务端,我是客户端!');
});
3.2 客户端事件监听

客户端的事件监听和 socket 完全通用,核心常用事件如下:

typescript 复制代码
// 监听服务端发来的消息
client.on('data', (data: Buffer) => {
  console.log('📩 服务端回复:', data.toString());
  // 收到回复后,1秒后主动断开连接
  setTimeout(() => {
    client.end();
  }, 1000);
});

// 监听连接断开
client.on('close', () => {
  console.log('👋 与服务端的连接已断开');
});

// 监听连接错误(必加!服务端没启动会触发)
client.on('error', (err: Error) => {
  console.error('❌ 连接失败:', err.message);
  if (err.message.includes('ECONNREFUSED')) {
    console.error('请检查服务端是否已启动,端口号是否正确');
  }
});

四、实战案例:从极简到进阶,写通每一行

案例 1:极简 TCP 回声服务(入门必写,10 行代码跑通)

回声服务的逻辑:服务端收到客户端的消息后,原封不动地回传给客户端,是网络编程最经典的入门案例。

1. 服务端代码 `src/echo-server.ts
typescript 复制代码
import * as net from 'net';

// 创建服务端
const server = net.createServer((socket: net.Socket) => {
  console.log(`客户端 ${socket.remoteAddress}:${socket.remotePort} 已连接`);

  // 收到客户端消息,原封不动回传
  socket.on('data', (data: Buffer) => {
    const message = data.toString();
    console.log(`收到消息:${message}`);
    socket.write(`回声:${message}`);
  });

  // 客户端断开连接
  socket.on('close', () => {
    console.log(`客户端 ${socket.remoteAddress}:${socket.remotePort} 已断开`);
  });

  // 捕获连接错误
  socket.on('error', (err) => {
    console.error('客户端连接出错:', err.message);
  });
});

// 监听3000端口
server.listen(3000, () => {
  console.log('✅ 回声服务端已启动,监听3000端口');
});

// 捕获服务端错误
server.on('error', (err) => {
  console.error('服务端出错:', err.message);
});
2. 客户端代码 `src/echo-client.ts
typescript 复制代码
import * as net from 'net';

// 连接服务端
const client = net.createConnection({ port: 3000 }, () => {
  console.log('✅ 已连接到回声服务端');
  // 发送测试消息
  client.write('Hello TypeScript 网络编程!');
});

// 接收服务端的回声
client.on('data', (data: Buffer) => {
  console.log('📩 收到服务端回复:', data.toString());
  // 收到回复后断开连接
  client.end();
});

// 断开提示
client.on('close', () => {
  console.log('👋 连接已断开');
});

// 错误处理
client.on('error', (err) => {
  console.error('❌ 连接失败:', err.message);
});
3. 运行步骤(必看!)
  1. 先启动服务端

    :新开一个终端,执行

    bash 复制代码
    tsc src/echo-server.ts && node dist/echo-server.js

    看到「服务端已启动」的提示,就说明服务端正常运行,

    这个终端不要关!

  2. 再启动客户端

    :再新开一个终端(必须新开,不能复用服务端的终端),执行

    bash 复制代码
    tsc src/echo-client.ts && node dist/echo-client.js
  3. 观察两个终端的日志,就能看到完整的通信过程了。

案例 2:多人 TCP 聊天室(进阶实战,巩固核心能力)

学会了基础的一对一通信,我们来写一个支持多人同时在线的聊天室,核心逻辑是:服务端管理所有接入的客户端连接,收到某个客户端的消息后,广播给所有其他在线的客户端。

服务端代码 `src/chat-server.ts
typescript 复制代码
import * as net from 'net';

// 核心:存储所有在线的客户端连接
const clientMap = new Map<string, net.Socket>();

// 创建服务端
const server = net.createServer((socket: net.Socket) => {
  // 生成客户端唯一标识
  const clientId = `${socket.remoteAddress}:${socket.remotePort}`;
  console.log(`新用户加入聊天室:${clientId}`);

  // 把新客户端加入map
  clientMap.set(clientId, socket);

  // 给所有在线用户广播新用户加入的消息
  broadcast(`【系统消息】用户 ${clientId} 加入了聊天室`, clientId);

  // 监听用户发来的消息
  socket.on('data', (data: Buffer) => {
    const message = data.toString().trim();
    console.log(`${clientId} 说:${message}`);
    // 把消息广播给所有其他用户
    broadcast(`【${clientId}】:${message}`, clientId);
  });

  // 用户断开连接
  socket.on('close', () => {
    console.log(`用户 ${clientId} 离开了聊天室`);
    // 从map中移除
    clientMap.delete(clientId);
    // 广播用户离开的消息
    broadcast(`【系统消息】用户 ${clientId} 离开了聊天室`, clientId);
  });

  // 错误处理
  socket.on('error', (err) => {
    console.error(`用户 ${clientId} 连接出错:`, err.message);
    clientMap.delete(clientId);
  });
});

// 广播消息的工具函数
function broadcast(message: string, excludeClientId: string) {
  // 遍历所有在线客户端,发送消息
  for (const [clientId, socket] of clientMap) {
    // 不给发送者自己发消息
    if (clientId !== excludeClientId && socket.connected) {
      socket.write(message);
    }
  }
}

// 启动服务端
server.listen(3000, () => {
  console.log('✅ 多人聊天室服务端已启动,监听3000端口');
});

// 服务端错误处理
server.on('error', (err) => {
  console.error('服务端出错:', err.message);
});
客户端代码 `src/chat-client.ts
typescript 复制代码
import * as net from 'net';
import * as readline from 'readline';

// 创建终端输入接口,让用户能在终端输入消息
const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});

// 连接聊天室服务端
const client = net.createConnection({ port: 3000 }, () => {
  console.log('✅ 成功加入多人聊天室!');
  console.log('请输入消息,按回车发送:');

  // 监听用户在终端的输入
  rl.on('line', (input) => {
    // 把用户输入的内容发送给服务端
    client.write(input.trim());
  });
});

// 监听服务端广播的消息
client.on('data', (data: Buffer) => {
  // 换行打印消息,避免和输入框重叠
  console.log(`\n${data.toString()}`);
  console.log('请输入消息,按回车发送:');
});

// 连接断开
client.on('close', () => {
  console.log('\n👋 已退出聊天室');
  rl.close();
  process.exit(0);
});

// 错误处理
client.on('error', (err) => {
  console.error('❌ 聊天室连接失败:', err.message);
  rl.close();
  process.exit(1);
});
运行效果
  1. 启动服务端
  2. 新开多个终端,分别启动多个客户端
  3. 在任意一个客户端输入消息,其他所有客户端都能收到,完美实现多人聊天室。

五、新手必踩的 10 个坑 & 避坑指南

我把 TS 网络编程入门阶段 99% 的新手会踩的坑都整理在这里,提前避坑,少走弯路。

1. 导入错误:Module "net" has no default export

  • 原因 :用了 import net from 'net' 的错误写法
  • 解决 :必须用 import * as net from 'net' 导入

2. 运行命令报错:标记"&&"不是此版本中的有效语句分隔符

  • 原因 :Windows 旧版 PowerShell(5.x)不支持 && 语法
  • 解决 :分两行执行命令,先 tsc 文件名.ts,再 node 文件名.js;或者升级到 PowerShell 7+

3. 端口占用报错:EADDRINUSE :::3000

  • 原因:3000 端口已经被其他程序占用,或者上一次的服务端没正常关闭
  • 解决:
    1. 终端按 Ctrl+C 关闭正在运行的服务端
    2. 更换代码里的端口号(比如 3001)
    3. Windows 执行 netstat -ano | findstr "3000" 找到占用进程,杀掉即可

4. 客户端连接失败:ECONNREFUSED 127.0.0.1:3000

  • 原因:服务端没启动,或者端口号、IP 地址填错了
  • 解决:先确认服务端已经正常启动,端口号和客户端填写的完全一致,IP 地址正确

5. 终端复用导致客户端无法运行

  • 原因:服务端启动后占住了终端,再在同一个终端运行客户端,自然无法执行
  • 解决 :服务端和客户端必须在两个完全独立的终端里运行,不能复用同一个终端

6. 收到的消息是乱码,或者是 <Buffer 68 65 6c 6c 6f>

  • 原因 :收到的 data 是 Buffer 类型,没有转成字符串
  • 解决 :用 data.toString() 把 Buffer 转成 UTF8 字符串

7. 服务端重启后,客户端连不上

  • 原因:TCP 连接有「TIME_WAIT」状态,服务端关闭后,端口会被临时占用几十秒

  • 解决

    :等待几十秒再重启,或者给服务端加上

    复制代码
    reuseAddress

    配置:

    typescript 复制代码
    const server = net.createServer({ allowHalfOpen: false, pauseOnConnect: false });
    server.on('error', (err) => {
      if (err.message.includes('EADDRINUSE')) {
        server.close();
        server.listen(3000);
      }
    });

8. 多次发送消息,服务端一次收到了多条

  • 原因:TCP 的「粘包问题」,TCP 是面向流的协议,会把多次小的数据包合并成一个大的发送
  • 入门级解决 :给每条消息加结束符(比如换行符 \n),接收端按结束符拆分消息

9. 客户端断开后,服务端还在给它发消息,导致崩溃

  • 原因:没有判断连接是否还处于活跃状态,给已经断开的 socket 执行 write 操作
  • 解决 :发送消息前,先判断 socket.connected 是否为 true,只有活跃的连接才能发消息

10. Code Runner 点运行,总是跑错文件

  • 原因:Code Runner 默认复用同一个终端,且没有正确识别当前打开的文件

  • 解决:

    1. 打开 VSCode 设置,勾选 Code Runner: Run In New Terminal(每次运行新开终端)
    2. 勾选 Code Runner: Respect File Dir(识别当前打开的文件)
    3. 把 Code Runner 的 TS 执行命令改成:tsc $fileName && node $fileNameWithoutExt.js

六、进阶学习路线

学完这篇博客的内容,你已经掌握了 TS 网络编程的核心能力,能独立写出完整的 TCP 通信程序。接下来可以按这个路线继续深入:

  1. UDP 网络编程 :用 Node.js 内置的 dgram 模块,学习无连接的 UDP 通信,适合游戏、直播等实时场景
  2. HTTP/HTTPS 服务开发 :用 Node.js 内置的 http/https 模块,手写 HTTP 接口服务,理解 Web 请求的本质
  3. WebSocket 通信:学习双向实时通信,适合 IM 聊天、实时看板等场景
  4. 计算机网络基础:深入学习 TCP/IP 协议、三次握手 / 四次挥手、OSI 七层模型,理解底层原理
  5. 高并发网络模型:学习 Node.js 的事件循环、异步 IO、Reactor 模型,理解高性能网络服务的底层逻辑

很多人觉得网络编程门槛很高,必须学 C++/Java 才能入门,但其实 TypeScript + Node.js 给我们提供了一个极低门槛的入门方式,不用折腾复杂的环境,不用被晦涩的语法劝退,先写通核心逻辑,再逐步深入底层原理。

网络编程的核心从来不是语言,而是「通信逻辑」和「网络协议」,你用 TS 掌握了这些核心能力,未来无论切换到什么语言,都能快速上手。

希望这篇博客能帮你敲开网络编程的大门,少走弯路,真正享受编程的乐趣。

相关推荐
FS_Marking1 小时前
短距离网络10G SFP+光模块选型指南
网络·人工智能
爱学习的小囧2 小时前
vSphere 9.0 API 实操教程 —— 轻松检索 vGPU 与 DirectPath 配置文件
linux·运维·服务器·网络·数据库·esxi·vmware
fei_sun2 小时前
数字积木(IP)设计流程
服务器·网络·tcp/ip
椰猫子2 小时前
Javaweb(Http、Maven)
网络·网络协议·http
EasyGBS2 小时前
从“看得见”到“看得安全”:国密GB35114国标GB28181平台EasyGBS双标协同重构安防视频安全体系
网络·安全·重构
苏瞳儿2 小时前
前端/后端-配置跨域
前端·javascript·node.js·vue
竹林8182 小时前
从轮询到订阅:我在 React 项目中实现实时监听 ERC-20 转账事件的完整踩坑记录
前端·javascript
@encryption2 小时前
HCIA第一次作业
网络
小心我捶你啊2 小时前
SOCKS5与HTTP代理的差异与应用场景抉择
网络·网络协议·http
视觉CG2 小时前
【tailwindcss】网页标题样式
javascript·ecmascript·tailwindcss