TCP/IP-协议详解

本篇博文主要介绍TCP/IP协议特性。

OSI模型

TCP/IP协议族常用的应用层协议包括:

  • HTTP(Hyper Text Transfer Protocol):超文本传输协议,<客户端-服务端>形式。属于单向同步协议,耗费资源多,不适合实时控制场合。
  • FTP(File Transfer Protocl):文件传输协议,多个设备间共享文件,如文件上传/下载、目录操作、权限设置等。
  • MQTT(Messege Queue Telemerty Transport):消息队列遥测传输,用于推送消息,实时聊天等。

按照OSI模型,TCP/IP协议被分为7层:物理层、网络层、链路层、传输层、会话层、表示层。但实际使用时常用5层表示,由下至上分别为:

  • 物理层: PHY
  • 链路层: MAC
  • 网络层: IP、ICMP、ARP
  • 传输层: TCP、UDP
  • 应用层: DNS、HTTP、FTP、SMTP

TCP协议特性

TCP是采用数据流形式传输的全双工通信方式,传输过程中,发送方将数据起始编号与长度放在TCP报文中,接收方将所有数据按编号拼接后返回确认,传输双方的数据标号互相独立。

连接、确认与重传

TCP是面向连接的协议,传输前必须有双方IP地址与端口号进行连接验证。传输中接收方在收到数据后必须返回确认结果,否则超时未确认后发送方认为发送失败,进行数据重传。由于IP层是无连接的。数据和确认都可能丢失,因此发送端在超时还未收到确认时进行重传。

类似于IIC或者CAN协议穿插在数据流中的应答位,TCP为附在其他报文中的确认机制

TCP协议连接包括传输层与应用层的连接,每个主机有IP的主机可以提供不同Web、FTP、SMTP等不同线程,通过不同端口来实现线程区分。

类似RTOS中多个任务有各自独特的TCB

  • 20/21:FTP,文件传输协议
  • 23:Telnet,远程登录终端协议
  • 25:SMTP,简单邮件传输协议
  • 69:TFTP,普通文件传输协议
  • 80:HTTP,超文本传输协议
  • 110:POP3,邮局协议版本3

三次握手

两个主机连接以后,由客户端A建立连接。A首先向B发送空包,通过3个包判断A、B两端的seqno和ackno序号是否一致判断是否连接成功。过程如下:

  1. A向B发序号,B收到并将其+1作为应答序号,回送给A;

    ====> B知晓A->B连通

  2. A判断B发送的应答序号,同时将B的序号+1作为应答序号,回送给B;

    A的seqno = B的ackno ====> A知晓A->B、B->A连通

  3. B收到A的应答序号;

    A的ackno = B的seqno ====> B知晓B->A联通

同时引入SYN同步头标志与ACK应答标志,附加自己窗口大小(用于流量控制),得流程简图如下:

image can't load. TCP三次握手
实际抓包结果如下:
image can't load. Wireshrak三次握手抓包
  1. PC->ZYNQ: SYN = 1,发起同步;seqno = ..0698
  2. ZYNQ->PC: SYN = 1,发起同步;ACK = 1,应答上一报文; seqno = 16326,ackno = seqno + 1 = ..00699;
  3. PC->ZYNQ: SYN = 0,同步结束;ACK = 1,应答上一报文; seqno = ..00699,ackno = seqno + 1 = 16327;

执行无误后即同步完成,双方建立连接。

### 四次挥手

通常来说,TCP协议断开时是四次挥手,分别断开两个主机间的两条信号通路。大致过程如下:

  • C向D发送断开连接报文,D接收并应答

    ====> D端知晓C端主动断开发送的请求

  • C收到D端应答

    ====> 断开C->D的报文发送流向

  • D向C发送断开连接报文,C接收并应答

    ====> C端知晓D端主动断开发送的请求

  • D收到C端应答

    ====> 断开D->C的报文流向

类似SYN同步头,引入FIN标志作为中止连接表示。

image can't load. TCP四次挥手

为什么是四次挥手?

下方小节提到,TCP通信双方均会设置一个缓存区,例如本例中PC端为65535、ZYNQ端为2048。有可能ZYNQ在主动断开连接后PC端的数据尚未完全发送,仍然有PC->ZYNQ方向的报文帧,ZYNQ停止发送但仍保持接收。直到PC端数据发送完成,PC主动断开连接,ZYNQ发送应答。共计4次。

三次挥手?TCP延时应答

在实际测试过程中PC端断开TCP连接只发现3次挥手?似乎与常说的4次不符合,如下图。

image can't load. TCP三次挥手

查阅TCP协议标准参考文档RFC 793第3.5节有关Close Connection的说明,其介绍了3种情况:

There are essentially three cases: 1) The user initiates by telling the TCP to CLOSE the connection 2) The remote TCP initiates by sending a FIN control signal 3) Both users CLOSE simultaneously

无论是单端关闭TCP还是双端同时关闭,均与实测结果不同。但网络上能查找到类似现象的解析,例如参考资料5中对Linux下的源码进行分析,其中提到了延时应答机制。所以LwIP协议栈应当有类似的措施或解决办法,查阅参考资料6中对LwIP的源码分析。

找到tcp.c文件中的tcp_fasttmr()函数:该函数用于每250ms处理被应用层拒绝的数据并且发送延时应答。

Is called every TCP_FAST_INTERVAL (250 ms) and process data previously “refused” by upper layer (application) and sends delayed ACKs.

其中有以下代码段:

1
2
3
4
5
6
7
if (pcb->flags & TF_ACK_DELAY) //到达延迟时间
{
LWIP_DEBUGF(TCP_DEBUG, ("tcp_fasttmr: delayed ACK\n"));
tcp_ack_now(pcb);
tcp_output(pcb);
pcb->flags &= ~(TF_ACK_DELAY | TF_ACK_NOW); //ACK复位
}

即在每250ms时刻调用tcp_ack_now(pcb)函数使能立即发送延时ACK,并使用对外输出函数tcp_output()向外输出。函数tcp_output()中定义了延时ACK的处理方式:

  • 如果TF_ACK_NOW使能且没有数据待发送,则组一个空的ACK报文立即发送
