TCP相关

如何保证可靠传输

  • 三次握手建立连接

  • 序列号和确认号保证有序传输

    每个数据带有序号 seq,接收方返回 ack 告知发送方已接受到数据,可以继续发送

  • 重传机制

    • 超时重传,超时还未收到 akc 后,重传可能丢失的数据
    • 快速重传,当连续收到三个相同 ack 后,立即重传可能丢失的数据
  • 流量控制

    维护一个代表接收能力的滑动窗口,若接收缓冲区快满了,就缩小窗口,反之扩大窗口

  • 拥塞控制

    网络拥堵时,TCP 会减慢发送速度,防止因网络拥堵丢失大量数据

  • 四次挥手断开连接



三次握手

假设客户端为 A,服务器为 B

  • 第一次握手(SYN)
    • A 发送一个 SYN 包给 B,请求建立连接
    • 进入 SYN_SENT 状态
  • 第二次握手(SYN-ACK)
    • B 收到后,回复一个 SYN-ACK 包,表示同意连接,并请求建立反方向连接
    • B 进入 SYN_RCVD 状态
    • 此时为半连接队列
  • 第三次握手(ACK)
    • A 收到后,发送一个 ACK 包给 B,表示连接建立成功
    • A 进入 ESTABLISHED 状态,B 收到ACK后也进入 ESTABLISHED 状态
    • 此时为全连接队列

此时,连接正式建立,双方向都可以开始发送数据了

为什么不是两次?

两次只进行到 B 回复 SYN-ACK 包,A 只知道 B 收到了自己的请求,但不知道 B 是否准备好接受数据

为什么不是四次?

三次已经保证双方都能接收和发送数据,无需再做一次

也可以将 B 给 A 的包拆成 SYN 和 ACK 分开发送,但没必要



四次挥手

假设客户端为 A,服务器为 B

  • 第一次挥手(FIN)

    A 发送一个 FIN 报文,表示它已经没有数据要发送了,请求关闭连接

  • 第二次挥手(ACK)

    B 收到 FIN 报文后,发送一个 ACK 报文作为确认。此时 A 到 B 的数据通道关闭,但 B 还可以继续发数据给 A

  • 第三次挥手(FIN)

    B 完成自己的数据发送后,也发送一个 FIN 报文,请求关闭连接

  • 第四次挥手(ACK)

    A 收到 FIN 报文后,发送一个 ACK 报文确认。此时,整个连接关闭

  • 进入 TIME_WAIT

    强制等待 2MSL(两倍最大报文段生存时间),即一个来回的时间

    防止旧连接中还在路上的数据包被新连接接收

    确保最后一个 ACK 被对方收到,保证整个连接关闭

为什么不能三次?

TCP 是双向通信,双方都可以接收和发送数据,三次挥手只能关闭一个方向,导致一方的数据未发完

为什么不是五次?

四次已经能实现双向关闭,额外的操作会浪费资源

TIME_WAIT 过多

占用大量端口,影响并发性能

解决方案

  • 端口复用
  • 使用长连接



网络模型

OSI 七层模型

自上而下

  1. 应用层:HTTP、HTTPS
  2. 表示层:SSL/TLS
  3. 会话层:RPC
  4. 传输层:TCP、UDP
  5. 网络层:IP
  6. 数据链路层:以太网
  7. 物理层:光纤、网线


五层模型

自上而下

  1. 应用层:HTTP、HTTPS
  2. 传输层:TCP、UDP
  3. 网络层:IP
  4. 数据链路层:以太网
  5. 物理层:光纤、网线


TCP/IP 四层模型

自上而下

  1. 应用层:HTTP、HTTPS
  2. 传输层:TCP、UDP
  3. 网络层:IP
  4. 网络接口层:以太网、WIFI



网络 I/O 模型

I/O 多路复用

一个或少量线程/进程中同时监听多个 I/O 事件,提高系统效率,适用于高并发场景

Select

早期的I/O多路复用机制,使用固定长度的数组表示文件描述符集。每次调用select时都需要重新构建和检查文件描述符集

支持的文件描述符数量有限(通常为1024),在大规模连接的场景下效率较低


Poll

pollselect类似,但使用动态数组来存储文件描述符,因此没有select的最大连接数限制

每次调用时仍需遍历全部描述符,在处理大量连接时效率不高


Epoll

基于事件机制的 I/O 多路复用,当事件发生时,才会通知程序

LT 水平触发

只要时间处于活跃状态,就会一直通知程序,可能导致程序收到大量重复通知,增加开销

ET 边缘触发

仅在事件状态发生时通知程序,提高系统效率

ET 模式下如何保证数据全部读完?

ET 模式下,内核只通知一次,所以你必须循环读取直到读不到数据为止,即读取返回 EAGAINEWOULDBLOCK

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
while (true)
{
n = read(fd, buf, sizeof(buf));
if (n == -1)
{
if (errno == EAGAIN || errno == EWOULDBLOCK)
{
// 数据已全部读取完
break;
}
else
{
// 发生错误,处理错误
break;
}
}
else if (n == 0)
{
// 对方关闭连接
break;
}
// 处理读取到的数据
}




网络问题排查

客户端发送数据给服务器,服务器没反应

send() 返回成功后,数据一定发送出去了吗?

send() 返回成功只代表数据已成功复制到内核的发送缓冲区,不代表数据已经真正通过网络发送或对方已接收

  • 数据仍可能滞留在发送缓冲区
  • 如果网络出现拥堵或对方不可达,数据可能延迟或丢失
  • TCP 的可靠性体现在协议层的重传和确认机制,应用层仅能确认数据进入内核缓冲区



网络攻击

DDoS

攻击者通过大量肉鸡,向目标服务器发送海量请求,耗尽其带宽、CPU、内存,导致服务器瘫痪,无法为正常用户提供访问

常见攻击方式

  • 流量型:UDP 洪水,直接用流量压垮
  • 协议型:SYN 洪水、Ping of Death,利用协议漏洞让服务器耗尽资源
  • 应用层攻击:HTTP 请求洪水,高频率模拟正常用户操作

防护措施

  • CDN 或 WAF 缓解压力
  • 启用速率限制、IP黑名单
  • 及时识别异常流量模式