当前位置: 首页 > article >正文

【LwIP源码学习3】TCP协议栈分析——数据接收流程

前言

本文介绍代码在lwip的tcp_in.c文件中,主要介绍TCP协议栈中数据的接收流程。

正文

1、一个正常的TCP数据,首先会传入到

tcp_input(struct pbuf *p, struct netif *inp)

函数,其中指针p指向传入的数据流。

2、从数据流中获取TCP头部

tcphdr = (struct tcp_hdr *)p->payload;

TCP头部数据结构为:
在这里插入图片描述
3、获取TCP头部中重要信息:

  tcphdr->src = lwip_ntohs(tcphdr->src);
  tcphdr->dest = lwip_ntohs(tcphdr->dest);
  seqno = tcphdr->seqno = lwip_ntohl(tcphdr->seqno);
  ackno = tcphdr->ackno = lwip_ntohl(tcphdr->ackno);
  tcphdr->wnd = lwip_ntohs(tcphdr->wnd);

  flags = TCPH_FLAGS(tcphdr);

seqno是序号,是发送方告诉接受方正在发送的报文段的序号。
ackno是确认号,是接收方告诉发送方已经接受到的最后一个报文段序号。
flags是6个控制位,其中的SYN与ACK在3次握手时使用如下:
在这里插入图片描述
客户端发起请求时,SYN为1,ACK为0。
服务器回复时,SYN为1,ACK为1。
客户端再次回复时,SYN为0,ACK为1。
ACK为0时,确认号无效(也就是ackno),建立连接后所有报文ACK置1。

flags中的FIN控制位置1,表示报文段的发送方数据已发送完毕,并要求释放运输连接。
释放TCP连接过程如下:
在这里插入图片描述

4、从 tcp_active_pcbs链表中寻找对应的pcb,寻找方式主要是看端口号和IP号是否能对应上:

if (pcb->remote_port == tcphdr->src &&
        pcb->local_port == tcphdr->dest &&
        ip_addr_cmp(&pcb->remote_ip, ip_current_src_addr()) &&
        ip_addr_cmp(&pcb->local_ip, ip_current_dest_addr()))

tcp_active_pcbs链表上的pcb都是正在等待接受数据或者发送数据的。

5、将输入数据流放到输入报文段里:

inseg.next = NULL;
inseg.len = p->tot_len;
inseg.p = p;
inseg.tcphdr = tcphdr;

因为lwip每次只处理一个输入数据流,所以输入报文段inseg是一个全局变量,同时也不会出现访问冲突。
报文段的管理如下:
在这里插入图片描述
tcp_active_pcbs链表上有很多活跃pcb,每个pcb有一个unsent指针和一个unacked指针,用于维护TCP协议中的发送窗口,unsent指向发送窗口中还未发送的报文段,unacked指向发送窗口中已经发送但是还没收到响应确认号的报文段。
发送窗口如下:
在这里插入图片描述

6、然后执行:

err = tcp_process(pcb);

进行TCP状态机处理。
TCP状态机转换过程如下:
在这里插入图片描述

7、一个已经建立连接的正常通信的pcb状态是ESTABLISHED
处理代码为:

case ESTABLISHED:
      tcp_receive(pcb);
      if (recv_flags & TF_GOT_FIN) { /* passive close */
        tcp_ack_now(pcb);
        pcb->state = CLOSE_WAIT;
      }
      break;

可以看到是调用tcp_receive()函数对输入数据做进一步处理。这时输入数据仍然在inseg输入报文段这个全局变量里。

8、接下来进入到tcp_receive()函数,首先根据新发送来TCP报文段中的窗口大小更新本pcb的发送窗口大小。

pcb->snd_wnd = SND_WND_SCALE(pcb, tcphdr->wnd);

9、查看接收到的报文段中显示的报文序号是否在接收窗口内:

if (TCP_SEQ_BETWEEN(seqno, pcb->rcv_nxt,
                        pcb->rcv_nxt + pcb->rcv_wnd - 1)) 

pcb->rcv_nxt表示下一个要接收的报文序号。
pcb->rcv_wnd表示接收窗口大小。
其中pcb在初始化时,设置接收窗口大小代码如下:

pcb->rcv_wnd = pcb->rcv_ann_wnd = TCPWND_MIN16(TCP_WND);

在lwipopts.h文件中设置

#define TCP_WND                         (2*TCP_MSS)
#define TCP_MSS                         (1500 - 40) 

也就是窗口大小为两个报文段。

10、查看接收到的报文段序号是否就是期望的序号:

      if (pcb->rcv_nxt == seqno) 

如果是则更新期望报文段序号:

pcb->rcv_nxt = seqno + tcplen;

把收到的数据放到全局变量recv_data里:

recv_data = inseg.p;

11、然后回到tcp_input()函数
首先判断是否接受到了数据:

if (recv_data != NULL)

有数据的话将数据传给应用层:

TCP_EVENT_RECV(pcb, recv_data, ERR_OK, err);

然后再试试能不能发点数据出去:

tcp_output(pcb);

http://www.kler.cn/a/349196.html

相关文章:

  • 深入解析单片机原理及其物联网应用:附C#示例代码
  • 关于this指针
  • K8s的储存
  • springboot 集成flyway数据库迁移版本控制详细教程
  • C++list
  • 初级网络工程师之从入门到入狱(七)
  • 如何使用 Puppeteer 和 Browserless 运行自动化测试?
  • 期货数据接口 - 包含实时+历史数据
  • rtsp协议:rtsp协议参数介绍
  • Leetcode热题100-200 岛屿数量
  • 2.1 机器学习--KNN算法(分类)
  • ARP欺骗的多种手法
  • 基于离群点修正、优化分解和DLinear模型的多步风速预测方法
  • 【Vue】vue中针对地址栏参数进行加解密
  • 深入理解Transformer的笔记记录(精简版本)---- ELMO->GPT->BERT
  • Windows 下纯手工打造 QT 开发环境
  • 总结拓展十四:批次管理(2)
  • SAP SD学习笔记- 豆知识 - SAP中的英文 - SD中英文,日语,中文
  • [Python学习日记-47] Python 中的系统调用模块—— os 与 sys
  • 腾讯云SDK连麦应用