← 返回计算机网络总览

📡 TCP/IP 协议栈面试题

深入理解网络分层模型与协议原理

📚 TCP/IP 协议栈核心知识

1. 请描述 OSI 七层模型和 TCP/IP 四层模型的对应关系。简单

OSI 七层模型 vs TCP/IP 四层模型

OSI 七层TCP/IP 四层主要协议功能
应用层应用层HTTP, FTP, SMTP, DNS为应用程序提供网络服务
表示层-数据格式转换、加密
会话层-建立、管理、终止会话
传输层传输层TCP, UDP端到端通信、可靠传输
网络层网络层IP, ICMP, ARP路由选择、逻辑寻址
数据链路层网络接口层Ethernet, PPP帧封装、MAC 寻址
物理层物理介质比特流传输

核心区别:

  • OSI 是理论模型,TCP/IP 是实际应用模型
  • TCP/IP 将 OSI 的上三层合并为应用层
  • TCP/IP 将 OSI 的下两层合并为网络接口层
2. TCP 和 UDP 的主要区别是什么?各自适用于什么场景?简单

TCP vs UDP 对比

特性TCPUDP
连接面向连接(三次握手)无连接
可靠性可靠传输(确认、重传)不可靠(尽最大努力交付)
顺序保证顺序不保证顺序
速度较慢(开销大)快速(开销小)
头部大小20 字节8 字节
流量控制有(滑动窗口)
拥塞控制

应用场景

TCP 适用场景:

  • 文件传输(FTP)
  • 邮件传输(SMTP)
  • Web 浏览(HTTP/HTTPS)
  • 远程登录(SSH)

UDP 适用场景:

  • 实时视频/音频(直播、视频会议)
  • 在线游戏
  • DNS 查询
  • 物联网设备通信
3. 什么是 IP 地址?IPv4 和 IPv6 有什么区别?中等

IP 地址概念

IP 地址是互联网协议地址,用于在网络中唯一标识一台设备。

IPv4 vs IPv6

特性IPv4IPv6
地址长度32 位(4 字节)128 位(16 字节)
地址数量约 43 亿(2^32)约 3.4×10^38
表示方式点分十进制
192.168.1.1
冒号十六进制
2001:0db8::1
地址分配手动/DHCP自动配置(SLAAC)
安全性可选(IPSec)内置(IPSec)
头部大小20-60 字节固定 40 字节

IPv4 地址分类

  • A 类:1.0.0.0 ~ 126.255.255.255(大型网络)
  • B 类:128.0.0.0 ~ 191.255.255.255(中型网络)
  • C 类:192.0.0.0 ~ 223.255.255.255(小型网络)
  • 私有地址:
    • 10.0.0.0/8
    • 172.16.0.0/12
    • 192.168.0.0/16

为什么需要 IPv6?

  • IPv4 地址枯竭(43 亿不够用)
  • 物联网设备爆发式增长
  • 简化路由表,提高转发效率
  • 更好的安全性和移动性支持
4. 什么是子网掩码?如何进行子网划分?中等

子网掩码概念

子网掩码用于区分 IP 地址中的网络部分和主机部分,通过与 IP 地址进行按位与运算得到网络地址。

常见子网掩码

CIDR 表示子网掩码可用主机数
/24255.255.255.0254
/25255.255.255.128126
/26255.255.255.19262
/27255.255.255.22430
/28255.255.255.24014
/30255.255.255.2522(点对点链路)

子网划分示例

将 192.168.1.0/24 划分为 4 个子网:

原网络:192.168.1.0/24(256 个地址)

划分为 4 个子网,需要借 2 位(2^2 = 4)
新子网掩码:/26(255.255.255.192)

子网 1:192.168.1.0/26   (192.168.1.0   ~ 192.168.1.63)
子网 2:192.168.1.64/26  (192.168.1.64  ~ 192.168.1.127)
子网 3:192.168.1.128/26 (192.168.1.128 ~ 192.168.1.191)
子网 4:192.168.1.192/26 (192.168.1.192 ~ 192.168.1.255)

每个子网:64 个地址,62 个可用主机(去掉网络地址和广播地址)

子网划分步骤

  1. 确定需要的子网数量
  2. 计算需要借用的主机位数(2^n ≥ 子网数)
  3. 计算新的子网掩码
  4. 计算每个子网的地址范围
5. ARP 协议的工作原理是什么?什么是 ARP 缓存?中等

ARP(Address Resolution Protocol)地址解析协议

ARP 用于将 IP 地址解析为 MAC 地址,工作在网络层和数据链路层之间。

