手把手教你手撸通讯协议(三)-开始手撕TCP
节
初识TCP
struct tcp_hdr { PACK_STRUCT_FIELD(u16_t src); // 源端口 PACK_STRUCT_FIELD(u16_t dest); // 目的端口 PACK_STRUCT_FIELD(u32_t seqno); // 序号 PACK_STRUCT_FIELD(u32_t ackno); // 确认序号 PACK_STRUCT_FIELD(u16_t _hdrlen_rsvd_flags); // 首部长度+保留位+标志位 PACK_STRUCT_FIELD(u16_t wnd); // 窗口大小 PACK_STRUCT_FIELD(u16_t chksum); // 校验和 PACK_STRUCT_FIELD(u16_t urgp); // 紧急指针 }PACK_STRUCT_STRUCT;
第二节
TCP的断开和连接
2.1
TCP连接建立
但这边会存在一个问题,如果两端同时发起连接,即同时发送个 SYN 数据包,这时这两端都处于主动打开状态,TCP中又是如何解决的?
2.2
TCP断开
第三节
TCP的状态转换
第四节
TCP控制块解读
struct tcp_pcb { IP_PCB; //这是一个宏,描述了连接的 IP相关信息,包括双方IP地址,TTL等信息 struct tcp_pcb *next; //用于连接各个TCP控制块的链表指针 enum tcp_state state; //TCP 连接的状态,即为状态图中描述的那些状态 u8_t prio; //该控制块的优先级 void* callback_arg;// u16_t local_port; //本地端口 u16_t remote_port; //远程端口 u8_t flags;// 附加状态信息,如连接是快速恢复、一个被延迟的 ACK 是否被发送等 #define TF_ACK_DELAY (u8_t)0x01U /延迟发送 ACK(推迟确认) #define TF_ACK_NOW (u8_t)0x02U /立即发送 ACK #define TF_INFR ((u8_t)0x04U) //连接处于快重传状态 #define TF_TIMESTAMP ((u8_t)0x08U) //连接的时间戳选项已使能 #define TF_FIN ((u8_t)0x20U) //应用程序已关闭该连接 #define TF_NODELAY ((u8_t)0x40U) //禁止 Nagle 算法 #define TF_NAGLEMEMERR ((u8_t)0x80U) //本地缓冲区溢出 // 接收相关字段 u32_t rcv_nxt; //期望接收的下一个字节,即它向发送端 ACK 的序号 u16_t rcv_wnd; //接收窗口 u16_t rcv_ann_wnd; //通告窗口大小 u32_t tmr; // 该字段记录该 PCB 被创建的时刻 u8_t polltmr, pollinterval; // 三个定时器,后续讲解 u16_t rtime; //重传定时,该值随时间增加,当大于 rto 的值时则重传发生 u16_t mss; //大数据段大小 //RTT 估计相关的参数 u32_t rttest; //估计得到的 500ms 滴答数 u32_t rtseq; //用于测试 RTT 的包的序号 s16_t sa, sv; //RTT 估计出的平均值及其时间差 u16_t rto; // 重发超时时间,利用前面的几个值计算出来 u8_t nrtx; // 重发的次数,该字段在数据包多次超时时被使用到,与设置 rto 的值相关 // 快速重传/恢复相关的参数 u32_t lastack; // 大的确认序号,该字段不解 u8_t dupacks; // 上面这个序号被重传的次数 // 阻塞控制相关参数 u16_t cwnd; //连接的当前阻塞窗口 u16_t ssthresh; // 慢速启动阈值 // 发送相关字段 u32_t snd_nxt, // 下一个将要发送的字节序号 snd_max, // 高的发送字节序号 snd_wnd, // 发送窗口 snd_wl1, snd_wl2, // 上次窗口更新时的数据序号和确认序号 snd_lbb; // 发送队列中后一个字节的序号 u16_t acked; // u16_t snd_buf; // 可用的发送缓冲字节数 u8_t snd_queuelen; // 可用的发送包数 struct tcp_seg *unsent; // 发送的数据段队列 struct tcp_seg *unacked; // 发送了未收到确认的数据队列 struct tcp_seg *ooseq; // 接收到序列以外的数据包队列 #if LWIP_CALLBACK_API // 回调函数 err_t (* sent)(void *arg, struct tcp_pcb *pcb, u16_t space)? //当数据被成功发送后被调用 err_t (* recv)(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err)? //接收到数据后被调用 err_t (* connected)(void *arg, struct tcp_pcb *pcb, err_t err)? //连接建立后被调用 err_t (* poll)(void *arg, struct tcp_pcb *pcb)? //该函数被内核周期性调用 void (* errf)(void *arg, err_t err)? //连接发生错误时调用 #endif u32_t keep_idle; #if LWIP_TCP_KEEPALIVE u32_t keep_intvl; // 保活定时器,用于检测空闲连接的另一端是否崩溃 u32_t keep_cnt; //坚持定时器计数值 #endif u32_t persist_cnt; // 这两个字段可以使窗口大小信息保持不断流动 u8_t persist_backoff;//坚持定时器探查报文发送的数目 u8_t keep_cnt_sent; //保活报文发送的次数