热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

lwip源码解析之TCP协议定时器tcp_slowtmr();和tcp_fasttmr();

文章目录一,定时器时钟二,快速定时任务三,低速定时任务1,超时重传2,保活keepalive3,


文章目录

    • 一,定时器时钟
    • 二,快速定时任务
    • 三,低速定时任务
      • 1,超时重传
      • 2,保活keepalive
      • 3,删除超时PCB
    • 四,小结


TCP协议中许多地方是需要使用到定时功能的,如定时重传功能,保活keepalive功能,坚持定时器功能,这些定时功能会在lwip中的两个定时器函数中实现。



一,定时器时钟


二,快速定时任务

void tcp_fasttmr(void)比较简单,它的功能主要是每250ms处理延时发送的ack报文和fin报文,同时通知上层应用处理数据。

void
tcp_fasttmr(void)
{struct tcp_pcb *pcb;++tcp_timer_ctr;tcp_fasttmr_start:pcb = tcp_active_pcbs; //在active中遍历while (pcb != NULL) {if (pcb->last_timer != tcp_timer_ctr) {struct tcp_pcb *next;pcb->last_timer = tcp_timer_ctr;//发送延时的ackif (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);}//发送延时的finif (pcb->flags & TF_CLOSEPEND) {LWIP_DEBUGF(TCP_DEBUG, ("tcp_fasttmr: pending FIN\n"));pcb->flags &= ~(TF_CLOSEPEND);tcp_close_shutdown_fin(pcb);}next = pcb->next;//若当前tcp有未被上层应用接收的数据if (pcb->refused_data != NULL) {tcp_active_pcbs_changed = 0;tcp_process_refused_data(pcb); //通过回调函数使上层处理数据if (tcp_active_pcbs_changed) {goto tcp_fasttmr_start;}}pcb = next; //下一个} else {pcb = pcb->next;}}
}

三,低速定时任务

void tcp_slowtmr(void)每500ms调用,该函数完成了超时重传,tcp保活功能,并会遍历activetimewait链表的PCB,删除那些超时或者出错的PCB,同时将PCB中unsent队列中的数据发送出去。一般使用tcp_write();写入数据后,数据不会马上发送,而是在定时任务中发送。


1,超时重传

重点的代码注释如下:

