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

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

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

上次说基于UDP的打洞程序改了五版一直没有成功,要写一下问题所在,但是我后续又查询了一些资料,成功实现了,这次先写一下未成功的逻辑,我认为未成功的排查错误的部分也很重要,如果想直接看成功的可以直接看我的下一篇文章。

首先 基于上篇文章的UDP打洞逻辑
这里直接将图贴出来:
在这里插入图片描述
我的逻辑思路:(ps:会把代码贴到最后面。)

逻辑梳理

1 服务器端(server.c)

  1. 监听与接收注册
    • 服务器创建 UDP 套接字并绑定到固定端口(5050)。
    • 依次调用 recvfrom() 接收两个客户端(先后为 C1 和 C2)的注册消息,获取各自的源地址(即 NAT 映射后的公网 IP 和端口)。
  2. 地址交换
    • 服务器把 C2 的公网地址(IP 和端口)格式化成字符串(用“^”分隔)发送给 C1。
    • 同样把 C1 的地址发送给 C2。
  3. 后续处理
    • 服务器完成地址交换后退出(没有额外发送探测包)。

客户端 C1(UDPClientcc1.c)

  1. 两个套接字
    • 使用一个套接字(sockS)与服务器通信,另一个(sockC)用于后续对等通信,并绑定到固定端口(6003)。
  2. 注册阶段
    • C1 向服务器发送注册消息(“I am C1”)。
    • 接收服务器返回的字符串,解析出对方地址信息(格式 “ip^port”),存入 oppositeSideAddr。
  3. P2P 交互循环
    • 在循环中,每隔 500ms 使用 sockC 向 oppositeSideAddr 发送数据(keep-alive/消息),并尝试接收对方回复。

客户端 C2(UDPClientcc2.c)

  1. 逻辑与 C1 类似
    • 使用两个套接字,一个与服务器通信(sockS),一个用于 P2P(sockC),绑定固定端口(6002)。
    • 向服务器发送注册消息(“I am C2”),接收并解析服务器返回的对方地址信息,存入 oppositeSideAddr。
    • 进入循环,每隔 500ms 向 oppositeSideAddr 发送数据,并等待回复。

执行结果

服务器:
在这里插入图片描述

客户端c1:
在这里插入图片描述
客户端c2:
在这里插入图片描述

可以看到c1,c2 一直在向从服务器获取的公网ip和端口发送数据 但是一直未收到对端回复。
服务器在向双方发送数据后就直接退出了。

排查问题:

考虑可能存在的问题并逐步排查:

  1. 服务器配置问题
    • 确保服务器S正确交换了双方的公网IP和端口信息,并且客户端解析无误。
  2. 防火墙设置
    • 检查云服务器、客户端以及NAT设备的防火墙是否允许UDP流量通过,特别是目标端口是否开放。
    • 也需要确保双方的UDP打洞程序所在主机允许接收来自对端的UDP数据包。
  3. NAT映射问题
    • 可能两端的NAT设备类型不支持直接UDP打洞,或映射策略比较严格(例如对称NAT)。
    • 您可以检查客户端所在网络的NAT类型,尝试在不同网络环境下测试。
  4. 端口绑定和映射问题
    • 确认代码中绑定的本地端口(6003、6002)与NAT映射结果是否符合预期。
    • 有些NAT设备可能会复用端口或调整外部映射,导致双方看到相同的公网端口,从而影响打洞效果。
  5. 代码逻辑问题
    • 您的代码中目前只是不断发送数据包,但并未实现对收到数据包进行有效处理。如果对端也没有收到数据包,可能是由于发送方向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打洞–>不同子网下的客户端和服务器通信&#xff08;未成功版&#xff09; 上次说基于UDP的打洞程序改了五版一直没有成功&#xff0c;要写一下问题所在&#xff0c;但是我后续又查询了一些资料&#xff0c;成功实现了&#xff0c;这次先写一下未成功的…...

【微信小程序】每日心情笔记

个人团队的比赛项目&#xff0c;仅供学习交流使用 一、项目基本介绍 1. 项目简介 一款基于微信小程序的轻量化笔记工具&#xff0c;旨在帮助用户通过记录每日心情和事件&#xff0c;更好地管理情绪和生活。用户可以根据日期和心情分类&#xff08;如开心、平静、难过等&#…...

PMP项目管理—沟通管理篇—3.监督沟通

文章目录 基本信息4W1HITTO输入工具与技术输出 工作绩效信息和变更请求 基本信息 4W1H what: 确保满足项目及其相关方的信息需求的过程。why: 通过监督沟通过程&#xff0c;来确定规划的沟通工作和沟通活动是否如预期&#xff0c;提高或保持了相关方对项目可交付成果与预计结…...

在Linux中开发OpenGL——检查开发环境对OpenGL ES的支持

