STM32H743-ARM例程26-TCP_CLIENT

目录

实验平台

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

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

互联网模型

在介绍本章实验前,我们先来了解下互联网相关知识,这样阅读起来遇到专有名词更好理解。

通信协议是指双方实体完成通信或服务所必须遵循的规则和约定。以太网通信比较复杂,国际标准组织将整个以太网通信结构制定了OSI模型 , 总共分层七个层,分别为应用层、表示层、会话层、传输层、网络层、数据链路层以及物理层,每一层都负责不同的功能,从物理连接到应用程序的处理。这种模型有助于不同的系统之间进行通信时,更好地理解和管理网络通信的过程。

应用层:为应用程序或用户请求提供各种请求服务。OSI参考模型最高层,也是最靠近用户的一层,为计算机用户、各种应用程序以及网络提供接口,也为用户直接提供各种网络服务。

表示层:数据编码、格式转换、数据加密。提供各种用于应用层数据的编码和转换功能,确保一个系统的应用层发送的数据能被另一个系统的应用层识别。如果必要,该层可提供一种标准表示形式,用于将计算机内部的多种数据格式转换成通信中采用的标准表示形式。数据压缩和加密也是表示层可提供的转换功能之一。

会话层:创建、管理和维护会话。接收来自传输层的数据,负责建立、管理和终止表示层实体之间的通信会话,支持它们之间的数据交换。该层的通信由不同设备中的应用程序之间的服务请求和响应组成。

传输层:数据通信。建立主机端到端的链接,为会话层和网络层提供端到端可靠的和透明的数据传输服务,确保数据能完整的传输到网络层。

网络层:IP选址及路由选择。通过路由选择算法,为报文或通信子网选择最适当的路径。控制数据链路层与传输层之间的信息转发,建立、维持和终止网络的连接。数据链路层的数据在这一层被转换为数据包,然后通过路径选择、分段组合、顺序、进/出路由等控制,将信息从一个网络设备传送到另一个网络设备。

数据链路层:提供介质访问和链路管理。接收来自物理层的位流形式的数据,封装成帧,传送到网络层;将网络层的数据帧,拆装为位流形式的数据转发到物理层;负责建立和管理节点间的链路,通过各种控制协议,将有差错的物理信道变为无差错的、能可靠传输数据帧的数据链路。

物理层:管理通信设备和网络媒体之间的互联互通。传输介质为数据链路层提供物理连接,实现比特流的透明传输。实现相邻计算机节点之间比特流的透明传送,屏蔽具体传输介质和物理设备的差异。

TCP/IP五层模型

TCP/IP是一组协议的代名词,它包括许多协议,组成了TCP/IP协议簇。它是把OSI七层模型简化成了五层模型。每一层都呼叫它的下一层所提供的网络来完成自己的需求.

开放式系统互联模型(OSI)是一个参考标准,解释协议相互之间应该如何相互作用。TCP/IP协议是美国国防部发明的,是让互联网成为了目前这个样子的标准之一,是互联网通信使用的网络协议,由网络层的IP协议和传输层的TCP协议组成。

在TCP/IP模型中,数据链路层又被分为LLC层(逻辑链路层)和MAC层(媒体介质访问层)。目前,对于普通的接入网络终端的设备, LLC层和MAC层是软、硬件的分界线。如PC的网卡主要负责实现参考模型中的MAC子层和物理层,在PC的软件系统中则有一套庞大程序实现了LLC层及以上的所有网络层次的协议。

由硬件实现的物理层和MAC子层在不同的网络形式有很大的区别,如以太网和Wi-Fi,这是由物理传输方式决定的。 但由软件实现的其它网络层次通常不会有太大区别,在PC上也许能实现完整的功能,一般支持所有协议,而在嵌入式领域则按需要进行裁剪。

以太网

以太网(Ethernet)是一种基于IEEE 802.3标准的局域网(LAN)技术,用于设备间通过有线或光纤介质进行数据通信。由于它是在组网技术中占的比例最高,很多人直接把以太网理解为互联网。

