【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;
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/72572.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/72572.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/72572.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【Python編程】高性能Python Web服務部署架構解析

一、FastAPI 與 Uvicorn/Gunicorn 的協同 1. 開發環境&#xff1a;Uvicorn 直接驅動 作用&#xff1a;Uvicorn 作為 ASGI 服務器&#xff0c;原生支持 FastAPI 的異步特性&#xff0c;提供熱重載&#xff08;--reload&#xff09;和高效異步請求處理。 啟動命令&#xff1a; u…

前端權限流程(基于rbac實現思想)

1. 權限控制 1.1. 實現思想 基于rbac權限控制思想實現&#xff0c;給用戶分配角色&#xff0c;給角色分配權限 給用戶分配角色業務 注意&#xff1a;上方圖片是個示例圖&#xff0c;代表給用戶分配職位(角色)&#xff0c;頁面中使用了Element-plus的el- checkbox組件…

軟件高級架構師 - 軟件工程

補充中 測試 測試類型 靜態測試 動態測試 測試階段 單元測試中&#xff0c;包含性能測試&#xff0c;如下&#xff1a; 集成測試中&#xff0c;包含以下&#xff1a; 維護 遺留系統處置 高水平低價值&#xff1a;采取集成 對于這類系統&#xff0c;采取 集成 的方式&…

python3.13安裝教程【2025】python3.13超詳細圖文教程(包含安裝包)

文章目錄 前言一、python3.13安裝包下載二、Python 3.13安裝步驟三、Python3.13驗證 前言 本教程將為你詳細介紹 Python 3.13 python3.13安裝教程&#xff0c;幫助你順利搭建起 Python 3.13 開發環境&#xff0c;快速投身于 Python 編程的精彩實踐中。 一、python3.13安裝包下…

【極客時間】瀏覽器工作原理與實踐-2 宏觀視角下的瀏覽器 (6講) - 2.5 渲染流程(上):HTML、CSS和JavaScript,是如何變成頁面的?

https://time.geekbang.org/column/article/118205 2.5 渲染流程&#xff08;上&#xff09;&#xff1a;HTML、CSS和JavaScript&#xff0c;是如何變成頁面的&#xff1f; 2.4講了導航相關的流程&#xff0c;那導航被提交后又會怎么樣呢&#xff1f; 就進入了渲染階段。 這…

小模型和小數據可以實現AGI嗎

小模型和小數據很難實現真正的 通用人工智能&#xff08;AGI, Artificial General Intelligence&#xff09;&#xff0c;但在特定任務或受限環境下&#xff0c;可以通過高效的算法和優化方法實現“近似 AGI” 的能力。 1. 為什么小模型小數據難以實現 AGI&#xff1f; AGI 需…

Android14 OTA差分包升級報kPayloadTimestampError (51)

由于VF 架構&#xff0c; 所以鏡像的打包時間可能存在偏差&#xff0c; 如 boot.img 和 客制化的一些鏡像打包 可能會在 vendor 側進行打包。 而 與system 側進行merge 時&#xff0c;時間戳比較亂&#xff0c;為了解決這個問題&#xff0c;讓時間戳進行統一。 使用adb方式驗證…

CMake學習筆記(一):工程的新建和如何將源文件生成二進制文件

cmake是我們在工作過程中比較常見的一個工具&#xff0c;該系列文章是自己用來學習的筆記。目前只是記錄下自己學習cmake的過程中的一些重要的知識點&#xff0c;其是以項目需求為導向并非完整的cmake的學習路線和系統&#xff0c;同樣也并非適合所有的人。 1.生成一個可執行文…

重定位(1)

一、重定位 1、對于有強大ROM的板子&#xff0c;他們會將上電后的程序放到指定RAM內存 2、無強大片內ROM的板子&#xff0c;自己編程序讓他知道RAM內存指定位置 指定位置&#xff1a;就是鏈接地址&#xff0c;指定哪里&#xff0c;哪里就被編譯好一塊內存用來存放上電的程序 …

自由學習記錄(41)

代理服務器的核心功能是在客戶端&#xff08;用戶設備&#xff09;和目標服務器&#xff08;網站/資源服務器&#xff09;之間充當“中介”&#xff0c;具體過程如下&#xff1a; 代理服務器的工作流程 當客戶端希望訪問某個網站&#xff08;比如 example.com&#xff09;時&…

