• 关于LWIP的一点记录(四)


    TCP编程本质是操作tcp_pcb,每个TCP连接对应一个tcp_pcb,
    每个tcp_pcb维护3个链(缓冲队列):unset,unacked,ooseq ———— 记录了本连接的所有数据
    TCP缓冲:
    struct tcp_seg{

    struct tcp_seg * next;	//单链
    struct pbuf * p;	//数据链
    len,flags;		//长度,选项
    * tcphdr;		//TCP首部
    
    • 1
    • 2
    • 3
    • 4

    }

    unset,unacked,ooseq就是三个tcp_seg链
    unsent链上的tcp_seg试图将缓冲数据组织成最佳发送长度(TCP_MSS),
    ————so,应该是每个pbuf加入缓冲的时候,看下当前unsent链尾的tcp_seg够不够TCP_MSS,
    如果不够就继续挂在这个tcp_seg上,如果够了就新开一个tcp_seg
    tcp_write负责申请tcp_seg和pbuf,将待发送数据按照每个tcp_seg TCP_MSS大小组织,设置首部字段,并挂接到unsent链上,
    然后调整snd_lbb,snd_buf,snd_queuelen,
    tcp_write只负责组织,不管发送 ———— 内核会定期发送,也可以使用tcp_output手动发送
    ———— 看描述是要正好填够TCP_MSS,所以用的pbuf是固定大小?还是说完整的数据可以被切到俩tcp_seg中?

    tcp_output先检查是否符合发送条件,判断一下别跟tcp_output冲突,if pcb->flags中设置了TF_ACK_NOW,则需要赶紧发一个ACK,
    这时if unsent是空的?或者发送窗口不够了,就先发一个空ACK,否则可以跟在正常报文中捎带确认,
    其次是正常数据,前面tcp_write填装了一个个TCP_MSS大小的报文段(一个tcp_seg管理一个报文段),
    此时tcp_output就可以在有空闲发送窗口的情况下调用tcp_output_segment(填写字段、校验和、启动重传定时器等)将报文段一个个发送,直到空闲窗口被用完
    每个发送出去的报文段会按照序号大小加入unacked链

    SYN攻击:同时向服务器发送大量SYN请求,服务器为每个请求(连接)申请空间,返回ACK+SYN,但无法获得回复,占用资源

    TCP状态转换:

    tcp_process是TCP的状态机函数
    目前看来,状态机似乎不用处理TIME_WAIT?

    tcp_receive:

    1. 用新的窗口通告、seqno,ackno同snd_wnd,snd_wl1,snd_wl2比较,看是否更新窗口
    2. lastack与ackno比较,if相同,则说明报文丢失,可执行快重传或者快恢复(包括计算RTT)
    3. 看确认号能否释放unacked空间,联合ooseq链看能否组出有序数据放入recv_data,无序则加入ooseq
    4. 对于需要重传的,会被挂到unsent上,此时可将unsent上序号小于ackno的都删掉(应该是之前重传的还没来得及发)

    超时重传与RTT:
    当rtime超过rto时,unacked队列中的所有报文段都会被重传(插到unsent前端),
    if重发的报文超时,则开始指数退避(这里直接让rto每次翻倍直到上限),超限后停止发送,删除pcb

    慢启动与拥塞避免是为了调节发送流量(超时是触发条件),与重传机制无关
    而快启动与快恢复是3个重复ack后不等rtime溢出,直接将丢的这一个放到unsent中
    快重传与快恢复侧重于解决偶尔丢包的问题,而前者是为了限流

    糊涂窗口:小窗口通告和小报文段发送导致带宽浪费(头多数据少)
    ———— so,LWIP的tcp_seg会组织满长度(TCP_MSS)的报文段(unacked非空的情况下),而接收方使用推迟确认

    所谓的nagle算法,貌似是判断一些条件成立时不发送而是缓冲起来去拼接最大长度(TCP_MSS)
    而平时禁止nagle,是说定时发送,能拼多少是多少?

    零窗口探查:发送方周期性查询窗口大小(通过发送unacked或unsent中的一字节数据),避免先收到0窗口,而之后的非0窗口丢失而死锁

    保活机制:服务器在长时间没和client通信后(2h),会发送探查报文(只有TCP首部),看一下对面挂了没,根据结果保活或终止连接

    7个定时器(其实只是变量和宏):

    1. 建立连接:server响应SYN后等75s,没响应就终止
    2. 重传
    3. 数据组装:用于清理ooseq
    4. 坚持:0窗口探查
    5. 保活
    6. FIN_WAIT2:超时后关闭连接(因为对方已经知道你关了,而LWIP不支持半打开)
    7. TIME_WAIT:(2MSL)超时关、删、重用,另外LAST_ACK也用这个超时关闭,而不必非等ACK
      每个定时器是通过比较当前tcp_ticks与本pcb的tmr来判断的,连接切换状态时会记录pcb->tmr = tcp_ticks,
      so定时器可以通过当前tcp_ticks - pcb->tmr知道pcb处于某种状态的时间
      tcp_tmr负责周期调用tcp_slowtmr(500MS)和tcp_fasttmr(250MS)
      定时器结构:

    struct sys_timeo{

    sys_timeo * next;//单链
    u32_t time;		//表示当处于链表头结点时还需等待的时间
    sys_timeout_handler h,void * arg;//超时回调和参数
    
    • 1
    • 2
    • 3

    }
    sys_timeout负责插入定时事件到链表,注册回调等,通过超时时间在链中找到位置插入,注意需要修改后面那一位的等待时间,
    另:在回调中重新使用sys_timeout注册就是循环定时了

    使用OS的情况下:
    网络接口收到数据包后会回调注册的tcpip_input,构造消息,投递邮箱
    收数据包可使用中断或查询线程
    之前的RAW_API中是收到数据后调ethernet_input(向上逐层递交),而使用OS之后,调tcpip_input投递邮箱,内核线程从邮箱取到之后再调ethernet_input,
    多了中间的邮箱,慢了一点,好处是中断处理缩短了,网卡这边接收能力提高

    用户进程使用的netbuf只是对内核pbuf的简单封装(共享了),so避免了数据copy

    一个栗子:调用netconn_bind,则会发个TCPIP_MSG_API类型的消息,阻塞在邮箱上(信号量),
    内核拿到这个消息,会调用do_bind ——> tcp_bind … 执行完后释放信号量,netconn_bind继续执行

  • 相关阅读:
    Matlab在同一张图中如何加入多个图例
    配置hadoop模板虚拟机
    【MyBatis】多条件查询、动态SQL、多表操作、注解开发
    华为云HECS云服务器docker环境下安装nacos
    搭建react项目
    GDevelop开源游戏引擎教程——(一)简介和安装
    从0到1搭建Halo博客系统教程
    sqlite加载csv文件,并做数据分析
    获取Windows远程桌面端口
    3.你所不知道的go语言控制语句——Leetcode习题69
  • 原文地址:https://blog.csdn.net/weixin_40852534/article/details/126203244