当前位置: 首页 > 知识库问答 >
问题:

客户端未收到UDP连接响应

穆建华
2023-03-14

我在实现UDP连接时遇到了麻烦,因为当我在局域网内尝试它时,它是有效的,但是当NAT内部的人试图连接到公共服务器地址时,它会失败,因为从服务器作为响应发送的数据包永远不会到达客户端。

我的协议如下:

  1. 客户端A向服务器发送一个字节作为连接请求
  2. 服务器B为客户端创建一个新的套接字,并从那里向recvfrom()调用中报告的客户端端口响应一个字节。永远不会联系到客户

我也试过:

执行许多调用,每个调用在步骤 1 中发送一个字节。

为步骤2进行多次调用,每次发送一个字节。

客户端代码:

#define GPK_CONSOLE_LOG_ENABLED
#include "gpk_stdsocket.h"
#include "gpk_sync.h"

int main() {
    ::gpk::tcpipInitialize();
    sockaddr_in         sa_server           = {AF_INET};
    while(true) {
//#define MAKE_IT_WORK
#if defined MAKE_IT_WORK
        ::gpk::tcpipAddressToSockaddr({{192,168,0,2}, 9898}, sa_server);
#else
        ::gpk::tcpipAddressToSockaddr({{201,235,131,233}, 9898}, sa_server);
#endif
        ::gpk::SIPv4        addrRemote          = {};
        SOCKET              handle              = socket(AF_INET, SOCK_DGRAM, 0);
        ree_if(INVALID_SOCKET == handle, "Failed to create socket.");
        sockaddr_in         sa_client           = {AF_INET};
        gpk_necall(::bind(handle, (sockaddr *)&sa_client, sizeof(sockaddr_in)), "Failed to bind listener to address");
        char                commandToSend       = '1';
        int                 sa_length           = sizeof(sa_server);
        gpk_necall(sendto(handle, (const char*)&commandToSend, (int)sizeof(char), 0, (sockaddr *)&sa_server, sa_length), "Failed to send connect request to server.");
        {
            sockaddr_in         sa_battery          = sa_server;
            for(uint32_t j=3; j < 3; ++j) {
            for(uint32_t i=16*1024; i < 64*1024; ++i) {
                sa_battery.sin_port = htons((u_short)i);
                gpk_necall(sendto(handle, (const char*)&commandToSend, (int)sizeof(char), 0, (sockaddr *)&sa_battery, sa_length), "Failed to send connect request to server.");
                //::gpk::sleep(1);
            }}
        }
        ::gpk::tcpipAddressFromSockaddr(sa_server, addrRemote);
        info_printf("Send connect request to server: %c to %u.%u.%u.%u:%u", commandToSend, GPK_IPV4_EXPAND(addrRemote));

        char                connectAcknowledge  = 0;
        sa_server.sin_port  = 0;
        gpk_necall(recvfrom(handle, (char *)&connectAcknowledge, (int)sizeof(char), 0, (sockaddr *)&sa_server, &sa_length), "Failed to receive response from server");
        addrRemote          = {};
        ::gpk::tcpipAddressFromSockaddr(sa_server, addrRemote);
        info_printf("Received connect response from server: %c from %u.%u.%u.%u:%u.", connectAcknowledge, GPK_IPV4_EXPAND(addrRemote));
        ::gpk::sleep(1000);
        gpk_safe_closesocket(handle);
    }
    ::gpk::tcpipShutdown();
    return 0;
}

服务器代码:

#define GPK_CONSOLE_LOG_ENABLED
#include "gpk_stdsocket.h"
#include "gpk_sync.h"

