【Linux】【网络】UDP打洞-->不同子网下的客户端和服务器通信(未成功版)
【Linux】【网络】UDP打洞–>不同子网下的客户端和服务器通信(未成功版)
上次说基于UDP的打洞程序改了五版一直没有成功,要写一下问题所在,但是我后续又查询了一些资料,成功实现了,这次先写一下未成功的逻辑,我认为未成功的排查错误的部分也很重要,如果想直接看成功的可以直接看我的下一篇文章。
首先 基于上篇文章的UDP打洞逻辑
这里直接将图贴出来:

我的逻辑思路:(ps:会把代码贴到最后面。)
逻辑梳理
1 服务器端(server.c)
- 监听与接收注册
- 服务器创建 UDP 套接字并绑定到固定端口(5050)。
- 依次调用 recvfrom() 接收两个客户端(先后为 C1 和 C2)的注册消息,获取各自的源地址(即 NAT 映射后的公网 IP 和端口)。
- 地址交换
- 服务器把 C2 的公网地址(IP 和端口)格式化成字符串(用“^”分隔)发送给 C1。
- 同样把 C1 的地址发送给 C2。
- 后续处理
- 服务器完成地址交换后退出(没有额外发送探测包)。
客户端 C1(UDPClientcc1.c)
- 两个套接字
- 使用一个套接字(sockS)与服务器通信,另一个(sockC)用于后续对等通信,并绑定到固定端口(6003)。
- 注册阶段
- C1 向服务器发送注册消息(“I am C1”)。
- 接收服务器返回的字符串,解析出对方地址信息(格式 “ip^port”),存入 oppositeSideAddr。
- P2P 交互循环
- 在循环中,每隔 500ms 使用 sockC 向 oppositeSideAddr 发送数据(keep-alive/消息),并尝试接收对方回复。
客户端 C2(UDPClientcc2.c)
- 逻辑与 C1 类似
- 使用两个套接字,一个与服务器通信(sockS),一个用于 P2P(sockC),绑定固定端口(6002)。
- 向服务器发送注册消息(“I am C2”),接收并解析服务器返回的对方地址信息,存入 oppositeSideAddr。
- 进入循环,每隔 500ms 向 oppositeSideAddr 发送数据,并等待回复。
执行结果
服务器:

客户端c1:

客户端c2:

可以看到c1,c2 一直在向从服务器获取的公网ip和端口发送数据 但是一直未收到对端回复。
服务器在向双方发送数据后就直接退出了。
排查问题:
考虑可能存在的问题并逐步排查:
- 服务器配置问题
- 确保服务器S正确交换了双方的公网IP和端口信息,并且客户端解析无误。
- 防火墙设置
- 检查云服务器、客户端以及NAT设备的防火墙是否允许UDP流量通过,特别是目标端口是否开放。
- 也需要确保双方的UDP打洞程序所在主机允许接收来自对端的UDP数据包。
- NAT映射问题
- 可能两端的NAT设备类型不支持直接UDP打洞,或映射策略比较严格(例如对称NAT)。
- 您可以检查客户端所在网络的NAT类型,尝试在不同网络环境下测试。
- 端口绑定和映射问题
- 确认代码中绑定的本地端口(6003、6002)与NAT映射结果是否符合预期。
- 有些NAT设备可能会复用端口或调整外部映射,导致双方看到相同的公网端口,从而影响打洞效果。
- 代码逻辑问题
- 您的代码中目前只是不断发送数据包,但并未实现对收到数据包进行有效处理。如果对端也没有收到数据包,可能是由于发送方向NAT设备发送的数据包没有成功映射到对端。
1 服务器是否正确交换了双方的ip和端口
这个测试结果是我第四版的结果在里面已经打印出来对应的ip,端口我这边对比了并未出现问题 你们可以再看看上面的图片
结论:正常
2防火墙设置
本地防火墙: 检查客户端和服务器上的防火墙状态(使用 ufw status、iptables -L 等命令),确认UDP目标端口是否被允许。
云防火墙: 登录云服务器控制台或路由器管理界面,检查是否设置了安全组或防火墙规则,确保允许相应的UDP流量(包括注册端口和通信端口)。
2.1 本地防火墙
本地防火墙未打开

