当前位置: 首页 > article >正文

【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和端口;然后就可以连接并发送数据了。

相关文章:

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

1. 简介 TCP&#xff08;Transmission Control Protocol&#xff09;&#xff0c;全称传输控制协议。它的特点有以下几点&#xff1a;面向连接&#xff0c;每一个TCP连接只能是点对点的&#xff08;一对一&#xff09;&#xff1b;提供可靠交付服务&#xff1b;提供全双工通信&…...

算法基础学习——快排与归并(附带java模版)

快速排序和归并排序是两种速度较快的排序方式&#xff0c;是最应该掌握的两种排序算法&#xff0c; &#xff08;一&#xff09;快速排序&#xff08;不稳定的&#xff09; 基本思想&#xff1a;分治 平均时间复杂度&#xff1a;O(nlogn) / 最慢O(n^2) / 最快O(n) 步骤&…...

指针的进化—sizeof和strlen对比(字符串和字符数组的区分)

1.前言 如果你对各个数组的内容存放是什么没有个清晰的概念&#xff0c;对指针偏移之后的数量算不出来或者模棱两可&#xff0c;那么本篇就来详细介绍sizeof和strlen来具象化的显示数组的内容存放了多少内容&#xff0c;偏移量变化后的变化&#xff0c;这个数组进行运算后会不会…...

TensorFlow简单的线性回归任务

如何使用 TensorFlow 和 Keras 创建、训练并进行预测 1. 数据准备与预处理 2. 构建模型 3. 编译模型 4. 训练模型 5. 评估模型 6. 模型应用与预测 7. 保存与加载模型 8.完整代码 1. 数据准备与预处理 我们将使用一个简单的线性回归问题&#xff0c;其中输入特征 x 和标…...

【memgpt】letta 课程1/2:从头实现一个自我编辑、记忆和多步骤推理的代理

llms-as-operating-systems-agent-memory llms-as-operating-systems-agent-memory内存 操作系统的内存管理...

6-图像金字塔与轮廓检测

文章目录 6.图像金字塔与轮廓检测(1)图像金字塔定义(2)金字塔制作方法(3)轮廓检测方法(4)轮廓特征与近似(5)模板匹配方法6.图像金字塔与轮廓检测 (1)图像金字塔定义 高斯金字塔拉普拉斯金字塔 高斯金字塔:向下采样方法(缩小) 高斯金字塔:向上采样方法(放大)…...

深入理解Java引用传递

先看一段代码&#xff1a; public static void add(String a) {a "new";System.out.println("add: " a); // 输出内容&#xff1a;add: new}public static void main(String[] args) {String a null;add(a);System.out.println("main: " a);…...

925.长按键入

目录 一、题目二、思路三、解法四、收获 一、题目 你的朋友正在使用键盘输入他的名字 name。偶尔&#xff0c;在键入字符 c 时&#xff0c;按键可能会被长按&#xff0c;而字符可能被输入 1 次或多次。 你将会检查键盘输入的字符 typed。如果它对应的可能是你的朋友的名字&am…...

【Rust自学】15.2. Deref trait Pt.1:什么是Deref、解引用运算符*与实现Deref trait

喜欢的话别忘了点赞、收藏加关注哦&#xff0c;对接下来的教程有兴趣的可以关注专栏。谢谢喵&#xff01;(&#xff65;ω&#xff65;) 15.2.1. 什么是Deref trait Deref的全写是Dereference&#xff0c;就是引用的英文reference加上"de"这个反义前缀&#xff0c…...

图书管理系统 Axios 源码__新增图书

目录 功能介绍 核心代码解析 源码&#xff1a;新增图书功能 总结 本项目基于 HTML、Bootstrap、JavaScript 和 Axios 开发&#xff0c;实现了图书的增删改查功能。以下是新增图书的功能实现&#xff0c;适合前端开发学习和项目实践。 功能介绍 用户可以通过 模态框&#xf…...

吴恩达深度学习——超参数调试