int main() { 
    ::gpk::tcpipInitialize();
    sockaddr_in             sa_server               = {};
    ::gpk::SIPv4            addrLocal               = {{}, 9898};
    ::gpk::tcpipAddress(0, 9898, 1, ::gpk::TRANSPORT_PROTOCOL_UDP, addrLocal);
    ::gpk::tcpipAddressToSockaddr(addrLocal, sa_server);

    SOCKET                  handle                  = socket(AF_INET, SOCK_DGRAM, 0);
    ree_if(INVALID_SOCKET == handle, "Failed to create socket.");
    gpk_necall(::bind(handle, (sockaddr *)&sa_server, sizeof(sockaddr_in)), "Failed to bind listener to address");
    info_printf("Server listening on %u.%u.%u.%u:%u.", GPK_IPV4_EXPAND(addrLocal));
    while(true) {
        ::gpk::SIPv4            addrLocalClient         = addrLocal;
        addrLocalClient.Port = 0;
        SOCKET                  clientHandle            = socket(AF_INET, SOCK_DGRAM, 0);

        sockaddr_in             sa_server_client        = {AF_INET};
        ::gpk::tcpipAddressToSockaddr(addrLocalClient, sa_server_client);
        gpk_necall(::bind(clientHandle, (sockaddr *)&sa_server_client, sizeof(sockaddr_in)), "Failed to bind listener to address");

        sockaddr_in             sa_client               = {AF_INET};
        int                     client_length           = sizeof(sa_client);
        char                    connectReceived         = 0;
        gpk_necall(::recvfrom(handle, (char*)&connectReceived, (int)sizeof(char), 0, (sockaddr*)&sa_client, &client_length), "Failed to receive connect request.");
        ::gpk::SIPv4            addrRemote;
        ::gpk::tcpipAddressFromSockaddr(sa_client, addrRemote);
        info_printf("Received connect request: %c from %u.%u.%u.%u:%u.", connectReceived, GPK_IPV4_EXPAND(addrRemote));

        char                    commandToSend           = '2';
        //::gpk::tcpipAddressFromSockaddr(sa_server, addrLocal);
        ::gpk::tcpipAddress(clientHandle, addrLocal);
        info_printf("Sending connect response %c from %u.%u.%u.%u:%u to %u.%u.%u.%u:%u.", commandToSend, GPK_IPV4_EXPAND(addrLocal), GPK_IPV4_EXPAND(addrRemote));
        ::gpk::sleep(10);
        ree_if(INVALID_SOCKET == clientHandle, "Failed to create socket.");
        for(uint32_t i=16*1024; i < 65535; ++i)
            gpk_necall(::sendto(clientHandle, (const char*)&commandToSend, (int)sizeof(char), 0, (sockaddr*)&sa_client, sizeof(sockaddr_in)), "Failed to respond.");
        info_printf("Sent connect response %c from %u.%u.%u.%u:%u to %u.%u.%u.%u:%u.", commandToSend, GPK_IPV4_EXPAND(addrLocal), GPK_IPV4_EXPAND(addrRemote));
        if(handle != clientHandle)
            gpk_safe_closesocket(clientHandle);
    }
    ::gpk::tcpipShutdown();
    return 0; 
}

注意:我将示例udp_server和udp_client项目保留在 https://github.com/asm128/gpk,以防您想要一个工作示例,该示例通过取消注释 //#define MAKE_IT_WORK 并将您的 IP 地址放在我的 IP 地址来有条件地编译工作和损坏的情况。

共有1个答案

商俊智
2023-03-14

您的响应不会返回到客户端,因为您正在使用单独的套接字将响应发送回。此套接字的本地端口号与从客户端接收数据包的套接字不同,因此对于 NAT,它似乎来自不同的来源,因此不会转发。

当UDP数据报退出NAT时,NAT会跟踪目标IP和端口以及NAT使用的本地IP和端口,并将其与内部网络上的原始源IP和端口相匹配。对于传入数据包要传回原始源,传入数据包的源IP和端口必须与前一个传出数据包的目标IP和端口匹配,并且传入数据包中的目标IP与端口必须与传出数据包中NAT的IP和端口相匹配。如果满足该条件,NAT将数据包转发到原始源IP和端口

让我们用一个例子来说明这一点。假设您有以下主机:

  • 服务器(NAT之外):IP192.168.0.10
  • NAT:内部IP192.168.0.1,外部IP10.0.0.1
  • 客户端(NAT内部):IP10.0.0.2

