LWIP应用开发|TCP/IP设计原理二

TCP/IP设计原理二

1. TCP数据结构
  • TCP报文封装:lwIP通过数据结构tcp_hdr描述TCP报头

在这里插入图片描述

PACK_STRUCT_BEGIN
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;
PACK_STRUCT_END
  • TCP状态机封装:lwIP通过数据结构tcp_state对TCP各状态进行封装

在这里插入图片描述

enum tcp_state {
  CLOSED      = 0,
  LISTEN      = 1,
  SYN_SENT    = 2,
  SYN_RCVD    = 3,
  ESTABLISHED = 4,
  FIN_WAIT_1  = 5,
  FIN_WAIT_2  = 6,
  CLOSE_WAIT  = 7,
  CLOSING     = 8,
  LAST_ACK    = 9,
  TIME_WAIT   = 10
};
  • IP控制块:IP_PCB宏(在ip.h文件中定义)
//定义IP_PCB,它是与IP层相关的字段
#define IP_PCB         \	//宏定义中"\"符号表示上一行和下一行是一起的
  ip_addr_t local_ip;  \	//本地IP地址
  ip_addr_t remote_ip; \	//远端IP地址
  u8_t so_options;     \	//socket选项
  u8_t tos;            \	//服务类型
  u8_t ttl             		//生存时间TTL
//定义IP控制块  
struct ip_pcb {
  IP_PCB;					//宏IP_PCB相关的字段
};
  • TCP_PCB_COMMON宏定义:表示两种控制块都具有的字段
/* lwIP中定义了两种类型的TCP控制块,一种用于描述处于LISTEN状态的连接,另一种
   用于描述处于其他状态的连接,该宏定义出了两种类型控制块共有的一些字段 */
#define TCP_PCB_COMMON(type) \	//type为宏参数
  type *next; 				 \	//用于将控制块组成链表
  void *callback_arg; 		 \	//指向用户自定义数据,在函数回调时使用
  enum tcp_state state; 	 \	//连接的状态
  u8_t prio; 				 \	//优先级,用于挥手低优先级控制块
  u16_t local_port				//连接绑定的本地端口
  • tcp_pcb_listen结构:用于LISTEN状态TCP连接的控制块结构
struct tcp_pcb_listen {
  IP_PCB;
  TCP_PCB_COMMON(struct tcp_pcb_listen);
#if LWIP_CALLBACK_API
  /* 监听的协议控制块,当有客户端连接时,需要有个回调函数进行处理 */
  tcp_accept_fn accept;
#endif /* LWIP_CALLBACK_API */
#if TCP_LISTEN_BACKLOG  
  u8_t backlog;	//可同时监听的TCP最大连接数  
  u8_t accepts_pending;	//已经接入,还未处理的连接数
#endif /* TCP_LISTEN_BACKLOG */
};
  • TCP控制块数据结构:tcp_pcb
