lwip --- (十六)TCP建立流程
这一节我们就看看如何在我们的LWIP上实现一个http
服务器的过程,结合连接建立过程来理解TCP状态转换图
和TCP控制块
中各个字段的意义。这里先讲解一些与TCP相关的最基础的函数,至于是怎样将这些函数合理高效的组织起来以方便实际应用,这里先不涉及。
第一个函数是tcp_new
函数,该函数简单的调用tcp_alloc
函数为一个连接分配一个TCP控制块tcp_pcb
。tcp_alloc
函数首先为新的tcp_pcb
分配内存空间,若内存空间不够,则函数会释放处于TIME-WAIT
状态的TCP或者优先级更低的PCB(在PCB控制块的prio
字段)以为新的PCB分配空间。当内存空间成功分配后,函数会初始化新的tcp_pcb
的内容,源码如下:
if (pcb != NULL) {memset(pcb, 0, sizeof(struct tcp_pcb)); // 清0所有字段的值pcb->prio = TCP_PRIO_NORMAL; // 设置PCB的优先级为64,优先级在1~127之间pcb->snd_buf = TCP_SND_BUF; // TCP发送数据缓冲区剩余大小pcb->snd_queuelen = 0; // 发送缓冲中的数据包pbuf个数pcb->rcv_wnd = TCP_WND; // 接收窗口大小pcb->rcv_ann_wnd = TCP_WND; // 通告窗口大小pcb->tos = 0; // IP报头部TOS字段pcb->ttl = TCP_TTL; // IP报头部TTL字段pcb->mss = (TCP_MSS > 536) ? 536 : TCP_MSS; // 设置最大段大小,不能超过536字节pcb->rto = 3000 / TCP_SLOW_INTERVAL; // 初始超时时间值,为6spcb->sa = 0; // 估计出的RTT平均值??pcb->sv = 3000 / TCP_SLOW_INTERVAL; // 估计出的RTT方差??pcb->rtime = -1; // 重传定时器,当该值大于rto时则重传发生pcb->cwnd = 1; // 阻塞窗口iss = tcp_next_iss(); // iss为一个临时变量,保存该连接的初始数据序列号pcb->snd_wl2 = iss; // 上一个窗口更新时收到的ACK号pcb->snd_nxt = iss; // 下一个将要发送的数据编号pcb->snd_max = iss; // 发送了的最大数据编号pcb->lastack = iss; // 上一个ACK编号pcb->snd_lbb = iss; // 下一个将要缓冲的数据编号pcb->tmr = tcp_ticks; // tcp_ticks是一个全局变量,记录了当前协议时钟滴答pcb->polltmr = 0; // 未解???#if LWIP_CALLBACK_APIpcb->recv = tcp_recv_null; // 注册默认的接收回调函数#endifpcb->keep_idle = TCP_KEEPIDLE_DEFAULT;#if LWIP_TCP_KEEPALIVE // 保活定时器相关设置。。未解??pcb->keep_intvl = TCP_KEEPINTVL_DEFAULT;pcb->keep_cnt = TCP_KEEPCNT_DEFAULT;#endifpcb->keep_cnt_sent = 0;}
上面有很多晕的地方,这些将在后续一一讲解。PCB中的还有一些函数字段如发送、接收函数等是在具体应用中初始化的。
当一个新建的PCB被初始化好后,tcp_bind
函数将会被调用,用来将IP地址及端口号与该TCP控制块绑定。该函数的输入参数很明显有三个,即TCP控制块、IP地址和端口号。tcp_bind函数的工作也很简单,就是将两个参数的值赋值给TCP控制块中local_ip
和local_port
的字段。但这里有个前提,就是这个组合没有被使用。所以,函数需要先遍历各个pcb链表,以保证这个组合没有被其他PCB使用,这里的pcb链表有好几种:处于侦听状态的链表tcp_listen_pcbs
、处于稳定状态的链表tcp_active_pcbs
、已经绑定完毕的PCB链表tcp_bound_pcbs
、处于TIME-WAIT
状态的PCB链表tcp_tw_pcbs
。如果遍历完这些链表后,都没有找到相应的对,则说明该对可用,则可进行上面说的赋值操作,最后,函数将这个PCB加入绑定完毕的PCB链表tcp_bound_pcbs
。
上面一共说了四种PCB链表,现在看看它们各自用来链接了处于哪种状态的PCB控制块。tcp_bound_pcbs
链表用来连接新创建的控制块,可以认为新建的控制块处于closed
状态。tcp_listen_pcbs
链表用来连接处于LISTEN
状态的控制块,tcp_tw_pcbs
链表用来连接处于TIME_WAIT
状态的控制块,tcp_active_pcbs
链表用来连接处于TCP状态转换图中其他所有状态
的控制块。
从状态转换图可以知,服务器端需进入LISTEN
状态等待客户端的连接。因此,服务器端此时需要调用函数tcp_listen
使相应TCP控制块进入LISTEN
状态。可以直接的想象,要把一个控制块置为LISTEN
状态很简单,先将其从tcp_bound_pcbs
链表上取下来,将其state
字段置为LISTEN
,最后再将该PCB挂接到链表tcp_listen_pcbs
上。但事实上,LWIP的实现有一定的区别,它引入了一个叫tcp_pcb_listen
的结构,该结构与tcp_pcb
结构相近,但是去掉了其中在LISTEN
阶段用不到的传输控制字段,这样tcp_pcb_listen
的结构更小,更可以节省内存空间。所以,其实tcp_listen
是这样做的,先申请一个tcp_pcb_listen
的结构,然后将tcp_pcb
参数中的有用字段拷贝进来,然后将这个tcp_pcb_listen
的结构挂接到链表tcp_listen_pcbs
上。
到这里服务器就等待客户端发送来的SYN
数据包进行连接了,要等待外面的数据包,这就和以前讨论过的ip_input
函数相关了,ip_input
函数会判断IP包头部的协议字段,并把TCP数数据包通过tcp_input
函数传递到TCP层。SYN
数据包当然是TCP层数据包,当然也要经过tcp_input
函数进行处理并递交上层,现在就来看看tcp_input
函数。
tcp_input
函数开始会对IP层递交进来的数据包进行一些基础操作,如移动数据包的payload
指针、丢弃广播或多播数据包、数据和校验、提取TCP头部各个字段的值等等。接下来,函数根据接收到的TCP包的对遍历tcp_active_pcbs
链表,寻找匹配的PCB控制块,若找到,则调用tcp_process
函数对该数据包进行处理。若找不到,则再分别到tcp_tw_pcbs
链表和tcp_listen_pcbs
中寻找,找到则调用各自的数据包处理函数tcp_timewait_input
和tcp_listen_input
对数据包进行处理,若到这里都还未找到匹配的TCP控制块,则tcp_input
函数会调用函数tcp_rst
向源主机发送一个TCP复位数据包。
这里我们的TCP控制块处于LISTEN
状态,连接在tcp_listen_pcbs
上,正在等待一个SYN
数据包。因此,当等到该数据包后,函数tcp_listen_input
应该被调用。从状态转换图上可以看出,处于LISTEN
状态的TCP控制块只能响应SYN
握手包,所以,tcp_listen_input
函数对非SYN
握手包返回一个TCP复位数据包,若一个数据包不是SYN
包,则其TCP包头中的ACK
字段通常会被置1
,所以tcp_listen_input
函数是通过检验该位来实现的。接下来,函数通过验证SYN
位来确认该包是否为SYN
握手包。若是,则需要新建一个tcp_pcb
结构,因为处于tcp_listen_pcbs
上的控制块结构是tcp_pcb_listen
结构的,而其他链表上的控制块结构是tcp_pcb
结构的,所以这里新建一个tcp_pcb
结构,并将相应tcp_pcb_listen
结构拷贝至其中,同时在tcp_active_pcbs
链表中添加这个新的tcp_pcb
结构。这样新的TCP控制块就处在tcp_active_pcbs
中了,注意此时的这个tcp_pcb
结构的state
字段应该设置为SYN_RCVD
,表示进入了收到SYN
状态。注意tcp_listen_pcbs
链表中的这个tcp_pcb_listen
结构还一直存在,它并不会被删除,以等待其他客户端的连接,服务器正是需要这样的功能。
到这里,函数tcp_listen_input
还没完。它应该从收到的SYN数据报中提取TCP头部中选项字段的值,并设置自己的TCP控制块。这里要被调到用的函数叫tcp_parseopt
,它目前仅能够做的是提取选项中的MSS
(最长报文大小)字段,在LWIP以后的更高版本中,该函数将被扩充,以支持更多的TCP选项。此后,函数还可以调用tcp_eff_send_mss
来设置控制块中mss
字段的值,该函数可直译为“有效发送最长报文大小”,所谓有效,就是指收到SYN数据包中的MSS
值不能大于我的硬件支持的最大发送报文长度,即硬件的MTU
。因此当收到的MSS
值更大时,设置控制块中mss
字段值会被设置为MTU
,而不是MSS
。
最后,函数需要向源端返回一个带SYN和ACK标志的握手数据包,并可以向源端通告自己的MSS大小。发送数据包是通过tcp_enqueue
和tcp_output
函数共同完成的。关于数据包的发送,将在以后介绍。
最最后,来看看函数tcp_listen_input
内部的关键源代码部分,这几行代码涉及到TCP控制块内部各个字段值的设置,其中很重要的就是滑动窗口相关的字段。
ip_addr_set(&(npcb->local_ip), &(iphdr->dest)); // 复制本地IP地址npcb->local_port = pcb->local_port; // 复制本地端口ip_addr_set(&(npcb->remote_ip), &(iphdr->src)); // 复制源IP地址npcb->remote_port = tcphdr->src; // 复制源端口npcb->state = SYN_RCVD; // 设置TCP状态npcb->rcv_nxt = seqno + 1; // 期望接收到的下一个字节序号npcb->snd_wnd = tcphdr->wnd; // 设置发送窗口大小npcb->ssthresh = npcb->snd_wnd; // 快速启动阈值设为和发送窗口大小相同??npcb->snd_wl1 = seqno - 1; // 该字段??npcb->callback_arg = pcb->callback_arg; // 该字段??#if LWIP_CALLBACK_APInpcb->accept = pcb->accept; // 接收回调函数#endif
其中npcb
表示新建的tcp_pcb
结构,还有很多不懂的地方,为啥仅仅拷贝保留了这几个字段,其他字段直接被忽略?
lwip --- (十六)TCP建立流程相关推荐
- 十六、MySQL流程控制结构(顺序、分支、循环)详解 强化练习
流程控制结构:顺序.分支.循环 一.分支结构 case结构作为表达式: case结构作为独立的语句: if函数 语法:if(条件,值1,值2) 功能:实现双分支 应用在begin end中或外面 ca ...
- 第十六届全国大学生广东赛区线上比赛流程规范
简 介: 本文给出了广东赛区线上比赛的流程规范. 关键词: 智能车竞赛,线上比赛 §01 背景介绍 在2021年举办的第十六届全国大学生智能车竞赛 暑期线下比赛,根据新冠疫情防控形势,对 广东省赛 ...
- Java学习系列(十六)Java面向对象之基于TCP协议的网络通信
TCP/IP的网络分层模型:应用层(HTTP/FTP/SMTP/POPS...),传输层(TCP协议),网络层(IP协议,负责为网络上节点分配唯一标识),物理层+数据链路层). IP地址用于标识网络中 ...
- TCP/IP协议栈之LwIP(六)---网络传输管理之TCP协议
文章目录 一.TCP协议简介 1.1 正面确认与超时重传 1.2 连接管理与保活机制 1.3 滑动窗口与缓冲机制 1.4 流量控制与拥塞控制 1.5 提高网络利用率的其他机制 二.TCP协议实现 2. ...
- Alink漫谈(十六) :Word2Vec源码分析 之 建立霍夫曼树
Alink漫谈(十六) :Word2Vec源码分析 之 建立霍夫曼树 文章目录 Alink漫谈(十六) :Word2Vec源码分析 之 建立霍夫曼树 0x00 摘要 0x01 背景概念 1.1 词向量 ...
- 十六、广义表的建立与基本操作
十六.广义表的建立与基本操作 文章目录 十六.广义表的建立与基本操作 题目描述 解题思路 上机代码 补充说明 题目描述 采用"头尾法存储广义表,实现以下广义表的操作: 1.Status Cr ...
- web工作流管理系统开发之十六 主子流程参数传递的实现
在设计流程的时候,如果涉及到子流程,就会有主子流程间传递参数的过程. 例如,将主流程的执行人,传递给子流程,子流程根据主流程的执行人不同,会有不同的处理方式: 又例如,采购流程中,卖方备货处理时,发现 ...
- 【正点原子FPGA连载】第十六章Petalinux设计流程实战摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Linux开发指南
1)实验平台:正点原子MPSoC开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=692450874670 3)全套实验源码+手册+视频下载地址: h ...
- 8. 【gRPC系列学习】resetTransport建立TCP连接流程
在Balance实现UpdateClientConnState方法过程中会调用Connect()方法,该方法会调用resetTransport建立TCP连接,本节分析resetTransport执行流 ...
最新文章
- Linux下中文man帮助安装。
- 如果我有jQuery背景,那么“ AngularJS中的思考”吗? [关闭]
- codeforce 154C - Double Profiles(hash)
- UVA 253 Cube painting
- Java面向对象之USB接口实例
- 划分子网和构造超网的学习
- canvas整体放大_【HTML5】Canvas 实现放大镜效果
- 为什么我突然在Firefox中出现“阻止加载混合的活动内容”的问题?
- [转载] PYTHON 网络编程
- MySQL之数据库操作
- C# 实现二维码的生成、解析及保存
- virtualbox窗口和win10窗口切换
- 2019计算机考研学校排行,2019计算机考研:中国大学计算机学科排行榜
- java计算机毕业设计科技专业师生沟通平台源码+数据库+lw文档+系统
- 二进制换算十进制、八进制和十六进制。
- MT6771平台简要了解
- 购买太平洋保险公司聚宝盆险发现虚假宣传,没有证据,如何向保监会投诉
- 111. 二叉树的最小深度
- pdf添加书签操作介绍
- STN32单片机学习笔记(五)-按键检测