/* ======================= SV_SendClientDatagram ======================= */ void SV_SendClientDatagram( sv_client_t *cl ) { byte msg_buf[NET_MAX_PAYLOAD]; sizebuf_t msg; svs.currentPlayer = cl; svs.currentPlayerNum = (cl - svs.clients); BF_Init( &msg, "Datagram", msg_buf, sizeof( msg_buf )); // always send servertime at new frame BF_WriteByte( &msg, svc_time ); BF_WriteFloat( &msg, sv.time ); SV_WriteClientdataToMessage( cl, &msg ); SV_WriteEntitiesToClient( cl, &msg ); // copy the accumulated multicast datagram // for this client out to the message if( BF_CheckOverflow( &cl->datagram )) MsgDev( D_WARN, "datagram overflowed for %s\n", cl->name ); else BF_WriteBits( &msg, BF_GetData( &cl->datagram ), BF_GetNumBitsWritten( &cl->datagram )); BF_Clear( &cl->datagram ); if( BF_CheckOverflow( &msg )) { // must have room left for the packet header MsgDev( D_WARN, "msg overflowed for %s\n", cl->name ); BF_Clear( &msg ); } // send the datagram Netchan_TransmitBits( &cl->netchan, BF_GetNumBitsWritten( &msg ), BF_GetData( &msg )); }
/* ============================== Netchan_CopyNormalFragments ============================== */ qboolean Netchan_CopyNormalFragments( netchan_t *chan, sizebuf_t *msg ) { fragbuf_t *p, *n; if( !chan->incomingready[FRAG_NORMAL_STREAM] ) return false; if( !chan->incomingbufs[FRAG_NORMAL_STREAM] ) { MsgDev( D_ERROR, "Netchan_CopyNormalFragments: Called with no fragments readied\n" ); chan->incomingready[FRAG_NORMAL_STREAM] = false; return false; } p = chan->incomingbufs[FRAG_NORMAL_STREAM]; BF_Init( msg, "NetMessage", net_message_buffer, sizeof( net_message_buffer )); while( p ) { n = p->next; // copy it in BF_WriteBits( msg, BF_GetData( &p->frag_message ), BF_GetNumBitsWritten( &p->frag_message )); Mem_Free( p ); p = n; } chan->incomingbufs[FRAG_NORMAL_STREAM] = NULL; // reset flag chan->incomingready[FRAG_NORMAL_STREAM] = false; return true; }
/* =================== CL_WritePacket Create and send the command packet to the server Including both the reliable commands and the usercmds =================== */ void CL_WritePacket( void ) { sizebuf_t buf; qboolean send_command = false; byte data[MAX_CMD_BUFFER]; int i, from, to, key, size; int numbackup = 2; int numcmds; int newcmds; int cmdnumber; // don't send anything if playing back a demo if( cls.demoplayback || cls.state == ca_cinematic ) return; if( cls.state == ca_disconnected || cls.state == ca_connecting ) return; CL_ComputePacketLoss (); #ifndef _DEBUG if( cl_cmdrate->value < MIN_CMD_RATE ) { Cvar_SetFloat( "cl_cmdrate", MIN_CMD_RATE ); } #endif Q_memset( data, 0, MAX_CMD_BUFFER ); BF_Init( &buf, "ClientData", data, sizeof( data )); // Determine number of backup commands to send along numbackup = bound( 0, cl_cmdbackup->integer, MAX_BACKUP_COMMANDS ); if( cls.state == ca_connected ) numbackup = 0; // Check to see if we can actually send this command // In single player, send commands as fast as possible // Otherwise, only send when ready and when not choking bandwidth if(( cl.maxclients == 1 ) || ( NET_IsLocalAddress( cls.netchan.remote_address ) && !host_limitlocal->integer )) send_command = true; else if(( host.realtime >= cls.nextcmdtime ) && Netchan_CanPacket( &cls.netchan )) send_command = true; if( cl.force_send_usercmd ) { send_command = true; cl.force_send_usercmd = false; } if(( cls.netchan.outgoing_sequence - cls.netchan.incoming_acknowledged ) >= CL_UPDATE_MASK ) { if(( host.realtime - cls.netchan.last_received ) > CONNECTION_PROBLEM_TIME ) { Con_NPrintf( 1, "^3Warning:^1 Connection Problem^7\n" ); cl.validsequence = 0; } } if( cl_nodelta->integer ) { cl.validsequence = 0; } // send a userinfo update if needed if( userinfo->modified ) { BF_WriteByte( &cls.netchan.message, clc_userinfo ); BF_WriteString( &cls.netchan.message, Cvar_Userinfo( )); } if( send_command ) { int outgoing_sequence; if( cl_cmdrate->integer > 0 ) cls.nextcmdtime = host.realtime + ( 1.0f / cl_cmdrate->value ); else cls.nextcmdtime = host.realtime; // always able to send right away if( cls.lastoutgoingcommand == -1 ) { outgoing_sequence = cls.netchan.outgoing_sequence; cls.lastoutgoingcommand = cls.netchan.outgoing_sequence; } else outgoing_sequence = cls.lastoutgoingcommand + 1; // begin a client move command BF_WriteByte( &buf, clc_move ); // save the position for a checksum byte key = BF_GetRealBytesWritten( &buf ); BF_WriteByte( &buf, 0 ); // write packet lossage percentation BF_WriteByte( &buf, cls.packet_loss ); // say how many backups we'll be sending BF_WriteByte( &buf, numbackup ); // how many real commands have queued up newcmds = ( cls.netchan.outgoing_sequence - cls.lastoutgoingcommand ); // put an upper/lower bound on this newcmds = bound( 0, newcmds, MAX_TOTAL_CMDS ); if( cls.state == ca_connected ) newcmds = 0; BF_WriteByte( &buf, newcmds ); numcmds = newcmds + numbackup; from = -1; for( i = numcmds - 1; i >= 0; i-- ) { cmdnumber = ( cls.netchan.outgoing_sequence - i ) & CL_UPDATE_MASK; to = cmdnumber; CL_WriteUsercmd( &buf, from, to ); from = to; if( BF_CheckOverflow( &buf )) Host_Error( "CL_Move, overflowed command buffer (%i bytes)\n", MAX_CMD_BUFFER ); } // calculate a checksum over the move commands size = BF_GetRealBytesWritten( &buf ) - key - 1; buf.pData[key] = CRC32_BlockSequence( buf.pData + key + 1, size, cls.netchan.outgoing_sequence ); // message we are constructing. i = cls.netchan.outgoing_sequence & CL_UPDATE_MASK; // determine if we need to ask for a new set of delta's. if( cl.validsequence && (cls.state == ca_active) && !( cls.demorecording && cls.demowaiting )) { cl.delta_sequence = cl.validsequence; BF_WriteByte( &buf, clc_delta ); BF_WriteByte( &buf, cl.validsequence & 0xFF ); } else { // request delta compression of entities cl.delta_sequence = -1; } if( BF_CheckOverflow( &buf )) { Host_Error( "CL_Move, overflowed command buffer (%i bytes)\n", MAX_CMD_BUFFER ); } // remember outgoing command that we are sending cls.lastoutgoingcommand = cls.netchan.outgoing_sequence; // composite the rest of the datagram.. if( BF_GetNumBitsWritten( &cls.datagram ) <= BF_GetNumBitsLeft( &buf )) BF_WriteBits( &buf, BF_GetData( &cls.datagram ), BF_GetNumBitsWritten( &cls.datagram )); BF_Clear( &cls.datagram ); // deliver the message (or update reliable) Netchan_Transmit( &cls.netchan, BF_GetNumBytesWritten( &buf ), BF_GetData( &buf )); } else { // increment sequence number so we can detect that we've held back packets. cls.netchan.outgoing_sequence++; } if( cls.demorecording ) { // Back up one because we've incremented outgoing_sequence each frame by 1 unit cmdnumber = ( cls.netchan.outgoing_sequence - 1 ) & CL_UPDATE_MASK; CL_WriteDemoUserCmd( cmdnumber ); } // update download/upload slider. Netchan_UpdateProgress( &cls.netchan ); }
/* ======================= SV_UpdateToReliableMessages ======================= */ void SV_UpdateToReliableMessages( void ) { int i; sv_client_t *cl; // check for changes to be sent over the reliable streams to all clients for( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) { if( !cl->edict ) continue; // not in game yet if( cl->state != cs_spawned ) continue; if( cl->sendinfo ) { cl->sendinfo = false; SV_FullClientUpdate( cl, &sv.reliable_datagram ); } if( cl->sendmovevars ) { cl->sendmovevars = false; SV_FullUpdateMovevars( cl, &cl->netchan.message ); } } // 1% chanse for simulate random network bugs if( sv.write_bad_message && Com_RandomLong( 0, 512 ) == 404 ) { // just for network debugging (send only for local client) BF_WriteByte( &sv.datagram, svc_bad ); BF_WriteLong( &sv.datagram, rand( )); // send some random data BF_WriteString( &sv.datagram, host.finalmsg ); // send final message sv.write_bad_message = false; } // clear the server datagram if it overflowed. if( BF_CheckOverflow( &sv.datagram )) { MsgDev( D_ERROR, "sv.datagram overflowed!\n" ); BF_Clear( &sv.datagram ); } // clear the server datagram if it overflowed. if( BF_CheckOverflow( &sv.spectator_datagram )) { MsgDev( D_ERROR, "sv.spectator_datagram overflowed!\n" ); BF_Clear( &sv.spectator_datagram ); } // now send the reliable and server datagrams to all clients. for( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) { if( cl->state < cs_connected || cl->fakeclient ) continue; // reliables go to all connected or spawned BF_WriteBits( &cl->netchan.message, BF_GetData( &sv.reliable_datagram ), BF_GetNumBitsWritten( &sv.reliable_datagram )); BF_WriteBits( &cl->datagram, BF_GetData( &sv.datagram ), BF_GetNumBitsWritten( &sv.datagram )); if( cl->hltv_proxy ) { BF_WriteBits( &cl->datagram, BF_GetData( &sv.spectator_datagram ), BF_GetNumBitsWritten( &sv.spectator_datagram )); } } // now clear the reliable and datagram buffers. BF_Clear( &sv.spectator_datagram ); BF_Clear( &sv.reliable_datagram ); BF_Clear( &sv.datagram ); }
/* ============================== Netchan_CopyFileFragments ============================== */ qboolean Netchan_CopyFileFragments( netchan_t *chan, sizebuf_t *msg ) { fragbuf_t *p, *n; char filename[CS_SIZE]; int nsize; byte *buffer; int pos; if( !chan->incomingready[FRAG_FILE_STREAM] ) return false; if( !chan->incomingbufs[FRAG_FILE_STREAM] ) { MsgDev( D_WARN, "Netchan_CopyFileFragments: Called with no fragments readied\n" ); chan->incomingready[FRAG_FILE_STREAM] = false; return false; } p = chan->incomingbufs[FRAG_FILE_STREAM]; BF_Init( msg, "NetMessage", net_message_buffer, sizeof( net_message_buffer )); // copy in first chunk so we can get filename out BF_WriteBits( msg, BF_GetData( &p->frag_message ), BF_GetNumBitsWritten( &p->frag_message )); BF_SeekToBit( msg, 0 ); // rewind buffer //Q_strncpy( filename, BF_ReadString( msg ), sizeof( filename )); Q_snprintf( filename, sizeof( filename ), "downloaded/%s", BF_ReadString( msg ) ); if( Q_strlen( filename ) <= 0 ) { MsgDev( D_ERROR, "File fragment received with no filename\nFlushing input queue\n" ); // clear out bufs Netchan_FlushIncoming( chan, FRAG_FILE_STREAM ); return false; } else if( Q_strstr( filename, ".." )) { MsgDev( D_ERROR, "File fragment received with relative path, ignoring\n" ); // clear out bufs Netchan_FlushIncoming( chan, FRAG_FILE_STREAM ); return false; } Q_strncpy( chan->incomingfilename, filename, sizeof( chan->incomingfilename )); if( FS_FileExists( filename, false )) { MsgDev( D_ERROR, "Can't download %s, already exists\n", filename ); // clear out bufs Netchan_FlushIncoming( chan, FRAG_FILE_STREAM ); return true; } // create file from buffers nsize = 0; while ( p ) { nsize += BF_GetNumBytesWritten( &p->frag_message ); // Size will include a bit of slop, oh well if( p == chan->incomingbufs[FRAG_FILE_STREAM] ) { nsize -= BF_GetNumBytesRead( msg ); } p = p->next; } buffer = Mem_Alloc( net_mempool, nsize + 1 ); p = chan->incomingbufs[ FRAG_FILE_STREAM ]; pos = 0; while( p ) { int cursize; n = p->next; cursize = BF_GetNumBytesWritten( &p->frag_message ); // first message has the file name, don't write that into the data stream, // just write the rest of the actual data if( p == chan->incomingbufs[FRAG_FILE_STREAM] ) { // copy it in cursize -= BF_GetNumBytesRead( msg ); Q_memcpy( &buffer[pos], &p->frag_message.pData[BF_GetNumBytesRead( msg )], cursize ); } else { Q_memcpy( &buffer[pos], p->frag_message.pData, cursize ); } pos += cursize; Mem_Free( p ); p = n; } FS_WriteFile( filename, buffer, pos ); Mem_Free( buffer ); // clear remnants BF_Clear( msg ); chan->incomingbufs[FRAG_FILE_STREAM] = NULL; // reset flag chan->incomingready[FRAG_FILE_STREAM] = false; return true; }
/* =============== Netchan_TransmitBits tries to send an unreliable message to a connection, and handles the transmition / retransmition of the reliable messages. A 0 length will still generate a packet and deal with the reliable messages. ================ */ void Netchan_TransmitBits( netchan_t *chan, int length, byte *data ) { sizebuf_t send; byte send_buf[NET_MAX_MESSAGE]; qboolean send_reliable_fragment; qboolean send_resending = false; qboolean send_reliable; size_t size1, size2; uint w1, w2, hdr_size; int i, j; float fRate; // check for message overflow // check for message overflow if( BF_CheckOverflow( &chan->message )) { MsgDev( D_ERROR, "%s:outgoing message overflow\n", NET_AdrToString( chan->remote_address )); return; } // if the remote side dropped the last reliable message, resend it send_reliable = false; if( chan->incoming_acknowledged > chan->last_reliable_sequence && chan->incoming_reliable_acknowledged != chan->reliable_sequence ) { send_reliable = true; send_resending = true; } // A packet can have "reliable payload + frag payload + unreliable payload // frag payload can be a file chunk, if so, it needs to be parsed on the receiving end and reliable payload + unreliable payload need // to be passed on to the message queue. The processing routine needs to be able to handle the case where a message comes in and a file // transfer completes // if the reliable transmit buffer is empty, copy the current message out if( !chan->reliable_length ) { qboolean send_frag = false; fragbuf_t *pbuf; // will be true if we are active and should let chan->message get some bandwidth int send_from_frag[MAX_STREAMS] = { 0, 0 }; int send_from_regular = false; // if we have data in the waiting list(s) and we have cleared the current queue(s), then // push the waitlist(s) into the current queue(s) Netchan_FragSend( chan ); // sending regular payload send_from_regular = BF_GetNumBytesWritten( &chan->message ) ? 1 : 0; // check to see if we are sending a frag payload for( i = 0; i < MAX_STREAMS; i++ ) { if( chan->fragbufs[i] ) { send_from_frag[i] = 1; } } // stall reliable payloads if sending from frag buffer if( send_from_regular && ( send_from_frag[FRAG_NORMAL_STREAM] )) { send_from_regular = false; // if the reliable buffer has gotten too big, queue it at the end of everything and clear out buffer if( BF_GetNumBytesWritten( &chan->message ) > MAX_RELIABLE_PAYLOAD ) { Netchan_CreateFragments(( chan->sock == NS_SERVER ), chan, &chan->message ); BF_Clear( &chan->message ); } } // startpos will be zero if there is no regular payload for( i = 0; i < MAX_STREAMS; i++ ) { chan->frag_startpos[i] = 0; // assume no fragment is being sent chan->reliable_fragment[i] = 0; chan->reliable_fragid[i] = 0; chan->frag_length[i] = 0; if( send_from_frag[i] ) { send_frag = true; } } if( send_from_regular || send_frag ) { chan->reliable_sequence ^= 1; send_reliable = true; } if( send_from_regular ) { Q_memcpy( chan->reliable_buf, chan->message_buf, BF_GetNumBytesWritten( &chan->message )); chan->reliable_length = BF_GetNumBitsWritten( &chan->message ); BF_Clear( &chan->message ); // if we send fragments, this is where they'll start for( i = 0; i < MAX_STREAMS; i++ ) { chan->frag_startpos[i] = chan->reliable_length; } } for( i = 0; i < MAX_STREAMS; i++ ) { int fragment_size; int newpayloadsize; // is there someting in the fragbuf? pbuf = chan->fragbufs[i]; fragment_size = 0; if( pbuf ) { fragment_size = BF_GetNumBytesWritten( &pbuf->frag_message ); // files set size a bit differently. if( pbuf->isfile && !pbuf->isbuffer ) { fragment_size = pbuf->size; } } newpayloadsize = (( chan->reliable_length + ( fragment_size << 3 )) + 7 ) >> 3; // make sure we have enought space left if( send_from_frag[i] && pbuf && ( newpayloadsize < MAX_RELIABLE_PAYLOAD )) { sizebuf_t temp; // which buffer are we sending ? chan->reliable_fragid[i] = MAKE_FRAGID( pbuf->bufferid, chan->fragbufcount[i] ); // if it's not in-memory, then we'll need to copy it in frame the file handle. if( pbuf->isfile && !pbuf->isbuffer ) { byte filebuffer[2048]; file_t *hfile; hfile = FS_Open( pbuf->filename, "rb", false ); FS_Seek( hfile, pbuf->foffset, SEEK_SET ); FS_Read( hfile, filebuffer, pbuf->size ); BF_WriteBits( &pbuf->frag_message, filebuffer, pbuf->size << 3 ); FS_Close( hfile ); } // copy frag stuff on top of current buffer BF_StartWriting( &temp, chan->reliable_buf, sizeof( chan->reliable_buf ), chan->reliable_length, -1 ); BF_WriteBits( &temp, BF_GetData( &pbuf->frag_message ), BF_GetNumBitsWritten( &pbuf->frag_message )); chan->reliable_length += BF_GetNumBitsWritten( &pbuf->frag_message ); chan->frag_length[i] = BF_GetNumBitsWritten( &pbuf->frag_message ); // unlink pbuf Netchan_UnlinkFragment( pbuf, &chan->fragbufs[i] ); chan->reliable_fragment[i] = 1; // offset the rest of the starting positions for( j = i + 1; j < MAX_STREAMS; j++ ) { chan->frag_startpos[j] += chan->frag_length[i]; } } } }