以太网基于的IEEE 802.3标准主要是位于参考模型的物理层(PHY)和数据链路层中的介质访问控制子层(MAC)。

IEEE还有其它局域网标准, 如IEEE 802.11是无线局域网,俗称Wi-Fi。IEEE802.15是个人域网,即蓝牙技术,其中的802.15.4标准则是ZigBee技术。

PHY层

从硬件的角度看,以太网接口电路主要由MAC(Media Access Control)控制器和物理层接口PHY(Physical Layer,PHY)两大部分构成。如下图所示:
  DMA控制器通常属于CPU的一部分,用虚线放在这里是为了表示DMA控制器可能会参与到网口数据传输中。

但是,在实际的设计中,以上三部分并不一定独立分开的。由于,PHY整合了大量模拟硬件,而MAC是典型的全数字器件。考虑到芯片面积及模拟/数字混合架构的原因,通常,将MAC集成进微控制器而将PHY留在片外。更灵活、密度更高的芯片技术已经可以实现MAC和PHY的单芯片整合。

PHY(Physical Layer,物理层)是以太网协议栈中的最底层(OSI模型第1层),负责在物理介质(如电缆、光纤)上传输和接收原始比特流。它是连接数字信号(MAC层)与模拟信号(物理介质)的桥梁,确保数据在硬件层面的可靠传输。
PHY的核心功能

  • 信号调制与编码:
    • 将MAC层发送的数字信号转换为适合物理介质传输的模拟信号(如差分电压、光脉冲)。
  • 常用编码方式:
    • 曼彻斯特编码(10BASE-T)
    • 4D-PAM5(100BASE-TX)
    • PAM4(25G/100G以太网)
  • 时钟恢复与同步:
    • 从接收信号中提取时钟信息,确保发送端与接收端时序一致。
  • 链路检测与自协商:
    • 通过**链路脉冲(Link Pulse)**检测物理连接状态。
    • 支持Auto-Negotiation(自动协商),自动选择最佳速率(10/100/1000Mbps)和双工模式。
  • 错误检测与校正:
    • 使用前向纠错(FEC)技术(如RS-FEC),减少高速传输中的误码率。

MAC子层

链路层,又称 MAC 层。MAC 的全称为(Medium Access Control),即媒体访问控制,主要负责控制与连接物理层的物理介质。在发送数据的时候,MAC 协议可以事先判断是否可以发送数据,如果可以发送将给数据加上一些控制信息,最终将数据以及控制信息以规定的格式发送到物理层;在接收数据的时候,MAC 协议首先判断输入的信息是否发生传输错误,如果没有错误,则去掉控制信息发送至 LLC 层。

有关MC数据包本章节不再详解,有兴趣的读者可以参考MAC数据包格式链接。

TCP/IP协议栈

TCP/IP协议栈是计算机网络通信中的核心概念,它定义了不同计算机之间如何进行数据交换和通信的规则。通俗讲就是符合以太网通信要求的代码集合, 一般要求它可以实现图 TCP_IP参考模型 中每个层对应的协议,比如应用层的HTTP、FTP、DNS、SMTP协议, 传输层的TCP、UDP协议、网络层的IP、ICMP协议等等。

Windows操作系统、UNIX类操作系统都有自己的一套方法来实现TCP/IP通信协议,它们都提供非常完整的TCP/IP协议。对于一般的嵌入式设备, 受制于硬件条件没办法支持使用在Window或UNIX类操作系统的运行的TCP/IP协议栈,一般只能使用简化版本的TCP/IP协议栈, 目前开源的适合嵌入式的有uIP、TinyTCP、uC/TCP-IP、LwIP等等。其中LwIP是目前在嵌入式网络领域被讨论和使用广泛的协议栈。

LwIP是Light Weight (轻型)IP协议,有无操作系统的支持都可以运行。LwIP实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用,它只需十几KB的RAM和40K左右的ROM就可以运行,这使LwIP协议栈适合在低端的嵌入式系统中使用。