内容来自https://www.bilibili.com/video/BV1FT4y1E74V&#xff0c;仅为本人学习所用。 文章目录 超参数调试调试选择范围 Batch归一化公式整合 Softmax 超参数调试 调试 目前学习的一些超参数有学习率 α \alpha α&#xff08;最重要&#xff09;、动量梯度下降法 β \bet…...

【赵渝强老师】K8s中Pod探针的ExecAction

在K8s集群中&#xff0c;当Pod处于运行状态时&#xff0c;kubelet通过使用探针&#xff08;Probe&#xff09;对容器的健康状态执行检查和诊断。K8s支持三种不同类型的探针&#xff0c;分别是&#xff1a;livenessProbe&#xff08;存活探针&#xff09;、readinessProbe&#…...

如何对系统调用进行扩展?

扩展系统调用是操作系统开发中的一个重要任务。系统调用是用户程序与操作系统内核之间的接口,允许用户程序执行内核级操作(如文件操作、进程管理、内存管理等)。扩展系统调用通常包括以下几个步骤: 一、定义新系统调用 扩展系统调用首先需要定义新的系统调用的功能。系统…...

ChatGPT与GPT的区别与联系

ChatGPT 和 GPT 都是基于 Transformer 架构的语言模型&#xff0c;但它们有不同的侧重点和应用。下面我们来探讨一下它们的区别与联系。 1. GPT&#xff08;Generative Pre-trained Transformer&#xff09; GPT 是一类由 OpenAI 开发的语言模型&#xff0c;基于 Transformer…...

安卓(android)订餐菜单【Android移动开发基础案例教程(第2版)黑马程序员】

一、实验目的&#xff08;如果代码有错漏&#xff0c;可查看源码&#xff09; 1.掌握Activity生命周的每个方法。 2.掌握Activity的创建、配置、启动和关闭。 3.掌握Intent和IntentFilter的使用。 4.掌握Activity之间的跳转方式、任务栈和四种启动模式。 5.掌握在Activity中添加…...

Python安居客二手小区数据爬取(2025年)

目录 2025年安居客二手小区数据爬取观察目标网页观察详情页数据准备工作&#xff1a;安装装备就像打游戏代码详解&#xff1a;每行代码都是你的小兵完整代码大放送爬取结果 2025年安居客二手小区数据爬取 这段时间需要爬取安居客二手小区数据&#xff0c;看了一下相关教程基本…...

happytime

happytime 一、查壳 无壳&#xff0c;64位 二、IDA分析 1.main 2.cry函数 总体&#xff1a;是魔改的XXTEA加密 在main中可以看到被加密且分段的flag在最后的循环中与V6进行比较&#xff0c;刚好和上面v6数组相同。 所以毫无疑问密文是v6. 而与flag一起进入加密函数的v5就…...

深度学习 DAY3:NLP发展史

NLP发展史 NLP发展脉络简要梳理如下&#xff1a; (远古模型&#xff0c;上图没有但也可以算NLP&#xff09; 1940 - BOW&#xff08;无序统计模型&#xff09; 1950 - n-gram&#xff08;基于词序的模型&#xff09; (近代模型&#xff09; 2001 - Neural language models&am…...

前端知识速记:节流与防抖

前端知识速记&#xff1a;节流与防抖 什么是防抖&#xff1f; 防抖是一种控制事件触发频率的方法&#xff0c;通常用于处理用户频繁触发事件的场景。防抖的核心思想是将多个连续触发事件合并为一个事件&#xff0c;以减少执行次数。它在以下场景中特别有效&#xff1a; 输入…...

家居EDI:Hom Furniture EDI需求分析

HOM Furniture 是一家成立于1977年的美国家具零售商&#xff0c;总部位于明尼苏达州。公司致力于提供高品质、时尚的家具和家居用品&#xff0c;满足各种家庭和办公需求。HOM Furniture 以广泛的产品线和优质的客户服务在市场上赢得了良好的口碑。公司经营的产品包括卧室、客厅…...

【3】阿里面试题整理