Jadx Gui 的詳細介紹、安裝指南、使用方法及配置說明

Jadx Gui&#xff1a;安卓應用逆向分析神器 一、Jadx Gui 簡介 Jadx 是一款開源的 Android 反編譯工具&#xff0c;支持將 .apk、.aab、.dex 等文件反編譯為可讀的 Java/Kotlin 源代碼和資源文件&#xff08;如 XML、PNG&#xff09;。其特點包括&#xff1a; 圖形化界面&am…

Linux+apache之 瀏覽器訪問云服務器磁盤的圖片,通過tomcat

https://javab.blog.csdn.net/article/details/80580520 安裝tomcact 修改添加 <Context docBase"/home/wyp/images" path"/img" debug"0" reloadable"true" />修改完成后保存重啟tomcat服務。 測試訪問方式&#xff1a;http…

軟件工程與實踐(第4版 新形態) 練習與實踐1

軟件工程與實踐&#xff08;第4版 新形態&#xff09; 練習與實踐1 1.填空題 (1)程序&#xff0c;文檔 (2)系統軟件&#xff0c;支撐軟件&#xff0c;應用軟件 (3)系統方法 (4)軟件開發和維護 (5)工程的概念、原理、技術和方法 (6)實現軟件的優質高產 (7)軟件開發技術和…

基于遺傳算法的無人機三維路徑規劃仿真步驟詳解

基于遺傳算法的無人機三維路徑規劃仿真步驟詳解 一、問題定義 目標:在三維空間內,尋找從起點到終點的最優路徑,需滿足: 避障:避開所有障礙物。路徑最短:總飛行距離盡可能短。平滑性:轉折角度不宜過大,降低機動能耗。輸入: 三維地圖(含障礙物,如立方體、圓柱體)。起…

LIUNX學習-線程

線程概念 一個進程需要訪的大部分資源&#xff0c;諸如自身的代碼、數據、new\malloc的空間數據、命令行參數和環境變量、動態庫、甚至是系統調用訪問內核代碼…都是通過虛擬地址空間來訪問的。換而言之&#xff0c;進程地址空間是進程的資源窗口&#xff01;&#xff01; ? …

1.Big-endian/ little endian大端對齊、小端對齊

一、大端模式、小端模式的介紹 Little endian&#xff1a;是低位字節排放在內存的低地址端、高位字節排放在內存的高地址端。 Big-endian&#xff1a;是高位字節排放在內存的低地址端、低位字節排放在內存的高地址端。 西門子是大端模式&#xff0c;因為比如 MW100 MB100(高位…

[mybatis]resultMap詳解

resultMap Mybatis中提供了resultMap功能&#xff0c;可以將數據庫查詢結果映射到Java對象&#xff0c;用于解決 字段名與屬性名不一致 或 復雜關系&#xff08;如一對多&#xff09;的映射問題。 比如一個User類&#xff0c;在它的屬性里還有另一個子對象&#xff08;或者多…

SpringBoot Actuator

SpringBoot Actuator 一、簡介二、入門1、依賴2、默認監控指標3、查詢監控指標4、全量監控指標 三、Spring Boot Admin1、主要功能2、Admin3、Client4、應用墻5、其他 四、定制化1、定制Health端點2、定制Info端點3、定制Metrics端點4、定制Endpoint端點 一、簡介 SpringBoot自…

python標識符

在 Python 中&#xff0c;標識符&#xff08;Identifier&#xff09;是指用來標識變量、函數、類、模塊等的名稱。標識符的命名規則如下&#xff1a; 1. 標識符的命名規則 字母、數字和下劃線&#xff1a;標識符可以由字母&#xff08;a-z, A-Z&#xff09;、數字&#xff08;…

06 HarmonyOS Next性能優化之LazyForEach 列表渲染基礎與實現詳解 (一)

溫馨提示&#xff1a;本篇博客的詳細代碼已發布到 git : https://gitcode.com/nutpi/HarmonyosNext 可以下載運行哦&#xff01; 目錄 一、代碼結構概覽二、詳細代碼解析1. 數據源管理實現2. 數據結構定義3. 優化的列表項組件4. 主列表組件實現 一、代碼結構概覽 本文將詳細解…