# 端口与 localhost 原理

> 💡 **学习指南**：当你执行 `npm run dev`，终端里出现 `http://localhost:5173` 时，你有没有想过：`localhost` 是什么？`5173` 又代表什么？为什么有时候会报 `EADDRINUSE` 错误？本章就来把这些日常开发中天天见、却很少深究的概念一次讲透。

在开始之前，建议你先补两块"基础砖"：

- **网络基础**：如果你不太清楚 IP 地址和 HTTP 的概念，可以先看 [计算机基础 - 网络通信](../1-computer-fundamentals/network-fundamentals.md) 部分。
- **终端基础**：如果你还不熟悉终端命令行，可以先看 [命令行与 Shell 脚本导论](./command-line-shell.md)。

---

## 0. 引言：那个天天见的 `localhost:5173` 到底是什么

<DevServerFlowDemo />

每个开发者的日常都离不开这一行输出：

```
➜  Local:   http://localhost:5173/
```

但你有没有想过，这短短一行字里，藏着好几个关键概念：

- **http://** → 通信协议（用什么语言对话）
- **localhost** → 目标地址（找谁）
- **:5173** → 端口号（找到之后，敲哪扇门）

搞懂这三件事，你就能理解 90% 的开发环境网络问题。接下来我们逐个拆解。

---

## 1. 端口定义

### 1.1 一个直觉比喻

想象一台服务器是一栋大楼：

- **IP 地址**（如 `192.168.1.100`）就是大楼的门牌地址——告诉你"去哪栋楼"。
- **端口号**（如 `:80`）就是楼里的房间号——告诉你"进哪间房"。

一栋楼里可以同时有餐厅（80 号房）、咖啡厅（443 号房）、办公室（22 号房）。同理，一台电脑上可以同时运行 Web 服务器、数据库、SSH 服务，各自占用不同的端口。

👇 **动手点点看**：
点击下面的"房间门牌"，模拟向不同端口发起连接。注意观察：当端口"开着"（有程序在监听）和"关着"时，分别会发生什么？

<PortAnalogyDemo />

### 1.2 端口号的取值范围

端口号是一个 **0–65535** 之间的整数（共 65536 个）。这么多端口被分为三个区间：

| 区间 | 范围 | 用途 | 举例 |
| :--- | :--- | :--- | :--- |
| **系统端口** | 0 – 1023 | 预留给标准协议，普通用户不能随意占用 | 80 (HTTP)、443 (HTTPS)、22 (SSH) |
| **注册端口** | 1024 – 49151 | 给常见应用注册使用 | 3306 (MySQL)、5432 (PostgreSQL)、6379 (Redis) |
| **动态端口** | 49152 – 65535 | 操作系统临时分配 | 浏览器发请求时，系统随机分配一个源端口 |

> 为什么你的开发服务器喜欢用 3000、5173、8080？因为这些都在"注册端口"范围内，不需要管理员权限就能监听，又不太容易和系统服务冲突。

### 1.3 开发中常见的端口号速查

👇 **动手点点看**：
输入端口号或服务名搜索，点击任意一行可以展开查看使用示例。

<CommonPortsDemo />

---

## 2. localhost定义

### 2.1 "环回"的核心概念

`localhost` 是一个特殊的域名，它永远指向**你自己这台电脑**。

当你在浏览器输入 `http://localhost:3000` 时，发生了这些事：

1. 浏览器问操作系统："`localhost` 的 IP 是多少？"
2. 操作系统直接回答："`127.0.0.1`"（不需要联网查 DNS）
3. 数据包发往 `127.0.0.1`，但**不会真的离开本机**
4. 操作系统通过"环回接口（loopback interface）"把数据包**折返**回来
5. 监听在 3000 端口上的程序收到请求，返回响应

**整个过程不经过网线、不经过路由器、不需要联网。**

👇 **动手点点看**：
点击"发送请求"，观察数据包的完整旅程。然后点击下方的"马甲卡片"，了解 localhost 的几种写法和区别。

<LocalhostLoopbackDemo />

### 2.2 `localhost` vs `127.0.0.1` vs `0.0.0.0`

这三个概念经常被混淆，但它们的含义完全不同：

| 写法 | 含义 | 谁能访问 |
| :--- | :--- | :--- |
| `localhost` / `127.0.0.1` | 环回地址，仅本机 | 只有你自己的电脑 |
| `0.0.0.0` | 监听所有网络接口 | 本机 + 局域网内其他设备 |
| `192.168.x.x` | 局域网 IP | 局域网内的设备 |

**实际场景**：

```bash
# 只有自己能访问（安全，适合开发）
npm run dev -- --host localhost

# 手机也能访问（适合移动端调试）
npm run dev -- --host 0.0.0.0
```

> 很多框架（如 Vite、Next.js）默认监听 `localhost`，所以你的手机即使连着同一个 WiFi 也访问不了。想用手机调试？加上 `--host` 参数就行。

---

## 3. 端口冲突：最常见的开发环境问题

### 3.1 会冲突的设计理由

**一个端口同一时刻只能被一个程序监听。** 这就像一个房间只能住一户人家。

如果你尝试启动第二个服务在同一个端口上，就会看到这个经典错误：

```
Error: listen EADDRINUSE :::3000
```

翻译成人话就是：**"3000 号房已经有人住了，你进不去！"**

