TCP 创建过程和链接折除过程是由 TCP/IP 协议栈自动创建的,对于理解 TCP 底层运作机制,相当有帮助。

TCP 报文格式

TCP/IP 协议的详细信息参看《TCP/IP协议详解》三卷本。下面是 TCP 报文格式图:

上图中几个字段的介绍:

  1. 序号:Seq 序号,占 32 位,用来标识从 TCP 源端向目的端发送的字节流,发起方发送数据时对此进行标记。
  2. 确认序号:Ack 序号,占 32 位,只有 ACK 标志位为 1 时,确认序号字段才有效,Ack=Seq+1。
  3. 标志位:共6个,即 URG、ACK、PSH、RST、SYN、FIN 等,具体含义如下:
    • URG:紧急指针(urgent pointer)有效。
    • ACK:确认序号有效。
    • PSH:接收方应该尽快将这个报文交给应用层。
    • RST:重置连接。
    • SYN:发起一个新连接。
    • FIN:释放一个连接。

注意:

  1. 不要将确认序号 ack 与标志位中的 ACK 搞混了。
  2. 确认方 ack = 发起方 seq+1,两端配对。

TCP 三次握手

所谓三次握手(Three-way Handshake),是指建立一个 TCP 连接时,需要客户端和服务器总共发送 3 个包。三次握手的目的是连接服务器指定端口,建立 TCP 连接,并同步连接双方的序列号和确认号并交换 TCP 窗口大小信息。客户端执行 connect()时,将触发三次握手:

  1. 第一次握手:
    客户端发送一个 TCP 的 SYN 标志位 为 1,ACK 标志位为 0,seq 序号为 X ,ack 为 0 的包到服务器端。这个过程消耗一个序号。

  1. 第二次握手:
    若服务器统一客户端的请求,则会发回确认包应答:即 SYN 标志位和 ACK 标志位均为 1,同时将序号设为 seq = Y,将确认序列号设为 ack = X + 1。seq 取 Y 和之前的过程没有关系,ack = X + 1是因为客户端发送的数据 seq = X,并且在发送的过程中消耗了一个序号,所以下一次应该收到的数据的地址为 X + 1,加的 1 就是消耗的一个序号:

  1. 第三次握手:
    客户端需要确认,SYN = 0, ACK = 1, seq = X + 1, ack = Y + 1。seq 表示发送的数据序号为 X + 1。

整个过程如下:

SYN 攻击

  1. 在三次握手过程中,服务器发送 SYN-ACK 之后,收到客户端的 ACK 之前的 TCP 连接称为半连接(half-open connect)。此时服务器处于 Syn_RECV 状态。当收到 ACK 后,服务器转入 ESTABLISHED 状态。
  2. Syn攻击就是攻击客户端在短时间内伪造大量不存在的 IP 地址,向服务器不断地发送 syn 包,服务器回复确认包,并等待客户的确认,由于源地址是不存在的,服务器需要不断的重发直至超时,这些伪造的 SYN 包将长时间占用未连接队列,正常的 SYN 请求被丢弃,目标系统运行缓慢,严重者引起网络堵塞甚至系统瘫痪。Syn 攻击是一个典型的 DDOS 攻击。检测 SYN 攻击非常的方便,当你在服务器上看到大量的半连接状态时,特别是源 IP 地址是随机的,基本上可以断定这是一次 SYN攻击。在 Linux 下可以如下命令检测是否被 Syn 攻击 netstat -n -p TCP | grep SYN_RECV。一般较新的 TCP/IP 协议栈都对这一过程进行修正来防范 Syn 攻击,修改 tcp 协议实现。主要方法有 SynAttackProtect 保护机制、SYN cookies 技术、增加最大半连接和缩短超时时间等。但是不能完全防范 syn 攻击。

TCP 的四次挥手

TCP 的连接的拆除需要发送四个包,因此称为四次挥手(four-way handshake)。客户端或服务器均可主动发起挥手动作,任何一方执行close() 操作即可产生挥手操作。

为什么建立连接是三次握手,而关闭连接却是四次挥手呢?

这是因为服务端在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。而关闭连接时,当收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,己方也未必全部数据都发送给对方了,所以己方可以立即close,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送。

实例演示

  1. 发送的第一个数据包:

在第一个包的时候SYN = 1,ACK = 1,seq的值为一个随机的值,ack为0。
  1. 发送的第二个数据包:

在第二个数据包的时候ACK = 1,SYN = 1,seq的值为一个随机数,ack的值为上一个数据包的seq的值+1。
  1. 发送的第三个数据包:

在第三个数据包中:SYN = 0,ACK = 1,seq的值为第一个数据包的seq的值+1,ack的值为第二个数据包的seq值+1。

总结:

  1. 在三次握手的时候,有四个字段是非常重要的。SYN 和 ACK均占一位,表示同步和确认。seq 和 ack 均为 32 位,分别为发送的数据的序号,和希望对方发送的数据的序号。
  2. 在三次握手的过程中,第一次和第二次的过程中 SYN 的值均为 1,并且每个过程都消耗掉一个序号,消耗一个序号的意思是:使得数据的序号加 1,好像是发送了一位数据,但是并没有真正的传递数据。

参考

一次经典的tcp三次握手
TCP的三次握手四次挥手