概述
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首部
}
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:
- 用新的窗口通告、seqno,ackno同snd_wnd,snd_wl1,snd_wl2比较,看是否更新窗口
- lastack与ackno比较,if相同,则说明报文丢失,可执行快重传或者快恢复(包括计算RTT)
- 看确认号能否释放unacked空间,联合ooseq链看能否组出有序数据放入recv_data,无序则加入ooseq
- 对于需要重传的,会被挂到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个定时器(其实只是变量和宏):
- 建立连接:server响应SYN后等75s,没响应就终止
- 重传
- 数据组装:用于清理ooseq
- 坚持:0窗口探查
- 保活
- FIN_WAIT2:超时后关闭连接(因为对方已经知道你关了,而LWIP不支持半打开)
- 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;//超时回调和参数
}
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继续执行
最后
以上就是真实小甜瓜为你收集整理的关于LWIP的一点记录(四)的全部内容,希望文章能够帮你解决关于LWIP的一点记录(四)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复