struct tcp_pcb {
  IP_PCB;	//该宏包含源IP地址、目的IP地址两个重要字段
  TCP_PCB_COMMON(struct tcp_pcb);	//两种控制块共有的字段
  u16_t remote_port;	//目标端口号
  tcpflags_t flags;		//控制块状态、标志字段
#define TF_ACK_DELAY   0x01U   /* 延时应答标志 */
#define TF_ACK_NOW     0x02U   /* 立刻应答标志 */    
#define TF_INFR        0x04U   /* 快速回复标志 */
#define TF_CLOSEPEND   0x08U   /* 当FIN包发送失败时,该标志置位 */
#define TF_RXCLOSED    0x10U   /* 主动关闭接收,此标志位置位 */
#define TF_FIN         0x20U   /* 关闭本地连接(发送),此标志位置位 */
#define TF_NODELAY     0x40U   /* 关闭Nagle算法的标志位(在TCP通信时,加上缓冲延时算法)*/
#define TF_NAGLEMEMERR 0x80U   /* 启动nagle算法标志位*/
#if TCP_LISTEN_BACKLOG
#define TF_BACKLOGPEND 0x0200U /* 当置位时,增大backlog值*/
  /* 定时器相关 */
  u8_t polltmr, pollinterval;	//轮询定时器,在裸机程序中使用,其实就RAW接口
  u8_t last_timer;	//最后一次操作pcb时间 
  u32_t tmr;	//pcb中的基础定时器
  /* 接收窗口相关的成员变量 */
  u32_t rcv_nxt;   			 //下一个期望接收的字节序号
  tcpwnd_size_t rcv_wnd;   	 //当前接收窗口的大小,会随着数据的接收与递交动态变化 
  tcpwnd_size_t rcv_ann_wnd; //向对方通告的窗口大小, 随着数据的接收与递交动态变化
  u32_t rcv_ann_right_edge;  //上一次窗口通告时窗口的右边界值
   /* 发送窗口相关的成员变量 */
  u32_t snd_nxt;   			 //下个要发送的窗口序号
  u32_t snd_wl1, snd_wl2; 	 //在下一次窗口更新之前的序号和应答序号
  u32_t snd_lbb;       		 //下一个将被应用程序缓存的数据的起始编号
  tcpwnd_size_t snd_wnd;   	 //程序内部定义的发送窗口的大小
  tcpwnd_size_t snd_wnd_max; //其实就是目标机的介绍窗口
  tcpwnd_size_t snd_buf;     //可使用的发送缓冲区的大小
  /* 重发定时器 (协议交互,握手时) */
  s16_t rtime;	//重传定时器,该值随时间递增,当大于rto值时重传报文
  s16_t rto;    //重发超时时间
  u8_t nrtx;    //重发次数,多次重发时,将使用该字段设置rto的值
  u16_t mss;    //对方可接收的最大报文段大小
  /* RTT传输时间,用于优化通讯链路使用*/
  u32_t rttest; //RTT估计时,以500ms为周期递增
  u32_t rtseq;  //用于测试RTT的报文段序号
  s16_t sa, sv; //RTT估计出的平均值及其时间差
  /* 快速重传与恢复相关的字段 */    
  u32_t lastack; //最后一次应答序号(接收到的最大确认号)
  /* 阻塞控制相关的字段 */
  tcpwnd_size_t cwnd;		//连接当前的阻塞窗口大小
  tcpwnd_size_t ssthresh;	//拥塞避免算法启动阈值
  /* 实际的TCP数据段内容(用户数据缓冲的队列指针) */
  struct tcp_seg *unsent;   //未发送的tcp报文段队列
  struct tcp_seg *unacked;  //发送完毕,但未应答的报文段队列
#if TCP_QUEUE_OOSEQ
  struct tcp_seg *ooseq;    //接收到的无序报文段队列
#endif /* TCP_QUEUE_OOSEQ */
  struct pbuf *refused_data; //指向上一次成功接收但未被应用层取用的数据pbuf
#if LWIP_CALLBACK_API || TCP_LISTEN_BACKLOG
  struct tcp_pcb_listen* listener;
#endif /* LWIP_CALLBACK_API || TCP_LISTEN_BACKLOG */
#if LWIP_CALLBACK_API /* RAW接口 */
  tcp_sent_fn sent;				//数据被发送成功后被调用
  tcp_recv_fn recv;				//接收到数据后被调用
  tcp_connected_fn connected;	//连接建立后被调用
  tcp_poll_fn poll;				//该函数被内核周期性调用
  tcp_err_fn errf;				//连接发生错误时调用
#endif /* LWIP_CALLBACK_API */
  /* 心跳检测相关的 */
  u32_t keep_idle;		//心跳空闲周期
#if LWIP_TCP_KEEPALIVE
  u32_t keep_intvl;		//发送心跳时间间隔
  u32_t keep_cnt;		//心跳最大重发次数
#endif /* LWIP_TCP_KEEPALIVE */
  /* KEEPALIVE counter */
  u8_t keep_cnt_sent;	//心跳已重发的次数
};
  • 接收窗口:TCP控制块中与接收窗口相关的字段包括rcv_nxt、rcv_wnd、rcv_ann_wnd和rcv_ann_right_edge,这4个字段的含义详见上面tcp_pcb数据结构里的注释。