1
2
3
4
5
6
7
8
9
10
11
/* If the TF_ACK_NOW flag is set and no data will be sent (either
* because the ->unsent queue is empty or because the window does
* not allow it), construct an empty ACK segment and send it.
*
* If data is to be sent, we will just piggyback the ACK (see below).
*/
if (pcb->flags & TF_ACK_NOW &&
(seg == NULL ||
lwip_ntohl(seg->tcphdr->seqno) - pcb->lastack + seg->len > wnd)) {
return tcp_send_empty_ack(pcb);
}

防御性设计

  • 缓存

    由于发送方的数据大小、类型不定,TCP提供缓存机制处理数据。数据量过小时,TCP将数据存储在缓存中,等到数据量够大时发送,待接收方应答后再删除(便于无应答时重传)

    ​ 类似于CAN短帧以8字节为单位发送

  • 流量控制

    两种控制方式本质是希望匹配收发双方和中间路由的传输速度。TCP 提供了流量控制服务(flow-control service)以消除发送方使接收方缓冲区溢出的可能性。

    流量控制是速度匹配,用于匹配收发双方读写速率。TCP 通过让发送方维护一个称为接收窗口(receive window)的变量来提供流量控制,用于给发送方指示:接收方还能接收多少数据,接收方会将此窗口值放在TCP 报文的首部中的窗口字段传递给发送方,窗口大小是在发送数据时动态调整的。

    如果接收方窗口为0,则发送方发送只有1个字节的报文段,直到缓存清空并在确认报文中包含一个非0的接收窗口值。

    在确定接收方窗口值之后,发送方可动态调整窗口大小对外发送数据。

    滑动窗口思路

  • 拥塞控制

    如果两个千兆主机之间经过百兆路由,即便双方主机均有能力处理500Mbps的数据,但仍收到路由的限制,因此发送方需要实现自适应机制,对发送方成为拥塞机制。流量控制限制单次通信数据量,拥塞控制协调设备间速率。

  • 差错控制

    或者说是校验机制。TCP协议采用校验和进行校验,主机接收时丢弃重复报文、重组乱序报文、请求重发丢失报文。

TCP报文结构

TCP是基于字节流方式的传输,各个协议层之间数据传输均会增删各层的数据头:

image can't load. TCP/IP协议栈报文封装

在LwIP协议栈中lwip-2.0.2/src/include/lwip/port/tcp.h中,tcp_hdr定义TCP报文帧的组成:

1
2
3
4
5
6
7
8
9
10
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报文结构对应:

image can't load. TCP报文
  • src、dst:源端、目标端的端口
  • seqno:发送端起始字节位置码,方便对数据进行管理
  • ackno:上次已成功收到数据的最后一个字节序号+1,ACK=1时有效。ackno通常会封装在反向数据报文中。
  • wnd:窗口大小。起始端配置seqno确定发送字节数,实现流量控制。接收方返回wnd=0时,发送端停止发送。
  • chksum:校验值。覆盖TCP所有数据。
  • urgp:紧急指针。相对于seqno的正偏移量,表示前urgp个数据为紧急数据。

标志位说明:

  • hdrlen:首部字节数,4bit,单位为字。最大60Byte。

  • rsvd:保留值

  • URG:urgp使能

  • ACK:ackno使能

  • PSH:push使能。=1时尽快将报文端推送给应用层

  • RST:复位tcp连接

  • SYN:首次建立新连接时,SYN=1,序号字段包含主机随机的初始序号ISN。该主机发送数据的第一个字节序号为ISN+1。

  • FIN:中止连接

与CAN类似,源节点+目标节点+应答+数据+校验,但多了窗口值、序号、应答序号、紧急指针。CAN可以8Byte为单帧进行收发。

PC与ZYNQ建立连接时首次的“握手”的帧为例:

image can't load. PC建立连接
  • src: 61816
  • dst: 7
  • seqno: ..1677
  • ackno: 0
  • hdrlen: 32
  • SYN: 1
  • wnd: 65535
  • chksum: 0x7407
  • urgp: 0

TCP连接状态

LwIP协议中,lwip-2.0.2/src/core/tcp.c文件内定义了TCP协议中11种状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* 限制初始报文发送段最大长度为536B,后续可在SYN连接里动态修改. */
#if TCP_MSS > 536
#define INITIAL_MSS 536
#else
#define INITIAL_MSS TCP_MSS
#endif
/* 11种连接状态 */
static const char * const tcp_state_str[] = {
"CLOSED", //关闭状态-无连接
"LISTEN", //监听态
"SYN_SENT", //发起SYN
"SYN_RCVD", //收到SYN
"ESTABLISHED", //稳定连接
"FIN_WAIT_1", //单向终止
"FIN_WAIT_2", //对方应答终止
"CLOSE_WAIT", //等待终止
"CLOSING", //两端同时关闭
"LAST_ACK", //服务器等待对方关闭
"TIME_WAIT" //关闭成功-2MSL等待状态
};

由于TCP为全双工通信,客户端与服务端的行为基本一致,通过以下流程图介绍客户端与服务器的状态变迁,其中,蓝色实线与红色实线是序号对应,可以组成一次完整的连接过程。红色虚线则表示特殊情况,即客户端与服务器同时发送SYN,使得客户端进入TCP_RSVD状态,如果此时: + 收到ACK,进入ESTABLISHED + 收到RST,恢复LISTEN + 内存不足导致客户端进程结束,或服务端ACK应答超时,进入FIN_WAIT_1

image can't load. TCP状态切换

TCP数据结构

类比FreeRTOS任务的任务控制块TCB,LwIP中有同样的协议控制块PCB:

IP块:

1
2
3
4
5
6
7
8
9
10
11
12
#define IP_PCB \
/* ip addresses in network byte order */ \
ip_addr_t local_ip; \
ip_addr_t remote_ip; \
/* Socket options */ \
u8_t so_options; \
/* Type Of Service */ \
u8_t tos; \
/* Time To Live */ \
u8_t ttl \
/* link layer address resolution hint */ \
IP_PCB_ADDRHINT

