网站查询域名ip入口/广告代理公司

网站查询域名ip入口,广告代理公司,东莞网站制作网络建设公司,国内外基于vue框架的网站建设现状【Linux】【网络】UDP打洞–>不同子网下的客户端和服务器通信(成功版) 根据上个文章的分析 问题可能出现在代码逻辑上面 我这里重新查找资料怀疑: 1 NAT映射可能需要多次数据包的发送才能建立。 2 NAT映射保存时间太短&#xff…

【Linux】【网络】UDP打洞–>不同子网下的客户端和服务器通信(成功版)
根据上个文章的分析 问题可能出现在代码逻辑上面 我这里重新查找资料怀疑:

1 NAT映射可能需要多次数据包的发送才能建立

2 NAT映射保存时间太短,并且 NAT 可能会在短时间内改变这些映射,需要一直保持映射。

  • 有些 NAT 设备会因为短时间内没有数据而回收端口映射,导致服务器提供的 IP:Port 失效。

  • 保活机制:双方定期发送保活包以防 NAT 超时关闭映射。

3 服务器只是向双方发送了IP和端口后直接退出了,并未发送数据包给客户端,导致NAT 设备未建立映射
(服务器只发送了一次数据,但部分 NAT 设备需要多次交互才会创建映射,一些 NAT 需要收到外部数据包后才会保持映射。)

4 我在代码中绑定了固定端口,NAT内部可能会有自己的实现,让系统自己分配端口可能更合适

5 需要双方在获得对方公网地址后,立即向对方发送“敲门”数据包,以便各自的 NAT 建立映射。

  • NAT 设备通常只允许曾经主动发送数据给对方的地址接收数据。
  • 客户端第一次sendto()的数据包会被丢掉 后续才能正确通信

这里贴一下udp通信接口
在这里插入图片描述

整体流程

1. 注册阶段

  • 客户端动作:

    • 每个客户端(C1 和 C2)启动时创建至少一个 UDP 套接字(有些实现中为了隔离控制与数据可能创建两个,但最关键的是:
      • 用于向服务器发送注册消息,并接收服务器返回的信息)。
    • 客户端向服务器发送注册消息(例如 “HELLO”)。
    • 此时,通过调用 recvfrom(),服务器能够获得客户端发送数据时的源地址信息,即 NAT 映射后的公网 IP 和端口。
  • 服务器动作:

    • 服务器启动后创建一个 UDP 套接字并绑定到固定端口(例如 50001)。
    • 服务器循环等待接收客户端的注册消息,同时记录每个客户端的公网映射地址(从 recvfrom 返回的 sockaddr 信息中获得)。

2. 地址交换阶段

  • 服务器端:
    • 用于存储两个客户端的地址信息当前注册的客户端数量(例如使用数组 struct sockaddr_in clientAddrs[2] 和计数器 clientCount)。
    • 当服务器接收到两个客户端的注册消息后(比如两条 “HELLO” 消息),服务器将这两个客户端的公网映射地址互换:
      • 向客户端1发送消息,格式例如 "PEER <C2公网IP>^<C2映射端口>"
      • 向客户端2发送消息,格式例如 "PEER <C1公网IP>^<C1映射端口>"
    • 为确保 NAT 映射持续有效(避免超时关闭),服务器还可以再额外向两个客户端发送探测数据包(如 “ping”),激活双方的 NAT 映射。

3. NAT 打洞阶段

  • 客户端(C1 和 C2):
    • 在收到服务器返回的包含对方公网映射地址的信息后,每个客户端解析出对方的公网 IP 和映射端口。
    • 然后,客户端立即向对方发送一个“敲门”数据包(例如 “knock” 或 “ping”),目的是:
      • 主动触发自己 NAT 设备建立向对方的映射,
      • 同时激活对方 NAT 中关于本端发送数据的映射。
    • 双方互发“敲门”包后,各自的 NAT 设备就会允许对方返回数据,即使之前还没有真正建立起正式的数据通信。

