3、TCP协议模拟:三次握手与四次挥手的状态机模拟

TCP协议,说白了就是两台设备之间「说人话」的规矩。你想想看,如果A给B发数据,B连个「收到」都不回,那这通信还怎么搞?所以TCP搞了一套状态机,把每一次握手、每一次挥手都定义得清清楚楚。

我个人习惯在嵌入式开发中,用Python先模拟一遍TCP的状态流转。为什么?因为Python写起来快,调试也方便。等逻辑跑通了,再移植到C或者MicroPython里,心里就有底了。

3.1 三次握手:建立连接的过程

三次握手,说白了就是三次对话:

  • 第一次:客户端说「嘿,我想跟你说话」(发送SYN)
  • 第二次:服务器说「好的,我听到了,我也准备好了」(回复SYN+ACK)
  • 第三次:客户端说「收到,那我们开始吧」(发送ACK)

我在项目中遇到过一个问题:客户端发了SYN之后,服务器没回。查了半天,发现是防火墙把SYN包给丢了。嗯,这种坑踩过一次就记住了。

核心要点:三次握手的本质是「双方都确认对方的收发能力正常」。

  • 第一次:客户端确认自己的发送能力OK
  • 第二次:服务器确认自己的收发能力都OK
  • 第三次:客户端确认自己的接收能力OK

3.2 四次挥手:断开连接的过程

四次挥手比握手多了一次,为什么?因为TCP是全双工的,两边都要说再见。

  1. 第一次:客户端说「我要关了」(发送FIN)
  2. 第二次:服务器说「收到,但我还有数据要发」(回复ACK)
  3. 第三次:服务器说「我也发完了,关吧」(发送FIN)
  4. 第四次:客户端说「好,确认关闭」(回复ACK)

你想想看,如果服务器在第二次挥手之后直接发FIN,那客户端可能还在等数据呢。所以这个「半关闭」状态(TIME_WAIT)是必须的。

避坑指南:我曾经在做一个物联网网关时,发现设备频繁断连。查了抓包日志,发现是客户端没有正确处理TIME_WAIT状态,导致端口被占用。后来加了个超时重试机制,问题就解决了。

3.3 状态机模拟:用Python实现

下面是我写的一个简化版TCP状态机。它不处理真正的网络包,而是用字典来模拟状态流转。

import time
import threading

class TCPStateMachine:
    def __init__(self, role='client'):
        self.role = role
        self.state = 'CLOSED'
        self.state_history = []
        
        # 状态转移表
        self.transitions = {
            'CLOSED': {
                'SYN_SENT': 'SYN_SENT',
                'LISTEN': 'LISTEN'
            },
            'SYN_SENT': {
                'SYN_ACK_RECEIVED': 'ESTABLISHED'
            },
            'LISTEN': {
                'SYN_RECEIVED': 'SYN_RCVD'
            },
            'SYN_RCVD': {
                'ACK_RECEIVED': 'ESTABLISHED'
            },
            'ESTABLISHED': {
                'FIN_SENT': 'FIN_WAIT_1',
                'FIN_RECEIVED': 'CLOSE_WAIT'
            },
            'FIN_WAIT_1': {
                'ACK_RECEIVED': 'FIN_WAIT_2',
                'FIN_ACK_RECEIVED': 'TIME_WAIT'
            },
            'FIN_WAIT_2': {
                'FIN_RECEIVED': 'TIME_WAIT'
            },
            'CLOSE_WAIT': {
                'FIN_SENT': 'LAST_ACK'
            },
            'LAST_ACK': {
                'ACK_RECEIVED': 'CLOSED'
            },
            'TIME_WAIT': {
                'TIMEOUT': 'CLOSED'
            }
        }
    
    def trigger(self, event):
        if self.state in self.transitions:
            if event in self.transitions[self.state]:
                old_state = self.state
                self.state = self.transitions[self.state][event]
                self.state_history.append((old_state, event, self.state))
                print(f"[{self.role}] {old_state} --{event}--> {self.state}")
                return True
        print(f"[{self.role}] 非法事件: {event} 在当前状态 {self.state}")
        return False
    
    def get_state(self):
        return self.state
    
    def get_history(self):
        return self.state_history