//请求连接次数超出限制if (pcb->state == SYN_SENT && pcb->nrtx >= TCP_SYNMAXRTX) {++pcb_remove; //移除增加LWIP_DEBUGF(TCP_DEBUG, ("tcp_slowtmr: max SYN retries reached\n"));}//数据重发次数超出限制else if (pcb->nrtx >= TCP_MAXRTX) {++pcb_remove;LWIP_DEBUGF(TCP_DEBUG, ("tcp_slowtmr: max DATA retries reached\n"));} else {//如果坚持定时器已经开启if (pcb->persist_backoff > 0) {//获取坚持定时器触发值u8_t backoff_cnt = tcp_persist_backoff[pcb->persist_backoff-1];//坚持定时器不超过触发值,则加1if (pcb->persist_cnt < backoff_cnt) {pcb->persist_cnt++;}//坚持定时器触发,发送窗口探查if (pcb->persist_cnt >= backoff_cnt) {if (tcp_zero_window_probe(pcb) == ERR_OK) {pcb->persist_cnt = 0; //发送成功,清除计数值if (pcb->persist_backoff < sizeof(tcp_persist_backoff)) {pcb->persist_backoff++; //发送探查次数+1}}}} else { //无开启坚持定时器//如果开启了超时重传定时器,则加1if (pcb->rtime >= 0) {++pcb->rtime;}//有未确认报文且重传时间到,要重传了if (pcb->unacked != NULL && pcb->rtime >= pcb->rto) {LWIP_DEBUGF(TCP_RTO_DEBUG, ("tcp_slowtmr: rtime %"S16_F" pcb->rto %"S16_F"\n",pcb->rtime, pcb->rto));ESP_STATS_TCP_PCB(pcb);if (pcb->state != SYN_SENT) {u8_t backoff_idx = LWIP_MIN(pcb->nrtx, sizeof(tcp_backoff)-1); //获得重传次数,但重传次数不会超过7//动态设置rto,每次超时后,rto时间会增加pcb->rto = ((pcb->sa >> 3) + pcb->sv) << tcp_backoff[backoff_idx]; }pcb->rtime = 0; //重置超时重传定时器//TODO 出现重传,说明报文丢失了,可能是网络出现阻塞,减小拥塞窗口eff_wnd = LWIP_MIN(pcb->cwnd, pcb->snd_wnd);pcb->ssthresh = eff_wnd >> 1; //ssthresh减少到拥塞窗口的一半//若ssthresh比最大报文长度的两倍还小,后者的数值(限制了ssthresh的最小值)if (pcb->ssthresh < (tcpwnd_size_t)(pcb->mss << 1)) {pcb->ssthresh = (pcb->mss << 1);}pcb->cwnd = pcb->mss; //拥塞窗口设置成最大报文长度,一个报文长度LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_slowtmr: cwnd %"TCPWNDSIZE_F" ssthresh %"TCPWNDSIZE_F"\n",pcb->cwnd, pcb->ssthresh));//重传报文tcp_rexmit_rto(pcb);}}}if (pcb->state == FIN_WAIT_2) {//TODO 处于FIN_WAIT_2的时间太长(由于对方长时间无反应)则删除if (pcb->flags & TF_RXCLOSED) {/* PCB was fully closed (either through close() or SHUT_RDWR):normal FIN-WAIT timeout handling. */if ((u32_t)(tcp_ticks - pcb->tmr) >TCP_FIN_WAIT_TIMEOUT / TCP_SLOW_INTERVAL) {++pcb_remove;LWIP_DEBUGF(TCP_DEBUG, ("tcp_slowtmr: removing pcb stuck in FIN-WAIT-2\n"));}}}

2,保活keepalive

服务端需要检查客户端是否还能通信,若两小时内无通信,客户端发送探查报文,若客户端ack,则更新保活计时器,否则,每隔75s发送一个探查报文,若发送超过9个报文,则认为客户端已挂掉

/* Check if KEEPALIVE should be sent *///服务端需要检查客户端是否还能通信,若两小时内无通信,客户端发送探查报文,若客户端ack,则更新保活计时器//否则,每隔75s发送一个探查报文,若发送超过9个报文,则认为客户端已挂掉if (ip_get_option(pcb, SOF_KEEPALIVE) &&((pcb->state == ESTABLISHED) ||(pcb->state == CLOSE_WAIT))) {//发送9个以上探查报文,对方仍无反应,应该关闭tcpif ((u32_t)(tcp_ticks - pcb->tmr) >(pcb->keep_idle + TCP_KEEP_DUR(pcb)) / TCP_SLOW_INTERVAL){LWIP_DEBUGF(TCP_DEBUG, ("tcp_slowtmr: KEEPALIVE timeout. Aborting connection to "));ip_addr_debug_print(TCP_DEBUG, &pcb->remote_ip);LWIP_DEBUGF(TCP_DEBUG, ("\n"));++pcb_remove;++pcb_reset; //复位对方} else if ((u32_t)(tcp_ticks - pcb->tmr) >(pcb->keep_idle + pcb->keep_cnt_sent * TCP_KEEP_INTVL(pcb))/ TCP_SLOW_INTERVAL) //发送探查报文{err = tcp_keepalive(pcb); if (err == ERR_OK) {pcb->keep_cnt_sent++; //探查次数加一}}}

3,删除超时PCB

