---
URL: "/zh-CN/blog/2026-08/Electron-net.html"
LLMS_URL: "/zh-CN/blog/2026-08/Electron-net.md"
lastUpdated: true
commentabled: true
recommended: true
title: "Electron net 模块你可以没用过，但不能不知道"
description: "Electron net 模块你可以没用过，但不能不知道"
date:

pageClass: "blog-page-class"
cover: "/covers/electron.svg"
---

在桌面端应用开发中，绝大多数 Electron 开发者习惯在渲染进程中使用 `window.fetch()` 或 `axios` 处理网络交互。然而，面对复杂企业网关 PAC 代理失效、多账号 Session 登录态同步、大文件静默下载卡顿、双向 TLS 硬件证书校验等重度场景，主进程（Main Process）的内置 net 模块才是终极解法。

本文立足中高级 Electron 架构设计，抛弃基础入门 API 讲解，通过 4 个核心质问与工业级代码实践，彻底剖析 Electron 网络层的底层机制与选型边界。

## 质问一：Electron 的 `net` 和 Node.js 的 `net` 到底是什么关系？ ##

很多开发者常误以为 Electron 的 `net` 模块是 Node.js 原生 `net` 模块的继承或包装。实际上，两者在底层机制上毫无关联，仅为同名模块。

```txt
                          ┌─────────────────────────────────────────┐
                          │            Electron 主进程              │
                          └────────────────────┬────────────────────┘
                                               │
                        ┌──────────────────────┴──────────────────────┐
                        ▼                                             ▼
         【Chromium Network Stack】                      【Node.js / libuv Stack】
         API: electron.net                              API: http / https / net
         • 应用层 (HTTP/HTTPS/QUIC)                     • 传输层 (TCP/UNIX Socket) / HTTP
         • 自动继承操作系统代理 & PAC                    • 忽略系统代理（需显式配置 Agent）
         • 绑定 Electron Session                        • 独立 Cookie Jar & Node CA 库
```

### 底层架构差异解析 ###

| 维度 | Chromium网络栈 | Node.js默认 |
| :--- | :--- | :--- |
| 网络层级 | 应用层（HTTP/HTTPS/QUIC）<br>传输层（TCP/UNIXDomainSocket） | 应用层（HTTP/HTTPS/QUIC）<br>传输层（TCP/UNIXDomainSocket） |
| 底层引擎 | Chromium Network Stack (Network Service) | libuv+OpenSSL |
| 代理继承 | 自动无感读取Windows/macOS系统代理与PAC脚本 | 默认忽略系统代理，需额外挂载https-proxy-agent |
| DNS解析 | 调用Chromium独立AsyncDNSSResolver (net.resolveHost) | 默认调用操作系统getaddrinfo，并发高时会卡死libuv线程池 |
| 安全体系 | 集成操作系统原生证书信任链（WindowsCert Store/macosKeychain） | 依赖Node.js内置PEM根证书集 |

### Chromium 网络栈的杀手级特性 ###

*原生 HTTP/3 (QUIC) & HTTP/2 支持*：利用 Chromium 的套接字复用与 Connection Pool 管理，彻底规避 TCP 队头阻塞与频繁 TLS 握手开销。

*零配置企业级代理解析*：对于企业内部通过 WPAD/PAC 动态分发的代理规则，或者需要 NTLM / Kerberos 域鉴权的代理网关，net 模块完全无需手动解析代理脚本，继承浏览器的无感通行能力。

## 质问二：net 发请求依赖渲染进程吗？频繁调用会有性能与内存风险吗？ ##

### 物理依赖关系 ###

`net` 模块完全不依赖任何渲染进程或 BrowserWindow。

主进程通过 Mojo IPC 直接与 Chromium 的 Network Service 进程通信。即便应用当前没有任何可见窗口（如托盘后台运行），net 依然可以独立执行网络交互。

### 澄清误区：大文件/离线包下载“必须”放在主进程 ###

有些开发者担心在主进程下载大文件（如几百 MB 的更新包或 AI 模型）会卡死 UI。但将大文件下载移至主进程才是工业级 Electron 应用的推荐做法。

- 生命周期解耦：若在渲染进程下载，用户按 Ctrl+R 刷新页面或关闭当前窗口时，V8 上下文被销毁，下载任务直接中断。放在主进程能够确保下载在后台静默且稳定地运行（如 Electron 官方 `autoUpdater` 的底层架构）。

- 零拷贝流式落盘：主进程使用 `net.request()` 或 `net.fetch()`，通过 Node.js Stream 将响应管道直接 pipe 到本地磁盘，数据完全不经过 V8 堆内存解析，CPU 和内存占用极低：

```javascript
// 主进程：零拷贝大文件流式写盘
const { app, net } = require('electron');
const fs = require('fs');

app.whenReady().then(async () => {
  const response = await net.fetch('https://cdn.example.com/model.bin');
  const fileStream = fs.createWriteStream('./model.bin');

  // 直接通过 Node.js Stream 传输，零 V8 堆内存占用
  const readable = response.body;
  if (readable) {
    // Web Stream 转 Node Stream 并落盘
    const { Readable } = require('stream');
    Readable.fromWeb(readable).pipe(fileStream);
  }
});
```

