热门标签 | 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接收发送的动力来源,需要关注。
在这里插入图片描述


推荐阅读
  • 【爬虫】关于企业信用信息公示系统加速乐最新反爬虫机制
    ( ̄▽ ̄)~又得半夜修仙了,作为一个爬虫小白,花了3天时间写好的程序,才跑了一个月目标网站就更新了,是有点悲催,还是要只有一天的时间重构。升级后网站的层次结构并没有太多变化,表面上 ... [详细]
  • 本文介绍了如何使用Express App提供静态文件,同时提到了一些不需要使用的文件,如package.json和/.ssh/known_hosts,并解释了为什么app.get('*')无法捕获所有请求以及为什么app.use(express.static(__dirname))可能会提供不需要的文件。 ... [详细]
  • 本文介绍了Python高级网络编程及TCP/IP协议簇的OSI七层模型。首先简单介绍了七层模型的各层及其封装解封装过程。然后讨论了程序开发中涉及到的网络通信内容,主要包括TCP协议、UDP协议和IPV4协议。最后还介绍了socket编程、聊天socket实现、远程执行命令、上传文件、socketserver及其源码分析等相关内容。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 如何在HTML中获取鼠标的当前位置
    本文介绍了在HTML中获取鼠标当前位置的三种方法,分别是相对于屏幕的位置、相对于窗口的位置以及考虑了页面滚动因素的位置。通过这些方法可以准确获取鼠标的坐标信息。 ... [详细]
  • JavaScript和HTML之间的交互是经由过程事宜完成的。事宜:文档或浏览器窗口中发作的一些特定的交互霎时。能够运用侦听器(或处置惩罚递次来预订事宜),以便事宜发作时实行相应的 ... [详细]
  • 深入解析Linux下的I/O多路转接epoll技术
    本文深入解析了Linux下的I/O多路转接epoll技术,介绍了select和poll函数的问题,以及epoll函数的设计和优点。同时讲解了epoll函数的使用方法,包括epoll_create和epoll_ctl两个系统调用。 ... [详细]
  • 本文总结了在编写JS代码时,不同浏览器间的兼容性差异,并提供了相应的解决方法。其中包括阻止默认事件的代码示例和猎取兄弟节点的函数。这些方法可以帮助开发者在不同浏览器上实现一致的功能。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 本文探讨了C语言中指针的应用与价值,指针在C语言中具有灵活性和可变性,通过指针可以操作系统内存和控制外部I/O端口。文章介绍了指针变量和指针的指向变量的含义和用法,以及判断变量数据类型和指向变量或成员变量的类型的方法。还讨论了指针访问数组元素和下标法数组元素的等价关系,以及指针作为函数参数可以改变主调函数变量的值的特点。此外,文章还提到了指针在动态存储分配、链表创建和相关操作中的应用,以及类成员指针与外部变量的区分方法。通过本文的阐述,读者可以更好地理解和应用C语言中的指针。 ... [详细]
  • Week04面向对象设计与继承学习总结及作业要求
    本文总结了Week04面向对象设计与继承的重要知识点,包括对象、类、封装性、静态属性、静态方法、重载、继承和多态等。同时,还介绍了私有构造函数在类外部无法被调用、static不能访问非静态属性以及该类实例可以共享类里的static属性等内容。此外,还提到了作业要求,包括讲述一个在网上商城购物或在班级博客进行学习的故事,并使用Markdown的加粗标记和语句块标记标注关键名词和动词。最后,还提到了参考资料中关于UML类图如何绘制的范例。 ... [详细]
  • 本文介绍了在MFC下利用C++和MFC的特性动态创建窗口的方法,包括继承现有的MFC类并加以改造、插入工具栏和状态栏对象的声明等。同时还提到了窗口销毁的处理方法。本文详细介绍了实现方法并给出了相关注意事项。 ... [详细]
  • 单页面应用 VS 多页面应用的区别和适用场景
    本文主要介绍了单页面应用(SPA)和多页面应用(MPA)的区别和适用场景。单页面应用只有一个主页面,所有内容都包含在主页面中,页面切换快但需要做相关的调优;多页面应用有多个独立的页面,每个页面都要加载相关资源,页面切换慢但适用于对SEO要求较高的应用。文章还提到了两者在资源加载、过渡动画、路由模式和数据传递方面的差异。 ... [详细]
  • 用Vue实现的Demo商品管理效果图及实现代码
    本文介绍了一个使用Vue实现的Demo商品管理的效果图及实现代码。 ... [详细]
  • 本文介绍了解决mysql 5.1启动问题的方法,通过修改my.ini文件中的相关配置,包括innodb_data_home_dir和skip-innodb等,可以解决启动问题。同时还介绍了如何调整内存池来存储metadata信息。 ... [详细]
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社区 版权所有