STM32H743-ARM27例程-TCP_Server

目录

实验平台

硬件:银杏科技GT7000双核心开发板-ARM-STM32H743XIH6,银杏科技iToolXE仿真器

软件:最新版本STM32CubeH7固件库STM32CubeMX v6.10.0,开发板环境MDK v5.35TCP&UDP测试工具

TCP_Server服务器

在 TCP 通讯场景中,TCP 客户端和 TCP 服务器端的角色可以看作网络传输中的两个关键节点,分别负责发起连接和处理请求。这种基于 TCP(传输控制协议)的通信方式,确保了数据的可靠性和顺序传输,使得应用程序能够在不需要关注底层网络传输细节的情况下,进行稳健的数据交换。

TCP 客户端 是负责发起通信的一方。它通过向服务器端发起连接请求,开始建立一个稳定的通信通道。在 TCP 通讯中,客户端的主要任务是根据特定的 IP 地址和端口号找到服务器,并与之建立连接。当连接建立后,客户端可以向服务器发送请求数据并接收服务器的响应。

TCP 服务器端 则是负责接收连接请求的实体。它会在特定的 IP 地址和端口号上进行监听,等待客户端的连接请求。当一个客户端发起连接时,服务器根据一定的协议规则(例如 TCP 的三次握手过程)来确认和建立连接。之后,服务器会处理来自客户端的请求,可能是发送数据、响应查询或其他应用逻辑的处理。

上一章节我们介绍了互联网到TCP相关知识,并且做了一个TCP客户端模式的实验,本章节我们来介绍下TCP的通信过程,并完成TCP作为服务器模式实验。

TCP通信过程

我们来详细解析 TCP 通信 的整个过程。TCP 是一种面向连接的、可靠的、基于字节流的传输层通信协议,它的核心在于建立连接、传输数据、断开连接这三个阶段。

为了让读者对整个过程有一个宏观的认识,下图清晰地展示了 TCP 通信的完整生命周期,即著名的三次握手、数据传输、四次挥手:

1. 建立连接:三次握手

这个过程确保了通信的双方都同意建立连接,并同步初始序列号。它解决了网络延迟导致的重复分组问题。

  • 第一步:SYN

    • 客户端发送一个 TCP 报文段,其中:

      • SYN 标志位 置为 1,表示请求建立连接。
      • 序列号 字段被设置为一个随机值 x。
    • 客户端进入 SYN-SENT 状态。

  • 第二步:SYN-ACK

    • 服务器收到 SYN 报文后,如果同意连接,则会回复一个报文段,其中:

      • SYN 标志位 和 ACK 标志位 都置为 1。

      • 序列号 字段被设置为一个随机值 y。

      • 确认号 字段被设置为 x + 1,表示"我收到了你的序列号为 x 的包,我期望下一个从 x+1 开始的包"。

    • 服务器进入 SYN-RCVD 状态。

  • 第三步:ACK

    • 客户端收到服务器的 SYN-ACK 报文后,会再次发送一个确认报文,其中:

      • ACK 标志位 置为 1。

      • 序列号 为 x + 1(第一步的序列号+1)。

      • 确认号 为 y + 1,表示"我收到了你的序列号为 y 的包,我期望下一个从 y+1 开始的包"。

    • 客户端进入 ESTABLISHED 状态。

    • 服务器收到这个 ACK 后,也进入 ESTABLISHED 状态。

至此,连接建立成功,双方可以开始数据传输。

2. 数据传输

在连接建立后,TCP 通过以下机制来保证数据的可靠传输:

  • 序列号与确认应答

    • 每个发送的字节都会被分配一个序列号。接收方在成功收到数据后,会回复一个 ACK 报文,其中的确认号指明了下一个期望接收的字节序列号。例如,发送方发送"序列号=1,数据长度=100",接收方成功接收后会回复"ACK=101"。
  • 超时重传

    • 发送方在发送一个数据段后会启动一个计时器。如果在规定时间内没有收到对应的 ACK 确认,就会认为数据丢失,并重新发送该数据段。
  • 滑动窗口

    • 为了提升效率,TCP 不是发送一个包就等待一个确认,而是采用"窗口"机制。窗口大小决定了在需要等待确认之前,可以连续发送多少数据包。这实现了流量控制,防止快的发送方淹没慢的接收方。
  • 拥塞控制

    • 为了防止过多的数据注入网络,导致路由器或链路过载,TCP 使用复杂的算法(如慢启动、拥塞避免、快速重传、快速恢复)来动态调整其发送速率。

