正点原子lwIP学习笔记——TCP协议

news2024/11/23 3:36:38

1.TCP协议简介

TCP协议,是一种面向连接、可靠的、基于字节流的传输层通信协议。

TCP特点
主要就是要知道,TCP协议是需要连接才可以互发数据的,连接需要三次挥手,而断开连接需要四次挥手。

2.TCP协议报文结构

TCP关键结构体
TCP协议的头部一共有20字节,左边的结构体与右边的框图示意图一一对应;src就是16位表本地端口号;dest是16位目标端口号;seqno是32位序号,用来重组TCP的分包(因为TCP不能在网络层进行分片,也就是IP协议不能分片,只能在传输层层进行分包);ackno是32位确认序号;hdrlen_rsvd_flags是16位的flag;wnd是16位窗口大小;然后是16位校验和chksum;最后是16位紧急指针urgp。

这其中,关于标志位的具体解析如下:

  • URG:为1时紧急指针有效;
  • ACK:为1时,确认序号有效;
  • PSH:为1时,接收方应该尽快将这个报文段交给应用层;
  • RST:为1时,重建连接;
  • SYN:为1时,同步程序,发起一个连接;
  • FIN:为1时,发送端完成任务,释放一个连接。

3.TCP转换状态

TCP的状态转换示意图
进入连接,就是通过三次握手来确认客户端和服务器连接:

三次握手,就是首先由客户端发送SYN给服务器,这是第一次握手;然后服务器就会回复SYN+ACK信号给客户端,客户端进入SYN-SENT模式,而服务器切换到LISTEN模式,这是第二次握手;第三次握手,就是客户端发送ACK信号给服务器。至此,完成三次握手,客户端和服务器均进入ESTAB-LISHED状态,可以完成数据的互发。

四次挥手,客户端和服务器都可以发起,以此来完成断联。客户端在ESTAB-LISHED状态发送FIN给服务器,客户端进入FIN-WAIT-1状态,这就是第一次挥手;然后服务器在ESTAB-LISHED状态接受FIN,回应一个ACK信号给客户端,服务器进入CLOSE-WAIT状态,这就是第二次挥手;然后服务器发送一个FIN信号给客户端,客户端进入FIN-WAIT-2状态,服务器进入LAST-ACK状态,这就是第三次挥手;最后客户端发送ACK信号给服务器,自身进入TIME-WAIT状态(2s),然后进入CLOSED状态,服务器也进入CLOSED状态,这就是第四次挥手。

lwIP中,通过一个枚举类型的tcp_state来描述以上的状态,完成TCP协议中的三次握手和四次挥手。枚举类型如下所示:

TCP的状态枚举

4. RAW接口相关函数

TCP控制块

TCP的控制块类似UDP,定义在tcp.h中,如下所示:

tcp_pcb TCP控制块
TCP的控制块如上所示,主要会用到的已经罗列在上面了;操作的TCP首部的,就是pcb控制块,协议特定的TCP_PCB_COMMON控制块,远程端口号以及标志位flags(用于判断处于什么状态,完成三次握手和四次挥手);
之后还会用到发送和接收成功的两个回调函数,以及连接成功的回调函数;轮询查阅是否有信息的函数,以及发生致命错误时的函数。

具体的就不会多看,只需要知道有这些成员,然后之后学会调用就可以了,源码整个TCP占据lwIP的一半,可以直接看计算机网络的书进行学习。

lwIP将TCP的控制块连接成为单向链表如下所示:
TCP控制块的链表
通过TCP_PCB_COMMON里面的next指针进行链接,最后一个就是NULL,连接之后就可以遍历完成寻找。

TCP回调函数

RAW编程接口的TCP实验需要自行实现对应的回调函数,然后讲这些回调函数注册给指定的TCP控制块:
TCP相关回调函数
TCP实现框图
首先,如果是发送,就会调用tcp_write进行发送,把数据挂载到缓冲之中,也就是图中的enqueue,然后通过tcp_output发送出去,最终由ip_output发送到网络层;