您的服务器创建一个绑定到点 9898 的套接字并等待。然后,客户端创建一个绑定到端口 0 的套接字,这意味着选择一个随机端口。假设它是端口 10000。然后,客户端将 UDP 数据包发送到 192.168.0.10:9898。因此,数据包具有:

  • 源 IP:10.0.0.2
  • 目标 IP:192.168.0.10
  • 源端口:10000
  • 目标端口:9898

然后,数据包通过NAT,NAT会调整源IP和端口,以便将响应发送回客户端。它选择端口15000。现在这个包看起来像这样:

  • 来源IP:192.168.0.1
  • 目的地IP:192.168.0.10
  • 源端口:15000
  • 目的端口:9898

如果NAT稍后看到来自外部网络的数据包具有相同的IP/端口对,但源/目的地颠倒,则它会将其发送到10.0.0.2:10000。

然后服务器会收到这个数据包。但是现在您在服务器上创建一个新套接字并将其绑定到端口0,因此选择了一个随机端口,假设12000。然后服务器使用此套接字将响应发送回它来的地方。所以响应数据包看起来像这样:

    < li >源IP: 192.168.0.10 < li >目的IP: 192.168.0.1 < li >源端口:12000 < li >目的端口:15000

然后,NAT 接收此数据包,并需要决定是否将其转发到内部主机。如果源端口是 9898,它会将目标 IP/端口更改为 10.0.0.2:10000 并将其发送到那里。但它不匹配,因此 NAT 会丢弃数据包。

服务器需要使用从客户端接收数据包的同一个套接字来发送响应。如果是,数据包将如下所示:

  • 源 IP:192.168.0.10
  • 目标 IP:192.168.0.1
  • 源端口:9898
  • 目标端口:15000

NAT 会将其转发给客户端,因为它与发出的数据包匹配,但源/目标已交换。

对于处理来自多个客户端的请求的服务器,它需要跟踪请求来自哪里,并拥有某种机制来保存每个客户端的状态,以确定要发送的适当响应。

 类似资料:
  • 我是一个使用python进行套接字编程的初学者。我正在做我的课程项目。我的项目的一部分需要用不同的端口发送和接收UDP消息。提供了名为robot的服务器程序,我需要编写名为student的客户端程序,它可以与机器人进行交互。因此,我不能显示服务器程序中的所有源代码。 这是服务器程序中与UDP套接字相关的部分 这是我的客户端程序。s3 是 UDP 套接字。我可以成功地向服务器程序发送消息,但无法从中

  • udp 客户端 udp 客户端 源码/* * Copyright (c) 2006-2018, RT-Thread Development Team * * SPDX-License-Identifier: Apache-2.0 * * Change Logs: * Date Author Notes * *//* * 程序清单:udp 客户端 * * 这是一个 udp 客户端的例程 * 导出 u

  • 我有一个简单的udp客户机/服务器程序。 如果客户端正在失去连接,或者服务器正在重新启动,客户端不会自动重新连接。我总是要手动重启客户端。 这是我的客户端套接字配置:

  • 下面是我的服务器代码: 下面是我的客户端活动代码: 以下是客户端活动的xml布局文件: 因此,我开始认为这不是连接端口的问题,而是应用程序的android客户端的问题。但我想不出有什么问题。 顺便说一下,当我试图发送消息时,运行客户端的手机和运行服务器的笔记本电脑都连接到了同一个网络。

  • 问题内容: 我有一个Java控制器,必须向我发送一些文本数据和不同的字节数组。因此,我正在构建n多部分请求,并将其写入到HttpServletResponse的流中。 现在我的问题是如何在客户端解析响应并提取多个部分。 服务器代码片段:- 客户代码片段:- 我检查了CloseableHttpResponse和HttpEntity,但是它们都不提供解析多部分请求的方法。 编辑1:这是我在客户端流中收

  • 我有一个java控制器,它必须向我发送一些文本数据和不同的字节数组。因此,我正在构建一个多部分请求,并将其从HttpServletResponse写入流。 现在我的问题是如何在客户端解析响应并提取多个部分。 服务器代码片段:- 客户端代码段:- 我检查了CloseableHttpResponse和HttpEntity,但它们都没有提供解析多部分请求的方法。 编辑1:这是我在客户端流中收到的示例响应