1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202
|
////////////////////////////////////////////////////////////////////////////////
//
// 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
////////////////////////////////////////////////////////////////////////////////
int16_t CNetServer::Startup( // Returns 0 if successfull, non-zero otherwise
uint16_t usPort, // In: Server base port number
char* pszHostName, // In: Host name (max size is MaxHostName!!!)
RSocket::BLOCK_CALLBACK callback) // In: Blocking callback
{
int16_t 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)
{
int16_t 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
int16_t 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];
int32_t lReceived;
RSocket::Address address;
int16_t 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
int32_t lBytesSent;
int16_t 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).
uint32_t 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;
int16_t sNumExpected = 0;
int16_t 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(
int16_t sRealmNum, // In: Realm number
const char* pszRealmFile, // In: Realm file name
int16_t sDifficulty, // In: Difficulty
int16_t sRejuvenate, // In: Rejuvenate flag
int16_t sTimeLimit, // In: Time limit in minutes, or negative if none
int16_t sKillLimit, // In: Kill limit, or negative if none
int16_t sCoopLevels, // In: Zero for deathmatch levels, non-zero for cooperative levels.
int16_t 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
int16_t sRealmNum, // In: Realm number
char* pszRealmFile, // In: Realm file name
int16_t sDifficulty, // In: Difficulty
int16_t sRejuvenate, // In: Rejuvenate flag
int16_t sTimeLimit, // In: Time limit in minutes, or negative if none
int16_t sKillLimit, // In: Kill limit, or negative if none
int16_t sCoopLevels, // In: Zero for deathmatch levels, non-zero for cooperative levels.
int16_t sCoopMode, // In: Zero for deathmatch mode, non-zero for cooperative mode.
int16_t 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(
uint8_t 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
////////////////////////////////////////////////////////////////////////////////
|