【ESP32】ESP-IDF开发 | WiFi开发 | TCP传输控制协议 + TCP服务器和客户端例程

1. 简介

        TCP(Transmission Control Protocol),全称传输控制协议。它的特点有以下几点:面向连接,每一个TCP连接只能是点对点的(一对一);提供可靠交付服务;提供全双工通信面向字节流

1.1 三次握手

        三次握手代表的是TCP的连接过程,它表示在成功连接之前,服务端和客户端需要通信三次才能最终确认。

  • 第一次握手:客户端将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给服务器端,客户端进入SYN_SENT状态,等待服务器端确认;
  • 第二次握手:服务器端收到数据包后由标志位SYN=1知道客户端请求建立连接,服务器端将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给客户端以确认连接请求,服务器端进入SYN_RCVD状态;
  •  第三次握手:客户端收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给服务器端,服务器端检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,客户端和服务器端进入ESTABLISHED状态,完成三次握手,随后客户端与服务器端之间可以开始传输数据了。

1.2 四次挥手

        四次挥手代表的是TCP的断开连接过程,它表示在成功断开前,服务端和客户端需要通信四次才能最终确认。

  • 第一次挥手:客户端发送一个FIN=M,用来关闭客户端到服务器端的数据传送,客户端进入FIN_WAIT_1状态,表示客户端没有数据需要发送了;但是如果服务器端还有数据没有发送完成,则可以继续发送数据;
  • 第二次挥手:服务器端收到FIN后,先发送ack=M+1,告诉客户端请求收到了,但是我还没准备好,要继续等待我的消息;这个时候客户端就进入FIN_WAIT_2 状态,继续等待服务器端的FIN报文;
  • 第三次挥手:当服务器端确定数据已发送完成,则向客户端发送FIN=N报文,告诉客户端数据发完了,准备关闭连接;服务器端进入LAST_ACK状态;
  • 第四次挥手:客户端收到FIN=N报文后,就知道可以关闭连接了,但是他还是不相信网络,怕服务器端不知道要关闭,所以发送ack=N+1后会进入TIME_WAIT状态,如果服务端没有收到ACK则可以重传。服务器端收到ACK后,就知道可以断开连接了。如果客户端等待了2MSL后依然没有收到回复,则证明服务器端已正常关闭,那客户端也可以关闭连接了。

 

1.3 拥塞控制

         网络就像我们生活中的交通系统,当车流大的时候就可能会导致拥塞,TCP为了保证可靠的交付服务,所以引入了拥塞控制,灵活调整发送策略。

        拥塞控制是一个动态的过程,它既要提高带宽利用率发送尽量多的数据又要避免网络拥堵丢包RTT增大等问题,基于这种高要求并不是单一策略可以搞定的,因此TCP的拥塞控制策略实际上是分阶段分策略的综合过程,包括慢开始(slow start)、拥塞避免(congestion avoidance)、快重传(fast retransmit)和快恢复(fast recovery)。

1. 慢开始

        慢开始算法的思路为,在数据开始发送时,由于不清楚网络的负荷情况,如果此时立即把大量数据发送到网络,那么就有可能引起网络拥塞。根据生活中的经验进行引伸,较好的方法是由小大到逐渐增大发送窗口,一步步探测网络链路的极限;也就是说,由小到大逐渐增大拥塞窗口数值(cwnd),下面是其简要工作流程图。

        由上图可见,一开始发送方的初始cwnd 为1,发送方发送第一个报文段M1,并收到接收方的确认。此时,发送方将cwnd从1增大到2,接着发送M2和M3两个报文段,收到两个报文的确认后,发送方继续将cwnd加倍,增加到4。只要网络仍然通畅,那么发送方就会以此类推,在每一轮的传输成功后将cwnd进行加倍的操作。

        显然,cwnd不能无限制地加倍,这样会引起网络拥塞,因此需要设置一个慢开始门限(ssthresh)状态变量。当cwnd < ssthresh时,才会使用上述的慢开始算法。