3. 断开连接:四次挥手

当数据传输完毕,任何一方都可以发起关闭连接的请求。由于 TCP 连接是全双工的,每个方向必须单独关闭。

  • 第一步:FIN

    • 假设客户端主动关闭连接。它发送一个 TCP 报文段,其中:

      • FIN 标志位 置为 1,表示没有数据要发送了。

      • 序列号 假设为 u。

    • 客户端进入 FIN-WAIT-1 状态。

  • 第二步:ACK

    • 服务器收到 FIN 报文后,会立即回复一个 ACK 报文,其中:

      • ACK 标志位 置为 1。

      • 确认号 为 u + 1。

    • 服务器进入 CLOSE-WAIT 状态。此时,从客户端到服务器的连接已经关闭,客户端不能再发送数据,但服务器可能还有数据要发送给客户端。

    • 客户端收到这个 ACK 后,进入 FIN-WAIT-2 状态。

  • 第三步:FIN

    • 当服务器也准备好关闭连接时,它会发送自己的 FIN 报文,其中:

      • FIN 标志位 和 ACK 标志位 置为 1。

      • 序列号 假设为 v。

      • 确认号 仍然为 u + 1。

    • 服务器进入 LAST-ACK 状态。

  • 第四步:ACK

    • 客户端收到服务器的 FIN 报文后,会发送一个 ACK 报文作为确认,其中:

      • ACK 标志位 置为 1。

      • 确认号 为 v + 1。

    • 客户端进入 TIME-WAIT 状态,并等待一个 2MSL 的时间。

    • 服务器收到这个 ACK 后,立即关闭连接,进入 CLOSED 状态。

    • 客户端在等待 2MSL 时间后,也正式关闭连接,进入 CLOSED 状态。

TIME-WAIT 状态的作用:

确保最后的 ACK 能到达服务器:如果服务器没有收到第四个 ACK,它会重发第三个 FIN。客户端在 TIME-WAIT 状态下可以再次回复 ACK。

让本次连接的所有报文在网络中消失:防止旧的、延迟的报文段被之后新建的、具有相同四元组(源IP、源端口、目的IP、目的端口)的连接错误地接收。

STM32CubeMX生成工程

我们参考前面章节STM32H743-结合CubeMX新建HAL库MDK工程,打开CubeMX软件,重复步骤不再展示。我们来看配置MPU配置、以太网部分和Lwip部分配置如下图所示:
配置以太网。选用RMII(精简的独立于介质接口)模式。




MPU配置

LWIP配置

选择PHY芯片型号。LAN8742向下兼容LAN8720

实验代码

1. 主函数

cpp 复制代码
int main(void)
{
    MPU_Config();
    SCB_EnableICache();
    SCB_EnableDCache();
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_LWIP_Init();
    MX_USART6_UART_Init();
    
    uart6.initialize(115200);
    uart6.printf("\x0c");
    uart6.printf("GT7000 OK!\r\n");
    HAL_GPIO_WritePin(PHYAD0_GPIO_Port,PHYAD0_Pin,GPIO_PIN_RESET);
    eth_tcps_init();

    while (1)
    {
        MX_LWIP_Process();
    }

}

2. LwIP初始化

