都发往 443,服务器怎么知道是哪条连接?
设想 A 与 B 都访问服务器 S 的 198.51.100.20:443。两台电脑恰好都选了本地端口 50000,A 还又建立了一条连接。目的 IP 和目的端口相同,服务器却能分别维护它们的数据和状态。
端口标识传输入口;一条已建立的 TCP 连接还包含双方地址与端口。 如果把“一个端口”“一个 Socket”“一个进程”“一条连接”当作同一个东西,就无法解释服务器如何同时处理多个客户端。
本章使用文档保留地址,先讨论普通单路径 TCP、没有 NAT 改写的教学场景。后面再把它和出口映射联系起来,不展开具体语言或框架的服务器代码。
端口不是进程 ID,也不是硬件插口
TCP 和 UDP 头部都包含 16 位源端口与目的端口,数值范围为 0—65535;0 有保留和特殊语义,不能把所有数字都当作普通服务端口。端口是传输层标识,进程 ID 则是操作系统管理进程的标识。
服务器通常绑定一个服务入口,客户端发起通信时通常由系统选择一个临时源端口,也可以显式绑定。临时端口不需要与服务端口一致:访问 S:443,并不意味着客户端也使用 443。
IANA 把端口划为 System Ports(0—1023)、User Ports(1024—49151)和 Dynamic/Private Ports(49152—65535)。这是分配范围的分类,系统实际自动选端口的范围还取决于实现与配置,不能只凭 IANA 表推断一台机器的配置。IANA 服务名与端口注册表
协议也要一起看:TCP:53 与 UDP:53 属于不同的传输入口。端口号提供服务约定,但一个报文发往注册端口,不证明它一定遵守对应应用协议,更不证明它安全。
五元组怎样区分三条流量?
按客户端到服务器的方向,网络分析中常用五元组:
(源 IP,源端口,目的 IP,目的端口,传输协议)| 流量 | 源入口 | 目的入口 | 协议 |
|---|---|---|---|
| A 的连接 1 | 192.0.2.10:50000 | 198.51.100.20:443 | TCP |
| A 的连接 2 | 192.0.2.10:50001 | 198.51.100.20:443 | TCP |
| B 的连接 1 | 192.0.2.11:50000 | 198.51.100.20:443 | TCP |
A 的两条连接用不同源端口区分,A 与 B 即使源端口相同,源 IP 仍不同。在 TCP 范围内,一对本地与远端入口形成四元组;五元组额外明确协议,便于和 UDP 等流量一起讨论。TCP 的连接模型由双方 Socket 地址组成,见 RFC 9293 §2.2。
服务器发回响应时,源与目的交换。这是同一连接的反向流量,不是因为有两个方向就出现两条独立 TCP 连接。
五元组也不能被当作永久身份。同样的地址与端口组合在连接结束后可能重新出现;序列号、状态与报文有效性判断还要防止旧数据混入。网络命名空间、地址族及扩展协议等条件也影响系统内部匹配,本章表格只用于普通场景。
若中间有 NAPT,第 05 章中的出口会改写源入口:服务器看到的是转换后的公网侧五元组,不能用服务器抓包直接认定内部主机的原始地址与端口。
Socket 为什么不能简单等于“IP 加端口”?
Socket(套接字)是应用使用通信能力的端点抽象。概念上,Internet Socket 地址可以写成 IP 与端口;操作系统 API 创建的 Socket 对象还包含类型、协议、状态、缓冲区及关联信息等。
例如在 BSD 风格接口里,socket() 返回用于访问对象的文件描述符;地址绑定、监听、连接是后续动作,而不是调用 socket() 就已经连上对端。Socket 还可用于本机 Unix 域通信,所以不是所有 Socket 都有 IP 地址。OpenBSD socket(2) 手册
进程通过描述符操作 Socket。一个进程可持有许多 Socket,描述符也可在进程之间共享或传递;不能保证端口与进程一一对应。端口复用等具体规则取决于地址绑定、协议、选项与系统实现,不用“一个端口永远只能属于一个进程”概括所有情况。
监听者继续接客,每个连接单独收发
用 BSD 风格 API 看普通 TCP 服务端的关键动作:
socket()创建对象,bind()绑定本地地址与端口。listen()把它作为监听 Socket,接收建立连接的请求。accept()取出一个待接受连接,返回与该连接关联的新 Socket。- 应用通过新 Socket 进行
send/recv等收发;原监听 Socket 继续接受其他连接。
accept() 不会把原监听对象变成唯一的业务连接,也不需要为每个客户端另开一个服务端口。OpenBSD accept(2) 手册明确区分原监听 Socket 与返回的新对象。
| 服务器对象 | 本地入口 | 对端入口 | 用途 |
|---|---|---|---|
| 监听 Socket | 198.51.100.20:443 | 未绑定某个具体客户端 | 接受新连接 |
| 已连接 Socket 1 | 198.51.100.20:443 | 192.0.2.10:50000 | 与 A 的连接 1 收发 |
| 已连接 Socket 2 | 198.51.100.20:443 | 192.0.2.10:50001 | 与 A 的连接 2 收发 |
| 已连接 Socket 3 | 198.51.100.20:443 | 192.0.2.11:50000 | 与 B 的连接 1 收发 |
操作系统依据连接信息把到达的数据交给对应对象,应用再读取。若监听绑定 0.0.0.0,通常表示覆盖适用的本地 IPv4 地址,而不是报文真正发往 0.0.0.0,也不保证防火墙或外部路径允许访问。
接收请求时创建线程、使用线程池还是事件循环,是应用的并发组织方式。TCP 并不要求“一条连接对应一条线程”;连接数量也不只受端口数字限制,还受文件描述符、内存、队列、CPU 和应用能力影响。
动手接待三条连接
连续 accept 三次,看监听对象与新对象的区别,再切换来包来源。A 的两条连接与 A/B 的同源端口连接,分别靠哪个字段区分?
三个客户端连接已完成握手,等待应用接受。逐次调用 accept,再选一条已接受连接的数据。
本地:198.51.100.20:443
尚待 accept:3 条连接
应用尚未取得已连接 Socket。监听对象仍在,accept 会返回新对象。
继续 accept 不需要新开服务器端口,也不把监听对象变成唯一业务连接。
教学对象标签不是文件描述符或进程 ID。普通单路径 TCP,无 NAT、复用选项或命名空间差异;省略握手和队列细节,只选择已接受连接的有效数据,不推演未知报文如何处理。连接不要求各占一条线程;TCP 收发仍是无消息边界的字节流。这里不创建真实 Socket。
TCP 字节流与 UDP 数据报有什么不同?
TCP(Transmission Control Protocol,传输控制协议)向应用提供可靠、有序的字节流,在两个方向各自管理数据与传输状态。“可靠”描述的是传输层机制与交付语义,不是无限等待也保证成功;连接最终可能报错,更不能证明业务已经执行完成。RFC 9293 §2.2、§3.8.3
UDP(User Datagram Protocol,用户数据报协议)以独立数据报为单位,不由 UDP 本身建立 TCP 式连接,也不提供可靠交付、顺序恢复或重复消除保证。RFC 768
| 比较项 | TCP | UDP |
|---|---|---|
| 应用看到的单位 | 字节流 | 数据报 |
| 协议状态 | 建立并维护连接 | UDP 本身无 TCP 式连接建立 |
| 交付机制 | 确认、重传与按序交付等 | 不自动补齐丢失报文或恢复顺序 |
| 边界 | 不保留每次写入的消息边界 | 接收接口以数据报为单位处理 |
| 发送节奏 | 有流量与拥塞控制 | 应用及其上层协议仍需控制负载 |
| 基础头部 | 至少 20 字节,可带选项 | 8 字节 |
假设应用连续写入 ABC、DEF。TCP 接收方可能先读到 AB,再读到 CDEF,也可能一次读到 ABCDEF;字节顺序仍正确,应用消息边界却需要额外编码。后面的消息分帧章会专门处理这个问题。
如果 UDP 发送两个数据报,分别载荷 ABC、DEF,接收操作按数据报处理,不会因为它们紧邻发送就自动合成一条 ABCDEF。不过数据报可能丢失、重复或乱序;接收缓冲太小还可能截断或丢弃超出部分,具体观察取决于 API 与标志,不能把“保留边界”误解为“任意大小都完整收到”。OpenBSD recv(2) 手册
UDP 为什么也能调用 connect?
在常见 Socket API 中,UDP Socket 调用 connect() 可以设置默认对端,便于直接使用 send,并让接收匹配该对端。这里设置的是本地关联,不是通过 UDP 三次握手确认对方,也不会自动添加确认和重传。OpenBSD connect(2) 手册
因此 UDP connect() 成功,并不能证明远端服务在线或愿意接收。未关联具体对端的 UDP Socket,则可通过带地址的发送和接收接口与多个对端交互,通常不使用 TCP 服务端的 listen/accept 过程。
UDP 少了机制,是否一定更快?
UDP 不承担 TCP 的建连和可靠字节流工作,适合由上层协议决定何时重传、什么可以放弃等场景。但程序要自己承担所需能力,不能因使用 UDP 就持续无限发送;拥塞控制、大小适配、超时与安全仍需处理。RFC 8085 §3
QUIC 等协议可以基于 UDP 建立可靠传输与连接管理,这些能力来自上层协议,不能反过来说“UDP 本身可靠”。TCP 丢失的数据影响该字节流的后续按序交付,UDP 应用则可选择处理其他已经到达的数据报;具体性能取决于业务容错、路径和实现,不存在“UDP 对所有场景都更快”的结论。
“端口没通”应该继续区分什么?
| 观察 | 可以核对的下一步 |
|---|---|
| ping 成功,TCP 连接失败 | 监听地址、服务端口、过滤策略与连接错误 |
| TCP 连接成功,应用无响应 | 应用协议、读取方式、业务耗时与超时 |
| UDP 发送调用成功,没有应答 | 对端是否接收、协议是否要求应答、回程与过滤 |
| 一个服务端口已有很多连接 | 对端入口是否不同、资源限制和应用调度 |
发送调用成功通常先说明本地系统接受了相应数据,不是“对端应用已经读取”,更不是“订单已经提交”。这些层次在可靠性与业务重试章节还会继续展开。
面试时怎样回答?
30—60 秒参考回答
端口是传输层入口标识,不是进程 ID。普通 TCP 连接由双方 IP 和端口区分,加入传输协议就是常说的五元组,因此多个客户端可以同时访问服务器同一个端口。Socket 是应用使用通信的端点对象;监听 Socket 负责接受新连接,accept 返回的已连接 Socket 分别负责收发。TCP 提供可靠有序字节流,不保留应用写入边界;UDP 以数据报为单位,本身不保证交付、顺序或去重。UDP connect 设置默认对端,不建立 TCP 式连接。协议的交付机制也不等于业务成功。
四个追问及解析
- 同一服务器端口只能有一条 TCP 连接吗? 不能这样说。表中三条连接的对端入口不同,可使用同一个服务器本地端口。
- TCP 53 与 UDP 53 会天然互相占用吗? 它们属于不同传输协议的入口;具体绑定还需检查地址族、地址与系统规则。
- accept 返回之后,原 Socket 还用来读该客户端数据吗? 普通 BSD 风格 TCP 流程中,新对象负责该连接收发,原对象继续监听。
- UDP connect 成功意味着对端已经确认吗? 不意味着。它是 Socket 的对端关联操作,没有 UDP 协议层的可靠握手。
两个易错说法
- “Socket 就是一条 TCP 连接。”监听对象、UDP 对象和 Unix 域对象都说明这个等式不成立。
- “TCP 可靠,所以 recv 一次就能拿到 send 的完整内容。”TCP 不保留应用消息边界,读取还受缓冲与调用条件影响。
现在知道连接状态用来区分双方,下一步就要问:TCP 怎样让两端就这份状态达成一致?下一章从三次握手回答。
资料与核验日期
核验于 2026-10-08。地址、连接表和读写轨迹为教学设定;本章解释机制,没有运行服务器代码。Socket 调用以 OpenBSD 的官方 BSD 风格接口说明为例,不把全部实现细节推广到所有系统。
- RFC 9293:TCP 连接、头部、字节流与连接失败。
- RFC 768、RFC 8085:UDP 报文与应用使用原则。
- IANA 端口注册表:端口范围与注册的边界。
- socket(2)、accept(2)、connect(2)、recv(2):Socket 对象、接受连接、数据报对端关联与接收行为。