From ac30d86db01a43130d2c9ff6fe31d6135d8e2592 Mon Sep 17 00:00:00 2001 From: Thilo Schulz Date: Wed, 13 Jul 2011 17:11:30 +0000 Subject: [PATCH] - Improve snapshot rate and data rate control - Make server send packet fragments and queued packets when server is idle - Voip protocol detection is tied to com_protocol making past-end-of-message reading unncessary - Use Hunk_AllocateTempMemory() for buffering VOIP packets and fix buffering scheme that ryan hates so much - Disable packet scrambling for new protocol as it is useless now - Get rid of the old packet scrambling functions predating latest point release - Use Hunk_AllocateTempMemory() for netchan packet queue to fix memory leak when client gets disconnected with packets in the queue - Use Hunk_AllocateTempMemory() for download blocks to fix memory leak when client gets disconnected with download blocks in the queue - Fix SV_RateMsec to account for udp/udp6 packet lengths --- code/client/cl_input.c | 14 --- code/client/cl_main.c | 8 +- code/client/cl_net_chan.c | 32 +++++-- code/client/cl_parse.c | 36 +++----- code/client/cl_scrn.c | 2 +- code/client/client.h | 3 +- code/qcommon/common.c | 21 ++++- code/qcommon/net_chan.c | 96 ++------------------ code/qcommon/qcommon.h | 14 ++- code/server/server.h | 10 ++- code/server/sv_client.c | 183 ++++++++++++++++++++++---------------- code/server/sv_init.c | 4 +- code/server/sv_main.c | 49 +++++++++- code/server/sv_net_chan.c | 103 ++++++++++++++------- code/server/sv_snapshot.c | 126 +++++++------------------- 15 files changed, 345 insertions(+), 356 deletions(-) diff --git a/code/client/cl_input.c b/code/client/cl_input.c index 60c26be4..a35a522f 100644 --- a/code/client/cl_input.c +++ b/code/client/cl_input.c @@ -840,8 +840,6 @@ void CL_WritePacket( void ) { cl_voipSendTarget->modified = qfalse; } - MSG_WriteByte (&buf, clc_EOF); // placate legacy servers. - MSG_WriteByte (&buf, clc_extension); MSG_WriteByte (&buf, clc_voip); MSG_WriteByte (&buf, clc.voipOutgoingGeneration); MSG_WriteLong (&buf, clc.voipOutgoingSequence); @@ -863,8 +861,6 @@ void CL_WritePacket( void ) { MSG_Init (&fakemsg, fakedata, sizeof (fakedata)); MSG_Bitstream (&fakemsg); MSG_WriteLong (&fakemsg, clc.reliableAcknowledge); - MSG_WriteByte (&fakemsg, svc_EOF); - MSG_WriteByte (&fakemsg, svc_extension); MSG_WriteByte (&fakemsg, svc_voip); MSG_WriteShort (&fakemsg, clc.clientNum); MSG_WriteByte (&fakemsg, clc.voipOutgoingGeneration); @@ -928,16 +924,6 @@ void CL_WritePacket( void ) { } CL_Netchan_Transmit (&clc.netchan, &buf); - - // clients never really should have messages large enough - // to fragment, but in case they do, fire them all off - // at once - // TTimo: this causes a packet burst, which is bad karma for winsock - // added a WARNING message, we'll see if there are legit situations where this happens - while ( clc.netchan.unsentFragments ) { - Com_DPrintf( "WARNING: #462 unsent fragments (not supposed to happen!)\n" ); - CL_Netchan_TransmitNextFragment( &clc.netchan ); - } } /* diff --git a/code/client/cl_main.c b/code/client/cl_main.c index 8070354f..53eec89f 100644 --- a/code/client/cl_main.c +++ b/code/client/cl_main.c @@ -235,7 +235,7 @@ void CL_Voip_f( void ) reason = "Not connected to a server"; else if (!clc.speexInitialized) reason = "Speex not initialized"; - else if (!cl_connectedToVoipServer) + else if (!clc.voipEnabled) reason = "Server doesn't support VoIP"; else if ( Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER || Cvar_VariableValue("ui_singlePlayerActive")) reason = "running in single-player mode"; @@ -331,7 +331,7 @@ void CL_CaptureVoip(void) qboolean dontCapture = qfalse; if (clc.state != CA_ACTIVE) dontCapture = qtrue; // not connected to a server. - else if (!cl_connectedToVoipServer) + else if (!clc.voipEnabled) dontCapture = qtrue; // server doesn't support VoIP. else if (clc.demoplaying) dontCapture = qtrue; // playing back a demo. @@ -1375,7 +1375,7 @@ void CL_Disconnect( qboolean showMainMenu ) { #ifdef USE_VOIP // not connected to voip server anymore. - cl_connectedToVoipServer = qfalse; + clc.voipEnabled = qfalse; #endif // Stop recording any video @@ -4397,7 +4397,7 @@ void CL_ShowIP_f(void) { /* ================= -bool CL_CDKeyValidate +CL_CDKeyValidate ================= */ qboolean CL_CDKeyValidate( const char *key, const char *checksum ) { diff --git a/code/client/cl_net_chan.c b/code/client/cl_net_chan.c index abea06d1..9d9f2399 100644 --- a/code/client/cl_net_chan.c +++ b/code/client/cl_net_chan.c @@ -24,6 +24,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #include "../qcommon/qcommon.h" #include "client.h" +#ifdef LEGACY_PROTOCOL /* ============== CL_Netchan_Encode @@ -125,14 +126,22 @@ static void CL_Netchan_Decode( msg_t *msg ) { *(msg->data + i) = *(msg->data + i) ^ key; } } +#endif /* ================= CL_Netchan_TransmitNextFragment ================= */ -void CL_Netchan_TransmitNextFragment( netchan_t *chan ) { - Netchan_TransmitNextFragment( chan ); +qboolean CL_Netchan_TransmitNextFragment(netchan_t *chan) +{ + if(chan->unsentFragments) + { + Netchan_TransmitNextFragment(chan); + return qtrue; + } + + return qfalse; } /* @@ -143,8 +152,18 @@ CL_Netchan_Transmit void CL_Netchan_Transmit( netchan_t *chan, msg_t* msg ) { MSG_WriteByte( msg, clc_EOF ); - CL_Netchan_Encode( msg ); - Netchan_Transmit( chan, msg->cursize, msg->data ); +#ifdef LEGACY_PROTOCOL + if(chan->compat) + CL_Netchan_Encode(msg); +#endif + + Netchan_Transmit(chan, msg->cursize, msg->data); + + // Transmit all fragments without delay + while(CL_Netchan_TransmitNextFragment(chan)) + { + Com_DPrintf("WARNING: #462 unsent fragments (not supposed to happen!)\n"); + } } /* @@ -159,7 +178,10 @@ qboolean CL_Netchan_Process( netchan_t *chan, msg_t *msg ) { if (!ret) return qfalse; - CL_Netchan_Decode( msg ); +#ifdef LEGACY_PROTOCOL + if(chan->compat) + CL_Netchan_Decode(msg); +#endif return qtrue; } diff --git a/code/client/cl_parse.c b/code/client/cl_parse.c index 5dfba5b4..cc49cd7e 100644 --- a/code/client/cl_parse.c +++ b/code/client/cl_parse.c @@ -34,7 +34,6 @@ char *svc_strings[256] = { "svc_download", "svc_snapshot", "svc_EOF", - "svc_extension", "svc_voip", }; @@ -330,10 +329,6 @@ void CL_ParseSnapshot( msg_t *msg ) { int cl_connectedToPureServer; int cl_connectedToCheatServer; -#ifdef USE_VOIP -int cl_connectedToVoipServer; -#endif - /* ================== CL_SystemInfoChanged @@ -363,14 +358,18 @@ void CL_SystemInfoChanged( void ) { } #ifdef USE_VOIP - // in the future, (val) will be a protocol version string, so only - // accept explicitly 1, not generally non-zero. - s = Info_ValueForKey( systemInfo, "sv_voip" ); - if ( Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER || Cvar_VariableValue("ui_singlePlayerActive")) - cl_connectedToVoipServer = qfalse; +#ifdef LEGACY_PROTOCOL + if(clc.compat) + clc.voipEnabled = qfalse; else - cl_connectedToVoipServer = (atoi( s ) == 1); - +#endif + { + s = Info_ValueForKey( systemInfo, "sv_voip" ); + if ( Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER || Cvar_VariableValue("ui_singlePlayerActive")) + clc.voipEnabled = qfalse; + else + clc.voipEnabled = atoi(s); + } #endif s = Info_ValueForKey( systemInfo, "sv_cheats" ); @@ -865,19 +864,6 @@ void CL_ParseServerMessage( msg_t *msg ) { cmd = MSG_ReadByte( msg ); - // See if this is an extension command after the EOF, which means we - // got data that a legacy client should ignore. - if ((cmd == svc_EOF) && (MSG_LookaheadByte( msg ) == svc_extension)) { - SHOWNET( msg, "EXTENSION" ); - MSG_ReadByte( msg ); // throw the svc_extension byte away. - cmd = MSG_ReadByte( msg ); // something legacy clients can't do! - // sometimes you get a svc_extension at end of stream...dangling - // bits in the huffman decoder giving a bogus value? - if (cmd == -1) { - cmd = svc_EOF; - } - } - if (cmd == svc_EOF) { SHOWNET( msg, "END OF MESSAGE" ); break; diff --git a/code/client/cl_scrn.c b/code/client/cl_scrn.c index 40d64638..2ec46efc 100644 --- a/code/client/cl_scrn.c +++ b/code/client/cl_scrn.c @@ -362,7 +362,7 @@ void SCR_DrawVoipMeter( void ) { return; // not recording at the moment. else if (clc.state != CA_ACTIVE) return; // not connected to a server. - else if (!cl_connectedToVoipServer) + else if (!clc.voipEnabled) return; // server doesn't support VoIP. else if (clc.demoplaying) return; // playing back a demo. diff --git a/code/client/client.h b/code/client/client.h index 9c7274d0..f1670e7f 100644 --- a/code/client/client.h +++ b/code/client/client.h @@ -234,6 +234,7 @@ typedef struct { unsigned char timeDemoDurations[ MAX_TIMEDEMO_DURATIONS ]; // log of frame durations #ifdef USE_VOIP + qboolean voipEnabled; qboolean speexInitialized; int speexFrameSize; int speexSampleRate; @@ -517,7 +518,6 @@ extern int cl_connectedToPureServer; extern int cl_connectedToCheatServer; #ifdef USE_VOIP -extern int cl_connectedToVoipServer; void CL_Voip_f( void ); #endif @@ -622,7 +622,6 @@ void LAN_SaveServersToCache( void ); // cl_net_chan.c // void CL_Netchan_Transmit( netchan_t *chan, msg_t* msg); //int length, const byte *data ); -void CL_Netchan_TransmitNextFragment( netchan_t *chan ); qboolean CL_Netchan_Process( netchan_t *chan, msg_t *msg ); // diff --git a/code/qcommon/common.c b/code/qcommon/common.c index a865f726..f8839baf 100644 --- a/code/qcommon/common.c +++ b/code/qcommon/common.c @@ -3111,7 +3111,11 @@ void Com_Frame( void ) { { if(com_sv_running->integer) { - // Send out download messages now that we're idle + // Send out fragmented packets now that we're idle + delayT = SV_SendQueuedMessages(); + if(delayT >= 0 && delayT < timeVal) + timeVal = delayT; + if(sv_dlRate->integer) { // Rate limiting. This is very imprecise for high @@ -3145,21 +3149,32 @@ void Com_Frame( void ) { // all of the bandwidth. This will result in an // effective maximum rate of 1MB/s per user, but the // low download window size limits this anyways. - timeVal = 2; + if(timeVal > 2) + timeVal = 2; + dlNextRound = dlStart + deltaT + 1; } else { dlNextRound = dlStart + delayT; - timeVal = delayT - deltaT; + delayT -= deltaT; + + if(delayT < timeVal) + timeVal = delayT; } } } } else + { SV_SendDownloadMessages(); + timeVal = 1; + } } + if(timeVal == 0) + timeVal = 1; + if(com_busyWait->integer) NET_Sleep(0); else diff --git a/code/qcommon/net_chan.c b/code/qcommon/net_chan.c index 7fb11e1c..aa553b5d 100644 --- a/code/qcommon/net_chan.c +++ b/code/qcommon/net_chan.c @@ -99,92 +99,6 @@ void Netchan_Setup(netsrc_t sock, netchan_t *chan, netadr_t adr, int qport, int #endif } -// TTimo: unused, commenting out to make gcc happy -#if 0 -/* -============== -Netchan_ScramblePacket - -A probably futile attempt to make proxy hacking somewhat -more difficult. -============== -*/ -#define SCRAMBLE_START 6 -static void Netchan_ScramblePacket( msg_t *buf ) { - unsigned seed; - int i, j, c, mask, temp; - int seq[MAX_PACKETLEN]; - - seed = ( LittleLong( *(unsigned *)buf->data ) * 3 ) ^ ( buf->cursize * 123 ); - c = buf->cursize; - if ( c <= SCRAMBLE_START ) { - return; - } - if ( c > MAX_PACKETLEN ) { - Com_Error( ERR_DROP, "MAX_PACKETLEN" ); - } - - // generate a sequence of "random" numbers - for (i = 0 ; i < c ; i++) { - seed = (119 * seed + 1); - seq[i] = seed; - } - - // transpose each character - for ( mask = 1 ; mask < c-SCRAMBLE_START ; mask = ( mask << 1 ) + 1 ) { - } - mask >>= 1; - for (i = SCRAMBLE_START ; i < c ; i++) { - j = SCRAMBLE_START + ( seq[i] & mask ); - temp = buf->data[j]; - buf->data[j] = buf->data[i]; - buf->data[i] = temp; - } - - // byte xor the data after the header - for (i = SCRAMBLE_START ; i < c ; i++) { - buf->data[i] ^= seq[i]; - } -} - -static void Netchan_UnScramblePacket( msg_t *buf ) { - unsigned seed; - int i, j, c, mask, temp; - int seq[MAX_PACKETLEN]; - - seed = ( LittleLong( *(unsigned *)buf->data ) * 3 ) ^ ( buf->cursize * 123 ); - c = buf->cursize; - if ( c <= SCRAMBLE_START ) { - return; - } - if ( c > MAX_PACKETLEN ) { - Com_Error( ERR_DROP, "MAX_PACKETLEN" ); - } - - // generate a sequence of "random" numbers - for (i = 0 ; i < c ; i++) { - seed = (119 * seed + 1); - seq[i] = seed; - } - - // byte xor the data after the header - for (i = SCRAMBLE_START ; i < c ; i++) { - buf->data[i] ^= seq[i]; - } - - // transpose each character in reverse order - for ( mask = 1 ; mask < c-SCRAMBLE_START ; mask = ( mask << 1 ) + 1 ) { - } - mask >>= 1; - for (i = c-1 ; i >= SCRAMBLE_START ; i--) { - j = SCRAMBLE_START + ( seq[i] & mask ); - temp = buf->data[j]; - buf->data[j] = buf->data[i]; - buf->data[i] = temp; - } -} -#endif - /* ================= Netchan_TransmitNextFragment @@ -225,7 +139,11 @@ void Netchan_TransmitNextFragment( netchan_t *chan ) { MSG_WriteData( &send, chan->unsentBuffer + chan->unsentFragmentStart, fragmentLength ); // send the datagram - NET_SendPacket( chan->sock, send.cursize, send.data, chan->remoteAddress ); + NET_SendPacket(chan->sock, send.cursize, send.data, chan->remoteAddress); + + // Store send time and size of this packet for rate control + chan->lastSentTime = Sys_Milliseconds(); + chan->lastSentSize = send.cursize; if ( showpackets->integer ) { Com_Printf ("%s send %4i : s=%i fragment=%i,%i\n" @@ -298,6 +216,10 @@ void Netchan_Transmit( netchan_t *chan, int length, const byte *data ) { // send the datagram NET_SendPacket( chan->sock, send.cursize, send.data, chan->remoteAddress ); + // Store send time and size of this packet for rate control + chan->lastSentTime = Sys_Milliseconds(); + chan->lastSentSize = send.cursize; + if ( showpackets->integer ) { Com_Printf( "%s send %4i : s=%i ack=%i\n" , netsrcString[ chan->sock ] diff --git a/code/qcommon/qcommon.h b/code/qcommon/qcommon.h index 836a1d6b..99063b99 100644 --- a/code/qcommon/qcommon.h +++ b/code/qcommon/qcommon.h @@ -227,6 +227,8 @@ typedef struct { byte unsentBuffer[MAX_MSGLEN]; int challenge; + int lastSentTime; + int lastSentSize; #ifdef LEGACY_PROTOCOL qboolean compat; @@ -250,7 +252,7 @@ PROTOCOL ============================================================== */ -#define PROTOCOL_VERSION 69 +#define PROTOCOL_VERSION 70 #define PROTOCOL_LEGACY_VERSION 68 // 1.31 - 67 @@ -296,9 +298,7 @@ enum svc_ops_e { svc_snapshot, svc_EOF, - // svc_extension follows a svc_EOF, followed by another svc_* ... - // this keeps legacy clients compatible. - svc_extension, +// new commands, supported only by ioquake3 protocol but not legacy svc_voip, // not wrapped in USE_VOIP, so this value is reserved. }; @@ -314,9 +314,7 @@ enum clc_ops_e { clc_clientCommand, // [string] message clc_EOF, - // clc_extension follows a clc_EOF, followed by another clc_* ... - // this keeps legacy servers compatible. - clc_extension, +// new commands, supported only by ioquake3 protocol but not legacy clc_voip, // not wrapped in USE_VOIP, so this value is reserved. }; @@ -1042,7 +1040,7 @@ void SV_PacketEvent( netadr_t from, msg_t *msg ); int SV_FrameMsec(void); qboolean SV_GameCommand( void ); int SV_SendDownloadMessages(void); - +int SV_SendQueuedMessages(void); // // UI interface diff --git a/code/server/server.h b/code/server/server.h index 67e71470..12f4f270 100644 --- a/code/server/server.h +++ b/code/server/server.h @@ -34,6 +34,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define MAX_ENT_CLUSTERS 16 #ifdef USE_VOIP +#define VOIP_QUEUE_LENGTH 64 + typedef struct voipServerPacket_s { int generation; @@ -162,7 +164,7 @@ typedef struct client_s { int nextReliableTime; // svs.time when another reliable command will be allowed int lastPacketTime; // svs.time when packet was last received int lastConnectTime; // svs.time when connection started - int nextSnapshotTime; // send another snapshot when svs.time >= nextSnapshotTime + int lastSnapshotTime; // svs.time of last sent snapshot qboolean rateDelayed; // true if nextSnapshotTime was set based on rate instead of snapshotMsec int timeoutCount; // must timeout a few frames in a row so debugging doesn't break clientSnapshot_t frames[PACKET_BACKUP]; // updates can be delta'd from here @@ -183,8 +185,9 @@ typedef struct client_s { qboolean hasVoip; qboolean muteAllVoip; qboolean ignoreVoipFromClient[MAX_CLIENTS]; - voipServerPacket_t voipPacket[64]; // !!! FIXME: WAY too much memory! + voipServerPacket_t *voipPacket[VOIP_QUEUE_LENGTH]; int queuedVoipPackets; + int queuedVoipIndex; #endif int oldServerTime; @@ -311,6 +314,7 @@ void SV_RemoveOperatorCommands (void); void SV_MasterShutdown (void); +int SV_RateMsec(client_t *client); @@ -460,6 +464,6 @@ void SV_ClipToEntity( trace_t *trace, const vec3_t start, const vec3_t mins, con // sv_net_chan.c // void SV_Netchan_Transmit( client_t *client, msg_t *msg); -void SV_Netchan_TransmitNextFragment( client_t *client ); +int SV_Netchan_TransmitNextFragment(client_t *client); qboolean SV_Netchan_Process( client_t *client, msg_t *msg ); diff --git a/code/server/sv_client.c b/code/server/sv_client.c index f90aeaba..90baabaa 100644 --- a/code/server/sv_client.c +++ b/code/server/sv_client.c @@ -557,7 +557,7 @@ gotnewcl: Com_DPrintf( "Going from CS_FREE to CS_CONNECTED for %s\n", newcl->name ); newcl->state = CS_CONNECTED; - newcl->nextSnapshotTime = svs.time; + newcl->lastSnapshotTime = 0; newcl->lastPacketTime = svs.time; newcl->lastConnectTime = svs.time; @@ -758,7 +758,7 @@ void SV_ClientEnterWorld( client_t *client, usercmd_t *cmd ) { client->gentity = ent; client->deltaMessage = -1; - client->nextSnapshotTime = svs.time; // generate a snapshot immediately + client->lastSnapshotTime = 0; // generate a snapshot immediately if(cmd) memcpy(&client->lastUsercmd, cmd, sizeof(client->lastUsercmd)); @@ -797,7 +797,7 @@ static void SV_CloseDownload( client_t *cl ) { // Free the temporary buffer space for (i = 0; i < MAX_DOWNLOAD_WINDOW; i++) { if (cl->downloadBlocks[i]) { - Z_Free( cl->downloadBlocks[i] ); + Hunk_FreeTempMemory(cl->downloadBlocks[i]); cl->downloadBlocks[i] = NULL; } } @@ -1017,7 +1017,7 @@ int SV_WriteDownloadToClient(client_t *cl, msg_t *msg) curindex = (cl->downloadCurrentBlock % MAX_DOWNLOAD_WINDOW); if (!cl->downloadBlocks[curindex]) - cl->downloadBlocks[curindex] = Z_Malloc( MAX_DOWNLOAD_BLKSIZE ); + cl->downloadBlocks[curindex] = Hunk_AllocateTempMemory(MAX_DOWNLOAD_BLKSIZE); cl->downloadBlockSize[curindex] = FS_Read( cl->downloadBlocks[curindex], MAX_DOWNLOAD_BLKSIZE, cl->download ); @@ -1084,11 +1084,42 @@ int SV_WriteDownloadToClient(client_t *cl, msg_t *msg) return 1; } +/* +================== +SV_SendQueuedMessages + +Send one round of fragments, or queued messages to all clients that have data pending. +Return the shortest time interval for sending next packet to client +================== +*/ + +int SV_SendQueuedMessages(void) +{ + int i, retval = -1, nextFragT; + client_t *cl; + + for(i=0; i < sv_maxclients->integer; i++) + { + cl = &svs.clients[i]; + + if(cl->state) + { + nextFragT = SV_Netchan_TransmitNextFragment(cl); + + if(nextFragT >= 0 && (retval == -1 || retval > nextFragT)) + retval = nextFragT; + } + } + + return retval; +} + + /* ================== SV_SendDownloadMessages -Send download messages to all clients +Send one round of download messages to all clients ================== */ @@ -1099,25 +1130,22 @@ int SV_SendDownloadMessages(void) msg_t msg; byte msgBuffer[MAX_MSGLEN]; - for(i=0, cl = svs.clients ; i < sv_maxclients->integer ; i++, cl++) + for(i=0; i < sv_maxclients->integer; i++) { + cl = &svs.clients[i]; + if(cl->state && *cl->downloadName) { - if(cl->netchan.unsentFragments) - SV_Netchan_TransmitNextFragment(cl); - else - { - MSG_Init(&msg, msgBuffer, sizeof(msgBuffer)); - MSG_WriteLong(&msg, cl->lastClientCommand); + MSG_Init(&msg, msgBuffer, sizeof(msgBuffer)); + MSG_WriteLong(&msg, cl->lastClientCommand); - retval = SV_WriteDownloadToClient(cl, &msg); + retval = SV_WriteDownloadToClient(cl, &msg); - if(retval) - { - MSG_WriteByte(&msg, svc_EOF); - SV_Netchan_Transmit(cl, &msg); - numDLs += retval; - } + if(retval) + { + MSG_WriteByte(&msg, svc_EOF); + SV_Netchan_Transmit(cl, &msg); + numDLs += retval; } } } @@ -1135,43 +1163,41 @@ Check to see if there is any VoIP queued for a client, and send if there is. */ void SV_WriteVoipToClient( client_t *cl, msg_t *msg ) { - voipServerPacket_t *packet = &cl->voipPacket[0]; - int totalbytes = 0; - int i; - - if (*cl->downloadName) { + if(*cl->downloadName) + { cl->queuedVoipPackets = 0; return; // no VoIP allowed if download is going, to save bandwidth. } - // Write as many VoIP packets as we reasonably can... - for (i = 0; i < cl->queuedVoipPackets; i++, packet++) { - totalbytes += packet->len; - if (totalbytes > MAX_DOWNLOAD_BLKSIZE) - break; + if(cl->queuedVoipPackets) + { + int totalbytes = 0; + int i; + voipServerPacket_t *packet; - // You have to start with a svc_EOF, so legacy clients drop the - // rest of this packet. Otherwise, those without VoIP support will - // see the svc_voip command, then panic and disconnect. - // Generally we don't send VoIP packets to legacy clients, but this - // serves as both a safety measure and a means to keep demo files - // compatible. - MSG_WriteByte( msg, svc_EOF ); - MSG_WriteByte( msg, svc_extension ); - MSG_WriteByte( msg, svc_voip ); - MSG_WriteShort( msg, packet->sender ); - MSG_WriteByte( msg, (byte) packet->generation ); - MSG_WriteLong( msg, packet->sequence ); - MSG_WriteByte( msg, packet->frames ); - MSG_WriteShort( msg, packet->len ); - MSG_WriteData( msg, packet->data, packet->len ); - } + // Write as many VoIP packets as we reasonably can... + for(i = cl->queuedVoipIndex; i < cl->queuedVoipPackets; i++) + { + packet = cl->voipPacket[i % ARRAY_LEN(cl->voipPacket)]; + + totalbytes += packet->len; + if (totalbytes > (msg->maxsize - msg->cursize) / 2) + break; - // !!! FIXME: I hate this queue system. - cl->queuedVoipPackets -= i; - if (cl->queuedVoipPackets > 0) { - memmove( &cl->voipPacket[0], &cl->voipPacket[i], - sizeof (voipServerPacket_t) * i); + MSG_WriteByte(msg, svc_voip); + MSG_WriteShort(msg, packet->sender); + MSG_WriteByte(msg, (byte) packet->generation); + MSG_WriteLong(msg, packet->sequence); + MSG_WriteByte(msg, packet->frames); + MSG_WriteShort(msg, packet->len); + MSG_WriteData(msg, packet->data, packet->len); + + Hunk_FreeTempMemory(packet); + } + + cl->queuedVoipPackets -= i; + cl->queuedVoipIndex += i; + cl->queuedVoipIndex %= ARRAY_LEN(cl->voipPacket); } } #endif @@ -1343,7 +1369,7 @@ static void SV_VerifyPaks_f( client_t *cl ) { } else { cl->pureAuthentic = 0; - cl->nextSnapshotTime = -1; + cl->lastSnapshotTime = 0; cl->state = CS_ACTIVE; SV_SendClientSnapshot( cl ); SV_DropClient( cl, "Unpure client detected. Invalid .PK3 files referenced!" ); @@ -1408,23 +1434,38 @@ void SV_UserinfoChanged( client_t *cl ) { // snaps command val = Info_ValueForKey (cl->userinfo, "snaps"); - if (strlen(val)) { + + if(strlen(val)) + { i = atoi(val); - if ( i < 1 ) { + + if(i < 1) i = 1; - } else if ( i > sv_fps->integer ) { + else if(i > sv_fps->integer) i = sv_fps->integer; - } - cl->snapshotMsec = 1000/i; - } else { - cl->snapshotMsec = 50; + + i = 1000 / i; + } + else + i = 50; + + if(i != cl->snapshotMsec) + { + // Reset last sent snapshot so we avoid desync between server frame time and snapshot send time + cl->lastSnapshotTime = 0; + cl->snapshotMsec = i; } #ifdef USE_VOIP - // in the future, (val) will be a protocol version string, so only - // accept explicitly 1, not generally non-zero. - val = Info_ValueForKey (cl->userinfo, "cl_voip"); - cl->hasVoip = (atoi(val) == 1) ? qtrue : qfalse; +#ifdef LEGACY_PROTOCOL + if(cl->compat) + cl->hasVoip = qfalse; + else +#endif + { + val = Info_ValueForKey(cl->userinfo, "cl_voip"); + cl->hasVoip = atoi(val); + } #endif // TTimo @@ -1759,7 +1800,7 @@ void SV_UserVoip( client_t *cl, msg_t *msg ) { const int recip2 = MSG_ReadLong(msg); const int recip3 = MSG_ReadLong(msg); const int packetsize = MSG_ReadShort(msg); - byte encoded[sizeof (cl->voipPacket[0].data)]; + byte encoded[sizeof(cl->voipPacket[0]->data)]; client_t *client = NULL; voipServerPacket_t *packet = NULL; int i; @@ -1833,13 +1874,15 @@ void SV_UserVoip( client_t *cl, msg_t *msg ) { continue; // no room for another packet right now. } - packet = &client->voipPacket[client->queuedVoipPackets]; + packet = Hunk_AllocateTempMemory(sizeof(*packet)); packet->sender = sender; packet->frames = frames; packet->len = packetsize; packet->generation = generation; packet->sequence = sequence; memcpy(packet->data, encoded, packetsize); + + client->voipPacket[(client->queuedVoipIndex + client->queuedVoipPackets) % ARRAY_LEN(client->voipPacket)] = packet; client->queuedVoipPackets++; } } @@ -1932,18 +1975,6 @@ void SV_ExecuteClientMessage( client_t *cl, msg_t *msg ) { do { c = MSG_ReadByte( msg ); - // See if this is an extension command after the EOF, which means we - // got data that a legacy server should ignore. - if ((c == clc_EOF) && (MSG_LookaheadByte( msg ) == clc_extension)) { - MSG_ReadByte( msg ); // throw the clc_extension byte away. - c = MSG_ReadByte( msg ); // something legacy servers can't do! - // sometimes you get a clc_extension at end of stream...dangling - // bits in the huffman decoder giving a bogus value? - if (c == -1) { - c = clc_EOF; - } - } - if ( c == clc_EOF ) { break; } diff --git a/code/server/sv_init.c b/code/server/sv_init.c index 14f4dac6..a0921463 100644 --- a/code/server/sv_init.c +++ b/code/server/sv_init.c @@ -554,7 +554,7 @@ void SV_SpawnServer( char *server, qboolean killBots ) { client->gentity = ent; client->deltaMessage = -1; - client->nextSnapshotTime = svs.time; // generate a snapshot immediately + client->lastSnapshotTime = 0; // generate a snapshot immediately VM_Call( gvm, GAME_CLIENT_BEGIN, i ); } @@ -723,7 +723,7 @@ void SV_FinalMessage( char *message ) { SV_SendServerCommand( cl, "disconnect \"%s\"", message ); } // force a snapshot to be sent - cl->nextSnapshotTime = -1; + cl->lastSnapshotTime = 0; SV_SendClientSnapshot( cl ); } } diff --git a/code/server/sv_main.c b/code/server/sv_main.c index c31965c4..a4ce5081 100644 --- a/code/server/sv_main.c +++ b/code/server/sv_main.c @@ -1156,5 +1156,52 @@ void SV_Frame( int msec ) { SV_MasterHeartbeat(sv_heartbeat->string); } -//============================================================================ +/* +==================== +SV_RateMsec +Return the number of msec until another message can be sent to +a client based on its rate settings +==================== +*/ + +#define UDPIP_HEADER_SIZE 28 +#define UDPIP6_HEADER_SIZE 48 + +int SV_RateMsec(client_t *client) +{ + int rate, rateMsec; + int messageSize; + + messageSize = client->netchan.lastSentSize; + rate = client->rate; + + if(sv_maxRate->integer) + { + if(sv_maxRate->integer < 1000) + Cvar_Set( "sv_MaxRate", "1000" ); + if(sv_maxRate->integer < rate) + rate = sv_maxRate->integer; + } + + if(sv_minRate->integer) + { + if(sv_minRate->integer < 1000) + Cvar_Set("sv_minRate", "1000"); + if(sv_minRate->integer > rate) + rate = sv_minRate->integer; + } + + if(client->netchan.remoteAddress.type == NA_IP6) + messageSize += UDPIP6_HEADER_SIZE; + else + messageSize += UDPIP_HEADER_SIZE; + + rateMsec = messageSize * 1000 / ((int) (rate * com_timescale->value)); + rate = Sys_Milliseconds() - client->netchan.lastSentTime; + + if(rate > rateMsec) + return 0; + else + return rateMsec - rate; +} diff --git a/code/server/sv_net_chan.c b/code/server/sv_net_chan.c index e83f4380..1677c678 100644 --- a/code/server/sv_net_chan.c +++ b/code/server/sv_net_chan.c @@ -24,6 +24,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #include "../qcommon/qcommon.h" #include "server.h" +#ifdef LEGACY_PROTOCOL /* ============== SV_Netchan_Encode @@ -127,38 +128,63 @@ static void SV_Netchan_Decode( client_t *client, msg_t *msg ) { *(msg->data + i) = *(msg->data + i) ^ key; } } +#endif + +/* +================= +SV_Netchan_TransmitNextInQueue +================= +*/ +void SV_Netchan_TransmitNextInQueue(client_t *client) +{ + netchan_buffer_t *netbuf; + + Com_DPrintf("#462 Netchan_TransmitNextFragment: popping a queued message for transmit\n"); + netbuf = client->netchan_start_queue; + +#ifdef LEGACY_PROTOCOL + if(client->compat) + SV_Netchan_Encode(client, &netbuf->msg); +#endif + + Netchan_Transmit(&client->netchan, netbuf->msg.cursize, netbuf->msg.data); + + // pop from queue + client->netchan_start_queue = netbuf->next; + if(!client->netchan_start_queue) + { + Com_DPrintf("#462 Netchan_TransmitNextFragment: emptied queue\n"); + client->netchan_end_queue = &client->netchan_start_queue; + } + else + Com_DPrintf("#462 Netchan_TransmitNextFragment: remaining queued message\n"); + + Hunk_FreeTempMemory(netbuf); +} /* ================= SV_Netchan_TransmitNextFragment +Transmit the next fragment and the next queued packet +Return number of ms until next message can be sent based on throughput given by client rate, +-1 if no packet was sent. ================= */ -void SV_Netchan_TransmitNextFragment( client_t *client ) { - Netchan_TransmitNextFragment( &client->netchan ); - if (!client->netchan.unsentFragments) + +int SV_Netchan_TransmitNextFragment(client_t *client) +{ + if(client->netchan.unsentFragments) { - // make sure the netchan queue has been properly initialized (you never know) - if ((!client->netchan_end_queue) && (client->state >= CS_CONNECTED)) { - Com_Error(ERR_DROP, "netchan queue is not properly initialized in SV_Netchan_TransmitNextFragment"); - } - // the last fragment was transmitted, check wether we have queued messages - if (client->netchan_start_queue) { - netchan_buffer_t *netbuf; - Com_DPrintf("#462 Netchan_TransmitNextFragment: popping a queued message for transmit\n"); - netbuf = client->netchan_start_queue; - SV_Netchan_Encode( client, &netbuf->msg ); - Netchan_Transmit( &client->netchan, netbuf->msg.cursize, netbuf->msg.data ); - // pop from queue - client->netchan_start_queue = netbuf->next; - if (!client->netchan_start_queue) { - Com_DPrintf("#462 Netchan_TransmitNextFragment: emptied queue\n"); - client->netchan_end_queue = &client->netchan_start_queue; - } - else - Com_DPrintf("#462 Netchan_TransmitNextFragment: remaining queued message\n"); - Z_Free(netbuf); - } - } + Netchan_TransmitNextFragment(&client->netchan); + return SV_RateMsec(client); + } + else if(client->netchan_start_queue) + { + SV_Netchan_TransmitNextInQueue(client); + return SV_RateMsec(client); + } + + return -1; } @@ -173,22 +199,28 @@ then buffer them and make sure they get sent in correct order ================ */ -void SV_Netchan_Transmit( client_t *client, msg_t *msg) { //int length, const byte *data ) { +void SV_Netchan_Transmit( client_t *client, msg_t *msg) +{ MSG_WriteByte( msg, svc_EOF ); - if (client->netchan.unsentFragments) { + + if(client->netchan.unsentFragments || client->netchan_start_queue) + { netchan_buffer_t *netbuf; Com_DPrintf("#462 SV_Netchan_Transmit: unsent fragments, stacked\n"); - netbuf = (netchan_buffer_t *)Z_Malloc(sizeof(netchan_buffer_t)); + netbuf = (netchan_buffer_t *) Hunk_AllocateTempMemory(sizeof(netchan_buffer_t)); // store the msg, we can't store it encoded, as the encoding depends on stuff we still have to finish sending MSG_Copy(&netbuf->msg, netbuf->msgBuffer, sizeof( netbuf->msgBuffer ), msg); netbuf->next = NULL; // insert it in the queue, the message will be encoded and sent later *client->netchan_end_queue = netbuf; client->netchan_end_queue = &(*client->netchan_end_queue)->next; - // emit the next fragment of the current message for now - Netchan_TransmitNextFragment(&client->netchan); - } else { - SV_Netchan_Encode( client, msg ); + } + else + { +#ifdef LEGACY_PROTOCOL + if(client->compat) + SV_Netchan_Encode(client, msg); +#endif Netchan_Transmit( &client->netchan, msg->cursize, msg->data ); } } @@ -203,7 +235,12 @@ qboolean SV_Netchan_Process( client_t *client, msg_t *msg ) { ret = Netchan_Process( &client->netchan, msg ); if (!ret) return qfalse; - SV_Netchan_Decode( client, msg ); + +#ifdef LEGACY_PROTOCOL + if(client->compat) + SV_Netchan_Decode(client, msg); +#endif + return qtrue; } diff --git a/code/server/sv_snapshot.c b/code/server/sv_snapshot.c index a9662615..91a6b6b0 100644 --- a/code/server/sv_snapshot.c +++ b/code/server/sv_snapshot.c @@ -521,45 +521,6 @@ static void SV_BuildClientSnapshot( client_t *client ) { } } - -/* -==================== -SV_RateMsec - -Return the number of msec a given size message is supposed -to take to clear, based on the current rate -==================== -*/ -#define HEADER_RATE_BYTES 48 // include our header, IP header, and some overhead -static int SV_RateMsec( client_t *client, int messageSize ) { - int rate; - int rateMsec; - - // individual messages will never be larger than fragment size - if ( messageSize > 1500 ) { - messageSize = 1500; - } - rate = client->rate; - if ( sv_maxRate->integer ) { - if ( sv_maxRate->integer < 1000 ) { - Cvar_Set( "sv_MaxRate", "1000" ); - } - if ( sv_maxRate->integer < rate ) { - rate = sv_maxRate->integer; - } - } - if ( sv_minRate->integer ) { - if ( sv_minRate->integer < 1000 ) - Cvar_Set( "sv_minRate", "1000" ); - if ( sv_minRate->integer > rate ) - rate = sv_minRate->integer; - } - - rateMsec = ( messageSize + HEADER_RATE_BYTES ) * 1000 / ((int) (rate * com_timescale->value)); - - return rateMsec; -} - /* ======================= SV_SendMessageToClient @@ -567,48 +528,15 @@ SV_SendMessageToClient Called by SV_SendClientSnapshot and SV_SendClientGameState ======================= */ -void SV_SendMessageToClient( msg_t *msg, client_t *client ) { - int rateMsec; - +void SV_SendMessageToClient(msg_t *msg, client_t *client) +{ // record information about the message client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageSize = msg->cursize; client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageSent = svs.time; client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageAcked = -1; // send the datagram - SV_Netchan_Transmit( client, msg ); //msg->cursize, msg->data ); - - // set nextSnapshotTime based on rate and requested number of updates - - // local clients get snapshots every server frame - // TTimo - https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=491 - // added sv_lanForceRate check - if ( client->netchan.remoteAddress.type == NA_LOOPBACK || (sv_lanForceRate->integer && Sys_IsLANAddress (client->netchan.remoteAddress)) ) { - client->nextSnapshotTime = svs.time + ((int) (1000.0 / sv_fps->integer * com_timescale->value)); - return; - } - - // normal rate / snapshotMsec calculation - rateMsec = SV_RateMsec(client, msg->cursize); - - if ( rateMsec < client->snapshotMsec * com_timescale->value) { - // never send more packets than this, no matter what the rate is at - rateMsec = client->snapshotMsec * com_timescale->value; - client->rateDelayed = qfalse; - } else { - client->rateDelayed = qtrue; - } - - client->nextSnapshotTime = svs.time + ((int) (rateMsec * com_timescale->value)); - - // don't pile up empty snapshots while connecting - if ( client->state != CS_ACTIVE ) { - // a gigantic connection message may have already put the nextSnapshotTime - // more than a second away, so don't shorten it - // do shorten if client is downloading - if (!*client->downloadName && client->nextSnapshotTime < svs.time + ((int) (1000.0 * com_timescale->value))) - client->nextSnapshotTime = svs.time + ((int) (1000 * com_timescale->value)); - } + SV_Netchan_Transmit(client, msg); } @@ -666,33 +594,47 @@ void SV_SendClientSnapshot( client_t *client ) { SV_SendClientMessages ======================= */ -void SV_SendClientMessages( void ) { - int i; +void SV_SendClientMessages(void) +{ + int i; client_t *c; // send a message to each connected client - for (i=0, c = svs.clients ; i < sv_maxclients->integer ; i++, c++) { - if (!c->state) { + for(i=0; i < sv_maxclients->integer; i++) + { + c = &svs.clients[i]; + + if(!c->state) continue; // not connected - } - - if ( svs.time < c->nextSnapshotTime ) { - continue; // not time yet - } if(*c->downloadName) continue; // Client is downloading, don't send snapshots - // send additional message fragments if the last message - // was too large to send at once - if ( c->netchan.unsentFragments ) { - c->nextSnapshotTime = svs.time + - SV_RateMsec( c, c->netchan.unsentLength - c->netchan.unsentFragmentStart ); - SV_Netchan_TransmitNextFragment( c ); - continue; + if(!(c->netchan.remoteAddress.type == NA_LOOPBACK || + (sv_lanForceRate->integer && Sys_IsLANAddress(c->netchan.remoteAddress)))) + { + // rate control for clients not on LAN + + if(svs.time - c->lastSnapshotTime < c->snapshotMsec * com_timescale->value) + continue; // It's not time yet + + if(c->netchan.unsentFragments || c->netchan_start_queue) + { + c->rateDelayed = qtrue; + continue; // Drop this snapshot if the packet queue is still full + } + + if(SV_RateMsec(c) > 0) + { + // Not enough time since last packet passed through the line + c->rateDelayed = qtrue; + continue; + } } // generate and send a new message - SV_SendClientSnapshot( c ); + SV_SendClientSnapshot(c); + c->lastSnapshotTime = svs.time; + c->rateDelayed = qfalse; } }