本地端口:

1
2
3
4
5
6
7
8
/* members common to struct tcp_pcb and struct tcp_listen_pcb */
#define TCP_PCB_COMMON(type) \
type *next; /* for the linked list */ \
void *callback_arg; \
enum tcp_state state; /* TCP state */ \
u8_t prio; \
/* ports are in host byte order */ \
u16_t local_port

TCP协议控制块定义(部分):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/** TCP protocol control block */
struct tcp_pcb {
/** 通用PCB成员 */
IP_PCB;
/** 特定PCB成员 */
TCP_PCB_COMMON(struct tcp_pcb);

/* 服务端口号 */
u16_t remote_port;
...
/* the rest of the fields are in host byte order
as we have to do some math with them */
/* Timers */
u8_t polltmr, pollinterval;
u8_t last_timer; /* 控制块最后一次被处理的时间 */
u32_t tmr;
/* receiver variables */
u32_t rcv_nxt; /* 下一次期望收到的seqno */
tcpwnd_size_t rcv_wnd; /* 接收窗口大小 */
tcpwnd_size_t rcv_ann_wnd; /* 告知对方的接收窗大小 */
u32_t rcv_ann_right_edge; /* 通知窗口右边缘 */

/* Retransmission timer. */
s16_t rtime;
u16_t mss; /* maximum segment size */
....
/* fast retransmit/recovery */
u8_t dupacks; /* 快速重传 */
u32_t lastack;/* 最近一次确认序号 */ /* Highest acknowledged seqno. */

/* congestion avoidance/control variables */
/* 避免拥塞/流量控制 */
tcpwnd_size_t cwnd; /* 连接当前的窗口大小*/
tcpwnd_size_t ssthresh; /* 拥塞避免算法启动的阈值 */

/* sender variables */ /* 发送方变量 */
u32_t snd_nxt; /* next new seqno to be sent */
u32_t snd_wl1, snd_wl2; /* Sequence and acknowledgement numbers of last
window update. */
u32_t snd_lbb; /* Sequence number of next byte to be buffered. */
tcpwnd_size_t snd_wnd; /* sender window */
tcpwnd_size_t snd_wnd_max; /* the maximum sender window announced by the remote host */
...
/* 保持空闲的控制变量 */
/* idle time before KEEPALIVE is sent */
u32_t keep_idle;
#if LWIP_TCP_KEEPALIVE
u32_t keep_intvl;
u32_t keep_cnt;
#endif /* LWIP_TCP_KEEPALIVE */

/* Persist timer counter */
u8_t persist_cnt;
/* Persist timer back-off */
u8_t persist_backoff;

/* KEEPALIVE counter */ /* 保活报文的发送次数 */
u8_t keep_cnt_sent;

#if LWIP_WND_SCALE
u8_t snd_scale;
u8_t rcv_scale;
#endif
};

在两个ip的设备建立连接前,设备处于LISTEN监听态而非ESTABLISHED连接态,LwIP为节省资源定义了tcp_pcb_listen控制块,其中仅保留通用pcb块,在建立连接后将tcp_pcb_listen接入tcp_pcb即可。tcp_pcb_listen块定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/** the TCP protocol control block for listening pcbs */
struct tcp_pcb_listen {
/** Common members of all PCB types */
IP_PCB;
/** Protocol specific PCB members */
TCP_PCB_COMMON(struct tcp_pcb_listen);

#if LWIP_CALLBACK_API
/* Function to call when a listener has been connected. */
tcp_accept_fn accept;
#endif /* LWIP_CALLBACK_API */

#if TCP_LISTEN_BACKLOG
u8_t backlog;
u8_t accepts_pending;
#endif /* TCP_LISTEN_BACKLOG */
};

LwIP中管理各个PCB的链表如下:

1
2
3
4
5
6
7
8
9
10
/* The TCP PCB lists. */
/** List of all TCP PCBs bound but not yet (connected || listening) */
struct tcp_pcb *tcp_bound_pcbs;
/** List of all TCP PCBs in LISTEN state */
union tcp_listen_pcbs_t tcp_listen_pcbs;
/** List of all TCP PCBs that are in a state in which
* they accept or send data. */
struct tcp_pcb *tcp_active_pcbs;
/** List of all TCP PCBs in TIME-WAIT state */
struct tcp_pcb *tcp_tw_pcbs;
  • 无连接时,tcp_bound_pcbs管理;
  • 处于监听态时,tcp_listen_pcbs管理;
  • 其他状态,tcp_active_pcbs管理;
  • Time-Wait态,tcp_tw_pcbs管理,主动关闭连接后进入;

同样类比FreeRTOS任务管理,阻塞态、就绪态、运行态、挂起态分别使用不同的链表控制,基本能按顺序一一对应。

TCP滑动窗口

TCP是基于字节流的传输方式,每字节都有对应的seq号,窗口则是能发送/接收的segment字段。包括发送方窗口、接收方窗口,目的是利用收发双方的字节序号进行缓存区数据收发的流量控制与重传,设计上是一个滑动窗口。

接收窗口

image can't load. TCP接收窗

LwIP中关于接收窗的几个变量:

  • rcv_wnd:received window,接收窗大小
  • rcv_ann_wnd:received annonce window,通知发送方接收窗大小
  • rcv_nxt:下次期望收到的seqno
  • rcv_ann_right_edge:通知发送方的窗口右边界

收到对端数据后,接收窗口会减小;应用层读走数据后接收窗口会增加。但是窗口的每次增减并不总是会告知对方。

发送窗口

image can't load. TCP发送窗

LwIP中关于发送窗的几个变量:

  • lastack:接收方应答回来的seq序号
  • snd_wnd:发送方窗口大小
  • snd_nxt:待发送的下一字节的seq
  • snd_lbb:load byte to buffer,装载到缓冲区的下一字节seq

发送窗口会存在“飞行数据”的情况,即发送端已发送,但未收到确认/应答。此时发送窗并不会做出其他操作,直到收到接收方的ackno,随后更新lastack、snd_wnd、snd_nxt、snd_lbb等变量。