2. 拥塞避免

        当cwnd > ssthresh时,系统会使用拥塞避免算法,该算法的思路是让拥塞窗口(cwnd)缓慢地增长,即每完成一轮传输就把发送方的拥塞窗口(cwnd)加1,而不是像慢开始阶段那样加倍增长。因此在拥塞避免阶段就有“加法增大”(Additive Increase)的特点。这表明在拥塞避免阶段,拥塞窗口(cwnd)按线性规律缓慢增长,比慢开始算法的拥塞窗口增长速率缓慢得多。下图展示了拥塞控制的工作流程。

        在上图中,慢开始门限的初始值为16。一开始系统执行慢开始算法,发送方每成功发送一轮报文段,就把拥塞窗口值加倍,然后开始下一轮的传输。因此拥塞窗口cwnd随着传输轮次按指数规律增长。当拥塞窗口cwnd增长到慢开始门限值ssthresh 时,就开始改为执行拥塞避免算法,拥塞窗口按线性规律增长。

        当进行到第12轮传输时(上图节点2),网络出现了超时,发送方判断为网络拥塞。于是调整门限值ssthresh = cwnd / 2 = 12,同时设置拥塞窗口cwnd = 1,重新进入慢开始阶段。

        上图节点3展示了一个特殊情况,此时拥塞窗口cwnd = 16,这时出现了发送方一连收到3个对同一报文段的重复确认的情况,此时执行了拥塞避免算法,调整门限值ssthresh = cwnd / 2 = 8。这是因为如果发送方迟迟收不到确认,就会产生超时,会误认为网络发生了拥塞。这就导致发送方错误地启动慢开始,把拥塞窗口cwnd又置为1,因而降低了传输效率。

3. 快重传

        TCP作为一个可靠的协议面临的很大的问题就是丢包,丢包就要重传因此发送方需要根据接收方回复的ACK来确认是否丢包了,下图为超时重传的典型时序图。

        重传超时时间(RTO)是随着复杂网络环境而动态变化的,在拥塞控制中发生超时重传将会极大拉低cwnd,如果网络状况并没有那么多糟糕,偶尔出现网络抖动造成丢包或者阻塞也非常常见,因此触发的慢启动将降低通信性能,故出现了快速重传机制。所谓快速重传时相比超时重传而言的,重发等待时间会降低并且后续尽量避免慢启动,来保证性能损失在最小的程度,下图为其时序图。

        快速重传和超时重传的区别在于cwnd在发生拥塞时的取值,超时重传会将cwnd修改为最初的值,也就是慢启动的值,快速重传将cwnd减半,二者都将ssthresh设置为cwnd的一半。从二者的区别可以看到,快速重传更加主动,有利于保证链路的传输性能。

4. 快恢复

        在快速重传之后就会进入快速恢复阶段,此时的cwnd为上次发生拥塞时的cwnd的1/2,之后cwnd再线性增加重复之前的过程。

2. lwIP

        ESP-IDF使用lwIP库实现TCP/IP协议栈,这个库在大多数嵌入式系统中都有用到,它是对底层硬件的上层封装,所以如果未来要写比如Linux的TCP/IP应用,代码也是通用的。

3. 例程

        例程分别在ESP32上实现TCP客户端和服务端,使用电脑作为另一方进行简单通信测试。需要注意的是,测试时,ESP32和电脑必须处于同一局域网

        电脑端测试会使用的上位机为野火串口调试助手,下载地址:FireTools

3.1 客户端

        这个例程配置ESP32为客户端,当连接WiFi热点成功后会请求连接服务端,连接成功后会发送一段消息,然后阻塞等待服务端回复,服务端恢复消息后ESP32会主动关闭套接字。

