1. 先放一张总览:数据怎么走

应用发出的字节不会直接变成网线里的电信号。协议栈会一层层加头、再一层层拆头。日常开发里最常碰到的是 TCP/IP 四层模型(和 OSI 七层对照时,你只要记住「应用 / 传输 / 网络 / 链路」这四层就够用了)。

graph TB
  A[应用层<br/>HTTP / DNS / SSH] --> B[传输层<br/>TCP / UDP + 端口]
  B --> C[网络层<br/>IP 地址 + 路由]
  C --> D[链路/物理层<br/>MAC、帧、网卡]
核心问题 你天天见到的标识
应用层 业务语义怎么编码 URL、HTTP 方法、DNS 查询
传输层 进程之间怎么交付 端口号、TCP 连接 / UDP 数据报
网络层 包怎么跨网到达主机 IP 地址
链路层 同一网段怎么到下一跳 MAC 地址

一句话记忆: IP 负责把包送到「哪台机器」,端口负责送到「机器上的哪个进程」,TCP/UDP 规定「交付方式靠不靠谱、要不要先握手」,Socket 则是操作系统给应用程序的那把「把手」。

2. IP:给主机在网络里贴门牌

2.1 IP 做什么

IP(Internet Protocol)工作在网络层,主要职责:

  • 寻址:每个网卡/接口配置 IP,标识主机(更准确说是网络接口)在网际中的位置
  • 路由转发:中间路由器根据目的 IP 查表,决定下一跳
  • 分片与重组(IPv4 常见):过大报文可按路径 MTU 切开,对端再拼回来
  • 尽力而为(best-effort):IP 不保证 一定送达、不丢包、不乱序;可靠传输要靠上层(通常是 TCP)补

常见版本:

版本 地址形态 备注
IPv4 32 位,点分十进制,如 192.168.1.10 仍是大多数内网默认
IPv6 128 位,冒号十六进制 地址空间大,很多云环境双栈

私有地址段(IPv4,不会在公网路由)记这三段就够日常用:

  • 10.0.0.0/8
  • 172.16.0.0/12
  • 192.168.0.0/16

127.0.0.1 是本机回环,流量不出网卡,适合本机自测。

2.2 IP alone 不够:为什么还要端口

一台机器上同时跑浏览器、SSH、数据库、聊天客户端。包到了 192.168.1.10 之后,内核还得知道交给 哪个进程。传输层用 16 位端口号(0–65535)做多路复用/分用。

可以粗暴地记:

主机定位  → IP
进程定位  → 端口
两端会话  → 往往还要看「五元组」(见下文)

3. 端口号:进程在传输层的工位号

3.1 端口范围(IANA)

端口是 16 位无符号整数,空间分成三段(工程上按这个记):

范围 名称 常见用法
0–1023 系统 / 知名端口(Well-known) HTTP 80、HTTPS 443、SSH 22、DNS 53
1024–49151 注册端口(Registered) 许多中间件、自定义服务登记在此
49152–65535 动态 / 私有端口(Ephemeral) 客户端临时源端口,连出去时由 OS 分配

权威登记表见 IANA Service Name and Transport Protocol Port Number Registry

注意两点,面试和排障都好用:

  1. TCP 的 80 和 UDP 的 80 是两套命名空间。同一数字端口,TCP 与 UDP 互不占用。
  2. Linux 上绑定 1024 以下端口通常需要特权(或 CAP_NET_BIND_SERVICE);开发机上自测服务多用 8000、8080、3000 等。

3.2 常见端口速查

端口 协议(常见) 服务
22 TCP SSH
53 UDP/TCP DNS
80 TCP HTTP
443 TCP HTTPS
3306 TCP MySQL
6379 TCP Redis

ss -lntp / netstat -an 能看到本机谁在听哪个端口;防火墙放行的也是「协议 + 端口」,不是只写一个数字就完事。

4. 套接字 Socket:程序眼里的网络端点

4.1 定义别背八股,抓这句

套接字(Socket)是操作系统提供的、面向应用的通信抽象,把「协议族 + 类型 + 本地/对端地址」封装成一个可 read/write(或 send/recv)的对象。

网络编程里常说的「一个 TCP 连接两端各有一个 socket」,可以理解为:

套接字地址 ≈ (协议, 本地 IP, 本地端口)  以及连接建立后的对端信息

更完整地描述一条 TCP 连接,用 五元组

(协议, 源 IP, 源端口, 目的 IP, 目的端口)

五元组唯一确定一条传输层会话。NAT、负载均衡、连接跟踪表,本质上都在跟五元组打交道。

