Вопрос или проблема
Я работаю над видеоигрой, и для неё я пытаюсь добавить онлайн-мультиплеер. Я создал свой собственный файл обертки winsock для обработки клиентов и серверов, и он работает для LAN-соединений. Однако при попытке подключения клиентов через интернет программа не удаётся подключиться.
В моём коде есть 3 основные функции для подключения клиентов: “createServer”, которая создаёт сервер, “OpenClientConnection”, которая позволяет клиентам подключаться, и “createClient”, которая подключает клиент к открытому серверу. Я много этого кода получил со страницы справки по winsock от Microsoft и хотел бы узнать, может ли кто-то помочь или предоставить полезные ресурсы по этому вопросу. Спасибо.
bool Ipv6Server::createServer()
{
//Флаг результата
int _result;
//Создаёт объект данных winsock
WSADATA wsa_data;
//Запускает WSA
_result = WSAStartup(MAKEWORD(2, 2), &wsa_data);
if (_result != 0)
{
std::cout << "WSAStartup не удался: " << _result << "\n";
return false;
}
//Информация об адресе
struct addrinfo *result = NULL,
hints;
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET6;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;
//Разрешает адрес сервера и порт
_result = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);
if (_result != 0)
{
std::cout << "getaddrinfo не удался: " << _result << "\n";
WSACleanup();
return false;
}
//Создаёт сокет для прослушивания соединения
mListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
if (mListenSocket == INVALID_SOCKET)
{
std::cout << "Ошибка в socket(): " << WSAGetLastError() << "\n";
freeaddrinfo(result);
WSACleanup();
return false;
}
//Привязывает сокет к IP-адресу и порту
_result = bind(mListenSocket, result->ai_addr, (int)result->ai_addrlen);
if (_result == SOCKET_ERROR)
{
std::cout << "Привязка не удалась: " << WSAGetLastError() << "\n";
freeaddrinfo(result);
closesocket(mListenSocket);
WSACleanup();
return false;
}
//Освобождает адрес
freeaddrinfo(result);
return mListenSocket;
}
//Позволяет новому клиенту подключиться
bool Ipv6Server::openClientConnection()
{
//Прослушивает сокет
if (listen(mListenSocket, SOMAXCONN) == SOCKET_ERROR)
{
std::cout << "Не удалось прослушивать: " << WSAGetLastError() << "\n";
closesocket(mListenSocket);
WSACleanup();
return false;
}
//Принимает соединение
mReceivers.push_back(Receiver{INVALID_SOCKET});
std::cout << "Прослушивание клиента\n";
mReceivers[mClientCount].client = accept(mListenSocket, NULL, NULL);
std::cout << "Клиент найден\n";
if (mReceivers[mClientCount].client == INVALID_SOCKET)
{
std::cout << "Не удалось принять соединение: " << WSAGetLastError() << "\n";
closesocket(mListenSocket);
WSACleanup();
return false;
}
mClientCount++;
return true;
}
//Создаёт клиента
bool Ipv6Client::createClient(const char* _server_ipv6)
{
//Флаг результата
int _result;
WSADATA wsa_data;
mReceiver.client = INVALID_SOCKET;
//Запускает WSA
_result = WSAStartup(MAKEWORD(2, 2), &wsa_data);
if (_result != 0)
{
std::cout << "WSAStartup не удался: " << _result << "\n";
return false;
}
//Информация об адресе
struct addrinfo *result = NULL,
*ptr = NULL,
hints;
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET6;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;
//Разрешает адрес сервера и порт
_result = getaddrinfo(_server_ipv6, DEFAULT_PORT, &hints, &result);
if (_result != 0)
{
std::cout << "getaddrinfo не удался: " << _result << "\n";
WSACleanup();
return false;
}
//Пытается подключиться к серверу
for (ptr = result; ptr != NULL; ptr = ptr->ai_next)
{
//Создаёт сокет для подключения
mReceiver.client = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
if (mReceiver.client == INVALID_SOCKET)
{
std::cout << "Сокет не смог подключиться к серверу: " << WSAGetLastError() << "\n";
WSACleanup();
return false;
}
//Подключается к серверу
_result = connect(mReceiver.client, ptr->ai_addr, (int)ptr->ai_addrlen);
if (_result == SOCKET_ERROR)
{
closesocket(mReceiver.client);
mReceiver.client = INVALID_SOCKET;
continue;
}
break;
}
freeaddrinfo(result);
if (mReceiver.client == INVALID_SOCKET)
{
std::cout << "Не удалось подключиться к серверу\n";
WSACleanup();
return false;
}
//Создаёт приемник
createReceiver();
return true;
}
Это приложение предназначено для подключения клиентов, и оно работает, но только когда они находятся в одной локальной сети. Я использую адреса IPv6, потому что знаю, что эти адреса могут быть глобально маршрутизированы, и это позволило мне избежать NAT для IPv4.
Ответ или решение
Использование Winsock для подключения через Интернет (IPv6)
При разработке онлайн-многопользовательской игры подключение клиентов через Интернет может представлять собой определённые сложности, особенно когда дело касается использования протокола IPv6. Ниже приведено подробное объяснение и рекомендации по отладке вашего кода, чтобы обеспечить корректное соединение клиентов через Winsock.
1. Основные шаги для создания сервера и клиента
Ваш код в функции createServer
и createClient
выглядит неплохо, однако есть несколько важных моментов, которые необходимо учитывать при работе через Интернет.
Настройки сервера
bool Ipv6Server::createServer()
{
// Получение информации о адресе
//...
hints.ai_flags = AI_PASSIVE | AI_V4MAPPED; // Обратите внимание на это изменение
//...
}
Включение флага AI_V4MAPPED
позволяет вашему серверу принимать соединения как с IPv6, так и с IPv4. Это особенно полезно, если ваш клиент использует IPv4-проксирование.
Подключение клиента
bool Ipv6Client::createClient(const char* _server_ipv6)
{
//...
hints.ai_flags = 0; // Убедитесь, что здесь стоит правильный флаг
//...
}
При попытке подключиться клиентом к серверу убедитесь, что вы используете правильный формат IPv6 адреса. Это должно быть в формате, соответствующем стандарту URI (например, [xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx]:port).
2. Проверка на корректное подключение через интернет
Проблемы NAT
Если вы работаете в локальной сети, то на вашем маршрутизаторе может быть включен NAT (трансляция сетевых адресов). Убедитесь, что:
- Порт, на котором ваш сервер прослушивает подключения, перенаправлен на локальный IP-адрес вашего сервера в настройках маршрутизатора.
- Firewall (брандмауэр) отключен или настроен так, чтобы разрешать входящие соединения на этот порт.
Использование getaddrinfo
При вызове getaddrinfo
, передавайте доменное имя или публичный IPv6-адрес вашего сервера, чтобы обеспечить возможность его разрешения. Если вы используете локальное (частное) имя, оно может не разрешаться за пределами вашей сети.
3. Тестирование
Для тестирования соединения попробуйте следующее:
- Запустите сервер в одной сети и попробуйте подключиться к нему с другого устройства, находящегося в другой сети (через VPN или другую сеть).
- Используйте инструменты, такие как
ping
илиtracert
, для проверки доступности IPv6-адреса вашего сервера.
4. Полезные ресурсы
- Документация MSDN по Winsock: содержит полное руководство по использованию Winsock для создания сетевых приложений.
- Справочные материалы по IPv6: важны для понимания концепций и адресации.
Заключение
Организация подключения клиентов к вашему серверу через интернет требует учёта множества факторов. Настройка правильных флагов, решение проблем с NAT и тестирование соединения с использованием реальных адресов — ключевые шаги к достижению цели. Работая с Winsock и IPv6, вы можете избежать многих распространённых проблем, если будете следовать изложенным выше рекомендациям.