【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.rhkb.cn/news/28125.html

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

相关文章

C# Unity 唐老狮 No.4 模拟面试题

本文章不作任何商业用途 仅作学习与交流 安利唐老狮与其他老师合作的网站,内有大量免费资源和优质付费资源,我入门就是看唐老师的课程 打好坚实的基础非常非常重要: 全部 - 游习堂 - 唐老狮创立的游戏开发在线学习平台 - Powered By EduSoho 如果你发现了文章内特殊的字体格式,…

数据结构——队列

1. 概念与结构 队列&#xff08;Queue&#xff09;是一种先进先出&#xff08;FIFO, First In First Out&#xff09;的数据结构&#xff0c;即最先被插入队列的数据会最先被删除。队列广泛应用于计算机科学中&#xff0c;特别是在任务调度、缓冲区管理、网络数据传输等领域。…

大语言模型技术发展

摘要 海外闭源模型领域竞争激烈&#xff0c;OpenAI 保持领先地位&#xff0c;而开源模型如 Meta 的 Llama 系列也逐渐崛起。LLM 技术呈现出大型模型和小型模型并行发展的趋势&#xff0c;同时&#xff0c;多模态功能和长上下文能力成为顶级模型的标准配置。MoE 架构的出现推动…

数据结构入门篇——什么是数据结构。

一、引入 工具是一种什么东西呢&#xff1f;是一种转化媒介&#xff0c;我们需要熟食&#xff0c;我们要通过用火来将生肉烤熟。在这个过程中。我们要输入一个东西——生肉&#xff0c;通过工具——火的加工&#xff0c;从而得到我们的目的产物——熟肉。 将上面的例子和红字部…

DeepSeek掘金——DeepSeek R1驱动的PDF机器人

DeepSeek掘金——DeepSeek R1驱动的PDF机器人 本指南将引导你使用DeepSeek R1 + RAG构建一个功能性的PDF聊天机器人。逐步学习如何增强AI检索能力,并创建一个能够高效处理和响应文档查询的智能聊天机器人。 本指南将引导你使用DeepSeek R1 + RAG构建一个功能性的PDF聊天机器人…

sqli-labs靶场通关

一.less-16 1.寻找注入点 发现url无法回显 构造闭合无论 还是 "都没有任何反应 最后发现闭合符号为")时成功登录 没有回显使用盲注 2.爆数据库 12") or (length(database()))8# 爆出数据库长度 获取数据库名&#xff0c;第一个字母的ascii码值 12") …

使用VSCode Debugger 调试 React项目

点击创建 launch.json 文件 端口号改成我们项目的端口号3000即可&#xff0c;保存 "version": "0.2.0","configurations": [{"type": "chrome","request": "launch","name": "针对 loca…

AI 芯片全解析:定义、市场趋势与主流芯片对比

1. 引言&#xff1a;什么是 AI 芯片&#xff1f; 随着人工智能&#xff08;AI&#xff09;的快速发展&#xff0c;AI 计算的需求不断增长&#xff0c;从云计算到边缘计算&#xff0c;AI 芯片成为推动智能化时代的核心动力。那么&#xff0c;什么样的芯片才算 AI 芯片&#xff…

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

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

知识图谱+智能问诊预诊系统vue+django+neo4j架构、带问诊历史

文章结尾部分有CSDN官方提供的学长 联系方式名片 文章结尾部分有CSDN官方提供的学长 联系方式名片 关注B站&#xff0c;有好处&#xff01; &#x1f90d;编号&#xff1a;D032 &#x1f90d;智能问答&#xff1a;智能问答自诊、预诊功能&#xff0c;同时可以保存问答历史 &…

Chromium_src源码

Chromium_src源码 码云上有一个OpenHarmony-TPC/chromium_src项目&#xff0c;目前已经停止维护了&#xff0c;迁移到GitCode上了&#xff0c;源代码项目地址为&#xff1a;openharmony-tpc/chromium_chrome 特此记录一下老的项目的相关软件架构 Chromium 简介 软件架构 软…

深度学习神经网络分类原理

每一个神经元做的是一个类似回归的操作 最后一层是softmax函数&#xff0c;每一个输出就会变成一个0到1之间的数&#xff0c;也就是概率&#xff0c;然后他们之间的和加起来等于1&#xff0c;到底是哪一个分类就是看哪个神经元的这个值最大。 那么如何算损失呢&#xff1a; 加…

编程题 - 汽水瓶【JavaScript/Node.js解法】

‌“学如逆水行舟&#xff0c;不进则退。”‌ ——《增广贤文》 目录 汽水瓶 题目&#xff1a;解答分析&#xff1a;js代码解答 -ACM模式&#xff1a;代码通过&#xff1a;题解分析&#xff1a;简洁思路代码&#xff1a; 汽水瓶 题目&#xff1a; 某商店规定&#xff1a;三个空…

Oracle 数据库基础入门(四):分组与联表查询的深度探索(上)

在 Oracle 数据库的学习进程中&#xff0c;分组查询与联表查询是进阶阶段的重要知识点&#xff0c;它们如同数据库操作的魔法棒&#xff0c;能够从复杂的数据中挖掘出有价值的信息。对于 Java 全栈开发者而言&#xff0c;掌握这些技能不仅有助于高效地处理数据库数据&#xff0…

Mybatis实现批量添加

1.设计一张商品表 CREATE TABLE IF NOT EXISTS goods (id BIGINT AUTO_INCREMENT PRIMARY KEY,name VARCHAR(255) NOT NULL );2.编写实体类映射数据库表格 Data public class Goods {private Long id;private String name;// Getters and Setters }3.编写持久层接口以及其对应…

FPGA开发时序图绘制

开始的时候画时序图都是拿 visio 硬连&#xff0c;但是那个线宽太难统一了&#xff0c;丑不拉几的&#xff0c;遂学习 waveform 语法使用代码来画时序图。 开始 Vscode 中安装 waveform render 或者在 GitHub 搜索 wavedrom 安装即可。由于 vscode 是我常用的编辑器&#xff…

【Python爬虫】爬取公共交通路网数据

程序来自于Github&#xff0c;以下这篇博客作为完整的学习记录&#xff0c;也callback上一篇爬取公共交通站点的博文。 Bardbo/get_bus_lines_and_stations_data_from_gaode: 这个项目是基于高德开放平台和公交网获取公交线路及站点数据&#xff0c;并生成shp文件&#xff0c;…

微服务组件详解——sentinel

1.启动sentinel&#xff1a; 下载jar sentinel-dashboard-1.8.0.jar 使用以下命令直接运行 jar 包&#xff08;JDK 版本必须≥ 1.8&#xff09;&#xff1a; java -Dserver.port9999 -jar D:\sentinel-dashboard-1.8.0.jar 控制台访问地址&#xff1a;http://localhost:9999…

AJAX介绍

XMLHttpRequest get请求使用 const xhr new XMLHttpRequest(); xhr.open("GET", "/data/test.json", true); xhr.onreadystatechange function () {if (xhr.readyState 4) {if (xhr.status 200) {alert(xhr.responseText);} else {console.log("…

Windows下使用docker安装rocketmq

最近准备把rocketmq重新学下&#xff0c;先把环境搭建好 Windows下安装docker拉取最新的rocketmq启动nameserver启动broker 首先在Windows下安装docker&#xff0c;之前不知道为什么&#xff0c;docker desktop启动一直报错&#xff0c;今天把Windows更新了一下&#xff0c;do…