### 主进程网络调用的三大真实性能陷阱 ###

虽然 `net` 模块本身运行高效，但在实际业务中如果缺乏控制，极易引发假死与内存泄漏：

#### 陷阱 A：高频 IPC 序列化轰炸 ####

现象：主进程每收到 64KB 的下载 chunk，就通过 `ipcMain.send('download-progress', chunk)` 实时推给渲染进程。

代价：频繁的 IPC 跨进程序列化/反序列化导致渲染进程 JS 主线程卡死掉帧。

防坑方案：主进程本地落盘，对 UI 通知进行节流（Throttle） 。例如限制每 500ms 仅通过 IPC 发送一次包含 `progressPercentage` 和 `speed` 的轻量级 JSON 数据。

#### 陷阱 B：CPU 密集型解压/算 Hash 阻塞 Event Loop ####

现象：大文件下载完成后，直接在主进程主线程调用 `crypto.createHash` 计算 SHA256，或调用 adm-zip 同步解压。

代价：主进程 Event Loop 被同步 CPU 密集型任务卡死，导致桌面窗口无法拖动、快捷键无响应。

防坑方案：将 Hash 计算与解压任务交由 Electron 22+ 提供的 UtilityProcess（独立 Node.js 子进程）或 Worker Threads 处理。

#### 陷阱 C：未关闭 Stream 导致连接池耗尽 ####

现象：使用 `net.fetch` 或 `net.request` 发起请求后，若未消费 `response.body` 或未及时销毁未完成的 Stream，底层 Socket 连接将无法被释放回 Socket Pool。

防坑方案：对于未读取完的请求必须配合 AbortController 触发中断信号。

## 质问三：主进程网络选型终极对决——何时用 Electron net？何时用 Node 原生 http / axios？ ##

在 Electron 主进程中，Chromium 网络栈与 Node.js libuv 网络栈同时存在，选型标准如下：

```txt
                             [主进程网络需求]
                                    │
         ┌──────────────────────────┴──────────────────────────┐
         ▼                                                     ▼
【业务优先 & 环境复杂】                                 【特定隔离 & 自定义配置】
  • 需要继承系统 PAC / 域代理                            • 在 UtilityProcess / Worker 中运行
  • 与 Session / Partition 共享 Cookie                  • 必须强制绕过系统代理 (固定 Agent)
  • 依赖系统硬件证书 (mTLS U-Key)                       • 纯内存加载私有 .pem 证书
  • HTTP/3 (QUIC) 弱网优化                             • 重度依赖 Axios 拦截器管道
         │                                                     │
         ▼                                                     ▼
  选择 Electron `net`                                  选择 Node.js `http` / `axios`
```

### 选型决策对比矩阵 ###

| 维度 | Chromium网络栈 | Node.js默认 |
| :--- | :--- | :--- |
| 系统代理/PAC继承 | 100%自动继承 ★★★★★ | 默认忽略（需配置 https-proxy-agent） |
| Session&Cookie绑定 | 原生支持指定 Session/Partition | 必须手动维护 CookieJar |
| 运行环境支持 | Main Process / UtilityProcess / WorkerThreads | 仅限 MainProcess |
| 特化代理/强制绕过代理 | 极为灵活（代码级配置 Agent） | 较难（依赖全局 --proxy-server） |
| 私有内存PEM证书加载 | 极度简单（`ca: fs.readFileSync(...)`） | 需挂载 certificate-error |
| 生态拦截器 | 庞大的 Axios/Got 生态库支持 | 仅支持 Web 标准 Fetch API |

## 质问四：真实企业级复杂业务下，net 模块有哪些杀手级落地姿势？ ##

在成熟的开源项目（如 VS Code 源码 `vs/platform/request/node/requestService.ts`）中，网络模块通常被抽象为一个统一的 Request Service。

以下是 net 模块在企业级生产环境中的 4 个杀手级应用场景。

### 场景一：多 Partition / 多账号沙箱网络隔离 ###

在支持多 Workspace 或多账号同时登录的应用中（如 Slack、Mattermost），必须保证主进程发起的后台请求与特定窗口的 Session 沙箱完全隔离。

```javascript
import { session, net } from 'electron';

// 为特定租户/账号创建隔离的 Session Partition
const tenantPartition = 'persist:tenant_workspace_123';
const tenantSession = session.fromPartition(tenantPartition);

async function fetchTenantData(endpoint: string) {
  // net.fetch 显式绑定特定 session，自动携带该 Partition 内的 Cookie 与 Storage
  const response = await net.fetch(`https://api.tenant.com${endpoint}`, {
    session: tenantSession, // 绑定特定 Session 上下文
    credentials: 'include'  // 自动附带 Session Cookie
  });

  return await response.json();
}
```

### 场景二：企业级 mTLS 双向证书认证与硬件 U-Key 拦截 ###

金融或政企桌面应用常要求插入硬件 U-Key 证书进行客户端双向 TLS 验证。net 模块可直接联动 Electron 的证书事件：

```javascript
import { app, net } from 'electron';