4. 双向通信阶段

  • 建立通信:

    • 双方均使用服务器返回的地址(即各自 NAT 映射后的公网 IP 和端口)作为目标地址来发送数据包。
    • 客户端进入一个循环:
      • 定时(例如每 500ms 或每 5 秒)向对方发送数据包(这可以是用户输入的消息,也可以是保活数据,如 “KEEP_ALIVE”),
      • 同时接收对方的回复,从而确认通信通道已建立。
  • 保活机制:

    • 为防止 NAT 映射因长时间无数据而超时关闭,每个客户端可启动独立的保活线程,定时向对方发送“保活包”,确保映射持续有效。

代码

server.cpp

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>
#include <errno.h>
#include <pthread.h>// 定义服务器监听端口和缓冲区大小
#define SERVER_PORT 50001
#define BUF_SIZE 1024// 全局变量:用于存储两个客户端的地址信息和当前注册的客户端数量
struct sockaddr_in clientAddrs[2];
int clientCount = 0;
// 互斥锁保护对 clientAddrs 和 clientCount 的访问
pthread_mutex_t clients_mutex = PTHREAD_MUTEX_INITIALIZER;int main() {int sockfd;                // UDP 套接字描述符char buffer[BUF_SIZE];     // 用于接收数据的缓冲区struct sockaddr_in serverAddr, clientAddr;socklen_t addrLen = sizeof(clientAddr);// 1. 创建 UDP 套接字sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0) {std::cerr << "Server: Socket creation error: " << strerror(errno) << std::endl;return -1;}// 2. 设置并初始化服务器地址结构体memset(&serverAddr, 0, sizeof(serverAddr));serverAddr.sin_family = AF_INET;              // IPv4serverAddr.sin_addr.s_addr = INADDR_ANY;        // 接受任意IPserverAddr.sin_port = htons(SERVER_PORT);       // 绑定到 SERVER_PORT// 3. 绑定套接字到指定地址和端口if (bind(sockfd, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) {std::cerr << "Server: Bind error: " << strerror(errno) << std::endl;close(sockfd);return -1;}std::cout << "Server is running on port " << SERVER_PORT << ". Waiting for clients..." << std::endl;// 4. 进入无限循环,不断接收客户端消息while (true) {// 清空缓冲区,准备接收新的数据memset(buffer, 0, BUF_SIZE);// 使用 recvfrom 接收客户端数据,同时获取发送者地址int n = recvfrom(sockfd, buffer, BUF_SIZE, 0, (struct sockaddr*)&clientAddr, &addrLen);if (n < 0) {std::cerr << "Server: recvfrom error: " << strerror(errno) << std::endl;continue;}buffer[n] = '\0';  // 确保数据以字符串形式结束std::cout << "Received from client: " << buffer << " from "<< inet_ntoa(clientAddr.sin_addr) << ":" << ntohs(clientAddr.sin_port) << std::endl;// 加锁后处理客户端注册数据pthread_mutex_lock(&clients_mutex);// 当收到 "HELLO" 消息且当前注册客户端数不足 2 时,保存客户端地址信息if (strcmp(buffer, "HELLO") == 0 && clientCount < 2) {clientAddrs[clientCount] = clientAddr;clientCount++;// 回复 ACK 给注册的客户端,确认收到消息const char* ack = "ACK";sendto(sockfd, ack, strlen(ack), 0, (struct sockaddr*)&clientAddr, addrLen);}// 如果两个客户端都已注册,交换它们的公网映射地址信息if (clientCount == 2) {char msg[BUF_SIZE] = { 0 };// 构造发送给客户端1的消息:包含客户端2的公网IP和映射端口,格式为 "PEER <ip> <port>"snprintf(msg, BUF_SIZE, "PEER %s %d",inet_ntoa(clientAddrs[1].sin_addr),ntohs(clientAddrs[1].sin_port));sendto(sockfd, msg, strlen(msg), 0,(struct sockaddr*)&clientAddrs[0], sizeof(clientAddrs[0]));std::cout << "Sent to client1: " << msg << std::endl;// 构造发送给客户端2的消息:包含客户端1的公网IP和映射端口snprintf(msg, BUF_SIZE, "PEER %s %d",inet_ntoa(clientAddrs[0].sin_addr),ntohs(clientAddrs[0].sin_port));sendto(sockfd, msg, strlen(msg), 0,(struct sockaddr*)&clientAddrs[1], sizeof(clientAddrs[1]));std::cout << "Sent to client2: " << msg << std::endl;// 为保持 NAT 映射有效,可选:服务器额外发送“ping”包给双方sendto(sockfd, "ping", 4, 0, (struct sockaddr*)&clientAddrs[0], sizeof(clientAddrs[0]));sendto(sockfd, "ping", 4, 0, (struct sockaddr*)&clientAddrs[1], sizeof(clientAddrs[1]));// 重置客户端计数,等待下一组客户端注册clientCount = 0;}pthread_mutex_unlock(&clients_mutex);}close(sockfd);return 0;
}

  1. 头文件包含与宏定义

    • 包含了网络编程、字符串操作、线程和错误处理所需的头文件。
    • 定义了服务器端口(50001)和缓冲区大小(1024字节)。
  2. 全局变量声明

    • clientAddrs[2] 用于存储两个客户端的地址。
    • clientCount 记录已注册的客户端数量。
    • 使用 clients_mutex 保护这些共享变量的并发访问。
  3. 主函数开始

    • 创建 UDP 套接字,并检查是否成功;若失败则打印错误信息并退出。
    • 初始化服务器地址结构体(IPv4、任意地址、端口转换为网络字节序)。
    • 绑定套接字到服务器地址。如果绑定失败则退出。
  4. 打印服务器启动信息

    • 输出服务器已启动并监听的提示信息。
  5. 进入无限循环,接收客户端消息

    • 每次循环开始前清空缓冲区。
    • 调用 recvfrom() 接收客户端数据,并填充客户端地址(即 NAT 映射的公网地址)。
    • 打印接收到的消息和客户端地址(转换为点分十进制 IP 与主机字节序的端口)。
  6. 客户端注册处理(加锁区域)

    • 当接收到 “HELLO” 消息且 clientCount < 2 时,将当前客户端地址保存到 clientAddrs 数组中,同时发送 “ACK” 消息确认注册。
    • 这一步确保服务器能记录每个客户端 NAT 映射后的公网地址。
  7. 地址互换与探测(当两个客户端均已注册时)

    • 如果 clientCount == 2,服务器构造两个字符串:
      • 分别包含对方的公网 IP 和映射后的端口,格式为 "PEER <ip> <port>"
    • 分别将该信息发送给两个客户端,完成地址信息的互换。
    • 额外发送“ping”数据包给双方,以确保双方 NAT 映射不会因长时间无数据而关闭。
    • 最后重置 clientCount,等待下一组客户端注册。
  8. 退出与关闭套接字

    • 循环结束后关闭套接字并返回。