4.2 Socket 不是协议

容易混的三点:

说法 对不对 说明
Socket = TCP Socket 是 API/抽象;底下可以是 TCP、UDP,甚至 Unix domain
Socket = IP + 端口 半对 地址部分常这么记;完整还要协议类型、已连接状态等
先有 TCP/IP,后有 Socket 编程接口 Berkeley Socket 成为事实上的跨平台网络 API 风格

应用层协议(HTTP、WebSocket 等)跑在传输层之上;浏览器访问网页时,下面通常是 TCP socket(或基于 UDP 的 QUIC,那是另一条故事线)。

4.3 TCP 与 UDP 在 socket 类型上的差别

SOCK_STREAM  → 字节流,对应 TCP:面向连接、有序、可靠(在协议设计目标上)
SOCK_DGRAM   → 数据报,对应 UDP:无连接、按消息边界、尽力送达

Python 里创建方式直接暴露了这个选择:

import socket

# TCP
tcp_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# UDP
udp_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

AF_INET 表示 IPv4;IPv6 用 AF_INET6。路径、权限、阻塞/非阻塞、超时,都是挂在这个 fd/句柄上的选项,和「协议长什么样」是两层问题。

5. TCP:面向连接的可靠字节流

5.1 设计目标

TCP(Transmission Control Protocol)面向 字节流

  • 发送方 send 多次,接收方可能一次 recv 读完,也可能多次才拼齐——没有应用层消息边界
  • 通过序号、确认、重传、流量控制、拥塞控制,尽量在不可靠的 IP 之上提供可靠、有序的交付

适合:网页、文件传输、SSH、数据库连接——错一个字节都不行 的场景。

5.2 三次握手(建立连接)

客户端                         服务端
  |                              |
  | -------- SYN seq=x --------> |  1. 我要连,起始序号 x
  |                              |
  | <---- SYN+ACK seq=y ack=x+1- |  2. 好,我的序号 y,确认你的 x
  |                              |
  | -------- ACK ack=y+1 ------> |  3. 确认你的 y,连接建立
  |                              |

要点:

  • 双方各自通告初始序号,后续数据靠序号对齐
  • 第三次握手可以捎带数据(多数栈支持),但教学上仍画成三步
  • 失败常见原因:SYN 被墙、对端没 listen、backlog 满、路由不通

5.3 四次挥手(断开)与 TIME_WAIT

断开比建立啰嗦,因为 TCP 全双工,每个方向都要单独半关闭:

A                               B
| ---- FIN ----> |  A 说:我这侧没数据了
| <---- ACK ---- |  B 确认
| <---- FIN ---- |  B 也发完了
| ---- ACK ----> |  A 确认,进入 TIME_WAIT

TIME_WAIT 存在是为了:

  1. 保证最后的 ACK 丢失时还能重传语义正确收尾
  2. 让旧连接延迟报文在超时后从网络中消失,避免被新连接误收

高并发短连接会堆很多 TIME_WAIT,这是协议行为,不是单纯「没关干净」。优化方向通常是连接复用(HTTP keep-alive、连接池),而不是一上来关 TIME_WAIT。

5.4 TCP 还顺带解决什么

机制 作用
序号 + ACK 检测丢包、乱序,触发重传
滑动窗口 流量控制,避免打爆对端接收缓冲
拥塞控制 体谅网络,慢启动、拥塞避免等
校验和 端到端基本完整性检查

这些细节够写几篇专项文。入门阶段只要建立直觉:TCP 贵在状态机和缓冲,换来的是应用少操心丢包重传。

6. UDP:无连接的数据报

6.1 它故意做得很少

UDP(User Datagram Protocol)几乎只做:

  • 源/目的端口
  • 长度
  • 可选校验和
  • 把数据交给 IP 发出去

没有 握手、没有确认、没有重传、没有拥塞控制(应用或上层协议如 QUIC 可自己做)。

因此:

  • 延迟通常更低、实现更简单
  • 可能丢包、重复、乱序
  • 保留消息边界:一次 recvfrom 对应一次 sendto 的数据报(在未被截断的前提下)

6.2 典型场景

场景 为什么常用 UDP
DNS 查询 一问一答,丢了客户端重试即可
实时音视频、游戏状态 晚到的包不如直接丢,保实时
内网广播/组播发现 无连接模型更自然
QUIC / HTTP/3 在 UDP 上自建可靠与多路复用

不要把 UDP 理解成「不可靠所以垃圾」——它是把可靠性策略交给 更懂业务的人。直播里丢一帧可以糊过去;银行转账丢一个包就必须有人重传,那人往往是 TCP。