# 模拟三次握手
def simulate_three_way_handshake():
    print("=== 三次握手模拟 ===")
    client = TCPStateMachine('client')
    server = TCPStateMachine('server')
    
    # 服务器开始监听
    server.trigger('LISTEN')
    
    # 客户端发送SYN
    client.trigger('SYN_SENT')
    print("客户端发送SYN...")
    
    # 服务器收到SYN
    server.trigger('SYN_RECEIVED')
    print("服务器回复SYN+ACK...")
    
    # 客户端收到SYN+ACK
    client.trigger('SYN_ACK_RECEIVED')
    print("客户端发送ACK...")
    
    # 服务器收到ACK
    server.trigger('ACK_RECEIVED')
    
    print(f"最终状态 - 客户端: {client.get_state()}, 服务器: {server.get_state()}")
    return client, server

# 模拟四次挥手
def simulate_four_way_wave(client, server):
    print("\n=== 四次挥手模拟 ===")
    
    # 客户端发起关闭
    client.trigger('FIN_SENT')
    print("客户端发送FIN...")
    
    # 服务器回复ACK
    server.trigger('FIN_RECEIVED')
    print("服务器回复ACK...")
    
    # 服务器也发起关闭
    server.trigger('FIN_SENT')
    print("服务器发送FIN...")
    
    # 客户端回复ACK
    client.trigger('FIN_ACK_RECEIVED')
    print("客户端回复ACK...")
    
    # 等待TIME_WAIT超时
    client.trigger('TIMEOUT')
    
    print(f"最终状态 - 客户端: {client.get_state()}, 服务器: {server.get_state()}")

if __name__ == "__main__":
    c, s = simulate_three_way_handshake()
    simulate_four_way_wave(c, s)

3.4 状态转移表

为了方便理解,我把TCP的状态转移整理成了表格。你写代码的时候可以直接参考这个表。

当前状态 触发事件 下一状态 说明
CLOSED 主动打开 SYN_SENT 客户端发起连接
CLOSED 被动打开 LISTEN 服务器等待连接
SYN_SENT 收到SYN+ACK ESTABLISHED 三次握手完成
LISTEN 收到SYN SYN_RCVD 服务器收到连接请求
SYN_RCVD 收到ACK ESTABLISHED 连接建立成功
ESTABLISHED 主动关闭 FIN_WAIT_1 客户端发起挥手
ESTABLISHED 收到FIN CLOSE_WAIT 服务器收到关闭请求
FIN_WAIT_1 收到ACK FIN_WAIT_2 等待服务器关闭
FIN_WAIT_2 收到FIN TIME_WAIT 等待2MSL
CLOSE_WAIT 主动关闭 LAST_ACK 服务器发送FIN
LAST_ACK 收到ACK CLOSED 连接彻底关闭
TIME_WAIT 超时 CLOSED 等待结束

3.5 实际项目中的注意事项

嗯,这里我要多说几句。你在嵌入式设备上写TCP协议时,有几个坑是绕不开的:

  • 资源有限:嵌入式设备的RAM很小,别开太多连接。我一般控制在5个以内。
  • 超时处理:网络不稳定时,握手可能失败。一定要加超时重试,不然设备就卡死了。
  • 状态机要健壮:非法事件来了怎么办?直接丢弃还是报错?我建议记录日志然后忽略,别让设备崩溃。

小技巧:调试TCP状态机时,可以用Wireshark抓包对比。看看你的状态机跟实际网络包的状态流转是否一致。我曾经靠这个办法,半小时就定位了一个握手失败的问题。

好了,这一章的内容就到这里。TCP状态机看着复杂,其实你动手写一遍就全明白了。下一章我们会讲UDP协议,那个就简单多了——无连接,爱发不发,哈哈。