三.传输层(主讲TCP协议)(tcp_input)
TCP连接的建立过程(三次握手):
1. 客户端发送一个SYN标志置1的TCP数据报,握手包中指明源端口和目的端口,同时告知客户端初始序号seqno_client
2. 当服务器接收到该数据包并解析后,也发回一个SYN标志置1的数据报作为应答,应答中包含服务器端初始序号seqno_server,同时将ACK标志置1,将确认序号设置为seqno_client+1
3. 当客户端接收到服务端的SYN应答包,会再次产生一个握手包,包中ACK标志置1,确认序号设置为seqno_server+1
TCP连接的断开过程(四次握手):
1. 当客户端应用程序主动执行关闭操作时,客户端会向服务器发送一个FIN标志置1的报文段,用来关闭从客户端到服务器的数据传送,该报文段序号字段为seqno_client
2. 当服务器接收到这个FIN报文段后,返回一个ACK报文,确认序号为seqno_client+1,当客户端收到这个ACK后,从客户端到服务器方向的连接就断开了
3. 服务器TCP向其上层应用程序通过客户端的端口操作,这会导致服务器应用程序关闭它的连接,同样,此时一个FIN置1的报文段将被发往客户端,该报文段序号字段为seqno_server
4. 当客户端收到这个FIN报文段后,也会返回一个ACK作为响应,确认序号为seqno_server+1,从服务器到客户端方向的连接也就被断开了
两条最经典的TCP状态转换路径:
1.第一条路径描述了客户端申请建立连接与断开连接的整个过程:
CLOSED —> SYN_SENT —> ESTABLISHED—> FIN_WAIT_1 —> FIN_WAIT_2 —> TIME_WAIT —> CLOSED
主动打开/syn syn+ack/ack /fin ack/ fin/ack
2.第二条路径描述了服务器建立连接与断开连接的整个过程:
CLOSED —> LISTEN —> SYN_RCVD —> ESTABLISHED —> CLOSE_WAIT —> LAST_ACK —>
CLOSED
被动打开/ syn/syn+ack ack/ fin/ack /fin ack/
11种TCP状态
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, // 关闭成功,等待网络中可能出现的剩余数据
}
tcp协议包头
lwip使用一个tcp_hdr的结构体来描述tcp协议包头:
struct tcp_hdr{
u16_t src; // 源端口
u16_t dest; // 目的端口
u32_t seqno; // 序号,用来标识从TCP发送端到接收端的数据字节流
u32_t ackno; // 确认序号,是发送确认的一段所期望收到的下一个序号
u16_t _hdrlen_rsvd_flags; // 包含4位TCP包头长(通常为5*4,即本结构体大小)、6个标志位(URG、ACK、PSH、RST、SYN、FIN)
u16_t wnd; // 窗口大小字段,表示还能接收的字节数,实现流量控制
u16_t chksum; // 16位整个TCP报文校验和,包含了TCP头和TCP数据,由发送端计算并由接收端验证
u16_t urgp; // 紧急指针,暂略
}
非常非常重要的结构体struct tcp_pcb
lwip使用一个tcp_pcb控制块来描述一个TCP连接(lwip实际定义了2种TCP控制块,一种专门用于描述处于LISTEN状态的连接,另一种用于描述处于其他状态的连接):
这个TCP控制块是整个TCP协议的核心,TCP协议实现的本质就是对TCP控制块中各字段的操作,所以非常重要!!!
struct tcp_pcb{
IP_PCB; // 该宏描述了连接的IP相关信息,主要包含源IP、目的IP两个重要字段
// 这部分是2种类型TCP控制块都具有的字段
struct tcp_pcb *next; // 指向下一个tcp_pcb控制块的链表指针
enum tcp_state state; // TCP连接的状态,如上所述共11种
u8_t prio; // 该控制块的优先级,可用于回收低优先级控制块
void *callback_arg; // 指向用户自定义数据,在函数回调时使用
tcp_accept_fn accept; // 连接accept时回调函数
u16_t local_port; // 绑定的本地端口
u16_t remote_port; // 远程端口
u8_t flags; // 控制块状态、标志字段,描述了当前控制块的特性,各位的含义如下宏定义
#define TF_ACK_DELAY 0x01 // 延迟发送ACK
#define TF_ACK_NOW 0x02 // 立即发送ACK
#define TF_INFR 0x04 // 连接处于快重传状态
#define TF_TIMESTAMP 0x08 // 连接的时间戳选项已使能
#define TF_RXCLOSED 0x10 // 因TCP连接断开导致RX关闭
#define TF_FIN 0x20 // 应用程序已关闭该连接
#define TF_NODELAY 0x40 // 禁止Nagle算法
#define TF_NAGLEMEMERR 0x80 // 本地缓冲区溢出
// 接收相关字段
u32_t rcv_nxt; // 期望接收的下一个序号,也即是本地将要反馈给对方的ACK的序号,也是本地接收窗口的左边界
u16_t rcv_wnd; // 当前接收窗口大小,会随着数据的接收与递交动态变化
u16_t rcv_ann_wnd; // 将向对方通告的窗口大小,也会随着数据的接收与递交动态变化
u32_t rcv_ann_right_edge; // 上一次窗口通告时窗口的右边界值
// 时间相关字段
u32_t tmr; // 其它各计数器都基于tmr的值来实现
u8_t polltmr, pollinterval; // 这两个字段用于周期性调用一个函数,polltmr会周期性增加,当超过pollinterval时,poll函数会被调用
s16_t rtime; // 重传定时器,当大于rto的值时则重传报文
u16_t mss; // 对方可接收的最大报文大小
// RTT估计相关的参数
u32_t rttest;
u32_t rtseq;
s16_t sa, sv;
s16_t rto; // 重传超时时间,使用上面3个RTT参数计算出来
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; // 上次窗口更新时收到的数据序号seqno和确认号ackno
u32_t snd_lbb; // 下一个被缓冲的应用程序数据的编号
u16_t acked; // 保存了被确认的已发送长度
u16_t snd_buf; // 可用的发送空间(以字节为单位)
u16_t snd_queuelen; // 被占用的发送空间(以数据段pbuf为单位)
u16_t unsent_oversize; // 尚未被发送的字节数
struct tcp_seg *unsent; // 未发送的数据段队列,链表形式
struct tcp_seg *unacked; // 发送了未收到确认的数据段队列,链表形式
struct tcp_seg *ooseq; // 接收到有序序号以外的数据段队列,链表形式
struct pbuf *refused_data; // 指向上一次成功接收但未被应用层取用的数据pbuf
// 回调函数
err_t (*sent)(void *arg,struct tcp_pcb *pcb,u16_t space); // 数据成功发送后被调用
err_t (*recv)(void *arg,struct tcp_pcb,struct pbuf *p,err_t err); // 接收到数据后被调用
err_t (*connected)(void *arg, struct tcp_pcb *tpcb, err_t err); // 连接建立后被调用
err_t (*poll)(void *arg, struct tcp_pcb *tpcb); // 该函数被内核周期性调用
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; // 坚持定时器开关,大于0开启
u8_t keep_cnt_sent; // 保活计时器(心跳)最大重发次数 ?
}
struct tcp_pcb_listen{
IP_PCB;
struct tcp_pcb *next;
enum tcp_state state;
u8_t prio;
void *callback_arg;
tcp_accept_fn accept;
u16_t local_port;
}
注:
#define IP_PCB ip_addr_t local_ip; // 本地IP
ip_addr_t remote_ip; // 目的IP
u8_t so_options; // 套接字选项 可取值:
#define SOF_ACCEPTCONN (u8_t)0x02U
#define SOF_REUSEADDR (u8_t)0x04U
#define SOF_KEEPALIVE (u8_t)0x08U
#define SOF_BROADCAST (u8_t)0x20U
#define SOF_LINGER (u8_t)0x80U
#define SOF_INHERITED (SOF_REUSEADDR|SOF_KEEPALIVE|SOF_LINGER)
u8_t tos; // 服务类型
u8_t ttl; // TTL
tcp_input函数
tcp_input是TCP层的总输入函数,它会为数据包寻找一个匹配的TCP控制块,以及调用相应的函数tcp_timewait_input,tcp_listen_input,tcp_process进行处理
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包头,提取TCP头
iphdr = (struct ip_hdr *)p->payload;
tcphdr = (struct tcp_hdr *)((u8_t *)p->payload + IPH_HL(iphdr)*4)
// 移动pbuf结构中的数据包指针,使指向TCP头
if (pbuf_header(p, -((s16_t)(IPH_HL(iphdr) * 4))) || (p->tot_len < sizeof(struct tcp_hdr)))
{
pbuf_free(p);
return;
}
// 不处理输入的广播包
if (ip_addr_isbroadcast(¤t_iphdr_dest, inp) || ip_addr_ismulticast(¤t_iphdr_dest))
{
pbuf_free(p);
return;
}
// 验证TCP校验和
if (inet_chksum_pseudo(p, ip_current_src_addr(), ip_current_dest_addr(),IP_PROTO_TCP, p->tot_len) != 0)
{
pbuf_free(p);
return;
}
// 继续移动pbuf结构中的数据包指针,使指向TCP数据
hdrlen = TCPH_HDRLEN(tcphdr);
if(pbuf_header(p, -(hdrlen * 4))
{
pbuf_free(p);
return;
}
// 网络字节序转主机字节序
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); // 6位标志位
tcplen = p->tot_len + ((flags & (TCP_FIN | TCP_SYN)) ? 1 : 0); // TCP数据包中数据的总长度,对于有FIN或SYN标志的数据包,该长度要加1
// 以下就是对接收到的数据包进行分类处理,也就是寻找合适的接口,根据IP,port
// 首先在tcp_active_pcbs 链表池中找,有没有匹配的tcp_pcb
prev = NULL;
for(pcb = tcp_active_pcbs; pcb != NULL; pcb = pcb->next)
{
if (pcb->remote_port == tcphdr->src && pcb->local_port == tcphdr->dest && ip_addr_cmp(&(pcb->remote_ip), ¤t_iphdr_src) && ip_addr_cmp(&(pcb->local_ip), ¤t_iphdr_dest))
{
// 找到匹配的接口之后,将该tcp_pcb从tcp_active_pcbs链表池中取出,然后退出循环往下运行,这时pcb != NULL
if (prev != NULL)
{
prev->next = pcb->next;
pcb->next = tcp_active_pcbs;
tcp_active_pcbs = pcb;
}
break;
}
prev = pcb;
}
// 如果在tcp_active_pcbs中没有找到,继续在tcp_tw_pcbs 和tcp_listen_pcbs中找
if (pcb == NULL)
{
// 在tcp_tw_pcbs中找
for(pcb = tcp_tw_pcbs; pcb != NULL; pcb = pcb->next)
{
if (pcb->remote_port == tcphdr->src && pcb->local_port == tcphdr->dest && ip_addr_cmp(&(pcb->remote_ip), ¤t_iphdr_src) && ip_addr_cmp(&(pcb->local_ip), ¤t_iphdr_dest))
{
// 进入TIME_WAIT状态处理(解析见下文),处理完直接这里返回不再往下运行
tcp_timewait_input(pcb);
pbuf_free(p);
return;
}
}
// 在tcp_listen_pcbs中找
prev = NULL;
for(lpcb = tcp_listen_pcbs.listen_pcbs; lpcb != NULL; lpcb = lpcb->next)
{
// 判断端口是否匹配
if (lpcb->local_port == tcphdr->dest)
{
// 然后判断IP是否匹配,或者是IPADDR_ANY接收任何IP
if (ip_addr_cmp(&(lpcb->local_ip), ¤t_iphdr_dest) || ip_addr_isany(&(lpcb->local_ip)))
{
// 找到匹配的接口之后退出循环往下运行,这时lpcb != NULL
break;
}
}
prev = (struct tcp_pcb *)lpcb;
}
// 这里是判断在tcp_listen_pcbs中是否找到
if (lpcb != NULL)
{
// 将该tcp_pcb从tcp_listen_pcbs.listen_pcbs链表池中取出
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;
}
}
// 如果在tcp_active_pcbs中找到了,则经过处理后进入tcp_process
if (pcb != NULL)
{
inseg.next = NULL; // 关闭报文段队列功能
inseg.len = p->tot_len; // 设置该报文段的数据长度
inseg.p = p; // 设置报文段数据链表头指针
inseg.tcphdr = tcphdr; // 报文段的TCP头
recv_data = NULL; // 数据接收结果被保存在该全局变量,然后往上层提交
recv_flags = 0; // tcp_process执行完后的结果(控制块的状态变迁)将会被保存在该全局变量,首先在这里被清0
// tcp_pcb的refused_data指针上是否还记录有尚未往上层递交的数据
if (pcb->refused_data != NULL)
{
// 有的话回调用户recv函数接收未递交的数据
TCP_EVENT_RECV(pcb, pcb->refused_data, ERR_OK, err);
// 判断处理recv函数的处理结果,成功refused_data指针清空,继续往下执行tcp_process
if (err == ERR_OK)
{
pcb->refused_data = NULL;
}
// 失败意味着tcp_pcb都被占用满,丢弃接收包不再处理,直接返回
else if ((err == ERR_ABRT) || (tcplen > 0))
{
pbuf_free(p);
return;
}
}
tcp_input_pcb = pcb; // 记录处理当前报文的控制块
// 这里就是进入tcp_process处理接收包环节了(解析见下文),该函数实现了TCP状态转换功能
err = tcp_process(pcb);
// 若返回值为ERR_ABRT,说明控制块已经被完全删除(tcp_abort()),什么也不需要做
if (err != ERR_ABRT)
{
// 返回值不为ERR_ABRT时,判断报文处理的3种结果
if (recv_flags & TF_RESET) // 接收到对方的复位报文
{
// 回调用户的errf函数
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) // 如果有被确认的已发送数据长度
{
// 回调用户的send函数
TCP_EVENT_SENT(pcb, pcb->acked, err);
if (err == ERR_ABRT)
{
goto aborted;
}
}
if (recv_data != NULL) // 如果有数据被接收到
{
if (pcb->flags & TF_RXCLOSED) // 如果本地TCP控制块已经处于TF_RXCLOSED状态,则后续接收到的数据都作废
{
pbuf_free(recv_data);
tcp_abort(pcb);
goto aborted;
}
if (flags & TCP_PSH) // 如果TCP标志位中带有PSH
{
// 设置pbuf首部的flag字段
recv_data->flags |= PBUF_FLAG_PUSH;
}
// 回调用户的recv函数,接收递交上去的TCP数据recv_data
TCP_EVENT_RECV(pcb, recv_data, ERR_OK, err);
// 判断返回值,如果是ERR_ABRT,则丢弃,返回
if (err == ERR_ABRT)
{
goto aborted;
}
// 除此之外,如果返回值是失败,将这部分尚未往上递交的数据暂存到refused_data指针中
if (err != ERR_OK)
{
pcb->refused_data = recv_data;
}
}
if (recv_flags & TF_GOT_FIN) // 如果收到对方的FIN请求
{
// 纠正接收窗口
if (pcb->rcv_wnd != TCP_WND)
{
pcb->rcv_wnd++;
}
// 用一个NULL指针回调用户的recv函数,通过这种方式用户程序可以知道对方的关闭请求
TCP_EVENT_CLOSED(pcb, err);
if (err == ERR_ABRT)
{
goto aborted;
}
}
tcp_input_pcb = NULL; // 当前报文到此处理完毕,清空当前报文的控制块
tcp_output(pcb); // 输出报文
}
}
aborted:
tcp_input_pcb = NULL;
recv_data = NULL;
if (inseg.p != NULL)
{
pbuf_free(inseg.p);
inseg.p = NULL;
}
}
else
{
// 如果在3张链表里都未找到匹配的pcb,则调用tcp_rst向源主机发送一个TCP复位数据包
if (!(TCPH_FLAGS(tcphdr) & TCP_RST))
{
tcp_rst(ackno, seqno + tcplen,ip_current_dest_addr(), ip_current_src_addr(),tcphdr->dest, tcphdr->src);
}
pbuf_free(p);
}
}
******************************************************************************************************************************************************************************************************
// 本函数是处于LISTEN状态的控制块对输入报文的处理函数,处于LISTEN状态的控制块只能响应SYN握手包
err_t tcp_listen_input(struct tcp_pcb_listen *pcb)
{
struct tcp_pcb *npcb;
err_t rc;
// 处于listen状态的pcb只能响应SYN握手包,对含有ACK标志的输入报文返回一个RST报文
if (flags & TCP_ACK)
{
tcp_rst(ackno + 1, seqno + tcplen,ip_current_dest_addr(), ip_current_src_addr(),tcphdr->dest, tcphdr->src);
}
// 处于listen状态的服务器端等到了SYN握手包
else if (flags & TCP_SYN)
{
// 建立一个新的tcp_pcb,因为处于tcp_listen_pcbs链表上的pcb是tcp_pcb_listen结构的,而其他链表上的pcb是tcp_pcb结构
npcb = tcp_alloc(pcb->prio);
// 如果新建失败,往往是因为内存不够
if (npcb == NULL)
{
TCP_STATS_INC(tcp.memerr);
ERR_MEM;
}
// 为这个新建的tcp_pcb填充成员
ip_addr_copy(npcb->local_ip, current_iphdr_dest);
npcb->local_port = pcb->local_port;
ip_addr_copy(npcb->remote_ip, current_iphdr_src);
npcb->remote_port = tcphdr->src;
npcb->state = SYN_RCVD; // 进入SYN_RCVD状态
npcb->rcv_nxt = seqno + 1; // 期望接收到的下一个序号,注意加1
npcb->rcv_ann_right_edge = npcb->rcv_nxt; // 初始化右侧通告窗口
npcb->snd_wnd = tcphdr->wnd; // 根据TCP头中对方可接收数据长度,初始化本地发送窗口大小
npcb->ssthresh = npcb->snd_wnd; // 拥塞算法相关,暂略
npcb->snd_wl1 = seqno - 1; // 初始化上次窗口更新时收到的序号
npcb->callback_arg = pcb->callback_arg; // 初始化用户自定义数据
npcb->accept = pcb->accept; // 初始化连接accept时的回调函数
npcb->so_options = pcb->so_options & SOF_INHERITED; // 继承socket选项
TCP_REG(&tcp_active_pcbs, npcb); // 将这个设置好的tcp_pcb注册到tcp_active_pcbs链表中去
tcp_parseopt(npcb); // 从收到的SYN握手包中提取TCP头中选项字段的值,并设置到自己的tcp_pcb
npcb->mss = tcp_eff_send_mss(npcb->mss, &(npcb->remote_ip)); // 初始化mss
// 回复带有SYN和ACK标志的握手数据包
rc = tcp_enqueue_flags(npcb, TCP_SYN | TCP_ACK);
if (rc != ERR_OK)
{
tcp_abandon(npcb, 0);
return rc;
}
// TCP层的总输出函数,详见下文
return tcp_output(npcb);
}
return ERR_OK;
}
******************************************************************************************************************************************************************************************************
// 本函数是处于TIMEWAIT状态的控制块处理输入报文的函数
err_t tcp_timewait_input(struct tcp_pcb *pcb)
{
// 如果报文中含RST标志,直接丢弃
if (flags & TCP_RST)
{
return ERR_OK;
}
// 如果报文中含SYN标志
if (flags & TCP_SYN)
{
// 如果SYN的序号在接收窗口内,返回一个RST报文
if (TCP_SEQ_BETWEEN(seqno, pcb->rcv_nxt, pcb->rcv_nxt+pcb->rcv_wnd))
{
tcp_rst(ackno, seqno + tcplen, ip_current_dest_addr(), ip_current_src_addr(),tcphdr->dest, tcphdr->src);
return ERR_OK;
}
}
// 如果报文中含FIN标志
else if(flags & TCP_FIN)
{
pcb->tmr = tcp_ticks;
}
// 如果TCP报文中有数据
if(tcp_len > 0)
{
pcb->flags |= TF_ACK_NOW; // 将当前控制块设为TF_ACK_NOW状态
// TCP层的总输出函数,详见下文
return tcp_output(pcb);
}
return ERR_OK;
}
******************************************************************************************************************************************************************************************************
break;
default:
break;
}
return ERR_OK;
}
************************************************************************************
tcp_process( )函数
除了处于LISTEN、TIME_WAIT状态的其余所有状态的pcb控制块,其报文的输入处理都在tcp_process( )里,该函数主要实现了TCP状态转换功能
err_t tcp_process(struct tcp_pcb *pcb)
{
struct tcp_seg *rseg;
u8_t acceptable = 0;
err_t err;
err = ERR_OK;
// 首先判断该报文是不是一个RST报文
if(flags & TCP_RST)
{
// 判断该RST报文是否合法
if (pcb->state == SYN_SENT) // 第一种情况,连接处于SYN_SENT状态
{
if (ackno == pcb->snd_nxt) // 且输入报文中的确认号就是控制块中想要发送的下一个序号
{
acceptable = 1;
}
}
else // 第二种情况,其他状态下,输入报文中的序号在接收窗口内
{
if (TCP_SEQ_BETWEEN(seqno, pcb->rcv_nxt,pcb->rcv_nxt+pcb->rcv_wnd))
{
acceptable = 1;
}
}
// 如果RST报文合法,则需要复位当前连接的控制块,非法则直接返回不做处理
if (acceptable)
{
recv_flags |= TF_RESET; // 表明该输入报文的处理结果中包含TF_RESET
pcb->flags &= ~TF_ACK_DELAY; // 因为输入是RST报文,意味当前控制块必然不处于TF_ACK_DELAY状态
return ERR_RST;
}
else
{
return ERR_OK;
}
}
// 然后处理握手报文SYN,在连接已经建立情况下,但还是接收到对方的握手包,说明这可能是一个超时重发的握手包,直接向对方返回一个ACK即可
if ((flags & TCP_SYN) && (pcb->state != SYN_SENT && pcb->state != SYN_RCVD))
{
tcp_ack_now(pcb); // #define tcp_ack_now(pcb) pcb->flags |= TF_ACK_NOW - 将当前控制块设为TF_ACK_NOW状态
return ERR_OK;
}
// TCP连接不处于半关闭前提下,更新控制块的活动计数器
if ((pcb->flags & TF_RXCLOSED) == 0)
{
pcb->tmr = tcp_ticks;
}
// 保活报文计数器清0
pcb->keep_cnt_sent = 0;
// 处理报文首部中的选项字段(暂略)
tcp_parseopt(pcb);
// 根据当前所处的不同的TCP状态执行相应动作
switch (pcb->state)
{
case SYN_SENT: // 客户端发出SYN后,就处于该状态等待服务器返回SYN+ACK
// 如果收到的是SYN+ACK,且输入报文中的确认号,就是控制块中已发送,但尚未收到应答报文段中的序号+1
if ((flags & TCP_ACK) && (flags & TCP_SYN) && ackno == ntohl(pcb->unacked->tcphdr->seqno) + 1)
{
pcb->snd_buf++; // 发出SYN被返回的ACK确认,释放1字节空间,所以可用的发送空间加1字节
pcb->rcv_nxt = seqno + 1; // 期望接收的下一个序号,即接收端向发送端ACK报文中的确认号
pcb->rcv_ann_right_edge = pcb->rcv_nxt; // 初始化通告窗口的右边界值(略存疑问)
pcb->lastack = ackno; // 更新接收到的最大确认号字段,也就是更新上一个确认号字段
pcb->snd_wnd = tcphdr->wnd; // 发送窗口设置为接收窗口大小,实现流量控制
pcb->snd_wl1 = seqno - 1; // 上次窗口更新时收到的数据序号
pcb->state = ESTABLISHED; // 进入ESTABLISHED状态
pcb->mss = tcp_eff_send_mss(pcb->mss, &(pcb->remote_ip)); // 计算并设置最大报文段
pcb->ssthresh = pcb->mss * 10; // 重设mss后,ssthresh值也要相应修改
pcb->cwnd = ((pcb->cwnd == 1) ? (pcb->mss * 2) : pcb->mss); // 初始化阻塞窗口
--pcb->snd_queuelen; // SYN被返回的ACK确认,所以占用的pbuf个数减1
rseg = pcb->unacked; // 从发送了未收到确认的数据段队列中取出SYN报文,相当于删除
pcb->unacked = rseg->next; // 指向下一个发送了未收到确认的数据段
if(pcb->unacked == NULL) // 如果未确认的数据段为空,则停止重传定时器
pcb->rtime = -1;
else // 如果队列中还有报文,则复位重传定时器和重传次数
{
pcb->rtime = 0;
pcb->nrtx = 0;
}
tcp_seg_free(rseg); // 释放取下的SYN报文段内存空间
TCP_EVENT_CONNECTED(pcb, ERR_OK, err); // 回调用户的connect函数(详解见下文)
if (err == ERR_ABRT)
{
return ERR_ABRT;
}
tcp_ack_now(pcb); // 向服务器返回ACK,三次握手结束,具体含义见L753
}
// 如果只收到对方的ACK却没有SYN,则向对方返回RST报文
else if(flag & TCP_ACK)
{
tcp_rst(ackno, seqno + tcplen, ip_current_dest_addr(), ip_current_src_addr(),tcphdr->dest, tcphdr->src);
}
break;
case SYN_RCVD: // 服务器发送SYN+ACK后,就处于该状态,等待客户端返回ACK
// 如果收到ACK,也就是三次握手的最后一个报文
if(flags & TCP_ACK)
{
// 如果ACK合法
if (TCP_SEQ_BETWEEN(ackno, pcb->lastack+1, pcb->snd_nxt))
{
if (TCP_SEQ_BETWEEN(ackno, pcb->lastack+1, pcb->snd_nxt))
{
u16_t old_cwnd;
pcb->state = ESTABLISHED; // 进入ESTABLISHED状态
TCP_EVENT_ACCEPT(pcb, ERR_OK, err); // 回调用户的accept函数
if (err != ERR_OK) // 如果accept函数返回错误,则关闭当前连接
{
if (err != ERR_ABRT)
{
tcp_abort(pcb);
}
return ERR_ABRT;
}
old_cwnd = pcb->cwnd; // 保存旧的阻塞窗口
tcp_receive(pcb); // 如果该ACK报文中还携带了数据,则调用tcp_receive处理报文中的数据(解析见下文)
// 调整本地未被确认的字节数,因为SYN报文占用1个字节,所以减1
if (pcb->acked != 0)
{
pcb->acked--;
}
pcb->cwnd = ((old_cwnd == 1) ? (pcb->mss * 2) : pcb->mss); // 初始化阻塞窗口
// 如果在上面的tcp_receive处理结果中包含FIN标志
if (recv_flags & TF_GOT_FIN)
{
tcp_ack_now(pcb); // 回复ACK,响应对方的FIN握手标志
pcb->state = CLOSE_WAIT; // 进入CLOSE_WAIT状态
}
}
}
else
{
// 对于不合法的ACK,则返回一个RST
tcp_rst(ackno, seqno + tcplen, ip_current_dest_addr(), ip_current_src_addr(),tcphdr->dest, tcphdr->src);
}
}
// 如果收到客户端重复SYN握手包,说明SYN+ACK包丢失,需要重传
else if ((flags & TCP_SYN) && (seqno == pcb->rcv_nxt - 1))
{
tcp_rexmit(pcb);
}
break;
case CLOSE_WAIT: // 服务器处于接收关闭的半连接状态,会一直等待上层应用执行关闭指令,发出FIN,并将状态变为LASK_ACK
case ESTABLISHED: // 连接双方都处于稳定状态
tcp_receive(pcb); // 调用函数处理报文中的数据
// 如果在上面的tcp_receive处理结果中包含FIN标志
if (recv_flags & TF_GOT_FIN)
{
tcp_ack_now(pcb); // 回复ACK,响应对方的FIN握手标志
pcb->state = CLOSE_WAIT; // 进入CLOSE_WAIT状态
}
break;
case FIN_WAIT_1: // 上层应用主动执行关闭指令,发送FIN后处于该状态(通常对于客户端来讲)
tcp_receive(pcb); // 调用函数处理报文中的数据
// 如果在上面的tcp_receive处理结果中包含FIN标志
if (recv_flags & TF_GOT_FIN)
{
// 如果该报文同时包含一个合法ACK,意味着本地端将直接跳过FIN_WAIT_2进入TIME_WAIT状态
if ((flags & TCP_ACK) && (ackno == pcb->snd_nxt))
{
tcp_ack_now(pcb); // 回复ACK
tcp_pcb_purge(pcb); // 清除该连接中的所有现存数据
TCP_RMV(&tcp_active_pcbs, pcb); // 从tcp_active_pcbs链表中删除该控制块
pcb->state = TIME_WAIT; // 跳过FIN_WAIT_2状态,直接进入TIME_WAIT状态
TCP_REG(&tcp_tw_pcbs, pcb); // 将该控制块加入tcp_tw_pcbs链表
}
// 如果该报文不含ACK,即表示双方同时执行了关闭连接操作
else
{
tcp_ack_now(pcb); // 返回ACK
pcb->state = CLOSING; // 进入CLOSING状态
}
}
// 如果只收到有效的ACK
else if ((flags & TCP_ACK) && (ackno == pcb->snd_nxt))
{
pcb->state = FIN_WAIT_2; // 进入FIN_WAIT_2状态
}
break;
case FIN_WAIT_2: // 主动关闭,发送FIN握手且收到ACK后处于该状态
tcp_receive(pcb); // 调用函数处理报文中的数据
// 如果在上面的tcp_receive处理结果中包含FIN标志
if (recv_flags & TF_GOT_FIN)
{
tcp_ack_now(pcb); // 回复ACK
tcp_pcb_purge(pcb); // 清除该连接中的所有现存数据
TCP_RMV(&tcp_active_pcbs, pcb); // 从tcp_active_pcbs链表中删除该控制块
pcb->state = TIME_WAIT; // 进入TIME_WAIT状态
TCP_REG(&tcp_tw_pcbs, pcb); // 将该控制块加入tcp_tw_pcbs链表
}
break;
case CLOSING: // 双方同时执行主动关闭,处于该状态(特殊情况)
tcp_receive(pcb); // 调用函数处理报文中的数据
// 如果收到合法ACK
if (flags & TCP_ACK && ackno == pcb->snd_nxt)
{
tcp_pcb_purge(pcb); // 清除该连接中的所有现存数据
TCP_RMV(&tcp_active_pcbs, pcb); // 从tcp_active_pcbs链表中删除该控制块
pcb->state = TIME_WAIT; // 进入TIME_WAIT状态
TCP_REG(&tcp_tw_pcbs, pcb); // 将该控制块加入tcp_tw_pcbs链表
}
break;
case LAST_ACK: // 服务器在执行被动关闭时,发送完FIN,等待ACK时处于该状态
tcp_receive(pcb); // 调用函数处理报文中的数据
// 如果收到合法ACK
if (flags & TCP_ACK && ackno == pcb->snd_nxt)
{
recv_flags |= TF_CLOSED; // recv_flags设置为TF_CLOSED,由tcp_input函数对该控制块进行释放和清除
}