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

news2025/3/6 1:19:56

【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 100

int main() {
    // server即外网服务器
    int serverPort = DEFAULT_PORT;
    int serverListen;
    struct sockaddr_in serverAddr;

    // 建立监听socket
    serverListen = 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的客户端C1
    struct 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的客户端C2
    struct 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和port
    char bufSend1[BUFFER_SIZE];// bufSend1中存储C2的外网ip和port
    memset(bufSend1, '\0', sizeof(bufSend1));
    char *ip2 = inet_ntoa(sourceAddr2.sin_addr);// C2的ip
    char port2[10];// C2的port
    snprintf(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和port
    char bufSend2[BUFFER_SIZE];// bufSend2中存储C1的外网ip和port
    memset(bufSend2, '\0', sizeof(bufSend2));
    char *ip1 = inet_ntoa(sourceAddr1.sin_addr);// C1的ip
    char port1[10];// C1的port
    snprintf(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 100

int 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));

    // 绑定固定端口 6003
    struct 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 100

int 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));

    // 绑定固定端口 6002
    struct 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.coloradmin.cn/o/2310279.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

(1)udp双向通信(2)udp实现文件复制(3)udp实现聊天室

一.udp双向通信 1.fork进程实现双向通信 【1】head.h 【2】client客户端 &#xff08;1&#xff09;父进程从键盘获取字符串 &#xff08;2&#xff09;输入quit&#xff0c;发送结束子进程信号 &#xff08;3&#xff09;exit退出父进程 &#xff08;1&#xff09;子进程接受…

c高级第五天

1> 在终端提示输入一个成绩&#xff0c;通过shell判断该成绩的等级 [90,100] : A [80, 90) : B [70, 80) : C [60, 70) : D [0, 60) : 不及格 #!/bin/bash# 提示用户输入成绩 read -p "请输入成绩&#xff08;0-100&#xff09;&#xff1a;" score# 判断成…

【JQuery—前端快速入门】JQuery 操作元素

JQuery 操作元素 1. 获取/修改元素内容 三个简单的获取元素的方法&#xff1a; 这三个方法即可以获取元素的内容&#xff0c;又可以设置元素的内容. 有参数时&#xff0c;就进行元素的值设置&#xff0c;没有参数时&#xff0c;就进行元素内容的获取. 接下来&#xff0c;我们需…

标签的ref属性 vue中为什么不用id标记标签

标签的ref属性 vue中为什么不用id标记标签 假设有一对父子组件&#xff0c;如果父组件和子组件中存在id相同的标签&#xff0c;会产生冲突。通过id获取标签会获取到先加载那个标签。 标签的ref属性的用法 在父组件App中&#xff0c;引入了子组件Person。 并使用ref标记了Pe…

7.1.1 计算机网络的组成

文章目录 物理组成功能组成工作方式完整导图 物理组成 计算机网络是将分布在不同地域的计算机组织成系统&#xff0c;便于相互之间资源共享、传递信息。 计算机网络的物理组成包括硬件和软件。硬件中包含主机、前端处理器、连接设备、通信线路。软件中包含协议和应用软件。 功…

IDEA 接入 Deepseek

在本篇文章中&#xff0c;我们将详细介绍如何在 JetBrains IDEA 中使用 Continue 插件接入 DeepSeek&#xff0c;让你的 AI 编程助手更智能&#xff0c;提高开发效率。 一、前置准备 在开始之前&#xff0c;请确保你已经具备以下条件&#xff1a; 安装了 JetBrains IDEA&…

mapbox基础,使用点类型geojson加载symbol符号图层,用于标注文字

👨‍⚕️ 主页: gis分享者 👨‍⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨‍⚕️ 收录于专栏:mapbox 从入门到精通 文章目录 一、🍀前言1.1 ☘️mapboxgl.Map 地图对象1.2 ☘️mapboxgl.Map style属性1.3 ☘️symbol符号图层样式二、🍀使用点类型…

STM32---FreeRTOS中断管理试验

一、实验 实验目的&#xff1a;学会使用FreeRTOS的中断管理 创建两个定时器&#xff0c;一个优先级为4&#xff0c;另一个优先级为6&#xff1b;注意&#xff1a;系统所管理的优先级范围 &#xff1a;5~15 现象&#xff1a;两个定时器每1s&#xff0c;打印一段字符串&#x…

HTTP 状态代码 501 502 问题

问题 单个客户端有时会出现 报错 501 或 502 如下&#xff1a; System.Net.Http.HttpRequestException: Response status code does not indicate success: 501 (Not Implemented) 分析 可以排除 服务器无法处理的问题&#xff08;测试发现 一个客户端报错&#xff0c;不会影响…