tcp_process完成三次握手和四次挥手,因为这些操作无需发送回应用程序,而是可以直接处理。

查看源码以及讲义,TCP实现如下:

首先,先调用tcp_connect函数进行远程服务器的连接客户端会发送一个SYN信号,并把pcb的状态改为SYN_SENT,通过tcp_output发送出去,这就是第一次握手
服务器这边,会调用tcp_listen函数,其就是一个宏定义,实际调用tcp_listen_with_backlog函数,这里面会调用tcp_listen_with_backlog_and_err函数,在这里面会把state改为LISTEN;然后会调用tcp_listen_input,在接受到SYN信号后,发送一个SYN|ACK的信号给客户端,同时把服务器状态改为SYN_RCVD
客户端在tcp_process函数中,判断pcb->state是SYN_SENT,就会处理,如果接收到了SYN+ACK的信号,会把状态改为ESTABLISHED,这里相当于第二次握手
然后调用tcp_output函数,在这里面调用tcp_output_segment发送一个应答包的ACK信号
服务器在tcp_process中,如果在SYN_RCVD状态接收到了ACK信号,就会把自身状态改为ESTABLISHED;这时就完成了第三次握手

tcp_output有两个定时器:快定时器250ms,用来发送ACK包;慢定时器500ms,实现了TCP协议的超时部分,是否接受到ACK包。

5. RAW接口的TCP函数

RAW接口的TCP函数

  • tcp_new()
    就是调用了tcp_alloc函数;这个函数里面定义了tcp_pcb的结构体pcb,然后内存池的方式memp_malloc申请内存,然后设置控制块参数,完成后返回pcb;
  • tcp_bind()
    一个tcp_pcb结构体pcb传参进来,通过ip_addr_set这是本地IP地址,然后把本地端口号port给到pcb->local_port;

实现与之前的UDP很类似,就不再赘述。

6. RAW接口的TCPClient实验

配置流程:

  1. tcp_new常见一个TCP控制块:描述当前TCP的端口号、IP地址等信息;
  2. tcp_connect设置目标IP地址和插入TCP PCB链表:把控制块插入TCP PCB链表;
  3. tcp_recv注册接受回调函数:接收回调函数由用户编写;
  4. tcp_write发送数据:网络搭建完成,可发数据。

与UDP实验类似,首先会进入lwip_tcp_client_set_remoteip()函数,也就是配置远程IP地址,也就是PC地址,因为是DHCP配置,所以前三个IP保持一致即可,然后可以通过按键修改最后一个IP地址;
然后tcp_new申请一个新的pcb;创建成功就通过IP4_ADDR来组合IP地址,传到rmtipaddr里面,然后tcp_connect来连接到目的地址的指定端口上;
这个函数中还包括了lwip_tcp_client_connected这个回调函数;会申请一个tcp_client_struct结构体es,(里面包含了TCP客户端的状态,是否连接成功;一个tcp_pcb结构体和一个pbuf结构体);申请成功就会更新状态,如果已经调用这个函数,说明已经完成三次握手,就连接成功了;这个函数中还要调用tcp的另外四个回调函数;

tcp_arg就是把tpcb和es传入就好了;

tcp_recv是接收回调函数,把我们自己实现的lwip_tcp_client_recv传入;这个函数定了一pbuf结构体q和tcp_client_struct结构体es,还定义了err_t结构体ret_err,es接上arg参数,也就是之前的es;如果es是连接成功的状态同时p非空,就需要遍历pcb的链表调用memcpy把pbuf的数据拷贝到g_lwip_demo_recvbuf缓冲中,然后把flag位置1表示收到数据,然后调用tcp_recved通知lwIP内核可以更新获取新的数据,最后pbuf_free释放内存;如果不是以上的情况就可以直接释放内存;

tcp_err函数传入自己实现的lwip_tcp_client_error,但是不做任何处理;