常见的冲突场景：
- 上次的开发服务器没关干净，还在后台运行
- 两个不同的项目用了相同的默认端口
- 某个系统服务已经占用了你想要的端口

👇 **动手点点看**：
试着在下面的模拟器里多次启动服务。当端口冲突时，对比"直接启动"和"智能启动"的不同处理方式。

<PortConflictDemo />

### 3.2 排查与解决

遇到端口冲突时，排查流程非常固定：

**macOS / Linux：**
```bash
# 第一步：查看谁在占用 3000 端口
lsof -i :3000

# 第二步：拿到 PID 后，强制终止
kill -9 <PID>
```

**Windows：**
```bash
# 第一步：查看谁在占用 3000 端口
netstat -ano | findstr :3000

# 第二步：终止进程
taskkill /PID <PID> /F
```

> 很多现代框架（Vite、Create React App 等）遇到端口冲突时会自动询问"是否换一个端口？"。但了解底层原理，能帮你更快地排查那些框架帮不了你的疑难杂症。

---

## 4. 开发中的"同源策略"与跨域

### 4.1 源概述

浏览器有一个安全机制叫做**同源策略（Same-Origin Policy）**：只有**协议、域名、端口**三者完全一致，才算"同源"。

| 地址 A | 地址 B | 是否同源 | 原因 |
| :--- | :--- | :--- | :--- |
| `http://localhost:5173` | `http://localhost:5173/about` | ✅ 同源 | 协议、域名、端口都一样 |
| `http://localhost:5173` | `http://localhost:3000` | ❌ 不同源 | **端口不同**（5173 vs 3000） |
| `http://localhost:5173` | `https://localhost:5173` | ❌ 不同源 | **协议不同**（http vs https） |

### 4.2 前后端分离必然遇到跨域的原理

当你的项目架构是：

```
前端 (Vite)  →  http://localhost:5173
后端 (Express) →  http://localhost:3000
```

前端页面从 `:5173` 加载，然后用 `fetch('/api/users')` 去请求 `:3000` 的接口——**端口不一样，触发跨域限制！**

**两种常见解决方案：**

**方案一：后端配置 CORS**
```javascript
// Express 后端
app.use(cors({ origin: 'http://localhost:5173' }))
```

**方案二：前端配置代理（推荐）**
```javascript
// vite.config.js
export default {
  server: {
    proxy: {
      '/api': 'http://localhost:3000'
    }
  }
}
```

代理的原理：让 Vite 开发服务器帮你"转发"请求。浏览器以为自己在和 `:5173` 通信（同源），实际上 Vite 在背后偷偷帮你把请求转给了 `:3000`。

---

## 5. 实战排查：三个最常见的问题

👇 **动手点点看**：
选择一个你遇到过的问题，跟着步骤一起排查。每一步都可以点击"执行"查看输出。

<PortTroubleshootDemo />

---

## 6. 名词对照表

| 英文术语 | 中文对照 | 解释 |
| :--- | :--- | :--- |
| **Port** | 端口 | 一个 0–65535 的数字，用来区分同一台机器上的不同网络服务。每个服务"监听"一个端口，等待客户端连接。 |
| **localhost** | 本地主机 | 一个特殊域名，永远指向本机（127.0.0.1）。用于在不联网的情况下访问本机上运行的服务。 |
| **Loopback Interface** | 环回接口 | 操作系统的虚拟网络接口。发往 127.0.0.1 的数据包不会离开本机，而是通过该接口"折返"回来。 |
| **EADDRINUSE** | 地址已被使用 | Node.js / 操作系统报的错误，表示你要监听的端口已经被另一个程序占用了。 |
| **CORS** | 跨域资源共享 | 浏览器安全机制。当前端页面尝试请求不同源（协议/域名/端口不同）的接口时，需要后端明确许可。 |
| **Same-Origin Policy** | 同源策略 | 浏览器的安全基石：只允许同协议、同域名、同端口的请求自由通信，阻止跨域的数据读取。 |
| **Proxy** | 代理 | 在开发环境中，代理服务器代替浏览器向后端转发请求，绕过浏览器的同源限制。 |
| **0.0.0.0** | 所有接口 | 当服务监听 0.0.0.0 时，表示它接受来自任何网络接口（本机、局域网等）的连接。 |
| **Well-known Ports** | 知名端口 | 0–1023 端口的统称，预留给 HTTP (80)、HTTPS (443)、SSH (22) 等标准协议。 |
| **PID** | 进程 ID | 操作系统为每个运行中的程序分配的唯一编号，用于管理和终止进程。 |
| **lsof** | 列出打开的文件 | macOS/Linux 命令，用于查看哪个进程占用了某个端口（`lsof -i :端口号`）。 |
| **HMR** | 热模块替换 | 开发服务器的功能：你修改代码后，浏览器自动更新，无需手动刷新页面。底层通过 WebSocket 通知浏览器。 |

---

## 总结

端口和 localhost 是开发环境中最基础、最高频的概念：

- **端口** = 一台机器上区分不同服务的"门牌号"（0–65535）
- **localhost** = "自己找自己"的特殊地址（127.0.0.1），数据不出本机
- **端口冲突**的本质是"一个门牌只能挂一块牌子"
- **跨域**的本质是"端口不同 = 不同源"，需要 CORS 或代理来解决

记住这四句话，你在开发环境里遇到的大多数网络问题，都能快速定位原因。