糊涂窗口?

TCP的窗口采用动态刷新的滑动窗口机制,变量的更改不仅与己方有关,也需要对端的ackno。那么假设一种可能:

  1. rcv端窗口接收后应用层一直未读取,直到rcv_wnd与rcv_ann_wnd缩小至20B
  2. snd端收到rcv应答,snd_wnd设为20B,随后发送20B报文
  3. rcv端应用层收到20B,rcv_wnd变为0,向snd应答
  4. rcv端应用层读取20B,随后重复1-4。

简单说,当rcv端通告小窗,snd端立即填充小窗,应用层继续读取少量数据,则会出现糊涂窗口综合征SWS (Silly WindowSyndrome) 。这种状况将导致大量TCP报文帧只有少量(20B)有效数据,而大量的小包会降低网络利用率甚至造成网络拥塞,降低网络性能。

产生原因有两个:

  • 接收端通告较小的rcv_wnd
  • 发送端收到较小的rcv_wnd时,立即响应发出对应小包。

按照RFC1122协议,避免SWS的措施:

  • 对于接收端要避免通告小窗口

    避免以小的增量来推进接收窗的右边沿(rcv_nxt+rcv_wnd),即使接收的数据包都是小包。只有当接收窗口的增量大于min( Fr * rcv_buff, snd_mss )的时候才通告新的窗口。其中Fr是一个分数因子,协议建议值为1/2,snd_mss为对端的发送最大段长。

  • 对于发送端来说在不超出对端接收窗口的前提下至少满足下列三个条件中的一个才能发送数据:

    1. 一个full-sized的数据包(即大小满足snd_mss)可以被发送;
    2. 数据包的大小超过对端曾经通告过的rcv_ann_wnd的一半;
    3. TCP发送端禁用了Nagle算法;
    4. 所有发出的数据都已经被对端ACK确认;

TCP报文段

报文段缓冲队列

tcp_pcb块维护发送/接收端的缓冲区队列指针,定义tcp_seg管理所有未发送、已发送未确认、或已收到的无序报文。

tcp_seg的结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* This structure represents a TCP segment on the unsent, unacked and ooseq queues */
struct tcp_seg {
struct tcp_seg *next; /* 链向下一个报文段 */
struct pbuf *p; /* 报文段pbuf - buffer containing data + TCP header */
u16_t len; /* the TCP length of this segment */
#if TCP_OVERSIZE_DBGCHECK
u16_t oversize_left; /* 当前 未发送数据队列(unsent) 中最后一个 pbuf 的 超出MSS(最大报文段大小)的字节数。 */

#endif /* TCP_OVERSIZE_DBGCHECK */
#if TCP_CHECKSUM_ON_COPY
u16_t chksum;
u8_t chksum_swapped;
#endif /* TCP_CHECKSUM_ON_COPY */
u8_t flags; /* 报文段标志属性 */
#define TF_SEG_OPTS_MSS (u8_t)0x01U /* Include MSS option. */
#define TF_SEG_OPTS_TS (u8_t)0x02U /* Include timestamp option. */
#define TF_SEG_DATA_CHECKSUMMED (u8_t)0x04U /* ALL data (not the header) is
checksummed into 'chksum' */
#define TF_SEG_OPTS_WND_SCALE (u8_t)0x08U /* Include WND SCALE option */
struct tcp_hdr *tcphdr; /* the TCP header */
};

tcp_pcb块中需要维护三个指针,在其定义里有:

  • unsent:未发送的报文段缓冲队列
  • unacked:已发送但未确认的缓冲队列
  • ooseq:已收到的无序报文队列

可以这么理解,最顶层的tcp_active_pcb变量管理CLOSED、LISTEN、Time-Wait以外状态的tcp_pcb块变量。各个tcp_pcb块包含不同的ip和port,管理各自的unsent、unacked、ooseq链表,每个链表又包含多个不同的tcp_seg报文段,同样用链表管理。

image can't load. TCP缓存队列

报文段发送