tcp_sent函数传入lwip_tcp_client_sent;其中tcp_client_struct通过arg接到之前的es,然后调用lwip_tcp_client_senddata发送数据;这个函数中,循环遍历es->p(pbuf),把整个pbuf链表通过tcp_write写入发送缓冲区,然后调用tcp_output发送出去

tcp_poll函数传入lwip_tcp_client_poll;其中检查es的state是否是关闭状态,如果是就调用lwip_tcp_client_connection_close关闭连接;这个函数中,移除所有的回调函数(传入tcp_pcb结构体tpcb,以及NULL清楚数据),然后把flag位清零;

以上均在tcp_connect中设置,如果设置成功res返回0,进入while循环;如果按下KEY0就会调用lwip_tcp_client_usersent发送数据;这个函数中,如果es有数据,就会申请pbuf(通过pbuf_alloc),然后pbuf_take把数据拷贝到pbuf中;通过lwip_tcp_client_senddata发送出去之后,把标识位置1;最后释放pbuf内存(通过pbuf_free)

7. RAW接口的TCP Server实验

TCP Server的相关函数
在lwip_demo中启动整个函数:

首先定义了连个tcp_pcb的tcp控制块,一个是tcppcbnew,还有一个是tcppcbconn用于监听;然后就跟之前的实验类似,通过tcp_new创建一个新的控制块给到tcppcbnew;创建成功后,通过tcp_bind把本地IP和指定端口绑定到tcppcbnew上;绑定完成,通过tcp_listen设置tcppcbnew到监听状态,返回值给到tcppcbconn;

tcp_listen就是一个宏定义,其就是指向了tcp_listen_with_backlog函数;然后这个函数return给了tcp_listen_with_backlog_and_err函数;这个函数会通过内存池的方式memp_malloc申请内存给到tcp_pcb_listen这个tcp控制块lpcb;然后初始化这个控制块的参数,其中state状态就是LISTEN监听状态;完成初始化后会把lpcb强转成tcp_pcb类型return出去;

完成后,调用tcp_accept,初始化lwIP这个回调函数,其实现就是自行定义lwip_tcp_server_accept;(这一步就是完成开发板作为server,然后网络调试助手作为client的初始化);进入之后,定义了tcp_server_struct结构体es(包括三个参数,一个是state状态,一个是tcp_pcb来只想当前控制块,最后一个就是传输的数据pbuf);然后es调用mem_malloc申请内存;内存分配成功后,进行接收连接,把es的参数进行初始化,状态state变成ES_TCPSERVER_ACCEPTED,pcb就是传入的newpcb,pbuf暂时没有是NULL;然后设置另外四个回调函数,把newpcb传入禁区,然后标记客户端连上(通过自定义的全局flag),并设置远程IP地址;

同样的,tcp_recv就是调用lwip_tcp_server_recv函数;如果是空数据关闭连接(设置es的state为ES_TCPSERVER_CLOSING);如果err说明有错误,就直接释放pbuf内存;如果es连接成功且pbuf有数据,就会通过memset,然后遍历pbuf的链表进行数据拷贝,然后标记收到数据(全局的flag),设置远程IP的地址,并调用tcp_recved读取接收数据,然后释放掉当前的pbuf内存(数据缓冲区已经传输完成,就可以释放掉了);

tcp_err就是调用lwip_tcp_server_err来进行连接过程中的错误处理;这里的操作就是判断arg参数是否为空,不为空就直接mem_free释放掉arg的内存;

tcp_poll调用lwip_tcp_server_poll函数注册轮询;就是新建一个tcp_server_struct结构体es然后把传入的arg强转成该类型并赋值给es;然后不断轮询if判断es的state是否是需要关闭的状态,如果是就调用lwip_tcp_connection_close进行关闭连接;这个函数的关闭操作就是调用tcp_close,然后所有的五个回调函数全部给NULL,再mem_free释放掉es的内存,把对应的flag标志位清零;