client.cpp(双方一致)

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>
#include <errno.h>
#include <pthread.h>
#include <stdlib.h>
#include <net/if.h>  // 用于 IFNAMSIZ 和 if_nametoindex#define SERVER_PORT 50001        // 服务器端口号
#define BUF_SIZE 1024            // 缓冲区大小
#define KEEP_ALIVE_INTERVAL 25   // 保活包发送间隔(秒)// 全局变量:保存对等端(peer)的地址信息
struct sockaddr_in peerAddr;
// 全局的 UDP 套接字描述符(用于所有操作)
int sockfd;
// 定义互斥锁,用于保护共享的套接字操作,避免多线程竞争
pthread_mutex_t sock_mutex = PTHREAD_MUTEX_INITIALIZER;// 保活线程函数:定时向对方发送 "KEEP_ALIVE" 消息,保持 NAT 映射
void* keep_alive(void* arg) {const char* keepAliveMsg = "KEEP_ALIVE";while (true) {pthread_mutex_lock(&sock_mutex);// 使用 sendto() 发送保活包到对方地址sendto(sockfd, keepAliveMsg, strlen(keepAliveMsg), 0,(struct sockaddr*)&peerAddr, sizeof(peerAddr));pthread_mutex_unlock(&sock_mutex);std::cout << "[KeepAlive] Sent keep alive to peer." << std::endl;sleep(KEEP_ALIVE_INTERVAL);}return NULL;
}int main(int argc, char* argv[]) {// 命令行参数:需要传入服务器 IP 和本地绑定的网络接口(例如 "eth0")if (argc < 3) {std::cerr << "Usage: " << argv[0] << " <server_ip> <bind_interface>" << std::endl;std::cerr << "Example: " << argv[0] << " 203.0.113.5 eth0" << std::endl;return -1;}const char* serverIP = argv[1];const char* bindInterface = argv[2]; // 指定绑定的网络接口// 1. 创建 UDP 套接字sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0) {std::cerr << "Client: Socket creation error: " << strerror(errno) << std::endl;return -1;}// 2. 绑定套接字到指定网络接口// 这一步可以确保数据包通过指定的接口发送,防止“Network is unreachable”错误if (setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, bindInterface, strlen(bindInterface)) < 0) {std::cerr << "Client: SO_BINDTODEVICE error: " << strerror(errno) << std::endl;close(sockfd);return -1;}// 3. 绑定本地地址,让系统自动分配端口(无需固定端口)struct sockaddr_in localAddr;memset(&localAddr, 0, sizeof(localAddr));localAddr.sin_family = AF_INET;localAddr.sin_addr.s_addr = INADDR_ANY;localAddr.sin_port = htons(0);  // 0 表示由系统自动分配if (bind(sockfd, (struct sockaddr*)&localAddr, sizeof(localAddr)) < 0) {std::cerr << "Client: Bind error: " << strerror(errno) << std::endl;close(sockfd);return -1;}// 4. 设置服务器地址结构struct sockaddr_in serverAddr;memset(&serverAddr, 0, sizeof(serverAddr));serverAddr.sin_family = AF_INET;// 将服务器 IP 转换为网络地址格式if (inet_pton(AF_INET, serverIP, &serverAddr.sin_addr) <= 0) {std::cerr << "Client: Invalid server IP." << std::endl;close(sockfd);return -1;}serverAddr.sin_port = htons(SERVER_PORT);socklen_t serverLen = sizeof(serverAddr);// 5. 向服务器发送注册消息 "HELLO"const char* helloMsg = "HELLO";if (sendto(sockfd, helloMsg, strlen(helloMsg), 0,(struct sockaddr*)&serverAddr, serverLen) < 0) {std::cerr << "Client: sendto error: " << strerror(errno) << std::endl;close(sockfd);return -1;}std::cout << "Sent HELLO to server." << std::endl;// 6. 接收来自服务器的回复char buffer[BUF_SIZE] = { 0 };struct sockaddr_in fromAddr;socklen_t fromLen = sizeof(fromAddr);int n = recvfrom(sockfd, buffer, BUF_SIZE, 0, (struct sockaddr*)&fromAddr, &fromLen);if (n < 0) {std::cerr << "Client: recvfrom error: " << strerror(errno) << std::endl;close(sockfd);return -1;}buffer[n] = '\0';std::cout << "Received from server: " << buffer << std::endl;// 7. 如果收到的是 ACK,则继续等待服务器发来的 PEER 信息if (strncmp(buffer, "ACK", 3) == 0) {memset(buffer, 0, BUF_SIZE);n = recvfrom(sockfd, buffer, BUF_SIZE, 0, (struct sockaddr*)&fromAddr, &fromLen);if (n < 0) {std::cerr << "Client: recvfrom error: " << strerror(errno) << std::endl;close(sockfd);return -1;}buffer[n] = '\0';std::cout << "Received from server: " << buffer << std::endl;}// 8. 解析服务器返回的 PEER 信息,格式为 "PEER <peer_ip> <peer_port>"char peerIP[INET_ADDRSTRLEN];int peerPort;if (sscanf(buffer, "PEER %s %d", peerIP, &peerPort) == 2) {memset(&peerAddr, 0, sizeof(peerAddr));peerAddr.sin_family = AF_INET;if (inet_pton(AF_INET, peerIP, &peerAddr.sin_addr) <= 0) {std::cerr << "Client: Invalid peer IP." << std::endl;close(sockfd);return -1;}peerAddr.sin_port = htons(peerPort);std::cout << "Peer address: " << peerIP << ":" << peerPort << std::endl;}else {std::cerr << "Client: Failed to parse peer info." << std::endl;close(sockfd);return -1;}// 9. 在收到 PEER 信息后,客户端可以立即向对方发送“敲门”包,以触发 NAT 映射const char* knockMsg = "knock";sendto(sockfd, knockMsg, strlen(knockMsg), 0,(struct sockaddr*)&peerAddr, sizeof(peerAddr));std::cout << "Sent knock to peer." << std::endl;// 10. 启动保活线程,定时向对方发送 "KEEP_ALIVE" 包,保持 NAT 映射pthread_t kaThread;if (pthread_create(&kaThread, NULL, keep_alive, NULL) != 0) {std::cerr << "Client: pthread_create error: " << strerror(errno) << std::endl;close(sockfd);return -1;}// 11. P2P 交互:从标准输入读取消息,向对方发送,并接收对方回复while (true) {std::cout << "Enter message to send to peer: ";std::string input;std::getline(std::cin, input);if (input.empty()) continue;// 加锁确保发送数据时套接字操作不会与保活线程冲突pthread_mutex_lock(&sock_mutex);sendto(sockfd, input.c_str(), input.length(), 0,(struct sockaddr*)&peerAddr, sizeof(peerAddr));pthread_mutex_unlock(&sock_mutex);// 设置接收超时,等待对方回复memset(buffer, 0, BUF_SIZE);struct timeval tv;tv.tv_sec = 5;tv.tv_usec = 0;setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv));n = recvfrom(sockfd, buffer, BUF_SIZE, 0, NULL, NULL);if (n > 0) {buffer[n] = '\0';std::cout << "Received from peer: " << buffer << std::endl;}}close(sockfd);return 0;
}

  1. 头文件包含与宏定义

    • 包含了进行网络编程、线程同步、字符串处理、错误处理等所需的头文件。
    • 定义服务器端口号、缓冲区大小和保活包的发送间隔。
  2. 全局变量

    • peerAddr:用于保存对方客户端的公网映射地址。
    • sockfd:全局 UDP 套接字,用于所有数据传输。
    • sock_mutex:互斥锁,确保多线程(保活线程与主线程)对同一套接字的操作不会冲突。
  3. keep_alive() 保活线程

    • 循环内使用互斥锁保护对 sockfd 的 sendto 操作,向 peerAddr 发送 “KEEP_ALIVE” 消息,间隔 25 秒一次,帮助保持 NAT 映射。
  4. main() 函数开始

    • 检查命令行参数,要求传入服务器 IP 和本地绑定的网络接口(例如 “eth0”)。
  5. 创建 UDP 套接字

    • 使用 socket() 创建一个 UDP 套接字,并检查是否成功。
  6. 绑定网络接口

    • 通过 setsockopt() 使用 SO_BINDTODEVICE 将套接字绑定到指定网络接口,确保数据包走正确的网络。
  7. 绑定本地地址

    • 绑定本地地址,让系统自动分配端口(使用端口 0)。
  8. 设置服务器地址

    • 构造服务器地址结构体,转换服务器 IP 字符串为网络格式,并设置服务器端口。
  9. 向服务器发送注册消息 “HELLO”

    • 使用 sendto() 发送 “HELLO” 消息给服务器,通知服务器本客户端注册。
  10. 接收服务器回复

    • 调用 recvfrom() 接收服务器的回应。
    • 第一次可能收到 ACK,如果收到 ACK,则继续等待服务器发送包含对方地址的 PEER 信息。
  11. 解析 PEER 信息

    • 使用 sscanf() 解析服务器返回的字符串,提取出对方的公网 IP 和映射端口,并存入 peerAddr 结构体中。
  12. 主动向对方发送“敲门”包

    • 收到对方地址后,立即使用 sendto() 向该地址发送 “knock” 包,以触发 NAT 映射的建立。
  13. 启动保活线程

    • 创建一个新线程运行 keep_alive(),定时向对方发送保活包,防止 NAT 映射超时关闭。
  14. 进入 P2P 交互循环

    • 循环中提示用户输入消息,然后加锁通过 sendto() 发送给对方。
    • 设置接收超时,使用 recvfrom() 尝试接收对方的回复,并打印收到的数据。
  15. 关闭套接字并结束程序

    • 循环退出后关闭套接字。

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

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

相关文章

C++ Primer 动态数组

欢迎阅读我的 【CPrimer】专栏 专栏简介&#xff1a;本专栏主要面向C初学者&#xff0c;解释C的一些基本概念和基础语言特性&#xff0c;涉及C标准库的用法&#xff0c;面向对象特性&#xff0c;泛型特性高级用法。通过使用标准库中定义的抽象设施&#xff0c;使你更加适应高级…

【Qt】ffmpeg照片提取、视频播放▲

目录 一、图像的成像原理&#xff1a; RGB成像原理&#xff1a; YUV成像原理&#xff1a; 二、多线程 三、ffmpeg解码&#xff08;照片提取&#xff09; 1.准备工作 &#xff08;1&#xff09;在工程文件夹里面新建三个文件夹 &#xff08;2&#xff09;在main函数中加…

【QGIS二次开发】地图显示与交互-01

1. 系统界面设计 设计的系统界面如下&#xff0c;很好还原了QGIS、ArcGIS等软件的系统界面&#xff0c;充分利用了QT中顶部工具栏、菜单栏、底部状态栏&#xff0c;实现了图层管理器、鹰眼图、工具箱三个工具面板。 菜单栏、工具栏、工具箱集成了系统中实现的全部功能&#x…

Skynet入门(一)

概念 skynet 是一个为网络游戏服务器设计的轻量框架。但它本身并没有任何为网络游戏业务而特别设计的部分&#xff0c;所以尽可以把它用于其它领域。 设计初衷 如何充分利用它们并行运作数千个相互独立的业务。 模块设计建议 在 skynet 中&#xff0c;用服务 (service) 这…

threejs:用着色器给模型添加光带扫描效果

第一步&#xff1a;给模型添加光带 首先创建一个立方体&#xff0c;不进行任何缩放平移操作&#xff0c;也不要set position。 基础代码如下&#xff1a; 在顶点着色器代码里varying vec3 vPosition;vPosition position;获得threejs自动计算的顶点坐标插值&#xff08;也就…

Spring Boot如何利用Twilio Verify 发送验证码短信?

Twilio提供了一个名为 Twilio Verify 的服务&#xff0c;专门用于处理验证码的发送和验证。这是一个更为简化和安全的解决方案&#xff0c;适合需要用户身份验证的应用。 使用Twilio Verify服务的步骤 以下是如何在Spring Boot中集成Twilio Verify服务的步骤&#xff1a; 1.…

【Linux操作系统】VM17虚拟机安装Ubuntu22.04,图文详细记录

1.双击桌面的 VMware Workstation17 Player&#xff0c;点击“创建新虚拟机”&#xff0c;如下图所示。 2.选择“稍后安装操作系统”&#xff0c;点击“下一步”。如下图所示。 3.客户机操作系统选择“Linux”&#xff0c;版本选择“ Ubuntu 64位”&#xff0c;然后点击“下一…

Linux--基础命令3

大家好&#xff0c;今天我们继续学习Linux的基础命令 mv命令 mv命令是move的缩写&#xff0c;可以用来移动文件或者将文件改名 move(rename) files&#xff0c;经常⽤来备份⽂件或者目录 语法: mv [ 选项 ] 源⽂件或目录 目标⽂件或目录 mv src[文件、目录] dst[路径、文…

【实战 ES】实战 Elasticsearch:快速上手与深度实践-2.2.3案例:电商订单日志每秒10万条写入优化

&#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 文章大纲 Elasticsearch批量写入性能调优实战&#xff1a;2.2.3 案例&#xff1a;电商订单日志每秒10万条写入优化1. 原始架构与瓶颈分析1.1 初始集群配置1.2 性能瓶颈定位 2. 全链路…

统计Excel列中某值出现的次数

统计Excel列中某值出现的次数&#xff1a; 1、COUNTIF 函数用于计算满足特定条件的单元格数量。假设要统计 A 列中值为 “苹果” 出现的次数&#xff0c;在其他单元格中输入公式&#xff1a;COUNTIF(A:A,“苹果”)。其中&#xff0c;A:A表示要统计的范围是 A 列&#xff0c;&q…

Compose Multiplatform+Kotlin Multiplatfrom 第四弹跨平台

文章目录 引言功能效果开发准备依赖使用gradle依赖库MVIFlow设计富文本显示 总结 引言 Compose Multiplatformkotlin Multiplatfrom 今天已经到compose v1.7.3&#xff0c;从界面UI框架上实战开发看&#xff0c;很多api都去掉实验性注解&#xff0c;表示稳定使用了&#xff01;…

(十一)基于vue3+mapbox-GL实现模拟高德实时导航轨迹播放

要在 Vue 3 项目中结合 Mapbox GL 实现类似高德地图的实时导航轨迹功能,您可以按照以下步骤进行: 安装依赖: 首先,安装 mapbox-gl 和 @turf/turf 这两个必要的库: npm install mapbox-gl @turf/turf引入 Mapbox GL: 在组件中引入 mapbox-gl 并初始化地图实例: <templ…

智慧园区大数据云平台建设总体方案,平台方案架构-智慧园区大数据平台(320页原件Word)

第一章 项目建设背景及现状 1.1. 项目建设背景 1.2. 项目建设必要性 1.3. 项目建设目标 1.4. 建设原则 第二章 园区创新发展趋势 2.1园区经济向生态型转变 2.2 园区企业向高新型转变 2.3园区管理向城市化转变 第三章 工业园区大数据存在的问题 3.1信息化配套设施及服…

NeurIPS24 Oral!多模态融合+目标检测全新里程碑!

最近发现多模态融合目标检测实在太热了&#xff01;顶会频出&#xff01;像是NeurIPS24 Oral上端到端算法E2E-MFD&#xff1b;ECCV24上性能提升30.8&#xff05;的FRN&#xff1b;TPAMI24上推理效率狂飙270&#xff05;倍的FSF…… 主要在于&#xff1a;一方面&#xff0c;其能…

EasyDSS视频推拉流/直播点播平台:Mysql数据库接口报错502处理方法

视频推拉流/视频直播点播EasyDSS互联网直播平台支持一站式的上传、转码、直播、回放、嵌入、分享功能&#xff0c;具有多屏播放、自由组合、接口丰富等特点。平台可以为用户提供专业、稳定的直播推流、转码、分发和播放服务&#xff0c;全面满足超低延迟、超高画质、超大并发访…

C语言--简单排序算法(冒泡、选择、插入)

实现三种简单的排序算法 文章目录 冒泡排序改进改进2 选择排序插入排序执行结果 冒泡排序 每次外层循环&#xff0c;排出一个最大值 void bubbleSort(int arr[], int len) {for (int i 0; i < len - 1; i) {for (int j 0; j < len - i - 1; j) {if (arr[j] > arr[…

【STM32项目实战系列】基于STM32G474的FDCAN驱动配置

前言&#xff1a;本周工作中用到了CANFD的驱动&#xff0c;由于以前都是用到的CAN2.0&#xff0c;所以过程并不是特别的顺利&#xff0c;所以中间遇到几个比较小的问题导致自己卡住了一段时间&#xff0c;特此记录一下并完全奉上自己的配置的源码。 1&#xff0c;CANFD配置与简…

解决git clone下载慢或者超时问题

在网上找了很多办法&#xff0c;直接最简单的使用镜像网站下载。 国内可用的镜像网站有&#xff1a; https://github.com.cnpmjs.org # 服务器位于香港https://gitclone.com # 服务器位于杭州https://doc.fastgit.org # 服务器位于香港 例如&#xff1a;将 git clone https:…

nginx+keepalived负载均衡及高可用

1 项目背景 keepalived除了能够管理LVS软件外&#xff0c;还可以作为其他服务的高可用解决方案软件。采用nginxkeepalived&#xff0c;它是一个高性能的服务器高可用或者热备解决方案&#xff0c;Keepalived主要来防止服务器单点故障的发生问题&#xff0c;可以通过其与Nginx的…