2.2 云服务器
防火墙对应端口已开启

结论:正常
3 抓包查看数据包是否发送出去
在Ubuntu下,使用抓包工具来监控和分析网络数据包的流向,常用的工具包括 tcpdump(命令行)和 Wireshark(图形界面)。
3.1. 使用 tcpdump
安装:
sudo apt-get update
sudo apt-get install tcpdump
基本用法:
-
抓取所有数据包:
sudo tcpdump -i eth0其中
eth0是您要监控的网络接口,可以通过命令ifconfig查看接口名称。
我的就是ens33

-
过滤特定协议和端口:
例如,抓取UDP数据包:
sudo tcpdump -i ens33 udp抓取目的端口为5050的UDP数据包:
sudo tcpdump -i ens33 udp port 5050

抓包发现数据发送出去了
3 NAT映射问题
使用 stun 工具
1. 安装 stun 客户端:
在 Ubuntu系统上运行:
sudo apt update
sudo apt install stun-client -y
2. 运行 STUN 客户端测试 NAT 类型
stun stun.l.google.com
或者:
stun stun.sipgate.net
这是我的结果

-
Independent Mapping(独立映射):
每个内部端口的映射是独立的,即无论目标地址如何变化,都保持相同的映射。对 UDP 打洞来说,这通常是有利的。 -
Independent Filter(独立过滤):
外部数据包只要符合映射的端口,就会被放行,与发送目标无关。这意味着只要内网设备先发起通信,外部的回复通常能通过 NAT 设备到达内网。 -
Random Port(随机端口):
每个新连接可能会被 NAT 分配一个随机的外部端口,这可能会导致端口映射不固定。为了保持连接,客户端需要持续发送数据包以维持映射。 -
No Hairpin:
表示 NAT 不支持内部设备通过公网地址直接访问同一 NAT 内的其他设备(NAT 回环)。这通常对 UDP 打洞影响不大,因为 C1 和 C2 是处于不同 NAT 或在不同网络下。 -
Return value is 0x000012:
表示 STUN 客户端检测成功,但没有显示映射的端口详细信息,通常这意味着端口由 NAT 设备随机分配。
这个结果说明 NAT 环境是相对有利于 UDP 打洞的(非对称 NAT),但由于随机端口的特性,客户端必须持续发送保持 UDP 映射(Keep-Alive)。
3. NAT 类型
- Full Cone NAT(全锥形 NAT) ✅ UDP 打洞最容易成功
- Restricted Cone NAT(受限锥形 NAT) ✅ 需要双向数据包打洞
- Port-Restricted Cone NAT(端口受限锥形 NAT) ⚠ 可能无法直接打洞
- Symmetric NAT(对称 NAT) ❌ UDP 打洞几乎不可能成功
证明NAT映射支持打洞
最后怀疑问题出在代码逻辑上,服务器返回的端口虽然正确,但 NAT设备 在一段时间后修改了端口映射,或者端口映射被丢弃,导致 C1 发送到错误端口。因此后续需要修改端口。
server.c
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>#define DEFAULT_PORT 5050
#define BUFFER_SIZE 100int main() {// server即外网服务器int serverPort = DEFAULT_PORT;int serverListen;struct sockaddr_in serverAddr;// 建立监听socketserverListen = socket(AF_INET, SOCK_DGRAM, 0);if (serverListen == -1) {perror("socket() failed");return -1;}serverAddr.sin_family = AF_INET;serverAddr.sin_port = htons(serverPort);serverAddr.sin_addr.s_addr = INADDR_ANY;if (bind(serverListen, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) == -1) {perror("bind() failed");return -1;}// 接收来自客户端的连接,source1即先连接到S的客户端C1struct sockaddr_in sourceAddr1;socklen_t sourceAddrLen1 = sizeof(sourceAddr1);char bufRecv1[BUFFER_SIZE];int len;len = recvfrom(serverListen, bufRecv1, sizeof(bufRecv1), 0, (struct sockaddr *)&sourceAddr1, &sourceAddrLen1);if (len == -1) {perror("recvfrom() failed");return -1;}bufRecv1[len] = '\0';printf("C1 IP:[%s],PORT:[%d]\n", inet_ntoa(sourceAddr1.sin_addr), ntohs(sourceAddr1.sin_port));// 接收来自客户端的连接,source2即后连接到S的客户端C2struct sockaddr_in sourceAddr2;socklen_t sourceAddrLen2 = sizeof(sourceAddr2);char bufRecv2[BUFFER_SIZE];len = recvfrom(serverListen, bufRecv2, sizeof(bufRecv2), 0, (struct sockaddr *)&sourceAddr2, &sourceAddrLen2);if (len == -1) {perror("recvfrom() failed");return -1;}bufRecv2[len] = '\0';printf("C2 IP:[%s],PORT:[%d]\n", inet_ntoa(sourceAddr2.sin_addr), ntohs(sourceAddr2.sin_port));// 向C1发送C2的外网ip和portchar bufSend1[BUFFER_SIZE];// bufSend1中存储C2的外网ip和portmemset(bufSend1, '\0', sizeof(bufSend1));char *ip2 = inet_ntoa(sourceAddr2.sin_addr);// C2的ipchar port2[10];// C2的portsnprintf(port2, sizeof(port2), "%d", ntohs(sourceAddr2.sin_port));snprintf(bufSend1, sizeof(bufSend1), "%s^%s", ip2, port2);len = sendto(serverListen, bufSend1, strlen(bufSend1), 0, (struct sockaddr *)&sourceAddr1, sourceAddrLen1);if (len == -1) {perror("sendto() failed");return -1;} else {printf("send() byte:%d\n", len);}// 向C2发送C1的外网ip和portchar bufSend2[BUFFER_SIZE];// bufSend2中存储C1的外网ip和portmemset(bufSend2, '\0', sizeof(bufSend2));char *ip1 = inet_ntoa(sourceAddr1.sin_addr);// C1的ipchar port1[10];// C1的portsnprintf(port1, sizeof(port1), "%d", ntohs(sourceAddr1.sin_port));snprintf(bufSend2, sizeof(bufSend2), "%s^%s", ip1, port1);len = sendto(serverListen, bufSend2, strlen(bufSend2), 0, (struct sockaddr *)&sourceAddr2, sourceAddrLen2);if (len == -1) {perror("sendto() failed");return -1;} else {printf("send() byte:%d\n", len);}// server的中间人工作已完成,退出即可,剩下的交给C1与C2相互通信close(serverListen);return 0;
}
client1.c
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>#define PORT 6003
#define BUFFER_SIZE 100int main(int argc, char* argv[]) {struct sockaddr_in serverAddr;struct sockaddr_in thisAddr;thisAddr.sin_family = AF_INET;thisAddr.sin_port = htons(PORT);thisAddr.sin_addr.s_addr = INADDR_ANY;if (argc < 3) {printf("Usage: UDPClient1 <Server IP address> <Server Port>\n");return -1;}int sockS = socket(AF_INET, SOCK_DGRAM, 0);if (sockS == -1) {perror("socket() failed");return -1;}if (bind(sockS, (struct sockaddr *)&thisAddr, sizeof(thisAddr)) == -1) {perror("bind() failed");return -1;}int sockC = socket(AF_INET, SOCK_DGRAM, 0);if (sockC == -1) {perror("socket() failed");return -1;}// 允许端口复用int optval = 1;setsockopt(sockC, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));// 绑定固定端口 6003struct sockaddr_in bindAddr;bindAddr.sin_family = AF_INET;bindAddr.sin_port = htons(6003);bindAddr.sin_addr.s_addr = INADDR_ANY;bind(sockC, (struct sockaddr *)&bindAddr, sizeof(bindAddr));char bufSend[] = "I am C1";char bufRecv[BUFFER_SIZE];memset(bufRecv, '\0', sizeof(bufRecv));struct sockaddr_in sourceAddr;socklen_t sourceAddrLen = sizeof(sourceAddr);struct sockaddr_in oppositeSideAddr;int len;serverAddr.sin_family = AF_INET;serverAddr.sin_port = htons(atoi(argv[2]));serverAddr.sin_addr.s_addr = inet_addr(argv[1]);len = sendto(sockS, bufSend, sizeof(bufSend), 0, (struct sockaddr *)&serverAddr, sizeof(serverAddr));if (len == -1) {perror("sendto() to S failed");return -1;}printf("C1 sent registration packet to server S.\n");len = recvfrom(sockS, bufRecv, sizeof(bufRecv), 0, (struct sockaddr *)&sourceAddr, &sourceAddrLen);if (len == -1) {perror("recvfrom() from S failed");return -1;}bufRecv[len] = '\0';printf("C1 received from S: %s\n", bufRecv);close(sockS);char ip[20];char port[10];int i = 0;while (i < strlen(bufRecv) && bufRecv[i] != '^') {ip[i] = bufRecv[i];i++;}ip[i] = '\0';int j = 0;i++;while (i < strlen(bufRecv)) {port[j++] = bufRecv[i++];}port[j] = '\0';oppositeSideAddr.sin_family = AF_INET;oppositeSideAddr.sin_port = htons(atoi(port));oppositeSideAddr.sin_addr.s_addr = inet_addr(ip);int flags = fcntl(sockC, F_GETFL, 0);fcntl(sockC, F_SETFL, flags | O_NONBLOCK);printf("C1 will now try to communicate directly with C2 at %s:%s\n", ip, port);int attempts = 0;while (1) {usleep(500000); // 500ms 发送一次len = sendto(sockC, bufSend, sizeof(bufSend), 0, (struct sockaddr *)&oppositeSideAddr, sizeof(oppositeSideAddr));if (len == -1) {perror("sendto() to C2 failed");} else {printf("Sent keep-alive UDP packet to %s:%d\n", inet_ntoa(oppositeSideAddr.sin_addr), ntohs(oppositeSideAddr.sin_port));}len = recvfrom(sockC, bufRecv, sizeof(bufRecv), 0, (struct sockaddr *)&sourceAddr, &sourceAddrLen);if (len == -1) {if (errno == EAGAIN || errno == EWOULDBLOCK) {attempts++;if (attempts % 10 == 0) {printf("No response from C2 after 5 seconds. Retrying...\n");}continue;} else {perror("recvfrom() failed");break;}} else {bufRecv[len] = '\0';printf("C1 received from C2 [%s:%d]: %s\n", inet_ntoa(sourceAddr.sin_addr), ntohs(sourceAddr.sin_port), bufRecv);attempts = 0; // 成功收到数据,重置重试计数}}close(sockC);return 0;
}
client2.c
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>#define PORT 6002
#define BUFFER_SIZE 100int main(int argc, char* argv[]) {struct sockaddr_in serverAddr;struct sockaddr_in thisAddr;thisAddr.sin_family = AF_INET;thisAddr.sin_port = htons(PORT);thisAddr.sin_addr.s_addr = INADDR_ANY;if (argc < 3) {printf("Usage: UDPClient2 <Server IP address> <Server Port>\n");return -1;}int sockS = socket(AF_INET, SOCK_DGRAM, 0);if (sockS == -1) {perror("socket() failed");return -1;}if (bind(sockS, (struct sockaddr *)&thisAddr, sizeof(thisAddr)) == -1) {perror("bind() failed");return -1;}int sockC = socket(AF_INET, SOCK_DGRAM, 0);if (sockC == -1) {perror("socket() failed");return -1;}// 允许端口复用int optval = 1;setsockopt(sockC, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));// 绑定固定端口 6002struct sockaddr_in bindAddr;bindAddr.sin_family = AF_INET;bindAddr.sin_port = htons(6002);bindAddr.sin_addr.s_addr = INADDR_ANY;bind(sockC, (struct sockaddr *)&bindAddr, sizeof(bindAddr));char bufSend[] = "I am C2";char bufRecv[BUFFER_SIZE];memset(bufRecv, '\0', sizeof(bufRecv));struct sockaddr_in sourceAddr;socklen_t sourceAddrLen = sizeof(sourceAddr);struct sockaddr_in oppositeSideAddr;int len;serverAddr.sin_family = AF_INET;serverAddr.sin_port = htons(atoi(argv[2]));serverAddr.sin_addr.s_addr = inet_addr(argv[1]);len = sendto(sockS, bufSend, sizeof(bufSend), 0, (struct sockaddr *)&serverAddr, sizeof(serverAddr));if (len == -1) {perror("sendto() to S failed");return -1;}printf("C2 sent registration packet to server S.\n");len = recvfrom(sockS, bufRecv, sizeof(bufRecv), 0, (struct sockaddr *)&sourceAddr, &sourceAddrLen);if (len == -1) {perror("recvfrom() from S failed");return -1;}bufRecv[len] = '\0';printf("C2 received from S: %s\n", bufRecv);close(sockS);char ip[20];char port[10];int i = 0;while (i < strlen(bufRecv) && bufRecv[i] != '^') {ip[i] = bufRecv[i];i++;}ip[i] = '\0';int j = 0;i++;while (i < strlen(bufRecv)) {port[j++] = bufRecv[i++];}port[j] = '\0';oppositeSideAddr.sin_family = AF_INET;oppositeSideAddr.sin_port = htons(atoi(port));oppositeSideAddr.sin_addr.s_addr = inet_addr(ip);int flags = fcntl(sockC, F_GETFL, 0);fcntl(sockC, F_SETFL, flags | O_NONBLOCK);printf("C2 will now try to communicate directly with C1 at %s:%s\n", ip, port);int attempts = 0;while (1) {usleep(500000); // 500ms 发送一次len = sendto(sockC, bufSend, sizeof(bufSend), 0, (struct sockaddr *)&oppositeSideAddr, sizeof(oppositeSideAddr));if (len == -1) {perror("sendto() to C1 failed");} else {printf("Sent keep-alive UDP packet to %s:%d\n", inet_ntoa(oppositeSideAddr.sin_addr), ntohs(oppositeSideAddr.sin_port));}len = recvfrom(sockC, bufRecv, sizeof(bufRecv), 0, (struct sockaddr *)&sourceAddr, &sourceAddrLen);if (len == -1) {if (errno == EAGAIN || errno == EWOULDBLOCK) {attempts++;if (attempts % 10 == 0) {printf("No response from C1 after 5 seconds. Retrying...\n");}continue;} else {perror("recvfrom() failed");break;}} else {bufRecv[len] = '\0';printf("C2 received from C1 [%s:%d]: %s\n", inet_ntoa(sourceAddr.sin_addr), ntohs(sourceAddr.sin_port), bufRecv);attempts = 0; // 成功收到数据,重置重试计数}}close(sockC);return 0;
}
相关文章:
【Linux】【网络】UDP打洞-->不同子网下的客户端和服务器通信(未成功版)
【Linux】【网络】UDP打洞–>不同子网下的客户端和服务器通信(未成功版) 上次说基于UDP的打洞程序改了五版一直没有成功,要写一下问题所在,但是我后续又查询了一些资料,成功实现了,这次先写一下未成功的…...
【微信小程序】每日心情笔记
个人团队的比赛项目,仅供学习交流使用 一、项目基本介绍 1. 项目简介 一款基于微信小程序的轻量化笔记工具,旨在帮助用户通过记录每日心情和事件,更好地管理情绪和生活。用户可以根据日期和心情分类(如开心、平静、难过等&#…...
PMP项目管理—沟通管理篇—3.监督沟通
文章目录 基本信息4W1HITTO输入工具与技术输出 工作绩效信息和变更请求 基本信息 4W1H what: 确保满足项目及其相关方的信息需求的过程。why: 通过监督沟通过程,来确定规划的沟通工作和沟通活动是否如预期,提高或保持了相关方对项目可交付成果与预计结…...
在Linux中开发OpenGL——检查开发环境对OpenGL ES的支持
由于移动端GPU规模有限,厂商并没有实现完整的OpenGL特性,而是实现了它的子集——OpenGL ES。因此如果需要开发的程序要支持移动端平台,最好使用OpenGL ES开发。 1、 下载支持库、OpenGL ES Demo 1.1、下载PowerVRSDK支持库作为准备ÿ…...
低空经济-飞行数据平台 搭建可行方案
搭建一个飞行数据平台是低空经济中至关重要的一环,它能够实现对飞行器的实时监控、数据分析、路径优化以及安全管理。以下是搭建飞行数据平台的详细步骤和技术方案: 一、平台的核心功能 实时监控: 实时获取飞行器的位置、速度、高度、电池状态等数据。提供可视化界面,展示飞…...
python量化交易——金融数据管理最佳实践——使用qteasy大批量自动拉取金融数据
文章目录 使用数据获取渠道自动填充数据QTEASY数据拉取功能数据拉取接口refill_data_source()数据拉取API的功能特性多渠道拉取数据实现下载流量控制实现错误重试日志记录其他功能 qteasy是一个功能全面且易用的量化交易策略框架, Github地址在这里。使用它&#x…...
为AI聊天工具添加一个知识系统 之136 详细设计之77 通用编程语言 之7
问题 Q1492、针对前面您给出的“AI聊天工具知识系统设计文档”,请就您后面所述“智能进化:认知演进路由驱动知识库持续优化”进行更深入的实现讨论 Q1493、感觉不够完整。下面我们针对您前面给出的“知识系统三层架构详述”逐层给出详细地实现方案。 …...
【CSRF实践】DVWA靶场之CSRF实践
CSRF介绍 CSRF(Cross-site request forgery),中文名叫做“跨站请求伪造”,也被称作“one click attack/session riding”,缩写为“CSRF/XSRF”。在场景中,攻击者会伪造一个请求(通常是一个链接)࿰…...
数据库设计方面如何进行PostgreSQL 17的性能调优?
在数据库设计方面,PostgreSQL 17 的性能调优可以从以下几个方面入手: 表结构设计 选择合适的数据类型:根据数据的实际范围和业务需求,选择占用空间小、查询效率高的数据类型。对于固定长度的字符串,如性别字段&#…...
[场景题]如何实现购物车
1. 基于Session的购物车(适合小型单体应用) 核心思路:将购物车数据存储在用户会话(Session)中,适用于无需持久化的临时购物车。 实现步骤: 数据结构:使用Map<商品ID, 商品数量&g…...
Rust 并发编程:Futures、Tasks 和 Threads 的结合使用
一、线程(Threads)与异步(Async)的对比 1.1. 线程的优势与限制 线程是一种广泛使用的并发模型,几乎所有现代操作系统都支持。Rust 的标准库提供了 std::thread API,使得线程编程变得直观。然而࿰…...
常见的网络协议介绍
一、什么是网络协议 指的是通信双方的数据发送和接收顺序,数据的封装规则。 通俗解释:描述双方发送和接收的每个字节是按照什么规则。 二、TCP/IP体系的常用协议 (一)应用层 HTTP:超文本协议;指的是用来传输文本网页的协议&#…...
一文读懂加载地址、链接地址和运行地址
我们在做嵌入式系统开发时,会经常遇到加载地址、链接地址和运行地址的概念,可能会感到很困惑,搞不清它们三者的关系。希望此文能帮助大家彻底理解三者的关系。 一.概念 1.1.加载地址 加载地址,即Load Memory Address(…...
Unity帧同步与状态同步混合架构开发指南
一、技术背景与适用场景 1. 技术定位差异 帧同步(Lockstep):同步操作指令,强调确定性计算,适用于实时性要求高的场景(如MOBA、RTS),但存在反作弊难题16。 状态同步(Sta…...
后路式编程
今天遇到一个问题,反馈的时候,已经提审过了,不能重新出包了。只能依赖Lua热更解决。非常巧的是,C#那边的变量全是Public的,这算是救了一命。想想确实可笑,本来是封装的问题,没有封装的太好。结果…...
Rust语言入门与应用:未来发展趋势解析
一、Rust语言核心优势解析 1.1 内存安全革命 rust复制 // 所有权系统示例 fn main() { let s1 String::from("hello"); // s1获得所有权 let s2 s1; // 所有权转移至s2 // println!("{}", s1); // 编译错误!s1已失效 println!("{}&quo…...
【2025小白版】计算复试/保研机试模板(个人总结非GPT生成)附代码
一、编程语言选择 很多高校在机试中对编程语言都有明确规定,像复旦大学计算机学院就说明可选择 C、C 或 Java 语言答题,还支持 C11(gcc5.4),C14(g5.4),Java (openjdk1.8)…...
android11使用gpio口控制led状态灯
目录 一、简介 二、解决方法 A、底层驱动 B、上层调用 C、验证 一、简介 1、需求:这里是用2个gpio口来控制LED灯,开机时默认亮蓝灯,按开机键,休眠亮红灯,唤醒亮蓝灯。 原理图: 这里由于主板上电阻R63…...
基于Asp.net的高校一卡通管理系统
作者:计算机学姐 开发技术:SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等,“文末源码”。 专栏推荐:前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏:…...
C++蓝桥杯基础篇(七)
片头 嗨~小伙伴们,大家好!今天我们来一起学习蓝桥杯基础篇(七),学习相关字符串的知识,准备好了吗?咱们开始咯! 一、字符与整数的联系——ASCII码 每个常用字符都对应一个-128~127的…...
在软件开发中正确使用MySQL日期时间类型的深度解析
在日常软件开发场景中,时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志,到供应链系统的物流节点时间戳,时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库,其日期时间类型的…...
使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...
循环冗余码校验CRC码 算法步骤+详细实例计算
通信过程:(白话解释) 我们将原始待发送的消息称为 M M M,依据发送接收消息双方约定的生成多项式 G ( x ) G(x) G(x)(意思就是 G ( x ) G(x) G(x) 是已知的)࿰…...
java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别
UnsatisfiedLinkError 在对接硬件设备中,我们会遇到使用 java 调用 dll文件 的情况,此时大概率出现UnsatisfiedLinkError链接错误,原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用,结果 dll 未实现 JNI 协…...
为什么需要建设工程项目管理?工程项目管理有哪些亮点功能?
在建筑行业,项目管理的重要性不言而喻。随着工程规模的扩大、技术复杂度的提升,传统的管理模式已经难以满足现代工程的需求。过去,许多企业依赖手工记录、口头沟通和分散的信息管理,导致效率低下、成本失控、风险频发。例如&#…...
Mac软件卸载指南,简单易懂!
刚和Adobe分手,它却总在Library里给你写"回忆录"?卸载的Final Cut Pro像电子幽灵般阴魂不散?总是会有残留文件,别慌!这份Mac软件卸载指南,将用最硬核的方式教你"数字分手术"࿰…...
Ascend NPU上适配Step-Audio模型
1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统,支持多语言对话(如 中文,英文,日语),语音情感(如 开心,悲伤)&#x…...
【数据分析】R版IntelliGenes用于生物标志物发现的可解释机器学习
禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍流程步骤1. 输入数据2. 特征选择3. 模型训练4. I-Genes 评分计算5. 输出结果 IntelliGenesR 安装包1. 特征选择2. 模型训练和评估3. I-Genes 评分计…...
动态 Web 开发技术入门篇
一、HTTP 协议核心 1.1 HTTP 基础 协议全称 :HyperText Transfer Protocol(超文本传输协议) 默认端口 :HTTP 使用 80 端口,HTTPS 使用 443 端口。 请求方法 : GET :用于获取资源,…...
day36-多路IO复用
一、基本概念 (服务器多客户端模型) 定义:单线程或单进程同时监测若干个文件描述符是否可以执行IO操作的能力 作用:应用程序通常需要处理来自多条事件流中的事件,比如我现在用的电脑,需要同时处理键盘鼠标…...