工作流程

  1. 检查 ARP 缓存:主机 A 要发送数据给主机 B,先查看本地 ARP 缓存表
  2. 发送 ARP 请求:如果缓存中没有,广播 ARP 请求(谁是 192.168.1.100?)
  3. 接收 ARP 响应:目标主机 B 收到请求后,单播回复自己的 MAC 地址
  4. 更新 ARP 缓存:主机 A 将 IP-MAC 映射存入缓存
  5. 发送数据:使用获取的 MAC 地址封装数据帧

ARP 报文格式

硬件类型(2 字节):1 表示以太网
协议类型(2 字节):0x0800 表示 IPv4
硬件地址长度(1 字节):6(MAC 地址长度)
协议地址长度(1 字节):4(IPv4 地址长度)
操作码(2 字节):1=请求,2=响应
发送方 MAC 地址(6 字节)
发送方 IP 地址(4 字节)
目标 MAC 地址(6 字节):请求时为全 0
目标 IP 地址(4 字节)

ARP 缓存

  • 作用:减少 ARP 请求,提高效率
  • 类型:
    • 动态缓存:自动学习,有超时时间(通常 2-20 分钟)
    • 静态缓存:手动配置,永久有效
  • 查看命令:arp -a(Windows/Linux)

ARP 攻击与防御

  • ARP 欺骗:攻击者发送伪造的 ARP 响应,篡改 IP-MAC 映射
  • 防御措施:
    • 使用静态 ARP 绑定
    • 部署 ARP 防火墙
    • 启用端口安全(交换机)
    • 使用 DHCP Snooping
6. ICMP 协议是什么?ping 和 traceroute 的原理是什么?中等

ICMP(Internet Control Message Protocol)

ICMP 是网络层协议,用于在 IP 主机、路由器之间传递控制消息,报告错误和测试连通性。

常见 ICMP 消息类型

类型代码描述用途
00Echo Replyping 响应
30-15Destination Unreachable目标不可达
50-3Redirect重定向
80Echo Requestping 请求
110-1Time ExceededTTL 超时

ping 工作原理

  1. 发送 ICMP Echo Request(类型 8)
  2. 目标主机收到后回复 ICMP Echo Reply(类型 0)
  3. 计算往返时间(RTT)
  4. 统计丢包率
# ping 示例
$ ping www.baidu.com
PING www.a.shifen.com (14.215.177.38): 56 data bytes
64 bytes from 14.215.177.38: icmp_seq=0 ttl=54 time=8.123 ms
64 bytes from 14.215.177.38: icmp_seq=1 ttl=54 time=7.891 ms

# 参数说明
# ttl: Time To Live,经过的路由器跳数
# time: 往返时间(Round Trip Time)

traceroute 工作原理

通过逐步增加 TTL 值,探测数据包到达目标的路径。

  1. 发送 TTL=1 的数据包,第一跳路由器返回 ICMP Time Exceeded
  2. 发送 TTL=2 的数据包,第二跳路由器返回 ICMP Time Exceeded
  3. 依次递增 TTL,直到到达目标主机
  4. 目标主机返回 ICMP Echo Reply 或 Port Unreachable
# traceroute 示例(Linux)
$ traceroute www.baidu.com
traceroute to www.a.shifen.com (14.215.177.38), 30 hops max
 1  192.168.1.1 (192.168.1.1)  1.234 ms
 2  10.0.0.1 (10.0.0.1)  5.678 ms
 3  * * *  # 某些路由器不响应
 4  14.215.177.38 (14.215.177.38)  8.901 ms

# Windows 使用 tracert 命令

为什么有些 ping 不通但网站能访问?

  • 防火墙屏蔽了 ICMP 协议
  • 服务器禁用了 ping 响应(安全策略)
  • ICMP 优先级较低,可能被丢弃
7. TCP 如何实现可靠传输?请详细说明确认机制、重传机制、流量控制和拥塞控制。困难

TCP 可靠传输机制

1. 确认机制(ACK)

  • 序列号(Sequence Number):每个字节都有唯一序号
  • 确认号(Acknowledgment Number):期望收到的下一个字节序号
  • 累积确认:ACK=1000 表示 1000 之前的数据都已收到

2. 重传机制

超时重传(RTO - Retransmission Timeout):

  • 发送数据后启动定时器
  • 超时未收到 ACK,重传数据
  • RTO 动态计算:基于 RTT(往返时间)
# RTO 计算(简化版)
SRTT = (1 - α) × SRTT + α × RTT_sample  # 平滑 RTT
RTTVAR = (1 - β) × RTTVAR + β × |SRTT - RTT_sample|  # RTT 方差
RTO = SRTT + 4 × RTTVAR

# 典型值:α = 1/8, β = 1/4

快速重传(Fast Retransmit):

  • 收到 3 个重复 ACK,立即重传(不等超时)
  • 比超时重传更快