[1]. ES架构&#xff0c;如何进行路由以及选主 路由&#xff1a;在Elasticsearch&#xff08;ES&#xff09;中&#xff0c;默认的路由算法是基于文档的_id。具体来说&#xff0c;Elasticsearch会对文档的_id进行哈希计算&#xff0c;然后对分片数量取模&#xff0c;以确定该文…...

【08-飞线和布线与输出文件】

导入网表后 1.复制结构图(带板宽的) 在机械一层画好外围线 2.重新定义板子形状(根据选则对象取定义) 选中对象生成板子线条形状 3.PCB和原理图交叉选择模式 过滤器选择原理图里的元器件 过滤器"OFF",只开启Componnets,只是显示元器件 4. 模块化布局 PCB高亮元…...

python 从知网的期刊导航页面抓取与农业科技相关的数据

要从知网的期刊导航页面抓取与农业科技相关的数据&#xff0c;并提取《土壤学报》2016年06期的结果&#xff0c;可以使用requests库来获取网页内容&#xff0c;BeautifulSoup库来解析HTML。由于知网页面结构可能会发生变化&#xff0c;在实际使用中&#xff0c;需要根据页面结构…...

【单细胞第二节:单细胞示例数据分析-GSE218208】

GSE218208 1.创建Seurat对象 #untar(“GSE218208_RAW.tar”) rm(list ls()) a data.table::fread("GSM6736629_10x-PBMC-1_ds0.1974_CountMatrix.tsv.gz",data.table F) a[1:4,1:4] library(tidyverse) a$alias:gene str_split(a$alias:gene,":",si…...

机器学习优化算法:从梯度下降到Adam及其变种

机器学习优化算法&#xff1a;从梯度下降到Adam及其变种 引言 最近deepseek的爆火已然说明&#xff0c;在机器学习领域&#xff0c;优化算法是模型训练的核心驱动力。无论是简单的线性回归还是复杂的深度神经网络&#xff0c;优化算法的选择直接影响模型的收敛速度、泛化性能…...

.NET Core 中依赖注入的使用

ASP.NET Core中服务注入的地方 在ASP.NET Core项目中一般不需要自己创建ServiceCollection、IServiceProvider。在Program.cs的builder.Build()之前向builder.Services中注入。在Controller中可以通过构造方法注入服务。 低使用频率的服务 把Action用到的服务通过Action的参…...

XML Schema 数值数据类型

XML Schema 数值数据类型 引言 XML Schema 是一种用于描述 XML 文档结构的语言。它定义了 XML 文档中数据的有效性和结构。在 XML Schema 中,数值数据类型是非常重要的一部分,它定义了 XML 文档中可以包含的数值类型。本文将详细介绍 XML Schema 中常用的数值数据类型,以及…...

【机器学习理论】生成模型和判别模型

生成模型和判别模型是机器学习中两种不同的建模方式。生成模型关注的是联合概率分布 P ( X , Y ) P(X, Y) P(X,Y)&#xff0c;即同时考虑数据 X X X和标签 Y Y Y的关系&#xff1b;判别模型则直接学习条件概率 P ( Y ∣ X ) P(Y|X) P(Y∣X)或决策边界。 生成模型 生成模型的目…...

ZZNUOJ(C/C++)基础练习1031——1040(详解版)

1031 : 判断点在第几象限 题目描述 从键盘输入2个整数x、y值&#xff0c;表示平面上一个坐标点&#xff0c;判断该坐标点处于第几象限&#xff0c;并输出相应的结果。 输入 输入x&#xff0c;y值表示一个坐标点。坐标点不会处于x轴和y轴上&#xff0c;也不会在原点。 输出 输出…...

使用PyTorch实现逻辑回归:从训练到模型保存与性能评估

1. 引入必要的库 首先&#xff0c;需要引入必要的库。PyTorch用于构建和训练模型&#xff0c;pandas和numpy用于数据处理&#xff0c;scikit-learn用于计算性能指标。 import torch import torch.nn as nn import torch.optim as optim import pandas as pd import numpy as …...