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

【Linux】【网络】UDP打洞-->不同子网下的客户端和服务器通信(成功版)

【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. 关闭套接字并结束程序

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

相关文章:

【Linux】【网络】UDP打洞-->不同子网下的客户端和服务器通信(成功版)

【Linux】【网络】UDP打洞–&#xff1e;不同子网下的客户端和服务器通信&#xff08;成功版&#xff09; 根据上个文章的分析 问题可能出现在代码逻辑上面 我这里重新查找资料怀疑&#xff1a; 1 NAT映射可能需要多次数据包的发送才能建立。 2 NAT映射保存时间太短&#xff…...

【微知】如何查看Mellanox网卡上的光模块的信息?(ethtool -m enp1s0f0 看型号、厂商、生产日期等)

背景 服务器上插入的光模块经常被忽略&#xff0c;往往这里是定位问题最根本的地方。如何通过命令查看&#xff1f; 命令 ethtool提供了-m参数&#xff0c;m是module-info的意思&#xff0c;他是从光模块的eeprom中读取数据。&#xff08;应该是用i2c协议读取的&#xff09;…...

图论基础算法: 二分图的判定(C++)

二分图的基本概念 什么是二分图? 二分图(Bipartite Graph)是指一个图的顶点集可以被分割为两个互不相交的子集 U U U 和 V V V, 并且图中的每一条边都连接 U U U 中的一个顶点和 V V V 中的一个顶点. 换句话说, 二分图中的顶点可以被分成两组, 组内的顶点之间没有边相连…...

AI赋能校园安全:科技助力预防与应对校园霸凌

校园本应是学生快乐学习、健康成长的地方&#xff0c;然而&#xff0c;校园霸凌却成为威胁学生身心健康的隐形“毒瘤”。近年来&#xff0c;随着人工智能&#xff08;AI&#xff09;技术的快速发展&#xff0c;AI在校园安全领域的应用逐渐成为解决校园霸凌问题的新突破口。通过…...

PyTorch系列教程:评估和推理模式下模型预测

使用PyTorch时&#xff0c;将模型从训练阶段过渡到推理阶段是至关重要的一步。在推理过程中&#xff0c;该模型用于对以前从未见过的新数据进行预测。这种转换的一个重要方面是使用推理模式&#xff0c;它通过禁用仅在训练期间需要的操作来帮助优化模型的性能。 理解推理模式 …...

Linux注册进程终止处理函数

atexit() 是一个标准库函数&#xff0c;用于注册在进程正常终止时要调用的函数。通过 atexit()&#xff0c;你可以确保在程序结束时自动执行一些清理工作&#xff0c;比如释放资源、保存状态等。 函数原型如下&#xff1a; #include <stdlib.h> int atexit(void (*func…...

Lumerical INTERCONNECT 中的自相位调制 (SPM)

一、自相位调制的数学介绍 A.非线性薛定谔方程&#xff08;NLSE&#xff09;&#xff1a; NLSE 是光学中的一个关键方程。它告诉我们光脉冲在具有非线性和色散特性的介质中的行为方式。该方程如下所示&#xff1a; i ∂A/∂z β2/2 ∂A/∂t γ|A|A 0 其中&#xff1a; - …...

DICOM服务中的C-STORE、 C-FIND、C-MOVE、C-GET、Worklist

DICOM服务说明 DICOM&#xff08;Digital Imaging and Communications in Medicine&#xff09;是一种用于处理、存储、打印和传输医学影像的标准。DICOM定义了多种服务类&#xff0c;其中C-STORE、C-FIND、C-MOVE和C-GET是与影像数据查询和检索相关的四个主要服务类&#xff…...

Python的pdf2image库将PDF文件转换为PNG图片

您可以使用Python的pdf2image库将PDF文件转换为PNG图片。以下是一个完整的示例&#xff0c;包含安装步骤、代码示例和注意事项。 安装依赖库 首先&#xff0c;您需要安装pdf2image库&#xff1a; pip install pdf2imagepdf2image依赖于poppler库来解析PDF文件。 Windows系统…...

在Blender中给SP分纹理组

