学习《嵌入式网络那些事lwip协议 深度剖析与实战演练》过程中
对tcp_process这个函数的理解
连接建立过程中的握手分为三个报文分别为①② ③
连接断开过程中的握手分为④ ⑤ ⑥ ⑦
此外还有双方同时关闭的情况需要考虑
函数前面部分属于对输入的报文进行判断,是否合法,是否为存在RST标志置位,并判断收到的握手包为超时重发
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) {
if (pcb->state == 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;
if (acceptable) {
LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_process: Connection RESET\n"));
LWIP_ASSERT("tcp_input: pcb->state != CLOSED", pcb->state != CLOSED);
recv_flags |= TF_RESET;
pcb->flags &= ~TF_ACK_DELAY;
return ERR_RST;
} else {
LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_process: unacceptable reset seqno %"U32_F" rcv_nxt %"U32_F"\n",
seqno, pcb->rcv_nxt));
LWIP_DEBUGF(TCP_DEBUG, ("tcp_process: unacceptable reset seqno %"U32_F" rcv_nxt %"U32_F"\n",
seqno, pcb->rcv_nxt));
return ERR_OK;
if ((flags & TCP_SYN) && (pcb->state != SYN_SENT && pcb->state != SYN_RCVD)) {
tcp_ack_now(pcb);
return ERR_OK;
if ((pcb->flags & TF_RXCLOSED) == 0) {
pcb->tmr = tcp_ticks;
pcb->keep_cnt_sent = 0;
tcp_parseopt(pcb);
switch (pcb->state)
下面是实现TCP状态机的具体部分
case SYN_SENT:
LWIP_DEBUGF(TCP_INPUT_DEBUG, ("SYN-SENT: ackno %"U32_F" pcb->snd_nxt %"U32_F" unacked %"U32_F"\n", ackno,
pcb->snd_nxt, ntohl(pcb->unacked->tcphdr->seqno)));
if ((flags & TCP_ACK) && (flags & TCP_SYN)
&& ackno == ntohl(pcb->unacked->tcphdr->seqno) + 1) {
pcb->snd_buf++;
pcb->rcv_nxt = seqno + 1;
pcb->rcv_ann_right_edge = pcb->rcv_nxt;
pcb->lastack = ackno;
pcb->snd_wnd = tcphdr->wnd;
pcb->snd_wnd_max = tcphdr->wnd;
pcb->snd_wl1 = seqno - 1;
pcb->state = ESTABLISHED;
#if TCP_CALCULATE_EFF_SEND_MSS
pcb->mss = tcp_eff_send_mss(pcb->mss, &(pcb->remote_ip));
#endif
pcb->ssthresh = pcb->mss * 10;
pcb->cwnd = ((pcb->cwnd == 1) ? (pcb->mss * 2) : pcb->mss);
LWIP_ASSERT("pcb->snd_queuelen > 0", (pcb->snd_queuelen > 0));
--pcb->snd_queuelen;
LWIP_DEBUGF(TCP_QLEN_DEBUG, ("tcp_process: SYN-SENT --queuelen %"U16_F"\n", (u16_t)pcb->snd_queuelen));
rseg = pcb->unacked;
pcb->unacked = rseg->next;
tcp_seg_free(rseg);
if(pcb->unacked == NULL)
pcb->rtime = -1;
else {
pcb-
>rtime = 0;
pcb->nrtx = 0;
TCP_EVENT_CONNECTED(pcb, ERR_OK, err);
if (err == ERR_ABRT) {
return ERR_ABRT;
tcp_ack_now(pcb);
else if (flags & TCP_ACK) {
tcp_rst(ackno, seqno + tcplen, ip_current_dest_addr(), ip_current_src_addr(),
tcphdr->dest, tcphdr->src);
break;
SYN_SENT (此状态是客户端发送完SYN握手请求后的状态,等待SYN+ACK)
一、如果收到②SYN+ACK,且对确认序号ackno进行校验,符合预期。设置出口参数,
控制块进入ESTABLISHED状态,对控制块中unacked进行更新。调用connected函数(回调用tcp_enqueue_flags将握手报文段放到TCP控制块的unsent队列上。调用tcp_output(会调用tcp_output_segment判断报文是否在允许的发送窗口内,并将报文段从unsent转移到unacked队列上))发送ACK,结束三次握手。
二、如果收到的是ACK,返回一个RST
case SYN_RCVD:
if (flags & TCP_ACK) {
if (TCP_SEQ_BETWEEN(ackno, pcb->lastack+1, pcb->snd_nxt)) {
u16_t old_cwnd;
pcb->state = ESTABLISHED;
LWIP_DEBUGF(TCP_DEBUG, ("TCP connection established %"U16_F" -> %"U16_F".\n", inseg.tcphdr->src, inseg.tcphdr->dest));
#if LWIP_CALLBACK_API
LWIP_ASSERT("pcb->accept != NULL", pcb->accept != NULL);
#endif
TCP_EVENT_ACCEPT(pcb, ERR_OK, err);
if (err != ERR_OK) {
if (err != ERR_ABRT) {
tcp_abort(pcb);
return ERR_ABRT;
old_cwnd = pcb->cwnd;
tcp_receive(pcb);
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);
pcb->state = CLOSE_WAIT;
} else {
tcp_rst(ackno, seqno + tcplen, ip_current_dest_addr(), ip_current_src_addr(),
tcphdr->dest, tcphdr->src);
} else if ((flags & TCP_SYN) && (seqno == pcb->rcv_nxt - 1)) {
tcp_rexmit(pcb);
break;
SYN_RCVD(此状态是服务器发送完SYN+ACK后的状态,等待ACK)
一、 如果收到③ACK,且确认序号ackno合法
控制块进入ESTABLISHED状态,开始接收数据。调用用户的accept函数(调用出错就关闭当前连接,返回出错),保持旧的阻塞窗口,调用函数tcp_receive处理报文中的数据,同时当本地的unacked被报文中的ACK确认时,重新设置阻塞窗口。
数据传输过程中,当接收到④FIN握手,响应对方的FIN握手,并回复一个ACK⑤,并且控制块进入CLOSE_WAIT状态。
二、 如果收到①SYN握手报文,说明自己发的②SYN+ACK丢失,重传②SYN+ACK
case CLOSE_WAIT:
CLOSE_WAIT (此状态是服务器处于半关闭状态)
case ESTABLISHED:
tcp_receive(pcb);
if (recv_flags & TF_GOT_FIN) {
tcp_ack_now(pcb);
pcb->state = CLOSE_WAIT;
break;
ESTABLISHED (握手建立后,传输数据的过程中)
一、接收到④FIN握手,响应对方的FIN握手,并回复一个ACK⑤,并且控制块进入CLOSE_WAIT状态。
case FIN_WAIT_1:
tcp_receive(pcb);
if (recv_flags & TF_GOT_FIN) {
if ((flags & TCP_ACK) && (ackno == pcb->snd_nxt)) {
LWIP_DEBUGF(TCP_DEBUG,
("TCP connection closed: FIN_WAIT_1 %"U16_F" -> %"U16_F".\n", inseg.tcphdr->src, inseg.tcphdr->dest));
tcp_ack_now(pcb);
tcp_pcb_purge(pcb);
TCP_RMV_ACTIVE(pcb);
pcb->state = TIME_WAIT;
TCP_REG(&tcp_tw_pcbs, pcb);
} else {
tcp_ack_now(pcb);
pcb->state = CLOSING;
} else if ((flags & TCP_ACK) && (ackno == pcb->snd_nxt)) {
pcb->state = FIN_WAIT_2;
break;
FIN_WAIT1 (上层应用调用tcp_close函数,并发送FIN后的状态)
继续调用tcp_receive函数处理报文中的数据
一、 收到一个⑥FIN握手,且包含有效ACK。则响应此FIN握手,回复一个ACK,开始消除连接中的所有数据。控制块从tcp_active_pcb链表中删除,控制块进入TIME_WAIT状态,控制块加入到tcp_tw_pcbs链表中
收到的这个FIN握手也可能是④,即双方同时执行关闭操作,则此时返回ACK,进入CLOSING状态
二、 收到一个只收到⑤ACK,则进入FIN_WAIT2状态
case FIN_WAIT_2:
tcp_receive(pcb);
if (recv_flags & TF_GOT_FIN) {
LWIP_DEBUGF(TCP_DEBUG, ("TCP connection closed: FIN_WAIT_2 %"U16_F" -> %"U16_F".\n", inseg.tcphdr->src, inseg.tcphdr->dest));
tcp_ack_now(pcb);
tcp_pcb_purge(pcb);
TCP_RMV_ACTIVE(pcb);
pcb->state = TIME_WAIT;
TCP_REG(&tcp_tw_pcbs, pcb);
break;
FIN_WAIT2 (主动关闭,发送FIN握手且接受到ACK后的状态)
继续调用tcp_receive函数处理报文中的数据
一、收到一个⑥FIN握手,则响应此FIN握手,回复一个ACK,开始消除连接中的所有数据。控制块从tcp_active_pcb链表中删除,控制块进入TIME_WAIT状态,控制块加入到tcp_tw_pcbs链表中
case CLOSING:
tcp_receive(pcb);
if (flags & TCP_ACK && ackno == pcb->snd_nxt) {
LWIP_DEBUGF(TCP_DEBUG, ("TCP connection closed: CLOSING %"U16_F" -> %"U16_F".\n", inseg.tcphdr->src, inseg.tcphdr->dest));
tcp_pcb_purge(pcb);
TCP_RMV_ACTIVE(pcb);
pcb->state = TIME_WAIT;
TCP_REG(&tcp_tw_pcbs, pcb);
break;
CLOSING (双方同时执行主动关闭后的状态)
继续调用tcp_receive函数处理报文中的数据
当收到有效ACK,开始消除连接中的所有数据。控制块从tcp_active_pcb链表中删除,控制块进入TIME_WAIT状态,控制块加入到tcp_tw_pcbs链表中
case LAST_ACK:
tcp_receive(pcb);
if (flags & TCP_ACK && ackno == pcb->snd_nxt) {
LWIP_DEBUGF(TCP_DEBUG, ("TCP connection closed: LAST_ACK %"U16_F" -> %"U16_F".\n", inseg.tcphdr->src, inseg.tcphdr->dest));
recv_flags |= TF_CLOSED;
break;
default:
break;
LAST_ACK
继续调用tcp_receive函数处理报文中的数据
当收到有效ACK,设置recv_flags为TF_CLOSED,返回tcp_input对该控制块进行释放和清除工作。
一、三次握手TCP状态。客户端:1.SYN_SENT 2.ESTABLISHED 服务端:1.SYN_RECVD 2.ESTABLISHED.
1.首先由客户端发起TCP连接,第一个报文段,TCP头部SYN位置为1,随机生成一个ISN, MSS数值(最大报文段 <= 1460),客户端窗口大小,客户端状态变为SYN_SENT。
2.服务端接收到SYN报文段,状...
lwip从逻辑上看也是分为4层:链路层、网络层(IP、ARP、(ICMP、IGMP这两个协议是网络层的补充协议,并不严格属于网络层))、传输层(TCP、UDP)、应用层,基本等同TCP/IP,只是各层之间可以进行交叉存取,没有严格划分。
协议汇总:
1. ARP协议:根据IP地址获取物理地址MAC的一个TCP/IP协议
一个典型的lwip系统包含3个进程:首先是上层应用程序进程,然后
这里只讲raw编程,如果你使用LWIP-socket或netconn,那就不存在这个问题,这些高级函数会自己在合适的地方调用tcp_recved函数。
问题总是重复的。对于初学者而言,有不少网络上流传的源码,一个典型的例子是tcp_echoserver。各个教学的资料中也都提供源码。但很可惜,这些教学板提供的源码缺乏可移植性。一具最常见的错误在于,大家对于什么时候调用tcp_reved没什么概念...
(tcp的收发与接收窗口/发送窗口/通告窗口关联比较紧密,接收/发送过程在《TCP/IP传输层协议实现 - TCP接收窗口/发送窗口/通告窗口(lwip)》https://blog.csdn.net/arm7star/article/details/117153533都有介绍,本文对收发过程进行更详细一步介绍。)
1、滑动窗口
1.1、接收窗口(接收滑动窗口)
接收窗口是本地可以接收数据的窗口,接收端只接收窗口内的数据,窗口外的丢弃。
接收到数据,接收窗口左边沿右移,接收窗口减小。
文章目录一,简介二,代码流程1,更新发送窗口2,快速重传与恢复3,拥塞控制算法4,更新unacked队列5,更新unsent队列6,rtt测试7,裁剪接收的数据a,接收的数据一部分已经接受过,另一部分是新数据b,数据正好在接收窗口左边界c,数据不在左边界
一个有着接近900行代码的函数,庞大的函数,需要我们有庖丁解牛的耐心
tcp_receive(struct tcp_pcb *pcb)用于处理接收到的一个报文,更新发送窗口,执行快速重传,拥塞控制算法,更新三个队列,由接收窗口来裁剪出有效数据
服务器端接收到SYN握手包,向客户端返回带SYN和ACK的握手包,并将相应TCP控制块置为SYN_RCVD状态,并挂在tcp_active_pcbs链表上。以后,继续等待客户端发送过来的握手包,这次,服务器期望的是接收一个ACK包以完成建立连接要求的三次握手操作。
还是和前几次一样,数据包进来通过ip_input传递给tcp_input,后者在三个链表中查找一个匹配的连接控制块。这次进来的是客户端发送的ACK握手包,服务器端相应的tcp控制块一定是在tcp_active_pcbs链表上。接下来,以查
接上文:TCP是传输层协议,它为应用程序提供可靠字节流服务。TCP相比于其他协议更加复杂,TCP代码量是整个LWIP协议栈代码的50%。基本的TCP处理被分为6个函数来完成(如图9所示)。函数tcp_input()、tcp_process()以及tcp_receive()与TCP输入处理相关,相应的tcp_write()、tcp_enqueue()以及tcp_output()处理输出相关。 当应用程序想要发送TCP数据时,调用tcp_write()。函数tcp_write()传输控制给tcp_enqueue