// 捕获客户端证书选择事件（双向 TLS 认证）
app.on('select-client-certificate', (event, webContents, authenticationScheme, certificateList, callback) => {
  event.preventDefault();

  // 查找匹配硬件 U-Key 或系统证书库中的私有证书
  const myCert = certificateList.find(cert => cert.issuerName === 'Enterprise Root CA');
  if (myCert) {
    callback(myCert); // 自动提交匹配的硬件/系统证书
  } else {
    callback(undefined);
  }
});

// 使用 net 发起的请求将自动继承上述证书校验逻辑
async function requestSecureBankAPI() {
  const res = await net.fetch('https://mtls-api.bank.internal/v1/trade');
  return await res.json();
}
```

### 场景三：`protocol.handle` + `net.fetch` 构建离线优先与无感动态代理 ###

结合 protocol.handle，主进程可无缝拦截自定义协议请求，并使用 `net.fetch` 的 `bypassCustomProtocolHandlers` 特性实现本地离线缓存回源：

```javascript
import { protocol, net } from 'electron';
import fs from 'fs/promises';
import path from 'path';

app.whenReady().then(() => {
  // 拦截自定义协议 app:// dynamic 资源
  protocol.handle('app', async (request) => {
    const url = new URL(request.url);
    const localCachePath = path.join(app.getPath('userData'), 'cache', url.pathname);

    // 1. 尝试优先读取本地文件缓存（离线优先策略）
    try {
      const fileData = await fs.readFile(localCachePath);
      return new Response(fileData);
    } catch {
      // 2. 本地无缓存，通过 net.fetch 动态回源远端服务器
      // bypassCustomProtocolHandlers 防止陷入递归循环
      const remoteUrl = `https://cdn.mycompany.com${url.pathname}`;
      const response = await net.fetch(remoteUrl, { bypassCustomProtocolHandlers: true });

      // 异步写盘落盘缓存
      if (response.ok) {
        const buffer = await response.arrayBuffer();
        fs.mkdir(path.dirname(localCachePath), { recursive: true })
          .then(() => fs.writeFile(localCachePath, Buffer.from(buffer)));
        return new Response(buffer);
      }

      return new Response('Resource Not Found', { status: 404 });
    }
  });
});
```

### 场景四：AI 桌面端 SSE (Server-Sent Events) 流式背压与传输 ###

重构 AI 桌面端（如类 Cursor、Claude 客户端）时，主进程通过 `net.fetch` 接收大模型 LLM Token 流，并配合 MessageChannelMain 零阻塞推送至渲染进程：

```javascript
import { ipcMain, net, MessageChannelMain } from 'electron';

ipcMain.handle('ai:stream-chat', async (event, prompt) => {
  const { port1, port2 } = new MessageChannelMain();

  // 1. 将 port2 传输给渲染进程
  event.sender.postMessage('ai:stream-port', null, [port2]);

  // 2. 主进程发起 AI 接口流式请求
  const response = await net.fetch('https://api.openai.com/v1/chat/completions', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer YOUR_KEY' },
    body: JSON.stringify({ model: 'gpt-4o', messages: [{ role: 'user', content: prompt }], stream: true })
  });

  const reader = response.body?.getReader();
  const decoder = new TextDecoder();

  // 3. 处理流式读取与背压控制
  if (reader) {
    while (true) {
      const { done, value } = await reader.read();
      if (done) {
        port1.postMessage({ done: true });
        port1.close();
        break;
      }

      const chunkText = decoder.decode(value, { stream: true });
      // 使用专用 MessagePort 传输 Token，完全绕过 ipcMain/ipcRenderer 主通道
      port1.postMessage({ done: false, data: chunkText });
    }
  }
});
```

## 总结与架构最佳实践 ##

在现代 Electron 架构中，网络层的三层分工原则可分层为：

```txt
┌─────────────────────────────────────────────────────────────────┐
│                    三层架构网络职责分工                         │
├─────────────────────────────────────────────────────────────────┤
│ 1. 主进程 (Main Process) ─── [本地后端]                         │
│    • 处理特权请求、企业系统代理继承 (net.fetch)                 │
│    • 大文件/更新包静默流式落盘与加解密                          │
│                                                                 │
│ 2. 预加载脚本 (Preload) ───── [API 安全网关]                    │
│    • 严禁直接导出原生 net / ipcRenderer                         │
│    • 仅导出具有业务语义的函数，过滤输入参数                     │
│                                                                 │
│ 3. 渲染进程 (Renderer) ───── [纯粹前端 UI]                      │
│    • 常规同源 UI 交互 API 直接使用原生 window.fetch / axios     │
│    • 涉及代理、证书或重度后台任务时调用 Preload 桥接方法        │
└─────────────────────────────────────────────────────────────────┘
```