cpp 复制代码
void MX_LWIP_Init(void)
{
    /* IP addresses initialization */
    IP_ADDRESS[0] = 192;
    IP_ADDRESS[1] = 168;
    IP_ADDRESS[2] = 0;
    IP_ADDRESS[3] = 11;
    NETMASK_ADDRESS[0] = 255;
    NETMASK_ADDRESS[1] = 255;
    NETMASK_ADDRESS[2] = 255;
    NETMASK_ADDRESS[3] = 0;
    GATEWAY_ADDRESS[0] = 192;
    GATEWAY_ADDRESS[1] = 168;
    GATEWAY_ADDRESS[2] = 0;
    GATEWAY_ADDRESS[3] = 1;

    /* Initilialize the LwIP stack without RTOS */
    lwip_init();

    /* IP addresses initialization without DHCP (IPv4) */
    IP4_ADDR(&ipaddr, IP_ADDRESS[0], IP_ADDRESS[1], IP_ADDRESS[2], IP_ADDRESS[3]);
    IP4_ADDR(&netmask, NETMASK_ADDRESS[0], NETMASK_ADDRESS[1] , NETMASK_ADDRESS[2], NETMASK_ADDRESS[3]);
    IP4_ADDR(&gw, GATEWAY_ADDRESS[0], GATEWAY_ADDRESS[1], GATEWAY_ADDRESS[2], GATEWAY_ADDRESS[3]);

    /* add the network interface (IPv4/IPv6) without RTOS */
    netif_add(&gnetif, &ipaddr, &netmask, &gw, NULL, &ethernetif_init, &ethernet_input);

    /* Registers the default network interface */
    netif_set_default(&gnetif);

    /* We must always bring the network interface up connection or not... */
    netif_set_up(&gnetif);

    /* Set the link callback function, this function is called on change of link status*/
    netif_set_link_callback(&gnetif, ethernet_link_status_updated);

    /* Create the Ethernet link handler thread */
}

3. 作为服务器端连接相关函数

cpp 复制代码
//--------------------------- Include ---------------------------//
#include "eth_tcps.h"
#include "tcp.h"
#include <string.h>
//--------------------- Function Prototype ----------------------//
void eth_tcps_init(void);
err_t tcp_loopback_accept(void *arg, struct tcp_pcb *newpcb, err_t err);
err_t tcp_loopback_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err);
//TCP初始化
void eth_tcps_init(void)
{
  struct tcp_pcb *pcb = NULL;
	
  pcb = tcp_new();																	//创建一个新的TCP协议控制块
	
  tcp_bind(pcb, IP_ADDR_ANY, TCP_SERVER_PORT);				//给TCP协议控制块绑定端口号和IP地址
	
  pcb = tcp_listen(pcb);														//将连接的状态设置为侦听
	
  tcp_accept(pcb, tcp_loopback_accept);   					  //指定LISTENing连接连接到其他主机时的回调函数

}
//TCP监听
err_t tcp_loopback_accept(void *arg, struct tcp_pcb *newpcb, err_t err)
{
  tcp_recv(newpcb, tcp_loopback_recv);							//初始化LwIP的tcp_recv回调功能  
  return ERR_OK;
}
//TCP接收
err_t tcp_loopback_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
{
  if (p != NULL) 
  {
		tcp_recved(tpcb, p->tot_len);				//TCP数据接收
		
		tcp_write(tpcb, p->payload, p->tot_len, 1);	//将接收到的数据加入到发送缓冲队列中
			
		memset(p->payload, 0, p->tot_len);
		pbuf_free(p);							    //释放ptr
    
  } 
  else if (err == ERR_OK) 
  {
    return tcp_close(tpcb);
  }
  return ERR_OK;
}

实验现象

1、打开控制面板-->网络和Internet-->网络和共享中心-->更改适配器设置-->以太网属性

2、Internet协议版本4,选择使用下面的IP地址,然后更改IP地址,IP地址与代码中的设置的一致。

3、打开测试工具,点击创建连接,弹出设置端口的窗口,设置为60001

相关推荐
切糕师学AI3 小时前
ARM Cortex-M 中的断点单元FPB是什么?
arm开发·cortex-m
czy87874753 小时前
用C语言实现建造者模式
c语言
hollq3 小时前
SATM32F103RCT6采集温度并设置阈值报警
stm32·嵌入式硬件
小龙报4 小时前
《算法通关指南之C++编程篇(5)----- 条件判断与循环(下)》
c语言·开发语言·c++·算法·visualstudio·学习方法·visual studio
1379号监听员_4 小时前
嵌入式软件架构--显示界面架构(工厂流水线模型,HOME界面,命令界面)
stm32·单片机·架构·命令模式
Fortunate Chen4 小时前
初识C语言12. 结构体(自定义类型的核心工具)
c语言·开发语言·笔记
Fortunate Chen4 小时前
初识C语言13.自定义类型(联合体与枚举)
c语言·开发语言
程小k5 小时前
C++设计模式
c语言·c++
爱倒腾的老唐5 小时前
07、点亮第一个LED
单片机·51单片机