端口 VS 套接字
Source: Dev.to
什么是端口?
端口只是一个数字(0–65535),用于标识机器上的某项服务。
- IP 地址 → 标识机器
- 端口 → 标识机器内部的应用程序
常用端口
22→ SSH80→ HTTP443→ HTTPS3306→ MySQL
当你看到 192.168.1.10:443 时,它表示:
- 机器 IP =
192.168.1.10 - 服务 = 运行在端口
443上
单独的端口并不意味着已经建立了连接;它仅表示有进程在监听。
什么是套接字?
套接字是完整的通信端点。它包括:
- IP 地址
- 端口
- 协议(TCP/UDP)
真实的 TCP 连接通过以下 5‑元组 唯一标识:
Source IP + Source Port + Destination IP + Destination Port + Protocol
示例
- 客户端:
10.0.0.5:51512 - 服务器:
192.168.1.10:443 - 协议:TCP
该 5‑元组定义了唯一的连接。
| 端口 | 套接字 |
|---|---|
| 仅是一个数字 | 完整的通信端点 |
| 标识一个服务 | 标识一个连接 |
| 在没有流量时仍然存在 | 在通信期间存在 |
套接字是如何创建的?
客户端
当浏览器连接到 HTTPS 时:
-
socket()– 应用程序请求内核创建一个套接字。- 内核在内存中分配一个套接字结构。
- 返回一个文件描述符。
-
connect()– 内核分配一个临时端口(例如51512),并发起 TCP 三次握手:SYN → SYN‑ACK → ACK握手完成后,连接状态变为 ESTABLISHED。
服务器端
当 Nginx 启动时:
socket()– 创建一个监听套接字。bind()– 预留一个端口(例如443)。listen()– 将套接字标记为监听状态。accept()– 当有客户端连接时,内核为该客户端创建一个 新 套接字,而监听套接字保持打开。
每个客户端对应一个新套接字。
如果有 10,000 个客户端连接 → 10,000 个套接字。
谁管理套接字?
Linux kernel TCP/IP stack 管理:
- TCP 状态(
SYN_SENT、ESTABLISHED、TIME_WAIT,…) - 发送/接收缓冲区
- 序列号
- 拥塞控制
- 内存分配
应用程序只负责:
readwriteclose
其他全部由内核负责。
为什么套接字使用文件描述符?
套接字并不写入磁盘,但它们使用文件描述符(FD),因为在 Unix/Linux 中,“一切皆文件”。
Linux 将以下对象视为文件描述符:
- 文件
- 套接字
- 管道
- 终端
- 设备
epoll、eventfd等。
实际上文件描述符是什么?
文件描述符是:
- 仅仅是一个整数
- 每个进程的表中的索引
- 指向内核对象
标准描述符
| FD | 含义 |
|---|---|
0 | stdin |
1 | stdout |
2 | stderr |
3 | 第一个打开的套接字/文件 |
示例 (C)
int fd = socket(AF_INET, SOCK_STREAM, 0);
内核:
- 创建一个套接字对象。
- 将其存储在进程的文件描述符表中。
- 返回一个小整数(句柄)。
该整数仅仅是一个引用;它 不 表示磁盘文件。
为什么重用文件描述符机制?
它提供了一个 统一的 API:
read(fd);
write(fd);
close(fd);
poll(fd);
epoll(fd);
相同的系统调用可用于文件、套接字和管道,消除了对单独的“网络 API”的需求。这种抽象非常强大。
为什么这在实际系统中很重要
- 高流量服务可能拥有 50,000 个并发连接 → 50,000 个套接字 → 50,000 个文件描述符。
- 错误 “Too many open files” 通常表示你已经耗尽了文件描述符,而不是磁盘文件。
使用以下命令检查限制:
ulimit -n
- 容器和 Kubernetes Pod 共享节点内核,因此 节点范围的 FD 限制 很重要。
- 套接字耗尽(例如大量
TIME_WAIT状态)可能会导致吞吐量下降。
可视化摘要
| 概念 | 它代表的含义 |
|---|---|
| 端口 | 服务标识符(无主动通信) |
| 套接字 | 表示活跃连接的内核对象(包含 TCP 状态 + 缓冲区) |
| 文件描述符 | 指向内核对象的整数句柄,用于统一的 I/O 抽象 |
Final Mental Model
- IP = 建筑
- Port = 门
- Socket = 两扇门之间的活跃电话通话
- File Descriptor = 操作系统内部使用的通话引用号