由于移动端GPU规模有限&#xff0c;厂商并没有实现完整的OpenGL特性&#xff0c;而是实现了它的子集——OpenGL ES。因此如果需要开发的程序要支持移动端平台&#xff0c;最好使用OpenGL ES开发。 1、 下载支持库、OpenGL ES Demo 1.1、下载PowerVRSDK支持库作为准备&#xff…...

低空经济-飞行数据平台 搭建可行方案

搭建一个飞行数据平台是低空经济中至关重要的一环,它能够实现对飞行器的实时监控、数据分析、路径优化以及安全管理。以下是搭建飞行数据平台的详细步骤和技术方案: 一、平台的核心功能 实时监控: 实时获取飞行器的位置、速度、高度、电池状态等数据。提供可视化界面,展示飞…...

python量化交易——金融数据管理最佳实践——使用qteasy大批量自动拉取金融数据

文章目录 使用数据获取渠道自动填充数据QTEASY数据拉取功能数据拉取接口refill_data_source()数据拉取API的功能特性多渠道拉取数据实现下载流量控制实现错误重试日志记录其他功能 qteasy是一个功能全面且易用的量化交易策略框架&#xff0c; Github地址在这里。使用它&#x…...

为AI聊天工具添加一个知识系统 之136 详细设计之77 通用编程语言 之7

问题 Q1492、针对前面您给出的“AI聊天工具知识系统设计文档”&#xff0c;请就您后面所述“智能进化&#xff1a;认知演进路由驱动知识库持续优化”进行更深入的实现讨论 Q1493、感觉不够完整。下面我们针对您前面给出的“知识系统三层架构详述”逐层给出详细地实现方案。 …...

【CSRF实践】DVWA靶场之CSRF实践

CSRF介绍 CSRF(Cross-site request forgery)&#xff0c;中文名叫做“跨站请求伪造”&#xff0c;也被称作“one click attack/session riding”&#xff0c;缩写为“CSRF/XSRF”。在场景中&#xff0c;攻击者会伪造一个请求&#xff08;通常是一个链接&#xff09;&#xff0…...

数据库设计方面如何进行PostgreSQL 17的性能调优?

在数据库设计方面&#xff0c;PostgreSQL 17 的性能调优可以从以下几个方面入手&#xff1a; 表结构设计 选择合适的数据类型&#xff1a;根据数据的实际范围和业务需求&#xff0c;选择占用空间小、查询效率高的数据类型。对于固定长度的字符串&#xff0c;如性别字段&#…...

[场景题]如何实现购物车

1. 基于Session的购物车&#xff08;适合小型单体应用&#xff09; 核心思路&#xff1a;将购物车数据存储在用户会话&#xff08;Session&#xff09;中&#xff0c;适用于无需持久化的临时购物车。 实现步骤&#xff1a; 数据结构&#xff1a;使用Map<商品ID, 商品数量&g…...

Rust 并发编程:Futures、Tasks 和 Threads 的结合使用

一、线程&#xff08;Threads&#xff09;与异步&#xff08;Async&#xff09;的对比 1.1. 线程的优势与限制 线程是一种广泛使用的并发模型&#xff0c;几乎所有现代操作系统都支持。Rust 的标准库提供了 std::thread API&#xff0c;使得线程编程变得直观。然而&#xff0…...

常见的网络协议介绍

一、什么是网络协议 指的是通信双方的数据发送和接收顺序&#xff0c;数据的封装规则。 通俗解释&#xff1a;描述双方发送和接收的每个字节是按照什么规则。 二、TCP/IP体系的常用协议 (一)应用层 HTTP&#xff1a;超文本协议&#xff1b;指的是用来传输文本网页的协议&#…...

一文读懂加载地址、链接地址和运行地址

我们在做嵌入式系统开发时&#xff0c;会经常遇到加载地址、链接地址和运行地址的概念&#xff0c;可能会感到很困惑&#xff0c;搞不清它们三者的关系。希望此文能帮助大家彻底理解三者的关系。 一.概念 1.1.加载地址 加载地址&#xff0c;即Load Memory Address&#xff08…...

Unity帧同步与状态同步混合架构开发指南

一、技术背景与适用场景 1. 技术定位差异 帧同步&#xff08;Lockstep&#xff09;&#xff1a;同步操作指令&#xff0c;强调确定性计算&#xff0c;适用于实时性要求高的场景&#xff08;如MOBA、RTS&#xff09;&#xff0c;但存在反作弊难题16。 状态同步&#xff08;Sta…...

后路式编程

今天遇到一个问题&#xff0c;反馈的时候&#xff0c;已经提审过了&#xff0c;不能重新出包了。只能依赖Lua热更解决。非常巧的是&#xff0c;C#那边的变量全是Public的&#xff0c;这算是救了一命。想想确实可笑&#xff0c;本来是封装的问题&#xff0c;没有封装的太好。结果…...

