lwip-数据的收发过程3-传输层

三、传输层(tcp_receive)

  • tcp_receive()函数

tcp_receive( )只会被tcp_process函数调用,用于进一步完成对输入报文的处理,具体来说,该函数主要是完成输入报文的冗余截断,管理unacked、unsent、ooseq三张链表

  
        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标志,置1表示是重复ack    
            
            // 首先检测报文是否包含ACK标志
            if (flags & TCP_ACK)
            {
                right_wnd_edge = pcb->snd_wl2 + pcb->snd_wnd;    // 获取本地发送窗口右边界
                
                // 有3种情况可以导致本地发送窗口更新
                if (TCP_SEQ_LT(pcb->snd_wl1, seqno)||                                // snd_wl1小于新seqno,说明对方有发来数据
                    (pcb->snd_wl1 == seqno && TCP_SEQ_LT(pcb->snd_wl2, ackno))||    // snd_wl1等于新seqno且snd_wl2小于新ackno,说明对方没有发送数据,只是在收到数据后发送一个确认
                    (pcb->snd_wl2 == ackno && tcphdr->wnd > pcb->snd_wnd))             // snd_wl2等于新ackno且snd_wnd小于报文首部的窗口通告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,需要满足5个条件
                // 1.如果ackno小于等于lastack,即没有确认新数据
                if (TCP_SEQ_LEQ(ackno, pcb->lastack))                         
                {
                    pcb->acked = 0;        // 没有确认新数据,那么acked为0
                    
                    // 2.如果报文段中没有数据
                    if (tcplen == 0)
                    {
                        // 3.本地发送窗口没有更新
                        if (pcb->snd_wl2 + pcb->snd_wnd == right_wnd_edge)
                        {
                            // 4.如果重传定时器正在运行,即本地有数据正等待被确认
                            if (pcb->rtime >= 0)
                            {
                                // 5.如果ackno等于lastack
                                if (pcb->lastack == ackno)
                                {
                                    // 此时可以确定这是一个重复的ack,说明报文发生了丢失
                                    found_dupack = 1;
                                    // 该ack被重复收到的次数自增
                                    if (pcb->dupacks + 1 > pcb->dupacks)
                                        ++pcb->dupacks;
                                    // 如果该ack重复收到超过3次,说明发生了拥塞
                                    if (pcb->dupacks > 3)
                                    {
                                        if ((u16_t)(pcb->cwnd + pcb->mss) > pcb->cwnd)
                                        {
                                            pcb->cwnd += pcb->mss;
                                        }
                                    }
                                    // 如果该ack重复第3次收到,执行快速重传算法
                                    else if (pcb->dupacks == 3)
                                    {
                                        tcp_rexmit_fast(pcb);
                                    }
                                }
                            }
                        }
                    }
                    
                    // 如果没有确认新数据但又不属于重复ack
                    if (!found_dupack)
                    {
                        pcb->dupacks = 0;        // 将ack重复收到的次数清0
                      }
                }
                // 如果是正常情况的ACK,lastack+1<=ackno<=snd_nxt
                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;                                // 重传次数清0
                    pcb->rto = (pcb->sa >> 3) + pcb->sv;        // 复位重传超时时间
                    pcb->acked = (u16_t)(ackno - pcb->lastack);    // 更新acked字段为被确认的已发送数据长度
                    pcb->snd_buf += pcb->acked;                    // 更新可用的发送空间
                    pcb->dupacks = 0;                            // 将ack重复收到的次数清0
                    pcb->lastack = ackno;                        // 更新接收到的ackno
                    
                    // 如果处于TCP连接已经建立状态,调整拥塞算法功能模块
                    if (pcb->state >= ESTABLISHED)
                    {
                        if (pcb->cwnd < pcb->ssthresh)
                        {
                              if ((u16_t)(pcb->cwnd + pcb->mss) > pcb->cwnd)
                            {
                                pcb->cwnd += pcb->mss;
                              }
                        }
                        else
                        {
                              u16_t new_cwnd = (pcb->cwnd + pcb->mss * pcb->mss / pcb->cwnd);
                              if (new_cwnd > pcb->cwnd)
                            {
                                pcb->cwnd = new_cwnd;
                              }
                        }
                    }
                    
                    // 遍历unacked队列,将所有数据编号小于等于ackno的报文段移除
                    while (pcb->unacked != NULL && TCP_SEQ_LEQ(ntohl(pcb->unacked->tcphdr->seqno) + TCP_TCPLEN(pcb->unacked), ackno))
                    {      
                        // 将满足要求的报文从unacked链表取出
                        next = pcb->unacked;
                        pcb->unacked = pcb->unacked->next;
                        
                        // 如果该报文包含FIN标志,意味着当前收到的ACK对FIN做了确认,则acked字段减1,即不需要提交上层使知道FIN被对方成功接收
                        if ((pcb->acked != 0) && ((TCPH_FLAGS(next->tcphdr) & TCP_FIN) != 0))
                        {
                              pcb->acked--;
                        }
                        
                        pcb->snd_queuelen -= pbuf_clen(next->p);        // 释放被该报文占用的发送空间
                        tcp_seg_free(next);                                // 释放被该报文占用的tcp报文段
                    }
                    
                    // 当所有满足要求的报文段移除成功后,判断unacked队列是否为空
                    if(pcb->unacked == NULL)
                        pcb->rtime = -1;    // 若为空,关闭重传定时器
                    else
                        pcb->rtime = 0;        // 否则复位重传定时器
        
                    pcb->polltmr = 0;        // 复位轮询定时器
                }        
                // 如果该ACK既不是重复ACK,又不是正常ACK,则acked字段清0,即该ACK不确认任何已发送数据
                else
                {
                    pcb->acked = 0;
                }
                
                // 遍历unsent队列,将所有数据编号小于等于ackno的报文段移除
                // 这是因为对于需要重传的报文段,lwip直接将它们挂在unsent队列上,所以收到的ACK可能是对已超时报文段的确认
                while (pcb->unsent != NULL && TCP_SEQ_BETWEEN(ackno, ntohl(pcb->unsent->tcphdr->seqno) + TCP_TCPLEN(pcb->unsent), pcb->snd_nxt))
                {
                    // 将满足要求的报文从unsent链表取出
                    next = pcb->unsent;
                    pcb->unsent = pcb->unsent->next;
                    
                    // 如果该报文包含FIN标志,意味着当前收到的ACK对FIN做了确认,则acked字段减1,即不需要提交上层使知道FIN被对方成功接收
                    if ((pcb->acked != 0) && ((TCPH_FLAGS(next->tcphdr) & TCP_FIN) != 0))
                    {
                        pcb->acked--;
                    }
                    
                    pcb->snd_queuelen -= pbuf_clen(next->p);        // 释放被该报文占用的发送空间
                    tcp_seg_free(next);                                // 释放被该报文占用的tcp报文段
                }
                
                // RTT计算,暂略
                if (pcb->rttest && TCP_SEQ_LT(pcb->rtseq, ackno))
                {
                    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;    
                    pcb->rttest = 0;
                }
            }
            
            // 如果该输入报文还包含了数据,则要继续对数据进行处理
            if (tcplen > 0)
            {
                // 如果seqno + 1 <= rcv_nxt <= seqno + tcplen - 1,意味着收到的数据区域头部有无效数据(收到的数据有部分处于本地左侧接收窗口外),需要截断数据头
                if (TCP_SEQ_BETWEEN(pcb->rcv_nxt, seqno + 1, seqno + tcplen - 1))
                {
                    off = pcb->rcv_nxt - seqno;                            // 需要截掉的数据长度
                    p = inseg.p;                                        // 获取收到的报文段的pbuf链表头
                    
                    // 判断需要截断的长度是否超出了第一个pbuf中存储的数据长度
                    if (inseg.p->len < off)
                    {
                        new_tot_len = (u16_t)(inseg.p->tot_len - off);    // 截断重复数据后的有效数据长度
 
                        // 如果超出,则需要遍历pbuf链表,依次摘除数据,直到最后一个包含摘除数据的pbuf
                        while (p->len < off)
                        {
                            off -= p->len;                                // 剩余摘除长度
                            p->tot_len = new_tot_len;                    // 更新当前pbuf中的数据总长,
                            p->len = 0;                                    // 因为数据被摘除,所以当前pbuf中的数据分长清0
                            p = p->next;                                // 指向下一个pbuf
                        }
                        
                        // 处理最后一个包含摘除数据的pbuf,就是调整数据指针略过摘除数据
                        pbuf_header(p, (s16_t)-off);
                    }
                    else
                    {
                        // 如果未超出,则调整第一个pbuf中的数据指针略过摘除数据
                        pbuf_header(inseg.p, (s16_t)-off);
                    }
                    
                    inseg.len -= (u16_t)(pcb->rcv_nxt - seqno);    // 更新TCP报文段数据总长
                    inseg.tcphdr->seqno = seqno = pcb->rcv_nxt;    // 更新TCP头中的seqno,指向接收窗口头位置
                }
                else
                {
                    // 如果seqno < rcv_nxt,意味着seqno+tcplen-1 < rcv_nxt,说明这是个完全重复的报文段
                    if (TCP_SEQ_LT(seqno, pcb->rcv_nxt))
                    {
                        tcp_ack_now(pcb);        // 只回复一个ACK给对方(这里是否应该直接返回不再运行下去)
                    }
                }
                
                // 如果数据起始编号在接收窗口内
                if (TCP_SEQ_BETWEEN(seqno, pcb->rcv_nxt, pcb->rcv_nxt + pcb->rcv_wnd - 1))
                {
                    // 如果该报文数据处于接收起始位置,意味着该报文是连续到来的
                    if (pcb->rcv_nxt == seqno)
                    {
                        tcplen = TCP_TCPLEN(&inseg);        // 更新该报文的总数据长度
 
                        // 如果总长大于接收窗口大小,就需要做尾部截断处理,这里包含对FIN和SYN两种标志的不同处理结果,注意体会
                        if (tcplen > pcb->rcv_wnd)
                        {
                            // 如果TCP头中带FIN标志,清除FIN标志,因为对方还有数据要发过来
                            if (TCPH_FLAGS(inseg.tcphdr) & TCP_FIN)
                            {
                                TCPH_FLAGS_SET(inseg.tcphdr, TCPH_FLAGS(inseg.tcphdr) &~ TCP_FIN);
                            }
                            
                            inseg.len = pcb->rcv_wnd;        // 根据接收窗口调整数据长度
 
                            // 如果TCP头中带SYN标志,报文段数据长度减1
                            if (TCPH_FLAGS(inseg.tcphdr) & TCP_SYN)
                            {
                                inseg.len -= 1;
                            }
                            
                            pbuf_realloc(inseg.p, inseg.len);    //  因为数据被截断,pbuf中的参数需要相应调整
                            tcplen = TCP_TCPLEN(&inseg);        // 再次更新该报文的总数据长度
                        }
                        
                        // 如果无序报文段队列ooseq上存在报文段
                        if (pcb->ooseq != NULL)
                        {
                            // 判断当前有序报文段的TCP头中是否带FIN标志
                            if (TCPH_FLAGS(inseg.tcphdr) & TCP_FIN)
                            {
                                // 如果该有序报文段带FIN标志,意味着单向TCP连接结束
                                // 不可能再从对方收到新的报文段,ooseq队列中的报文段没有成为有序报文段可能,只能作废
                                while (pcb->ooseq != NULL)
                                {
                                  struct tcp_seg *old_ooseq = pcb->ooseq;
                                  pcb->ooseq = pcb->ooseq->next;
                                  tcp_seg_free(old_ooseq);
                                }
                            }
                            else
                            {
                                next = pcb->ooseq;
                                // 遍历ooseq链表,删除序号被当前有序报文段完全覆盖的报文段
                                while (next && TCP_SEQ_GEQ(seqno + tcplen,next->tcphdr->seqno + next->len))
                                {
                                    // 如果这些即将被删除的报文段带FIN标志且当前有序报文段不带SYN标志
                                    if (TCPH_FLAGS(next->tcphdr) & TCP_FIN &&(TCPH_FLAGS(inseg.tcphdr) & TCP_SYN) == 0)
                                    {
                                        TCPH_SET_FLAG(inseg.tcphdr, TCP_FIN);    // 在当前有效报文段的TCP头中添加FIN标志
                                        tcplen = TCP_TCPLEN(&inseg);            // 再次更新该报文的总数据长度
                                    }
                                    prev = next;
                                    next = next->next;
                                    tcp_seg_free(prev);
                                }
                                
                                // 如果当前有序报文段尾部与ooseq中的报文段存在部分重叠    
                                if (next && TCP_SEQ_GT(seqno + tcplen,next->tcphdr->seqno))
                                {
                                    inseg.len = (u16_t)(next->tcphdr->seqno - seqno);    // 截断当前有序报文段尾部的重叠部分,得到有效部分长度
                                    
                                    // 如果当前有序报文段TCP头中带SYN标志,报文段数据长度减1
                                    if (TCPH_FLAGS(inseg.tcphdr) & TCP_SYN)
                                    {
                                        inseg.len -= 1;
                                    }
                                    
                                    pbuf_realloc(inseg.p, inseg.len);            //  因为数据被截断,pbuf中的参数需要相应调整
                                    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;        // 将全局指针recv_data指向报文段中的数据pbuf
                            inseg.p = NULL;
                        }
                        
                        // 如果该有序报文段的TCP头中带FIN标志
                        if (TCPH_FLAGS(inseg.tcphdr) & TCP_FIN)
                        {
                          recv_flags |= TF_GOT_FIN;        // 则在报文处理结果变量recv_flags添加TF_GOT_FIN标志
                        }
                        
                        // 遍历ooseq队列,取出所有有序的报文段
                        // (通过比较ooseq队列中报文段的seqno和当前TCP控制块中保存的rcv_nxt来判定该报文段是否有序)
                        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);        // 更新公告窗口
                            
                            // 如果该有序报文段中存在数据,则通过全局指针recv_data向上层提交数据
                            if (cseg->p->tot_len > 0)
                            {
                                // 判断全局指针recv_data是否为空
                                if (recv_data)
                                {
                                    // 如果不为空,意味着有更早的数据准备向上提交
                                    pbuf_cat(recv_data, cseg->p);    // 将当前数据pbuf挂到recv_data指向的数据链表的尾部
                                }
                                else
                                {
                                    // 如果为空,直接将当前数据pbuf赋给recv_data
                                    recv_data = cseg->p;
                                }
                                cseg->p = NULL;
                            }
                            
                            // 如果该有序报文段的TCP头中带FIN标志
                            if (TCPH_FLAGS(cseg->tcphdr) & TCP_FIN)
                            {
                                recv_flags |= TF_GOT_FIN;        // 则全局变量recv_flags添加TF_GOT_FIN标志
 
                                // 如果当前TCP处于ESTABLISHED状态,则变成CLOSE_WAIT状态
                                if (pcb->state == ESTABLISHED)
                                {
                                    pcb->state = CLOSE_WAIT;
                                }
                            }
                            
                            pcb->ooseq = cseg->next;
                            tcp_seg_free(cseg);
                        }
                        
                        // 以上都执行完毕后,向源端返回一个ACK,此处其实只是先在TCP控制块中添加ACK标志
                        tcp_ack(pcb);
                    }
                    // 如果该报文数据不处于接收起始位置,意味着该报文不是有序的
                    else
                    {
                        // 首先向源端返回一个立即ACK
                        tcp_send_empty_ack(pcb);
                        
                        // 然后将该报文段放入ooseq队列
                        if (pcb->ooseq == NULL)
                        {
                            // 如果ooseq为空,则拷贝该报文段到新开辟的报文段空间,并将新开辟报文段作为ooseq起始单元
                              pcb->ooseq = tcp_seg_copy(&inseg);
                        }
                        else
                        {
                            prev = NULL;    // 定义为ooseq链表中上一个报文段,这里首先清空
                            // 遍历ooseq队列,选择合适位置插入该报文段
                            for(next = pcb->ooseq; next != NULL; next = next->next)
                            {
                                // 依次比较两个报文段的起始序号seqno,如果相等
                                if (seqno == next->tcphdr->seqno)
                                {
                                    // 继续比较两个报文段的数据长度
                                    if (inseg.len > next->len)
                                    {
                                        // 如果输入报文段数据长度更长
                                        // 拷贝该报文段到新开辟的报文段空间
                                        cseg = tcp_seg_copy(&inseg);
                                        
                                        // 插入ooseq链表
                                        if (cseg != NULL)
                                        {
                                            // 如果不是ooseq上的第一个报文段
                                            if (prev != NULL)
                                            {
                                                prev->next = cseg;    // 插入ooseq链表的上一个报文段之后
                                            }
                                            // 如果是第一个
                                            else
                                            {
                                                pcb->ooseq = cseg;    // 直接替换原有的第一个
                                            }
                                            
                                            tcp_oos_insert_segment(cseg, next);    // 处理好插入后与原有的下一个报文段的影响,简单来说,就是切掉冗余,释放内存
                                        }
                                        break;    // 退出循环                            
                                    }
                                    else
                                    {
                                        // 如果输入报文段数据长度更短,则直接丢弃,并退出循环
                                        break;
                                    }
                                }
                                // 如果不相等
                                else
                                {
                                    // 如果是ooseq上的第一个报文段
                                    if (prev == NULL)
                                    {
                                        // 如果该报文段的起始序号大于要插入的报文段起始序号
                                        if (TCP_SEQ_LT(seqno, next->tcphdr->seqno))
                                        {
                                            cseg = tcp_seg_copy(&inseg);            // 拷贝要插入的报文段到新开辟的报文段空间
                                            if (cseg != NULL)
                                            {
                                                pcb->ooseq = cseg;                    // 将新报文段插到ooseq第一个位置
                                                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);                    // 因为数据被截断,pbuf中的参数需要相应调整
                                                }
                                                
                                                prev->next = cseg;                    // 将新报文段插入前一个报文段之后
                                                tcp_oos_insert_segment(cseg, next);    // 处理好插入后与原有的下一个报文段的影响
                                            }
                                            break;
                                        }
                                    }
                                    
                                    // 如果已经是ooseq上的最后一个报文段
                                    // 且待插入的报文段起始序号大于该报文起始序号(其实函数运行到这里该条件必然成立)
                                    if (next->next == NULL && TCP_SEQ_GT(seqno, next->tcphdr->seqno))
                                    {
                                        // 如果该报文的TCP头中有FIN标志,则直接丢弃待插入的报文段,退出循环
                                        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);                    // 因为数据被截断,pbuf中的参数需要相应调整
                                            }
                                            
                                            // 如果新插入的报文段数据长度超出了当前接收窗口大小
                                            if ((u32_t)tcplen + seqno > pcb->rcv_nxt + (u32_t)pcb->rcv_wnd)
                                            {
                                                // 如果新插入的报文段的TCP头中有FIN标志
                                                if (TCPH_FLAGS(next->next->tcphdr) & TCP_FIN)
                                                {
                                                    TCPH_FLAGS_SET(next->next->tcphdr, TCPH_FLAGS(next->next->tcphdr) &~ TCP_FIN);    // 去掉TCP头中的FIN标志
                                                }
                                                
                                                next->next->len = pcb->rcv_nxt + pcb->rcv_wnd - seqno;    // 根据接收窗口大小调制新插入的报文段数据长度
                                                pbuf_realloc(next->next->p, next->next->len);            // 因为数据被截断,pbuf中的参数需要相应调整
                                                tcplen = TCP_TCPLEN(next->next);                        // 再次更新该报文的总数据长度
                                            }
                                            
                                        }
                                        break;
                                    }
                                }
                                
                                prev = next;    // 以上都不满足,则遍历ooseq链表中下一个
                            }
                        }
                    }
                }
                // 如果数据不在接收范围内
                else
                {
                    tcp_send_empty_ack(pcb);    // 直接向源端返回一个立即确认ACK
                }
            }
            // 如果输入的报文段中不包含数据
            else
            {
                // 且序号位于接收窗口之内
                if(!TCP_SEQ_BETWEEN(seqno, pcb->rcv_nxt, pcb->rcv_nxt + pcb->rcv_wnd-1))
                {
                      tcp_ack_now(pcb);        // 回一个ACK
                }
            }
        }
        
        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)  ))
            {
                    
            }
        }
        
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

江鸟的坚持

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

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

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

打赏作者

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

抵扣说明:

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

余额充值