#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_mac.h"
#include "nvs_flash.h"
#include "sys/socket.h"
#include "lwip/err.h"
#include "lwip/sys.h"
#include "netdb.h"
#include "arpa/inet.h"#include <string.h>#define TAG "app"
#define HOST_IP_ADDR "192.168.10.117"
#define HOST_PORT 20001static char rx_buffer[128];
static const char *payload = "Message from ESP32";
static TaskHandle_t client_task_handle;static void tcp_client_task(void *args)
{struct sockaddr_in dest_addr;inet_pton(AF_INET, HOST_IP_ADDR, &dest_addr.sin_addr);dest_addr.sin_family = AF_INET;dest_addr.sin_port = htons(HOST_PORT);while (1) {int sock =  socket(AF_INET, SOCK_STREAM, IPPROTO_IP);if (sock < 0) {ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);break;}ESP_LOGI(TAG, "Socket created, connecting to %s:%d", HOST_IP_ADDR, HOST_PORT);int err = connect(sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr));if (err != 0) {ESP_LOGE(TAG, "Socket unable to connect: errno %d", errno);break;}ESP_LOGI(TAG, "Successfully connected");err = send(sock, payload, strlen(payload), 0);if (err < 0) {ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);break;}memset(rx_buffer, 0, sizeof(rx_buffer));int len = recv(sock, rx_buffer, sizeof(rx_buffer) - 1, 0);if (len < 0) {ESP_LOGE(TAG, "recv failed: errno %d", errno);} else {ESP_LOGI(TAG, "Received %d bytes from %s:", len, HOST_IP_ADDR);ESP_LOGI(TAG, "data: %s", rx_buffer);}close(sock);vTaskDelay(1000 / portTICK_PERIOD_MS);}
}static void wifi_event_handler(void* arg,esp_event_base_t event_base,int32_t event_id,void* event_data)
{if (event_base == IP_EVENT) {if (event_id == IP_EVENT_STA_GOT_IP) {xTaskCreate(tcp_client_task, "tcp_client", 2048, NULL, 5, &client_task_handle);}} else if (event_base == WIFI_EVENT) {if (event_id == WIFI_EVENT_STA_DISCONNECTED) {vTaskDelete(client_task_handle);} else if (event_id == WIFI_EVENT_STA_START) {esp_wifi_connect();}}
}int app_main()
{/* 初始化NVS */esp_err_t ret = nvs_flash_init();if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {ESP_ERROR_CHECK(nvs_flash_erase());ESP_ERROR_CHECK(nvs_flash_init());}/* 初始化WiFi协议栈 */ESP_ERROR_CHECK(esp_netif_init());ESP_ERROR_CHECK(esp_event_loop_create_default());esp_netif_create_default_wifi_sta();wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();ESP_ERROR_CHECK(esp_wifi_init(&cfg));ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,ESP_EVENT_ANY_ID,&wifi_event_handler,NULL,NULL));ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,ESP_EVENT_ANY_ID,&wifi_event_handler,NULL,NULL));wifi_config_t wifi_config = {.sta = {.ssid = "Your SSID",.password = "Your password",.threshold.authmode = WIFI_AUTH_WPA_WPA2_PSK,},};ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));ESP_ERROR_CHECK(esp_wifi_start());return 0;
}

        ESP32的WiFi驱动初始化在前面的文章已经有详细的介绍了,这里不再赘述。

        在回调函数中,当驱动获取到IP后,就会创建TCP客户端的任务。

1. 创建socket套接字

        调用socket函数创建,第一个参数表示域,这里使用IPv4,对应IP_INET;第二个参数表示socket类型,TCP协议只能填SOCK_STREAM;第三个参数表示协议栈类型,这里填IPPROTO_IP。函数会返回套接字描述符。

2. 连接服务器

        调用connect函数,第一个参数传入套接字描述符;比较重要的是第二个参数,要传入服务器的地址信息。结构体的定义如下:

struct sockaddr_in {u8_t            sin_len;sa_family_t     sin_family;in_port_t       sin_port;struct in_addr  sin_addr;
#define SIN_ZERO_LEN 8char            sin_zero[SIN_ZERO_LEN];
};
  • sin_len:数据长度(一般不需要填);
  • sin_family:套接字类型,IPv4填AF_INET,IPv6填AF_INET6,其他填AF_UNSPEC;
  • sin_port:端口;
  • sin_zero:上层预留字节(不用管)。

3.  发送数据

        调用send函数。传入套接字描述符、数据指针和数据长度即可;最后一个参数是标志位,一般填0即可,可选的标志位如下:

#define MSG_PEEK       0x01
#define MSG_WAITALL    0x02
#define MSG_OOB        0x04
#define MSG_DONTWAIT   0x08
#define MSG_MORE       0x10
#define MSG_NOSIGNAL   0x20

        这些标志位是发送和接收都支持的,比较常用的是MSG_DONTWAIT,像发送和接收函数是阻塞的,使能这个标志位可以让函数立即返回,不等待数据。

4. 接收数据

        调用recv函数。传入的参数与send函数是一致的,不再赘述。

5. 关闭连接

        调用close函数。传入套接字描述符即可。

        测试的时候先打开上位机,设置为TCP服务器,填写电脑的IP和端口,端口是自定义的,但注意不要与原有的端口冲突,建议设置20000以上比较保险;最后点击开始监听。

3.2 服务端

        这个例程就是在ESP32上搭建一个TCP服务器,接受局域网中的客户端连接并接收数据。