LwIP协议栈主要关注的是怎么样减少内存的使用和代码的大小,这样就可以让LwIP适用于资源有限的小型平台例如嵌入式系统。为了简化处理过程和内存要求,LwIP对API进行了裁减,可以不需要复制一些数据。
其主要特性如下:

(1) 支持多网络接口下的IP转发;

(2) 支持ICMP协议;

(3) 包括实验性扩展的UDP(用户数据报协议);

(4) 包括阻塞控制、RTT 估算、快速恢复和快速转发的TCP(传输控制协议);

(5) 提供专门的内部回调接口(Raw API),用于提高应用程序性能;

(6) 可选择的Berkeley接口API (在多线程情况下使用) ;

(7) 在最新的版本中支持ppp;

(8) 新版本中增加了的IP fragment的支持;

(9) 支持DHCP协议,动态分配ip地址。

ETH

STM32H743系列控制器内部集成了一个以太网外设,它实际是一个通过DMA控制器进行介质访问控制(MAC),它的功能就是实现MAC层的任务。 借助以太网外设,STM32H743控制器可以通过ETH外设按照IEEE 802.3-2002标准发送和接收MAC数据包。ETH内部自带专用的DMA控制器用于MAC, ETH支持两个工业标准接口介质独立接口(MII)和简化介质独立接口(RMII)用于与外部PHY芯片连接。MII和RMII接口用于MAC数据包传输, ETH还集成了站管理接口(SMI)接口专门用于与外部PHY通信,用于访问PHY芯片寄存器。

STM32H743自带以太网模块特点包括:

  • 实现10M/100Mbit/s的数据传输速率;
  • 专用DMA控制器允许专用SRAM之间高速传输;
  • 标明MAC支持(VLAN支持);
  • 通过符合IEEE802.3的MII/RMII接口与外部以太网PHY进行通信;
  • 支持全双工和半双工操作;
  • MAC控制子层(控制帧)支持;
  • 32位CRC生成和删除;
  • 支持多种灵活的地址过滤模式;
  • 每次发送或接收32位状态码;
  • 提供接收和发送两组FIFO;
  • 通过SMI(MDIO)接口配置和管理PHY设备;
  • 支持以太网时间戳(参见IEEE1588-2008),时间戳比较器连接到TIM2输入;
  • 当系统时间大于目标时间时触发中断。
  • 支持DMA。

STM32H743以太网功能框图,如图所示:

PHY:LAN8720A

LAN8720A是低功耗的10/100M以太网PHY层芯片,I/O引脚电压符合IEEE802.3-2005标准,支持通过RMII接口与以太网MAC层通信,内置10-BASE-T/100BASE-TX全双工传输模块,支持10Mbps和100Mbps。由它组成的网络结构如下图所示:

LAN8720A功能框图如图所示:

LAN8720A可以通过自协商的方式与目的主机最佳的连接方式(速度和双工模式),支持HPAuto-MDIX自动翻转功能,无需更换网线即可将连接更改为直连或交叉连接。LAN8720A的主要特点如下:

  • 高性能的10/100M以太网传输模块;
  • 支持RMII接口以减少引脚数;
  • 支持全双工和半双工模式;
  • 两个状态LED输出;
  • 可以使用25M晶振以降低成本;
  • 支持自协商模式;
  • 支持HPAuto-MDIX自动翻转功能;
  • 支持SMI串行管理接口;
  • 支持MAC接口。

原理图

GT7000带有LAN8720A嵌入式以太网控制器,本实验实现TCP客户端功能。以PC作为服务器,GT7000作为客户端,PC的IP地址192.168.0.1,端口号为60001,GT7000的IP地址为192.168.0.10,端口随机。当客户端连接到服务器,TCP建立成功即可进行数据信息传输。实验原理图如下

STM32CubeMX生成工程

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




MPU配置

LWIP配置

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

这里需要是能IPV6

这里需要使能IPV6