visual studio 2022 手工写一个简单的MFC程序

书籍&#xff1a;《Visual C 2017从入门到精通》的2.1.2 MFC方式中2.手工写一个简单的MFC程序 环境&#xff1a;visual studio 2022 内容&#xff1a;手工写一个简单的MFC程序 1.文件->新建->项目 2.根据以下步骤选择Windows桌面向导 3.输入项目名&#xff0c;选择保…

测试用例总结

一、通用测试用例八要素   1、用例编号&#xff1b;    2、测试项目&#xff1b;   3、测试标题&#xff1b; 4、重要级别&#xff1b;    5、预置条件&#xff1b;    6、测试输入&#xff1b;    7、操作步骤&#xff1b;    8、预期输出 二、具体分析通…

vulnhub靶场之【digitalworld.local系列】的development靶机

前言 靶机&#xff1a;digitalworld.local-devt-improved&#xff0c;IP地址为192.168.10.10 攻击&#xff1a;kali&#xff0c;IP地址为192.168.10.6 kali采用VMware虚拟机&#xff0c;靶机选择使用VMware打开文件&#xff0c;都选择桥接网络 这里官方给的有两种方式&…

Select 下拉菜单选项分组

使用<select>元素创建下拉菜单&#xff0c;并使用 <optgroup> 元素对选项进行分组。<optgroup> 元素允许你将相关的 <option> 元素分组在一起&#xff0c;并为每个分组添加一个标签。 <form action"#" method"post"><la…

文件上传漏洞详细利用流程

一、了解基本术语 1、后门 像房子一样&#xff0c;前门后门都可以进出房子&#xff0c;而较之前门&#xff0c;后门更具有隐蔽性。电脑技术中的后门是抽象概念&#xff0c;意指隐蔽性高或不常用的&#xff0c;区别于常规操作所使用的一种出入口。现金网络后门形形色色&#x…

蓝桥与力扣刷题(蓝桥 旋转)

题目&#xff1a;图片旋转是对图片最简单的处理方式之一&#xff0c;在本题中&#xff0c;你需要对图片顺时针旋转 90 度。 我们用一个 nm的二维数组来表示一个图片&#xff0c;例如下面给出一个 34 的 图片的例子&#xff1a; 1 3 5 7 9 8 7 6 3 5 9 7 这个图片顺时针旋转…

transformer架构解析{掩码,(自)注意力机制,多头(自)注意力机制}(含代码)-3

目录 前言 掩码张量 什么是掩码张量 掩码张量的作用 生成掩码张量实现 注意力机制 学习目标 注意力计算规则 注意力和自注意力 注意力机制 注意力机制计算规则的代码实现 多头注意力机制 学习目标 什么是多头注意力机制 多头注意力计算机制的作用 多头注意力机…

使用DiskGenius工具来实现物理机多硬盘虚拟化迁移

使用DiskGenius工具来实现物理机多硬盘虚拟化迁移 概述准备工作注意事项实操过程记录1、Win7虚拟机&#xff0c;安装有两个硬盘&#xff08;硬盘0和硬盘1&#xff09;&#xff0c;各分了一个区&#xff0c;磁盘2是一块未使用的磁盘2、运行DiskGenius程序&#xff0c;记录现有各…

React封装通用Table组件,支持搜索(多条件)、筛选、自动序号、数据量统计等功能。未采用二次封装调整灵活,包含使用文档

封装通用组件 一、封装思想二、react代码三、css代码四、实现效果五、使用文档 BasicTableModal 表格模态框组件1.组件简介2.功能特点3.使用方法基础用法宽度控制示例带筛选功能搜索功能示例自定义单元格渲染 4.API 说明PropsColumn 配置项Filter 配置项 5.注意事项 一、封装思…

【JavaEE】-- 多线程(初阶)4

文章目录 8.多线程案例8.1 单例模式8.1.1 饿汉模式8.1.2 懒汉模式 8.2 阻塞队列8.2.1 什么是阻塞队列8.2.2 生产者消费者模型8.2.3 标准库中的阻塞队列8.2.4 阻塞队列的应用场景8.2.4.1 消息队列 8.2.5 异步操作8.2.5 自定义实现阻塞队列8.2.6 阻塞队列--生产者消费者模型 8.3 …

WP 高级摘要插件:助力 WordPress 文章摘要精准自定义显示

wordpress插件介绍 “WP高级摘要插件”功能丰富&#xff0c;它允许用户在WordPress后台自定义文章摘要。 可设置摘要长度&#xff0c;灵活调整展示字数&#xff1b;设定摘要最后的显示字符&#xff0c; 如常用的省略号等以提示内容未完整展示&#xff1b;指定允许在摘要中显示…