#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_mac.h"
#include "nvs_flash.h"
#include "sys/socket.h"
#include "lwip/err.h"
#include "lwip/sys.h"
#include "netdb.h"
#include "arpa/inet.h"#include <string.h>#define TAG "app"
#define HOST_PORT 20001static const char *payload = "I have received your message";
static TaskHandle_t server_task_handle;static void tcp_client_task(void *args)
{int *sock = args;int len;char rx_buffer[128] = {0};while (1) {memset(rx_buffer, 0, sizeof(rx_buffer));len = recv(*sock, rx_buffer, sizeof(rx_buffer) - 1, 0);if (len < 0) {ESP_LOGE(TAG, "Error occurred during receiving: errno %d", errno);} else if (len == 0) {ESP_LOGW(TAG, "Connection closed");goto __exit;} else {ESP_LOGI(TAG, "Received %d bytes, data: %s", len, rx_buffer);send(*sock, payload, strlen(payload), 0);}}__exit:close(*sock);free(sock);vTaskDelete(NULL);
}static void tcp_server_task(void *args)
{esp_ip4_addr_t *ip_addr = args;struct sockaddr_in dest_addr = {0};dest_addr.sin_addr.s_addr = ip_addr->addr;dest_addr.sin_family = AF_INET;dest_addr.sin_port = htons(HOST_PORT);int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);if (sock < 0) {ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);goto __exit;}int err = bind(sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr));if (err != 0) {ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);goto __exit;}err = listen(sock, 1);if (err != 0) {ESP_LOGE(TAG, "Error occurred during listen: errno %d", errno);goto __exit;}ESP_LOGI(TAG, "Server listen at " IPSTR ":%d", IP2STR(ip_addr), HOST_PORT);while (1) {struct sockaddr_in source_addr = {0};socklen_t addr_len = sizeof(struct sockaddr_in);int *client = malloc(sizeof(int));*client = accept(sock, (struct sockaddr *)&source_addr, &addr_len);if (*client < 0) {ESP_LOGE(TAG, "Unable to accept connection: errno %d", errno);} else {ESP_LOGI(TAG, "Client " IPSTR ":%d connected", IP2STR((struct esp_ip4_addr *)&source_addr.sin_addr), source_addr.sin_port);xTaskCreate(tcp_client_task, "client_task", 2048, client, 6, NULL);}}__exit:close(sock);free(ip_addr);vTaskDelete(NULL);
}static void wifi_event_handler(void* arg,esp_event_base_t event_base,int32_t event_id,void* event_data)
{if (event_base == IP_EVENT) {if (event_id == IP_EVENT_STA_GOT_IP) {ip_event_got_ip_t *data = event_data;esp_ip4_addr_t *ip_addr = malloc(sizeof(esp_ip4_addr_t));memcpy(ip_addr, &data->ip_info.ip, sizeof(esp_ip4_addr_t));xTaskCreate(tcp_server_task, "tcp_server", 2048, ip_addr, 5, &server_task_handle);}} else if (event_base == WIFI_EVENT) {if (event_id == WIFI_EVENT_STA_DISCONNECTED) {vTaskDelete(server_task_handle);} else if (event_id == WIFI_EVENT_STA_START) {esp_wifi_connect();}}
}int app_main()
{/* 初始化NVS */esp_err_t ret = nvs_flash_init();if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {ESP_ERROR_CHECK(nvs_flash_erase());ESP_ERROR_CHECK(nvs_flash_init());}/* 初始化WiFi协议栈 */ESP_ERROR_CHECK(esp_netif_init());ESP_ERROR_CHECK(esp_event_loop_create_default());esp_netif_create_default_wifi_sta();wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();ESP_ERROR_CHECK(esp_wifi_init(&cfg));ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,ESP_EVENT_ANY_ID,&wifi_event_handler,NULL,NULL));ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,ESP_EVENT_ANY_ID,&wifi_event_handler,NULL,NULL));wifi_config_t wifi_config = {.sta = {.ssid = "Your SSID",.password = "Your password",.threshold.authmode = WIFI_AUTH_WPA_WPA2_PSK,},};ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));ESP_ERROR_CHECK(esp_wifi_start());return 0;
}

        服务器的代码与客户端是有一部分重合的,所以下面只重点介绍不同的地方。

1. 创建套接字

        参考上面。

2. 绑定IP与端口

        调用bind函数。传入的参数其实跟connect函数是一样的;但是这里的IP地址是自己的IP地址,端口的话就是自定义的。

3. 监听端口

        调用listen函数。第一个参数传入套接字描述符,第二个参数用来使能log记录。