7. TCP vs UDP:对照表 + 选型

维度 TCP UDP
连接 面向连接(握手/挥手) 无连接
可靠性 确认、重传、有序 尽力而为
交付模型 字节流(无消息边界) 数据报(有边界)
速度/开销 头部与状态开销更大 头部小,快路径多
拥塞控制 无(默认)
典型应用 HTTP/HTTPS、SSH、邮件、DB DNS、RTC、游戏、部分隧道
Socket 类型 SOCK_STREAM SOCK_DGRAM

选型口诀:

  • 正确性优先、逻辑是长连接会话 → TCP
  • 实时性优先、能容忍丢失或自己做重传 → UDP
  • 已经在 UDP 上需要可靠多路复用时,看 QUIC 这类方案,而不是硬在业务里重造半套 TCP

8. 最小可运行示例(Python)

下面两段代码只为把概念钉死:地址是 (host, port),TCP 要 listen/acceptconnect,UDP 直接 bind + sendto/recvfrom

8.1 TCP 回显

# tcp_echo_server.py
import socket

HOST, PORT = "127.0.0.1", 9000

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.bind((HOST, PORT))
    s.listen(8)
    print(f"TCP listening on {HOST}:{PORT}")
    conn, addr = s.accept()
    with conn:
        print("client:", addr)
        data = conn.recv(1024)
        conn.sendall(b"echo: " + data)
# tcp_echo_client.py
import socket

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect(("127.0.0.1", 9000))
    s.sendall(b"hello tcp")
    print(s.recv(1024))

8.2 UDP 回显

# udp_echo_server.py
import socket

HOST, PORT = "127.0.0.1", 9001

with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
    s.bind((HOST, PORT))
    print(f"UDP bound on {HOST}:{PORT}")
    data, addr = s.recvfrom(1024)
    s.sendto(b"echo: " + data, addr)
# udp_echo_client.py
import socket

with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
    s.sendto(b"hello udp", ("127.0.0.1", 9001))
    data, _ = s.recvfrom(1024)
    print(data)

对照着跑一遍,你会实际看到:

  • TCP 必须先有人 listen,客户端 connect 失败会立刻报错
  • UDP 客户端即使对端没开,sendto 也常常「看起来成功」(失败可能在后续 ICMP 或根本静默)

9. 把概念串成一条故事线

访问 https://example.com 时,简化链路是这样的:

  1. DNS(多为 UDP 53)把域名解析成 IP
  2. 浏览器(或系统)创建 TCP socket,对端一般是 IP:443
  3. 三次握手 建立 TCP
  4. 其上再跑 TLS,然后是 HTTP 请求/响应
  5. 内核用 本地 IP:临时端口 ↔ 服务器 IP:443 这条五元组区分连接
  6. 关闭标签页后,连接复用或四次挥手结束

你在应用代码里只摸到 socket 和读写;底下 IP 负责找主机,端口负责找进程,TCP 负责尽量可靠地搬字节。

10. 排障时怎么用这些知识

现象 优先怀疑
connect 超时 路由/防火墙/安全组丢 SYN,或对端没监听
Connection refused 到达主机了,但该端口无进程 listen
能 ping 通但业务不通 ICMP 通不代表 TCP/UDP 端口通
偶发错乱的「半包粘包」 TCP 字节流正常现象,要在应用层做定界(长度头/分隔符)
UDP「丢包」 可能是真丢、也可能是缓冲区满、或对端逻辑丢弃

命令工具(Linux):pingtraceroute/mtrss -lntpcurl -vdig/nslookup、抓包用 tcpdump/Wireshark。Windows 上对应 Test-NetConnectionnetstatWireshark 等。

11. 小结

  • IP:主机(接口)级寻址与跨网转发,不保证可靠
  • 端口:同一主机上区分应用进程;TCP/UDP 端口空间分离
  • Socket:OS 给应用的通信句柄;TCP 用流套接字,UDP 用数据报套接字
  • TCP:连接 + 可靠字节流,握手挥手、重传与拥塞控制换正确性
  • UDP:无连接数据报,轻、快,可靠性交给上层

把这五块钉牢,再看 HTTP、gRPC、WebRTC、服务网格,都只是在这层楼上继续加规则。

参考与延伸阅读

  1. RFC 791 — Internet Protocol
  2. RFC 9293 — Transmission Control Protocol (TCP)
  3. RFC 768 — User Datagram Protocol
  4. IANA 端口号登记
  5. AWS:OSI 模型说明(含传输层 TCP/UDP)

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