/* * TV_Lobby_SendClientMessages */ static void TV_Lobby_SendClientMessages( void ) { int i; client_t *client; // send a message to each connected client for( i = 0, client = tvs.clients; i < tv_maxclients->integer; i++, client++ ) { if( client->state != CS_SPAWNED ) continue; if( client->relay ) continue; if( !TV_Lobby_SendClientDatagram( client ) ) { Com_Printf( "%s" S_COLOR_WHITE ": Error sending message: %s\n", client->name, NET_ErrorString() ); if( client->reliable ) { TV_Downstream_DropClient( client, DROP_TYPE_GENERAL, "Error sending message: %s\n", NET_ErrorString() ); } } } }
/* * TV_Downstream_SendClientsFragments */ qboolean TV_Downstream_SendClientsFragments( void ) { client_t *client; int i; qboolean remaining = qfalse; // send a message to each connected client for( i = 0, client = tvs.clients; i < tv_maxclients->integer; i++, client++ ) { if( client->state == CS_FREE || client->state == CS_ZOMBIE ) continue; if( !client->netchan.unsentFragments ) continue; if( !Netchan_TransmitNextFragment( &client->netchan ) ) { Com_Printf( "%s" S_COLOR_WHITE ": Error sending fragment: %s\n", client->name, NET_ErrorString() ); if( client->reliable ) { TV_Downstream_DropClient( client, DROP_TYPE_GENERAL, "Error sending fragment: %s\n", NET_ErrorString() ); } continue; } if( client->netchan.unsentFragments ) remaining = qtrue; } return remaining; }
/* * TV_Downstream_AddServerCommand * * The given command will be transmitted to the client, and is guaranteed to * not have future snapshot_t executed before it is executed */ void TV_Downstream_AddServerCommand( client_t *client, const char *cmd ) { int index; unsigned int i; assert( client ); assert( cmd && strlen( cmd ) ); if( !cmd || !cmd[0] || !strlen( cmd ) ) return; // ch : To avoid overflow of messages from excessive amount of configstrings // we batch them here. On incoming "cs" command, we'll trackback the queue // to find a pending "cs" command that has space in it. If we'll find one, // we'll batch this there, if not, we'll create a new one. if( !strncmp( cmd, "cs ", 3 ) ) { // length of the index/value (leave room for one space and null char) size_t len = strlen( cmd ) - 1; for( i = client->reliableSequence; i > client->reliableSent; i-- ) { size_t otherLen; char *otherCmd; otherCmd= client->reliableCommands[i & ( MAX_RELIABLE_COMMANDS - 1)]; if( !strncmp( otherCmd, "cs ", 3 ) ) { otherLen = strlen( otherCmd ); // is there any room? (should check for sizeof client->reliableCommands[0]?) if( (otherLen + len) < MAX_STRING_CHARS ) { // yahoo, put it in here Q_strncatz( otherCmd, cmd + 2, MAX_STRING_CHARS - 1 ); return; } } } } client->reliableSequence++; // if we would be losing an old command that hasn't been acknowledged, we must drop the connection // we check == instead of >= so a broadcast print added by SV_DropClient() doesn't cause a recursive drop client if( client->reliableSequence - client->reliableAcknowledge == MAX_RELIABLE_COMMANDS + 1 ) { //Com_Printf( "===== pending server commands =====\n" ); for( i = client->reliableAcknowledge + 1; i <= client->reliableSequence; i++ ) { Com_DPrintf( "cmd %5d: %s\n", i, client->reliableCommands[i & ( MAX_RELIABLE_COMMANDS-1 )] ); } Com_DPrintf( "cmd %5d: %s\n", i, cmd ); TV_Downstream_DropClient( client, DROP_TYPE_GENERAL, "Server command overflow" ); return; } index = client->reliableSequence & ( MAX_RELIABLE_COMMANDS - 1 ); Q_strncpyz( client->reliableCommands[index], cmd, sizeof( client->reliableCommands[index] ) ); }
/* * TV_Module_DropClient */ static void TV_Module_DropClient( relay_t *relay, int numClient, int type, const char *message ) { client_t *client; if( !relay ) { Com_Printf( "Error: TV_Module_DropClient: Relay not set\n" ); return; } if( numClient < 0 || numClient >= tv_maxclients->integer ) TV_Relay_Error( relay, "TV_Module_DropClient: Invalid numClient" ); client = &tvs.clients[numClient]; if( client->state == CS_FREE || client->state == CS_ZOMBIE || client->relay != relay ) TV_Relay_Error( relay, "TV_Module_DropClient: Invalid client" ); if( message ) TV_Downstream_DropClient( client, type, "%s", message ); else TV_Downstream_DropClient( client, type, "" ); }
/* * TV_Downstream_UserinfoChanged * * Pull specific info from a newly changed userinfo string * into a more C friendly form. */ void TV_Downstream_UserinfoChanged( client_t *client ) { char *val; assert( client ); assert( Info_Validate( client->userinfo ) ); // force the IP key/value pair so the game can filter based on ip if( !Info_SetValueForKey( client->userinfo, "socket", NET_SocketTypeToString( client->netchan.socket->type ) ) ) { TV_Downstream_DropClient( client, DROP_TYPE_GENERAL, "Error: Couldn't set userinfo (socket)\n" ); return; } if( !Info_SetValueForKey( client->userinfo, "ip", NET_AddressToString( &client->netchan.remoteAddress ) ) ) { TV_Downstream_DropClient( client, DROP_TYPE_GENERAL, "Error: Couldn't set userinfo (ip)\n" ); return; } // we handle name ourselves here, since tv module doesn't know about all the players val = TV_Downstream_FixName( Info_ValueForKey( client->userinfo, "name" ), client ); Q_strncpyz( client->name, val, sizeof( client->name ) ); if( !Info_SetValueForKey( client->userinfo, "name", client->name ) ) { TV_Downstream_DropClient( client, DROP_TYPE_GENERAL, "Error: Couldn't set userinfo (name)" ); return; } if( client->relay ) TV_Relay_ClientUserinfoChanged( client->relay, client ); if( !Info_Validate( client->userinfo ) ) { TV_Downstream_DropClient( client, DROP_TYPE_GENERAL, "Error: Invalid userinfo (after game)" ); return; } }
/* * TV_Downstream_UserinfoCommand_f */ static void TV_Downstream_UserinfoCommand_f( client_t *client ) { char *info; info = Cmd_Argv( 1 ); if( !Info_Validate( info ) ) { TV_Downstream_DropClient( client, DROP_TYPE_GENERAL, "Error: Invalid userinfo" ); return; } Q_strncpyz( client->userinfo, info, sizeof( client->userinfo ) ); TV_Downstream_UserinfoChanged( client ); }
/* * TV_Downstream_SendClientMessages */ void TV_Downstream_SendClientMessages( void ) { int i; client_t *client; msg_t message; qbyte messageData[MAX_MSGLEN]; // send a message to each connected client for( i = 0, client = tvs.clients; i < tv_maxclients->integer; i++, client++ ) { if( client->state == CS_FREE || client->state == CS_ZOMBIE ) continue; if( client->state < CS_SPAWNED ) { // send pending reliable commands, or send heartbeats for not timing out /* if( client->reliableSequence > client->reliableSent || (client->reliableSequence > client->reliableAcknowledge && tvs.realtime - client->lastPacketSentTime > 50) || tvs.realtime - client->lastPacketSentTime > 500 ) */ if( client->reliableSequence > client->reliableAcknowledge || tvs.realtime - client->lastPacketSentTime > 1000 ) { TV_Downstream_InitClientMessage( client, &message, messageData, sizeof( messageData ) ); TV_Downstream_AddReliableCommandsToMessage( client, &message ); if( !TV_Downstream_SendMessageToClient( client, &message ) ) { Com_Printf( "%s" S_COLOR_WHITE ": Error sending message: %s\n", client->name, NET_ErrorString() ); if( client->reliable ) { TV_Downstream_DropClient( client, DROP_TYPE_GENERAL, "Error sending message: %s\n", NET_ErrorString() ); } } } } } }
/* * TV_Downstream_CheckTimeouts */ void TV_Downstream_CheckTimeouts( void ) { client_t *client; int i; #ifdef TCP_SUPPORT // timeout incoming upstreams for( i = 0; i < MAX_INCOMING_CONNECTIONS; i++ ) { if( tvs.incoming[i].active && tvs.incoming[i].time + 1000 * 15 < tvs.realtime ) { Com_Printf( "Incoming TCP upstream from %s timed out\n", NET_AddressToString( &tvs.incoming[i].address ) ); NET_CloseSocket( &tvs.incoming[i].socket ); tvs.incoming[i].active = qfalse; } } #endif // timeout clients for( i = 0, client = tvs.clients; i < tv_maxclients->integer; i++, client++ ) { // message times may be wrong across a changelevel if( client->lastPacketReceivedTime > tvs.realtime ) client->lastPacketReceivedTime = tvs.realtime; if( client->state == CS_ZOMBIE && client->lastPacketReceivedTime + 1000 * tv_zombietime->value < tvs.realtime ) { client->state = CS_FREE; // can now be reused if( client->individual_socket ) NET_CloseSocket( &client->socket ); continue; } if( ( client->state != CS_FREE && client->state != CS_ZOMBIE ) && ( client->lastPacketReceivedTime + 1000 * tv_timeout->value < tvs.realtime ) ) { TV_Downstream_DropClient( client, DROP_TYPE_GENERAL, "Upstream timed out" ); client->state = CS_FREE; // don't bother with zombie state if( client->socket.open ) NET_CloseSocket( &client->socket ); } // timeout downloads left open if( ( client->state != CS_FREE && client->state != CS_ZOMBIE ) && ( client->download.name && client->download.timeout < tvs.realtime ) ) { Com_Printf( "Download of %s to %s" S_COLOR_WHITE " timed out\n", client->download.name, client->name ); if( client->download.data ) { FS_FreeBaseFile( client->download.data ); client->download.data = NULL; } Mem_ZoneFree( client->download.name ); client->download.name = NULL; client->download.size = 0; client->download.timeout = 0; } } }
/* * TV_Downstream_ReadPackets */ void TV_Downstream_ReadPackets( void ) { int i, socketind, ret, game_port; client_t *cl; #ifdef TCP_SUPPORT socket_t newsocket; #endif socket_t *socket; netadr_t address; msg_t msg; qbyte msgData[MAX_MSGLEN]; socket_t* sockets [] = { &tvs.socket_udp, &tvs.socket_udp6, }; #ifdef TCP_SUPPORT if( tvs.socket_tcp.open ) { while( qtrue ) { // find a free slot for( i = 0; i < MAX_INCOMING_CONNECTIONS; i++ ) { if( !tvs.incoming[i].active ) break; } if( i == MAX_INCOMING_CONNECTIONS ) break; if( ( ret = NET_Accept( &tvs.socket_tcp, &newsocket, &address ) ) == 0 ) break; if( ret == -1 ) { Com_Printf( "NET_Accept: Error: %s\n", NET_ErrorString() ); continue; } tvs.incoming[i].active = qtrue; tvs.incoming[i].socket = newsocket; tvs.incoming[i].address = address; tvs.incoming[i].time = tvs.realtime; } } for( i = 0; i < MAX_INCOMING_CONNECTIONS; i++ ) { if( !tvs.incoming[i].active ) continue; ret = NET_GetPacket( &tvs.incoming[i].socket, &address, &msg ); if( ret == -1 ) { NET_CloseSocket( &tvs.incoming[i].socket ); tvs.incoming[i].active = qfalse; } else if( ret == 1 ) { if( *(int *)msg.data != -1 ) { // sequence packet without upstreams NET_CloseSocket( &tvs.incoming[i].socket ); tvs.incoming[i].active = qfalse; continue; } TV_Downstream_UpstreamlessPacket( &tvs.incoming[i].socket, &address, &msg ); } } #endif MSG_Init( &msg, msgData, sizeof( msgData ) ); for( socketind = 0; socketind < sizeof( sockets ) / sizeof( sockets[0] ); socketind++ ) { socket = sockets[socketind]; while( socket->open && ( ret = NET_GetPacket( socket, &address, &msg ) ) != 0 ) { if( ret == -1 ) { Com_Printf( "NET_GetPacket: Error: %s\n", NET_ErrorString() ); continue; } // check for upstreamless packet (0xffffffff) first if( *(int *)msg.data == -1 ) { TV_Downstream_UpstreamlessPacket( socket, &address, &msg ); continue; } // read the game port out of the message so we can fix up // stupid address translating routers MSG_BeginReading( &msg ); MSG_ReadLong( &msg ); // sequence number MSG_ReadLong( &msg ); // sequence number game_port = MSG_ReadShort( &msg ) & 0xffff; // data follows // check for packets from connected clients for( i = 0, cl = tvs.clients; i < tv_maxclients->integer; i++, cl++ ) { unsigned short remoteaddr_port, addr_port; if( cl->state == CS_FREE || cl->state == CS_ZOMBIE ) continue; if( !NET_CompareBaseAddress( &address, &cl->netchan.remoteAddress ) ) continue; if( cl->netchan.game_port != game_port ) continue; remoteaddr_port = NET_GetAddressPort( &cl->netchan.remoteAddress ); addr_port = NET_GetAddressPort( &address ); if( remoteaddr_port != addr_port ) { Com_DPrintf( "%s" S_COLOR_WHITE ": Fixing up a translated port from %i to %i\n", cl->name, remoteaddr_port, addr_port ); NET_SetAddressPort( &cl->netchan.remoteAddress, addr_port ); } if( TV_Downstream_ProcessPacket( &cl->netchan, &msg ) ) { // this is a valid, sequenced packet, so process it cl->lastPacketReceivedTime = tvs.realtime; TV_Downstream_ParseClientMessage( cl, &msg ); } break; } } } // handle clients with individual sockets for( i = 0; i < tv_maxclients->integer; i++ ) { cl = &tvs.clients[i]; if( cl->state == CS_ZOMBIE || cl->state == CS_FREE ) continue; if( !cl->individual_socket ) continue; // not while, we only handle one packet per client at a time here if( ( ret = NET_GetPacket( cl->netchan.socket, &address, &msg ) ) != 0 ) { if( ret == -1 ) { Com_Printf( "%s" S_COLOR_WHITE ": Error receiving packet: %s\n", cl->name, NET_ErrorString() ); if( cl->reliable ) TV_Downstream_DropClient( cl, DROP_TYPE_GENERAL, "Error receiving packet: %s", NET_ErrorString() ); } else { if( *(int *)msg.data == -1 ) { TV_Downstream_UpstreamlessPacket( cl->netchan.socket, &address, &msg ); } else { if( TV_Downstream_ProcessPacket( &cl->netchan, &msg ) ) { cl->lastPacketReceivedTime = tvs.realtime; TV_Downstream_ParseClientMessage( cl, &msg ); } } } } } }
/* * TV_Downstream_Disconnect_f * The client is going to disconnect, so remove the upstream immediately */ static void TV_Downstream_Disconnect_f( client_t *client ) { TV_Downstream_DropClient( client, DROP_TYPE_GENERAL, "User disconnected" ); }