4. 接受客户端连接

        调用accept函数。传入套接字描述符、IP地址结构体和结构体的长度。这个结构体是用来接收客户端的IP信息的,初始化为空即可。这个函数是阻塞的,只要没有客户端连接就不会返回;如果有客户端连接,就会返回该客户端的套接字描述符。

        例程中,一旦客户端成功连接就会创建一个线程处理这个客户端的数据,这样的话就能实现多客户端的连接服务。

5. 接收数据

        参考上面。如果recv函数返回0则代表客户端断开了连接,这时我们就可以关闭这个套接字,退出线程。

6. 发送数据

        参考上面。

        测试时先将ESP32上电,确保服务器已经启动并处于监听状态;然后在上位机这里设置为TCP客户端模式,填入ESP32的IP和端口;然后就可以连接并发送数据了。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

AI时序预测: iTransformer算法代码深度解析

在之前的文章中&#xff0c;我对iTransformer的Paper进行了详细解析&#xff0c;具体文章如下&#xff1a; 文章链接&#xff1a;深度解析iTransformer&#xff1a;维度倒置与高效注意力机制的结合 今天&#xff0c;我将对iTransformer代码进行解析。回顾Paper&#xff0c;我…

某盾Blackbox参数参数逆向

以前叫同盾&#xff0c;现在改名了&#xff0c;叫小盾安全&#xff0c;好像不做验证码了

docker中运行的MySQL怎么修改密码

1&#xff0c;进入MySQL容器 docker exec -it 容器名 bash 我运行了 docker ps命令查看。正在运行的容器名称。可以看到MySQL的我起名为db docker exec -it db bash 这样就成功的进入到容器中了。 2&#xff0c;登录MySQL中 mysql -u 用户名 -p 回车 密码 mysql -u root -p roo…

春节期间,景区和酒店如何合理用工?

春节期间&#xff0c;景区和酒店如何合理用工&#xff1f; 春节期间&#xff0c;旅游市场将迎来高峰期。景区与酒店&#xff0c;作为旅游产业链中的两大核心环节&#xff0c;承载着无数游客的欢乐与期待。然而&#xff0c;也隐藏着用工管理的巨大挑战。如何合理安排人力资源&a…

初始化mysql报错cannot open shared object file: No such file or directory

报错展示 我在初始化msyql的时候报错&#xff1a;mysqld: error while loading shared libraries: libaio.so.1: cannot open shared object file: No such file or directory 解读&#xff1a; libaio包的作用是为了支持同步I/O。对于数据库之类的系统特别重要&#xff0c;因此…

C语言------数组从入门到精通

1.一维数组 目标:通过思维导图了解学习一维数组的核心知识点: 1.1定义 使用 类型名 数组名[数组长度]; 定义数组。 // 示例&#xff1a; int arr[5]; 1.2一维数组初始化 数组的初始化可以分为静态初始化和动态初始化两种方式。 它们的主要区别在于初始化的时机和内存分配的方…

Docker/K8S

文章目录 项目地址一、Docker1.1 创建一个Node服务image1.2 volume1.3 网络1.4 docker compose 二、K8S2.1 集群组成2.2 Pod1. 如何使用Pod(1) 运行一个pod(2) 运行多个pod 2.3 pod的生命周期2.4 pod中的容器1. 容器的生命周期2. 生命周期的回调3. 容器重启策略4. 自定义容器启…

【开源免费】基于SpringBoot+Vue.JS公交线路查询系统(JAVA毕业设计)

本文项目编号 T 164 &#xff0c;文末自助获取源码 \color{red}{T164&#xff0c;文末自助获取源码} T164&#xff0c;文末自助获取源码 目录 一、系统介绍二、数据库设计三、配套教程3.1 启动教程3.2 讲解视频3.3 二次开发教程 四、功能截图五、文案资料5.1 选题背景5.2 国内…

< OS 有关 > Android 手机 SSH 客户端 app: connectBot

connectBot 开源且功能齐全的SSH客户端,界面简洁,支持证书密钥。 下载量超 500万 方便在 Android 手机上&#xff0c;连接 SSH 服务器&#xff0c;去运行命令。 Fail2ban 12小时内抓获的 IP ~ ~ ~ ~ rootjpn:~# sudo fail2ban-client status sshd Status for the jail: sshd …

中国股市“慢牛”行情的实现路径与展望

在现代经济体系中&#xff0c;股市不仅是企业融资的重要平台&#xff0c;也是投资者财富增值的关键渠道。一个健康、稳定、持续增长的股市&#xff0c;对于推动经济高质量发展、提升国家金融竞争力具有深远意义。近年来&#xff0c;“慢牛”行情成为众多投资者和市场参与者对我…

