mirror of
https://github.com/HikikoMarmy/Champions-Reborn-Server.git
synced 2026-04-05 00:49:48 -03:00
Reorganized and cleaned up the solution.
This commit is contained in:
422
Source/Lobby Server/LobbyServer.cpp
Normal file
422
Source/Lobby Server/LobbyServer.cpp
Normal file
@@ -0,0 +1,422 @@
|
||||
// ╔═╗ ╦ ╦ ╔═╗ ╔╦╗ ╔═╗ ╦ ╔═╗ ╔╗╔ ╔═╗
|
||||
// ║ ╠═╣ ╠═╣ ║║║ ╠═╝ ║ ║ ║ ║║║ ╚═╗
|
||||
// ╚═╝ ╩ ╩ ╩ ╩ ╩ ╩ ╩ ╩ ╚═╝ ╝╚╝ ╚═╝
|
||||
// ╦ ╔═╗╔╗ ╔╗ ╦ ╦ ╔═╗╔═╗╦═╗╦ ╦╔═╗╦═╗
|
||||
// ║ ║ ║╠╩╗╠╩╗╚╦╝ ╚═╗║╣ ╠╦╝╚╗╔╝║╣ ╠╦╝
|
||||
// ╩═╝╚═╝╚═╝╚═╝ ╩ ╚═╝╚═╝╩╚═ ╚╝ ╚═╝╩╚═
|
||||
|
||||
#include "Lobby Server/LobbyServer.hpp"
|
||||
|
||||
#include "Game/RealmUserManager.hpp"
|
||||
#include "Network/Events.hpp"
|
||||
#include "configuration.hpp"
|
||||
#include "logging.hpp"
|
||||
|
||||
LobbyServer::LobbyServer()
|
||||
{
|
||||
m_running = false;
|
||||
m_conSocket.reset();
|
||||
m_rtaSocket.reset();
|
||||
|
||||
m_gatewaySockets.clear();
|
||||
|
||||
m_clientSockets.clear();
|
||||
m_recvBuffer.resize( 1024 );
|
||||
}
|
||||
|
||||
LobbyServer::~LobbyServer()
|
||||
{
|
||||
Log::Info( "Lobby Server stopped." );
|
||||
|
||||
for( auto &socket : m_clientSockets )
|
||||
{
|
||||
if( socket->fd != INVALID_SOCKET )
|
||||
{
|
||||
closesocket( socket->fd );
|
||||
}
|
||||
}
|
||||
|
||||
for( auto &socket : m_gatewaySockets )
|
||||
{
|
||||
if( socket->fd != INVALID_SOCKET )
|
||||
{
|
||||
closesocket( socket->fd );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LobbyServer::Start( std::string ip )
|
||||
{
|
||||
// Champions of Norrath Gateway Sockets
|
||||
for( int i = 0; i < 8; i++ )
|
||||
{
|
||||
auto gatewaySocket = OpenListenerSocket( ip, 40800 + i, RealmGameType::CHAMPIONS_OF_NORRATH );
|
||||
if( nullptr == gatewaySocket ) return;
|
||||
|
||||
gatewaySocket->flag.is_gateway = true;
|
||||
m_gatewaySockets.push_back( gatewaySocket );
|
||||
}
|
||||
|
||||
// Return to Arms Gateway Sockets
|
||||
for( int i = 0; i < 8; i++ )
|
||||
{
|
||||
auto gatewaySocket = OpenListenerSocket( ip, 40810 + i, RealmGameType::RETURN_TO_ARMS );
|
||||
if( nullptr == gatewaySocket ) return;
|
||||
|
||||
gatewaySocket->flag.is_gateway = true;
|
||||
m_gatewaySockets.push_back( gatewaySocket );
|
||||
}
|
||||
|
||||
m_conSocket = OpenListenerSocket( ip, Config::con_lobby_port, RealmGameType::CHAMPIONS_OF_NORRATH );
|
||||
m_rtaSocket = OpenListenerSocket( ip, Config::rta_lobby_port, RealmGameType::RETURN_TO_ARMS );
|
||||
|
||||
m_running = true;
|
||||
m_thread = std::thread( &LobbyServer::Run, this );
|
||||
|
||||
Log::Info( "Lobby Server started" );
|
||||
}
|
||||
|
||||
void LobbyServer::Stop()
|
||||
{
|
||||
Log::Info( "Stopping Lobby Server..." );
|
||||
|
||||
for( auto &client : m_clientSockets )
|
||||
{
|
||||
client->send( NotifyForcedLogout() );
|
||||
}
|
||||
|
||||
m_running = false;
|
||||
if( m_thread.joinable() )
|
||||
{
|
||||
m_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
sptr_socket LobbyServer::OpenListenerSocket( std::string ip, int32_t port, RealmGameType type )
|
||||
{
|
||||
SOCKET sock = ::WSASocket( AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED );
|
||||
if( sock == INVALID_SOCKET )
|
||||
{
|
||||
Log::Error( "WSASocket() failed on port {}", port );
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
sockaddr_in service{};
|
||||
service.sin_family = AF_INET;
|
||||
service.sin_port = htons( port );
|
||||
|
||||
if( ip == "0.0.0.0" )
|
||||
{
|
||||
service.sin_addr.s_addr = INADDR_ANY;
|
||||
}
|
||||
else if( InetPtonA( AF_INET, ip.c_str(), &service.sin_addr) != 1 )
|
||||
{
|
||||
Log::Error( "Invalid IP address format: {}", ip );
|
||||
closesocket( sock );
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if( bind( sock, reinterpret_cast< SOCKADDR * >( &service ), sizeof( service ) ) == SOCKET_ERROR )
|
||||
{
|
||||
Log::Error( "bind() failed on port {}", port );
|
||||
closesocket( sock );
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if( listen( sock, SOMAXCONN ) == SOCKET_ERROR )
|
||||
{
|
||||
Log::Error( "listen() failed on port {}", port );
|
||||
closesocket( sock );
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto realmSocket = std::make_shared< RealmSocket >();
|
||||
realmSocket->fd = sock;
|
||||
realmSocket->remote_ip = ip;
|
||||
realmSocket->remote_port = port;
|
||||
realmSocket->gameType = type;
|
||||
realmSocket->flag.is_listener = true;
|
||||
|
||||
Log::Info( "Socket Opened on {}:{}", ip, port );
|
||||
|
||||
return realmSocket;
|
||||
}
|
||||
|
||||
void LobbyServer::Run()
|
||||
{
|
||||
FD_SET readSet;
|
||||
FD_SET writeSet;
|
||||
|
||||
timeval timeout = { 0, 1000 };
|
||||
|
||||
while( m_running )
|
||||
{
|
||||
FD_ZERO( &readSet );
|
||||
FD_ZERO( &writeSet );
|
||||
|
||||
FD_SET( m_conSocket->fd, &readSet );
|
||||
FD_SET( m_rtaSocket->fd, &readSet );
|
||||
|
||||
for( const auto &sock : m_gatewaySockets )
|
||||
{
|
||||
if( sock != nullptr && sock->fd != INVALID_SOCKET )
|
||||
{
|
||||
FD_SET( sock->fd, &readSet );
|
||||
}
|
||||
}
|
||||
|
||||
CheckSocketProblem();
|
||||
|
||||
for( const auto &client : m_clientSockets )
|
||||
{
|
||||
FD_SET( client->fd, &readSet );
|
||||
FD_SET( client->fd, &writeSet );
|
||||
}
|
||||
|
||||
auto result = select( 0, &readSet, &writeSet, NULL, &timeout );
|
||||
|
||||
if( result == SOCKET_ERROR )
|
||||
{
|
||||
std::this_thread::sleep_for( std::chrono::milliseconds( 1 ) );
|
||||
continue;
|
||||
}
|
||||
|
||||
for( auto &sock : m_gatewaySockets )
|
||||
{
|
||||
if( sock == nullptr || sock->fd == INVALID_SOCKET )
|
||||
continue;
|
||||
|
||||
if( FD_ISSET( sock->fd, &readSet ) )
|
||||
{
|
||||
AcceptConnection( sock );
|
||||
}
|
||||
}
|
||||
|
||||
if( FD_ISSET( m_conSocket->fd, &readSet ) )
|
||||
{
|
||||
AcceptConnection( m_conSocket );
|
||||
}
|
||||
|
||||
if( FD_ISSET( m_rtaSocket->fd, &readSet ) )
|
||||
{
|
||||
AcceptConnection( m_rtaSocket );
|
||||
}
|
||||
|
||||
for( auto &client : m_clientSockets )
|
||||
{
|
||||
if( FD_ISSET( client->fd, &readSet ) )
|
||||
{
|
||||
ReadSocket( client );
|
||||
}
|
||||
|
||||
if( FD_ISSET( client->fd, &writeSet ) )
|
||||
{
|
||||
WriteSocket( client );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LobbyServer::CheckSocketProblem()
|
||||
{
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
for( auto it = m_clientSockets.begin(); it != m_clientSockets.end(); )
|
||||
{
|
||||
auto &socket = *it;
|
||||
auto elapsed = std::chrono::duration_cast< std::chrono::seconds >( now - socket->last_recv_time );
|
||||
|
||||
// Check for client timeouts
|
||||
if( elapsed.count() > 30 )
|
||||
{
|
||||
socket->flag.disconnected_forced = true;
|
||||
Log::Info( "[LOBBY] Client Timeout : ({})", socket->remote_ip );
|
||||
}
|
||||
|
||||
// Check if we're waiting to disconnect after sending buffered data
|
||||
if( socket->flag.disconnected_wait && socket->m_pendingWriteBuffer.empty() )
|
||||
{
|
||||
socket->flag.disconnected_forced = true;
|
||||
}
|
||||
|
||||
if( socket->flag.disconnected_forced )
|
||||
{
|
||||
UserManager::Get().RemoveUser( socket );
|
||||
Log::Info( "[LOBBY] Client Disconnected : ({})", socket->remote_ip );
|
||||
it = m_clientSockets.erase( it );
|
||||
}
|
||||
else
|
||||
{
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LobbyServer::AcceptConnection( sptr_socket srcSocket )
|
||||
{
|
||||
sockaddr_in clientInfo{};
|
||||
int32_t addrSize = sizeof( clientInfo );
|
||||
|
||||
SOCKET clientSocket = accept( srcSocket->fd, ( SOCKADDR * )&clientInfo, &addrSize );
|
||||
if( clientSocket == INVALID_SOCKET )
|
||||
{
|
||||
Log::Error( "accept() failed" );
|
||||
return;
|
||||
}
|
||||
|
||||
auto new_socket = std::make_shared< RealmSocket >();
|
||||
auto gameType = srcSocket->gameType;
|
||||
|
||||
new_socket->fd = clientSocket;
|
||||
new_socket->remote_addr = clientInfo;
|
||||
new_socket->remote_ip = Util::IPFromAddr( clientInfo );
|
||||
new_socket->remote_port = ntohs( clientInfo.sin_port );
|
||||
new_socket->gameType = gameType;
|
||||
m_clientSockets.push_back( new_socket );
|
||||
|
||||
if( !srcSocket->flag.is_gateway )
|
||||
{
|
||||
UserManager::Get().CreateUser( new_socket, gameType );
|
||||
}
|
||||
|
||||
Log::Info( "New Client Connected : ({}) From Port {}", new_socket->remote_ip, srcSocket->remote_port );
|
||||
}
|
||||
|
||||
void LobbyServer::ReadSocket( sptr_socket socket )
|
||||
{
|
||||
if( socket->flag.disconnected_forced )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto bytesReceived = recv( socket->fd, reinterpret_cast< char * >( m_recvBuffer.data() ), static_cast< int >( m_recvBuffer.size() ), 0 );
|
||||
|
||||
if( bytesReceived == SOCKET_ERROR )
|
||||
{
|
||||
Log::Info( "Socket Error [{}].", WSAGetLastError() );
|
||||
socket->flag.disconnected_forced = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if( bytesReceived == 0 )
|
||||
{
|
||||
socket->flag.disconnected_forced = true;
|
||||
return;
|
||||
}
|
||||
|
||||
socket->last_recv_time = std::chrono::steady_clock::now();
|
||||
|
||||
// Append received data to socket's pending buffer
|
||||
socket->m_pendingReadBuffer.insert(
|
||||
socket->m_pendingReadBuffer.end(),
|
||||
m_recvBuffer.begin(),
|
||||
m_recvBuffer.begin() + bytesReceived
|
||||
);
|
||||
|
||||
// Process packets in the buffer
|
||||
while( socket->m_pendingReadBuffer.size() >= 4 )
|
||||
{
|
||||
int32_t packetSize = ntohl( *reinterpret_cast< const int32_t * >( &socket->m_pendingReadBuffer[ 0 ] ) );
|
||||
|
||||
if( packetSize <= 0 || packetSize > 2048 )
|
||||
{
|
||||
Log::Error( "Invalid packet size: {}. Disconnecting client.", packetSize );
|
||||
socket->flag.disconnected_wait = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if( socket->m_pendingReadBuffer.size() < static_cast< size_t >( packetSize ) )
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
auto stream = std::make_shared< ByteBuffer >( socket->m_pendingReadBuffer.data() + 4, packetSize - 4 );
|
||||
|
||||
// Erase the packet from the buffer
|
||||
socket->m_pendingReadBuffer.erase(
|
||||
socket->m_pendingReadBuffer.begin(),
|
||||
socket->m_pendingReadBuffer.begin() + packetSize
|
||||
);
|
||||
|
||||
if( stream->m_buffer.size() == 0 ) break;
|
||||
|
||||
// Process the packet
|
||||
HandleRequest( socket, stream );
|
||||
}
|
||||
}
|
||||
|
||||
void LobbyServer::WriteSocket( sptr_socket socket )
|
||||
{
|
||||
if( socket->flag.disconnected_forced || socket->m_pendingWriteBuffer.empty() )
|
||||
return;
|
||||
|
||||
socket->last_send_time = std::chrono::steady_clock::now();
|
||||
|
||||
size_t totalBytesSent = 0;
|
||||
const size_t bufferSize = socket->m_pendingWriteBuffer.size();
|
||||
|
||||
while( totalBytesSent < bufferSize )
|
||||
{
|
||||
const size_t remaining = bufferSize - totalBytesSent;
|
||||
const int chunkSize = static_cast< int >( std::min<size_t>( remaining, 1024 ) );
|
||||
|
||||
int bytesSent = send(
|
||||
socket->fd,
|
||||
reinterpret_cast< const char * >( socket->m_pendingWriteBuffer.data() + totalBytesSent ),
|
||||
chunkSize,
|
||||
0
|
||||
);
|
||||
|
||||
if( bytesSent == SOCKET_ERROR )
|
||||
{
|
||||
const int err = WSAGetLastError();
|
||||
if( err == WSAEWOULDBLOCK )
|
||||
break;
|
||||
|
||||
Log::Error( "Send failed: {}", err );
|
||||
socket->flag.disconnected_wait = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if( bytesSent == 0 )
|
||||
{
|
||||
socket->flag.disconnected_wait = true;
|
||||
return;
|
||||
}
|
||||
|
||||
totalBytesSent += bytesSent;
|
||||
|
||||
if( bytesSent < chunkSize )
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::lock_guard< std::mutex > lock( socket->write_mutex );
|
||||
|
||||
socket->m_pendingWriteBuffer.erase(
|
||||
socket->m_pendingWriteBuffer.begin(),
|
||||
socket->m_pendingWriteBuffer.begin() + totalBytesSent
|
||||
);
|
||||
}
|
||||
|
||||
void LobbyServer::HandleRequest( sptr_socket socket, sptr_byte_stream stream )
|
||||
{
|
||||
auto packetId = stream->read< uint16_t >();
|
||||
stream->set_position( 0 );
|
||||
|
||||
auto it = REQUEST_EVENT.find( packetId );
|
||||
if( it == REQUEST_EVENT.end() )
|
||||
{
|
||||
Log::Error( "[LOBBY] Unknown packet id : {}", packetId );
|
||||
Log::Packet( stream->m_buffer, stream->m_buffer.size(), false );
|
||||
return;
|
||||
}
|
||||
|
||||
auto request = it->second();
|
||||
|
||||
if( auto res = request->ProcessRequest( socket, stream ) )
|
||||
{
|
||||
socket->send( res );
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user