SACK(Selective Acknowledgment):

  • 选择性确认,告知哪些数据已收到
  • 避免重传已收到的数据

3. 流量控制(Flow Control)

滑动窗口机制:

  • 接收方通过窗口大小(Window Size)告知发送方可接收的数据量
  • 发送方根据接收窗口调整发送速率
  • 防止发送方发送过快导致接收方缓冲区溢出
# 滑动窗口示例
发送窗口 = min(拥塞窗口, 接收窗口)

接收方缓冲区:[已读取][已接收未读][可接收]
                        ↑
                    窗口大小(在 TCP 头部通告)

# 零窗口探测
当接收窗口为 0 时,发送方定期发送 1 字节探测包

4. 拥塞控制(Congestion Control)

四个核心算法:

① 慢启动(Slow Start):

  • 初始拥塞窗口(cwnd)= 1 MSS
  • 每收到一个 ACK,cwnd 加 1
  • 指数增长:1 → 2 → 4 → 8 → 16...
  • 达到慢启动阈值(ssthresh)后进入拥塞避免

② 拥塞避免(Congestion Avoidance):

  • 每个 RTT,cwnd 加 1
  • 线性增长,缓慢探测网络容量

③ 快速重传(Fast Retransmit):

  • 收到 3 个重复 ACK,立即重传丢失的包
  • ssthresh = cwnd / 2
  • 进入快速恢复

④ 快速恢复(Fast Recovery):

  • cwnd = ssthresh + 3 MSS
  • 每收到重复 ACK,cwnd 加 1(膨胀窗口)
  • 收到新 ACK,cwnd = ssthresh,进入拥塞避免
# 拥塞控制状态转换
初始状态:慢启动
  ↓ cwnd >= ssthresh
拥塞避免
  ↓ 超时
慢启动(ssthresh = cwnd/2, cwnd = 1)
  ↓ 3 个重复 ACK
快速恢复(ssthresh = cwnd/2, cwnd = ssthresh + 3)
  ↓ 新 ACK
拥塞避免(cwnd = ssthresh)

TCP 拥塞控制算法演进

  • Reno:经典算法(慢启动 + 拥塞避免 + 快速重传/恢复)
  • NewReno:改进快速恢复,处理多个丢包
  • CUBIC:Linux 默认算法,适合高带宽网络
  • BBR:Google 提出,基于带宽和 RTT 估计
8. TCP 粘包和拆包问题是什么?如何解决?中等

粘包和拆包现象

TCP 是面向字节流的协议,没有消息边界概念,可能出现:

  • 粘包:多个小包被合并成一个大包发送
  • 拆包:一个大包被拆分成多个小包发送

产生原因

  1. Nagle 算法:为提高效率,合并小包发送
  2. MSS 限制:数据超过最大报文段长度,被拆分
  3. 接收缓冲区:应用层读取不及时,多个包堆积
  4. 滑动窗口:发送方一次发送多个包

解决方案

1. 固定长度:

// 每个消息固定 100 字节
byte[] buffer = new byte[100];
while (true) {
    int read = inputStream.read(buffer);
    if (read == 100) {
        processMessage(buffer);
    }
}

2. 分隔符:

// 使用 \n 作为消息分隔符
BufferedReader reader = new BufferedReader(
    new InputStreamReader(socket.getInputStream())
);
String message;
while ((message = reader.readLine()) != null) {
    processMessage(message);
}

3. 消息头 + 长度:

// 前 4 字节表示消息长度
DataInputStream dis = new DataInputStream(socket.getInputStream());
while (true) {
    int length = dis.readInt();  // 读取长度
    byte[] data = new byte[length];
    dis.readFully(data);  // 读取完整消息
    processMessage(data);
}

// 发送端
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
byte[] message = "Hello".getBytes();
dos.writeInt(message.length);  // 写入长度
dos.write(message);  // 写入数据

4. 使用应用层协议:

  • HTTP:Content-Length 头
  • Protobuf:自带长度编码
  • Netty:提供多种解码器(LengthFieldBasedFrameDecoder)
// Netty 示例
pipeline.addLast(new LengthFieldBasedFrameDecoder(
    1024,  // 最大帧长度
    0,     // 长度字段偏移
    4,     // 长度字段长度
    0,     // 长度调整
    4      // 跳过的字节数
));

UDP 为什么没有粘包问题?

  • UDP 是面向消息的协议,有明确的消息边界
  • 每个 UDP 数据报都是独立的
  • 接收方一次读取一个完整的数据报
9. 什么是 NAT(网络地址转换)?它是如何工作的?中等

NAT 概念