//若pcb的osseq队列中无序的数据超过一定时长会被丢弃if (pcb->ooseq != NULL &&(u32_t)tcp_ticks - pcb->tmr >= pcb->rto * TCP_OOSEQ_TIMEOUT) {tcp_segs_free(pcb->ooseq);pcb->ooseq = NULL;LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_slowtmr: dropping OOSEQ queued data\n"));}//一直等不到服务端回答的tcp要被移除if (pcb->state == SYN_RCVD) {if ((u32_t)(tcp_ticks - pcb->tmr) >TCP_SYN_RCVD_TIMEOUT / TCP_SLOW_INTERVAL) {++pcb_remove;LWIP_DEBUGF(TCP_DEBUG, ("tcp_slowtmr: removing pcb stuck in SYN-RCVD\n"));}}//在last_ack超过一定时间也被删除if (pcb->state == LAST_ACK) {if ((u32_t)(tcp_ticks - pcb->tmr) > 2 * TCP_MSL / TCP_SLOW_INTERVAL) {++pcb_remove;LWIP_DEBUGF(TCP_DEBUG, ("tcp_slowtmr: removing pcb stuck in LAST-ACK\n"));}}

以上的代码中若当前PCB需要被删除,则pcb_remove不为0,具体的删除代码如下:

if (pcb_remove) { //不为0则需要删除pcbstruct tcp_pcb *pcb2;tcp_err_fn err_fn = pcb->errf; //错误回调函数void *err_arg;enum tcp_state last_state;tcp_pcb_purge(pcb); //释放pcb部分成员,pcb结构体不会被释放if (prev != NULL) {LWIP_ASSERT("tcp_slowtmr: middle tcp != tcp_active_pcbs", pcb != tcp_active_pcbs);prev->next = pcb->next;} else {/* This PCB was the first. */LWIP_ASSERT("tcp_slowtmr: first pcb == tcp_active_pcbs", tcp_active_pcbs == pcb);tcp_active_pcbs = pcb->next;}//需要发送重置报文if (pcb_reset) {tcp_rst(pcb->snd_nxt, pcb->rcv_nxt, &pcb->local_ip, &pcb->remote_ip,pcb->local_port, pcb->remote_port);}err_arg = pcb->callback_arg;last_state = pcb->state;pcb2 = pcb;pcb = pcb->next; //获取下一个pcbmemp_free(MEMP_TCP_PCB, pcb2); //释放pcb结构体tcp_active_pcbs_changed = 0;TCP_EVENT_ERR(last_state, err_fn, err_arg, ERR_ABRT); //调用错误回调函数if (tcp_active_pcbs_changed) {goto tcp_slowtmr_start;}} else {//不需要删除pcbprev = pcb;pcb = pcb->next; //获取下一个pcb//TODO 定时周期到,调用回调函数++prev->polltmr;if (prev->polltmr >= prev->pollinterval) {prev->polltmr = 0;LWIP_DEBUGF(TCP_DEBUG, ("tcp_slowtmr: polling application\n"));tcp_active_pcbs_changed = 0;TCP_EVENT_POLL(prev, err); //!回调周期性函数if (tcp_active_pcbs_changed) {goto tcp_slowtmr_start;}if (err == ERR_OK) {tcp_output(prev); //输出unsent报文}}}}

以上都是处理active链表的pcb,接下来处理timewait的pcb,苍天饶过谁?

/*--------------------------------遍历所有timewait的pcb-----------------------*//* Steps through all of the TIME-WAIT PCBs. */prev = NULL;pcb = tcp_tw_pcbs;while (pcb != NULL) {LWIP_ASSERT("tcp_slowtmr: TIME-WAIT pcb->state == TIME-WAIT", pcb->state == TIME_WAIT);pcb_remove = 0;//是否达到2MSL,是则删除if ((u32_t)(tcp_ticks - pcb->tmr) > 2 * TCP_MSL / TCP_SLOW_INTERVAL) {++pcb_remove;}if (pcb_remove) {struct tcp_pcb *pcb2;tcp_pcb_purge(pcb);/* Remove PCB from tcp_tw_pcbs list. */if (prev != NULL) {LWIP_ASSERT("tcp_slowtmr: middle tcp != tcp_tw_pcbs", pcb != tcp_tw_pcbs);prev->next = pcb->next;} else {/* This PCB was the first. */LWIP_ASSERT("tcp_slowtmr: first pcb == tcp_tw_pcbs", tcp_tw_pcbs == pcb);tcp_tw_pcbs = pcb->next;}pcb2 = pcb;pcb = pcb->next;memp_free(MEMP_TCP_PCB, pcb2);} else {prev = pcb;pcb = pcb->next;}}

四,小结

tcp定时任务是tcp接收发送的动力来源,需要关注。
在这里插入图片描述


推荐阅读
  • 扫描线三巨头 hdu1928hdu 1255  hdu 1542 [POJ 1151]
    学习链接:http:blog.csdn.netlwt36articledetails48908031学习扫描线主要学习的是一种扫描的思想,后期可以求解很 ... [详细]
  • 前言--页数多了以后需要指定到某一页(只做了功能,样式没有细调)html ... [详细]
  • 本文探讨了 Spring Boot 应用程序在不同配置下支持的最大并发连接数,重点分析了内置服务器(如 Tomcat、Jetty 和 Undertow)的默认设置及其对性能的影响。 ... [详细]
  • golang常用库:配置文件解析库/管理工具viper使用
    golang常用库:配置文件解析库管理工具-viper使用-一、viper简介viper配置管理解析库,是由大神SteveFrancia开发,他在google领导着golang的 ... [详细]
  • 本文详细介绍了 Dockerfile 的编写方法及其在网络配置中的应用,涵盖基础指令、镜像构建与发布流程,并深入探讨了 Docker 的默认网络、容器互联及自定义网络的实现。 ... [详细]
  • ImmutableX Poised to Pioneer Web3 Gaming Revolution
    ImmutableX is set to spearhead the evolution of Web3 gaming, with its innovative technologies and strategic partnerships driving significant advancements in the industry. ... [详细]
  • 题目Link题目学习link1题目学习link2题目学习link3%%%受益匪浅!-----&# ... [详细]
  • Linux设备驱动程序:异步时间操作与调度机制
    本文介绍了Linux内核中的几种异步延迟操作方法,包括内核定时器、tasklet机制和工作队列。这些机制允许在未来的某个时间点执行任务,而无需阻塞当前线程,从而提高系统的响应性和效率。 ... [详细]
  • 深入探讨CPU虚拟化与KVM内存管理
    本文详细介绍了现代服务器架构中的CPU虚拟化技术,包括SMP、NUMA和MPP三种多处理器结构,并深入探讨了KVM的内存虚拟化机制。通过对比不同架构的特点和应用场景,帮助读者理解如何选择最适合的架构以优化性能。 ... [详细]
  • 实体映射最强工具类:MapStruct真香 ... [详细]
  • 作者:守望者1028链接:https:www.nowcoder.comdiscuss55353来源:牛客网面试高频题:校招过程中参考过牛客诸位大佬的面经,但是具体哪一块是参考谁的我 ... [详细]
  • Explore how Matterverse is redefining the metaverse experience, creating immersive and meaningful virtual environments that foster genuine connections and economic opportunities. ... [详细]
  • 1.如何在运行状态查看源代码?查看函数的源代码,我们通常会使用IDE来完成。比如在PyCharm中,你可以Ctrl+鼠标点击进入函数的源代码。那如果没有IDE呢?当我们想使用一个函 ... [详细]
  • 本文详细介绍了VMware的多种认证选项,帮助你根据职业需求和个人技能选择最合适的认证路径,涵盖从基础到高级的不同层次认证。 ... [详细]
  • 本题涉及一棵由N个节点组成的树(共有N-1条边),初始时所有节点均为白色。题目要求处理两种操作:一是改变某个节点的颜色(从白变黑或从黑变白);二是查询从根节点到指定节点路径上的第一个黑色节点,若无则输出-1。 ... [详细]
author-avatar
爱碩爱你_静莫失心
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有