下图展示了四个字段在初始时刻的一种取指情况。当接收到数据后,数据会被放在接收窗口中等待上层取用,rcv_nxt字段会指向下一个期望接收的编号,同时窗口值rcv_wnd会减少,当上层取走相关数据后,窗口值会增加;rcv_ann_wnd在整个过程中都是动态计算的,当rcv_wnd值改变时,内核会计算出一个合理的通告窗口值rcv_ann_wnd,在下一次报文发送时,通告窗口值会被填入报文首部,同时右边界值rcv_ann_right_edge也在报文发送后更新

在这里插入图片描述

  • 发送窗口:发送窗口设计控制块中的6个字段,lastack、snd_nxt、snd_wnd和snd_lbb这四个字段与窗口状态密切相关(如下图示);snd_wl1和snd_wl2两个字段与发送窗口的更新密切相关,它们分别记录上一次窗口更新时收到的数据序号和确认序号

当收到接收方的一个有效ACK后,lastack值就做相应的增加,指向下一个待确认数据的编号,当发送一个报文后,snd_nxt就做相应的增加,指向下一个待发送数据。snd_nxt和lastack之间的差值不能超过snd_wnd的大小。由于实际数据发送时时按照报文段的形式组织的,因此可能存在这样的情况:即使发送窗口允许,但并不是窗口内的所有数据都能被发送以填满窗口,如下图示中编号11~13的数据,可能因为太小不能组织成一个有效的报文段,因此不会被发送。发送方会等到新的确认到来,从而使发送窗口向右滑动,使得更多的数据被包含在窗口中,这样再启动下一个报文段的发送

在这里插入图片描述

2. TCP报文处理
  • 控制块链表:为了组织和描述系统内的所有TCP控制块,内核定义了四条链表来连接处于不同状态下的控制块,TCP操作过程通常都包含对链表上控制块的查找
/** 连接所有进行了端口号绑定,但还没发起连接(主动连接)或进入侦听状
	态(被动连接)的控制块,即处于close状态的TCP都在该链表进行管理 */
struct tcp_pcb *tcp_bound_pcbs;
/** 连接所有处于监听状态(被动连接)的TCP */
union tcp_listen_pcbs_t tcp_listen_pcbs;
/** 连接所有处于数据收发状态的TCP*/
struct tcp_pcb *tcp_active_pcbs;
/** 连接所有处于TIME-WAIT状态的TCP */
struct tcp_pcb *tcp_tw_pcbs;
  • 报文段缓冲:在内核中,所有待发送数据、已接收数据都是以报文段的形式被保存,报文段存放于pbuf中,为了实现对所有这些报文段pbuf的管理,引入了tcp_seg结构
/* 定义组织TCP报文段的结构 */
struct tcp_seg {
  struct tcp_seg *next;    //该指针用于将报文段组织为队列的形式
  struct pbuf *p;          //指向装载报文段的pbuf
  u16_t len;               //报文段中的数据长度
  u8_t  flags;			   //表示所描述报文段的选项属性
  struct tcp_hdr *tcphdr;  //指向报文段中的TCP首部
};

如下图示,每个控制块中都维护了三个缓冲队列,unsent、unacked、ooseq三个字段分别为队列的首指针。unsent用于连接还未被发送出去的报文段,unacked用于连接已经发送出去但是还未被确认的报文段,ooseq用于连接接收到的无序报文段。下图显示了几个报文段被连接在队列中的情景。能够进行有效数据交互的控制块都组织在链表tcp_active_pcbs上,每个控制块的三个指针记录了该连接的所有数据

在这里插入图片描述

  • TCP报文发送:发送报文段的函数叫做tcp_output,该函数功能描述为"Find out what we can send and send it",参数为某个连接的TCP控制块指针,函数把这个控制块unsent队列上的报文段发送出去或者只发送一个ACK报文段
/* 详细源码定义于tcp_out.c文件中 */
err_t tcp_output(struct tcp_pcb *pcb)
  • TCP报文接收:在IP层接收到数据报后,ip_input函数会判断IP首部中的协议字段,把属于TCP的报文通过tcp_input函数传递到TCP层。tcp_input完成报文向各个控制块的分发,并等待控制块对相应报文的处理结果,它会根据处理结果向用户递交数据或者向连接另一端输出响应报文
/* 详细源码定义于tcp_in.c文件中 */
void tcp_input(struct pbuf *p, struct netif *inp)

在这里插入图片描述

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

安迪西嵌入式

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值