最后是发送的回调函数tcp_sent,调用lwip_tcp_server_sent函数;这里面就是判断es是否有数据(pbuf是否存在),有数据就调用lwip_tcp_server_senddata进行发送;这个函数就是在有数据的情况下遍历pbuf链表,然后通过tcp_write把pbuf加入到发送缓冲队列,然后把当前的pbuf释放掉并tcp_recved,最后通过tcp_output发送出去

以上任务全部完成后,判断是否成功完成,成功完成就会进入死循环进行任务执行;其中发送数据调用了lwip_tcp_server_usersent来发送数据;这个函数通过tcp_server_struct结构体es,es->p通过pbuf_alloc申请内存,然后pbuf_take把数据拷贝到申请的pbuf之中,最后调用lwip_tcp_server_senddata发送数据

总结

这一章没有将太多TCP实现的源码,因为实在是太多了……把我文章里涉及到的几个回调函数,以及三次握手四次挥手理解一下就OK了。

主要就是要自己在raw接口中实现五个回调函数,然后就能完成TCP的客户端的搭建,与PC完成通讯。
TCP的Server实验很类似,都是实现五个回调函数,然后进行通讯。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1045344.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

网易一面:Eureka怎么AP?Nacos既CP又AP,怎么实现的?

说在前面 在40岁老架构师 尼恩的读者交流群(50)中,最近有小伙伴拿到了一线互联网企业如网易、微博、阿里、汽车之家、极兔、有赞、希音、百度、滴滴的面试资格,遇到一几个很重要的面试题: Eureka是AP还是CP? 说说其集群数据一致性…

有哪些ai智能写作是永久免费的

无论你是一个自媒体作者,企业家,还是一个博客写手,你都了解创作的挑战。创意和时间常常成为限制因素,而AI智能写作工具则旨在解决这些问题。 这些工具利用先进的自然语言处理技术,可以生成各种类型的文本,包…

Appium+python+unittest搭建UI自动化框架

阅读本小节,需要读者具备如下前提条件: 掌握一种编程语言基础,如java、python等。 掌握一种单元测试框架,如java语言的testng框架、python的unittest框架。 掌握目前主流的UI测试框架,移动端APP测试框架Appium&…

Java中的IO流的缓冲流

不爱生姜不吃醋⭐️ 如果本文有什么错误的话欢迎在评论区中指正 与其明天开始,不如现在行动! 文章目录 🌴IO流体系结构🌴缓冲流1.提高效率的原理2.缓冲流的类型3.字符缓冲流两个特有方法 🌴总结 🌴IO流体系…

如何查阅下载美国物理学会(APS)文献

APS美国物理学会数据库简介: The American Physical Society (APS)成立于1899年,是世界上最具声望的物理学专业学会之一。APS不仅为用户带来今日尖端研究,同时为全球各研究单位提供自1893年以来,在“PHYSICAL REVIEW”上刊载的所…

2020年12月 Python(二级)真题解析#中国电子学会#全国青少年软件编程等级考试

Python编程(1~6级)全部真题・点这里 一、单选题(共25题,每题2分,共50分) 第1题 执行以下代码中,输出的结果是?( ) sum0 for i in range(1,10,3):sumsumi p…

88、Redis 的 value 所支持的数据类型(String、List、Set、Zset、Hash)---->Set相关命令

本次讲解要点: ** Set相关命令:是指value中的数据类型** 启动redis服务器: 打开小黑窗: C:\Users\JH>e: E:>cd E:\install\Redis6.0\Redis-x64-6.0.14\bin E:\install\Redis6.0\Redis-x64-6.0.14\bin>redis-server.exe …

智慧能源:引领未来的能源革命

在当今世界,能源问题是一个备受关注的焦点话题。随着全球人口的不断增长和工业化进程的加速,对能源的需求也日益增加,同时,传统的能源资源面临着日益严重的枯竭和环境污染问题。在这一背景下,智慧能源应运而生&#xf…

vite跨域proxy设置与开发、生产环境的接口配置,接口在生产环境下,还能使用proxy代理地址吗

