You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1035 lines
26 KiB
1035 lines
26 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
//============================================================================= |
|
#if !defined( _X360 ) |
|
#define FD_SETSIZE 1024 |
|
#endif |
|
|
|
#include <assert.h> |
|
#include "winlite.h" |
|
#if !defined( _X360 ) |
|
#include "winsock.h" |
|
#else |
|
#include "winsockx.h" |
|
#endif |
|
#include "msgbuffer.h" |
|
#include "socket.h" |
|
#include "inetapi.h" |
|
#include "tier0/vcrmode.h" |
|
|
|
#include <VGUI/IVGui.h> |
|
|
|
#if defined( _X360 ) |
|
#include "xbox/xbox_win32stubs.h" |
|
#endif |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: All socket I/O occurs on a thread |
|
//----------------------------------------------------------------------------- |
|
class CSocketThread |
|
{ |
|
public: |
|
typedef struct threadsocket_s |
|
{ |
|
struct threadsocket_s *next; |
|
CSocket *socket; |
|
} threadsocket_t; |
|
|
|
// Construction |
|
CSocketThread( void ); |
|
virtual ~CSocketThread( void ); |
|
|
|
// Sockets add/remove themselves via their constructor |
|
virtual void AddSocketToThread( CSocket *socket ); |
|
virtual void RemoveSocketFromThread( CSocket *socket ); |
|
|
|
// Lock changes to socket list, etc. |
|
virtual void Lock( void ); |
|
// Unlock socket list, etc. |
|
virtual void Unlock( void ); |
|
|
|
// Retrieve handle to shutdown event |
|
virtual HANDLE GetShutdownHandle( void ); |
|
// Get head of socket list |
|
virtual threadsocket_t *GetSocketList( void ); |
|
|
|
// Sample clock for socket thread |
|
virtual float GetClock( void ); |
|
|
|
private: |
|
// Initialize the clock |
|
void InitTimer( void ); |
|
|
|
private: |
|
// Critical section used for synchronizing access to socket list |
|
CRITICAL_SECTION cs; |
|
// List of sockets we are listening on |
|
threadsocket_t *m_pSocketList; |
|
// Thread handle |
|
HANDLE m_hThread; |
|
// Thread id |
|
DWORD m_nThreadId; |
|
// Event to set when we want to tell the thread to shut itself down |
|
HANDLE m_hShutdown; |
|
|
|
// High performance clock frequency |
|
double m_dClockFrequency; |
|
// Current accumulated time |
|
double m_dCurrentTime; |
|
// How many bits to shift raw 64 bit sample count by |
|
int m_nTimeSampleShift; |
|
// Previous 32 bit sample count |
|
unsigned int m_uiPreviousTime; |
|
}; |
|
|
|
// Singleton handler |
|
static CSocketThread *GetSocketThread() |
|
{ |
|
static CSocketThread g_SocketThread; |
|
return &g_SocketThread; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Main winsock processing thread |
|
// Input : threadobject - |
|
// Output : static DWORD WINAPI |
|
//----------------------------------------------------------------------------- |
|
static DWORD WINAPI SocketThreadFunc( LPVOID threadobject ) |
|
{ |
|
// Get pointer to CSocketThread object |
|
CSocketThread *socketthread = ( CSocketThread * )threadobject; |
|
assert( socketthread ); |
|
if ( !socketthread ) |
|
{ |
|
return 0; |
|
} |
|
|
|
// Keep looking for data until shutdown event is triggered |
|
while ( 1 ) |
|
{ |
|
// List of sockets |
|
CSocketThread::threadsocket_t *sockets; |
|
// file descriptor set for sockets |
|
fd_set fdset; |
|
// number of sockets with messages ready |
|
int number; |
|
// number of sockets added to fd_set |
|
int count; |
|
|
|
// Check for shutdown event |
|
if ( WAIT_OBJECT_0 == VCRHook_WaitForSingleObject( socketthread->GetShutdownHandle(), 0 ) ) |
|
{ |
|
break; |
|
} |
|
|
|
// Clear the set |
|
FD_ZERO(&fdset); |
|
|
|
// No changes to list right now |
|
socketthread->Lock(); |
|
|
|
// Add all active sockets to the fdset |
|
count = 0; |
|
for ( sockets = socketthread->GetSocketList(); sockets; sockets = sockets->next ) |
|
{ |
|
FD_SET( static_cast<u_int>( sockets->socket->GetSocketNumber() ), &fdset ); |
|
count = max( count, sockets->socket->GetSocketNumber() ); |
|
} |
|
|
|
// Done |
|
socketthread->Unlock(); |
|
|
|
if ( count ) |
|
{ |
|
struct timeval tv; |
|
tv.tv_sec = 0; |
|
tv.tv_usec = 100000; // 100 millisecond == 100000 usec |
|
|
|
// Block for 100000 usec, or until a message is in the queue |
|
number = select( count + 1, &fdset, NULL, NULL, &tv ); |
|
#if !defined( NO_VCR ) |
|
VCRGenericValue( "", &number, sizeof( number ) ); |
|
#endif |
|
if ( number > 0 ) |
|
{ |
|
// Iterate through socket list and see who has data waiting // |
|
// No changes to list right now |
|
socketthread->Lock(); |
|
|
|
// Check FD_SET for incoming network messages |
|
for ( sockets = socketthread->GetSocketList(); sockets; sockets = sockets->next ) |
|
{ |
|
bool bSet = FD_ISSET( sockets->socket->GetSocketNumber(), &fdset ); |
|
#if !defined( NO_VCR ) |
|
VCRGenericValue( "", &bSet, sizeof( bSet ) ); |
|
#endif |
|
if ( bSet ) |
|
{ |
|
// keep reading as long as there is data on the socket |
|
while (sockets->socket->ReceiveData()) |
|
{ |
|
} |
|
} |
|
} |
|
|
|
// Done |
|
socketthread->Unlock(); |
|
} |
|
} |
|
|
|
// no need to sleep here, much better let it sleep in the select |
|
} |
|
|
|
ExitThread( 0 ); |
|
|
|
return 0; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Construction |
|
//----------------------------------------------------------------------------- |
|
CSocketThread::CSocketThread( void ) |
|
{ |
|
InitTimer(); |
|
|
|
m_pSocketList = NULL; |
|
|
|
InitializeCriticalSection( &cs ); |
|
|
|
m_hShutdown = CreateEvent( NULL, TRUE, FALSE, NULL ); |
|
assert( m_hShutdown ); |
|
|
|
m_hThread = 0; |
|
m_nThreadId = 0; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CSocketThread::~CSocketThread( void ) |
|
{ |
|
Lock(); |
|
if ( m_hThread ) |
|
{ |
|
SetEvent( m_hShutdown ); |
|
Sleep( 2 ); |
|
TerminateThread( m_hThread, 0 ); |
|
} |
|
Unlock(); |
|
|
|
// Kill the socket |
|
//!! need to validate this line |
|
// assert( !m_pSocketList ); |
|
|
|
if ( m_hThread ) |
|
{ |
|
CloseHandle( m_hThread ); |
|
} |
|
|
|
CloseHandle( m_hShutdown ); |
|
|
|
DeleteCriticalSection( &cs ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Initialize socket thread timer |
|
//----------------------------------------------------------------------------- |
|
void CSocketThread::InitTimer( void ) |
|
{ |
|
BOOL success; |
|
LARGE_INTEGER PerformanceFreq; |
|
unsigned int lowpart, highpart; |
|
|
|
// Start clock at zero |
|
m_dCurrentTime = 0.0; |
|
|
|
success = QueryPerformanceFrequency( &PerformanceFreq ); |
|
assert( success ); |
|
|
|
// get 32 out of the 64 time bits such that we have around |
|
// 1 microsecond resolution |
|
lowpart = (unsigned int)PerformanceFreq.LowPart; |
|
highpart = (unsigned int)PerformanceFreq.HighPart; |
|
|
|
m_nTimeSampleShift = 0; |
|
|
|
while ( highpart || ( lowpart > 2000000.0 ) ) |
|
{ |
|
m_nTimeSampleShift++; |
|
lowpart >>= 1; |
|
lowpart |= (highpart & 1) << 31; |
|
highpart >>= 1; |
|
} |
|
|
|
m_dClockFrequency = 1.0 / (double)lowpart; |
|
|
|
// Get initial sample |
|
unsigned int temp; |
|
LARGE_INTEGER PerformanceCount; |
|
QueryPerformanceCounter( &PerformanceCount ); |
|
if ( !m_nTimeSampleShift ) |
|
{ |
|
temp = (unsigned int)PerformanceCount.LowPart; |
|
} |
|
else |
|
{ |
|
// Rotate counter to right by m_nTimeSampleShift places |
|
temp = ((unsigned int)PerformanceCount.LowPart >> m_nTimeSampleShift) | |
|
((unsigned int)PerformanceCount.HighPart << (32 - m_nTimeSampleShift)); |
|
} |
|
|
|
// Set first time stamp |
|
m_uiPreviousTime = temp; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Thread local timer function |
|
// Output : float |
|
//----------------------------------------------------------------------------- |
|
float CSocketThread::GetClock( void ) |
|
{ |
|
LARGE_INTEGER PerformanceCount; |
|
unsigned int temp, t2; |
|
double time; |
|
|
|
// Get sample counter |
|
QueryPerformanceCounter( &PerformanceCount ); |
|
|
|
if ( !m_nTimeSampleShift ) |
|
{ |
|
temp = (unsigned int)PerformanceCount.LowPart; |
|
} |
|
else |
|
{ |
|
// Rotate counter to right by m_nTimeSampleShift places |
|
temp = ((unsigned int)PerformanceCount.LowPart >> m_nTimeSampleShift) | |
|
((unsigned int)PerformanceCount.HighPart << (32 - m_nTimeSampleShift)); |
|
} |
|
|
|
// check for turnover or backward time |
|
if ( ( temp <= m_uiPreviousTime ) && |
|
( ( m_uiPreviousTime - temp ) < 0x10000000) ) |
|
{ |
|
m_uiPreviousTime = temp; // so we can't get stuck |
|
} |
|
else |
|
{ |
|
// gap in performance clocks |
|
t2 = temp - m_uiPreviousTime; |
|
|
|
// Convert to time using frequencey of clock |
|
time = (double)t2 * m_dClockFrequency; |
|
|
|
// Remember old time |
|
m_uiPreviousTime = temp; |
|
|
|
// Increment clock |
|
m_dCurrentTime += time; |
|
} |
|
#if !defined( NO_VCR ) |
|
VCRGenericValue( "", &m_dCurrentTime, sizeof( m_dCurrentTime ) ); |
|
#endif |
|
// Convert to float |
|
return (float)m_dCurrentTime; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Returns handle of shutdown event |
|
// Output : HANDLE |
|
//----------------------------------------------------------------------------- |
|
HANDLE CSocketThread::GetShutdownHandle( void ) |
|
{ |
|
return m_hShutdown; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Returns head of socket list |
|
// Output : CSocketThread::threadsocket_t |
|
//----------------------------------------------------------------------------- |
|
CSocketThread::threadsocket_t *CSocketThread::GetSocketList( void ) |
|
{ |
|
return m_pSocketList; |
|
} |
|
|
|
int socketCount = 0; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Locks object and adds socket to thread |
|
//----------------------------------------------------------------------------- |
|
void CSocketThread::AddSocketToThread( CSocket *socket ) |
|
{ |
|
// create the thread if it isn't there |
|
if (!m_hThread) |
|
{ |
|
m_hThread = VCRHook_CreateThread( NULL, 0, SocketThreadFunc, (void *)this, 0, &m_nThreadId ); |
|
assert( m_hThread ); |
|
} |
|
|
|
socketCount++; |
|
|
|
threadsocket_t *p = new threadsocket_t; |
|
p->socket = socket; |
|
|
|
Lock(); |
|
p->next = m_pSocketList; |
|
m_pSocketList = p; |
|
Unlock(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Locks list and removes specified socket from thread |
|
//----------------------------------------------------------------------------- |
|
void CSocketThread::RemoveSocketFromThread( CSocket *socket ) |
|
{ |
|
if (!m_hThread) |
|
return; |
|
|
|
socketCount--; |
|
|
|
Lock(); |
|
if ( m_pSocketList ) |
|
{ |
|
threadsocket_t *p, *n; |
|
p = m_pSocketList; |
|
if ( p->socket == socket ) |
|
{ |
|
m_pSocketList = m_pSocketList->next; |
|
delete p; |
|
} |
|
else |
|
{ |
|
while ( p->next ) |
|
{ |
|
n = p->next; |
|
if ( n->socket == socket ) |
|
{ |
|
p->next = n->next; |
|
delete n; |
|
break; |
|
} |
|
p = n; |
|
} |
|
} |
|
} |
|
Unlock(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CSocketThread::Lock( void ) |
|
{ |
|
VCRHook_EnterCriticalSection( &cs ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CSocketThread::Unlock( void ) |
|
{ |
|
LeaveCriticalSection( &cs ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Constructs a message handler for incoming socket messages |
|
//----------------------------------------------------------------------------- |
|
CMsgHandler::CMsgHandler( HANDLERTYPE type, void *typeinfo /*=NULL*/ ) |
|
{ |
|
m_Type = type; |
|
m_pNext = NULL; |
|
|
|
// Assume no socket |
|
SetSocket( NULL ); |
|
|
|
// Assume no special checking |
|
m_ByteCode = 0; |
|
m_szString[ 0 ] = 0; |
|
|
|
switch ( m_Type ) |
|
{ |
|
default: |
|
case MSGHANDLER_ALL: |
|
break; |
|
case MSGHANDLER_BYTECODE: |
|
m_ByteCode = *(unsigned char *)typeinfo; |
|
break; |
|
case MSGHANDLER_STRING: |
|
strcpy( m_szString, (char *)typeinfo ); |
|
break; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CMsgHandler::~CMsgHandler( void ) |
|
{ |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Default message handler for received messages |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CMsgHandler::Process( netadr_t *from, CMsgBuffer *msg ) |
|
{ |
|
// Swallow message by default |
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Check for special handling |
|
// Input : *from - |
|
// *msg - |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CMsgHandler::ProcessMessage( netadr_t *from, CMsgBuffer *msg ) |
|
{ |
|
bool bret = false; |
|
unsigned char ch; |
|
const char *str; |
|
|
|
// Crack bytecode or string code |
|
switch( m_Type ) |
|
{ |
|
case MSGHANDLER_BYTECODE: |
|
msg->Push(); |
|
ch = (unsigned char)msg->ReadByte(); |
|
msg->Pop(); |
|
if ( ch == m_ByteCode ) |
|
{ |
|
bret = Process( from, msg ); |
|
} |
|
break; |
|
case MSGHANDLER_STRING: |
|
msg->Push(); |
|
str = msg->ReadString(); |
|
msg->Pop(); |
|
if ( str && str[ 0 ] && !stricmp( m_szString, str ) ) |
|
{ |
|
bret = Process( from, msg ); |
|
} |
|
break; |
|
default: |
|
case MSGHANDLER_ALL: |
|
bret = Process( from, msg ); |
|
break; |
|
} |
|
|
|
return bret; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Get next in chain of handlers |
|
//----------------------------------------------------------------------------- |
|
CMsgHandler *CMsgHandler::GetNext( void ) const |
|
{ |
|
return m_pNext; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Set next in handler chain |
|
// Input : *next - |
|
//----------------------------------------------------------------------------- |
|
void CMsgHandler::SetNext( CMsgHandler *next ) |
|
{ |
|
m_pNext = next; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Get underlying socket object |
|
// Output : CSocket |
|
//----------------------------------------------------------------------------- |
|
CSocket *CMsgHandler::GetSocket( void ) const |
|
{ |
|
return m_pSocket; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Set underlying socket object |
|
// Input : *socket - |
|
//----------------------------------------------------------------------------- |
|
void CMsgHandler::SetSocket( CSocket *socket ) |
|
{ |
|
m_pSocket = socket; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Creates a non-blocking, broadcast capable, UDP socket. If port is |
|
// specified, binds it to listen on that port, otherwise, chooses a random port. |
|
//----------------------------------------------------------------------------- |
|
CSocket::CSocket( const char *socketname, int port /*= -1*/ ) : m_SendBuffer(socketname) |
|
{ |
|
struct sockaddr_in address; |
|
unsigned long _true = 1; |
|
int i = 1; |
|
|
|
m_pSocketName = socketname; |
|
|
|
m_bValid = false; |
|
m_bResolved = false; |
|
m_pMessageHandlers = NULL; |
|
m_nUserData = 0; |
|
m_bBroadcastSend = false; |
|
m_iTotalPackets = 0; |
|
m_iCurrentPackets = 0; |
|
m_iRetries = 0; |
|
|
|
m_pBufferCS = new CRITICAL_SECTION; |
|
InitializeCriticalSection((CRITICAL_SECTION *)m_pBufferCS); |
|
|
|
// ensure the socketthread singleton has been created |
|
GetSocketThread(); |
|
|
|
// Set up the socket |
|
m_Socket = socket( PF_INET, SOCK_DGRAM, IPPROTO_UDP ); |
|
if ( m_Socket == -1 ) |
|
{ |
|
//int err = WSAGetLastError(); |
|
// WSANOTINITIALISED |
|
return; |
|
} |
|
|
|
// Set it to non-blocking |
|
if ( ioctlsocket ( m_Socket, FIONBIO, &_true ) == -1 ) |
|
{ |
|
closesocket( m_Socket ); |
|
m_Socket = 0; |
|
return; |
|
} |
|
|
|
// Allow broadcast packets |
|
if ( setsockopt( m_Socket, SOL_SOCKET, SO_BROADCAST, (char *)&i, sizeof(i) ) == -1 ) |
|
{ |
|
closesocket( m_Socket ); |
|
m_Socket = 0; |
|
return; |
|
} |
|
|
|
// LATER: Support specifying interface name |
|
//if (!net_interface || !net_interface[0] || !stricmp(net_interface, "localhost")) |
|
address.sin_addr.s_addr = INADDR_ANY; |
|
//else |
|
// NET_StringToSockaddr (net_interface, (struct sockaddr *)&address); |
|
|
|
if ( port == -1 ) |
|
{ |
|
address.sin_port = 0; |
|
} |
|
else |
|
{ |
|
address.sin_port = htons( (short)port ); |
|
} |
|
|
|
address.sin_family = AF_INET; |
|
|
|
// only bind if we're required to be on a certain port |
|
if ( address.sin_port > 0) |
|
{ |
|
// Bind the socket to specified port |
|
if ( bind( m_Socket, (struct sockaddr *)&address, sizeof(address) ) == -1 ) |
|
{ |
|
closesocket (m_Socket); |
|
m_Socket = 0; |
|
return; |
|
} |
|
} |
|
|
|
// Mark as valid |
|
m_bValid = true; |
|
|
|
// Only add valid sockets to thread |
|
GetSocketThread()->AddSocketToThread( this ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CSocket::~CSocket( void ) |
|
{ |
|
DeleteCriticalSection((CRITICAL_SECTION *)m_pBufferCS); |
|
delete (CRITICAL_SECTION *)m_pBufferCS; |
|
|
|
// Try to remove socket from thread |
|
GetSocketThread()->RemoveSocketFromThread( this ); |
|
|
|
// Ask message handlers to remove selves? |
|
if ( m_bValid ) |
|
{ |
|
::shutdown(m_Socket, 0x01); |
|
::shutdown(m_Socket, 0x02); |
|
closesocket( m_Socket ); |
|
m_Socket = 0; |
|
} |
|
|
|
// Remove handlers |
|
CMsgHandler *handler = m_pMessageHandlers; |
|
while ( handler ) |
|
{ |
|
RemoveMessageHandler( handler ); |
|
delete handler; |
|
handler = m_pMessageHandlers; |
|
} |
|
m_pMessageHandlers = NULL; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Add hander to head of chain |
|
// Input : *handler - |
|
//----------------------------------------------------------------------------- |
|
void CSocket::AddMessageHandler( CMsgHandler *handler ) |
|
{ |
|
handler->SetNext( m_pMessageHandlers ); |
|
m_pMessageHandlers = handler; |
|
|
|
// Set the socket pointer |
|
handler->SetSocket( this ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Removed indicated handler |
|
// Input : *handler - |
|
//----------------------------------------------------------------------------- |
|
void CSocket::RemoveMessageHandler( CMsgHandler *handler ) |
|
{ |
|
if ( !handler ) |
|
{ |
|
return; |
|
} |
|
|
|
CMsgHandler *list = m_pMessageHandlers; |
|
if ( list == handler ) |
|
{ |
|
m_pMessageHandlers = m_pMessageHandlers->GetNext(); |
|
return; |
|
} |
|
|
|
while ( list ) |
|
{ |
|
if ( list->GetNext() == handler ) |
|
{ |
|
list->SetNext( handler->GetNext() ); |
|
handler->SetNext( NULL ); |
|
return; |
|
} |
|
list = list->GetNext(); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Send message to specified address |
|
// Input : *to - |
|
// Output : int - number of bytes sent |
|
//----------------------------------------------------------------------------- |
|
int CSocket::SendMessage( netadr_t *to, CMsgBuffer *msg /*= NULL*/ ) |
|
{ |
|
m_bBroadcastSend = false; |
|
m_ToAddress = *to; |
|
|
|
if ( !m_bValid ) |
|
{ |
|
return 0; |
|
} |
|
|
|
if ( !msg ) |
|
{ |
|
msg = GetSendBuffer(); |
|
} |
|
|
|
struct sockaddr addr; |
|
net->NetAdrToSockAddr ( to, &addr ); |
|
|
|
int bytessent = sendto( m_Socket, (const char *)msg->GetData(), msg->GetCurSize(), 0, &addr, sizeof( addr ) ); |
|
if ( bytessent == msg->GetCurSize() ) |
|
{ |
|
return bytessent; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Send broadcast message on specified port |
|
// Input : port - |
|
// Output : int - number of bytes sent |
|
//----------------------------------------------------------------------------- |
|
int CSocket::Broadcast( int port, CMsgBuffer *msg /*= NULL*/ ) |
|
{ |
|
m_bBroadcastSend = true; |
|
memset( &m_ToAddress, 0, sizeof( m_ToAddress ) ); |
|
|
|
if ( !m_bValid ) |
|
{ |
|
return 0; |
|
} |
|
|
|
if ( !msg ) |
|
{ |
|
msg = GetSendBuffer(); |
|
} |
|
|
|
struct sockaddr addr; |
|
netadr_t to; |
|
|
|
to.port = (unsigned short)htons( (unsigned short)port ); |
|
to.type = NA_BROADCAST; |
|
|
|
net->NetAdrToSockAddr ( &to, &addr ); |
|
|
|
int bytessent = sendto( m_Socket, (const char *)msg->GetData(), msg->GetCurSize(), 0, &addr, sizeof( addr ) ); |
|
if ( bytessent == msg->GetCurSize() ) |
|
{ |
|
return bytessent; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Retrieve internal message buffer |
|
// Output : CMsgBuffer |
|
//----------------------------------------------------------------------------- |
|
CMsgBuffer *CSocket::GetSendBuffer( void ) |
|
{ |
|
return &m_SendBuffer; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Called once per frame (outside of the socket thread) to allow socket to receive incoming messages |
|
// and route them as appropriate |
|
//----------------------------------------------------------------------------- |
|
void CSocket::Frame( void ) |
|
{ |
|
// No data waiting |
|
if (!m_MsgBuffers.Size()) |
|
return; |
|
|
|
VCRHook_EnterCriticalSection( (CRITICAL_SECTION *)m_pBufferCS ); |
|
|
|
// pass up all the receive buffers |
|
for (int i = 0; i < m_MsgBuffers.Size(); i++) |
|
{ |
|
// See if there's a handler for this message |
|
CMsgHandler *handler = m_pMessageHandlers; |
|
netadr_t addr = m_MsgBuffers[i].GetNetAddress(); |
|
while ( handler ) |
|
{ |
|
// Swallow message? |
|
if ( handler->ProcessMessage( &addr, &m_MsgBuffers[i] ) ) |
|
break; |
|
|
|
handler = handler->GetNext(); |
|
} |
|
} |
|
|
|
// free the buffer list |
|
m_MsgBuffers.RemoveAll(); |
|
|
|
LeaveCriticalSection((CRITICAL_SECTION *)m_pBufferCS); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Is socket set up correctly |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CSocket::IsValid( void ) const |
|
{ |
|
return m_bValid; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : float |
|
//----------------------------------------------------------------------------- |
|
float CSocket::GetClock( void ) |
|
{ |
|
return GetSocketThread()->GetClock(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Resolves the socket address |
|
// Output : const netadr_t |
|
//----------------------------------------------------------------------------- |
|
const netadr_t *CSocket::GetAddress( void ) |
|
{ |
|
assert( m_bValid ); |
|
|
|
if ( !m_bResolved ) |
|
{ |
|
m_bResolved = true; |
|
// Determine resulting socket address |
|
net->GetSocketAddress( m_Socket, &m_Address ); |
|
} |
|
|
|
return &m_Address; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Let the user store/retrieve a 32 bit value |
|
// Input : userData - |
|
//----------------------------------------------------------------------------- |
|
void CSocket::SetUserData( unsigned int userData ) |
|
{ |
|
m_nUserData = userData; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Let the user store/retrieve a 32 bit value |
|
// Output : unsigned int |
|
//----------------------------------------------------------------------------- |
|
unsigned int CSocket::GetUserData(void ) const |
|
{ |
|
return m_nUserData; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Returns the underlying socket id number for setting up the fd_set |
|
//----------------------------------------------------------------------------- |
|
int CSocket::GetSocketNumber( void ) const |
|
{ |
|
return m_Socket; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Called once FD_ISSET is detected |
|
//----------------------------------------------------------------------------- |
|
bool CSocket::ReceiveData( void ) |
|
{ |
|
// Check for data |
|
struct sockaddr from; |
|
int fromlen; |
|
int bytes; |
|
unsigned char buffer[ CMsgBuffer::NET_MAXMESSAGE ]; |
|
|
|
fromlen = sizeof( from ); |
|
bytes = VCRHook_recvfrom( m_Socket, (char *)buffer, CMsgBuffer::NET_MAXMESSAGE, 0, (struct sockaddr *)&from, &fromlen ); |
|
|
|
//int port = ntohs( ((struct sockaddr_in *)&from)->sin_port); |
|
|
|
// Socket error |
|
if ( bytes == -1 ) |
|
{ |
|
return false; |
|
} |
|
|
|
// Too much data, ignore it |
|
if ( bytes >= CMsgBuffer::NET_MAXMESSAGE ) |
|
{ |
|
return false; |
|
} |
|
|
|
// Packets must have -1 tag |
|
if ( bytes < 4 ) |
|
{ |
|
return false; |
|
} |
|
|
|
// Mark the time no matter what since FD_SET said there was data and we should have it now |
|
float recvTime = GetClock(); |
|
|
|
if( *(int *)&buffer[0] == -2 ) // its a split packet :) |
|
{ |
|
int curPacket=0,offset=0; |
|
SPLITPACKET *pak =reinterpret_cast<SPLITPACKET *>(&buffer[0]); |
|
|
|
if(m_iTotalPackets==0) // this is the first in the series |
|
{ |
|
m_iTotalPackets = (pak->packetID & 0x0f); |
|
m_iSeqNo = pak->sequenceNumber; |
|
m_iRetries=0; |
|
|
|
m_iCurrentPackets=1;// packet numbers start at zero, total is the total number (i.e =2 for packet 0,1) |
|
curPacket= (pak->packetID & 0xf0)>>4; |
|
} |
|
else if (m_iSeqNo == pak->sequenceNumber) |
|
{ |
|
m_iCurrentPackets++; |
|
curPacket= (pak->packetID & 0xf0)>>4; |
|
} |
|
else |
|
{ |
|
m_iRetries++; |
|
if(m_iRetries>MAX_RETRIES) // make sure we give up eventually on fragments |
|
{ |
|
m_iTotalPackets=0; |
|
} |
|
return false; // TODO: add support for multiple fragments at one time? |
|
} |
|
|
|
|
|
if(curPacket==0) |
|
{ |
|
offset=4; // strip the "-1" at the front of the first packet |
|
} |
|
|
|
if(curPacket<MAX_PACKETS) // just in case... |
|
{ |
|
m_CurPacket[curPacket].Clear(); // new packet, clear the buffer out |
|
m_CurPacket[curPacket].WriteBuf(bytes-offset-sizeof(SPLITPACKET),&buffer[offset+sizeof(SPLITPACKET)]); |
|
} |
|
|
|
if(m_iCurrentPackets==m_iTotalPackets) |
|
{ |
|
|
|
VCRHook_EnterCriticalSection((CRITICAL_SECTION *)m_pBufferCS); |
|
|
|
// Get from address |
|
netadr_t addr; |
|
net->SockAddrToNetAdr( &from, &addr ); |
|
|
|
// append to the receive buffer |
|
int idx = m_MsgBuffers.AddToTail(); |
|
CMsgBuffer &msgBuffer = m_MsgBuffers[idx]; |
|
|
|
msgBuffer.Clear(); |
|
|
|
// copy all our fragments together |
|
for(int i=0;i<m_iTotalPackets;i++) |
|
{ |
|
// buffer must be big enough for us to use, that is where the data originally came from :) |
|
m_CurPacket[i].ReadBuf(m_CurPacket[i].GetCurSize(),buffer); |
|
msgBuffer.WriteBuf(m_CurPacket[i].GetCurSize(),buffer); |
|
} |
|
msgBuffer.SetTime(recvTime); |
|
msgBuffer.SetNetAddress(addr); |
|
|
|
LeaveCriticalSection((CRITICAL_SECTION *)m_pBufferCS); |
|
|
|
m_iTotalPackets = 0; // we have collected all the fragments for |
|
//this packet, we can start on a new one now |
|
|
|
} |
|
|
|
|
|
} |
|
else if ( *(int *)&buffer[0] == -1 ) // Must have 255,255,255,255 oob tag |
|
{ |
|
/* |
|
// Fake packet loss |
|
if ( rand() % 1000 < 200 ) |
|
return; |
|
*/ |
|
|
|
VCRHook_EnterCriticalSection((CRITICAL_SECTION *)m_pBufferCS); |
|
|
|
// Get from address |
|
netadr_t addr; |
|
net->SockAddrToNetAdr( &from, &addr ); |
|
|
|
// append to the receive buffer |
|
int idx = m_MsgBuffers.AddToTail(); |
|
CMsgBuffer &msgBuffer = m_MsgBuffers[idx]; |
|
|
|
// Copy payload minus the -1 tag |
|
msgBuffer.Clear(); |
|
msgBuffer.WriteBuf( bytes - 4, &buffer[ 4 ] ); |
|
msgBuffer.SetTime(recvTime); |
|
msgBuffer.SetNetAddress(addr); |
|
|
|
LeaveCriticalSection((CRITICAL_SECTION *)m_pBufferCS); |
|
} |
|
|
|
return true; |
|
}
|
|
|