服务器端接收到SYN握手包,向客户端返回带SYN和ACK的握手包,并将相应TCP控制块置为SYN_RCVD状态,并挂在tcp_active_pcbs链表上。以后,继续等待客户端发送过来的握手包,这次,服务器期望的是接收一个ACK包以完成建立连接要求的三次握手操作

  还是和前几次一样,数据包进来通过ip_input传递给tcp_input,后者在三个链表中查找一个匹配的连接控制块。这次进来的是客户端发送的ACK握手包,服务器端相应的tcp控制块一定是在tcp_active_pcbs链表上。接下来,以查找到的tcp_pcb结构为参数,调用TCP状态机函数tcp_process处理输入数据段。在讲解tcp_process函数之前,先来看看这个状态机函数要用到的一些重要的全局变量,这些变量是在tcp_input函数中,通过接收数据段的各个字段的值进行设置的。全局变量tcphdr指向收到的数据段的TCP头部;全局变量seqno记录了TCP头部中的数据序号字段;全局变量ackno存储确认序号字段;全局变量flags表示TCP首部中的各个标志字段;全局变量tcplen表示数据报中数据的长度,对于SYN包和FIN包,该长度为1;全局变量inseg用于描述收到的数据内容,它是tcp_seg类型的,tcp_seg这个结构体后面再讲;全局变量recv_flags用来标识tcp_process函数对数据段的处理结果,初始化为 0

  tcp_process函数首先判断该数据段是不是一个复位数据段若是则进行相应的处理,这里先跳过这小部分。直接到达tcp_process函数状态机部分,它就是对TCP状态转换图的简单代码诠释。必须要贴一大段代码上来了,这比任何语言更能说清楚问题,注意下面的代码已经被我去掉了相关注释和编译输出部分。

