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

阅读: 评论:0

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

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

1.TCP协议简介

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


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

2.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转换状态


进入连接,就是通过三次握手来确认客户端和服务器连接:

三次握手,就是首先由客户端发送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协议中的三次握手和四次挥手。枚举类型如下所示:

4. RAW接口相关函数

TCP控制块

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


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

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

lwIP将TCP的控制块连接成为单向链表如下所示:

通过TCP_PCB_COMMON里面的next指针进行链接,最后一个就是NULL,连接之后就可以遍历完成寻找。

TCP回调函数

RAW编程接口的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函数

  • 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实验


在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实验很类似,都是实现五个回调函数,然后进行通讯。

本文发布于:2024-02-04 14:48:41,感谢您对本站的认可!

本文链接:https://www.4u4v.net/it/170709690856511.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:原子   学习笔记   协议   lwIP   TCP
留言与评论(共有 0 条评论)
   
验证码:

Copyright ©2019-2022 Comsenz Inc.Powered by ©

网站地图1 网站地图2 网站地图3 网站地图4 网站地图5 网站地图6 网站地图7 网站地图8 网站地图9 网站地图10 网站地图11 网站地图12 网站地图13 网站地图14 网站地图15 网站地图16 网站地图17 网站地图18 网站地图19 网站地图20 网站地图21 网站地图22/a> 网站地图23