123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203 |
- ////////////////////////////////////////////////////////////////////////////////
- //
- // Copyright 2016 RWS Inc, All Rights Reserved
- //
- // This program is free software; you can redistribute it and/or modify
- // it under the terms of version 2 of the GNU General Public License as published by
- // the Free Software Foundation
- //
- // This program is distributed in the hope that it will be useful,
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- // GNU General Public License for more details.
- //
- // You should have received a copy of the GNU General Public License along
- // with this program; if not, write to the Free Software Foundation, Inc.,
- // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- //
- // NetServer.cpp
- // Project: RSPiX
- //
- // History:
- // 09/01/97 MJR Nearing the end of a major overhaul.
- //
- // 09/06/97 MJR No longer responds to browse attempts once game starts.
- //
- // 09/07/97 MJR Added support for PROCEED and PROGRESS_REALM messages.
- //
- // 11/20/97 JMI Added support for new sCoopLevels & sCoopMode flag in
- // StartGame and SetupGame messages.
- //
- // 11/25/97 JMI Added determination between version conflicts and platform
- // conflicts. Also, added error propagation.
- //
- // 11/26/97 JMI Masking error in evaluation of version mismatch problem
- // such that platform mismatch was never detected.
- //
- ////////////////////////////////////////////////////////////////////////////////
- #include "RSPiX.h"
- #include "netserver.h"
- ////////////////////////////////////////////////////////////////////////////////
- // Startup
- ////////////////////////////////////////////////////////////////////////////////
- short CNetServer::Startup( // Returns 0 if successfull, non-zero otherwise
- unsigned short usPort, // In: Server base port number
- char* pszHostName, // In: Host name (max size is MaxHostName!!!)
- RSocket::BLOCK_CALLBACK callback) // In: Blocking callback
- {
- short sResult = 0;
- // Do a reset to be sure we're starting at a good point
- Reset();
- // Save base socket (all sockets are done as offsets from this one)
- m_usBasePort = usPort;
- // Save callback
- m_callback = callback;
- // Create a unique number that this host can use to identify itself when
- // it browses for hosts. We simply use total elapsed microseconds, which
- // is EXTREMELY LIKELY to be different for every host. We also look for
- // the correct host name, but of course, many hosts can have the same name.
- m_lHostMagic = rspGetMicroseconds();
- // Save name (truncating if necessary)
- strncpy(m_acHostName, pszHostName, Net::MaxHostNameSize);
- m_acHostName[Net::MaxHostNameSize-1] = 0;
- // Setup listen socket
- sResult = m_socketListen.Open(
- m_usBasePort + Net::ListenPortOffset,
- RSocket::typStream,
- RSocket::optDontWaitOnClose | RSocket::optDontCoalesce | RSocket::optDontBlock,
- callback);
- if (sResult == 0)
- {
- sResult = m_socketListen.Listen();
- if (sResult == 0)
- {
- // Setup antenna socket, on which we receive broadcasts from potential
- // clients browsing for a host.
- sResult = m_socketAntenna.Open(
- m_usBasePort + Net::AntennaPortOffset,
- RSocket::typDatagram,
- RSocket::optDontBlock,
- callback);
- if (sResult == 0)
- {
- // Must set broadcast mode even for sockets that are RECEIVING them. Doesn't
- // seem to make sense, but empericial results say we need to do this.
- sResult = m_socketAntenna.Broadcast();
- if (sResult == 0)
- {
- }
- else
- TRACE("CNetServer::Setup(): Error putting antenna socket into broadcast mode!\n");
- }
- else
- TRACE("CNetServer::Setup(): Error opening antenna socket!\n");
- }
- else
- TRACE("CNetServer::Setup(): Error putting listen socket into listen mode!\n");
- }
- else
- TRACE("CNetServer::Setup(): Error opening listen socket!\n");
- return sResult;
- }
- ////////////////////////////////////////////////////////////////////////////////
- // Shutdown
- ////////////////////////////////////////////////////////////////////////////////
- void CNetServer::Shutdown(void)
- {
- Reset();
- }
- ////////////////////////////////////////////////////////////////////////////////
- // Update server. This should be called regularly.
- ////////////////////////////////////////////////////////////////////////////////
- void CNetServer::Update(void)
- {
- short sResult = 0;
- //------------------------------------------------------------------------------
- // Accept new clients
- //------------------------------------------------------------------------------
- // If there's no error on the listen socket, we can try to accept clients
- if (!m_socketListen.IsError())
- {
- // Look for an unconnected, unused slot in the array. I'm torn between searching
- // for an empty client each time versus checking if the socket can accept without
- // blocking each time -- I'm not sure which one is quicker! Obviously, the quicker
- // of the two should be checked first.
- for (Net::ID id = 0; id < Net::MaxNumIDs; id++)
- {
- if ((m_aClients[id].m_msgr.GetState() == CNetMsgr::Disconnected) && (m_aClients[id].m_state == CClient::Unused))
- {
- // Check if a new client is trying to connect
- if (m_socketListen.CanAcceptWithoutBlocking())
- {
- // Try to accept client's connection
- short serr = m_aClients[id].m_msgr.Accept(&m_socketListen, m_callback);
- if (serr == 0)
- {
- // Upgrade state
- m_aClients[id].m_state = CClient::Used;
- }
- else
- {
- // If the return error indicates that it would have blocked, then something
- // is not kosher since CanAcceptWithoutBlocking() just said it woulnd NOT block.
- if (serr == RSocket::errWouldBlock)
- TRACE("CNetServer()::Update(): It waid it wouldn't block, but then said it would!\n");
- // Don't return an actual error code from this function because we can't
- // get all bent-out-of-shape over not being able to connect to a client
- // TRACE("CNetServer()::Update(): Tried to accept connection, but failed.\n");
- }
- }
- // Break out of loop that was looking for unused clients
- break;
- }
- }
- }
- //------------------------------------------------------------------------------
- // Check for any reception on our antenna socket. Potential clients that are
- // looking for a host will periodically broadcast a message, and as a server
- // we are supposed to respond.
- //------------------------------------------------------------------------------
- // Don't respond once the game has started because we currently don't support
- // drop-in play, so we don't want to confuse potential players
- if (!m_bGameStarted)
- {
- // Try to read a message. This will almost always fail due to a lack of data,
- // since it's relatively rare for a client to be browsing, and even then it only
- // sends out a message every so often. If we get an incorrectly-sized message,
- // we simply ignore it -- this is a datagram socket, so if the message was larger
- // than we expected, the rest of it will be discarded, and if it was smaller, then
- // we can ignore it as well. Bad messages could come from a foreign app that is
- // using the same port as us. If we do get a message, the address of the sender
- // will be recorded -- this gives us the host's address!
- U8 buf1[4];
- long lReceived;
- RSocket::Address address;
- short serr = m_socketAntenna.ReceiveFrom(buf1, sizeof(buf1), &lReceived, &address);
- if (serr == 0)
- {
- // Validate the message to make sure it was sent by another app of this
- // type, as opposed to some unknown app that happens to use the same port.
- if ((lReceived == sizeof(buf1)) &&
- (buf1[0] == Net::BroadcastMagic0) &&
- (buf1[1] == Net::BroadcastMagic1) &&
- (buf1[2] == Net::BroadcastMagic2) &&
- (buf1[3] == Net::BroadcastMagic3))
- {
- // Send back a message including the host name and magic number
- // The endian nature of the magic number will always be correct because
- // the only entitity that is meant to recognize this value is the one
- // that sent it, so as long as the encoding and decoding of the bytes
- // is the same, that entity will get the same value that it sent. All
- // other entities will see this as a meaningless value, which is fine.
- U8 buf2[4 + 4 + sizeof(m_acHostName)];
- buf2[0] = Net::BroadcastMagic0;
- buf2[1] = Net::BroadcastMagic1;
- buf2[2] = Net::BroadcastMagic2;
- buf2[3] = Net::BroadcastMagic3;
- buf2[4] = (U8)(m_lHostMagic & 0xff);
- buf2[5] = (U8)((m_lHostMagic >> 8) & 0xff);
- buf2[6] = (U8)((m_lHostMagic >> 16) & 0xff);
- buf2[7] = (U8)((m_lHostMagic >> 24) & 0xff);
- strncpy((char*)&buf2[8], m_acHostName, sizeof(m_acHostName));
- // Send the message directly to the sender of the previous message
- long lBytesSent;
- short serr = m_socketAntenna.SendTo(buf2, sizeof(buf2), &lBytesSent, &address);
- if (serr == 0)
- {
- if (lBytesSent != sizeof(buf2))
- TRACE("CNetServer::Update(): Error sending broadcast (wrong size)!\n");
- }
- else
- {
- if (serr != RSocket::errWouldBlock)
- TRACE("CNetServer::Update(): Error sending broadcast!\n");
- }
- }
- else
- TRACE("CNetServer::Update(): Validation failed -- another app may be sending crap to our port!\n");
- }
- else
- {
- if (serr != RSocket::errWouldBlock)
- TRACE("CNetServer::Update(): Warning: Error receiving broadcast -- ignored!\n");
- }
- }
- //------------------------------------------------------------------------------
- // Update clients
- //------------------------------------------------------------------------------
- for (Net::ID id = 0; id < Net::MaxNumIDs; id++)
- m_aClients[id].m_msgr.Update();
- }
- ////////////////////////////////////////////////////////////////////////////////
- // Get message
- ////////////////////////////////////////////////////////////////////////////////
- void CNetServer::GetMsg(
- NetMsg* pmsg) // Out: Message is returned here
- {
- // This indicates whether we got a message to be returned to the caller
- bool bGotMsgForCaller = false;
- // We need to service all of the currently connected clients, but we can
- // only handle one message each time this function is called. Actually,
- // we can only handle one message if it's a message that needs to be
- // returned to the caller. If it wasn't, then we could theoretically
- // handle many such messages, with the only limitation being how much time
- // we're willing to spend here. For now, I'm going with handling just one
- // message each time.
- //
- // In order to give each client a chance, we use m_idPrevGet to keep track
- // of which client we got a message from the previous time. Starting with
- // the next client after that, we search for the first client that is
- // connected and has a message, and we process it. We save that client's
- // id so that next time we will start with the client after it.
- //
- // If we go through the entire list once and don't find any clients with
- // messages to get, then we have no client messages to process this time.
- bool bGotOne = false;
- Net::ID id = m_idPrevGet;
- do {
- if (++id >= Net::MaxNumIDs)
- id = 0;
- if (m_aClients[id].m_msgr.GetState() == CNetMsgr::Connected)
- bGotOne = m_aClients[id].m_msgr.GetMsg(pmsg);
- } while (!bGotOne && (id != m_idPrevGet));
- m_idPrevGet = id;
- // If we got a message, handle it
- if (bGotOne)
- {
- // The message's ucSenderID is not transmitted as part of the message in
- // order to save bandwidth (every byte counts :) since we know the sender
- // (we're connected to the sender) and its ID is implied by it's index.
- pmsg->ucSenderID = id;
- // Used for sending messages
- NetMsg msg;
- // The login message is the only mechanism we're using to detect foreign
- // apps that might accidentally connect to us. None of our other messages
- // has any protection, so random data that happens to resemble a message
- // could really screw us up. Therefore, it is very important that if the
- // first thing we recieve from a client is NOT a LOGIN message, we
- // immediately disconnect it. And if we do get what looks like a LOGIN
- // message, we must make sure the magic numbers are right.
- if (!m_aClients[id].m_bLoggedIn)
- {
- if (pmsg->msg.nothing.ucType == NetMsg::LOGIN)
- {
- if (pmsg->msg.login.ulMagic == CNetMsgr::MagicNum)
- {
- ////////////////////////////////////////////////////////////////
- // Version negotiation and useful error results ////////////////
- ////////////////////////////////////////////////////////////////
- //
- // In net implementation for Postal net version 1, both the
- // server and the client would check the version numbers -- the
- // server would check msg.login.ulVersion against its
- // MinVersionNum & CurVersionNum and, if the server accepted the
- // login, the client would check msg.loginAccept.ulVersion
- // against its MinVersionNum & CurVersionNum. Although the
- // version 1 client would display an error message when it
- // decided it could not support the server's version, the server
- // would refuse a login before that, never giving the client a
- // chance to notice and report this to the user.
- // To remedy this, the Postal Net version 2's server NEVER
- // rejects a client and, instaed, relies on the client to reject
- // it. This gives the client a chance to report the error.
- // Since under our new scheme we never send a LOGIN_DENY, we
- // know, if we receive a LOGIN_DENY, that we are dealing with a
- // version 1 server.
- //
- // Additionally, bit fifteen of the version number is set if on
- // a Mac platform so we can use the same mechanism to determine
- // if we are attempting to connect a Mac and a PC.
- //
- // Here's the actual scenarios and results for version conflicts:
- //
- // Server 1 vs Client 2 -- Server sends Client a LOGIN_DENY so
- // the client knows it's dealing with a version 1 server and
- // reports the problem.
- //
- // Server 2 vs Client 1 -- Server accepts connection. Client
- // drops, though, when it sees the server's version is 2 and
- // reports the problem.
- //
- // For future versions:
- //
- // Server 3 vs Client 1 -- Server accepts connection. Client
- // drops, though, when it sees the server's version is 3 and
- // reports the problem.
- //
- // Server 3 vs Client 2 -- Server accepts connection. Client
- // drops, though, when it sees the server's version is 3 and
- // reports the problem.
- //
- ////////////////////////////////////////////////////////////////
- // Check version number to see if we can support it (this also is a platform check).
- if ( (pmsg->msg.login.ulVersion >= CNetMsgr::MinVersionNum) && (pmsg->msg.login.ulVersion <= CNetMsgr::CurVersionNum))
- {
- // Compatible.
- }
- else
- {
- // Must set the type.
- pmsg->msg.err.ucType = NetMsg::ERR;
- pmsg->ucSenderID = Net::InvalidID;
- // Determine error type (possibilities are incompatible
- // versions and/or incompatible platforms).
- if ( (pmsg->msg.login.ulVersion & CNetMsgr::MacVersionBit) ^ (CNetMsgr::MinVersionNum & CNetMsgr::MacVersionBit) )
- {
- // One of us is a Mac and one is a PC.
- pmsg->msg.err.error = NetMsg::ServerPlatformMismatchError;
- }
- else
- {
- // Store version number from original message before we clobber it
- // by creating an error message in the same area (not sure if this
- // is really an issue but just in case).
- ULONG ulVersion = pmsg->msg.login.ulVersion;
- // Incompatible version number.
- pmsg->msg.err.error = NetMsg::ServerVersionMismatchError;
- pmsg->msg.err.ulParam = ulVersion & ~CNetMsgr::MacVersionBit;
- }
- // Return this error msg to caller
- bGotMsgForCaller = true;
- // We rely on the client to reject us but we report the error with this
- // opportunity.
- }
- // Note that we always allow the login, even if the version number and/or
- // platform are wrong -- see comment above.
- // Allow login, responding with our version number and the client's assigned ID
- msg.msg.loginAccept.ucType = NetMsg::LOGIN_ACCEPT;
- msg.msg.loginAccept.idAssigned = id;
- msg.msg.loginAccept.ulMagic = CNetMsgr::MagicNum;
- msg.msg.loginAccept.ulVersion = CNetMsgr::CurVersionNum;
- SendMsg(id, &msg);
- // Mark client as "logged in"
- m_aClients[id].m_bLoggedIn = true;
- }
- else
- {
- // Drop client
- DropClient(id);
- }
- }
- else
- {
- // Drop client
- DropClient(id);
- }
- }
- else
- {
- // Process message
- switch(pmsg->msg.nothing.ucType)
- {
- case NetMsg::NOTHING:
- break;
- case NetMsg::STAT:
- break;
- case NetMsg::ERR:
- // If a local client has been registered, and if this error came from that client,
- // then we abort the game, because we can't continue without the local client
- // (it seems unlikely that the user would want to sit around watching a blank screen).
- // If no local client was registered, it will be invalid, and this compare will fail.
- // If it isn't a local client, then we just drop the client that reported the error.
- if (id == m_idLocalClient)
- AbortGame(NetMsg::ErrorAbortedGame);
- else
- DropClient(id);
- // Return this msg to caller
- bGotMsgForCaller = true;
- break;
- case NetMsg::LOGOUT:
- // The client will send a LOGOUT if, after having it's LOGIN request accepted, it
- // determines that the server's version number is not acceptable. It may also
- // send this if it needs to abort at any time after it has sent a JOIN_REQ but
- // before it has received a response. In all cases, we simply call DropClient(),
- // which knows how to handle any situation.
- DropClient(id);
- break;
- case NetMsg::JOIN_REQ:
- if (!m_bGameStarted)
- {
- // Upgrade client's state
- m_aClients[id].m_state = CClient::Joined;
- // Add client info to database. Note that client's "peer address" is the same
- // as the one we're connected to, but with a different port number.
- m_aClients[id].m_address = m_aClients[id].m_msgr.GetAddress();
- RSocket::SetAddressPort(m_usBasePort + Net::FirstPeerPortOffset + id, &m_aClients[id].m_address);
- memcpy(m_aClients[id].m_acName, pmsg->msg.joinReq.acName, sizeof(m_aClients[id].m_acName));
- m_aClients[id].m_ucColor = pmsg->msg.joinReq.ucColor;
- m_aClients[id].m_ucTeam = pmsg->msg.joinReq.ucTeam;
- m_aClients[id].m_sBandwidth = pmsg->msg.joinReq.sBandwidth;
- // Tell client he was accepted
- msg.msg.joinAccept.ucType = NetMsg::JOIN_ACCEPT;
- SendMsg(id, &msg);
- // Send all clients (including new one) info about the new client
- msg.msg.joined.ucType = NetMsg::JOINED;
- msg.msg.joined.id = id;
- msg.msg.joined.address = m_aClients[id].m_address;
- memcpy(msg.msg.joined.acName, m_aClients[id].m_acName, sizeof(msg.msg.joined.acName));
- msg.msg.joined.ucColor = m_aClients[id].m_ucColor;
- msg.msg.joined.ucTeam = m_aClients[id].m_ucTeam;
- msg.msg.joined.sBandwidth = m_aClients[id].m_sBandwidth;
- SendMsg(&msg);
- // Send new client info about other joined clients
- for (Net::ID id2 = 0; id2 < Net::MaxNumIDs; id2++)
- {
- if ((id2 != id) && (m_aClients[id2].m_state == CClient::Joined))
- {
- msg.msg.joined.ucType = NetMsg::JOINED;
- msg.msg.joined.id = id2;
- msg.msg.joined.address = m_aClients[id2].m_address;
- memcpy(msg.msg.joined.acName, m_aClients[id2].m_acName, sizeof(msg.msg.joined.acName));
- msg.msg.joined.ucColor = m_aClients[id2].m_ucColor;
- msg.msg.joined.ucTeam = m_aClients[id2].m_ucTeam;
- msg.msg.joined.sBandwidth = m_aClients[id2].m_sBandwidth;
- SendMsg(id, &msg);
- }
- }
- // Send new client info about current game setup (if we know it yet!)
- if (m_bSetupGameValid)
- SendMsg(id, &m_msgSetupGame);
- // Return this msg to caller
- bGotMsgForCaller = true;
- }
- else
- {
- // Deny join request because we don't allow joins after game has started
- msg.msg.joinDeny.ucType = NetMsg::JOIN_DENY;
- msg.msg.joinDeny.ucReason = NetMsg::GameAlreadyStarted;
- SendMsg(id, &msg);
- // Drop client
- DropClient(id);
- }
- break;
- case NetMsg::CHANGE_REQ:
- // Change client's info in database
- memcpy(m_aClients[id].m_acName, pmsg->msg.changed.acName, sizeof(m_aClients[id].m_acName));
- m_aClients[id].m_ucColor = pmsg->msg.changed.ucColor;
- m_aClients[id].m_ucTeam = pmsg->msg.changed.ucTeam;
- // Send CHANGED message to all clients, including the one that requested the change
- msg.msg.changed.ucType = NetMsg::CHANGED;
- msg.msg.changed.id = id;
- memcpy(msg.msg.changed.acName, m_aClients[id].m_acName, sizeof(msg.msg.changed.acName));
- msg.msg.changed.ucColor = m_aClients[id].m_ucColor;
- msg.msg.changed.ucTeam = m_aClients[id].m_ucTeam;
- SendMsg(&msg);
- // Return this msg to caller
- bGotMsgForCaller = true;
- break;
- case NetMsg::DROP_REQ:
- // Drop the client -- DropClient() knows how to handle every situation
- DropClient(id);
- break;
- case NetMsg::DROP_ACK:
- // If we're doing a drop, handle this, otherwise ignore it
- if (!m_qDropIDs.IsEmpty())
- {
- // Make sure we only get one per client
- ASSERT(!m_aClients[id].m_bSentDropAck);
- // Set flag indicating that this client sent the message
- m_aClients[id].m_bSentDropAck = true;
- // Save info from message
- m_aClients[id].m_seqHighestDropeeInput = pmsg->msg.dropAck.seqLastDropeeInput;
- m_aClients[id].m_seqLastDoneFrame = pmsg->msg.dropAck.seqLastDoneFrame;
- // Check if all joined clients sent a DROP_ACK
- Net::ID id2;
- for (id2 = 0; id2 < Net::MaxNumIDs; id2++)
- {
- if ((m_aClients[id2].m_state == CClient::Joined) && (!m_aClients[id2].m_bSentDropAck))
- break;
- }
- if (id2 == Net::MaxNumIDs)
- {
- // Do the messy work required to figure out if any clients needs inputs from
- // other clients. The end result of this function is that if it needed input
- // data from a client, it asked for it and returns true. If not, it returns
- // false, which means we can immediately go on to the final step.
- if (GotAllDropAcks() == false)
- {
- // Finish the process of dropping client. If there's more clients in the drop
- // queue, this could also start the dropping of the next victim
- FinishDroppingClientDuringGame();
- }
- }
- }
- break;
- case NetMsg::INPUT_DATA:
- ASSERT(m_bWaitingForInputData);
- if (m_bWaitingForInputData)
- {
- // Make sure it's for the correct ID
- ASSERT(m_qDropIDs.PeekAtFront() == pmsg->msg.inputData.id);
- // No longer waiting for input data
- m_bWaitingForInputData = false;
- // Go through all the clients and feed inputs to those that need it. Note that
- // we simply forward the message we received. It may contain more data than
- // each client needs, but what the hell. This could be tuned some day to
- // be a bit more efficient by only sending as much as each client needs.
- for (Net::ID id = 0; id < Net::MaxNumIDs; id++)
- {
- if (m_aClients[id].m_state == CClient::Joined)
- {
- // If this client's highest dropee input is <= the highest frame, then he needs inputs
- if (SEQ_LT(m_aClients[id].m_seqHighestDropeeInput, m_seqHighestDoneFrame))
- SendMsg(id, pmsg);
- }
- }
- // Finish the process of dropping client. If there's more clients in the drop
- // queue, this could also start the dropping of the next victim
- FinishDroppingClientDuringGame();
- }
- break;
- case NetMsg::CHAT_REQ:
- // Send chat text to the clients specified by the mask
- msg.msg.chat.ucType = NetMsg::CHAT;
- strncpy(msg.msg.chat.acText, pmsg->msg.chatReq.acText, sizeof(msg.msg.chat.acText) );
- SendMsg(pmsg->msg.chatReq.u16Mask, &msg);
- // Return this msg to caller
- bGotMsgForCaller = true;
- break;
- case NetMsg::ABORT_GAME:
- break;
- case NetMsg::READY_REALM:
- // If not waiting for realm status messages, then ignore this
- if (m_bWaitingForRealmStatus)
- {
- // Make sure we only get one per client
- ASSERT(!m_aClients[id].m_bSentReadyRealm);
- // Set flag indicating that this client sent the message
- m_aClients[id].m_bSentReadyRealm = true;
- // Check if all joined clients sent a READY_REALM. Along the way, we need to
- // count the number of clients that have sent the message so far and we need
- // to create a mask that includes only those clients that sent the message.
- // These values are used to generate the PROGRESS_REALM message afterwards.
- Net::ID id2;
- short sNumExpected = 0;
- short sNumReady = 0;
- U16 mask = 0;
- for (id2 = 0; id2 < Net::MaxNumIDs; id2++)
- {
- mask = (mask >> 1) & 0x7fff;
- if (m_aClients[id2].m_state == CClient::Joined)
- {
- sNumExpected++;
- if (m_aClients[id2].m_bSentReadyRealm)
- {
- sNumReady++;
- mask |= 0x8000;
- }
- }
- }
- // This had to be disabled. The idea was great, with one major flaw -- what if the server is
- // the slow machine, so while it's off loading a level or something, all the clients have already
- // sent their READY_REALM messages and haven't gotten heard anything back from the server!?!?!
- // Fixing this would require that the server be called at all times, and that's very hard since
- // much of what the server does occurs on the GetMsg() call, which is difficult for the app to
- // deal with at every level of the code. It would be slightly easier for the app if all this work
- // were done silently by the Update() function, which is possible, but certainly not a trival
- // change at this level. By the way, another problem is that once the server DOES start getting
- // called again, it will have to deal with ALL of these messages at once! Very sucky.
- #if 0
- // Send PROGRESS_REALM message to all joined clients that have already responded
- // with their own READY_REALM messages. We tell each of them how many other
- // clients we're still waiting on. The idea behind using the mask is that it
- // cuts down on the number of messages we sent. We don't really want to send
- // them to clients that haven't responded yet since they are obviously busy doing
- // something else, and besides, with 16 clients, we would send 16 * 16 = 256
- // messages, which is a lot, but by cutting it down using the mask, we end up
- // with only 16+15+14+...+3+2+1 = 136, which is much better.
- msg.msg.progressRealm.ucType = NetMsg::PROGRESS_REALM;
- msg.msg.progressRealm.sNumReady = sNumReady;
- msg.msg.progressRealm.sNumNotReady = sNumExpected - sNumReady;
- SendMsg(mask, &msg);
- #endif
- // If we got through the whole list, then we got all the READY_REALM messages we need
- if (sNumReady == sNumExpected)
- {
- // Send START_REALM message to all joined clients
- msg.msg.startRealm.ucType = NetMsg::START_REALM;
- SendMsg(&msg);
- // Reset all related flags
- m_bWaitingForRealmStatus = false;
- for (Net::ID id2 = 0; id2 < Net::MaxNumIDs; id2++)
- m_aClients[id2].m_bSentReadyRealm = false;
- }
- }
- break;
- case NetMsg::BAD_REALM:
- // If not waiting for realm status messages, then ignore this
- if (m_bWaitingForRealmStatus)
- {
- // Reset all related flags
- m_bWaitingForRealmStatus = false;
- for (Net::ID id2 = 0; id2 < Net::MaxNumIDs; id2++)
- m_aClients[id2].m_bSentReadyRealm = false;
- // Abort game due to error
- AbortGame(NetMsg::ErrorAbortedGame);
- }
- break;
- // case FINISH_REALM:
- // break;
- case NetMsg::PING:
- // Simply echo the ping right back, and record the latest result
- SendMsg(id, pmsg);
- // m_lLatestPingTime = pmsg->msg.ping.lLatestPingTime;
- break;
- default:
- break;
- }
- }
- }
- // If we don't already have a message for the caller, check for listen error
- // (this has lower priority than a client message because accepting new clients
- // isn't as important as servicing existing ones)
- if (!bGotMsgForCaller)
- {
- if (m_socketListen.IsError())
- {
- // Generate a listen error message
- pmsg->msg.err.ucType = NetMsg::ERR;
- pmsg->msg.err.error = NetMsg::ListenError;
- pmsg->ucSenderID = Net::InvalidID;
- bGotMsgForCaller = true;
- }
- }
- // If we don't already have a message for the caller, generate a "nothing"
- // (this has the lowest priority of all)
- if (!bGotMsgForCaller)
- {
- pmsg->msg.nothing.ucType = NetMsg::NOTHING;
- pmsg->ucSenderID = Net::InvalidID;
- }
- }
- ////////////////////////////////////////////////////////////////////////////////
- // Send message to specified client
- ////////////////////////////////////////////////////////////////////////////////
- void CNetServer::SendMsg(
- Net::ID id, // In: ID to send message to
- NetMsg* pmsg) // In: Message to send
- {
- ASSERT(id != Net::InvalidID);
- ASSERT(id < Net::MaxNumIDs);
- m_aClients[id].m_msgr.SendMsg(pmsg);
- }
- ////////////////////////////////////////////////////////////////////////////////
- // Send message to specified group of clients - only Joined clients are allowed!
- ////////////////////////////////////////////////////////////////////////////////
- void CNetServer::SendMsg(
- U16 mask, // In: Bit-mask indicating who to send to
- NetMsg* pmsg) // In: Message to send
- {
- // Bit-mask determines who to send the message to
- for (Net::ID id = 0; id < Net::MaxNumIDs; id++)
- {
- if ((mask & 1) && (m_aClients[id].m_state == CClient::Joined))
- SendMsg(id, pmsg);
- mask = mask >> 1;
- }
- }
- ////////////////////////////////////////////////////////////////////////////////
- // Send message to all joined clients
- ////////////////////////////////////////////////////////////////////////////////
- void CNetServer::SendMsg(
- NetMsg* pmsg) // In: Message to send
- {
- for (Net::ID id = 0; id < Net::MaxNumIDs; id++)
- {
- if (m_aClients[id].m_state == CClient::Joined)
- SendMsg(id, pmsg);
- }
- }
- ////////////////////////////////////////////////////////////////////////////////
- // Determine if there is more data waiting to be sent. If there is data to
- // to be sent AND there is a send error, then that data can't be sent, so we
- // return false to indicate "no more data".
- ////////////////////////////////////////////////////////////////////////////////
- bool CNetServer::IsMoreToSend(void) // Returns true if more to send, false otherwise
- {
- bool bResult = false;
- for (Net::ID id = 0; id < Net::MaxNumIDs; id++)
- {
- if (m_aClients[id].m_msgr.IsMoreToSend())
- {
- bResult = true;
- break;
- }
- }
- return bResult;
- }
- ////////////////////////////////////////////////////////////////////////////////
- // Drop specified client
- ////////////////////////////////////////////////////////////////////////////////
- void CNetServer::DropClient(
- Net::ID id) // In: Client to drop
- {
- ASSERT(id != Net::InvalidID);
- ASSERT(id < Net::MaxNumIDs);
- switch(m_aClients[id].m_state)
- {
- case CClient::Unused:
- // This doesn't really make sense because once we're connected we should
- // also be in a state other than "unused".
- ASSERT(0);
- m_aClients[id].m_msgr.Disconnect(false);
- break;
- case CClient::Used:
- m_aClients[id].m_state = CClient::Unused;
- m_aClients[id].m_msgr.Disconnect(true);
- break;
- case CClient::Joined:
- // Whether or not the game has started makes a huge difference in how this is handled
- if (m_bGameStarted)
- {
- // If the queue is empty, we can start the drop process. Otherwise, we need
- // to add the new drop client to the drop queue.
- if (m_qDropIDs.IsEmpty())
- {
- // Add to queue (we use this as an indicator that we're dropping someone)
- m_qDropIDs.PutAtBack(id);
- // Start the process of dropping a client during a game
- StartDroppingClientDuringGame(id);
- }
- else
- {
- // Add client's ID to drop queue
- m_qDropIDs.PutAtBack(id);
- }
- }
- else
- {
- // Tell all clients to drop this client (including the one being dropped)
- NetMsg msg;
- msg.msg.dropped.ucType = NetMsg::DROPPED;
- msg.msg.dropped.id = id;
- msg.msg.dropped.sContext = -1;
- SendMsg(&msg);
- // Change dropped client's state to "unused" (only AFTER message was sent)
- m_aClients[id].m_state = CClient::Unused;
- }
- m_aClients[id].m_msgr.Disconnect(true);
- break;
- case CClient::Dropped:
- // Nothing to do (game must have already started for it to be in this state)
- break;
- }
- // Clear client's "logged in" flag
- m_aClients[id].m_bLoggedIn = false;
- }
- ////////////////////////////////////////////////////////////////////////////////
- // Send the specified game settings to all joined clients
- ////////////////////////////////////////////////////////////////////////////////
- void CNetServer::SetupGame(
- short sRealmNum, // In: Realm number
- const char* pszRealmFile, // In: Realm file name
- short sDifficulty, // In: Difficulty
- short sRejuvenate, // In: Rejuvenate flag
- short sTimeLimit, // In: Time limit in minutes, or negative if none
- short sKillLimit, // In: Kill limit, or negative if none
- short sCoopLevels, // In: Zero for deathmatch levels, non-zero for cooperative levels.
- short sCoopMode) // In: Zero for deathmatch mode, non-zero for cooperative mode.
- {
- // Setup a special START_GAME message that we keep around so we can send
- // it to clients as soon as they join. That way, they get better feedback.
- // Otherwise, they would have to wait until the next time this function
- // is called, which may not be all that often.
- m_msgSetupGame.msg.setupGame.ucType = NetMsg::SETUP_GAME;
- m_msgSetupGame.msg.setupGame.sRealmNum = sRealmNum;
- memcpy(m_msgSetupGame.msg.setupGame.acRealmFile, pszRealmFile, sizeof(m_msgSetupGame.msg.setupGame.acRealmFile));
- m_msgSetupGame.msg.setupGame.acRealmFile[sizeof(m_msgSetupGame.msg.setupGame.acRealmFile)-1] = 0;
- m_msgSetupGame.msg.setupGame.sDifficulty = sDifficulty;
- m_msgSetupGame.msg.setupGame.sRejuvenate = sRejuvenate;
- m_msgSetupGame.msg.setupGame.sTimeLimit = sTimeLimit;
- m_msgSetupGame.msg.setupGame.sKillLimit = sKillLimit;
- m_msgSetupGame.msg.setupGame.sCoopLevels = sCoopLevels;
- m_msgSetupGame.msg.setupGame.sCoopMode = sCoopMode;
-
- // Mark message as valid
- m_bSetupGameValid = true;
- SendMsg(&m_msgSetupGame);
- }
- ////////////////////////////////////////////////////////////////////////////////
- // Start game using specified settings
- ////////////////////////////////////////////////////////////////////////////////
- void CNetServer::StartGame(
- Net::ID idServer, // In: Server's client's ID
- short sRealmNum, // In: Realm number
- char* pszRealmFile, // In: Realm file name
- short sDifficulty, // In: Difficulty
- short sRejuvenate, // In: Rejuvenate flag
- short sTimeLimit, // In: Time limit in minutes, or negative if none
- short sKillLimit, // In: Kill limit, or negative if none
- short sCoopLevels, // In: Zero for deathmatch levels, non-zero for cooperative levels.
- short sCoopMode, // In: Zero for deathmatch mode, non-zero for cooperative mode.
- short sFrameTime, // In: Time per frame (in milliseconds)
- Net::SEQ seqMaxAhead) // In: Initial max ahead for input versus frame seq
- {
- ASSERT(!m_bGameStarted);
- // Set flag
- m_bGameStarted = true;
- // Tell all joined clients to start game
- NetMsg msg;
- msg.msg.startGame.ucType = NetMsg::START_GAME;
- msg.msg.startGame.idServer = idServer;
- msg.msg.startGame.sRealmNum = sRealmNum;
- memcpy(msg.msg.startGame.acRealmFile, pszRealmFile, sizeof(msg.msg.startGame.acRealmFile));
- msg.msg.startGame.acRealmFile[sizeof(msg.msg.startGame.acRealmFile)-1] = 0;
- msg.msg.startGame.sDifficulty = sDifficulty;
- msg.msg.startGame.sRejuvenate = sRejuvenate;
- msg.msg.startGame.sTimeLimit = sTimeLimit;
- msg.msg.startGame.sKillLimit = sKillLimit;
- msg.msg.startGame.sCoopLevels = sCoopLevels;
- msg.msg.startGame.sCoopMode = sCoopMode;
- msg.msg.startGame.sFrameTime = sFrameTime;
- msg.msg.startGame.seqMaxAhead = seqMaxAhead;
- SendMsg(&msg);
- // We need to wait for clients to respond with realm status messages
- m_bWaitingForRealmStatus = true;
- }
- ////////////////////////////////////////////////////////////////////////////////
- // Abort game
- ////////////////////////////////////////////////////////////////////////////////
- void CNetServer::AbortGame(
- unsigned char ucReason) // In: Why game was aborted
- {
- // Tell players to abort game
- NetMsg msg;
- msg.msg.abortGame.ucType = NetMsg::ABORT_GAME;
- msg.msg.abortGame.ucReason = ucReason;
- SendMsg(&msg);
- }
- ////////////////////////////////////////////////////////////////////////////////
- // Tell clients to go to the next realm when they reach the specified frame seq.
- // It is okay to call this multiple times -- "extra" calls are ignored.
- //
- // The return value is true if a new "next realm" sequence was initiated, and
- // false if there was already one in progress.
- ////////////////////////////////////////////////////////////////////////////////
- bool CNetServer::NextRealm(
- Net::SEQ seq) // In: The seq on which to go to next realm
- {
- // If we're already waiting, then we don't send this message. There are
- // two situations this can come up. First, the caller could have already
- // called us once, and is merely calling us over and over, which, as the
- // the function header comment says, is allowed. The second possibility
- // is that not all clients have responded to a much older attempt to
- // start the game or go to the next realm, and now the caller wants to go
- // on to yet another realm. In that situation, we can't accomodate the
- // caller -- we have to wait for all the clients to catch up to the previous
- // realm before we can go to the next one. All in all, this is rather
- // involved explanation for what works out to a simple check of a flag...
- bool bResult = true;
- if (!m_bWaitingForRealmStatus)
- {
- // Tell clients to go to the next realm when they reach this frame seq
- NetMsg msg;
- msg.msg.nextRealm.ucType = NetMsg::NEXT_REALM;
- msg.msg.nextRealm.seqHalt = seq;
- SendMsg(&msg);
- // We need to wait for clients to respond with realm status messages
- m_bWaitingForRealmStatus = true;
- }
- else
- {
- // There was already a "next realm" sequence in effect
- bResult = false;
- }
- return bResult;
- }
- ////////////////////////////////////////////////////////////////////////////////
- // Tell clients to proceed. This is used when all players are at a point where
- // they need to be told it's time to move on. There is NO attempt to
- // syncrhonize the clients, so this must only be used in non-critical sections
- // or at a point prior to a syncrhonized section. For instance, if all players
- // are viewing a dialog, the local user on the server might hit a key to go
- // on, at which point this function would be called to tell all the other
- // players to proceed.
- ////////////////////////////////////////////////////////////////////////////////
- void CNetServer::Proceed(void)
- {
- // Send PROCEED message to all joined clients
- NetMsg msg;
- msg.msg.proceed.ucType = NetMsg::PROCEED;
- SendMsg(&msg);
- }
- ////////////////////////////////////////////////////////////////////////////////
- // Start the process of dropping a client during a game
- ////////////////////////////////////////////////////////////////////////////////
- void CNetServer::StartDroppingClientDuringGame(
- Net::ID id)
- {
- // Tell all clients to drop this client (including the one being dropped)
- // Because there's something in the drop queue, GetMsg() will know that
- // it should be exptecting DROP_ACK messages from the remaining clients.
- NetMsg msg;
- msg.msg.dropped.ucType = NetMsg::DROPPED;
- msg.msg.dropped.id = id;
- msg.msg.dropped.sContext = 1;
- SendMsg(&msg);
- // Change dropped client's state to "dropped" (only AFTER message was sent)
- m_aClients[id].m_state = CClient::Dropped;
- // Clear "sent drop ack" flags for all clients
- for (Net::ID id2 = 0; id2 < Net::MaxNumIDs; id2++)
- m_aClients[id2].m_bSentDropAck = false;
- }
- ////////////////////////////////////////////////////////////////////////////////
- // Got all drop acks, now figure out what to do next
- //
- // A return value of true indicates that one or more clients needed inputs,
- // so the drop process is not yet complete. A return of false indicates that
- // no clients needed inputs, so we can proceed directly to the next phase.
- ////////////////////////////////////////////////////////////////////////////////
- bool CNetServer::GotAllDropAcks(void)
- {
- // Go through all the responses and find
- // (1) the lowest frame seq,
- // (2) the highest frame seq
- // (3) the highest known dropee input seq
- // To do this, we need to start out with some valid values, which we
- // get from the first client we come across. We can't do a typical thing
- // where you set the values to a very high or very low value to initial
- // it, because when comparing seq's, it is very important that they be
- // withing a small range of one another, or all bets are off.
- bool bFirst = true;
- m_seqHighestDoneFrame = 0;
- Net::ID idHighestDoneFrame = Net::InvalidID;
- Net::SEQ seqHighestDropeeInput = 0; // Used purely for debugging/verification
- Net::SEQ seqLowestDropeeInput = 0;
- Net::ID id;
- for (id = 0; id < Net::MaxNumIDs; id++)
- {
- if (m_aClients[id].m_state == CClient::Joined)
- {
- if (bFirst)
- {
- // Fill in the values from the first client we get to
- m_seqHighestDoneFrame = m_aClients[id].m_seqLastDoneFrame;
- idHighestDoneFrame = id;
- seqHighestDropeeInput = m_aClients[id].m_seqHighestDropeeInput;
- seqLowestDropeeInput = m_aClients[id].m_seqHighestDropeeInput;
- bFirst = false;
- }
- else
- {
- // If client's frame is >= highest frame, then this is the new highest frame
- if (SEQ_GTE(m_aClients[id].m_seqLastDoneFrame, m_seqHighestDoneFrame))
- {
- m_seqHighestDoneFrame = m_aClients[id].m_seqLastDoneFrame;
- idHighestDoneFrame = id;
- }
- // If client's input is >= highest input, then this is the new highest input
- if (SEQ_GTE(m_aClients[id].m_seqHighestDropeeInput, seqHighestDropeeInput))
- {
- seqHighestDropeeInput = m_aClients[id].m_seqHighestDropeeInput;
- }
- // If client's input is <= lowest input, then this is the new lowest input
- if (SEQ_LTE(m_aClients[id].m_seqHighestDropeeInput, seqLowestDropeeInput))
- {
- seqLowestDropeeInput = m_aClients[id].m_seqHighestDropeeInput;
- }
- }
- }
- }
- // If we don't come out of this with a valid ID, we're in deep shit
- ASSERT(idHighestDoneFrame != Net::InvalidID);
- // Make sure someone has the dropee's input up to and including the highest frame.
- // This should always be the case because if someone did a frame, they must have
- // had the dropee's input for that frame.
- ASSERT(SEQ_GTE(seqHighestDropeeInput, m_seqHighestDoneFrame));
- // Go through all the clients and see if any one of them needs inputs
- bool bSomeoneNeedsInputs = false;
- for (id = 0; id < Net::MaxNumIDs; id++)
- {
- if (m_aClients[id].m_state == CClient::Joined)
- {
- // If this client's highest dropee input is <= the highest frame, then he needs inputs
- if (SEQ_LT(m_aClients[id].m_seqHighestDropeeInput, m_seqHighestDoneFrame))
- {
- bSomeoneNeedsInputs = true;
- break;
- }
- }
- }
- // If someone needs inputs, get them from whoever had the highest done frame
- if (bSomeoneNeedsInputs)
- {
- // We need the inputs starting at the lowest dropee input + 1 (they already
- // have the lowest one) up to the highest done frame.
- NetMsg msg;
- msg.msg.inputReq.ucType = NetMsg::INPUT_REQ;
- msg.msg.inputReq.id = m_qDropIDs.PeekAtFront();
- msg.msg.inputReq.seqStart = (Net::SEQ)(seqLowestDropeeInput + (Net::SEQ)1);
- msg.msg.inputReq.sNum = (Net::SEQ)(m_seqHighestDoneFrame - seqLowestDropeeInput);
- SendMsg(idHighestDoneFrame, &msg);
- // Set flag to indicate that we're waiting for inputs
- m_bWaitingForInputData = true;
- }
- return bSomeoneNeedsInputs;
- }
- ////////////////////////////////////////////////////////////////////////////////
- // Finish the process of dropping client. If there's more clients in the drop
- // queue, this could also start the dropping of the next victim
- ////////////////////////////////////////////////////////////////////////////////
- void CNetServer::FinishDroppingClientDuringGame(void)
- {
- // Send INPUT_MARK message to all clients so they can mark the last active
- // input seq of the dropped client.
- NetMsg msg;
- msg.msg.inputMark.ucType = NetMsg::INPUT_MARK;
- msg.msg.inputMark.id = m_qDropIDs.PeekAtFront();
- msg.msg.inputMark.seqMark = m_seqHighestDoneFrame;
- SendMsg(&msg);
- // This client is now fully dropped, so remove it from the drop queue
- m_qDropIDs.GetFromFront();
- // Check if there's any more clients in the queue. If not, everyone can
- // resume playing. Otherwise, drop the next client in the queue.
- if (m_qDropIDs.IsEmpty())
- {
- // Send START_REALM message to all joined clients
- msg.msg.startRealm.ucType = NetMsg::START_REALM;
- SendMsg(&msg);
- // Reset all related flags
- m_bWaitingForRealmStatus = false;
- for (Net::ID id2 = 0; id2 < Net::MaxNumIDs; id2++)
- m_aClients[id2].m_bSentReadyRealm = false;
- }
- else
- {
- // Start the process of dropping the next client in the drop queue.
- StartDroppingClientDuringGame(m_qDropIDs.PeekAtFront());
- }
- }
- ////////////////////////////////////////////////////////////////////////////////
- // EOF
- ////////////////////////////////////////////////////////////////////////////////
|