若使用NETCONN API编程,当数据到达传输层后,会调用lwip_netconn_do_writemore()函数对发送数据,处理TCP报文段缓存操作是在tcp_write()函数中。LwIP在将数据写入缓冲区,利用Nagle算法进行发送,最后调用tcp_output() 函数将数据传输至IP层,tcp_out源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
err_t
tcp_output(struct tcp_pcb *pcb)
{
struct tcp_seg *seg, *useg;
u32_t wnd, snd_nxt;
err_t err;
struct netif *netif;
#if TCP_CWND_DEBUG
s16_t i = 0;
#endif /* TCP_CWND_DEBUG */

/* pcb->state LISTEN not allowed here */
LWIP_ASSERT("don't call tcp_output for listen-pcbs",
pcb->state != LISTEN);

/* 当前控制块正在处理输入数据,则退出 */
if (tcp_input_pcb == pcb) {
return ERR_OK;
}
/* 获取发送窗snd_wnd与流量/拥塞控制窗cwnd中较小的 */
wnd = LWIP_MIN(pcb->snd_wnd, pcb->cwnd);
/* 报文段切换为pcb中未发送的unsent链表 */
seg = pcb->unsent;

/* flag中ACK被置位为立即发送,但是
* 1)无数据待发
* 2)本次发送的报文段占据的序列号空间seqno-lastack +len大于接收方窗口大小wnd,接收方没有能力接收
* 立即发送一个空包ACK
*/
if (pcb->flags & TF_ACK_NOW &&
(seg == NULL ||
lwip_ntohl(seg->tcphdr->seqno) - pcb->lastack + seg->len > wnd)) {
return tcp_send_empty_ack(pcb);
}

/* useg指向最近一次发送但未应答队列unacked报文段,便于超时重传 */
useg = pcb->unacked;
if (useg != NULL) {
for (; useg->next != NULL; useg = useg->next);
}
/* 底层调用ip4_route函数进行路由查找,检查网口是否可用(UP+链路+IP),匹配子网或路由 */
netif = ip_route(&pcb->local_ip, &pcb->remote_ip);
if (netif == NULL) {
return ERR_RTE;
}

/* 若pcb未绑定本地ip,调用ip_netif_get_local_ip从netif中获取ip */
if (ip_addr_isany(&pcb->local_ip)) {
const ip_addr_t *local_ip = ip_netif_get_local_ip(netif, &pcb->remote_ip);
if (local_ip == NULL) {
return ERR_RTE;
}
ip_addr_copy(pcb->local_ip, *local_ip);
}

#if TCP_OUTPUT_DEBUG
if (seg == NULL) {
LWIP_DEBUGF(TCP_OUTPUT_DEBUG, ("tcp_output: nothing to send (%p)\n",
(void*)pcb->unsent));
}
#endif /* TCP_OUTPUT_DEBUG */
#if TCP_CWND_DEBUG
if (seg == NULL) {
LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_output: snd_wnd %"TCPWNDSIZE_F
", cwnd %"TCPWNDSIZE_F", wnd %"U32_F
", seg == NULL, ack %"U32_F"\n",
pcb->snd_wnd, pcb->cwnd, wnd, pcb->lastack));
} else {
LWIP_DEBUGF(TCP_CWND_DEBUG,
("tcp_output: snd_wnd %"TCPWNDSIZE_F", cwnd %"TCPWNDSIZE_F", wnd %"U32_F
", effwnd %"U32_F", seq %"U32_F", ack %"U32_F"\n",
pcb->snd_wnd, pcb->cwnd, wnd,
lwip_ntohl(seg->tcphdr->seqno) - pcb->lastack + seg->len,
lwip_ntohl(seg->tcphdr->seqno), pcb->lastack));
}
#endif /* TCP_CWND_DEBUG */
/* 处理不适合窗口的报文段 */
if (seg != NULL &&
lwip_ntohl(seg->tcphdr->seqno) - pcb->lastack + seg->len > wnd &&
wnd > 0 && wnd == pcb->snd_wnd && pcb->unacked == NULL) {
/* 启动persist持久化定时器 */
if (pcb->persist_backoff == 0) {
pcb->persist_cnt = 0;
pcb->persist_backoff = 1;
}
goto output_done; //跳转至最后
}
/* 数据可用且窗口允许发送,while直到发送完成 */
while (seg != NULL &&
lwip_ntohl(seg->tcphdr->seqno) - pcb->lastack + seg->len <= wnd)
{
LWIP_ASSERT("RST not expected here!",
(TCPH_FLAGS(seg->tcphdr) & TCP_RST) == 0);

/* tcp_do_output_nagel(pcb)检查nagle算法是否允许发送,1允许,0禁止。在Nagle禁止时
* 1)若Nagle内存出错,及时发送ACK
* 2)FIN已在队列中,终端连接
* RST不使用tcp_output发送
*/
if ((tcp_do_output_nagle(pcb) == 0) &&
((pcb->flags & (TF_NAGLEMEMERR | TF_FIN)) == 0)) {
break;
}
#if TCP_CWND_DEBUG
LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_output: snd_wnd %"TCPWNDSIZE_F", cwnd %"TCPWNDSIZE_F", wnd %"U32_F", effwnd %"U32_F", seq %"U32_F", ack %"U32_F", i %"S16_F"\n",
pcb->snd_wnd, pcb->cwnd, wnd,
lwip_ntohl(seg->tcphdr->seqno) + seg->len -
pcb->lastack,
lwip_ntohl(seg->tcphdr->seqno), pcb->lastack, i));
++i;
#endif /* TCP_CWND_DEBUG */

/* 未处于建立连接的SYN态,需要在tcp结构里置位ACK,捎带应答 */
if (pcb->state != SYN_SENT) {
TCPH_SET_FLAG(seg->tcphdr, TCP_ACK);
}

#if TCP_OVERSIZE_DBGCHECK
seg->oversize_left = 0;
#endif /* TCP_OVERSIZE_DBGCHECK */
/* IP传输层使用ip_output_if函数,发送当前pcb块中的unsent报文段 */
err = tcp_output_segment(seg, pcb, netif);
if (err != ERR_OK) {
/* 无论何种原因,报文段未发送 */
pcb->flags |= TF_NAGLEMEMERR; /* Nagle 存储错误 */
return err;
}

pcb->unsent = seg->next; /* 未发送链表指向下一个seg段 */

if (pcb->state != SYN_SENT) {
pcb->flags &= ~(TF_ACK_DELAY | TF_ACK_NOW);/* 发送完后设定延时应答状态 */
}
/* 计算下一个snd_nxt新序列号 seq+length(seq) */
snd_nxt = lwip_ntohl(seg->tcphdr->seqno) + TCP_TCPLEN(seg);
if (TCP_SEQ_LT(pcb->snd_nxt, snd_nxt)) {
pcb->snd_nxt = snd_nxt;
}
/* 本段有效长度大于0放入unacked链表,等待rcv端ACK */
if (TCP_TCPLEN(seg) > 0) {
seg->next = NULL;
/* unacked list is empty? */
if (pcb->unacked == NULL) {
pcb->unacked = seg;
useg = seg;
/* unacked list is not empty? */
} else {
/* 如果当前报文段seq序号小于未收到应答unacked链表最近的seq序号,需要对链表进行重排(按序) */
if (TCP_SEQ_LT(lwip_ntohl(seg->tcphdr->seqno), lwip_ntohl(useg->tcphdr->seqno))) {
/* 将报文段按顺序增加在中间位置 */
struct tcp_seg **cur_seg = &(pcb->unacked);
/* 找到unacked中第一个seqno ≥ 当前发送报文段seqno的节点 */
while (*cur_seg &&
TCP_SEQ_LT(lwip_ntohl((*cur_seg)->tcphdr->seqno), lwip_ntohl(seg->tcphdr->seqno))) {
cur_seg = &((*cur_seg)->next );
}
seg->next = (*cur_seg); /*seg->next 指向原序列较大seq的节点(*cur_seg)*/
(*cur_seg) = seg; /* 原序列较大seq的节点(*cur_seg)的前驱节点的 next 指针指向 seg */
} else {
/* seg插入unacked尾部 */
useg->next = seg;
useg = useg->next;
}
}
/* 报文段len=0的空包 */
} else {
tcp_seg_free(seg);
}
seg = pcb->unsent; /* 下一个报文段,直到循环结束所有seg发送完毕 */
}
output_done:
#if TCP_OVERSIZE
if (pcb->unsent == NULL) {
/* last unsent has been removed, reset unsent_oversize */
pcb->unsent_oversize = 0;
}
#endif /* TCP_OVERSIZE */