switch (pcb->state) {case SYN_SENT:                                 // 客户端将SYN发送到服务器等待握手包返回if ((flags & TCP_ACK) && (flags & TCP_SYN) // 实际收到ACK和SYN包&& ackno == ntohl(pcb->unacked->tcphdr->seqno) + 1) {pcb->snd_buf++;pcb->rcv_nxt = seqno + 1;pcb->lastack = ackno;pcb->snd_wnd = tcphdr->wnd;pcb->snd_wl1 = seqno - 1;pcb->state = ESTABLISHED;tcp_parseopt(pcb);                     // 处理选项字段#if TCP_CALCULATE_EFF_SEND_MSS         // 根据需要设置有效发送mss字段pcb->mss = tcp_eff_send_mss(pcb->mss, &(pcb->remote_ip));#endifpcb->ssthresh = pcb->mss * 10;         // 由于重新设置了mss字段值,所以要重设ssthresh值pcb->cwnd = ((pcb->cwnd == 1) ? (pcb->mss * 2) : pcb->mss);  // 设置阻塞窗口--pcb->snd_queuelen;                   // 要发送的数据段个数减1??rseg = pcb->unacked;                   // 取下要被确认的字段pcb->unacked = rseg->next;if(pcb->unacked == NULL)               // 没有字段需要被确认,则停止定时器pcb->rtime = -1;else {                                 // 否则重新开启定时器pcb->rtime = 0;pcb->nrtx = 0;}tcp_seg_free(rseg);                    // 释放已经被确认了的段TCP_EVENT_CONNECTED(pcb, ERR_OK, err);tcp_ack_now(pcb);                      // 返回ACK握手包}else if (flags & TCP_ACK) {                // 仅仅只有ACK而无SYN标志tcp_rst(ackno, seqno + tcplen, &(iphdr->dest), &(iphdr->src),tcphdr->dest, tcphdr->src);            // 不支持半打开状态,所以返回一个复位包}break;case SYN_RCVD:                                 // 服务器端发送出SYN+ACK后便处于该状态if (flags & TCP_ACK &&                     // 在SYN_RCVD状态接收到ACK返回包!(flags & TCP_RST)) {                      // 判断ACK序号是否合法if (TCP_SEQ_BETWEEN(ackno, pcb->lastack+1, pcb->snd_nxt)) {u16_t old_cwnd;pcb->state = ESTABLISHED;          // 进入ESTABLISHED状态old_cwnd = pcb->cwnd;              // 保存旧的阻塞窗口accepted_inseq = tcp_receive(pcb); // 若包含数据则接收数据段pcb->cwnd = ((old_cwnd == 1) ? (pcb->mss * 2) : pcb->mss);//重新设置阻塞窗口if ((flags & TCP_FIN) && accepted_inseq) { // 如果ACK包同时含有FIN位且tcp_ack_now(pcb);              // 已经接收完了最后的数据,则响应FINpcb->state = CLOSE_WAIT;       // 进入CLOSE_WAIT状态}}else {                                 // 不合法的ACK序号则返回一个复位包tcp_rst(ackno, seqno + tcplen, &(iphdr->dest), &(iphdr->src),tcphdr->dest, tcphdr->src);}}break;case CLOSE_WAIT:                              // 服务器,TCP处于半打开状态,在该方向上不会再接收到数据包// 服务器在此状态下会一直等待上层应用执行关闭命令tcp_close,并将状态变为LAST_ACKcase ESTABLISHED://稳定状态,客户端会一直保持稳定状态直到上层应用调用tcp_close函数关闭连接,将状态变为FIN_WAIT_1accepted_inseq = tcp_receive(pcb);        // 直接接收数据if ((flags & TCP_FIN) && accepted_inseq) {// 同时数据包有FIN标志,且接收到了最后tcp_ack_now(pcb);     的数据,则响应FINpcb->state = CLOSE_WAIT;              // 进入CLOSE_WAIT状态}break;case FIN_WAIT_1:                              // 客户端独有的状态tcp_receive(pcb);                         // 还可以接收来自服务器的数据if (flags & TCP_FIN) {                    // 如果收到FIN包if (flags & TCP_ACK && ackno == pcb->snd_nxt) {//且还有ACK,则进入TIME_WAITtcp_ack_now(pcb);                 // 发ACKtcp_pcb_purge(pcb);               // 清除该连接中的所有现存数据TCP_RMV(&tcp_active_pcbs, pcb);   // 从tcp_active_pcbs链表中删除pcb->state = TIME_WAIT;           // 置为TIME_WAIT状态TCP_REG(&tcp_tw_pcbs, pcb);       // 加入tcp_tw_pcbs链表} else {                              // 无ACK,则表示两端同时关闭的情况发生tcp_ack_now(pcb);                 // 发送ACKpcb->state = CLOSING;             // 进入CLOSING状态}} else if (flags & TCP_ACK && ackno == pcb->snd_nxt) {//不是FIN包,而是有效ACKpcb->state = FIN_WAIT_2;              // 则进入FIN_WAIT_2状态}break;case FIN_WAIT_2:                              // 客户端在该状态等待服务器返回的FINtcp_receive(pcb);                         // 还可以接收来自服务器的数据if (flags & TCP_FIN) {                    // 如果收到FIN包tcp_ack_now(pcb);                     // 发ACKtcp_pcb_purge(pcb);                   // 清除该连接中的所有现存数据TCP_RMV(&tcp_active_pcbs, pcb);       // 从tcp_active_pcbs链表中删除pcb->state = TIME_WAIT;               // 置为TIME_WAIT状态TCP_REG(&tcp_tw_pcbs, pcb);           // 加入tcp_tw_pcbs链表}break;case CLOSING:                                 // 进入了同时关闭的状态,这种情况极少出现tcp_receive(pcb);                         // 还可以接收对方的数据if (flags & TCP_ACK && ackno == pcb->snd_nxt) { //若是有效ACKtcp_ack_now(pcb);                     // 与上面类似的操作,不解释了tcp_pcb_purge(pcb);TCP_RMV(&tcp_active_pcbs, pcb);pcb->state = TIME_WAIT;TCP_REG(&tcp_tw_pcbs, pcb);}break;case LAST_ACK:                                // 服务器端(被动关闭端)能出现该状态tcp_receive(pcb);                         // 还可以接收对方的数据if (flags & TCP_ACK && ackno == pcb->snd_nxt) {//接收到有效ACKrecv_flags = TF_CLOSED;               // 全局变量recv_flags用于标识该数据段进行了哪些处理}                                         // 以便tcp_input的后续处理,此时并没有把pcb->state的状态设置为CLOSED状态break;default:break;
}

  这就是TCP状态机的大致流程图,如果对照着TCP状态转换图来看,你会觉得它是如此的简单。不过还有很多搞不清楚的地方,比如控制块中各个字段的值特别是阻塞窗口和发送接收窗口等的值,它们代表了什么意义,为什么要如此设置,等等。

  注意TCP状态转换图中的双方同时打开的情况,即从LISTEN状态到SYN_SENT状态,SYN_SENT状态到SYN_RCVD状态,没有被实现。许多其他TCP/IP实现,如BSD中也是这样的,没有实现这部分功能。因为在实际应用中这种情况几乎不可见,所以实现并没有严格按照协议来实行。

