UDP 源端口在客户端 getsocketname() 和服务器 recv() 上不同

UDP Source port different on client getsocketname() and server recv()

嗨,我的问题很简单:

我尝试通过 TCP 连接建立 UDP 连接(因为多个客户端通过多个通道连接到服务器,我想通过主 TCP 连接识别相同的客户端)

我通过在客户端创建一个 TCP 和 UDP 套接字并通过 TCP 将 OS 随机分配的本地 UDP 端口传输到服务器来完成此操作。 (在 Windows 上,我通过 getsocketname() 获得 sendto() 之后的端口)。 在服务器上,我使用这个端口将 UDP 数据包发送到这个确切的客户端。 不幸的是,我真正要发送到的端口与我发送到服务器的本地端口不同。

例如:

我的客户端分配了一个本地 UDP 端口 56423。通过已建立的 TCP 连接将其发送到我的服务器。服务器尝试将 UDP 数据包发送到该端口 -> 失败。 当我使用标准方法通过 UDP 套接字上的 recv() 检索服务器上的端口时,它报告源端口为 30299。(我必须提前发送 UDP 数据包以获得该信息,但我想避免实现 UDP连接握手)

怎么会这样?

据我了解,UDP 只有一个目标端口和一个源端口。所以它应该通过将客户端本地端口发送到服务器来工作。 中间可能有使用备用端口的代理服务吗?

编辑: 一些代码:

在客户端: (s32 = int, c8 = char)

if(m_WinSock == INVALID_SOCKET)
    return;

struct sockaddr_storage addr;
s32 len = sizeof(sockaddr);

getsockname(m_WinSock, (SOCKADDR*)&addr, &len);
c8 ipstr[INET6_ADDRSTRLEN + 1];
s32 port;
if(addr.ss_family == AF_INET)
{
    struct sockaddr_in *s = (struct sockaddr_in *)&addr;
    m_uLocalPort = ntohs(s->sin_port);
    inet_ntop(AF_INET, &s->sin_addr, ipstr, sizeof ipstr);
}
else
{ // AF_INET6
    struct sockaddr_in6 *s = (struct sockaddr_in6 *)&addr;
    m_uLocalPort = ntohs(s->sin6_port);
    inet_ntop(AF_INET6, &s->sin6_addr, ipstr, sizeof ipstr);
}

m_uLocalPort之后通过TCP传输

在服务器上:

    SOCKADDR_IN address;
    ZeroMemory(&address, sizeof(address));
    address.sin_addr = addrin;
    address.sin_port = htons((u16)remoteport);
    address.sin_family = AF_INET;

remoteport是客户端发送的端口。

从客户端发送数据包后在服务器上使用此代码:

struct sockaddr_storage addr;
s32 len = sizeof(sockaddr);
s32 bytes = recvfrom(m_WinSock, m_RecvBuffer, NET_MAX_UDP_SIZE, 0, (SOCKADDR*)&addr, &len);
m_RecvAddress = *(struct sockaddr_in *)&addr;

s32 port = ntohs(m_RecvAddress.sin_port);
printf("recv from port %i\n", port);

我收到一个完全不同的端口。

这不是解决问题的可靠方法。例如,NAT 会阻碍并重新映射您的端口,甚至是从您的客户端看到的本地 ip。你可能还需要做udp hole punching.

正确的方法是不假设 tcp 和 udp 有任何相互关系——它们是不同的管道。

编辑:例如:

  1. 客户端 connect() 到服务器的 public tcp 接口。
  2. 身份验证
  3. 服务器向客户端发送一个验证码(cookie)。
  4. 客户端 sendto() 服务器的 public udp 接口,带有 cookie。
  5. 服务器从 recvfrom().
  6. 获取 sockaddr
  7. 服务器关联,例如 sockaddr 到有效的 tcp 连接,使用 cookie。可选地 check/restrict 防止被其他人暴力攻击的 ip。
  8. 也就是说,现在你有一对连接 - 一个接受的 tcp 套接字和 sockaddr 用于 udp 通信。

PS。您可能还想对握手进行一些加密,但这超出了问题的范围。