NAT(Network Address Translation)用于将私有 IP 地址转换为公网 IP 地址,解决 IPv4 地址不足问题。

NAT 工作原理

  1. 内网主机(192.168.1.100:5000)发送数据到外网
  2. NAT 路由器将源地址改为公网 IP(203.0.113.1:60000)
  3. 记录映射关系到 NAT 表
  4. 外网服务器响应到 203.0.113.1:60000
  5. NAT 路由器查表,转发到 192.168.1.100:5000

NAT 类型

  • 静态 NAT:一对一映射,一个私有 IP 对应一个公网 IP
  • 动态 NAT:从公网 IP 池中动态分配
  • NAPT(PAT):端口地址转换,多个私有 IP 共享一个公网 IP

NAT 穿透问题

内网主机无法被外网主动访问,解决方案:

  • 端口映射(Port Forwarding)
  • UPnP(通用即插即用)
  • STUN/TURN 协议(用于 P2P 通信)
  • 反向代理
10. 什么是端口号?常见服务的端口号有哪些?简单

端口号概念

端口号是传输层协议用于标识应用程序的 16 位整数(0-65535)。

端口号分类

  • 知名端口(0-1023):系统保留,需要 root 权限
  • 注册端口(1024-49151):注册给特定应用
  • 动态端口(49152-65535):客户端临时使用

常见端口号

端口协议服务
20/21TCPFTP(数据/控制)
22TCPSSH
23TCPTelnet
25TCPSMTP(邮件发送)
53UDP/TCPDNS
80TCPHTTP
110TCPPOP3(邮件接收)
143TCPIMAP
443TCPHTTPS
3306TCPMySQL
3389TCPRDP(远程桌面)
6379TCPRedis
8080TCPHTTP 备用端口
11. TCP 的 TIME_WAIT 状态是什么?为什么需要等待 2MSL?中等

TIME_WAIT 状态

主动关闭方在发送最后一个 ACK 后进入 TIME_WAIT 状态,等待 2MSL(Maximum Segment Lifetime)时间。

为什么需要 2MSL?

  1. 确保最后的 ACK 能到达:如果 ACK 丢失,被动方会重传 FIN,主动方需要能够响应
  2. 防止旧连接的数据包干扰新连接:等待网络中所有旧数据包消失

MSL 时间

  • RFC 建议 2 分钟
  • Linux 默认 60 秒(可通过 net.ipv4.tcp_fin_timeout 调整)
  • 2MSL = 120 秒或 60 秒

TIME_WAIT 过多的问题

  • 占用端口资源(最多 65535 个)
  • 高并发场景下可能端口耗尽

解决方案

# Linux 系统参数调优
# 允许 TIME_WAIT 套接字重用
net.ipv4.tcp_tw_reuse = 1

# 快速回收 TIME_WAIT 套接字(不推荐)
net.ipv4.tcp_tw_recycle = 0  # 新版本已废弃

# 减少 FIN_WAIT_2 超时时间
net.ipv4.tcp_fin_timeout = 30

# 增加本地端口范围
net.ipv4.ip_local_port_range = 10000 65535

应用层优化

  • 使用 HTTP Keep-Alive 复用连接
  • 使用连接池
  • 让客户端主动关闭连接(TIME_WAIT 在客户端)
12. 请设计一个高性能的网络通信框架,需要考虑哪些关键点?困难

高性能网络框架设计要点

1. I/O 模型选择

  • Reactor 模式:单线程/多线程/主从 Reactor
  • Proactor 模式:异步 I/O(Windows IOCP)
  • 推荐:主从 Reactor + 线程池

2. 零拷贝技术

  • sendfile():文件到 socket 直接传输
  • mmap():内存映射文件
  • Direct Buffer:直接内存,减少 JVM 堆拷贝

3. 内存管理

  • 对象池:复用 Buffer、连接对象
  • 内存池:预分配内存块
  • 引用计数:自动回收

4. 协议设计

  • 高效的编解码(Protobuf、Thrift)
  • 合理的消息格式(长度 + 类型 + 数据)
  • 压缩(gzip、snappy)

5. 线程模型

  • Boss 线程:接受连接
  • Worker 线程:处理 I/O
  • 业务线程池:处理业务逻辑

6. 流量控制

  • 限流:令牌桶、漏桶算法
  • 背压(Backpressure):下游慢时通知上游
  • 水位线:缓冲区高低水位

7. 可靠性保障

  • 心跳检测:及时发现连接断开
  • 重连机制:指数退避
  • 超时控制:连接超时、读写超时
  • 优雅关闭:等待数据发送完成

参考实现

  • Java:Netty
  • C++:libevent、libev、Boost.Asio
  • Go:标准库 net 包(基于 epoll/kqueue)