LWIP之TCP协议
IP协议提供了在各个主机之间传送数据报的功能,但是数据的最终目的地是主机上的特定应用程序。传输层协议就承担了这样的责任,典型的传输层协议有UDP和TCP两种。
UDP只为应用程序提供了一种无连接的、不可靠的传输服务。
TCP适用于可靠性要求很高的场合。TCP将所有数据看作数据流按照编号的顺序组织起来,采用正面确认以及重传等机制,保证数据流能全部正确到达,才把数据递交给应用层。许多著名的上层协议都是基于TCP实现的,如DNS、HTTP、FTP、SMTP、TELNET等。
报文格式
源端口号和目的端口号:用于标识发送端和接收端应用进程
序号:从发送端到接收端的数据的第一个字节编号。新连接建立时(SYN为1),发送方随机一个初始序号ISN
确认序号:ACK为1时有效,表示上次已成功收到数据字节序号加1
首部长度:TCP首部长度,以4字节为长度。如果没有任何选项字段,首部长度应该为5(20字节)
6个标志比特:
窗口大小:通知发送方接收缓冲区大小,用于实现流量控制
检验和:保证数据正确性
紧急指针:URG为1时有效,表示报文中包含紧急数据。紧急数据始终放在数据区的开始,紧急指针定义了紧急数据结束处
数据流编号
在IP协议中,会对每个数据报进行编号,而在TCP中,没有报文编号的概念,因为它的目标是数据流传输,数据流由连续的字节流组成,尽管在上层可能用各种各样的数据结构和格式来描述数据,但在TCP看来,数据都是字节流。TCP把一个连接中的所有数据字节都进行编号,当然两个方向上的编号是彼此独立的,编号的初始值由发送数据的一方随机选取,编号的取值范围在0~2^32-1。
紧急数据
TCP是一个面向数据流的协议,应用程序需要发送的所有数据将被组织成字节流,每个字节的数据都在流中占用一个位置,并且依次发送。但是在某些特殊情况下,发送方应用程序需要发送一些紧急数据,它并不期望这些紧急数据和普通数据一样被放在流中,而是期望接收方能优先读取这些数据。
使用URG位置1的报文段,这种类型的报文段将告诉接收方:这里面的数据是紧急的,可以直接读取,不必把它们放在接收缓冲里面。在包含紧急数据的报文段中,紧急数据总是放在数据区域的起始处,且报文首部中的紧急指针表明了紧急数据的最后一个字节。
强迫数据交互
TCP采用了缓冲机制来保证协议的高效性,在数据发送时,软件将延迟小分组数据的交付,它将等待足够长的时间,以期待接收更多的应用数据,最后再一起发送;在接收数据时,TCP首先是将数据放在接收缓冲中,只有在应用程序准备就绪或者TCP协议认为时机恰当的时候,数据才会交付给应用程序。
缓冲机制是出于对网络性能提升的考虑,但是这种机制也降低了应用程序的实时性。为了解决这个问题,发送方应用程序向TCP传递数据时,请求推送操作,这时TCP协议不会等待发送缓冲区被填满,而是直接将报文发送出去。同时,被推送出去的报文首部中推送位(PSH)将被置1,接收端在收到这类的报文时。会尽快将数据递交给应用程序,而不必缓冲更多的数据再递交。
LWIP对于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;/* 标志位 */ #define TCP_FIN 0x01U //终止连接 #define TCP_SYN 0x02U //发起连接,同步序号 #define TCP_RST 0x04U //复位连接 #define TCP_PSH 0x08U //推送数据 #define TCP_ACK 0x10U //确认序号有效 #define TCP_URG 0x20U //紧急指针有效 #define TCP_ECE 0x40U //暂时LwIP不支持 #define TCP_CWR 0x80U //暂时LwIP不支持 #define TCP_FLAGS 0x3fU //TCP首部标志域
控制块
LwIP中定义了两种类型的TCP控制块,一种专门用于描述处于LISTEN状态的连接,另一种用于描述处于其他状态的连接。
/* TCP控制块共有字段 */ #define TCP_PCB_COMMON(type) \type *next; \ //用于将所有TCP控制块连接成链表enum tcp_state state; \ //TCP控制块状态u8_t prio; \ //TCP控制块优先级void *callback_arg; \ //用户自定义数据指针u16_t local_port; \ //本地端口号DEF_ACCEPT_CALLBACK /* listen状态TCP控制块 */ struct tcp_pcb_listen { /* IP相关字段 */IP_PCB;/* TCP相关字段 */TCP_PCB_COMMON(struct tcp_pcb_listen); }; /* TCP控制块 */ struct tcp_pcb {/* IP相关字段 */IP_PCB;/* TCP相关字段 */TCP_PCB_COMMON(struct tcp_pcb);/* 远程端口号 */u16_t remote_port;/* 标志位 */u8_t flags; #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) //不允许延迟发送ACK #define TF_NAGLEMEMERR ((u8_t)0x80U) //内存不足,必须尽快发送报文段腾出空间/* 下一个期望接收的编号 */u32_t rcv_nxt;/* 接收窗口大小 */u16_t rcv_wnd;/* 通告窗口大小 */u16_t rcv_ann_wnd;/* 接收通告窗口右边界值(每次发送数据后更新) */u32_t rcv_ann_right_edge;/* 某些状态时间 */u32_t tmr;/* poll计数器和间隔时间(polltmr超过pollinterval调用poll回调函数) */u8_t polltmr, pollinterval;/* 重传定时器,500ms加一 */s16_t rtime; //-1表示关闭/* 最大报文段长度 */u16_t mss;/* RTT计时器,500ms加一 */u32_t rttest; //0表示关闭/* 正在RTT估算的报文段序号 */u32_t rtseq;/* RTT估算的中间值 */s16_t sa, sv;/* 报文段超时间隔 */s16_t rto;/* 已经重传次数 */u8_t nrtx;/* 被接收方确认的最高编号(发送窗口前一个字节编号) */u32_t lastack;/* 重复确认次数 */u8_t dupacks;/* 阻塞窗口大小 */u16_t cwnd; /* 拥塞避免启动门限 */u16_t ssthresh;/* 下一个将要发送的数据编号 */u32_t snd_nxt;/* 发送窗口大小 */u16_t snd_wnd;/* 上一次窗口更新时收到的数据序号和确认序号 */u32_t snd_wl1, snd_wl2;/* 下一个可以被应用程序缓存的编号 */u32_t snd_lbb;/* 上一次成功发送的字节数 */u16_t acked;/* 可使用的发送缓冲区大小 */u16_t snd_buf;#define TCP_SNDQUEUELEN_OVERFLOW (0xffff-3)/* 缓冲数据已占用pbuf个数 */u16_t snd_queuelen;/* 待发送报文段队列 */struct tcp_seg *unsent;/* 待确认报文段队列 */struct tcp_seg *unacked;/* 接收无序报文段队列 */struct tcp_seg *ooseq;/* 指向上次成功接收但未被应用层取用的数据 */struct pbuf *refused_data;/* 发送成功回调函数 */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);/* 多久之后进行保活探测 */u32_t keep_idle;/* 保活时间间隔 */u32_t keep_intvl;/* 保活最大报文数 */u32_t keep_cnt;/* 坚持定时器计数 */u32_t persist_cnt;/* 已发送零探测报文个数 */u8_t persist_backoff;/* 已发送的保活报文数 */u8_t keep_cnt_sent; };为了组织和描述系统内的所有TCP控制块,内核定义了四条链表来连接处于不同状态下的控制块。
/* 已绑定本地端口号的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;
连接机制
TCP协议为通信双方提供了完善的连接建立机制和连接断开机制。
客户端和服务器建立连接的过程称为三次握手过程
客户端和服务器断开连接的过程称为四次握手过程
客户端和服务器复位连接
在正常情况下,通信双方通过使用关闭连接的握手过程来结束一个连接,但是在连接出现异常的情况下(例如TCP软件在检测到一个SYN报文段请求连接的端口在本地并不存在时),TCP可以直接中断这个连接,这就是连接的复位。复位连接时,发送方将发送一个RST标志被置1的报文段,接收方对复位报文段的处理是直接终止该连接上的数据传送,释放发送、接收缓冲,并向应用程序通知连接复位。
TCP为每个连接定义了11中状态,在LwIP中,这11中状态的定义如下
/* 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 //主动方收到断开请求,发送断开响应,等待2MSL };
差错控制(确认与重传)
确认与重传,接收方通过确认的机制来告诉发送方数据的接收状况,这是通过向发送方返回一个确认号来完成的,确认号标识出自己期望接收到的下一个字节的编号。如果接收方只收到报文段1和3,那么返回的确认号仍然是320。
发送方为每个报文启动一个定时器,在指定的时间内没有收到确认,会认为报文发送失败,并重传报文。超时重传,相关的字段包括rtime、rttest、rtseq、sa、sv、rto和nrtx。rtime表示重传定时器,它的值每500ms被内核加1,当该值超过rto时,报文段重传将发生;rttest用于对某个报文段计时,测算该报文段在两台主机之间的往返时间,不同的时间点、不同的网络状况下,往返时间的估计都会各有差异,TCP会根据往返时间的估计值动态设置各个报文段的超时间隔rto;rtseq表示了当前正在进行往返时间估计的报文段序号;sa、sv是与超时时间rto设置密切相关的两个字段;rto表示超时时间间隔;nrtx表示报文段被重传的次数,也是与rto的设置密切相关。
怎样决定超时间隔(RTO)是提高TCP性能的关键,而这些都与往返时间(RTT)估计密切相关。发送方可能在某段时间内连续发送多个报文段,但只能选择一个报文段作为基准,启动一个定时器以测量其RTT值。TCP/IP协议利用一些优化算法平滑RTT的值,使得这些报文段测量出来的RTT值更具有效性,而在往返时间变化起伏很大时,基于均值和方差来计算RTO能提供更好的效果。
上面各式中,M表示某次测量的RTT值;A表示已测得的RTT平均值;D值为RTT估计的方差;g和h都为常数,一般g取1/8,h取1/4。在算法初始时,RTO取值为6,即3s;A值为0,D值为6。
注:sa和sv是计算中间量,其中sa = 8A、sv = 4D。
当重发后仍然收不到确认,很可能是网络不通或者网络阻塞。如果还使用原来的RTO重发数据包,势必使问题越来越严重。参照TCP/IP标准中的算法,LwIP的做法是:如果重发的报文超时,则接下来的重发必须将RTO的值设置为前一次的两倍,且当重发次数超过上限后,停止重发。
注:重发报文期间不进行RTT估计。
缓冲机制
在发送方,上层应用程序可能会将各种各样、大小各异的数据流交给TCP发送,TCP提供了完整的缓冲机制来提高传送效率并减少网络中的通信量,在少量数据发送时,协议通常会短暂延迟数据的发送时间,以缓冲到更多的用户数据,以组成一个最佳大小的报文段发送出去;对于每个发送出去的报文,TCP不会马上删除它们,而是将它们保存在内部缓冲中,以便需要的时候重传,只有等到对应的确认到达后,报文才会在缓冲区中被删除;同理,接收方也必须维护良好的缓冲机制,因为底层的报文可能是无序到达的,这里需要把各个无序报文组织为有序数据流,重复的报文会被删除。
流量控制(滑动窗口)
发送方发送报文速度很快,而接收方处理报文的速度很慢,这时在接收方会发生数据丢失的情况,丢失最终导致发送方的重传,这使得整个连接的通信效率降低;另一方面,如果发生每次只发送很少的数据,然后等待确认的返回,而接收方在处理完数据并返回确认后,又继续等待接收后续的数据,这样不管是接收方还是发送方在很多时候都会处于等待状态,连接的效率也无法得到提升。TCP中引进了滑动窗口的概念来进行流量的控制,接收数据的一方可以向发送方通告自己接收窗口的大小,告诉发送方自己的接收能力,而发送方可以根据这个窗口的大小来发送尽可能多的数据,同时又保证了接收方能够处理这些数据。
接收窗口,相关的字段包括rcv_nxt、rcv_wnd、rcv_ann_wnd和rcv_ann_right_edge。rcv_nxt是自己期望接收到的下一个数据字编号;rcv_wnd表示接收窗口的大小,收到数据会减小,上层取走数据则增大;rcv_ann_wnd表示将向对方通告的窗口大小值,当rcv_wnd改变时,内核会计算出一个合理的通告窗口值rcv_ann_wnd;rcv_ann_right_edge记录了上一次窗口通告时窗口右边界取值。
发送窗口,相关的字段包括lastack、snd_nxt、snd_wnd、snd_lbb、snd_wl1和snd_wl2。lastack记录了被接收方确认的最高序列号;snd_nxt表示自己将要发送的下一个数据的起始编号;snd_wnd记录了当前的发送窗口大小,它常被设置为接收方通告的接收窗口值;snd_lbb记录了下一个将被应用程序缓存的数据的起始编号;snd_wl1记录上一次窗口更新时收到的数据序号;snd_wl2记录上一次窗口更新时收到的确认序号。
拥塞控制(慢启动与拥塞避免)
拥塞控制考虑的是网络传输状况。在路由器发送拥塞时,会丢弃不能处理的数据报,这将导致发送方因接收不到确认而重传,重传的数据同样不会成功,且重传会使得路由器中拥塞更为严重。拥塞发生时报文被丢弃,但是发送方不会得到任何报文丢失的信息,因此,发送方必须实现一种自适应机制,及时检测网络中的拥塞状况,自动调节数据的发送速度,这样才能提高数据发送的成功率。
拥塞控制需要考虑两种情况:
1.在连接刚建立时,无法得知合理的报文发送速率。
2.在产生拥塞之后,必须减小报文发送速率。
慢启动算法,为发送方增加了阻塞窗口,相关字段为cwnd。阻塞窗口被初始化为1。当发送方检测到拥塞时(超时或收到重复确认),ssthresh被设置为有效发送窗口的一半,cwnd被设置为1个报文段大小。发送方取阻塞窗口与通告窗口两者中的最小值(有效发送窗口大小)作为发送上限。每收到一个ACK,阻塞窗口增加一个报文段大小。一旦发现报文丢失,发送方就把阻塞窗口减半,直至减少至最小的窗口(至少一个报文段)。
拥塞避免算法,相关字段为ssthresh,表示拥塞避免启动门限。在LwIP中,客户端初始值被设置为10个报文段大小,服务器初始值被设置为对方通告的接收窗口大小。当发送方检测到拥塞时(超时或收到重复确认),ssthresh被设置为有效发送窗口的一半,cwnd被设置为1个报文段大小。每次收到一个确认时将cwnd增加1/cwnd,当整个窗口内的数据被确认,cwnd值只增加了1。
如果cwnd小于或等于ssthresh,则处于慢启动阶段,否则处于拥塞避免阶段。
快速重传与快速恢复
两种情况可能使发送方得到重复的ACK(能够不断收到重复ACK,说明网络不存在拥塞)
如果网络环境不稳定导致报文段丢失,需要重传报文。但是TCP存在超时重传机制,这带来了一些新的问题
快速重传算法,当发送机接收到三个重复确认时,TCP假定网络环境不稳定导致报文段丢失。此时,发送方无需等待超时定时器溢出就进行重传,TCP进入快速重传模式。ssthresh设置为有效发送窗口的一半,cwnd设置为ssthresh + 3个报文段大小,此后每收到一个重复确认cwnd增加一个报文段大小。
快速恢复算法,若收到了新报文段的确认,将cwnd设置为ssthresh,即后续直接执行拥塞避免算法。
糊涂窗口与避免
糊涂窗口综合征(SWS):可能是由发送方引起的,也可能是由接收方引起的,最终导致网络上产生很多的小报文段。小报文段中IP首部和TCP首部这些字段占了大部分空间,而真正的TCP数据却很少,小报文段的传输浪费了网络的大量带宽。
1、发送方引起的糊涂窗口综合征
发送端产生数据很慢,一次将一个字节的数据写入发送端的TCP缓存。如果发送端的TCP没有特定的指令,它就产生一个字节数据的报文段,结果有很多41字节的IP数据报就在互连网中传来传去。
解决方法是:强迫发送端的TCP收集数据,然后用一个更大的数据块来发送。如果发送端的TCP等待时间过长,就会降低实时性;等待时间不够长,就可能发送较小的报文段。为了解决这个问题,Nagle发明了Nagle算法。
Nagle算法(LWIP的实现方式):
(1)如果不存在已发送未确认的报文段,则允许发送
(2)如果处于快速重传模式,则允许发送;
(3)设置了TCP_NODELAY选项,则允许发送;
(4)未发送报文段大于等于两个,则允许发送;
(5)第一个未发送的报文段长度达到MSS,则允许发送;
2、接收方引起的糊涂窗口综合征
接收端消耗数据很慢,缓存满了,一次消耗一个字节。接收端的TCP通告其窗口大小为1字节,发送端就发送一个字节数据的报文段,结果有很多41字节的IP数据报就在互连网中传来传去。
解决办法有两种:
(1)只要有数据到达就发送确认,但宣布的窗口大小为零,直到或者缓存空间已能放入具有最大长度的报文段,或者缓存空间的一半已经空了。
(2)当数据到达时并不立即确认,直到入缓存有足够的空间。迟延的确认还有另一个优点,它减少了通信量。但是如果延时过长,就会导致重传;如果延时不够长,就可能发送较小的报文段。需要用协议来平衡,如确认延迟不超过500毫秒。
零窗口探查
如果发送方接收到的通告窗口大小为0,则会停止报文段发送,直到接收方通告非0的窗口。但是通常这个非0窗口通告在一个不含任何数据的ACK报文中发送,并且接收方不会对ACK报文段进行确认。如果这个非0窗口通告丢失了,则双方可能陷入互相等待。为了防止这种死锁情况的发生,发送方使用一个坚持定时器来周期性地向接收方查询,以便发现窗口是否已经增大。
坚持定时器,相关的字段包括persist_cnt和persist_backoff。persist_cnt用于坚持定时器计数,当计时超过某个上限时,则发送窗口探测报文;persist_backoff是计时上限数组的索引。
当发送窗口太小以至于不能发送下一个报文时,启动窗口于探测。计时上限数组为1.5S、3S、6S、12S、24S、48S、60S,当persist_cnt大于数组元素个数时,时间间隔变为60S。
若检测到一个非0窗口,则停止窗口探查。
保活机制
当TCP连接已处于稳定状态,而双方没有数据需要发送,则在这个连接之间不会再有任何信息交互。如果其中一方崩溃或重启,那么原来的有效连接将变得无效。因此TCP提供一种保活机制,来进行检测。
1.如果双方没有任何数据交互,服务器默认将每两个小时检测一次。当然,一旦服务器收到任何数据,将重新进行计时。
2.如果客户端没有响应,服务器还会默认发送9个探查报文进行检测,每个报文默认间隔75s。
3.如果服务器一个响应都没有收到,就会任何客户端已经关闭。
注:如果客户端崩溃并重启,则客户端收到探查报文后会发送复位报文,服务器收到后结束该连接。
保活定时器,相关的字段包括keep_idle、keep_intvl、keep_cnt、keep_cnt_sent。keep_idle记录了多久之后进行保活探测,默认2小时;keep_cnt_sent表示已经发送保活探查报文个数;keep_intvl表示保活时间间隔,默认75s;keep_cnt表示保活最大报文数,默认9次。
TCP代码
/* tcp定时器节拍(周期500ms) */ u32_t tcp_ticks;/* 指数避让数组,数据包重发后扔收不到确认,RTO值按数组值进行指数扩大 */ const u8_t tcp_backoff[13] = { 1, 2, 3, 4, 5, 6, 7, 7, 7, 7, 7, 7, 7}; /* 坚持定时器计数上限值 */ const u8_t tcp_persist_backoff[7] = { 3, 6, 12, 24, 48, 96, 120 };/* 已绑定本地端口的TCP控制块链表 */ struct tcp_pcb *tcp_bound_pcbs; /* 侦听状态的TCP控制块链表 */ union tcp_listen_pcbs_t tcp_listen_pcbs; /* 活跃(正在交互)的TCP控制块链表 */ struct tcp_pcb *tcp_active_pcbs; /* 等待2MSL状态的TCP控制块链表 */ struct tcp_pcb *tcp_tw_pcbs;/* TCP控制块临时指针 */ struct tcp_pcb *tcp_tmp_pcb;static u8_t tcp_timer; static u16_t tcp_new_port(void);/* TCP定时器回调函数(周期250ms) */ void tcp_tmr(void) {/* 快定时器回调函数 */tcp_fasttmr();/* 慢定时器回调函数 */if (++tcp_timer & 1) {tcp_slowtmr();} }/* 关闭连接 */ err_t tcp_close(struct tcp_pcb *pcb) {err_t err;/* 根据不同的状态做不同的处理 */switch (pcb->state) {/* CLOSED态(连接断开) */case CLOSED:err = ERR_OK;/* 将控制块从已绑定本地端口的TCP控制块链表上移除 */TCP_RMV(&tcp_bound_pcbs, pcb);/* 释放控制块 */memp_free(MEMP_TCP_PCB, pcb);pcb = NULL;break;/* LISTEN态(服务器侦听中) */case LISTEN:err = ERR_OK;/* 将控制块从侦听状态的TCP控制块链表删除,释放所有缓冲区数据 */tcp_pcb_remove((struct tcp_pcb **)&tcp_listen_pcbs.pcbs, pcb);/* 释放控制块 */memp_free(MEMP_TCP_PCB_LISTEN, pcb);pcb = NULL;break;/* SYN_SENT态(客户端请求连接,等待对方同意并请求连接) */case SYN_SENT:err = ERR_OK;/* 将控制块从活跃(正在交互)的TCP控制块链表删除,释放所有缓冲区数据 */tcp_pcb_remove(&tcp_active_pcbs, pcb);/* 释放控制块 */memp_free(MEMP_TCP_PCB, pcb);pcb = NULL;break;/* SYN_RCVD态(服务器接受连接请求,发送同意请求并请求连接,等待对方同意请求) */case SYN_RCVD:/* 构造TCP结束报文 */err = tcp_send_ctrl(pcb, TCP_FIN);if (err == ERR_OK) {/* 设置PCB为FIN_WAIT_1态(主动方请求断开,等待被动方断开响应) */pcb->state = FIN_WAIT_1;}break;/* ESTABLISHED态(对方同意连接请求,连接已建立) */case ESTABLISHED:/* 构造TCP结束报文 */err = tcp_send_ctrl(pcb, TCP_FIN);if (err == ERR_OK) {/* 设置PCB为FIN_WAIT_1态(主动方请求断开,等待被动方断开响应) */pcb->state = FIN_WAIT_1;}break;/* CLOSE_WAIT态(被动方收到断开请求,发送断开响应,等待应用程序关闭) */case CLOSE_WAIT:/* 构造TCP结束报文 */err = tcp_send_ctrl(pcb, TCP_FIN);if (err == ERR_OK) {/* 设置PCB为LAST_ACK态(被动方收到关闭指令,发送断开请求,等待主动方断开响应) */pcb->state = LAST_ACK;}break;/* 其它状态返回错误 */default:err = ERR_OK;pcb = NULL;break;}/* 输出报文段 */if (pcb != NULL && err == ERR_OK) {tcp_output(pcb);}return err; }/* 删除TCP控制块,可选则发送TCP复位报文 */ void tcp_abandon(struct tcp_pcb *pcb, int reset) {u32_t seqno, ackno;u16_t remote_port, local_port;struct ip_addr remote_ip, local_ip;void (* errf)(void *arg, err_t err);void *errf_arg;/* 控制块处于TIME_WAIT状态(主动方收到断开请求,发送断开响应,等待2MSL) */if (pcb->state == TIME_WAIT) {/* 将控制块从等待2MSL状态的TCP控制块链表删除,释放所有缓冲区数据 */tcp_pcb_remove(&tcp_tw_pcbs, pcb);/* 释放控制块 */memp_free(MEMP_TCP_PCB, pcb);} /* 其它状态下 */else {/* 下一个将要发送的数据编号 */seqno = pcb->snd_nxt;/* 下一个期望收到的数据确认号 */ackno = pcb->rcv_nxt;/* 本地IP和远端IP */ip_addr_set(&local_ip, &(pcb->local_ip));ip_addr_set(&remote_ip, &(pcb->remote_ip));/* 本地端口号和远端端口号 */local_port = pcb->local_port;remote_port = pcb->remote_port;/* 错误回调函数 */errf = pcb->errf;/* 错误回调函数参数 */errf_arg = pcb->callback_arg;/* 将控制块从活跃(正在交互)的TCP控制块链表中移除 */tcp_pcb_remove(&tcp_active_pcbs, pcb);/* 释放待确认队列上的所有报文段 */if (pcb->unacked != NULL) {tcp_segs_free(pcb->unacked);}/* 释放待发送队列上的所有报文段 */if (pcb->unsent != NULL) {tcp_segs_free(pcb->unsent);}/* 释放接收失序队列上的所有报文段 */ if (pcb->ooseq != NULL) {tcp_segs_free(pcb->ooseq);}/* 释放控制块 */memp_free(MEMP_TCP_PCB, pcb);/* 调用错误回调函数 */TCP_EVENT_ERR(errf, errf_arg, ERR_ABRT);/* 发送TCP复位报文 */if (reset) {tcp_rst(seqno, ackno, &local_ip, &remote_ip, local_port, remote_port);}} }/* 绑定本地端口(端口号最好大于0x8000,0表示自动分配端口号) */ err_t tcp_bind(struct tcp_pcb *pcb, struct ip_addr *ipaddr, u16_t port) {struct tcp_pcb *cpcb;/* 如果没有指定端口号,自动分配一个没有被使用的端口号 */if (port == 0) {port = tcp_new_port();}/* 遍历侦听状态的TCP控制块链表 */for(cpcb = (struct tcp_pcb *)tcp_listen_pcbs.pcbs; cpcb != NULL; cpcb = cpcb->next) {/* 端口号相同 */if (cpcb->local_port == port) {/* IP地址存在重复 */if (ip_addr_isany(&(cpcb->local_ip)) || ip_addr_isany(ipaddr) || ip_addr_cmp(&(cpcb->local_ip), ipaddr)) {/* 该端口已经被使用,返回错误 */return ERR_USE;}}}/* 遍历活跃(正在交互)的TCP控制块链表 */for(cpcb = tcp_active_pcbs; cpcb != NULL; cpcb = cpcb->next) {/* 端口号相同 */if (cpcb->local_port == port) {/* IP地址存在重复 */if (ip_addr_isany(&(cpcb->local_ip)) || ip_addr_isany(ipaddr) || ip_addr_cmp(&(cpcb->local_ip), ipaddr)) {/* 该端口已经被使用,返回错误 */return ERR_USE;}}}/* 遍历已绑定本地端口的TCP控制块链表 */for(cpcb = tcp_bound_pcbs; cpcb != NULL; cpcb = cpcb->next) {/* 端口号相同 */if (cpcb->local_port == port) {/* IP地址存在重复 */if (ip_addr_isany(&(cpcb->local_ip)) || ip_addr_isany(ipaddr) || ip_addr_cmp(&(cpcb->local_ip), ipaddr)) {/* 该端口已经被使用,返回错误 */return ERR_USE;}}}/* 遍历等待2MSL状态的TCP控制块链表(进入该状态后,等待2MSL才能释放控制块,端口方可重新使用) */for(cpcb = tcp_tw_pcbs; cpcb != NULL; cpcb = cpcb->next) {/* 端口号相同 */if (cpcb->local_port == port) {/* IP地址相同 */if (ip_addr_cmp(&(cpcb->local_ip), ipaddr)) {/* 该端口已经被使用,返回错误 */return ERR_USE;}}}/* 绑定IP地址和端口号 */if (!ip_addr_isany(ipaddr)) {pcb->local_ip = *ipaddr;}pcb->local_port = port;/* 将TCP控制块插入已绑定本地端口的TCP控制块链表 */TCP_REG(&tcp_bound_pcbs, pcb);return ERR_OK; }static err_t tcp_accept_null(void *arg, struct tcp_pcb *pcb, err_t err) {return ERR_ABRT; }/* 服务器开始侦听 */ struct tcp_pcb *tcp_listen_with_backlog(struct tcp_pcb *pcb, u8_t backlog) {struct tcp_pcb_listen *lpcb;/* 控制块已经处于LISTEN态(服务器侦听中),不做处理 */if (pcb->state == LISTEN) {return pcb;}/* 申请侦听状态PCB空间 */lpcb = memp_malloc(MEMP_TCP_PCB_LISTEN);if (lpcb == NULL) {return NULL;}/* 将参数从原来的PCB中拷贝到侦听状态PCB中 *//* 用户自定义数据指针 */lpcb->callback_arg = pcb->callback_arg;/* 本地端口号 */lpcb->local_port = pcb->local_port;/* 控制块状态设置为LISTEN态(服务器侦听中) */lpcb->state = LISTEN;/* 套接字选项 */lpcb->so_options = pcb->so_options;lpcb->so_options |= SOF_ACCEPTCONN;/* 生命周期 */lpcb->ttl = pcb->ttl;/* IP服务选项 */lpcb->tos = pcb->tos;/* 本地IP */ip_addr_set(&lpcb->local_ip, &pcb->local_ip);/* 将控制块从已绑定本地端口的TCP控制块链表中移除 */TCP_RMV(&tcp_bound_pcbs, pcb);/* 释放原控制块内存空间 */memp_free(MEMP_TCP_PCB, pcb);/* 默认接受连接回调函数 */lpcb->accept = tcp_accept_null;/* 将控制块插入侦听状态的TCP控制块链表 */TCP_REG(&tcp_listen_pcbs.listen_pcbs, lpcb);/* 返回侦听控制块指针 */return (struct tcp_pcb *)lpcb; }/* 更新接收通告窗口大小 */ u32_t tcp_update_rcv_ann_wnd(struct tcp_pcb *pcb) {/* 真实的接收窗口右边界值 */u32_t new_right_edge = pcb->rcv_nxt + pcb->rcv_wnd;/* 真实的窗口右边界值,比通告窗口右边界值大超过1个mss */if (TCP_SEQ_GEQ(new_right_edge, pcb->rcv_ann_right_edge + LWIP_MIN((TCP_WND / 2), pcb->mss))) {/* 接收窗口通告值直接设为真实接收窗口值 */pcb->rcv_ann_wnd = pcb->rcv_wnd;/* 返回接收窗口右边界值增加量 */return new_right_edge - pcb->rcv_ann_right_edge;} /* 真实的窗口右边界值,比通告窗口右边界值大不超过1个mss *//* 不使用真实接收窗口值 */else {/* 刚刚收到的数据比通告窗口还大 */if (TCP_SEQ_GT(pcb->rcv_nxt, pcb->rcv_ann_right_edge)) {/* 接收通告窗口大小设为0 */pcb->rcv_ann_wnd = 0;}/* 刚刚收到的数据没有通告窗口大 */else {/* 缩小接收通告窗口值 */pcb->rcv_ann_wnd = pcb->rcv_ann_right_edge - pcb->rcv_nxt;}return 0;} }/* 应用层接收对方传来的数据 */ void tcp_recved(struct tcp_pcb *pcb, u16_t len) {int wnd_inflation;/* 更新接收窗口 */pcb->rcv_wnd += len;if (pcb->rcv_wnd > TCP_WND)pcb->rcv_wnd = TCP_WND;/* 更新接收通告窗口 */wnd_inflation = tcp_update_rcv_ann_wnd(pcb);/* 发送响应 */if (wnd_inflation >= TCP_WND_UPDATE_THRESHOLD) tcp_ack_now(pcb); }/* 分配一个没有被使用的端口号 */ static u16_t tcp_new_port(void) {struct tcp_pcb *pcb; #define TCP_LOCAL_PORT_RANGE_START 4096 #define TCP_LOCAL_PORT_RANGE_END 0x7fffstatic u16_t port = TCP_LOCAL_PORT_RANGE_START;again:/* 获取一个端口号 */if (++port > TCP_LOCAL_PORT_RANGE_END) {port = TCP_LOCAL_PORT_RANGE_START;}/* 遍历活跃(正在交互)的TCP控制块链表 */for(pcb = tcp_active_pcbs; pcb != NULL; pcb = pcb->next) {/* 该端口号如果被使用,则重新获取一个端口号 */if (pcb->local_port == port) {goto again;}}/* 遍历等待2MSL状态的TCP控制块链表 */for(pcb = tcp_tw_pcbs; pcb != NULL; pcb = pcb->next) {/* 该端口号如果被使用,则重新获取一个端口号 */if (pcb->local_port == port) {goto again;}}/* 遍历侦听状态的TCP控制块链表 */for(pcb = (struct tcp_pcb *)tcp_listen_pcbs.pcbs; pcb != NULL; pcb = pcb->next) {/* 该端口号如果被使用,则重新获取一个端口号 */if (pcb->local_port == port) {goto again;}}/* 返回没有被使用的端口号 */return port; }/* 连接远程端口 */ err_t tcp_connect(struct tcp_pcb *pcb, struct ip_addr *ipaddr, u16_t port, err_t (* connected)(void *arg, struct tcp_pcb *tpcb, err_t err)) {err_t ret;u32_t iss;/* 设置远程IP和端口号 */if (ipaddr != NULL) {pcb->remote_ip = *ipaddr;} else {return ERR_VAL;}pcb->remote_port = port;/* 如果没有绑定本地端口,则自动分配一个端口号 */if (pcb->local_port == 0) {pcb->local_port = tcp_new_port();}/* 获得一个初始数据编号 */iss = tcp_next_iss();/* 下一个期望接收的编号,收到同步报文后会重新设置 */pcb->rcv_nxt = 0;/* 下一个将要发送的数据编号 */pcb->snd_nxt = iss;/* 被确认的最高数据编号,相当于iss前面的数据都已确认 */pcb->lastack = iss - 1;/* 下一个可以被应用程序缓存的数据编号 */pcb->snd_lbb = iss - 1;/* 接收窗口大小 */pcb->rcv_wnd = TCP_WND;/* 接收通告窗口大小 */pcb->rcv_ann_wnd = TCP_WND;/* 接收窗口右边界值 */pcb->rcv_ann_right_edge = pcb->rcv_nxt;/* 发送窗口大小,通常设为对方的通告窗口大小 */pcb->snd_wnd = TCP_WND;/* 最大报文段大小(不超过536) */pcb->mss = (TCP_MSS > 536) ? 536 : TCP_MSS;/* 根据IP地址所在网络接口最大允许数据包长度,重新计算TCP最大报文段长度 */pcb->mss = tcp_eff_send_mss(pcb->mss, ipaddr);/* 客户端阻塞窗口,1表示大小不确定 */pcb->cwnd = 1;/* 客户端拥塞避免启动门限初始化为10mss */pcb->ssthresh = pcb->mss * 10;/* 控制块状态设为SYN_SENT态(客户端请求连接,等待对方同意并请求连接) */pcb->state = SYN_SENT;/* 连接成功回调函数 */pcb->connected = connected;/* 将控制块已绑定本地端口的TCP控制块链表中移除 */TCP_RMV(&tcp_bound_pcbs, pcb);/* 将控制块插入活跃(正在交互)的TCP控制块链表 */TCP_REG(&tcp_active_pcbs, pcb);/* 组建TCP同步报文 */ret = tcp_enqueue(pcb, NULL, 0, TCP_SYN, 0, TF_SEG_OPTS_MSS);if (ret == ERR_OK) {/* 发送报文 */tcp_output(pcb);}return ret; } /* TCP慢定时器函数(周期500ms) */ void tcp_slowtmr(void) {struct tcp_pcb *pcb, *pcb2, *prev;u16_t eff_wnd;u8_t pcb_remove;u8_t pcb_reset;err_t err;err = ERR_OK;/* TCP定时器节拍加一 */++tcp_ticks;/* 遍历活跃(正在交互)的TCP控制块链表 */prev = NULL;pcb = tcp_active_pcbs;while (pcb != NULL) {pcb_remove = 0;pcb_reset = 0;/* 该控制块处于SYN_SENT态(客户端请求连接,等待对方同意并请求连接),并且同步报文重传次数达到最大 */if (pcb->state == SYN_SENT && pcb->nrtx == TCP_SYNMAXRTX) {/* 需要删除该控制块 */++pcb_remove;}/* 控制块报文重传次数达到最大 */else if (pcb->nrtx == TCP_MAXRTX) {/* 需要删除该控制块 */++pcb_remove;} /* 报文重传次数没有达到最大 */else {/* 坚持定时器已启动 */if (pcb->persist_backoff > 0) {/* 坚持定时器计数加一 */pcb->persist_cnt++;/* 坚持定时器计数超时 */if (pcb->persist_cnt >= tcp_persist_backoff[pcb->persist_backoff - 1]) {/* 重新计数 */pcb->persist_cnt = 0;/* 零窗口探测次数加一 */if (pcb->persist_backoff < sizeof(tcp_persist_backoff)) {pcb->persist_backoff++;}/* 发送零窗口探测报文 */tcp_zero_window_probe(pcb);}} /* 坚持定时器未启动 */else {/* 重传定时器计数值加一 */if(pcb->rtime >= 0)++pcb->rtime;/* 有数据未确认且超时发生 */if (pcb->unacked != NULL && pcb->rtime >= pcb->rto) {/* SYN_SENT态(客户端请求连接,等待对方同意并请求连接)不进行指数避让 */if (pcb->state != SYN_SENT) {/* 动态计算RTO值 */pcb->rto = ((pcb->sa >> 3) + pcb->sv) << tcp_backoff[pcb->nrtx];}/* 重传定时器清零 */pcb->rtime = 0;/* 计算有效发送窗口大小 */eff_wnd = LWIP_MIN(pcb->cwnd, pcb->snd_wnd);/* 将拥塞避免启动门限设置为有效窗口大小的一半 */pcb->ssthresh = eff_wnd >> 1;/* 拥塞避免启动门限至少为2mss */if (pcb->ssthresh < pcb->mss) {pcb->ssthresh = pcb->mss * 2;}/* 设置阻塞窗口大小为1mss */pcb->cwnd = pcb->mss;/* 重传报文段 */tcp_rexmit_rto(pcb);}}}/* FIN_WAIT_2态(主动方收到断开响应,等待被动方断开请求),等待被动方断开请求超时 */if (pcb->state == FIN_WAIT_2) {if ((u32_t)(tcp_ticks - pcb->tmr) > TCP_FIN_WAIT_TIMEOUT / TCP_SLOW_INTERVAL) {/* 需要删除该控制块 */++pcb_remove;}}/* 启用了保活功能并且控制块处于ESTABLISHED态(对方同意连接请求,连接已建立),或处于CLOSE_WAIT态(被动方收到断开请求,发送断开响应,等待应用程序关闭) */if((pcb->so_options & SOF_KEEPALIVE) && ((pcb->state == ESTABLISHED) || (pcb->state == CLOSE_WAIT))) {/* 保活探测超时(2小时+9*75秒) */if((u32_t)(tcp_ticks - pcb->tmr) > (pcb->keep_idle + (pcb->keep_cnt * pcb->keep_intvl)) / TCP_SLOW_INTERVAL){/* 需要删除该控制块 */++pcb_remove;/* 需要复位该连接 */++pcb_reset;}/* 触发保活探测 */else if((u32_t)(tcp_ticks - pcb->tmr) > (pcb->keep_idle + pcb->keep_cnt_sent * pcb->keep_intvl) / TCP_SLOW_INTERVAL){/* 发送保活报文 */tcp_keepalive(pcb);/* 保活报文次数加一 */pcb->keep_cnt_sent++;}}/* 失序重组超时 */if (pcb->ooseq != NULL && (u32_t)tcp_ticks - pcb->tmr >= pcb->rto * TCP_OOSEQ_TIMEOUT) {/* 删除失序报文 */tcp_segs_free(pcb->ooseq);pcb->ooseq = NULL;}/* SYN_RCVD态(服务器接受连接请求,发送同意请求并请求连接,等待对方同意请求),等待客户端同意连接超时 */if (pcb->state == SYN_RCVD) {if ((u32_t)(tcp_ticks - pcb->tmr) > TCP_SYN_RCVD_TIMEOUT / TCP_SLOW_INTERVAL) {/* 需要删除该控制块 */++pcb_remove;}}/* LAST_ACK态(被动方收到关闭指令,发送断开请求,等待主动方断开响应) */if (pcb->state == LAST_ACK) {/* 主动方同意断开连接响应,超过2msl */if ((u32_t)(tcp_ticks - pcb->tmr) > 2 * TCP_MSL / TCP_SLOW_INTERVAL) {/* 需要删除该控制块 */++pcb_remove;}}/* 需要删除该控制块 */if (pcb_remove) {/* 清空控制块上各种缓冲区数据 */tcp_pcb_purge(pcb);/* 将控制块从活跃(正在交互)的TCP控制块链表移除 */if (prev != NULL) {prev->next = pcb->next;} else {tcp_active_pcbs = pcb->next;}/* 调用连接错误回调函数 */TCP_EVENT_ERR(pcb->errf, pcb->callback_arg, ERR_ABRT);/* 需要发送TCP复位报文 */if (pcb_reset) {/* 发送TCP复位报文 */tcp_rst(pcb->snd_nxt, pcb->rcv_nxt, &pcb->local_ip, &pcb->remote_ip, pcb->local_port, pcb->remote_port);}pcb2 = pcb->next;/* 释放控制块结构体 */memp_free(MEMP_TCP_PCB, pcb);pcb = pcb2;}/* 不需要删除该控制块 */else {/* 触发poll回调函数 */++pcb->polltmr;if (pcb->polltmr >= pcb->pollinterval) {pcb->polltmr = 0;/* 调用poll回调函数 */TCP_EVENT_POLL(pcb, err);if (err == ERR_OK) {/* 输出报文段 */tcp_output(pcb);}}prev = pcb;pcb = pcb->next;}}/* 遍历等待2MSL状态的TCP控制块链表 */prev = NULL; pcb = tcp_tw_pcbs;while (pcb != NULL) {pcb_remove = 0;/* 在该状态下持续时间超过2msl */if ((u32_t)(tcp_ticks - pcb->tmr) > 2 * TCP_MSL / TCP_SLOW_INTERVAL) {/* 需要删除该控制块 */++pcb_remove;}/* 需要删除该控制块 */if (pcb_remove) {/* 清空控制块上各种缓冲区数据 */tcp_pcb_purge(pcb); /* 将控制块从等待2MSL状态的TCP控制块链表移除 */if (prev != NULL) {prev->next = pcb->next;} else {tcp_tw_pcbs = pcb->next;}pcb2 = pcb->next;memp_free(MEMP_TCP_PCB, pcb);pcb = pcb2;} /* 不需要删除该控制块 */else {prev = pcb;pcb = pcb->next;}} }/* TCP块定时器函数(周期250ms) */ void tcp_fasttmr(void) {struct tcp_pcb *pcb;/* 遍历活跃(正在交互)的TCP控制块链表 */for(pcb = tcp_active_pcbs; pcb != NULL; pcb = pcb->next) {/* 该控制块有接收到的数据未被应用层取用 */if (pcb->refused_data != NULL) {err_t err;/* 调用接收回调函数 */TCP_EVENT_RECV(pcb, pcb->refused_data, ERR_OK, err);if (err == ERR_OK) {pcb->refused_data = NULL;}}/* 延迟确认最多250ms(未解决糊涂窗口综合征) */ if (pcb->flags & TF_ACK_DELAY) {/* 立即发送响应 */tcp_ack_now(pcb);/* 清空延迟确认标志和立即确认标志 */pcb->flags &= ~(TF_ACK_DELAY | TF_ACK_NOW);}} }/* 释放队列上的所有报文段 */ u8_t tcp_segs_free(struct tcp_seg *seg) {u8_t count = 0;struct tcp_seg *next;/* 遍历整个队列 */while (seg != NULL) {next = seg->next;/* 释放该报文段空间 */count += tcp_seg_free(seg);seg = next;}/* 返回释放的pbuf个数 */return count; }/* 释放报文段空间 */ u8_t tcp_seg_free(struct tcp_seg *seg) {u8_t count = 0;/* 释放报文段数据 */if (seg != NULL) {if (seg->p != NULL) {count = pbuf_free(seg->p);}/* 释放报文段结构 */memp_free(MEMP_TCP_SEG, seg);}/* 返回释放的pbuf个数 */return count; }/* 设置优先级 */ void tcp_setprio(struct tcp_pcb *pcb, u8_t prio) {pcb->prio = prio; }/* 拷贝一个新的报文段 */ struct tcp_seg *tcp_seg_copy(struct tcp_seg *seg) {struct tcp_seg *cseg;/* 为新的报文段申请空间 */cseg = memp_malloc(MEMP_TCP_SEG);if (cseg == NULL) {return NULL;}/* 将原报文段结构体拷贝到新报文段结构体中 */SMEMCPY((u8_t *)cseg, (const u8_t *)seg, sizeof(struct tcp_seg)); /* 报文段数据pbuf引用加一 */pbuf_ref(cseg->p);return cseg; }/* 默认接收回调函数 */ err_t tcp_recv_null(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err) {/* 有数据 */if (p != NULL) {/* 接收数据,并释放数据包空间 */tcp_recved(pcb, p->tot_len);pbuf_free(p);} /* 没有数据 */else if (err == ERR_OK) {/* 对方请求断开,本方也要发送关闭连接进行握手 */return tcp_close(pcb);}return ERR_OK; }/* 释放最低优先级(小于指定优先级)中最老的控制块 */ static void tcp_kill_prio(u8_t prio) {struct tcp_pcb *pcb, *inactive;u32_t inactivity;u8_t mprio;mprio = TCP_PRIO_MAX;inactivity = 0;inactive = NULL;/* 遍历活跃(正在交互)的TCP控制块链表 */for(pcb = tcp_active_pcbs; pcb != NULL; pcb = pcb->next) {/* 找出最低优先级(小于指定优先级)中最老的控制块 */if (pcb->prio <= prio && pcb->prio <= mprio && (u32_t)(tcp_ticks - pcb->tmr) >= inactivity) {inactivity = tcp_ticks - pcb->tmr;inactive = pcb;mprio = pcb->prio;}}/* 回收该控制块 */if (inactive != NULL) {tcp_abort(inactive);} }/* 回收最老的TIME-WAIT状态的TCP控制块 */ static void tcp_kill_timewait(void) {struct tcp_pcb *pcb, *inactive;u32_t inactivity;inactivity = 0;inactive = NULL;/* 遍历TIME-WAIT状态的TCP控制块链表 */for(pcb = tcp_tw_pcbs; pcb != NULL; pcb = pcb->next) {/* 找出最老的TIME-WAIT状态的TCP控制块 */if ((u32_t)(tcp_ticks - pcb->tmr) >= inactivity) {inactivity = tcp_ticks - pcb->tmr;inactive = pcb;}}/* 删除TCP控制块,并发送TCP复位报文 */if (inactive != NULL) {tcp_abort(inactive);} }/* 分配一个TCP控制块 */ struct tcp_pcb *tcp_alloc(u8_t prio) {struct tcp_pcb *pcb;u32_t iss;/* 为TCP控制块申请内存 */pcb = memp_malloc(MEMP_TCP_PCB);/* 申请失败 */if (pcb == NULL) {/* 回收最老的TIME-WAIT状态的TCP控制块 */tcp_kill_timewait();/* 再次为TCP控制块申请内存 */pcb = memp_malloc(MEMP_TCP_PCB);/* 申请失败 */if (pcb == NULL) {/* 释放最低优先级(小于指定优先级)中最老的控制块 */tcp_kill_prio(prio);/* 再次为TCP控制块申请内存 */pcb = memp_malloc(MEMP_TCP_PCB);}}/* 申请成功 */if (pcb != NULL) {memset(pcb, 0, sizeof(struct tcp_pcb));/* 优先级 */pcb->prio = TCP_PRIO_NORMAL;/* 发送缓冲区大小 */pcb->snd_buf = TCP_SND_BUF;/* 发送缓冲区数据已占用pbuf个数 */pcb->snd_queuelen = 0;/* 接收窗口大小 */pcb->rcv_wnd = TCP_WND;/* 接收通告窗口大小 */pcb->rcv_ann_wnd = TCP_WND;/* IP服务类型 */pcb->tos = 0;/* 生存周期 */pcb->ttl = TCP_TTL;/* 最大报文段不得小于536 */pcb->mss = (TCP_MSS > 536) ? 536 : TCP_MSS;/* 报文超时重传时间 */pcb->rto = 3000 / TCP_SLOW_INTERVAL;/* RTO计算中间变量 */pcb->sa = 0;pcb->sv = 3000 / TCP_SLOW_INTERVAL;/* 重传定时器 */pcb->rtime = -1;/* 阻塞窗口,1表示大小不确定 */pcb->cwnd = 1;/* 初始数据编号 */iss = tcp_next_iss();/* 上一次窗口更新收到的数据确认号 */pcb->snd_wl2 = iss;/* 下一个将要发送的数据编号 */pcb->snd_nxt = iss;/* 被接收方确认的最高数据编号 */pcb->lastack = iss;/* 下一个可以被应用程序缓存的数据编号 */pcb->snd_lbb = iss;/* TCP进入某些状态的时间点 */pcb->tmr = tcp_ticks;/* poll计数器,超过pollinterval调用poll回调函数 */pcb->polltmr = 0;/* 默认接收回调函数 */pcb->recv = tcp_recv_null;/* 空闲多久进行保活探测 */pcb->keep_idle = TCP_KEEPIDLE_DEFAULT;/* 保活探测报文间隔时间 */pcb->keep_intvl = TCP_KEEPINTVL_DEFAULT;/* 保活探测报文最多发送次数 */pcb->keep_cnt = TCP_KEEPCNT_DEFAULT;/* 已发送保活探测报文次数 */pcb->keep_cnt_sent = 0;}/* 返回控制块指针 */return pcb; }/* 新建TCP控制块 */ struct tcp_pcb *tcp_new(void) {return tcp_alloc(TCP_PRIO_NORMAL); }/* 设置用户自定义参数 */ void tcp_arg(struct tcp_pcb *pcb, void *arg) { pcb->callback_arg = arg; }/* 设置接收回调函数 */ void tcp_recv(struct tcp_pcb *pcb, err_t (* recv)(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)) {pcb->recv = recv; }/* 设置发送成功回调函数 */ void tcp_sent(struct tcp_pcb *pcb, err_t (* sent)(void *arg, struct tcp_pcb *tpcb, u16_t len)) {pcb->sent = sent; }/* 设置连接错误回调函数 */ void tcp_err(struct tcp_pcb *pcb, void (* errf)(void *arg, err_t err)) {pcb->errf = errf; }/* 设置接受连接回调函数 */ void tcp_accept(struct tcp_pcb *pcb, err_t (* accept)(void *arg, struct tcp_pcb *newpcb, err_t err)) {pcb->accept = accept; }/* 设置poll回调函数和周期 */ void tcp_poll(struct tcp_pcb *pcb, err_t (* poll)(void *arg, struct tcp_pcb *tpcb), u8_t interval) {pcb->poll = poll;pcb->pollinterval = interval; }/* 清空控制块上各种缓冲区数据 */ void tcp_pcb_purge(struct tcp_pcb *pcb) {if (pcb->state != CLOSED && pcb->state != TIME_WAIT && pcb->state != LISTEN) {/* 释放已接收但未被应用层取用的数据 */if (pcb->refused_data != NULL) {pbuf_free(pcb->refused_data);pcb->refused_data = NULL;}/* 关闭重传定时器 */pcb->rtime = -1;/* 释放接收失序重组报文段 */tcp_segs_free(pcb->ooseq);pcb->ooseq = NULL;/* 释放待发送报文段 */tcp_segs_free(pcb->unsent);/* 释放待确认报文段 */tcp_segs_free(pcb->unacked);pcb->unacked = pcb->unsent = NULL;} }/* 将控制块从链表删除,释放所有缓冲区数据,并将控制块设为CLOSE态 */ void tcp_pcb_remove(struct tcp_pcb **pcblist, struct tcp_pcb *pcb) {/* 将控制块从链表中移除 */TCP_RMV(pcblist, pcb);/* 清空控制块上各种缓冲区数据 */tcp_pcb_purge(pcb);/* 延迟发送的响应,发送出去 */if (pcb->state != TIME_WAIT && pcb->state != LISTEN && pcb->flags & TF_ACK_DELAY) {pcb->flags |= TF_ACK_NOW;tcp_output(pcb);}/* 将状态设置为关闭态 */pcb->state = CLOSED; }/* 获得一个初始数据编号 */ u32_t tcp_next_iss(void) {static u32_t iss = 6510;iss += tcp_ticks;return iss; }/* 根据IP地址所在网络接口最大允许数据包长度,重新计算TCP最大报文段长度 */ u16_t tcp_eff_send_mss(u16_t sendmss, struct ip_addr *addr) {u16_t mss_s;struct netif *outif;/* 根据IP地址选择一个合适(和目的主机处于同一子网)的网络接口 */outif = ip_route(addr);/* 根据网络接口最大允许数据包长度,重新计算mss */if ((outif != NULL) && (outif->mtu != 0)) {mss_s = outif->mtu - IP_HLEN - TCP_HLEN;sendmss = LWIP_MIN(sendmss, mss_s);}return sendmss; }
输出处理
/* 设置TCP头部 */ static struct tcp_hdr *tcp_output_set_header(struct tcp_pcb *pcb, struct pbuf *p, int optlen, u32_t seqno_be) {/* TCP头部指针 */struct tcp_hdr *tcphdr = p->payload;/* 源端口号 */tcphdr->src = htons(pcb->local_port);/* 目的端口号 */tcphdr->dest = htons(pcb->remote_port);/* 序号 */tcphdr->seqno = seqno_be;/* 确认序号为下一次期望收到的数据编号 */tcphdr->ackno = htonl(pcb->rcv_nxt);/* 设置ACK有效标志位 */TCPH_FLAGS_SET(tcphdr, TCP_ACK);/* 接收通告窗口 */tcphdr->wnd = htons(pcb->rcv_ann_wnd);/* 紧急指针无效 */tcphdr->urgp = 0;/* 首部长度 */TCPH_HDRLEN_SET(tcphdr, (5 + optlen / 4));/* 校验和初始化为0 */tcphdr->chksum = 0;/* 每次发送数据后,更新接收窗口右边界值 */pcb->rcv_ann_right_edge = pcb->rcv_nxt + pcb->rcv_ann_wnd;return tcphdr; }/* 构造只带标志位的报文(无数据) */ err_t tcp_send_ctrl(struct tcp_pcb *pcb, u8_t flags) {return tcp_enqueue(pcb, NULL, 0, flags, TCP_WRITE_FLAG_COPY, 0); }/* 发送数据 */ err_t tcp_write(struct tcp_pcb *pcb, const void *data, u16_t len, u8_t apiflags) {/* 部分状态允许发送数据 */if (pcb->state == ESTABLISHED || pcb->state == CLOSE_WAIT || pcb->state == SYN_SENT || pcb->state == SYN_RCVD) {if (len > 0) {/* 构建报文段 */return tcp_enqueue(pcb, (void *)data, len, 0, apiflags, 0);}return ERR_OK;} else {return ERR_CONN;} }/* 构建TCP报文段 */ err_t tcp_enqueue(struct tcp_pcb *pcb, void *arg, u16_t len, u8_t flags, u8_t apiflags, u8_t optflags) {struct pbuf *p;struct tcp_seg *seg, *useg, *queue;u32_t seqno;u16_t left, seglen;void *ptr;u16_t queuelen;u8_t optlen;/* 发送缓冲区大小不足以发送指定数据 */if (len > pcb->snd_buf) {pcb->flags |= TF_NAGLEMEMERR;return ERR_MEM;}/* 剩余数据长度 */left = len;/* 数据指针 */ptr = arg;optlen = LWIP_TCP_OPT_LENGTH(optflags);/* 下一个可以被应用程序缓存的数据编号 */seqno = pcb->snd_lbb;/* 发送缓冲区pbuf超过规定上限 */queuelen = pcb->snd_queuelen;if ((queuelen >= TCP_SND_QUEUELEN) || (queuelen > TCP_SNDQUEUELEN_OVERFLOW)) {pcb->flags |= TF_NAGLEMEMERR;return ERR_MEM;}/* 将所有数据组织成pbuf报文段 */useg = queue = seg = NULL;seglen = 0;while (queue == NULL || left > 0) {/* 计算合理报文段长度 */seglen = left > (pcb->mss - optlen) ? (pcb->mss - optlen) : left;/* 申请TCP报文段结构体 */seg = memp_malloc(MEMP_TCP_SEG);if (seg == NULL) {goto memerr;}seg->next = NULL;seg->p = NULL;/* 第一个报文段 */if (queue == NULL) {queue = seg;}/* 将该报文段插入报文段序列 */else {useg->next = seg;}/* 记录最后一个报文段 */useg = seg;/* 要求将数据拷贝出来 */if (apiflags & TCP_WRITE_FLAG_COPY) {/* 为数据申请内存空间 */if ((seg->p = pbuf_alloc(PBUF_TRANSPORT, seglen + optlen, PBUF_RAM)) == NULL) {goto memerr;}/* 更新发送缓冲区已用pbuf个数 */queuelen += pbuf_clen(seg->p);/* 将数据拷贝出来 */if (arg != NULL) {MEMCPY((char *)seg->p->payload + optlen, ptr, seglen);}seg->dataptr = seg->p->payload;}/* 不需要拷贝 */else {/* 为可选项字段申请空间 */if ((seg->p = pbuf_alloc(PBUF_TRANSPORT, optlen, PBUF_RAM)) == NULL) {goto memerr;}/* 更新发送缓冲区已用pbuf个数 */queuelen += pbuf_clen(seg->p);/* 为报文段申请pbuf空间 */if (left > 0) {if ((p = pbuf_alloc(PBUF_RAW, seglen, PBUF_ROM)) == NULL) {pbuf_free(seg->p);seg->p = NULL;goto memerr;}/* 更新发送缓冲区已用pbuf个数 */++queuelen;/* 设置数据指针 */p->payload = ptr;seg->dataptr = ptr;/* 将可选字段pbuf和数据pbuf拼接起来 */pbuf_cat(seg->p, p);p = NULL;}}/* 发送缓冲区pbuf超过规定上限 */if ((queuelen > TCP_SND_QUEUELEN) || (queuelen > TCP_SNDQUEUELEN_OVERFLOW)) {goto memerr;}/* 报文段长度 */seg->len = seglen;/* 向前调整出TCP首部 */if (pbuf_header(seg->p, TCP_HLEN)) {goto memerr;}/* TCP首部指针 */seg->tcphdr = seg->p->payload;/* 源端口号 */seg->tcphdr->src = htons(pcb->local_port);/* 目的端口号 */seg->tcphdr->dest = htons(pcb->remote_port);/* 序号 */seg->tcphdr->seqno = htonl(seqno);/* 紧急指针 */seg->tcphdr->urgp = 0;/* 标志位 */TCPH_FLAGS_SET(seg->tcphdr, flags);/* 报文段选项属性 */seg->flags = optflags;/* 首部长度 */TCPH_HDRLEN_SET(seg->tcphdr, (5 + optlen / 4));/* 更新剩余数据长度、序号、数据指针 */left -= seglen;seqno += seglen;ptr = (void *)((u8_t *)ptr + seglen);}/* 找出最后一个待发送报文段 */if (pcb->unsent == NULL) {useg = NULL;}else {for (useg = pcb->unsent; useg->next != NULL; useg = useg->next);}/* 待发送队列最后一个报文段和组建的第一个报文段能够合并(说明新组建的报文段只有一个) */if (useg != NULL && TCP_TCPLEN(useg) != 0 && !(TCPH_FLAGS(useg->tcphdr) & (TCP_SYN | TCP_FIN)) && (!(flags & (TCP_SYN | TCP_FIN)) || (flags == TCP_FIN)) && (useg->len + queue->len <= pcb->mss) && (useg->flags == queue->flags) && (ntohl(useg->tcphdr->seqno) + useg->len == ntohl(queue->tcphdr->seqno)) ) {/* 向后剥离TCP首部 */if(pbuf_header(queue->p, -(TCP_HLEN + optlen))) {goto memerr;}/* 第一个报文段没有数据,直接释放 */if (queue->p->len == 0) {struct pbuf *old_q = queue->p;queue->p = queue->p->next;old_q->next = NULL;queuelen--;pbuf_free(old_q);}/* 要求发送中断连接报文 */if (flags & TCP_FIN) {/* 设置中断连接标志位 */TCPH_SET_FLAG(useg->tcphdr, TCP_FIN);} /* 不要求发送中断连接报文 */else {/* 将两个报文段合并 */pbuf_cat(useg->p, queue->p);useg->len += queue->len;useg->next = queue->next;}if (seg == queue) {seg = useg;seglen = useg->len;}/* 释放报文段结构 */memp_free(MEMP_TCP_SEG, queue);}/* 待发送队列最后一个报文段不能和新组建的报文段合并 */else {/* 将报文段挂接到待发送链表尾部 */if (useg == NULL) {pcb->unsent = queue;}else {useg->next = queue;}}/* 同步报文或中止报文都占一个序号 */if ((flags & TCP_SYN) || (flags & TCP_FIN)) {++len;}if (flags & TCP_FIN) {pcb->flags |= TF_FIN;}pcb->snd_lbb += len;pcb->snd_buf -= len;/* 设置发送缓冲区已用pbuf个数 */pcb->snd_queuelen = queuelen;/* 要求推送报文 */if (seg != NULL && seglen > 0 && seg->tcphdr != NULL && ((apiflags & TCP_WRITE_FLAG_MORE)==0)) {TCPH_SET_FLAG(seg->tcphdr, TCP_PSH);}return ERR_OK; memerr:pcb->flags |= TF_NAGLEMEMERR;if (queue != NULL) {tcp_segs_free(queue);}return ERR_MEM; }/* 发送ACK报文段(不包含任何数据) */ err_t tcp_send_empty_ack(struct tcp_pcb *pcb) {struct pbuf *p;struct tcp_hdr *tcphdr;u8_t optlen = 0;/* 向前调整出TCP头部 */p = pbuf_alloc(PBUF_IP, TCP_HLEN + optlen, PBUF_RAM);if (p == NULL) {return ERR_BUF;}/* 清空延迟响应和立即响应标志位 */pcb->flags &= ~(TF_ACK_DELAY | TF_ACK_NOW);/* 设置TCP头部 */tcphdr = tcp_output_set_header(pcb, p, optlen, htonl(pcb->snd_nxt));/* 计算校验和 */tcphdr->chksum = inet_chksum_pseudo(p, &(pcb->local_ip), &(pcb->remote_ip), IP_PROTO_TCP, p->tot_len);/* 调用IP输出函数发送数据包 */ip_output(p, &(pcb->local_ip), &(pcb->remote_ip), pcb->ttl, pcb->tos,IP_PROTO_TCP);/* 释放报文段 */pbuf_free(p);return ERR_OK; }/* 输出报文段 */ err_t tcp_output(struct tcp_pcb *pcb) {struct tcp_seg *seg, *useg;u32_t wnd, snd_nxt;/* 当前控制块正有数据被处理,不输出直接返回 */if (tcp_input_pcb == pcb) {return ERR_OK;}/* 有效发送窗口值设置为发送窗口和阻塞窗口较小值 */wnd = LWIP_MIN(pcb->snd_wnd, pcb->cwnd);/* 获取第一个待发送报文段 */seg = pcb->unsent;/* 没有待发送报文段,但是要求发送响应 */if (pcb->flags & TF_ACK_NOW && (seg == NULL || ntohl(seg->tcphdr->seqno) - pcb->lastack + seg->len > wnd)) {/* 发送ACK报文段(不包含任何数据) */return tcp_send_empty_ack(pcb);}/* 找出最后一个待发送报文段 */useg = pcb->unacked;if (useg != NULL) {for (; useg->next != NULL; useg = useg->next);}/* 数据在有效发送窗口内 */while (seg != NULL && ntohl(seg->tcphdr->seqno) - pcb->lastack + seg->len <= wnd) {/* 使用nagle判断是否要推迟发送数据包,当产生内存不足或请求断开连接的数据报时算法失效 */if((tcp_do_output_nagle(pcb) == 0) && ((pcb->flags & (TF_NAGLEMEMERR | TF_FIN)) == 0)){break;}/* 从队列中删除该报文段 */pcb->unsent = seg->next;/* 设置ACK标志,清除延迟发送和立即发送标志位 */if (pcb->state != SYN_SENT) {TCPH_SET_FLAG(seg->tcphdr, TCP_ACK);pcb->flags &= ~(TF_ACK_DELAY | TF_ACK_NOW);}/* 发送该报文段 */tcp_output_segment(seg, pcb);/* 发送完有效数据之后,更新下一个将要发送的数据编号 */snd_nxt = ntohl(seg->tcphdr->seqno) + TCP_TCPLEN(seg);if (TCP_SEQ_LT(pcb->snd_nxt, snd_nxt)) {pcb->snd_nxt = snd_nxt;}/* 数据长度大于0,说明需要响应 */if (TCP_TCPLEN(seg) > 0) {/* 将报文段插入待确认队列 */seg->next = NULL;if (pcb->unacked == NULL) {pcb->unacked = seg;useg = seg;} else {if (TCP_SEQ_LT(ntohl(seg->tcphdr->seqno), ntohl(useg->tcphdr->seqno))){struct tcp_seg **cur_seg = &(pcb->unacked);while (*cur_seg && TCP_SEQ_LT(ntohl((*cur_seg)->tcphdr->seqno), ntohl(seg->tcphdr->seqno))) {cur_seg = &((*cur_seg)->next );}seg->next = (*cur_seg);(*cur_seg) = seg;} else {useg->next = seg;useg = useg->next;}}}/* 不需要响应,直接释放 */else {tcp_seg_free(seg);}seg = pcb->unsent;}/* 待发送队列不为空,并且发送窗口太小不足以发送下一个报文,并且坚持定时器未启动 */if (seg != NULL && pcb->persist_backoff == 0 && ntohl(seg->tcphdr->seqno) - pcb->lastack + seg->len > pcb->snd_wnd) {/* 启动坚持定时器 */pcb->persist_cnt = 0;pcb->persist_backoff = 1;}pcb->flags &= ~TF_NAGLEMEMERR;return ERR_OK; }/* 输出一个报文段 */ static void tcp_output_segment(struct tcp_seg *seg, struct tcp_pcb *pcb) {u16_t len;struct netif *netif;u32_t *opts;/* 确认号为下一个期望收到的数据编号 */seg->tcphdr->ackno = htonl(pcb->rcv_nxt);/* 通告窗口大小 */seg->tcphdr->wnd = htons(pcb->rcv_ann_wnd);/* 每次发送数据后,更新控制块接收窗口右边界 */pcb->rcv_ann_right_edge = pcb->rcv_nxt + pcb->rcv_ann_wnd;/* 可选字段,只支持MSS选项 */opts = (u32_t *)(seg->tcphdr + 1);if (seg->flags & TF_SEG_OPTS_MSS) {TCP_BUILD_MSS_OPTION(*opts);opts += 1;}/* 未明确本地IP的,通过路由算法明确本地IP */if (ip_addr_isany(&(pcb->local_ip))) {netif = ip_route(&(pcb->remote_ip));if (netif == NULL) {return;}ip_addr_set(&(pcb->local_ip), &(netif->ip_addr));}/* 如果该控制块的重传定时器是关闭的,则打开 */if(pcb->rtime == -1)pcb->rtime = 0; //定时器从0开始计数/* RTT估算定时器是关闭的 */if (pcb->rttest == 0) {pcb->rttest = tcp_ticks; //打开RTT估算,记录当前时间pcb->rtseq = ntohl(seg->tcphdr->seqno); //记录被估算的报文编号}/* 向前调整payload,包含TCP头部 */len = (u16_t)((u8_t *)seg->tcphdr - (u8_t *)seg->p->payload);seg->p->len -= len;seg->p->tot_len -= len;seg->p->payload = seg->tcphdr;/* 计算校验和 */seg->tcphdr->chksum = 0;seg->tcphdr->chksum = inet_chksum_pseudo(seg->p, &(pcb->local_ip), &(pcb->remote_ip), IP_PROTO_TCP, seg->p->tot_len);/* 调用IP数据包输出函数发送报文段 */ip_output(seg->p, &(pcb->local_ip), &(pcb->remote_ip), pcb->ttl, pcb->tos, IP_PROTO_TCP); }/* 发送TCP复位报文 */ void tcp_rst(u32_t seqno, u32_t ackno, struct ip_addr *local_ip, struct ip_addr *remote_ip, u16_t local_port, u16_t remote_port) {struct pbuf *p;struct tcp_hdr *tcphdr;/* 申请内存 */p = pbuf_alloc(PBUF_IP, TCP_HLEN, PBUF_RAM);if (p == NULL) {return;}/* 填写TCP首部 */tcphdr = p->payload;tcphdr->src = htons(local_port);tcphdr->dest = htons(remote_port);tcphdr->seqno = htonl(seqno);tcphdr->ackno = htonl(ackno);TCPH_FLAGS_SET(tcphdr, TCP_RST | TCP_ACK);tcphdr->wnd = htons(TCP_WND);tcphdr->urgp = 0;TCPH_HDRLEN_SET(tcphdr, 5);tcphdr->chksum = 0;tcphdr->chksum = inet_chksum_pseudo(p, local_ip, remote_ip, IP_PROTO_TCP, p->tot_len);/* 调用IP数据包输出函数发送报文段 */ip_output(p, local_ip, remote_ip, TCP_TTL, 0, IP_PROTO_TCP);/* 释放数据包 */pbuf_free(p); }/* 重传报文段 */ void tcp_rexmit_rto(struct tcp_pcb *pcb) {struct tcp_seg *seg;if (pcb->unacked == NULL) {return;}/* 将未确认数据包全部挂接到未发送数据包链表的头部 */for (seg = pcb->unacked; seg->next != NULL; seg = seg->next);seg->next = pcb->unsent;pcb->unsent = pcb->unacked;pcb->unacked = NULL;/* 重发次数加一 */++pcb->nrtx;/* 重发报文期间不进行RTT估算 */pcb->rttest = 0;/* 输出报文段 */tcp_output(pcb); }/* 重传第一个待确认报文段 */ void tcp_rexmit(struct tcp_pcb *pcb) {struct tcp_seg *seg;struct tcp_seg **cur_seg;/* 待确认报文不为空 */if (pcb->unacked == NULL) {return;}/* 将第一个待确认报文从链表中移除 */seg = pcb->unacked;pcb->unacked = seg->next;/* 将待确认报文按照数据编号从小到大插入待发送报文链表中 */cur_seg = &(pcb->unsent);while (*cur_seg && TCP_SEQ_LT(ntohl((*cur_seg)->tcphdr->seqno), ntohl(seg->tcphdr->seqno))) {cur_seg = &((*cur_seg)->next );}seg->next = *cur_seg;*cur_seg = seg;/* 重传次数加一 */++pcb->nrtx;/* 打开重传计时器 */pcb->rttest = 0; }/* 启动快速重传机制 */ void tcp_rexmit_fast(struct tcp_pcb *pcb) {/* 有待确认数据,且当前不处于快速重传模式 */if (pcb->unacked != NULL && !(pcb->flags & TF_INFR)) {/* 重传第一个待确认报文段 */tcp_rexmit(pcb);/* 进入快速重传模式,将拥塞避免启动门限设置为有效窗口的一半 */if (pcb->cwnd > pcb->snd_wnd)pcb->ssthresh = pcb->snd_wnd / 2;elsepcb->ssthresh = pcb->cwnd / 2;/* 拥塞避免启动门限不能小于2mss */if (pcb->ssthresh < 2*pcb->mss) {pcb->ssthresh = 2 * pcb->mss;}/* 执行慢启动算法,已经收到3个重复ACK */pcb->cwnd = pcb->ssthresh + 3 * pcb->mss;/* 设置快速重传模式标志位 */pcb->flags |= TF_INFR;} }/* 发送保活探测报文 */ void tcp_keepalive(struct tcp_pcb *pcb) {struct pbuf *p;struct tcp_hdr *tcphdr;/* 申请内存 */p = pbuf_alloc(PBUF_IP, TCP_HLEN, PBUF_RAM);if(p == NULL) {return;}/* 设置TCP报文头部(保活探测报文:序号为上一个报文序号减一) */tcphdr = tcp_output_set_header(pcb, p, 0, htonl(pcb->snd_nxt - 1));/* 检验和 */tcphdr->chksum = inet_chksum_pseudo(p, &pcb->local_ip, &pcb->remote_ip, IP_PROTO_TCP, p->tot_len);/* 调用IP数据包输出函数发送报文段 */ip_output(p, &pcb->local_ip, &pcb->remote_ip, pcb->ttl, 0, IP_PROTO_TCP);/* 释放数据包 */pbuf_free(p); }/* 发送保活零窗口探测报文 */ void tcp_zero_window_probe(struct tcp_pcb *pcb) {struct pbuf *p;struct tcp_hdr *tcphdr;struct tcp_seg *seg;u16_t len;u8_t is_fin;/* 没有待确认或待发送数据时,不发送零窗口探测 */seg = pcb->unacked;if(seg == NULL)seg = pcb->unsent;if(seg == NULL)return;/* TCP结束报文且不包含有效数据,则不需要进行探测,否则需要进行探测 */is_fin = ((TCPH_FLAGS(seg->tcphdr) & TCP_FIN) != 0) && (seg->len == 0);len = is_fin ? TCP_HLEN : TCP_HLEN + 1;/* 申请内存 */p = pbuf_alloc(PBUF_IP, len, PBUF_RAM);if(p == NULL) {return;}/* 设置TCP报文头部 */tcphdr = tcp_output_set_header(pcb, p, 0, seg->tcphdr->seqno);/* TCP结束报文,设置断开连接标志位 */if (is_fin) {TCPH_FLAGS_SET(tcphdr, TCP_ACK | TCP_FIN);}/* 零窗口探测报文,从报文段中拷贝一个字节进来 */else {*((char *)p->payload + sizeof(struct tcp_hdr)) = *(char *)seg->dataptr;}/* 计算校验和 */tcphdr->chksum = inet_chksum_pseudo(p, &pcb->local_ip, &pcb->remote_ip, IP_PROTO_TCP, p->tot_len);/* 调用IP数据包输出函数发送报文段 */ip_output(p, &pcb->local_ip, &pcb->remote_ip, pcb->ttl, 0, IP_PROTO_TCP);/* 释放数据包 */pbuf_free(p); }
输入处理
/* 当前报文段结构体 */ static struct tcp_seg inseg; /* 当前报文段TCP首部 */ static struct tcp_hdr *tcphdr; /* 当前报文段IP首部 */ static struct ip_hdr *iphdr; /* 当前报文段序号和确认序号 */ static u32_t seqno, ackno; /* 当前报文段标志位 */ static u8_t flags; /* 当前报文段TCP长度 */ static u16_t tcplen; /* 接收标志位,存放处理结果 */ static u8_t recv_flags; /* 已经接收的数据,用于递交给应用层 */ static struct pbuf *recv_data; /* 当前报文所属PCB */ struct tcp_pcb *tcp_input_pcb;/* 处理报文段 */ static err_t tcp_process(struct tcp_pcb *pcb); /* 接收函数处理数据 */ static void tcp_receive(struct tcp_pcb *pcb); /* 处理报文段中选项字段 */ static void tcp_parseopt(struct tcp_pcb *pcb); /* LISTEN控制块接收函数 */ static err_t tcp_listen_input(struct tcp_pcb_listen *pcb); /* TIME-WAIT控制块接收函数 */ static err_t tcp_timewait_input(struct tcp_pcb *pcb);/* TCP报文输入处理函数 */ void tcp_input(struct pbuf *p, struct netif *inp) {struct tcp_pcb *pcb, *prev;struct tcp_pcb_listen *lpcb;u8_t hdrlen;err_t err;/* IP首部指针 */iphdr = p->payload;/* TCP首部指针 */tcphdr = (struct tcp_hdr *)((u8_t *)p->payload + IPH_HL(iphdr) * 4);/* 向后剥离IP首部 */if (pbuf_header(p, -((s16_t)(IPH_HL(iphdr) * 4))) || (p->tot_len < sizeof(struct tcp_hdr))) {pbuf_free(p);return;}/* TCP不处理广播包和组播包 */if (ip_addr_isbroadcast(&(iphdr->dest), inp) || ip_addr_ismulticast(&(iphdr->dest))) {pbuf_free(p);return;}/* 验证TCP校验和 */if (inet_chksum_pseudo(p, (struct ip_addr *)&(iphdr->src), (struct ip_addr *)&(iphdr->dest), IP_PROTO_TCP, p->tot_len) != 0) {pbuf_free(p);return;}/* 向后调整剥离TCP首部 */hdrlen = TCPH_HDRLEN(tcphdr);if(pbuf_header(p, -(hdrlen * 4))){pbuf_free(p);return;}/* 将TCP首部各字段大端转小端 *//* 源端口号 */tcphdr->src = ntohs(tcphdr->src);/* 目的端口号 */tcphdr->dest = ntohs(tcphdr->dest);/* 数据编号 */seqno = tcphdr->seqno = ntohl(tcphdr->seqno);/* 确认号 */ackno = tcphdr->ackno = ntohl(tcphdr->ackno);/* 通告窗口大小 */tcphdr->wnd = ntohs(tcphdr->wnd);/* 标志位 */flags = TCPH_FLAGS(tcphdr);/* TCP数据长度 */tcplen = p->tot_len + ((flags & (TCP_FIN | TCP_SYN)) ? 1 : 0);/* 遍历活跃(正在交互)的TCP控制块链表 */prev = NULL;for(pcb = tcp_active_pcbs; pcb != NULL; pcb = pcb->next) {/* 匹配本地和远程端口号和IP地址,判断该报文段是不是发送给该控制块 */if (pcb->remote_port == tcphdr->src && pcb->local_port == tcphdr->dest && ip_addr_cmp(&(pcb->remote_ip), &(iphdr->src)) && ip_addr_cmp(&(pcb->local_ip), &(iphdr->dest))) {if (prev != NULL) {prev->next = pcb->next;pcb->next = tcp_active_pcbs;tcp_active_pcbs = pcb;}break;}prev = pcb;}/* 没有匹配到活跃(正在交互)的TCP控制块 */if (pcb == NULL) {/* 遍历等待2MSL状态的TCP控制块链表 */for(pcb = tcp_tw_pcbs; pcb != NULL; pcb = pcb->next) {/* 匹配本地和远程端口号和IP地址,判断该报文段是不是发送给该控制块 */if (pcb->remote_port == tcphdr->src && pcb->local_port == tcphdr->dest && ip_addr_cmp(&(pcb->remote_ip), &(iphdr->src)) && ip_addr_cmp(&(pcb->local_ip), &(iphdr->dest))) {/* 调用TIME-WAIT态接收函数 */tcp_timewait_input(pcb);pbuf_free(p);return;}}/* 遍历侦听状态的TCP控制块链表 */prev = NULL;for(lpcb = tcp_listen_pcbs.listen_pcbs; lpcb != NULL; lpcb = lpcb->next) {/* 匹配本地端口号和IP地址,判断该报文段是不是发送给该控制块 */if ((ip_addr_isany(&(lpcb->local_ip)) || ip_addr_cmp(&(lpcb->local_ip), &(iphdr->dest))) && lpcb->local_port == tcphdr->dest) {/* 将该控制块放到链表首部(最近可能要经常交互数据,可以增加效率) */if (prev != NULL) {((struct tcp_pcb_listen *)prev)->next = lpcb->next;lpcb->next = tcp_listen_pcbs.listen_pcbs;tcp_listen_pcbs.listen_pcbs = lpcb;}/* 调用LISTEN态接收函数 */tcp_listen_input(lpcb);pbuf_free(p);return;}prev = (struct tcp_pcb *)lpcb;}}/* 匹配到活跃(正在交互)的TCP控制块 */if (pcb != NULL) {/* 将数据组织成报文段 */inseg.next = NULL;inseg.len = p->tot_len;inseg.dataptr = p->payload;inseg.p = p;inseg.tcphdr = tcphdr;/* 清空当前报文段数据指针和接收处理标志 */recv_data = NULL;recv_flags = 0;/* 有数据已经接收但是未被应用层取用 */if (pcb->refused_data != NULL) {/* 调用接收回调函数 */TCP_EVENT_RECV(pcb, pcb->refused_data, ERR_OK, err);if (err == ERR_OK) {pcb->refused_data = NULL;}/* 应用程序依然没有取用,则释放当前接收到的数据包 */else {pbuf_free(p);return;}}/* 当前报文段所属PCB */tcp_input_pcb = pcb;/* 处理报文段 */err = tcp_process(pcb);if (err != ERR_ABRT) {/* 收到对方复位报文 */if (recv_flags & TF_RESET) {/* 调用连接错误回调函数 */TCP_EVENT_ERR(pcb->errf, pcb->callback_arg, ERR_RST);/* 将控制块从链表删除,释放所有缓冲区数据 */tcp_pcb_remove(&tcp_active_pcbs, pcb);/* 释放控制块 */memp_free(MEMP_TCP_PCB, pcb);} /* 连接成功断开 */else if (recv_flags & TF_CLOSED) {/* 将控制块从链表删除,释放所有缓冲区数据 */tcp_pcb_remove(&tcp_active_pcbs, pcb);/* 释放控制块 */memp_free(MEMP_TCP_PCB, pcb);} /* 有数据被确认,调用发送回调函数 */else {err = ERR_OK;if (pcb->acked > 0) {TCP_EVENT_SENT(pcb, pcb->acked, err);}/* 有数要推送,调用接收回调函数 */if (recv_data != NULL) {if(flags & TCP_PSH) {recv_data->flags |= PBUF_FLAG_PUSH;}TCP_EVENT_RECV(pcb, recv_data, ERR_OK, err);/* 上层没有读取数据,将数据挂接到refused_data队列 */if (err != ERR_OK) {pcb->refused_data = recv_data;}}/* 对方请求关闭,调用接收回调函数 */if (recv_flags & TF_GOT_FIN) {TCP_EVENT_RECV(pcb, NULL, ERR_OK, err);}tcp_input_pcb = NULL;/* 尝试输出 */tcp_output(pcb);}}tcp_input_pcb = NULL;/* 释放该报文段 */if (inseg.p != NULL){pbuf_free(inseg.p);inseg.p = NULL;}}/* 没有匹配到控制块 */else {/* 不是rst报文 */if (!(TCPH_FLAGS(tcphdr) & TCP_RST)) {/* 发送RST报文 */tcp_rst(ackno, seqno + tcplen, &(iphdr->dest), &(iphdr->src), tcphdr->dest, tcphdr->src);}/* 释放空间 */pbuf_free(p);} }/* LISTEN控制块接收函数 */ static err_t tcp_listen_input(struct tcp_pcb_listen *pcb) {struct tcp_pcb *npcb;err_t rc;/* 收到ACK报文 */if (flags & TCP_ACK) {/* 发送TCP复位报文 */tcp_rst(ackno + 1, seqno + tcplen, &(iphdr->dest), &(iphdr->src), tcphdr->dest, tcphdr->src);} /* 收到SYN报文 */else if (flags & TCP_SYN) {/* 分配一个TCP控制块 */npcb = tcp_alloc(pcb->prio);if (npcb == NULL) {return ERR_MEM;}/* 设置控制块的本地IP、本地端口号、远程IP、远程端口号 */ip_addr_set(&(npcb->local_ip), &(iphdr->dest));npcb->local_port = pcb->local_port;ip_addr_set(&(npcb->remote_ip), &(iphdr->src));npcb->remote_port = tcphdr->src;/* 设置控制块为SYN_RCVD态(服务器接受连接请求,发送同意请求并请求连接,等待对方同意请求) */npcb->state = SYN_RCVD;/* 下一个期望收到的序号(SYN+1) */npcb->rcv_nxt = seqno + 1;/* 接收窗口右边界值(SYN+ACK发送后会被更新) */npcb->rcv_ann_right_edge = npcb->rcv_nxt;/* 将发送窗口大小设置为对方通告窗口值 */npcb->snd_wnd = tcphdr->wnd;/* 将拥塞避免启动门限设置为对方通告窗口值 */npcb->ssthresh = npcb->snd_wnd;/* 上一次窗口更新时收到的序号(每次snd_wnd更新后) */npcb->snd_wl1 = seqno - 1;/* 用户数据指针 */npcb->callback_arg = pcb->callback_arg;/* 接受连接回调函数 */npcb->accept = pcb->accept;/* 套接字选项 */npcb->so_options = pcb->so_options & (SOF_DEBUG|SOF_DONTROUTE|SOF_KEEPALIVE|SOF_OOBINLINE|SOF_LINGER);/* 将控制块加入活跃(正在交互)的TCP控制块链表 */TCP_REG(&tcp_active_pcbs, npcb);/* 处理SYN中的选项字段 */tcp_parseopt(npcb);/* 根据IP地址所在网络接口最大允许数据包长度,计算TCP最大报文段长度 */npcb->mss = tcp_eff_send_mss(npcb->mss, &(npcb->remote_ip));/* 构建SYN|ACK报文 */rc = tcp_enqueue(npcb, NULL, 0, TCP_SYN | TCP_ACK, 0, TF_SEG_OPTS_MSS);if (rc != ERR_OK) {tcp_abandon(npcb, 0);return rc;}/* 发送出去 */return tcp_output(npcb);}return ERR_OK; }/* TIME-WAIT控制块接收函数 */ static err_t tcp_timewait_input(struct tcp_pcb *pcb) {/* 收到复位报文,不处理直接退出 */if (flags & TCP_RST) {return ERR_OK;}/* 收到同步报文 */if (flags & TCP_SYN) {/* 报文段在接收窗口内 */if (TCP_SEQ_BETWEEN(seqno, pcb->rcv_nxt, pcb->rcv_nxt+pcb->rcv_wnd)) {/* 发送TCP复位报文 */tcp_rst(ackno, seqno + tcplen, &(iphdr->dest), &(iphdr->src), tcphdr->dest, tcphdr->src);return ERR_OK;}} /* 收到断开连接报文 */else if (flags & TCP_FIN) {/* 重新进行2MSL计时 */pcb->tmr = tcp_ticks;}/* 收到有数据的报文或者FIN或者窗口之外的SYN,立即发送响应 */if ((tcplen > 0)) {pcb->flags |= TF_ACK_NOW;return tcp_output(pcb);}return ERR_OK; }/* 处理报文段 */ static err_t tcp_process(struct tcp_pcb *pcb) {struct tcp_seg *rseg;u8_t acceptable = 0;err_t err;err = ERR_OK;/* 报文段包含复位标志 */if (flags & TCP_RST) {/* 先确定是否允许处理RST报文 *//* SYN_SENT态(客户端请求连接,等待对方同意并请求连接) */if (pcb->state == SYN_SENT) {/* 确认序号等于下一个要发送的序号(即SYN+1) */if (ackno == pcb->snd_nxt) {/* 允许处理 */acceptable = 1;}}/* 其它状态 */else {/* 数据编号在接收窗口内 */if (TCP_SEQ_BETWEEN(seqno, pcb->rcv_nxt, pcb->rcv_nxt+pcb->rcv_wnd)) {/* 允许处理 */acceptable = 1;}}/* 允许处理复位报文 */if (acceptable) {/* 处理结果为对方请求复位 */recv_flags |= TF_RESET;pcb->flags &= ~TF_ACK_DELAY;return ERR_RST;} /* 不允许处理复位报文,直接退出 */else {return ERR_OK;}}/* 数据段包含同步标志,且当前不在同步握手阶段,说明可能是一个重复的同步报文 */if ((flags & TCP_SYN) && (pcb->state != SYN_SENT && pcb->state != SYN_RCVD)) { /* 立即响应 */tcp_ack_now(pcb);return ERR_OK;}/* 收到报文后,更新状态定时器 */pcb->tmr = tcp_ticks;/* 清空已发送的保活探测报文次数 */pcb->keep_cnt_sent = 0;/* 处理可选项 */tcp_parseopt(pcb);/* 判断PCB状态,做不同处理 */switch (pcb->state) {/* SYN_SEN态(客户端请求连接,等待对方同意并请求连接) */case SYN_SENT:/* SYN+ACK报文,且确认号为SYN+1,握手第2步完成 */if ((flags & TCP_ACK) && (flags & TCP_SYN) && ackno == ntohl(pcb->unacked->tcphdr->seqno) + 1) {/* SYN被确认,发送缓冲区长度加一 */pcb->snd_buf++;/* 收到对方同步报文后,初始化下一个期望接收到的序号 */pcb->rcv_nxt = seqno + 1;/* 接收窗口右边界值(ACK发送后会被更新) */pcb->rcv_ann_right_edge = pcb->rcv_nxt;/* 接收的最大确认号 */pcb->lastack = ackno;/* 将对方通告窗口大小设为发送窗口大小 */pcb->snd_wnd = tcphdr->wnd;/* 上一次窗口更新时收到的数据序号(每次变更snd_wnd时,记录一下) */pcb->snd_wl1 = seqno - 1;/* 进入ESTABLISHED态(对方同意连接请求,连接已建立) */pcb->state = ESTABLISHED;/* 根据IP地址所在网络接口最大允许数据包长度,重新计算TCP最大报文段长度 */pcb->mss = tcp_eff_send_mss(pcb->mss, &(pcb->remote_ip));/* 根据新的mss重新设置拥塞避免启动门限为10mss */pcb->ssthresh = pcb->mss * 10;/* 根据新的mss重新设置阻塞窗口为2mss */pcb->cwnd = ((pcb->cwnd == 1) ? (pcb->mss * 2) : pcb->mss);/* SYN被确认,pbuf个数减一 */--pcb->snd_queuelen;/* 从未确认队列中删除同步报文段(队列中第一个报文肯定是同步报文) */rseg = pcb->unacked;pcb->unacked = rseg->next;/* 未确认队列为空,停止重传定时器 */if(pcb->unacked == NULL)pcb->rtime = -1;/* 不为空,启动重传定时器 */else {pcb->rtime = 0;pcb->nrtx = 0;}/* 释放SYN报文段空间 */tcp_seg_free(rseg);/* 调用连接成功回调函数 */TCP_EVENT_CONNECTED(pcb, ERR_OK, err);/* 立即响应,三次握手结束 */tcp_ack_now(pcb);}/* ACK报文,没有SYN */else if (flags & TCP_ACK) {/* 直接发送复位报文 */tcp_rst(ackno, seqno + tcplen, &(iphdr->dest), &(iphdr->src), tcphdr->dest, tcphdr->src);}break;/* SYN_RCVD态(服务器接受连接请求,发送同意请求并请求连接,等待对方同意请求) */case SYN_RCVD:/* ACK报文 */if (flags & TCP_ACK) {/* 确认号在已发送的窗口内 */if (TCP_SEQ_BETWEEN(ackno, pcb->lastack+1, pcb->snd_nxt)) {u16_t old_cwnd;/* 进入ESTABLISHED态(对方同意连接请求,连接已建立) */pcb->state = ESTABLISHED;/* 调用接受连接回调函数 */TCP_EVENT_ACCEPT(pcb, ERR_OK, err);if (err != ERR_OK) {tcp_abort(pcb);return ERR_ABRT;}/* 记录老的阻塞窗口 */old_cwnd = pcb->cwnd;/* 调用函数处理报文中的数据 */tcp_receive(pcb);/* 确认号SYN占了一个编号,但是不占字节数,所以要调整之前计算出的成功发送的字节数 */if (pcb->acked != 0) {pcb->acked--;}/* 重新设置阻塞窗口 */pcb->cwnd = ((old_cwnd == 1) ? (pcb->mss * 2) : pcb->mss);/* 对方请求关闭连接 */if (recv_flags & TF_GOT_FIN) {/* 立即响应 */tcp_ack_now(pcb);/* 进入CLOSE_WAIT态(被动方收到断开请求,发送断开响应,等待应用程序关闭) */pcb->state = CLOSE_WAIT;}}/* 错误确认号 */else {/* 直接发送复位报文 */tcp_rst(ackno, seqno + tcplen, &(iphdr->dest), &(iphdr->src), tcphdr->dest, tcphdr->src);}}/* 收到对方重复SYN,说明对方没有收到SYN+ACK */else if ((flags & TCP_SYN) && (seqno == pcb->rcv_nxt - 1)) {/* 重传SYN+ACK */tcp_rexmit(pcb);}break;/* CLOSE_WAIT(被动方收到断开请求,发送断开响应,等待应用程序关闭)/ESTABLISHED(对方同意连接请求,连接已建立)态 */case CLOSE_WAIT:case ESTABLISHED:/* 调用函数处理报文中的数据 */tcp_receive(pcb);/* 对方请求断开连接 */if (recv_flags & TF_GOT_FIN) {/* 立即发送响应 */tcp_ack_now(pcb);/* 进入CLOSE_WAIT态(被动方收到断开请求,发送断开响应,等待应用程序关闭) */pcb->state = CLOSE_WAIT;}break;/* FIN_WAIT_1态(主动方请求断开,等待被动方断开响应) */case FIN_WAIT_1:/* 调用函数处理报文中的数据 */tcp_receive(pcb);/* 对方也请求断开连接 */if (recv_flags & TF_GOT_FIN) {/* 也收到对方响应,说明断开连接握手第3步已完成 */if ((flags & TCP_ACK) && (ackno == pcb->snd_nxt)) {/* 立即响应 */tcp_ack_now(pcb);/* 清空控制块上各种缓冲区数据,并将控制块设为CLOSE态 */tcp_pcb_purge(pcb);/* 将控制块从tcp_active_pcbs链表中移除 */TCP_RMV(&tcp_active_pcbs, pcb);/* 进入TIME_WAIT态(主动方收到断开请求,发送断开响应,等待2MSL) */pcb->state = TIME_WAIT;/* 将控制块插入tcp_tw_pcbs链表 */TCP_REG(&tcp_tw_pcbs, pcb);}/* 没有收到响应,说明双方同时执行关闭 */else {/* 立即响应 */tcp_ack_now(pcb);/* 进入CLOSING态(双方同时收到断开请求,发送断开响应,等待断开响应) */pcb->state = CLOSING;}}/* 收到ACK握手,说明断开连接握手第2步完成 */else if ((flags & TCP_ACK) && (ackno == pcb->snd_nxt)) {/* 进入FIN_WAIT_2态(主动方收到断开响应,等待被动方断开请求) */pcb->state = FIN_WAIT_2;}break;/* FIN_WAIT_2态(主动方收到断开响应,等待被动方断开请求) */case FIN_WAIT_2:/* 调用函数处理报文中的数据 */tcp_receive(pcb);/* 收到对方FIN握手,说明断开连接握手第3步已完成 */if (recv_flags & TF_GOT_FIN) {/* 立即响应 */tcp_ack_now(pcb);/* 清空控制块上各种缓冲区数据,并将控制块设为CLOSE态 */tcp_pcb_purge(pcb);/* 将控制块从tcp_active_pcbs链表中移除 */TCP_RMV(&tcp_active_pcbs, pcb);/* 进入TIME_WAIT态(主动方收到断开请求,发送断开响应,等待2MSL) */pcb->state = TIME_WAIT;/* 将控制块插入tcp_tw_pcbs链表 */TCP_REG(&tcp_tw_pcbs, pcb);}break;/* CLOSING态(双方同时收到断开请求,发送断开响应,等待断开响应) */case CLOSING:/* 调用函数处理报文中的数据 */tcp_receive(pcb);/* 收到ACK握手,断开连接 */if (flags & TCP_ACK && ackno == pcb->snd_nxt) {/* 清空控制块上各种缓冲区数据,并将控制块设为CLOSE态 */tcp_pcb_purge(pcb);/* 将控制块从tcp_active_pcbs链表中移除 */TCP_RMV(&tcp_active_pcbs, pcb);/* 进入TIME_WAIT态(主动方收到断开请求,发送断开响应,等待2MSL) */pcb->state = TIME_WAIT;/* 将控制块插入tcp_tw_pcbs链表 */TCP_REG(&tcp_tw_pcbs, pcb);}break;/* LAST_ACK态(被动方收到关闭指令,发送断开请求,等待主动方断开响应) */case LAST_ACK:/* 调用函数处理报文中的数据 */tcp_receive(pcb);/* 收到ACK握手 */if (flags & TCP_ACK && ackno == pcb->snd_nxt) {/* 连接关闭成功 */recv_flags |= TF_CLOSED;}break;default:break;}return ERR_OK; }/* 将报文段插入失序序列 */ static void tcp_oos_insert_segment(struct tcp_seg *cseg, struct tcp_seg *next) {struct tcp_seg *old_seg;if (TCPH_FLAGS(cseg->tcphdr) & TCP_FIN) {tcp_segs_free(next);next = NULL;}else {while (next && TCP_SEQ_GEQ((seqno + cseg->len), (next->tcphdr->seqno + next->len))) {if (TCPH_FLAGS(next->tcphdr) & TCP_FIN) {TCPH_FLAGS_SET(cseg->tcphdr, TCPH_FLAGS(cseg->tcphdr) | TCP_FIN);}old_seg = next;next = next->next;tcp_seg_free(old_seg);}if (next && TCP_SEQ_GT(seqno + cseg->len, next->tcphdr->seqno)) {cseg->len = (u16_t)(next->tcphdr->seqno - seqno);pbuf_realloc(cseg->p, cseg->len);}}cseg->next = next; }/* 接收函数处理数据 */ static void tcp_receive(struct tcp_pcb *pcb) {struct tcp_seg *next;struct tcp_seg *prev, *cseg;struct pbuf *p;s32_t off;s16_t m;u32_t right_wnd_edge;u16_t new_tot_len;int found_dupack = 0;/* 携带ACK字段 */if (flags & TCP_ACK) {/* 对方原接收窗口右边界 */right_wnd_edge = pcb->snd_wnd + pcb->snd_wl2;/* 对方窗口发生变化 */if (TCP_SEQ_LT(pcb->snd_wl1, seqno) || (pcb->snd_wl1 == seqno && TCP_SEQ_LT(pcb->snd_wl2, ackno)) || (pcb->snd_wl2 == ackno && tcphdr->wnd > pcb->snd_wnd)) {/* 更新发送窗口 */pcb->snd_wnd = tcphdr->wnd;pcb->snd_wl1 = seqno;pcb->snd_wl2 = ackno;/* 窗口通告非0,停止坚持定时器 */if (pcb->snd_wnd > 0 && pcb->persist_backoff > 0) {pcb->persist_backoff = 0;}}/* 报文段确认号在发送窗口前,可能是重复ACK */if (TCP_SEQ_LEQ(ackno, pcb->lastack)) {pcb->acked = 0;/* 报文长度为0,ACK报文不携带数据也不包含SYN、FIN */if (tcplen == 0) {/* 对方接收窗口右边界不变 */if (pcb->snd_wl2 + pcb->snd_wnd == right_wnd_edge){/* 仍有数据待确认 */if (pcb->rtime >= 0) {/* 确认号在发送窗口左边界 */if (pcb->lastack == ackno) {/* 发现重复确认 */found_dupack = 1;/* 重复确认次数加1 */if (pcb->dupacks + 1 > pcb->dupacks)++pcb->dupacks;/* 处于快速重传阶段 */if (pcb->dupacks > 3) {/* 快速重传阶段使用慢启动算法 */if ((u16_t)(pcb->cwnd + pcb->mss) > pcb->cwnd) {pcb->cwnd += pcb->mss;}} /* 收到3次重复确认,启动快速重传机制 */else if (pcb->dupacks == 3) {/* 启动快速重传机制 */tcp_rexmit_fast(pcb);}}}}}/* 不是重复ACK */if (!found_dupack) {pcb->dupacks = 0;}}/* 新的确认,确认编号在待确认窗口中 */else if (TCP_SEQ_BETWEEN(ackno, pcb->lastack+1, pcb->snd_nxt)){/* 控制块处于快重传阶段 */if (pcb->flags & TF_INFR) {/* 快速恢复,将阻塞窗口设置为拥塞避免启动门限大小 */pcb->flags &= ~TF_INFR;pcb->cwnd = pcb->ssthresh;}/* 重发次数清零 */pcb->nrtx = 0;/* 恢复RTO值 */pcb->rto = (pcb->sa >> 3) + pcb->sv;/* 计算上次成功发送的字节数 */pcb->acked = (u16_t)(ackno - pcb->lastack);/* 扩大发送缓冲区大小 */pcb->snd_buf += pcb->acked;/* 重复确认次数清零 */pcb->dupacks = 0;/* 更新最高确认号 */pcb->lastack = ackno;/* 控制块状态处于连接已经建立之后 */if (pcb->state >= ESTABLISHED) {/* 慢启动阶段 */if (pcb->cwnd < pcb->ssthresh) {/* 没收到一个响应增加一个mss */if ((u16_t)(pcb->cwnd + pcb->mss) > pcb->cwnd) {pcb->cwnd += pcb->mss;}}/* 拥塞避免阶段 */else {/* 没收到一个响应增加1/cwnd个mss */u16_t new_cwnd = (pcb->cwnd + pcb->mss * pcb->mss / pcb->cwnd);if (new_cwnd > pcb->cwnd) {pcb->cwnd = new_cwnd;}}}/* 移除待确认队列上已确认的报文段 */while (pcb->unacked != NULL && TCP_SEQ_LEQ(ntohl(pcb->unacked->tcphdr->seqno) + TCP_TCPLEN(pcb->unacked), ackno)) {next = pcb->unacked;pcb->unacked = pcb->unacked->next;/* 调整成功发送的字节数(因为FIN占一个字节) */if ((pcb->acked != 0) && ((TCPH_FLAGS(next->tcphdr) & TCP_FIN) != 0)) {pcb->acked--;}/* 调整发送缓冲区pbuf数 */pcb->snd_queuelen -= pbuf_clen(next->p);/* 释放已确认报文段空间 */tcp_seg_free(next);}/* 没有数据等待确认 */if(pcb->unacked == NULL)pcb->rtime = -1; //关闭重传定时器/* 依然有数据等待确认 */elsepcb->rtime = 0; //重置重传定时器pcb->polltmr = 0;}/* 确认不在窗口内 */else {pcb->acked = 0;}/* 有部分重发报文段被挂在待发送队列,确认号如果高于这些报文段序号,需要释放 */while (pcb->unsent != NULL && TCP_SEQ_BETWEEN(ackno, ntohl(pcb->unsent->tcphdr->seqno) + TCP_TCPLEN(pcb->unsent), pcb->snd_nxt)) {next = pcb->unsent;pcb->unsent = pcb->unsent->next;if ((pcb->acked != 0) && ((TCPH_FLAGS(next->tcphdr) & TCP_FIN) != 0)) {pcb->acked--;}pcb->snd_queuelen -= pbuf_clen(next->p);tcp_seg_free(next);}/* RTT估算正在进行且该报文段被确认 */if (pcb->rttest && TCP_SEQ_LT(pcb->rtseq, ackno)) {/* 计算rto值 */m = (s16_t)(tcp_ticks - pcb->rttest);m = m - (pcb->sa >> 3);pcb->sa += m;if (m < 0) {m = -m;}m = m - (pcb->sv >> 2);pcb->sv += m;pcb->rto = (pcb->sa >> 3) + pcb->sv;/* 停止RTT估算 */pcb->rttest = 0;}}/* 数据包包含数据或FIN、SYN */if (tcplen > 0) {/* 数据头部不在接收窗口左侧,但是部分数据在窗口内 */if (TCP_SEQ_BETWEEN(pcb->rcv_nxt, seqno + 1, seqno + tcplen - 1)){/* 在窗口内的数据偏移量 */off = pcb->rcv_nxt - seqno;p = inseg.p;/* 剥离前面不在窗口内的数据 */if (inseg.p->len < off) {new_tot_len = (u16_t)(inseg.p->tot_len - off);while (p->len < off) {off -= p->len;p->tot_len = new_tot_len;p->len = 0;p = p->next;}if(pbuf_header(p, (s16_t)-off)) {}} else {if(pbuf_header(inseg.p, (s16_t)-off)) {}}/* 更新报文段相关数据 */inseg.dataptr = p->payload;inseg.len -= (u16_t)(pcb->rcv_nxt - seqno);inseg.tcphdr->seqno = seqno = pcb->rcv_nxt;}/* 不是(数据头部不在接收窗口内,尾部在窗口内) */else {/* 收到重复数据,直接响应 */if (TCP_SEQ_LT(seqno, pcb->rcv_nxt)){tcp_ack_now(pcb);}}/* 数据头部在接收窗口内 */if (TCP_SEQ_BETWEEN(seqno, pcb->rcv_nxt, pcb->rcv_nxt + pcb->rcv_wnd - 1)){/* 刚好在起始处,说明这是连续报文 */if (pcb->rcv_nxt == seqno) {/* 当前报文段TCP长度 */tcplen = TCP_TCPLEN(&inseg);/* 数据尾部在窗口外 */if (tcplen > pcb->rcv_wnd) {/* 先清除FIN标志 */if (TCPH_FLAGS(inseg.tcphdr) & TCP_FIN) {TCPH_FLAGS_SET(inseg.tcphdr, TCPH_FLAGS(inseg.tcphdr) &~ TCP_FIN);}/* 剥离窗口外的尾部数据 */inseg.len = pcb->rcv_wnd;if (TCPH_FLAGS(inseg.tcphdr) & TCP_SYN) {inseg.len -= 1;}pbuf_realloc(inseg.p, inseg.len);tcplen = TCP_TCPLEN(&inseg);}/* 失序报文段不为空 */if (pcb->ooseq != NULL) {/* 收到FIN */if (TCPH_FLAGS(inseg.tcphdr) & TCP_FIN) {/* 释放所有失序报文段 */while (pcb->ooseq != NULL) {struct tcp_seg *old_ooseq = pcb->ooseq;pcb->ooseq = pcb->ooseq->next;tcp_seg_free(old_ooseq);} } /* 没收到FIN */else {struct tcp_seg *next = pcb->ooseq;struct tcp_seg *old_seg;/* 将报文段插入失序报文段 */while (next && TCP_SEQ_GEQ(seqno + tcplen, next->tcphdr->seqno + next->len)) {if (TCPH_FLAGS(next->tcphdr) & TCP_FIN && (TCPH_FLAGS(inseg.tcphdr) & TCP_SYN) == 0) {TCPH_FLAGS_SET(inseg.tcphdr, TCPH_FLAGS(inseg.tcphdr) | TCP_FIN);tcplen = TCP_TCPLEN(&inseg);}old_seg = next;next = next->next;tcp_seg_free(old_seg);}if (next && TCP_SEQ_GT(seqno + tcplen, next->tcphdr->seqno)) {inseg.len = (u16_t)(pcb->ooseq->tcphdr->seqno - seqno);if (TCPH_FLAGS(inseg.tcphdr) & TCP_SYN) {inseg.len -= 1;}pbuf_realloc(inseg.p, inseg.len);tcplen = TCP_TCPLEN(&inseg);}pcb->ooseq = next;}}/* 更新下一个期望接收序号 */pcb->rcv_nxt = seqno + tcplen;/* 更新接收窗口大小 */pcb->rcv_wnd -= tcplen;/* 更新通告窗口大小 */tcp_update_rcv_ann_wnd(pcb);/* 记录数据,用于提供给应用程序 */if (inseg.p->tot_len > 0) {recv_data = inseg.p;inseg.p = NULL;}/* 收到了FIN报文,需要发送响应包 */if (TCPH_FLAGS(inseg.tcphdr) & TCP_FIN) {recv_flags |= TF_GOT_FIN;}/* 将后续有序的报文段都挂接到recv_data上 */while (pcb->ooseq != NULL && pcb->ooseq->tcphdr->seqno == pcb->rcv_nxt) {cseg = pcb->ooseq;seqno = pcb->ooseq->tcphdr->seqno;pcb->rcv_nxt += TCP_TCPLEN(cseg);pcb->rcv_wnd -= TCP_TCPLEN(cseg);/* 更新通告窗口大小 */tcp_update_rcv_ann_wnd(pcb);if (cseg->p->tot_len > 0) {if (recv_data) {pbuf_cat(recv_data, cseg->p);} else {recv_data = cseg->p;}cseg->p = NULL;}/* 收到了FIN报文,需要发送响应包 */if (TCPH_FLAGS(cseg->tcphdr) & TCP_FIN) {recv_flags |= TF_GOT_FIN;/* 已经建立的连接要进入CLOSE_WAIT */if (pcb->state == ESTABLISHED) {pcb->state = CLOSE_WAIT;} }pcb->ooseq = cseg->next;tcp_seg_free(cseg);}/* 发送响应 */tcp_ack(pcb);}/* 头部不是窗口起始处,说明这是失序报文 */else {/* 发送ACK报文段(不包含任何数据) */tcp_send_empty_ack(pcb);/* 将报文段插入失序队列 */if (pcb->ooseq == NULL) {pcb->ooseq = tcp_seg_copy(&inseg);} else {prev = NULL;for(next = pcb->ooseq; next != NULL; next = next->next) {if (seqno == next->tcphdr->seqno) {if (inseg.len > next->len) {cseg = tcp_seg_copy(&inseg);if (cseg != NULL) {if (prev != NULL) {prev->next = cseg;} else {pcb->ooseq = cseg;}tcp_oos_insert_segment(cseg, next);}break;} else {break;}} else {if (prev == NULL) {if (TCP_SEQ_LT(seqno, next->tcphdr->seqno)) {cseg = tcp_seg_copy(&inseg);if (cseg != NULL) {pcb->ooseq = cseg;tcp_oos_insert_segment(cseg, next);}break;}} else {if (TCP_SEQ_BETWEEN(seqno, prev->tcphdr->seqno+1, next->tcphdr->seqno-1)) {cseg = tcp_seg_copy(&inseg);if (cseg != NULL) {if (TCP_SEQ_GT(prev->tcphdr->seqno + prev->len, seqno)) {prev->len = (u16_t)(seqno - prev->tcphdr->seqno);pbuf_realloc(prev->p, prev->len);}prev->next = cseg;tcp_oos_insert_segment(cseg, next);}break;}}if (next->next == NULL && TCP_SEQ_GT(seqno, next->tcphdr->seqno)) {if (TCPH_FLAGS(next->tcphdr) & TCP_FIN) {break;}next->next = tcp_seg_copy(&inseg);if (next->next != NULL) {if (TCP_SEQ_GT(next->tcphdr->seqno + next->len, seqno)) {next->len = (u16_t)(seqno - next->tcphdr->seqno);pbuf_realloc(next->p, next->len);}}break;}}prev = next;}}}}/* 数据头部不在接收窗口内 */else {/* 不在窗口内的报文段 */tcp_send_empty_ack(pcb);}}/* 数据包不包含数据 */else {/* 不在窗口内的报文 */if(!TCP_SEQ_BETWEEN(seqno, pcb->rcv_nxt, pcb->rcv_nxt + pcb->rcv_wnd-1)){/* 返回确认 */tcp_ack_now(pcb);}} }/* 处理报文段中选项字段 */ static void tcp_parseopt(struct tcp_pcb *pcb) {u16_t c, max_c;u16_t mss;u8_t *opts, opt;/* 选项字段指针 */opts = (u8_t *)tcphdr + TCP_HLEN;/* 首部长度大于20字节 */if(TCPH_HDRLEN(tcphdr) > 0x5) {/* 选项字段长度(单位字节) */max_c = (TCPH_HDRLEN(tcphdr) - 5) << 2;/* 一个一个选项进行解析 */for (c = 0; c < max_c; ) {opt = opts[c];switch (opt) {/* 结束选项字段 */case 0x00:return;/* 空操作 */case 0x01:++c;break;/* 最大报文段大小 */case 0x02:if (opts[c + 1] != 0x04 || c + 0x04 > max_c) {return;}mss = (opts[c + 2] << 8) | opts[c + 3];pcb->mss = ((mss > TCP_MSS) || (mss == 0)) ? TCP_MSS : mss;c += 0x04;break;/* 不支持其它选项 */default:if (opts[c + 1] == 0) {return;}/* 跳过选项长度 */c += opts[c + 1];}}} }
总结
以上是生活随笔为你收集整理的LWIP之TCP协议的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇: 拼智商,谷歌、苹果、微软、亚马逊的AI助
- 下一篇: ACM公布了2017年图灵奖得主:荣誉属