文章目录 vite的proxy开发环境设置如果后端没有提供可以替换的/mis等可替换的后缀的处理办法接口如何区分.env.development开发和.env.production生产环境接口在生产环境下,还能使用proxy代理地址吗? vite的proxy开发环境设置 环境: vite 4…

服务断路器_服务雪崩解决方案之服务隔离

那显而易见,做服务隔离的目的就是避免服务之间相互影响。毕竟谁也不能说自己的微服务百分百可用,如果不做隔离,一旦一个服务出现了问题,整个系统的稳定性都会受到影响! 因此,做服务隔离是很有必要的。 什么…

消费者偏移量_consumer_offsets相关解析

1.概述 __consumer_offsets 是 kafka 自行创建的,和普通的 topic 相同。它存在的目的之一就是保存 consumer 提交的位移。 __consumer_offsets 的每条消息格式大致如图所示: 可以想象成一个 KV 格式的消息,key 就是一个三元组:group.idtopi…

成都睿趣科技:抖音开通橱窗带货需要钱吗

随着社交媒体和电子商务的蓬勃发展,抖音作为一种流行的短视频平台,也推出了自己的“抖音橱窗”功能,让内容创作者能够通过视频展示和销售产品,从而实现商业化。那么,抖音橱窗带货是否需要费用呢? 首先,要开…

现代数据架构-湖仓一体

当前的数据架构已经从数据库、数据仓库,发展到了数据湖、湖仓一体架构,本篇文章从头梳理了一下数据行业发展的脉络。 上世纪,最早出现了关系型数据库,也就是DBMS,有商业的Oracle、 IBM的DB2、Sybase、Informix、 微软…

关于坐标的旋转变换和坐标系的旋转变换

不管是坐标的旋转变换还是坐标系下的旋转变换,只和旋转的顺时针和逆时针有关。然坐标系间的顺时针和逆时针是根据当前坐标系在目标坐标系下的相对位置确定。 一。逆时针旋转belta角度的公式 二。顺时针旋转belta角度的公式 三。坐标的旋转变换 1.坐标的旋转变换相…

一文了解企业如何实现文件自动化实时同步

在当今的数字化时代,数据是企业的核心资产,也是企业竞争力的重要体现。数据的传输、共享、协作、备份等都需要依赖文件同步技术,实现数据在不同平台和设备之间的一致性和可用性。文件同步是指将一个或多个文件夹中的内容复制或更新到另一个或…

网络安全攻防:软件逆向之反汇编

网络安全是当今社会中一个非常重要的问题,而软件逆向工程是网络安全攻防中常用的一种技术手段。在软件逆向工程中,反汇编是一种基础而重要的技术。通过反汇编,我们可以将二进制程序转换为汇编语言,从而更好地理解程序的执行流程和…

在伦敦银投资中,技术是万能的?

一般进行伦敦银投资的投资者都会学习很多技术分析的方法,技术分析是一种还很适合普通投资者使用的市场分析工具,但是在伦敦银投资中,技术分析的作用不是万能的,其实技术分析还是有很多各种各样的缺点,如果投资者迷信技…

前缀和实例5(连续数组)

题目: 给定一个二进制数组 nums , 找到含有相同数量的 0 和 1 的最长连续子数组,并返回该子数组的长度。 示例 1: 输入: nums [0,1] 输出: 2 说明: [0, 1] 是具有相同数量 0 和 1 的最长连续子数组。 示例 2: 输入: nums [0,1,0] 输出: 2 说明: [0…

Exception in thread “main“ java.sql.SQLException: No suitable driver

详细报错信息如下: Exception in thread "main" java.sql.SQLException: No suitable driver at java.sql.DriverManager.getDriver(DriverManager.java:315) at org.apache.spark.sql.execution.datasources.jdbc.JDBCOptions.$anonfun$driverC…

js遍历对象属性的方法

在 JavaScript 中,有许多方法可以遍历对象的属性,但在性能上并没有显著的差异。对于大多数用例,使用 for-in 循环或 Object.keys() 方法是最常见的。 然而,如果你正在寻找微优化,并希望遍历大量对象,可以考…