【发布时间】:2019-07-18 03:51:56
【问题描述】:
我在实现 UDP 连接时遇到了麻烦,因为当我在 LAN 中尝试它时它可以工作,但是当 NAT 内部的某人尝试连接到公共服务器地址时,它会失败,因为从服务器发送的数据包作为响应,永远不会到达客户。
我的协议如下:
- 客户端 A 向服务器发送一个字节作为连接请求。
- 服务器 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 地址替换为我的,有条件地编译工作和损坏的案例。
【问题讨论】:
-
将您尝试的代码添加到问题中。没有所有不重要的部分。
-
看来你自己解决了问题。服务器需要使用接收请求的同一个套接字发送响应。正如我之前提到的,要让 UDP 通过 NAT 工作,端口号必须在两个方向上匹配。
-
另外,修正名称并不是为了让事情更容易理解,而是为了准确。计算机会完全按照您告诉他们的操作,所以如果那是您的真实代码,它可能已经解释了问题所在。此处发布的代码应该是您运行的显示您的问题的确切代码,否则您在浪费每个人的时间。根据提供帮助的人的专业知识,暗示问题可能出在您的代码中并不是侮辱性的。
-
@PabloAriel 除了您最初发布的消息外,您尝试同时使用新套接字和现有套接字,但两种方法都失败了。
-
UDP 是无连接的,因此您必须为所有通信使用单个套接字,以便服务器端口号保持不变。它与 TCP 不同。 TCP 套接字由本地 IP/端口和远程 IP/端口定义,而 UDP 套接字仅由本地 IP/端口定义。