pcb->flags &= ~TF_NAGLEMEMERR;
return ERR_OK;
}

报文段接收

类似应用层向传输层发送接口tcp_output(),还有传输层(IP层)向应用层发送tcp_input()函数,其源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
void
tcp_input(struct pbuf *p, struct netif *inp)
{
struct tcp_pcb *pcb, *prev;
struct tcp_pcb_listen *lpcb;
#if SO_REUSE
struct tcp_pcb *lpcb_prev = NULL;
struct tcp_pcb_listen *lpcb_any = NULL;
#endif /* SO_REUSE */
u8_t hdrlen_bytes;
err_t err;

LWIP_UNUSED_ARG(inp);

PERF_START;

TCP_STATS_INC(tcp.recv);
MIB2_STATS_INC(mib2.tcpinsegs);

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

#if TCP_INPUT_DEBUG
tcp_debug_print(tcphdr);
#endif

/* 报文段是否有有效数据 */
if (p->len < TCP_HLEN) {
/* 没有就丢弃报文段 */
LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: short packet (%"U16_F" bytes) discarded\n", p->tot_len));
TCP_STATS_INC(tcp.lenerr);
goto dropped;
}

/* 不处理传入的广播/多播报文段 */
if (ip_addr_isbroadcast(ip_current_dest_addr(), ip_current_netif()) ||
ip_addr_ismulticast(ip_current_dest_addr())) {
TCP_STATS_INC(tcp.proterr);
goto dropped;
}

#if CHECKSUM_CHECK_TCP
IF__NETIF_CHECKSUM_ENABLED(inp, NETIF_CHECKSUM_CHECK_TCP) {
/* Verify TCP checksum. */
u16_t chksum = ip_chksum_pseudo(p, IP_PROTO_TCP, p->tot_len,
ip_current_src_addr(), ip_current_dest_addr());
if (chksum != 0) {
LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: packet discarded due to failing checksum 0x%04"X16_F"\n",
chksum));
tcp_debug_print(tcphdr);
TCP_STATS_INC(tcp.chkerr);
goto dropped;
}
}
#endif /* CHECKSUM_CHECK_TCP */

/* TCP报文段首部长度 */
hdrlen_bytes = TCPH_HDRLEN(tcphdr) * 4;
if ((hdrlen_bytes < TCP_HLEN) || (hdrlen_bytes > p->tot_len)) {
/* uint16 溢出,无法处理*/
LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: invalid header length (%"U16_F")\n", (u16_t)hdrlen_bytes));
TCP_STATS_INC(tcp.lenerr);
goto dropped;
}

/* 将pbuf指针从tcp header指向data区 */
tcphdr_optlen = hdrlen_bytes - TCP_HLEN;
tcphdr_opt2 = NULL;
if (p->len >= hdrlen_bytes) {
/* all options are in the first pbuf */
tcphdr_opt1len = tcphdr_optlen;
pbuf_header(p, -(s16_t)hdrlen_bytes); /* cannot fail */
} else {
u16_t opt2len;
/* TCP header fits into first pbuf, options don't - data is in the next pbuf */
/* there must be a next pbuf, due to hdrlen_bytes sanity check above */
LWIP_ASSERT("p->next != NULL", p->next != NULL);

/* advance over the TCP header (cannot fail) */
pbuf_header(p, -TCP_HLEN);

/* determine how long the first and second parts of the options are */
tcphdr_opt1len = p->len;
opt2len = tcphdr_optlen - tcphdr_opt1len;

/* options continue in the next pbuf: set p to zero length and hide the
options in the next pbuf (adjusting p->tot_len) */
pbuf_header(p, -(s16_t)tcphdr_opt1len);

/* check that the options fit in the second pbuf */
if (opt2len > p->next->len) {
/* drop short packets */
LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: options overflow second pbuf (%"U16_F" bytes)\n", p->next->len));
TCP_STATS_INC(tcp.lenerr);
goto dropped;
}

/* remember the pointer to the second part of the options */
tcphdr_opt2 = (u8_t*)p->next->payload;

/* advance p->next to point after the options, and manually
adjust p->tot_len to keep it consistent with the changed p->next */
pbuf_header(p->next, -(s16_t)opt2len);
p->tot_len -= opt2len;

LWIP_ASSERT("p->len == 0", p->len == 0);
LWIP_ASSERT("p->tot_len == p->next->tot_len", p->tot_len == p->next->tot_len);
}

/* 提取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);
tcplen = p->tot_len + ((flags & (TCP_FIN | TCP_SYN)) ? 1 : 0);

/* Demultiplex an incoming segment. First, we check if it is destined
for an active connection. */
prev = NULL;

/* 遍历活跃的tcp_active_pcbs寻找对应TCP控制块 */
for (pcb = tcp_active_pcbs; pcb != NULL; pcb = pcb->next) {
LWIP_ASSERT("tcp_input: active pcb->state != CLOSED", pcb->state != CLOSED);
LWIP_ASSERT("tcp_input: active pcb->state != TIME-WAIT", pcb->state != TIME_WAIT);
LWIP_ASSERT("tcp_input: active pcb->state != LISTEN", pcb->state != LISTEN);
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())) {
/* Move this PCB to the front of the list so that subsequent
lookups will be faster (we exploit locality in TCP segment
arrivals). */
LWIP_ASSERT("tcp_input: pcb->next != pcb (before cache)", pcb->next != pcb);
if (prev != NULL) {
prev->next = pcb->next;
pcb->next = tcp_active_pcbs;
tcp_active_pcbs = pcb;
} else {
TCP_STATS_INC(tcp.cachehit);
}
LWIP_ASSERT("tcp_input: pcb->next != pcb (after cache)", pcb->next != pcb);
break;
}
prev = pcb;
}