在Blender中怎么分SP的纹理组/纹理集 其实纹理组就是材质 把同一组的材质分给同一组的模型 导入到sp里面自然就是同一个纹理组 把模型导入SP之后 就自动分好了...

import模块到另一个文件夹报错:ModuleNotFoundError: No module named xxx

1. 问题 打开项目文件夹my_code&#xff0c;将bb.py的函数或者类import到aa.py中&#xff0c;然后运行aa.py文件&#xff0c;可能会报错ModuleNotFoundError: No module named xxx。 ‪E:\Desktop\my_code ├── a │ ├── train.sh │ └── aa.py └── b└── b…...

[SystemVerilog]例化

SystemVerilog 的例化方式和Verilog 类似 如果信号输入输出name一致 abc abc_inst( .a(a)&#xff0c; .b(b), c(c) ); 使用SystemVerilog abc abc_inst( .a, .b, .c ); 或者 abc abc_inst( .* ); 在SystemVerilog中&#xff0c;可以简化例化方式。 可以使用…...

Java方法详解

Java方法详解 方法1.方法的概念(1).什么是方法(2).方法的定义(3).实参与形参的关系 2.方法重载(1).方法重载的概念 3.递归&#xff08;C语言详细讲过&#xff09; 方法 1.方法的概念 (1).什么是方法 方法类似于C语言中的函数&#xff0c;我们重在体会与理解&#xff0c;不必…...

springboot自动插入创建时间和更新时间到数据库

springboot自动插入创建时间和更新时间到数据库 1.添加TableField注解2.添加TimeMetaObjectHandler配置3.测试 1.添加TableField注解 /*** 创建时间*/TableField(fill FieldFill.INSERT) // 插入时生效private LocalDateTime createTime;/*** 修改时间*/TableField(fill Fiel…...

如何将JAR交由Systemctl管理?

AI越来越火了&#xff0c;我们想要不被淘汰就得主动拥抱。推荐一个人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;最重要的屌图甚多&#xff0c;忍不住分享一下给大家。点击跳转到网站 废话不多说&#xff0c;进入正题。下面开始说如何使用 systemctl…...

VMware Workstation Pro安装openKylin 2.0全流程指南

原文链接&#xff1a;VMware Workstation Pro安装openKylin 2.0全流程指南 Hello&#xff0c;大家好啊&#xff01;今天给大家带来一篇在VMware Workstation Pro 上安装 openKylin 2.0 SP1 的文章。openKylin 2.0 作为国产开源桌面操作系统&#xff0c;目前已经发布了最新版本&…...

网络安全检查漏洞内容回复 网络安全的漏洞

网络安全的核心目标是保障业务系统的可持续性和数据的安全性&#xff0c;而这两点的主要威胁来自于蠕虫的暴发、黑客的攻击、拒绝服务攻击、木马。蠕虫、黑客攻击问题都和漏洞紧密联系在一起&#xff0c;一旦有重大安全漏洞出现&#xff0c;整个互联网就会面临一次重大挑战。虽…...

数据仓库的特点

数据仓库的主要特点可以概括为&#xff1a;面向主题、集成性、非易失性、时变性、高性能和可扩展性、支持复杂查询和分析、分层架构以及数据质量管理。 1. 面向主题&#xff08;Subject-Oriented&#xff09; 数据仓库是面向主题的&#xff0c;而不是面向事务的。这意味着数据…...

02_NLP文本预处理之文本张量表示法

文本张量表示法 概念 将文本使用张量进行表示,一般将词汇表示为向量,称为词向量,再由各个词向量按顺序组成矩阵形成文本表示 例如: ["人生", "该", "如何", "起头"]># 每个词对应矩阵中的一个向量 [[1.32, 4,32, 0,32, 5.2],[3…...

青蛙跳杯子(BFS)

#include <iostream> #include <queue> #include <string> #include <unordered_set> using namespace std;int main() {string a, b;cin >> a >> b; int n a.size(); // 字符串长度int d[] {1, -1, -2, 2, -3, 3}; // 跳跃距离queue&…...

【前端基础】1、HTML概述(HTML基本结构)

一、网页组成 HTML&#xff1a;网页的内容CSS&#xff1a;网页的样式JavaScript&#xff1a;网页的功能 二、HTML概述 HTML&#xff1a;全称为超文本标记语言&#xff0c;是一种标记语言。 超文本&#xff1a;文本、声音、图片、视频、表格、链接标记&#xff1a;由许许多多…...

Arm64架构的Linux服务器安装jdk8

一、下载 JDK8 打开浏览器&#xff0c;访问 oracle官网找到适用于自己服务器的 arm64 架构的 JDK8 安装包。 二、安装 JDK8 将下载好的 JDK 压缩包上传到服务器上 解压 JDK 压缩包&#xff1a; tar -zxvf jdk-8uXXX-linux-arm64.tar.gz选择安装目录&#xff0c;我将 JDK 安装…...

深入探索Python机器学习算法:模型调优

深入探索Python机器学习算法&#xff1a;模型调优 文章目录 深入探索Python机器学习算法&#xff1a;模型调优模型调优1. 超参数搜索方法1.1 网格搜索&#xff08;Grid Search&#xff09;1.2 随机搜索&#xff08;Random Search&#xff09;1.3 贝叶斯优化&#xff08;Bayesia…...

【Linux】冯诺依曼体系结构-操作系统

一.冯诺依曼体系结构 我们所使用的计算机&#xff0c;如笔记本等都是按照冯诺依曼来设计的&#xff1a; 截止目前&#xff0c;我们所知道的计算机都是由一个一个的硬件组装起来的&#xff0c;这些硬件又由于功能的不同被分为了输入设备&#xff0c;输出设备&#xff0c;存储器…...

Linux第五讲----gcc与g++,makefile/make

1.代码编译 1.1预处理 我们通过vim编辑完文件之后&#xff0c;想看一下运行结果这时我们便可以试用gcc编译C语言&#xff0c;g编译c. 编译代码&#xff1a; 上述两种方法均可&#xff0c;code.c是我的c语言文件&#xff0c;mycode是我给编译后产生的二进制文件起的名&#x…...

FastGPT 源码:基于 LLM 实现 Rerank (含Prompt)

文章目录 基于 LLM 实现 Rerank函数定义预期输出实现说明使用建议完整 Prompt 基于 LLM 实现 Rerank 下边通过设计 Prompt 让 LLM 实现重排序的功能。 函数定义 class LLMReranker:def __init__(self, llm_client):self.llm llm_clientdef rerank(self, query: str, docume…...

Virtual Box虚拟机安装Mac苹果Monterey和big sur版本实践

虚拟机安装苹果实践&#xff0c;在Windows10系统&#xff0c;安装Virtual Box7.1.6&#xff0c;安装虚拟苹果Monterey版本Monterey (macOS 12) 。碰到的主要问题是安装光盘不像Windows那么容易拿到&#xff0c;而且根据网上很多文章制作的光盘&#xff0c;在viritualBox里都无法…...

【高并发】Java 并行与串行深入解析:性能优化与实战指南

Java 并行与串行深入解析&#xff1a;性能优化与实战指南 在高性能应用开发中&#xff0c;我们常常会面临 串行&#xff08;Serial&#xff09; 和 并行&#xff08;Parallel&#xff09; 的选择。串行执行任务简单直观&#xff0c;但并行能更高效地利用 CPU 资源&#xff0c;…...

软考中级-数据库-3.2 数据结构-数组和矩阵

数组 一维数组是长度固定的线性表&#xff0c;数组中的每个数据元素类型相同。n维数组是定长线性表在维数上的扩张&#xff0c;即线性表中的元素又是一个线性表。 例如一维数组a[5][a1,a2,a3,a4,a5] 二维数组a[2][3]是一个2行2列的数组 第一行[a11,a12,a13] 第二行[a21,a22,a23…...

LeetCode 解题思路 9(Hot 100)

解题思路&#xff1a; 遍历并调整数组&#xff1a; 对于每个元素 nums[i]&#xff0c;若其值为正且不超过数组长度 len&#xff0c;则将其逐步交换到它应该在的位置。查找缺失的正整数&#xff1a; 遍历调整后的数组&#xff0c;若某个位置的值不等于其索引加1&#xff0c;则说…...

交叉编译 perl-5.40.0 perl-cross-1.5.3

1.下载地址&#xff1a; https://www.cpan.org/src/5.0/ https://github.com/arsv/perl-cross/tags2.编译 # 进入源码目录 cd /opt/snmp/perl # 合并perl-cross到Perl源码 cp -R perl-cross-1.5.3/* perl-5.40.0/ cd perl-5.40.0./configure --targetaarch64-poky-linux --p…...

go前后端开源项目go-admin,本地启动

https://github.com/go-admin-team/go-admin 教程 1.拉取项目 git clone https://github.com/go-admin-team/go-admin.git 2.更新整理依赖 go mod tidy会整理依赖&#xff0c;下载缺少的包&#xff0c;移除不用的&#xff0c;并更新go.sum。 # 更新整理依赖 go mod tidy 3.编…...

突破光学成像局限:全视野光学血管造影技术新进展

全视野光学血管造影&#xff08;FFOA&#xff09;作为一种实时、无创的成像技术&#xff0c;能够提取生物血液微循环信息&#xff0c;为深入探究生物组织的功能和病理变化提供关键数据。然而&#xff0c;传统FFOA成像方法受到光学镜头景深&#xff08;DOF&#xff09;的限制&am…...

RefuseManualStart/Stop增强Linux系统安全性?详解systemd单元保护机制

一、引子&#xff1a;一个“手滑”引发的血案 某天凌晨&#xff0c;运维工程师小张在维护生产服务器时&#xff0c;误输入了 systemctl start reboot.target&#xff0c;导致整台服务器瞬间重启&#xff0c;线上服务中断30分钟&#xff0c;直接损失数十万元。事后排查发现&…...

国产编辑器EverEdit - 超级丰富的标签样式设置!

1 设置-高级-标签 1.1 设置说明 选择主菜单工具 -> 设置 -> 常规&#xff0c;在弹出的选项窗口中选择标签分类&#xff0c;如下图所示&#xff1a; 1.1.1 多文档标签样式 默认 平坦 渐变填充 1.1.2 停靠窗格标签样式 默认 平坦 渐变填充 1.1.3 激活Tab的…...

装饰器模式:灵活扩展对象功能的利器

一、从咖啡加料说起&#xff1a;什么是装饰器模式&#xff1f; 假设您走进咖啡馆点单&#xff1a; 基础款&#xff1a;美式咖啡&#xff08;15元&#xff09;加料需求&#xff1a;加牛奶&#xff08;3元&#xff09;、加焦糖&#xff08;5元&#xff09;、加奶油&#xff08;…...

# [Linux] [Anaconda]解决在 WSL Ubuntu 中安装 Anaconda 报错问题

在 Windows 10 中安装了 WSL&#xff08;Windows Subsystem for Linux&#xff09;并使用 Ubuntu 后&#xff0c;你可能会下载 Anaconda 的 Linux 版本进行安装。但在安装过程中&#xff0c;可能会遇到 tar (child): bzip2: Cannot exec: No such file or directory 这样的错误…...

【回溯】216. 组合总和 III

题目 216. 组合总和 III 思路 不知道for有几层时&#xff0c;使用回溯&#xff0c;比上一题多了一个条件&#xff0c;组合需要和为n。 代码 class Solution { private:vector<vector<int>>result;vector<int>path;void backtracking(int target,int k,i…...

AI编程工具-(四)

250304今天用【通义灵码】做了下简单的分析建模工作。不够丝滑&#xff0c;但是在数据预处理方面还是有用。 目录 准备工作一分析工作建模结论 这个数据集是网上随手找的时许指标数据&#xff0c;然后分析时序指标A和B关联关系。 准备工作一 问大模型&#xff0c;这个场景有哪…...

一种事件驱动的设计模式-Reactor 模型

Reactor 模型 是一种事件驱动的设计模式&#xff0c;主要用于处理高并发的 I/O 操作&#xff08;如网络请求、文件读写等&#xff09;。其核心思想是通过事件分发机制&#xff0c;将 I/O 事件的监听和处理解耦&#xff0c;从而高效管理大量并发连接&#xff0c;避免传统多线程模…...

AI-Ollama本地大语言模型运行框架与Ollama javascript接入

1.Ollama Ollama 是一个开源的大型语言模型&#xff08;LLM&#xff09;平台&#xff0c;旨在让用户能够轻松地在本地运行、管理和与大型语言模型进行交互。 Ollama 提供了一个简单的方式来加载和使用各种预训练的语言模型&#xff0c;支持文本生成、翻译、代码编写、问答等多种…...

XPath路径表达式

1. 绝对路径表达式 语法&#xff1a;/根元素/子元素/子子元素... 特点**&#xff1a;**必须从根元素开始&#xff0c;完整地逐层写路径。 示例代码&#xff1a; <!-- XML结构 --> <school> <class id"1"> <student>小明</student> &l…...

大语言模型的逻辑:从“鹦鹉学舌”到“举一反三”

引言 近年来&#xff0c;大语言模型&#xff08;LLM&#xff09;在自然语言处理领域取得了突破性进展&#xff0c;其强大的文本生成和理解能力令人惊叹。然而&#xff0c;随着应用的深入&#xff0c;人们也开始关注LLM的“逻辑”问题&#xff1a;它究竟是机械地模仿人类语言&a…...

从0到1构建AI深度学习视频分析系统--基于YOLO 目标检测的动作序列检查系统:(0)系统设计与工具链说明

文章大纲 系统简介Version 1Version2环境摄像机数据流websocket 发送图像帧RTSP 视频流树莓派windows消息队列参考文献项目地址提示词系统简介 Version 1 Version2 环境 # 配置 conda 源 # 配置conda安装源 conda config --add channels https://mirrors.tuna.tsinghua.edu.c…...

在Linux环境部署SpringBoot项目

在xshell中手动开放8080端口 sudo ufw allow 8080/tcp systemctl reload ufw systemctl restart ufw 配置文件要求 也可以使用maven来分平台 部署到linux服务器上 1.建一个文件夹 2.将jar包拖拽到文件夹中 3.运行nohup java -jar jar包 &的命令启动程序 //后台启动 …...

8. 保存应用数据

一、课程笔记 1.0 引入 针对那些体积小&#xff0c;访问频率高&#xff0c;且对它的速度有一定要求的轻量化数据。例如&#xff0c;用户偏好设置用配置参数等&#xff0c;使用传统的惯性数据库进行存储&#xff0c;不惊险的笨重&#xff0c;还可能引入不必要的性能开销。 此时…...

ADC采集模块与MCU内置ADC性能对比

2.5V基准电压源&#xff1a; 1. 精度更高&#xff0c;误差更小 ADR03B 具有 0.1% 或更小的初始精度&#xff0c;而 电阻分压方式的误差主要来自电阻的容差&#xff08;通常 1% 或 0.5%&#xff09;。长期稳定性更好&#xff0c;分压电阻容易受到温度、老化的影响&#xff0c;长…...

量子算法:英译名、概念、历史、现状与展望?

李升伟 整理 #### 英译名 量子算法的英文为 **Quantum Algorithm**。 #### 概念 量子算法是利用量子力学原理&#xff08;如叠加态、纠缠态和干涉&#xff09;设计的算法&#xff0c;旨在通过量子计算机高效解决经典计算机难以处理的问题。其核心在于利用量子比特&#xff08…...

水仙花数(华为OD)

题目描述 所谓水仙花数&#xff0c;是指一个n位的正整数&#xff0c;其各位数字的n次方和等于该数本身。 例如153是水仙花数&#xff0c;153是一个3位数&#xff0c;并且153 13 53 33。 输入描述 第一行输入一个整数n&#xff0c;表示一个n位的正整数。n在3到7之间&#x…...

基于编程语言的建筑行业施工图设计系统开发可行性研究————从参数化建模到全流程自动化的技术路径分析

基于编程语言的建筑行业施工图设计系统开发可行性研究————从参数化建模到全流程自动化的技术路径分析 文章目录 **基于编程语言的建筑行业施工图设计系统开发可行性研究————从参数化建模到全流程自动化的技术路径分析** 摘要引言一、技术可行性深度剖析1.1 现有编程语言…...