lwip --- (十七)TCP状态机相关推荐

  1. LWIP应用开发|TCP状态机

    TCP状态机 1. TCP状态机 TCP状态机是TCP连接的变化过程.TCP在三次握手和四次挥手的过程,就是一个TCP的状态说明,由于TCP是一个面向连接的,可靠的传输,每一次的传输都会经历连接,传输 ...

  2. Lwip之TCP协议实现(二)

    接上文:Lwip之TCP协议实现(一)_龙赤子的博客-CSDN博客 第二部分:数据输入处理 Tcp数据的输入处理主要在文件tcp_in.c中实现.输入的数据包在IP层进行分发处理.如果输入的数据包为T ...

  3. tcp状态机-三次握手-四次挥手以及常见面试题

    TCP状态机介绍 在网络协议栈中,目前只有TCP提供了一种面向连接的可靠性数据传输.而可靠性,无非就是保证,我发给你的,你一定要收到.确保中间的通信过程中,不会丢失数据和乱序.在TCP保证可靠性数据传 ...

  4. LWIP之TCP协议

    IP协议提供了在各个主机之间传送数据报的功能,但是数据的最终目的地是主机上的特定应用程序.传输层协议就承担了这样的责任,典型的传输层协议有UDP和TCP两种. UDP只为应用程序提供了一种无连接的.不 ...

  5. LwIP C TCP/IP Stack 正确的TCP连接数据发送姿态

    注意,本文提供的代码来自本人搞起耍的 netstack,有一些类似 tun2socks LwIP 实现,目前不会考虑集成到产品上面作为可选 TCP/IP 网络栈,当然不会是基于 go-gvisor.g ...

  6. 【正点原子FPGA连载】 第三十二章基于lwip的TCP服务器性能测试实验 摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南

    第三十二章基于lwip的TCP服务器性能测试实验 上一章的lwip Echo Server实验让我们对lwip有一个基本的了解,而Echo Server是基于TCP协议的.TCP协议是为了在不可靠的互 ...

  7. LwIP协议栈-TCP控制块(tcp_pcb)详解

    LWIP协议TCP部分的结构体tcp_pcb的源代码如下: struct tcp_pcb { IP_PCB; //这是一个宏,描述了连接的IP相关信息,包括双方IP地址,TTL等信息 struct t ...

  8. 探索TCP状态机之旅:发现网络连接的生命周期与神秘魅力

    目录标题 前言 TCP状态简介 TCP状态机的目的与功能 TCP状态在连接建立.数据传输和连接关闭过程中的作用 TCP状态详解 LISTEN:服务器监听来自客户端的连接请求. SYN\_SENT:客户 ...

  9. STM32F407 + LAN8720A + LWIP 实现TCP服务器

    STM32F407 + LAN8720A + LWIP 实现TCP客户端 环境说明: 开发板:某宝买的,STM32F407IG STM32CUBEMX5.6 HAL Lib Version 1.25 ...

最新文章

  1. 设计模式之组合模式(Composite)摘录
  2. iOS 查看崩溃日志
  3. dataTable表头未对其解决方法
  4. 如何判断数组是静态还是动态分配的
  5. 一篇文章让你轻松搞定SpringBoot和SpringCloud之间的版本选择!!!
  6. C++ text search文本检索在较长的文本段落中搜索单词的算法(附完整源码)
  7. jquery设置滚动条距离页面顶部的高度
  8. MySQL事务ACID实现原理
  9. 【实习】T100开发学习笔记
  10. dvm与art的区别_Android运行时– DVM与ART,AOT与JIT
  11. Oracle11g在Windows和Linux下imp导入表,exp导出表,sqluldr2导出表,sqlldr导入表
  12. Qt_QTableWidget 详解 最全用法 网格线样式 最后一列自拉伸
  13. 手把手教你学DSP 28335学习笔记
  14. Java基础之三大特性
  15. SpringBoot---Tomcat日志配置
  16. 汇编语言编译器 masm.exe and link.exe
  17. 防止电脑自动锁屏(Windows系统)
  18. windbg加载符号
  19. 使用JLINK仿真器调试树莓派4
  20. 组态软件的开发(C#)

热门文章

  1. Linux工具之numactl
  2. 【吴恩达】机器学习作业 ex3data1 -- 多分类逻辑回归(Python)
  3. 破解滑动验证码,成功率在百分之九十九
  4. 解决Ubuntu与Virtualbox虚拟机共享文件夹无权限的问题(保姆级)
  5. 腾讯马松松谈企业安全建设:安全工程化如何落到实处
  6. Apple Watch苹果手表数据备份与数据存储结构
  7. 若依项目springcloud启动
  8. 软件工程应用与实践第八篇
  9. 最新十大热门职位排行榜(2019年版)
  10. 页面中实现轮播图效果