if (pcb == NULL) {
/* 如果没找到active连接,则检查TW(Time-Wait)状态连接 */
for (pcb = tcp_tw_pcbs; pcb != NULL; pcb = pcb->next) {
LWIP_ASSERT("tcp_input: TIME-WAIT pcb->state == TIME-WAIT", pcb->state == TIME_WAIT);
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())) {
/* We don't really care enough to move this PCB to the front
of the list since we are not very likely to receive that
many segments for connections in TIME-WAIT. */
LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: packed for TIME_WAITing connection.\n"));
tcp_timewait_input(pcb); /* 有匹配的ip和端口 */
pbuf_free(p);
return;
}
}

/* 若仍未找到,则在LISTENING态的网口中查找连接. */
prev = NULL;
for (lpcb = tcp_listen_pcbs.listen_pcbs; lpcb != NULL; lpcb = lpcb->next) {
if (lpcb->local_port == tcphdr->dest) {
if (IP_IS_ANY_TYPE_VAL(lpcb->local_ip)) {
/* found an ANY TYPE (IPv4/IPv6) match */
#if SO_REUSE
lpcb_any = lpcb;
lpcb_prev = prev;
#else /* SO_REUSE */
break;
#endif /* SO_REUSE */
} else if (IP_ADDR_PCB_VERSION_MATCH_EXACT(lpcb, ip_current_dest_addr())) {
if (ip_addr_cmp(&lpcb->local_ip, ip_current_dest_addr())) {
/* found an exact match */
break;
} else if (ip_addr_isany(&lpcb->local_ip)) {
/* found an ANY-match */
#if SO_REUSE
lpcb_any = lpcb;
lpcb_prev = prev;
#else /* SO_REUSE */
break;
#endif /* SO_REUSE */
}
}
}
prev = (struct tcp_pcb *)lpcb;
}
#if SO_REUSE
/* first try specific local IP */
if (lpcb == NULL) {
/* only pass to ANY if no specific local IP has been found */
lpcb = lpcb_any;
prev = lpcb_prev;
}
#endif /* SO_REUSE */
if (lpcb != NULL) {
/* Move this PCB to the front of the list so that subsequent
lookups will be faster (we exploit locality in TCP segment
arrivals). */
if (prev != NULL) {
((struct tcp_pcb_listen *)prev)->next = lpcb->next;
/* our successor is the remainder of the listening list */
lpcb->next = tcp_listen_pcbs.listen_pcbs;
/* put this listening pcb at the head of the listening list */
tcp_listen_pcbs.listen_pcbs = lpcb;
} else {
TCP_STATS_INC(tcp.cachehit);
}

LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: packed for LISTENing connection.\n"));
tcp_listen_input(lpcb);
pbuf_free(p);
return;
}
}

#if TCP_INPUT_DEBUG
LWIP_DEBUGF(TCP_INPUT_DEBUG, ("+-+-+-+-+-+-+-+-+-+-+-+-+-+- tcp_input: flags "));
tcp_debug_print_flags(TCPH_FLAGS(tcphdr));
LWIP_DEBUGF(TCP_INPUT_DEBUG, ("-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n"));
#endif /* TCP_INPUT_DEBUG */