注意:在CubeMX配置TCP过程中踩过的坑,给读者提供参考

  1. 在CubeMX配置工程文件中MDK版本号我们要选择v5.27如下图所示,5.32版本可能遇到LWIP初始化卡死问题。
  2. 生成工程文件后我们还需要修改Keilv5的编译环境如下图所示:

实验程序

1. 主函数

cpp 复制代码
int main(void)
{
    MPU_Config();
    SCB_EnableICache();
    SCB_EnableDCache();
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_USART6_UART_Init();
    MX_LWIP_Init();
    
    uart6.initialize(115200);
    uart6.printf("\x0c");
    uart6.printf("GT7000 OK!\r\n");
    HAL_GPIO_WritePin(PHYAD0_GPIO_Port,PHYAD0_Pin,GPIO_PIN_RESET);//PHY复位
    eth_tcpc.initialize();//初始化TCP
    
  while (1)
    {
        MX_LWIP_Process();
        //连接服务器
        if(_200ms_flag == 1){//每200ms连接次服务器
            _200ms_flag = 0;
            if(!eth_tcpc.connect_flag){
                eth_tcpc.connection_close(eth_tcpc.tcpc_pcb,0);
                eth_tcpc.initialize();
            }
        }
        
        if(eth_tcpc.receive_ok_flag == 1){//如果接收到TCP发来的数据返回该数据内容
            eth_tcpc.receive_ok_flag = 0;
            eth_tcpc.send_data(eth_tcpc.tcpc_pcb);  
        }
    }
}

2. LwIP初始化

