mirror of
https://github.com/thug1src/thug.git
synced 2024-12-02 21:07:12 +00:00
612 lines
18 KiB
C++
612 lines
18 KiB
C++
/*****************************************************************************
|
|
** **
|
|
** Neversoft Entertainment **
|
|
** **
|
|
** Copyright (C) 1999 - All Rights Reserved **
|
|
** **
|
|
******************************************************************************
|
|
** **
|
|
** Project: GEL (Game Engine Library) **
|
|
** **
|
|
** Module: Net (OBJ) **
|
|
** **
|
|
** File name: netclnt.cpp **
|
|
** **
|
|
** Created: 01/29/01 - spg **
|
|
** **
|
|
** Description: Network client code **
|
|
** **
|
|
*****************************************************************************/
|
|
|
|
/*****************************************************************************
|
|
** Includes **
|
|
*****************************************************************************/
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <core/defines.h>
|
|
|
|
#include <sys/timer.h>
|
|
|
|
#include <gel/net/net.h>
|
|
#include <gel/net/client/netclnt.h>
|
|
|
|
/*****************************************************************************
|
|
** Externals **
|
|
*****************************************************************************/
|
|
|
|
|
|
/*****************************************************************************
|
|
** Defines **
|
|
*****************************************************************************/
|
|
|
|
/*****************************************************************************
|
|
** DBG Defines **
|
|
*****************************************************************************/
|
|
|
|
namespace Net
|
|
{
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
** Private Types **
|
|
*****************************************************************************/
|
|
|
|
/*****************************************************************************
|
|
** Private Data **
|
|
*****************************************************************************/
|
|
|
|
/*****************************************************************************
|
|
** Public Data **
|
|
*****************************************************************************/
|
|
|
|
|
|
/*****************************************************************************
|
|
** Private Prototypes **
|
|
*****************************************************************************/
|
|
|
|
|
|
/*****************************************************************************
|
|
** Private Functions **
|
|
*****************************************************************************/
|
|
|
|
bool Client::init( void )
|
|
{
|
|
|
|
|
|
#ifdef __PLAT_NGPS__
|
|
Dbg_MsgAssert(Mem::SameContext(this,Mem::Manager::sHandle().NetworkHeap()),("Client not on network heap"));
|
|
#endif // __PLAT_NGPS__
|
|
|
|
|
|
m_connected = false;
|
|
m_Timestamp = 0;
|
|
if( App::init() == false )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
m_Dispatcher.AddHandler( MSG_ID_CONNECTION_ACCEPTED, handle_connection_accepted,
|
|
mHANDLE_FOREIGN | mHANDLE_LATE );
|
|
m_Dispatcher.AddHandler( MSG_ID_CONNECTION_REFUSED, handle_connection_refusal, mHANDLE_LATE );
|
|
m_Dispatcher.AddHandler( MSG_ID_CONNECTION_TERMINATED, handle_connection_refusal, mHANDLE_LATE );
|
|
m_Dispatcher.AddHandler( MSG_ID_TIMESTAMP, handle_timestamp, mHANDLE_LATE | mHANDLE_FOREIGN );
|
|
|
|
return true;
|
|
}
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
|
|
void Client::deinit( void )
|
|
{
|
|
|
|
|
|
App::deinit();
|
|
}
|
|
|
|
/*****************************************************************************
|
|
** Public Functions **
|
|
*****************************************************************************/
|
|
|
|
/******************************************************************/
|
|
/* Connect (socket-level) to a server at a given IP/Port */
|
|
/* */
|
|
/******************************************************************/
|
|
|
|
bool Client::ConnectToServer( int ip, unsigned short port )
|
|
{
|
|
int result;
|
|
bool connected;
|
|
|
|
memset( &m_server_address, 0, sizeof(m_server_address));
|
|
m_server_address.sin_family = AF_INET;
|
|
m_server_address.sin_port = htons( port );
|
|
m_server_address.sin_addr.s_addr = ip;
|
|
#ifdef __PLAT_NGC__
|
|
m_server_address.len = sizeof( sockaddr_in );
|
|
#else
|
|
memset ( &m_server_address.sin_zero, 0, 8 );
|
|
#endif
|
|
|
|
Dbg_Printf( "%s Connecting to server\n", GetName());
|
|
connected = false;
|
|
do
|
|
{
|
|
if(( result = connect( m_socket, (struct sockaddr *)&m_server_address,
|
|
sizeof( m_server_address ))) == SOCKET_ERROR )
|
|
{
|
|
#if defined( __PLAT_WN32__ ) || defined( __PLAT_XBOX__ )
|
|
if( WSAGetLastError() != WSAEWOULDBLOCK )
|
|
#else
|
|
#ifdef __PLAT_NGPS__
|
|
if( sn_errno( m_socket ) != EWOULDBLOCK )
|
|
#endif
|
|
#endif
|
|
{
|
|
ReportError();
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
connected = true;
|
|
}
|
|
} while( !connected );
|
|
|
|
// "Connecting" to INADDR_ANY disassociates our socket from all addresses
|
|
if( ip == INADDR_ANY )
|
|
{
|
|
m_connected = false;
|
|
}
|
|
else
|
|
{
|
|
m_connected = true;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/******************************************************************/
|
|
/* Client's send function. */
|
|
/* */
|
|
/******************************************************************/
|
|
|
|
void Client::SendEnqueuedMessages( Conn* conn )
|
|
{
|
|
int buf_len;
|
|
MsgPacketStamp msg;
|
|
bool buffer_full;
|
|
|
|
// Should we send this frame?
|
|
if( ShouldSendThisFrame( conn ) == false )
|
|
{
|
|
return;
|
|
}
|
|
|
|
buffer_full = false;
|
|
while( !buffer_full && MessagesToSend( conn ) && !BandwidthExceeded( conn ))
|
|
{
|
|
// Tack on a timestamp (for acking) if we are sending important
|
|
// messages in this packet
|
|
if( ImportantMessagesToSend( conn ))
|
|
{
|
|
MsgDesc msg_desc;
|
|
#ifdef __PLAT_NGPS__
|
|
SignalSema( m_send_semaphore_id );
|
|
#endif
|
|
msg.m_Packetstamp = (unsigned short) conn->m_latest_sent_packet_stamp;
|
|
//msg.m_Handle = conn->GetHandle();
|
|
|
|
msg_desc.m_Data = &msg;
|
|
msg_desc.m_Length = sizeof( MsgPacketStamp );
|
|
msg_desc.m_Id = MSG_ID_TIMESTAMP;
|
|
msg_desc.m_Priority = HIGHEST_PRIORITY;
|
|
EnqueueMessage( conn->GetHandle(), &msg_desc );
|
|
#ifdef __PLAT_NGPS__
|
|
WaitSema( m_send_semaphore_id );
|
|
#endif
|
|
}
|
|
|
|
// Send Latency Tests/Responses if applicable
|
|
if( ( conn->IsRemote()) &&
|
|
( conn->IsForeign() == false ) &&
|
|
( m_flags.TestMask( App::mDYNAMIC_RESEND )))
|
|
{
|
|
MsgDesc msg_desc;
|
|
MsgTimestamp latency_msg;
|
|
unsigned int cur_time;
|
|
|
|
cur_time = m_Timestamp;
|
|
latency_msg.m_Timestamp = cur_time;
|
|
// send out a new latency test, keeping track of the time at which
|
|
// we sent it
|
|
if( ( conn->m_latency_test.m_SendTime == 0 ) ||
|
|
( ( cur_time - conn->m_latency_test.m_SendTime ) > App::MAX_LATENCY ))
|
|
{
|
|
// If we never got a response, simulate an increased latency
|
|
if( conn->m_latency_test.m_SendTime > conn->m_latency_test.m_ReceiveTime )
|
|
{
|
|
unsigned int latency_value;
|
|
|
|
latency_value = conn->GetAveLatency();
|
|
latency_value += 100;
|
|
|
|
if( latency_value > App::MAX_LATENCY )
|
|
{
|
|
latency_value = App::MAX_LATENCY;
|
|
}
|
|
conn->m_latency_test.InputLatencyValue( latency_value );
|
|
}
|
|
|
|
conn->m_latency_test.m_SendTime = cur_time;
|
|
|
|
#ifdef __PLAT_NGPS__
|
|
SignalSema( m_send_semaphore_id );
|
|
#endif
|
|
msg_desc.m_Data = &latency_msg;
|
|
msg_desc.m_Id = MSG_ID_PING_TEST;
|
|
msg_desc.m_Length = sizeof( MsgTimestamp );
|
|
EnqueueMessage( conn->GetHandle(), &msg_desc );
|
|
|
|
#ifdef __PLAT_NGPS__
|
|
WaitSema( m_send_semaphore_id );
|
|
#endif
|
|
}
|
|
}
|
|
|
|
if( !BuildMsgStream( conn, QUEUE_DEFAULT ))
|
|
{
|
|
buffer_full = true;
|
|
}
|
|
// First, use up our bandwidth with re-sends or else they might remain un-sent
|
|
// indefinitely, causing a bad backup on the client if he's waiting on
|
|
// one particular sequence
|
|
if( !BuildMsgStream( conn, QUEUE_SEQUENCED, true ))
|
|
{
|
|
buffer_full = true;
|
|
}
|
|
if( !BuildMsgStream( conn, QUEUE_IMPORTANT, true ))
|
|
{
|
|
buffer_full = true;
|
|
}
|
|
if( !BuildMsgStream( conn, QUEUE_SEQUENCED ))
|
|
{
|
|
buffer_full = true;
|
|
}
|
|
if( !BuildMsgStream( conn, QUEUE_IMPORTANT ))
|
|
{
|
|
buffer_full = true;
|
|
}
|
|
|
|
buf_len = conn->m_write_ptr - conn->m_write_buffer;
|
|
// If there is data to send
|
|
if( ( buf_len > 0 ) &&
|
|
( conn->IsRemote()))
|
|
{
|
|
if( m_connected )
|
|
{
|
|
if( Send( conn->m_write_buffer, buf_len, 0 ))
|
|
{
|
|
m_TotalBytesOut += buf_len;
|
|
conn->GetOutboundMetrics()->AddPacket( buf_len + vUDP_PACKET_OVERHEAD, m_Timestamp );
|
|
conn->m_last_send_time = m_Timestamp;
|
|
conn->SetForceSendThisFrame( false );
|
|
}
|
|
else
|
|
{
|
|
// If it didn't send the messages properly, flag them for resend next frame
|
|
conn->FlagMessagesForResending( conn->m_latest_sent_packet_stamp );
|
|
conn->m_write_ptr = conn->m_write_buffer;
|
|
break;
|
|
}
|
|
|
|
conn->m_write_ptr = conn->m_write_buffer;
|
|
}
|
|
else
|
|
{
|
|
if( SendTo( conn->GetIP(), conn->GetPort(), conn->m_write_buffer, buf_len, 0 ))
|
|
{
|
|
m_TotalBytesOut += buf_len;
|
|
conn->GetOutboundMetrics()->AddPacket( buf_len + vUDP_PACKET_OVERHEAD, m_Timestamp );
|
|
conn->m_last_send_time = m_Timestamp;
|
|
conn->SetForceSendThisFrame( false );
|
|
}
|
|
else
|
|
{
|
|
Dbg_Printf( "*** SendTo Error: Flagging messages for resending\n" );
|
|
// If it didn't send the messages properly, flag them for resend next frame
|
|
conn->FlagMessagesForResending( conn->m_latest_sent_packet_stamp );
|
|
conn->m_write_ptr = conn->m_write_buffer;
|
|
break;
|
|
}
|
|
conn->m_write_ptr = conn->m_write_buffer;
|
|
}
|
|
}
|
|
|
|
conn->m_latest_sent_packet_stamp++;
|
|
}
|
|
}
|
|
|
|
/******************************************************************/
|
|
/* Client's receive function */
|
|
/* */
|
|
/******************************************************************/
|
|
|
|
void Client::ReceiveData( void )
|
|
{
|
|
Conn *conn;
|
|
int num_bytes, actual_data_len;
|
|
struct sockaddr from_address;
|
|
#ifdef __PLAT_NGC__
|
|
from_address.len = sizeof( sockaddr );
|
|
#else
|
|
int addr_len = sizeof( from_address );
|
|
#endif
|
|
struct sockaddr_in *foreign_address;
|
|
|
|
|
|
|
|
|
|
// Local clients never really receive. Their data is automatically "received" when
|
|
// the local server writes its data into the client's receive buffer automatically
|
|
if( IsLocal())
|
|
{
|
|
return;
|
|
}
|
|
// read data into local buffers
|
|
do
|
|
{
|
|
if( !m_connected )
|
|
{
|
|
num_bytes = recvfrom( m_socket, m_in_packet_buffer,
|
|
Manager::vMAX_PACKET_SIZE, 0, &from_address, &addr_len );
|
|
}
|
|
else
|
|
{
|
|
num_bytes = recv( m_socket, m_in_packet_buffer, Manager::vMAX_PACKET_SIZE, 0 );
|
|
}
|
|
if ( num_bytes < 0 )//== SOCKET_ERROR )
|
|
{
|
|
#if defined( __PLAT_WN32__ ) || defined( __PLAT_XBOX__ )
|
|
if( WSAGetLastError() != WSAEWOULDBLOCK )
|
|
#else
|
|
#ifdef __PLAT_NGPS__
|
|
if( sn_errno( m_socket ) != EWOULDBLOCK )
|
|
#else
|
|
#ifdef __PLAT_NGC__
|
|
if( num_bytes != SO_EWOULDBLOCK )
|
|
#endif
|
|
#endif
|
|
#endif
|
|
{
|
|
#ifdef NET_DEBUG_MESSAGES
|
|
Dbg_Printf( "Client Receive Error :" );
|
|
#ifdef __PLAT_WN32__
|
|
OutputDebugString( "Client Receive Error!\n" );
|
|
#endif
|
|
ReportError();
|
|
#endif
|
|
}
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
#ifdef __PLAT_NGPS__
|
|
WaitSema( m_receive_semaphore_id );
|
|
#endif // __PLAT_NGPS__
|
|
m_TotalBytesIn += num_bytes;
|
|
if( m_flags & App::mSECURE )
|
|
{
|
|
actual_data_len = num_bytes - sizeof( unsigned short ); // total size - CRC size
|
|
}
|
|
else
|
|
{
|
|
actual_data_len = num_bytes;
|
|
}
|
|
|
|
conn = GetConnectionByAddress( m_server_address.sin_addr.s_addr,
|
|
ntohs( m_server_address.sin_port ));
|
|
if( conn )
|
|
{
|
|
if(( conn->m_read_ptr + actual_data_len ) < ( conn->m_read_buffer + Conn::vREAD_BUFFER_LENGTH ))
|
|
{
|
|
Tmr::Time cur_time;
|
|
|
|
cur_time = Tmr::GetTime();
|
|
if( !validate_and_copy_stream( m_in_packet_buffer, conn->m_read_ptr, num_bytes ))
|
|
{
|
|
#ifdef __PLAT_NGPS__
|
|
SignalSema( m_receive_semaphore_id );
|
|
#endif // __PLAT_NGPS__
|
|
// If the game would like to handle this foreign data, allow it to do so now
|
|
if( m_foreign_handler )
|
|
{
|
|
m_foreign_handler( m_in_packet_buffer, num_bytes, &from_address );
|
|
}
|
|
continue;
|
|
}
|
|
conn->m_read_ptr += actual_data_len;
|
|
conn->GetInboundMetrics()->AddPacket( num_bytes + vUDP_PACKET_OVERHEAD, m_Timestamp );
|
|
if( cur_time > conn->m_last_comm_time )
|
|
{
|
|
conn->m_last_comm_time = cur_time;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
#ifdef __PLAT_WN32__
|
|
char message[1024];
|
|
sprintf( message, "****** %d: Dropped Packet\n", m_Timestamp );
|
|
OutputDebugString( message );
|
|
#endif // __PLAT_WN32__
|
|
}
|
|
}
|
|
else if( !m_connected )
|
|
{
|
|
Conn* foreign_conn;
|
|
|
|
foreign_address = (struct sockaddr_in*) &from_address;
|
|
// Foreign connection. Create temporary connection to process this foreign message
|
|
foreign_conn = NewConnection( foreign_address->sin_addr.s_addr,
|
|
ntohs( foreign_address->sin_port ),
|
|
Conn::mREMOTE |
|
|
Conn::mFOREIGN );
|
|
if( foreign_conn )
|
|
{
|
|
foreign_conn->m_app = this;
|
|
//memcpy( foreign_conn->m_read_buffer, m_read_buffer, actual_data_len );
|
|
if( !validate_and_copy_stream( m_in_packet_buffer, foreign_conn->m_read_ptr, num_bytes ))
|
|
{
|
|
#ifdef __PLAT_NGPS__
|
|
SignalSema( m_receive_semaphore_id );
|
|
#endif // __PLAT_NGPS__
|
|
// If the game would like to handle this foreign data, allow it to do so now
|
|
if( m_foreign_handler )
|
|
{
|
|
m_foreign_handler( m_in_packet_buffer, num_bytes, &from_address );
|
|
}
|
|
delete foreign_conn;
|
|
continue;
|
|
}
|
|
foreign_conn->m_read_ptr = foreign_conn->m_read_buffer + actual_data_len;
|
|
foreign_conn->Invalidate();
|
|
}
|
|
}
|
|
#ifdef __PLAT_NGPS__
|
|
SignalSema( m_receive_semaphore_id );
|
|
#endif // __PLAT_NGPS__
|
|
}
|
|
|
|
} while( num_bytes > 0 );
|
|
}
|
|
|
|
#ifdef USE_ALIASES
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
|
|
void Client::ClearAliasTable( void )
|
|
{
|
|
int i;
|
|
|
|
for( i = 0; i < vNUM_ALIASES; i++ )
|
|
{
|
|
m_alias_table[i].m_Id = 0;
|
|
m_alias_table[i].m_Expiration = -1;
|
|
}
|
|
}
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
|
|
unsigned char Client::GetObjectAlias( int obj_id, int cur_time )
|
|
{
|
|
int hash, i;
|
|
bool quit;
|
|
|
|
hash = obj_id & 0xff; // mask off all but lowest byte and use as a hash into the table
|
|
hash %= vNUM_ALIASES;
|
|
quit = false;
|
|
i = hash;
|
|
do
|
|
{
|
|
if( m_alias_table[i].m_Id == obj_id )
|
|
{
|
|
if( cur_time > m_alias_table[i].m_Expiration )
|
|
{
|
|
return (unsigned char) vNO_ALIAS;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
i = ( i + 1 ) % vNUM_ALIASES;
|
|
if( i == hash ) // complete revolution
|
|
{
|
|
break;
|
|
}
|
|
} while( !quit );
|
|
|
|
return (unsigned char) vNO_ALIAS;
|
|
}
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
|
|
unsigned char Client::NewObjectAlias( int obj_id, int cur_time, bool expires )
|
|
{
|
|
int hash, i;
|
|
bool quit;
|
|
|
|
|
|
|
|
hash = obj_id & 0xff; // mask off all but lowest byte and use as a hash into the table
|
|
hash %= vNUM_ALIASES;
|
|
quit = false;
|
|
i = hash;
|
|
do
|
|
{
|
|
if( m_alias_table[i].m_Id == obj_id )
|
|
{
|
|
m_alias_table[i].m_Expiration = cur_time + vALIAS_DURATION;
|
|
return (unsigned char) i;
|
|
}
|
|
else if(( cur_time - m_alias_table[i].m_Expiration ) > vALIAS_DURATION )
|
|
{
|
|
m_alias_table[i].m_Id = obj_id;
|
|
m_alias_table[i].m_Expiration = cur_time + vALIAS_DURATION;
|
|
return (unsigned char) i;
|
|
}
|
|
i = ( i + 1 ) % vNUM_ALIASES;
|
|
if( i == hash ) // complete revolution
|
|
{
|
|
break;
|
|
}
|
|
} while( !quit );
|
|
|
|
// no free aliases
|
|
return (unsigned char) vNO_ALIAS;
|
|
}
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
|
|
int Client::GetObjectId( unsigned char alias )
|
|
{
|
|
|
|
|
|
//Dbg_MsgAssert(( m_LatestPacketStamp <= m_alias_table[ alias ].m_Expiration ), "Server using expired alias\n" );
|
|
return m_alias_table[ alias ].m_Id;
|
|
}
|
|
|
|
#endif
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
|
|
Client::Client( int flags )
|
|
: App( flags )
|
|
{
|
|
m_send_network_data_task = new Tsk::Task< App > ( send_network_data, *this,
|
|
Tsk::BaseTask::Node::vLOGIC_TASK_PRIORITY_CLIENT_SEND_NETWORK_DATA );
|
|
}
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
|
|
} // namespace Net
|