if (pcb != NULL) {
/* The incoming segment belongs to a connection. */
#if TCP_INPUT_DEBUG
tcp_debug_print_state(pcb->state);
#endif /* TCP_INPUT_DEBUG */

/* Set up a tcp_seg structure. */
inseg.next = NULL;
inseg.len = p->tot_len;
inseg.p = p;
inseg.tcphdr = tcphdr;

recv_data = NULL;
recv_flags = 0;
recv_acked = 0;

if (flags & TCP_PSH) {
p->flags |= PBUF_FLAG_PUSH;
}

/* If there is data which was previously "refused" by upper layer */
if (pcb->refused_data != NULL) {
if ((tcp_process_refused_data(pcb) == ERR_ABRT) ||
((pcb->refused_data != NULL) && (tcplen > 0))) {
/* pcb has been aborted or refused data is still refused and the new
segment contains data */
if (pcb->rcv_ann_wnd == 0) {
/* this is a zero-window probe, we respond to it with current RCV.NXT
and drop the data segment */
tcp_send_empty_ack(pcb);
}
TCP_STATS_INC(tcp.drop);
MIB2_STATS_INC(mib2.tcpinerrs);
goto aborted;
}
}
tcp_input_pcb = pcb;
err = tcp_process(pcb);
/* A return value of ERR_ABRT means that tcp_abort() was called
and that the pcb has been freed. If so, we don't do anything. */
if (err != ERR_ABRT) {
if (recv_flags & TF_RESET) {
/* TF_RESET means that the connection was reset by the other
end. We then call the error callback to inform the
application that the connection is dead before we
deallocate the PCB. */
TCP_EVENT_ERR(pcb->state, pcb->errf, pcb->callback_arg, ERR_RST);
tcp_pcb_remove(&tcp_active_pcbs, pcb);
memp_free(MEMP_TCP_PCB, pcb);
} else {
err = ERR_OK;
/* If the application has registered a "sent" function to be
called when new send buffer space is available, we call it
now. */
if (recv_acked > 0) {
u16_t acked16;
#if LWIP_WND_SCALE
/* recv_acked is u32_t but the sent callback only takes a u16_t,
so we might have to call it multiple times. */
u32_t acked = recv_acked;
while (acked > 0) {
acked16 = (u16_t)LWIP_MIN(acked, 0xffffu);
acked -= acked16;
#else
{
acked16 = recv_acked;
#endif
TCP_EVENT_SENT(pcb, (u16_t)acked16, err);
if (err == ERR_ABRT) {
goto aborted;
}
}
recv_acked = 0;
}
if (recv_flags & TF_CLOSED) {
/* The connection has been closed and we will deallocate the
PCB. */
if (!(pcb->flags & TF_RXCLOSED)) {
/* Connection closed although the application has only shut down the
tx side: call the PCB's err callback and indicate the closure to
ensure the application doesn't continue using the PCB. */
TCP_EVENT_ERR(pcb->state, pcb->errf, pcb->callback_arg, ERR_CLSD);
}
tcp_pcb_remove(&tcp_active_pcbs, pcb);
memp_free(MEMP_TCP_PCB, pcb);
goto aborted;
}
#if TCP_QUEUE_OOSEQ && LWIP_WND_SCALE
while (recv_data != NULL) {
struct pbuf *rest = NULL;
pbuf_split_64k(recv_data, &rest);
#else /* TCP_QUEUE_OOSEQ && LWIP_WND_SCALE */
if (recv_data != NULL) {
#endif /* TCP_QUEUE_OOSEQ && LWIP_WND_SCALE */

LWIP_ASSERT("pcb->refused_data == NULL", pcb->refused_data == NULL);
if (pcb->flags & TF_RXCLOSED) {
/* received data although already closed -> abort (send RST) to
notify the remote host that not all data has been processed */
pbuf_free(recv_data);
#if TCP_QUEUE_OOSEQ && LWIP_WND_SCALE
if (rest != NULL) {
pbuf_free(rest);
}
#endif /* TCP_QUEUE_OOSEQ && LWIP_WND_SCALE */
tcp_abort(pcb);
goto aborted;
}

/* Notify application that data has been received. */
TCP_EVENT_RECV(pcb, recv_data, ERR_OK, err);
if (err == ERR_ABRT) {
#if TCP_QUEUE_OOSEQ && LWIP_WND_SCALE
if (rest != NULL) {
pbuf_free(rest);
}
#endif /* TCP_QUEUE_OOSEQ && LWIP_WND_SCALE */
goto aborted;
}

/* If the upper layer can't receive this data, store it */
if (err != ERR_OK) {
#if TCP_QUEUE_OOSEQ && LWIP_WND_SCALE
if (rest != NULL) {
pbuf_cat(recv_data, rest);
}
#endif /* TCP_QUEUE_OOSEQ && LWIP_WND_SCALE */
pcb->refused_data = recv_data;
LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_input: keep incoming packet, because pcb is \"full\"\n"));
#if TCP_QUEUE_OOSEQ && LWIP_WND_SCALE
break;
} else {
/* Upper layer received the data, go on with the rest if > 64K */
recv_data = rest;
#endif /* TCP_QUEUE_OOSEQ && LWIP_WND_SCALE */
}
}

/* If a FIN segment was received, we call the callback
function with a NULL buffer to indicate EOF. */
if (recv_flags & TF_GOT_FIN) {
if (pcb->refused_data != NULL) {
/* Delay this if we have refused data. */
pcb->refused_data->flags |= PBUF_FLAG_TCP_FIN;
} else {
/* correct rcv_wnd as the application won't call tcp_recved()
for the FIN's seqno */
if (pcb->rcv_wnd != TCP_WND_MAX(pcb)) {
pcb->rcv_wnd++;
}
TCP_EVENT_CLOSED(pcb, err);
if (err == ERR_ABRT) {
goto aborted;
}
}
}

tcp_input_pcb = NULL;
/* Try to send something out. */
tcp_output(pcb);
#if TCP_INPUT_DEBUG
#if TCP_DEBUG
tcp_debug_print_state(pcb->state);
#endif /* TCP_DEBUG */
#endif /* TCP_INPUT_DEBUG */
}
}
/* Jump target if pcb has been aborted in a callback (by calling tcp_abort()).
Below this line, 'pcb' may not be dereferenced! */
aborted:
tcp_input_pcb = NULL;
recv_data = NULL;

/* give up our reference to inseg.p */
if (inseg.p != NULL)
{
pbuf_free(inseg.p);
inseg.p = NULL;
}
} else {

/* If no matching PCB was found, send a TCP RST (reset) to the
sender. */
LWIP_DEBUGF(TCP_RST_DEBUG, ("tcp_input: no PCB match found, resetting.\n"));
if (!(TCPH_FLAGS(tcphdr) & TCP_RST)) {
TCP_STATS_INC(tcp.proterr);
TCP_STATS_INC(tcp.drop);
tcp_rst(ackno, seqno + tcplen, ip_current_dest_addr(),
ip_current_src_addr(), tcphdr->dest, tcphdr->src);
}
pbuf_free(p);
}

LWIP_ASSERT("tcp_input: tcp_pcbs_sane()", tcp_pcbs_sane());
PERF_STOP("tcp_input");
return;
dropped:
TCP_STATS_INC(tcp.drop);
MIB2_STATS_INC(mib2.tcpinerrs);
pbuf_free(p);
}
  • 正常接收处理的数据,如果收到的报文段是复位报文或终止连接应答报文,则释放pbuf,终止连接;
  • TCP 协议确认报文段是新数据,调用带参宏TCP_EVENT_SENT(sent 的回调函数)处理;
  • 如果报文段中包含有效的数据,就调用TCP_EVENT_RECV 去处理;
  • 如果是收到FIN 报文,则调用TCP_EVENT_CLOSED 去处理;

参考资料

  1. TCP协议_tcp全双工-CSDN博客
  2. tcp/ip 只有四次挥手?还有三次挥手_四次挥手中会出现只有3此的情况嘛-CSDN博客
  3. [linux - What is the reason and how to avoid the FIN, ACK] , [RST] and [RST, ACK] - Stack Overflow
  4. Wireshark抓包分析TCP,发现居然只有三次挥手_只抓到三次挥手-CSDN博客
  5. Linux的TCP实现之:四次挥手 | Zorro’s Linux Book
  6. 【lwip】14-TCP协议之可靠传输的实现(TCP干货) - 李柱明 - 博客园
  7. RFC 793: Transmission Control Protocol-S3.5
  8. 【lwip】12-一文解决TCP原理 - 李柱明 - 博客园
  9. TCP系列33—窗口管理&流控—7、Silly Window Syndrome(SWS) - lshs - 博客园
  10. 【lwip】13-TCP协议分析之源码篇 - 李柱明 - 博客园

————————— End —————————

“江山如此多娇,引无数英雄竞折腰。” —— 《沁园春·雪》