Linux Samba 低版本漏洞(远程控制)复现与剖析

目录 前言 漏洞介绍 漏洞原理 产生条件 漏洞影响 防御措施 复现过程 结语 前言 在网络安全的复杂生态中&#xff0c;系统漏洞的探索与防范始终是保障数字世界安全稳定运行的关键所在。Linux Samba 作为一款在网络共享服务领域应用极为广泛的软件&#xff0c;其低版本中…

ResNet 残差网络

目录 网络结构 残差块&#xff08;Residual Block&#xff09; ResNet网络结构示意图 残差块&#xff08;Residual Block&#xff09;细节 基本残差块&#xff08;ResNet-18/34&#xff09; Bottleneck残差块&#xff08;ResNet-50/101/152&#xff09; 残差连接类型对比 变体网…

【Unity3D】实现横版2D游戏角色二段跳、蹬墙跳、扶墙下滑

目录 一、二段跳、蹬墙跳 二、扶墙下滑 一、二段跳、蹬墙跳 GitHub - prime31/CharacterController2D 下载工程后直接打开demo场景&#xff1a;DemoScene&#xff08;Unity 2019.4.0f1项目环境&#xff09; Player物体上的CharacterController2D&#xff0c;Mask添加Wall层…

FPGA 使用 CLOCK_LOW_FANOUT 约束

使用 CLOCK_LOW_FANOUT 约束 您可以使用 CLOCK_LOW_FANOUT 约束在单个时钟区域中包含时钟缓存负载。在由全局时钟缓存直接驱动的时钟网段 上对 CLOCK_LOW_FANOUT 进行设置&#xff0c;而且全局时钟缓存扇出必须低于 2000 个负载。 注释&#xff1a; 当与其他时钟约束配合…

Excel 技巧21 - Excel中整理美化数据实例,Ctrl+T 超级表格(★★★)

本文讲Excel中如何整理美化数据的实例&#xff0c;以及CtrlT 超级表格的常用功能。 目录 1&#xff0c;Excel中整理美化数据 1-1&#xff0c;设置间隔行颜色 1-2&#xff0c;给总销量列设置数据条 1-3&#xff0c;根据总销量设置排序 1-4&#xff0c;加一个销售趋势列 2&…

Leetcode:219

1&#xff0c;题目 2&#xff0c;思路 第一种就是简单的暴力比对当时过年没细想 第二种&#xff1a; 用Map的特性key唯一&#xff0c;把数组的值作为Map的key值我们每加载一个元素都会去判断这个元素在Map里面存在与否如果存在进行第二个判断条件abs(i-j)<k,条件 符合直接…

MySQL(高级特性篇) 14 章——MySQL事务日志

事务有4种特性&#xff1a;原子性、一致性、隔离性和持久性 事务的隔离性由锁机制实现事务的原子性、一致性和持久性由事务的redo日志和undo日志来保证&#xff08;1&#xff09;REDO LOG称为重做日志&#xff0c;用来保证事务的持久性&#xff08;2&#xff09;UNDO LOG称为回…

芯片AI深度实战:进阶篇之vim内verilog实时自定义检视

本文基于Editor Integration | ast-grep&#xff0c;以及coc.nvim&#xff0c;并基于以下verilog parser(my-language.so&#xff0c;文末下载链接), 可以在vim中实时显示自定义的verilog 匹配。效果图如下&#xff1a; 需要的配置如下&#xff1a; 系列文章&#xff1a; 芯片…

C++:多继承习题5

题目内容&#xff1a; 先建立一个Point(点)类&#xff0c;包含数据成员x,y(坐标点)。以它为基类&#xff0c;派生出一个Circle(圆)类&#xff0c;增加数据成员r(半径)&#xff0c;再以Circle类为直接基类&#xff0c;派生出一个Cylinder(圆柱体)类&#xff0c;再增加数据成员h…

基于阿里云百炼大模型Sensevoice-1的语音识别与文本保存工具开发

基于阿里云百炼大模型Sensevoice-1的语音识别与文本保存工具开发 摘要 随着人工智能技术的不断发展&#xff0c;语音识别在会议记录、语音笔记等场景中得到了广泛应用。本文介绍了一个基于Python和阿里云百炼大模型的语音识别与文本保存工具的开发过程。该工具能够高效地识别东…