cpp 复制代码
void MX_LWIP_Init(void)
{
    //设置本机IP,和IP网关
    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] = 100;

    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);

    /* Create IPv6 local address */
    netif_create_ip6_linklocal_address(&gnetif, 0);
    netif_ip6_addr_set_state(&gnetif, 0, IP6_ADDR_VALID);
    gnetif.ip6_autoconfig_enabled = 1;

    /* 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_tcpc.h"
#include <string.h>
//--------------------- Function Prototype ----------------------//   
static void initialize(void);
static err_t receive_data(void *arg,struct tcp_pcb *tpcb,struct pbuf *p,err_t err);
static err_t send_data(struct tcp_pcb *tpcb);
static void connection_close(struct tcp_pcb *tpcb, struct tcp_client_struct * es);
static err_t connected(void *arg, struct tcp_pcb *tpcb, err_t err);	
static void tcp_client_senddata(struct tcp_pcb *tpcb, struct tcp_client_struct * es);

//--------------------------- Variable --------------------------// 
ETH_TCPC_T eth_tcpc = {
	.initialize = initialize,
	.receive_data = receive_data,
	.send_data = send_data,
	.connection_close = connection_close,
	.connected = connected,
	.receive_ok_flag = 0,
	.connect_flag = 0,
};

uint8_t PCIP_ADDRESS[4] = {192, 168, 0, 10};
//TCP初始化
void initialize(void)
{
	eth_tcpc.tcpc_pcb = tcp_new();											//创建一个新的TCP协议控制块
	
	IP4_ADDR(&eth_tcpc.rmtipaddr.u_addr.ip4, \
						PCIP_ADDRESS[0], PCIP_ADDRESS[1], \
						PCIP_ADDRESS[2], PCIP_ADDRESS[3]);					//设置服务器(PC)IP地址

	tcp_connect(eth_tcpc.tcpc_pcb, &eth_tcpc.rmtipaddr, PC_PORT,connected);	//连接至服务器
} 
//接收数据
err_t receive_data(void *arg,struct tcp_pcb *tpcb,struct pbuf *p,err_t err)
{
	uint32_t data_len = 0;
	struct pbuf *q;
	struct tcp_client_struct *es;
	err_t ret_err; 
	
	LWIP_ASSERT("arg != NULL",arg != NULL);
	es = (struct tcp_client_struct *)arg; 
	if(p == NULL){															//如果从服务器接收到空的数据帧就关闭连接
		es->state=ES_TCPCLIENT_CLOSING;										//需要关闭TCP 连接了 
 		es->p=p; 
		ret_err=ERR_OK;
	}else if(err != ERR_OK){												//当接收到一个非空的数据帧,但是err!=ERR_OK
		if(p){
			pbuf_free(p);													//释放接收pbuf
		}
		ret_err = err;
	}else if(es->state == ES_TCPCLIENT_CONNECTED){							//当处于连接状态时
		if(p != NULL){														//当处于连接状态并且接收到的数据不为空时
			memset(eth_tcpc.receive_buffer,0,EHT_BUFFER_SIZE);  			//数据接收缓冲区清零
			for(q=p;q!=NULL;q=q->next){  									//遍历完整个pbuf链表
				if(q->len > (EHT_BUFFER_SIZE-data_len)) memcpy(eth_tcpc.receive_buffer+data_len,q->payload,(EHT_BUFFER_SIZE-data_len));//拷贝数据
				else memcpy(eth_tcpc.receive_buffer+data_len,q->payload,q->len);
				data_len += q->len;  	
				if(data_len > EHT_BUFFER_SIZE) break; 						//超出TCP客户端接收数组,跳出	
			}
			eth_tcpc.receive_ok_flag = 1;
 			tcp_recved(tpcb,p->tot_len);									//用于获取接收数据,通知LWIP可以获取更多数据
			pbuf_free(p);  													//释放内存
			ret_err = ERR_OK;
		}
	}else{  																//接收到数据但是连接已经关闭,
		tcp_recved(tpcb,p->tot_len);										//用于获取接收数据,通知LWIP可以获取更多数据
		es->p=NULL;
		pbuf_free(p); 														//释放内存
		ret_err=ERR_OK;
	}
	
	return ret_err;
} 
//发送数据
err_t send_data(struct tcp_pcb *tpcb)
{
	err_t ret_err;
	struct tcp_client_struct *es; 
	
	memcpy(eth_tcpc.send_buffer,eth_tcpc.receive_buffer,1024);
	
	es = tpcb->callback_arg;
	if(es != NULL){  																																		//连接处于空闲可以发送数据
		es->p = pbuf_alloc(PBUF_TRANSPORT, strlen((char*)eth_tcpc.send_buffer),PBUF_POOL);//申请内存 
		pbuf_take(es->p,(char*)eth_tcpc.send_buffer,strlen((char*)eth_tcpc.send_buffer));//将eth_tcpc.send_buffer[]中的数据拷贝到es->p_tx中
		tcp_client_senddata(tpcb,es);																											//将eth_tcpc.send_buffer[]里面复制给pbuf的数据发送出去
		if(es->p)pbuf_free(es->p);																												//释放内存
		ret_err=ERR_OK;
	}else{
		tcp_abort(tpcb);																																	//终止连接,删除pcb控制块
		ret_err=ERR_ABRT;
	}
	return ret_err;
} 
//TCP客户端发送数据
void tcp_client_senddata(struct tcp_pcb *tpcb, struct tcp_client_struct * es)
{
	struct pbuf *ptr; 
 	err_t wr_err = ERR_OK;
	
	while((wr_err==ERR_OK)&&es->p&&(es->p->len<=tcp_sndbuf(tpcb))){
		ptr = es->p;
		wr_err = tcp_write(tpcb,ptr->payload,ptr->len,1); //将要发送的数据加入到发送缓冲队列中
		if(wr_err == ERR_OK){
			es->p = ptr->next;			                  //指向下一个pbuf
			if(es->p)pbuf_ref(es->p);					  //pbuf的ref加一
			pbuf_free(ptr);								  //释放ptr 
		}else if(wr_err == ERR_MEM)es->p=ptr;
      tcp_output(tpcb);		        					  //将发送缓冲队列中的数据立即发送出去
	} 	
} 
//TCP客户端报错
void tcp_client_error(void *arg,err_t err)
{  
	//这里我们不做任何处理
} 
//TCP客户端测试
err_t tcp_client_poll(void *arg, struct tcp_pcb *tpcb)
{
	err_t ret_err;
	struct tcp_client_struct *es;
	
	es = (struct tcp_client_struct*)arg;
  if(es->state == ES_TCPCLIENT_CLOSING){  		//连接断开
 		eth_tcpc.connection_close(tpcb,es);   	//关闭TCP连接
	} 
	ret_err = ERR_OK;
	
  return ret_err;
} 

err_t tcp_client_sent(void *arg, struct tcp_pcb *tpcb, u16_t len)
{
	struct tcp_client_struct *es;
	
	LWIP_UNUSED_ARG(len);
	
	es = (struct tcp_client_struct*)arg;
	if(es->p)
		tcp_client_senddata(tpcb,es);				//发送数据
	
	return ERR_OK;
}
//客户端断开连接
void connection_close(struct tcp_pcb *tpcb, struct tcp_client_struct * es)
{
	eth_tcpc.connect_flag = 0;

	tcp_abort(tpcb);
	tcp_arg(tpcb,NULL);  
	tcp_recv(tpcb,NULL);
	tcp_sent(tpcb,NULL);
	tcp_err(tpcb,NULL);
	tcp_poll(tpcb,NULL,0);  
	if(es)mem_free(es); 
}
//客户端链接
err_t connected(void *arg, struct tcp_pcb *tpcb, err_t err)
{
	struct tcp_client_struct *es = NULL;  
	
	if(err==ERR_OK){
		es = (struct tcp_client_struct*)mem_malloc(sizeof(struct tcp_client_struct));
		if(es){ 												//内存申请成功
 			es->state=ES_TCPCLIENT_CONNECTED;					//状态为连接成功
			es->pcb=tpcb;  
			es->p=NULL; 
			tcp_arg(tpcb,es);        							//使用es更新tpcb的callback_arg
			tcp_recv(tpcb,eth_tcpc.receive_data);  				//初始化LwIP的tcp_recv回调功能   
			tcp_err(tpcb,tcp_client_error); 					//初始化tcp_err()回调函数
			tcp_sent(tpcb,tcp_client_sent);						//初始化LwIP的tcp_sent回调功能
			tcp_poll(tpcb,tcp_client_poll,1); 					//初始化LwIP的tcp_poll回调功能 
			err=ERR_OK;
			eth_tcpc.connect_flag = 1;
		}else{ 
			eth_tcpc.connection_close(tpcb,es);					//关闭连接
			err=ERR_MEM;										//返回内存分配错误
		}
	}else{
		eth_tcpc.connection_close(tpcb,0);						//关闭连接
	}
	
	return err;
}

实验现象

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

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

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

服务器已经创建完成(如下图),点击启动服务器

4、在发送区编辑完要发送的数据信息后,点击发送即可收到发送的数据包。如图所示:

相关推荐
杨福瑞5 小时前
数据结构:顺序表讲解(1)
c语言·开发语言·数据结构
djk88885 小时前
一个完整的 TCP 服务器监听示例(C#)
服务器·tcp/ip·c#
清风6666665 小时前
基于单片机的开尔文电路电阻测量WIFI上传设计
单片机·嵌入式硬件·毕业设计·课程设计
程序猿编码5 小时前
轻量级却实用:sigtrace 如何靠 ptrace 实现 Linux 信号的捕获与阻断(C/C++代码实现)
linux·c语言·c++·信号·捕获·ptrace
曦樂~5 小时前
【Qt】TCP连接--客户端和服务器
服务器·网络·c++·qt·tcp/ip
久爱物联网6 小时前
串口调试数据-之TCP透传
网络·网络协议·tcp/ip
LaoZhangGong1236 小时前
IR红外遥控器和接收器
c语言·遥控器·红外·ir
☆璇7 小时前
【Linux】Socket编程TCP
linux·服务器·tcp/ip
晨非辰7 小时前
【数据结构入坑指南】--《层序分明:堆的实现、排序与TOP-K问题一站式攻克(源码实战)》
c语言·开发语言·数据结构·算法·面试