Rust语言入门与应用:未来发展趋势解析

一、Rust语言核心优势解析 1.1 内存安全革命 rust复制 // 所有权系统示例 fn main() { let s1 String::from("hello"); // s1获得所有权 let s2 s1; // 所有权转移至s2 // println!("{}", s1); // 编译错误&#xff01;s1已失效 println!("{}&quo…...

【2025小白版】计算复试/保研机试模板(个人总结非GPT生成)附代码

一、编程语言选择 很多高校在机试中对编程语言都有明确规定&#xff0c;像复旦大学计算机学院就说明可选择 C、C 或 Java 语言答题&#xff0c;还支持 C11&#xff08;gcc5.4&#xff09;&#xff0c;C14&#xff08;g5.4&#xff09;&#xff0c;Java (openjdk1.8&#xff09…...

android11使用gpio口控制led状态灯

目录 一、简介 二、解决方法 A、底层驱动 B、上层调用 C、验证 一、简介 1、需求&#xff1a;这里是用2个gpio口来控制LED灯&#xff0c;开机时默认亮蓝灯&#xff0c;按开机键&#xff0c;休眠亮红灯&#xff0c;唤醒亮蓝灯。 原理图&#xff1a; 这里由于主板上电阻R63…...

基于Asp.net的高校一卡通管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…...

C++蓝桥杯基础篇(七)

片头 嗨~小伙伴们&#xff0c;大家好&#xff01;今天我们来一起学习蓝桥杯基础篇&#xff08;七&#xff09;&#xff0c;学习相关字符串的知识&#xff0c;准备好了吗&#xff1f;咱们开始咯&#xff01; 一、字符与整数的联系——ASCII码 每个常用字符都对应一个-128~127的…...

简易版抽奖活动的设计技术方案

1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...

PPT|230页| 制造集团企业供应链端到端的数字化解决方案:从需求到结算的全链路业务闭环构建

制造业采购供应链管理是企业运营的核心环节&#xff0c;供应链协同管理在供应链上下游企业之间建立紧密的合作关系&#xff0c;通过信息共享、资源整合、业务协同等方式&#xff0c;实现供应链的全面管理和优化&#xff0c;提高供应链的效率和透明度&#xff0c;降低供应链的成…...

《Playwright:微软的自动化测试工具详解》

Playwright 简介:声明内容来自网络&#xff0c;将内容拼接整理出来的文档 Playwright 是微软开发的自动化测试工具&#xff0c;支持 Chrome、Firefox、Safari 等主流浏览器&#xff0c;提供多语言 API&#xff08;Python、JavaScript、Java、.NET&#xff09;。它的特点包括&a…...

vscode(仍待补充)

写于2025 6.9 主包将加入vscode这个更权威的圈子 vscode的基本使用 侧边栏 vscode还能连接ssh&#xff1f; debug时使用的launch文件 1.task.json {"tasks": [{"type": "cppbuild","label": "C/C: gcc.exe 生成活动文件"…...

视频字幕质量评估的大规模细粒度基准

大家读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 摘要 视频字幕在文本到视频生成任务中起着至关重要的作用&#xff0c;因为它们的质量直接影响所生成视频的语义连贯性和视觉保真度。尽管大型视觉-语言模型&#xff08;VLMs&#xff09;在字幕生成方面…...

拉力测试cuda pytorch 把 4070显卡拉满

import torch import timedef stress_test_gpu(matrix_size16384, duration300):"""对GPU进行压力测试&#xff0c;通过持续的矩阵乘法来最大化GPU利用率参数:matrix_size: 矩阵维度大小&#xff0c;增大可提高计算复杂度duration: 测试持续时间&#xff08;秒&…...

Linux 内存管理实战精讲:核心原理与面试常考点全解析

Linux 内存管理实战精讲&#xff1a;核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用&#xff0c;还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...

Java毕业设计:WML信息查询与后端信息发布系统开发

JAVAWML信息查询与后端信息发布系统实现 一、系统概述 本系统基于Java和WML(无线标记语言)技术开发&#xff0c;实现了移动设备上的信息查询与后端信息发布功能。系统采用B/S架构&#xff0c;服务器端使用Java Servlet处理请求&#xff0c;数据库采用MySQL存储信息&#xff0…...

音视频——I2S 协议详解

I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议&#xff0c;专门用于在数字音频设备之间传输数字音频数据。它由飞利浦&#xff08;Philips&#xff09;公司开发&#xff0c;以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...

Caliper 配置文件解析:fisco-bcos.json

config.yaml 文件 config.yaml 是 Caliper 的主配置文件,通常包含以下内容: test:name: fisco-bcos-test # 测试名称description: Performance test of FISCO-BCOS # 测试描述workers:type: local # 工作进程类型number: 5 # 工作进程数量monitor:type: - docker- pro…...