Field report (first internet-join attempt): both join bats 'hard crash' on the remote machine. Root cause was procedural -- the host session was not running -- but the failure PRESENTATION was the bug: Release Fail() is a bare abort(), so a dead relay = silent process death + instantly-closing console window. - L4NET: the two player-facing dead-ends (LAN discovery no-answer, seat request unreachable/full) now show a MessageBox saying what happened and what to do before exiting (verified live against a dead relay: 'BattleTech -- can't join the game' appears). - join bats: echo + pause after exit so the window stays readable; join_lan.bat header now says it is LAN-only (the remote user ran both -- join_lan can never work over the internet). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
4558 lines
140 KiB
C++
4558 lines
140 KiB
C++
//===========================================================================//
|
|
// File: l4net.cpp //
|
|
// Project: MUNGA Brick: Network Manager //
|
|
// Contents: Interface specification for network brick //
|
|
//---------------------------------------------------------------------------//
|
|
// Date Who Modification //
|
|
// -------- --- ---------------------------------------------------------- //
|
|
// 03/02/95 GAC Initial coding. //
|
|
// 04/08/95 GAC Netnow Removed, TCP/NetNub support added //
|
|
// NOTE: old netnow code is saved in L4NetNow.* //
|
|
//---------------------------------------------------------------------------//
|
|
// Copyright (C) 1994-1995, Virtual World Entertainment, Inc. //
|
|
// PROPRIETARY AND CONFIDENTIAL //
|
|
//===========================================================================//
|
|
|
|
#include "mungal4.h"
|
|
#pragma hdrstop
|
|
|
|
#include "l4app.h"
|
|
#include "l4host.h"
|
|
#include "l4net.h"
|
|
#include "..\munga\mission.h"
|
|
#include "..\munga\notation.h"
|
|
//#include <netnub.hpp>
|
|
|
|
|
|
#if defined(TRACE_SEND_PACKET)
|
|
static BitTrace Send_Packet("Send Packet");
|
|
#define SET_SEND_PACKET() Send_Packet.Set()
|
|
#define CLEAR_SEND_PACKET() Send_Packet.Clear()
|
|
#else
|
|
#define SET_SEND_PACKET()
|
|
#define CLEAR_SEND_PACKET()
|
|
#endif
|
|
|
|
#if defined(TRACE_CHECK_BUFFERS)
|
|
static BitTrace Check_Buffers("Check Buffers");
|
|
#define SET_CHECK_BUFFERS() Check_Buffers.Set()
|
|
#define CLEAR_CHECK_BUFFERS() Check_Buffers.Clear()
|
|
#else
|
|
#define SET_CHECK_BUFFERS()
|
|
#define CLEAR_CHECK_BUFFERS()
|
|
#endif
|
|
|
|
#if defined(TRACE_CALL_NETNUB)
|
|
static BitTrace Call_Netnub("Call Netnub");
|
|
#define SET_CALL_NETNUB() Call_Netnub.Set()
|
|
#define CLEAR_CALL_NETNUB() Call_Netnub.Clear()
|
|
#else
|
|
#define SET_CALL_NETNUB()
|
|
#define CLEAR_CALL_NETNUB()
|
|
#endif
|
|
|
|
#if defined(TRACE_LOST_DATA)
|
|
static BitTrace Lost_Data("Lost Data");
|
|
#define SET_LOST_DATA() Lost_Data.Set()
|
|
#define CLEAR_LOST_DATA() Lost_Data.Clear()
|
|
#else
|
|
#define SET_LOST_DATA()
|
|
#define CLEAR_LOST_DATA()
|
|
#endif
|
|
|
|
#define BATCHED_TRANSMIT True // true sends broadcast messages with a single call to netnub
|
|
#define BATCHED_RECEIVE True // true receives from all streams with a single call to netnub
|
|
#define REPORT_LOST_DATA False // Print a message if data is lost (may be toxic)
|
|
|
|
#if !defined(MESSAGE_BUFFERING)
|
|
#define MESSAGE_BUFFERING True // true uses munga level buffering for dropped packets
|
|
#endif
|
|
|
|
#define CONSOLE_NET_PORT 1501 // Port number the console will connect on
|
|
#define GAME_NET_PORT 1502 // Port number the game will connect on
|
|
|
|
//
|
|
// D1 RELAY-MODE wire constants (must match tools/btconsole.py --relay).
|
|
// TCP envelope on the game relay connection: { int32 route; uint32 length; }
|
|
// followed by `length` payload bytes (the byte-exact classic frame:
|
|
// NetworkPacketHeader + Receiver::Message). UDP envelope (later phase):
|
|
// { int32 route; int32 fromHost; uint32 seq; } + frame.
|
|
//
|
|
enum
|
|
{
|
|
RELAY_ROUTE_BROADCAST = -1, // to every registered pod except the sender
|
|
RELAY_ROUTE_HELLO = -2, // first frame: magic + our hostID
|
|
RELAY_ROUTE_PEER_UP = -3, // relay->pod: hostID registered
|
|
RELAY_ROUTE_PEER_DOWN = -4, // relay->pod: hostID gone
|
|
RELAY_ROUTE_UDP_ACK = -5, // relay->pod: UDP HELLO acknowledged
|
|
RELAY_ROUTE_SEAT_REQUEST= -6, // pod->relay: assign me a free seat
|
|
RELAY_ROUTE_SEAT_ASSIGN = -7, // relay->pod: int32 hostID + NUL tag
|
|
RELAY_ROUTE_SEAT_FULL = -8 // relay->pod: roster full
|
|
};
|
|
#define RELAY_HELLO_MAGIC 0x31525442 // 'BTR1' little-endian
|
|
// LAN auto-discovery (BT_RELAY=auto): probe broadcast on this UDP port; the
|
|
// relay answers "BTR1HERE" + <u16 consolePort>. Must match btconsole.py.
|
|
#define RELAY_DISCOVERY_PORT 15999
|
|
#define RELAY_DISC_PROBE "BTR1DISC"
|
|
#define RELAY_DISC_REPLY "BTR1HERE"
|
|
struct RelayTcpEnvelope
|
|
{
|
|
int route;
|
|
unsigned int length;
|
|
};
|
|
struct RelayUdpEnvelope
|
|
{
|
|
int route;
|
|
int fromHost;
|
|
unsigned int sequence;
|
|
};
|
|
|
|
char GlobalEggFileName[80]; // NASTY HACK !!! should suffice till we get console up though
|
|
|
|
//struct RMREG //real mode registers structure
|
|
//{
|
|
//unsigned long
|
|
// edi,
|
|
// esi,
|
|
// ebp,
|
|
// reserved,//0
|
|
// ebx,
|
|
// edx,
|
|
// ecx,
|
|
// eax;
|
|
//unsigned short
|
|
// flags, //0
|
|
// es,
|
|
// ds,
|
|
// fs, //0
|
|
// gs, //0
|
|
// ip,
|
|
// cs,
|
|
// sp,
|
|
// ss;
|
|
//};
|
|
|
|
//
|
|
// !!!! this stuff is also nasty but is currently required to be global for the
|
|
// assembly language stuff to work properly.
|
|
//
|
|
//Netcom_Ptr
|
|
// Net_Common_Ptr = NULL;
|
|
//
|
|
//unsigned short
|
|
// *Buffer_Length_Ptr; // Where the network common buffer length field is located
|
|
//
|
|
//short
|
|
// *Function_Ptr, // Where the network common function field is located
|
|
// offs, // offset of Real Mode Netcom struct
|
|
// baseadr, // segment of Real Mode Netcom struct
|
|
// intno,
|
|
// RMFunction_Off, // Offset of function field for real mode structure
|
|
// RMBuffer_Length_Off; // Offset of buffer length field for real mode structure
|
|
//long
|
|
// RMverno; // Real mode version number
|
|
////
|
|
//// !!!! These are the assembyly code functions we will be calling
|
|
////
|
|
//extern "C"
|
|
//{
|
|
// void mapRMBuff(void); //get selector for rm buffer
|
|
// void getRMNumbers(void); //get interr and version numbers from rm buffer
|
|
// void getRMBuff(void); //copy block from rm buffer
|
|
// void setRMBuff(void); //copy block to rm buffer
|
|
//};
|
|
|
|
//#############################################################################
|
|
// Shared Data Support
|
|
//
|
|
L4NetworkManager::SharedData
|
|
L4NetworkManager::DefaultData(
|
|
L4NetworkManager::GetClassDerivations(),
|
|
L4NetworkManager::GetMessageHandlers()
|
|
);
|
|
|
|
Derivation* L4NetworkManager::GetClassDerivations()
|
|
{
|
|
static Derivation classDerivations(NetworkManager::GetClassDerivations(), "L4NetworkManager");
|
|
return &classDerivations;
|
|
}
|
|
|
|
//#############################################################################
|
|
// Messaging support
|
|
//
|
|
const Receiver::HandlerEntry
|
|
L4NetworkManager::MessageHandlerEntries[]=
|
|
{
|
|
MESSAGE_ENTRY(L4NetworkManager,ReceiveEggFile),
|
|
MESSAGE_ENTRY(L4NetworkManager,AcknowledgeEggFile),
|
|
MESSAGE_ENTRY(L4NetworkManager,HostConnected),
|
|
MESSAGE_ENTRY(L4NetworkManager,HostDisconnected)
|
|
};
|
|
|
|
Receiver::MessageHandlerSet& L4NetworkManager::GetMessageHandlers()
|
|
{
|
|
static Receiver::MessageHandlerSet messageHandlers(ELEMENTS(L4NetworkManager::MessageHandlerEntries), L4NetworkManager::MessageHandlerEntries, NetworkManager::GetMessageHandlers());
|
|
return messageHandlers;
|
|
}
|
|
|
|
//
|
|
//#############################################################################
|
|
// Code for the network manager class
|
|
//#############################################################################
|
|
//
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Constructor for the L4NetworkManager
|
|
//
|
|
//WinSock support :ADB 01/06/07
|
|
L4NetworkManager::L4NetworkManager():
|
|
NetworkManager(DefaultData),
|
|
messageBuffer(this)
|
|
{
|
|
unsigned long
|
|
network_common_flat_address;
|
|
|
|
network_common_flat_address =
|
|
((L4Application *)application)->GetNetworkCommonFlatAddress();
|
|
lastHostIteratorPosition = 0;
|
|
nextOpenHostID = FirstLegalHostID+1; // Reserve first legal id for console
|
|
currentNetworkState = NormalState;
|
|
myConsoleHost = NULL;
|
|
eggAcknowledged = False;
|
|
networkStartupMode = SlaveMode;
|
|
numberOfMungaHostsConnected = 0;
|
|
numberOfConsoleHostsConnected = 0;
|
|
remoteHostCount = 0x7FFFFFFF;
|
|
wsaData = new WSADATA;
|
|
|
|
// D1 relay-mode defaults: inert until BT_RELAY is parsed at ctor end.
|
|
relayMode = False;
|
|
relaySelf[0] = '\0';
|
|
memset(&relayConsoleAddress, 0, sizeof(relayConsoleAddress));
|
|
memset(&relayGameAddress, 0, sizeof(relayGameAddress));
|
|
relayGameSocket = INVALID_SOCKET;
|
|
relayUdpSocket = INVALID_SOCKET;
|
|
relayPadTail = 0;
|
|
relayConsoleDialedOnce = False;
|
|
udpUp = False;
|
|
relayLocalHostID = 0;
|
|
lastUdpKeepaliveTick = 0;
|
|
lastUdpHelloTick = 0;
|
|
udpTxSeq = 0;
|
|
memset(udpRxSeq, 0, sizeof(udpRxSeq));
|
|
currentNetworkMode = NetworkManager::ReliableMode;
|
|
|
|
//
|
|
// If there is no net common, set things up for single user mode
|
|
//
|
|
if(network_common_flat_address == 0)
|
|
{
|
|
//Net_Common_Ptr = NULL;
|
|
wsaData = NULL;
|
|
|
|
//
|
|
//---------------------------------------------------------------------
|
|
// Retrieve the egg as specified on the command line, and send it as an
|
|
// egg message
|
|
//---------------------------------------------------------------------
|
|
//
|
|
const char* egg_name = l4_application->GetEggNotationFileName();
|
|
if (!egg_name || !strlen(egg_name))
|
|
{
|
|
DEBUG_STREAM << "ERROR: No source exists for egg!\n" << std::flush;
|
|
PostQuitMessage(AbortExitCodeID);
|
|
}
|
|
networkEggNotationFile = new NotationFile(egg_name);
|
|
Register_Object(networkEggNotationFile);
|
|
networkEggNotationFile->WriteFile("last.egg");
|
|
currentNetworkState = NormalState;
|
|
|
|
ReceiveEggFileMessage egg_message(-1, 10, "local egg", 10);
|
|
application->Post(DefaultEventPriority, this, &egg_message);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Setup the local netcom structure and some pointers to things inside it
|
|
//
|
|
//Net_Common_Ptr = new Netcom;
|
|
//Register_Pointer(Net_Common_Ptr);
|
|
//Function_Ptr = &Net_Common_Ptr->Function;
|
|
//Buffer_Length_Ptr = &Net_Common_Ptr->Buffer_Length;
|
|
|
|
int iResult = WSAStartup(MAKEWORD(2,2), wsaData);
|
|
if(iResult != NO_ERROR)
|
|
{
|
|
DEBUG_STREAM << "ERROR: WSAStartup() failed with " << iResult << "!\n" << std::flush;
|
|
WSACleanup();
|
|
PostQuitMessage(AbortExitCodeID);
|
|
}
|
|
consoleListenerSocket = NULL;
|
|
gameListenerSocket = NULL;
|
|
|
|
//
|
|
// Setup the global pointers that the netnub real/protected mode interface
|
|
// requires to work.
|
|
//
|
|
//offs = (short)(network_common_flat_address & 0x0000000f);
|
|
//baseadr = (short)(network_common_flat_address>>4);
|
|
//mapRMBuff(); //get selector for baseadr
|
|
//RMFunction_Off = (short)(offs
|
|
// + sizeof(Net_Common_Ptr->Version_Number)
|
|
// + sizeof(Net_Common_Ptr->Interrupt_Number));
|
|
//RMBuffer_Length_Off = (short)(RMFunction_Off
|
|
// + sizeof(Net_Common_Ptr->Function)
|
|
// + sizeof(Net_Common_Ptr->Status));
|
|
|
|
//getRMNumbers(); // get Interrupt_Number , Version_Number
|
|
//intno = Net_Common_Ptr->Interrupt_Number;
|
|
//RMverno = Net_Common_Ptr->Version_Number;
|
|
|
|
//
|
|
// Check for right network common number
|
|
////
|
|
//if(RMverno != NETCOM_VERSION)
|
|
//{
|
|
// DEBUG_STREAM << "Netnub version " << RMverno
|
|
// << ", code version " << NETCOM_VERSION << endl;
|
|
// Fail("MUNGA was compiled with an old version of netnub.h");
|
|
//}
|
|
|
|
//
|
|
// Get my network address from the netnub (also verify's we are connected)
|
|
//
|
|
addresses = GetMyAddress();
|
|
if(addresses == NULL)
|
|
{
|
|
DEBUG_STREAM<<"ERROR: GetMyAddress() failed!\n";
|
|
WSACleanup();
|
|
Fail("Unable to initialize the network");
|
|
}
|
|
|
|
//
|
|
// Force us into reliable mode
|
|
//
|
|
Mode(NetworkManager::ReliableMode);
|
|
|
|
//
|
|
// D1 RELAY MODE: BT_RELAY=<host>:<consolePort> switches the whole net layer
|
|
// to outbound-only (the pod dials the relay for console AND game traffic).
|
|
// The host part may be a DNS name (internet play: one shared hostname).
|
|
// BT_SELF must name this pod's exact [pilots] entry (NIC matching cannot
|
|
// identify us across NAT). Parsed here, AFTER WSAStartup (gethostbyname).
|
|
//
|
|
{
|
|
const char *relay_env = getenv("BT_RELAY");
|
|
if (relay_env != NULL && relay_env[0] != '\0')
|
|
{
|
|
if (_stricmp(relay_env, "auto") == 0)
|
|
{
|
|
//
|
|
// LAN AUTO-DISCOVERY: broadcast a probe; the relay answers
|
|
// with its console port; its source IP is the relay host.
|
|
// Zero-config for anyone on the operator's LAN (the 90s
|
|
// arcade model: cabinets find the operator station).
|
|
//
|
|
if (!RelayDiscover(&relayConsoleAddress))
|
|
{
|
|
// Release Fail() is a bare abort() -- without this box a
|
|
// player just sees a "crash" (field report 2026-07-18)
|
|
MessageBoxA(NULL,
|
|
"No game server answered on this network.\n\n"
|
|
"join_lan.bat only works on the SAME network as the\n"
|
|
"operator's machine. Joining over the internet?\n"
|
|
"Use join.bat instead.\n\n"
|
|
"On the operator's own network: make sure the\n"
|
|
"operator has pressed Start Session, then try again.",
|
|
"BattleTech -- can't find the game server",
|
|
MB_OK | MB_ICONERROR);
|
|
Fail("BT_RELAY=auto: no relay answered on this LAN\n");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
char host_part[128];
|
|
int relay_port = 0;
|
|
const char *colon = strrchr(relay_env, ':');
|
|
if (colon == NULL || colon == relay_env
|
|
|| (relay_port = atoi(colon + 1)) <= 0
|
|
|| (colon - relay_env) >= (int)sizeof(host_part))
|
|
{
|
|
Fail("BT_RELAY must be <host>:<consolePort> or 'auto'\n");
|
|
}
|
|
memcpy(host_part, relay_env, colon - relay_env);
|
|
host_part[colon - relay_env] = '\0';
|
|
|
|
unsigned long relay_ip = inet_addr(host_part);
|
|
if (relay_ip == INADDR_NONE)
|
|
{
|
|
hostent *he = gethostbyname(host_part);
|
|
if (he == NULL || he->h_addr_list[0] == NULL)
|
|
{
|
|
DEBUG_STREAM << "ERROR: BT_RELAY host '" << host_part
|
|
<< "' did not resolve\n" << std::flush;
|
|
Fail("BT_RELAY host unresolvable\n");
|
|
}
|
|
relay_ip = *(unsigned long *)he->h_addr_list[0];
|
|
}
|
|
relayConsoleAddress.sin_family = AF_INET;
|
|
relayConsoleAddress.sin_addr.S_un.S_addr = relay_ip;
|
|
relayConsoleAddress.sin_port = htons((unsigned short)relay_port);
|
|
}
|
|
relayGameAddress = relayConsoleAddress;
|
|
relayGameAddress.sin_port =
|
|
htons((unsigned short)(ntohs(relayConsoleAddress.sin_port) + 1));
|
|
|
|
//
|
|
// Identity: an explicit BT_SELF claims a specific seat; absent
|
|
// (or "auto"), the RELAY assigns the next free roster seat --
|
|
// players never need to know a player number.
|
|
//
|
|
const char *self_env = getenv("BT_SELF");
|
|
if (self_env == NULL || self_env[0] == '\0'
|
|
|| _stricmp(self_env, "auto") == 0)
|
|
{
|
|
if (!RelayRequestSeat())
|
|
{
|
|
char seat_error[256];
|
|
sprintf(seat_error,
|
|
"Could not join the game at %s:%d.\n\n"
|
|
"Either the operator has not started the session\n"
|
|
"yet, the game is full, or the server is not\n"
|
|
"reachable from here.\n\n"
|
|
"Check with the operator and run join.bat again.",
|
|
inet_ntoa(relayConsoleAddress.sin_addr),
|
|
(int)ntohs(relayConsoleAddress.sin_port));
|
|
MessageBoxA(NULL, seat_error,
|
|
"BattleTech -- can't join the game",
|
|
MB_OK | MB_ICONERROR);
|
|
Fail("relay seat request failed (roster full or relay "
|
|
"unreachable)\n");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Str_Copy(relaySelf, self_env, sizeof(relaySelf));
|
|
}
|
|
relayMode = True;
|
|
DEBUG_STREAM << "[relay] mode ON: relay "
|
|
<< inet_ntoa(relayConsoleAddress.sin_addr) << ":"
|
|
<< (int)ntohs(relayConsoleAddress.sin_port)
|
|
<< " (game " << (int)ntohs(relayGameAddress.sin_port)
|
|
<< "), self='" << relaySelf << "'\n" << std::flush;
|
|
}
|
|
}
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// RelayDiscover -- BT_RELAY=auto: find the relay on the LAN by UDP broadcast.
|
|
// Sends the probe to 255.255.255.255 AND 127.0.0.1 (same-box sessions --
|
|
// broadcast does not reliably loop back on Windows), ~5 attempts at 1s. The
|
|
// answering relay's source address + its advertised console port fill
|
|
// *console_endpoint.
|
|
//
|
|
Logical L4NetworkManager::RelayDiscover(SOCKADDR_IN *console_endpoint)
|
|
{
|
|
SOCKET probe_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
if (probe_socket == INVALID_SOCKET)
|
|
{
|
|
return False;
|
|
}
|
|
BOOL allow_broadcast = TRUE;
|
|
setsockopt(probe_socket, SOL_SOCKET, SO_BROADCAST,
|
|
(char *)&allow_broadcast, sizeof(allow_broadcast));
|
|
unsigned long enable = 1;
|
|
ioctlsocket(probe_socket, FIONBIO, &enable);
|
|
|
|
SOCKADDR_IN broadcast_addr, loopback_addr;
|
|
memset(&broadcast_addr, 0, sizeof(broadcast_addr));
|
|
broadcast_addr.sin_family = AF_INET;
|
|
broadcast_addr.sin_addr.S_un.S_addr = INADDR_BROADCAST;
|
|
broadcast_addr.sin_port = htons(RELAY_DISCOVERY_PORT);
|
|
loopback_addr = broadcast_addr;
|
|
loopback_addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
|
|
|
|
const int probe_len = (int)strlen(RELAY_DISC_PROBE);
|
|
const int reply_len = (int)strlen(RELAY_DISC_REPLY);
|
|
Logical found = False;
|
|
|
|
DEBUG_STREAM << "[relay] BT_RELAY=auto: probing the LAN (udp/"
|
|
<< RELAY_DISCOVERY_PORT << ")...\n" << std::flush;
|
|
for (int attempt = 0; attempt < 5 && !found; ++attempt)
|
|
{
|
|
sendto(probe_socket, RELAY_DISC_PROBE, probe_len, 0,
|
|
(const sockaddr *)&broadcast_addr, sizeof(broadcast_addr));
|
|
sendto(probe_socket, RELAY_DISC_PROBE, probe_len, 0,
|
|
(const sockaddr *)&loopback_addr, sizeof(loopback_addr));
|
|
|
|
unsigned long wait_until = GetTickCount() + 1000UL;
|
|
while (GetTickCount() < wait_until && !found)
|
|
{
|
|
fd_set read_set;
|
|
FD_ZERO(&read_set);
|
|
FD_SET(probe_socket, &read_set);
|
|
timeval tv;
|
|
tv.tv_sec = 0;
|
|
tv.tv_usec = 100000;
|
|
if (select(0, &read_set, NULL, NULL, &tv) != 1)
|
|
{
|
|
continue;
|
|
}
|
|
char reply[32];
|
|
SOCKADDR_IN from;
|
|
int from_len = sizeof(from);
|
|
int received = recvfrom(probe_socket, reply, sizeof(reply), 0,
|
|
(sockaddr *)&from, &from_len);
|
|
if (received >= reply_len + 2
|
|
&& memcmp(reply, RELAY_DISC_REPLY, reply_len) == 0)
|
|
{
|
|
unsigned short console_port;
|
|
memcpy(&console_port, reply + reply_len, 2);
|
|
*console_endpoint = from;
|
|
console_endpoint->sin_port = htons(console_port);
|
|
DEBUG_STREAM << "[relay] discovered relay at "
|
|
<< inet_ntoa(from.sin_addr) << ":" << (int)console_port
|
|
<< "\n" << std::flush;
|
|
found = True;
|
|
}
|
|
}
|
|
}
|
|
closesocket(probe_socket);
|
|
return found;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// L4NetworkManager::MakeNotationFileEgg This routine creates the console remote
|
|
// host
|
|
//
|
|
//WinSock support :ADB 01/06/07
|
|
void L4NetworkManager::CreateConsoleHost()
|
|
{
|
|
//unsigned long console_socket;
|
|
|
|
//
|
|
// Die if there is no network common around
|
|
//
|
|
if (wsaData == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
unsigned short networkPort = (unsigned short)((L4Application *)application)->GetNetworkCommonFlatAddress();
|
|
|
|
//
|
|
// Initialize a bunch of stuff to sane states
|
|
//
|
|
nextOpenHostID = FirstLegalHostID+1; // Reserve first legal id for console
|
|
networkStartupMode = SlaveMode; // We're a slave, (not a simulated console)
|
|
currentNetworkState = ConsoleOnly; // Only accept messages from the console
|
|
numberOfConsoleHostsConnected = 0; // Shouldn't be any consoles yet
|
|
numberOfMungaHostsConnected = 0; // No game machines either
|
|
|
|
//
|
|
// D1 RELAY MODE: dial OUT to the relay's console port instead of listening
|
|
// (outbound = NAT-friendly; the relay terminates the same legacy console
|
|
// protocol, so the egg/ack/launch handlers are untouched). The console
|
|
// host starts in OpeningConnectionStatus; CheckBuffers' existing Opening
|
|
// completion path (getpeername + address match, which holds by
|
|
// construction) fires the HostConnectedMessage.
|
|
//
|
|
if (relayMode)
|
|
{
|
|
// First dial waits for the relay to come up (like mesh peers wait on
|
|
// each other); a mid-game RE-dial (console-loss handler) is bounded
|
|
// short so a dead relay can't hang the pod -- the app's no-console
|
|
// self-launch guard (APP.cpp CheckLoadMessageHandler) and the mesh
|
|
// console-loss semantics both tolerate a console-less pod.
|
|
int timeout_seconds = relayConsoleDialedOnce ? 3 : 60;
|
|
SOCKET relay_console_socket =
|
|
RelayDialTcp(relayConsoleAddress, timeout_seconds);
|
|
if (relay_console_socket == INVALID_SOCKET && !relayConsoleDialedOnce)
|
|
{
|
|
DEBUG_STREAM << "ERROR: BT_RELAY set but the relay console port "
|
|
"did not answer within " << timeout_seconds << "s\n" << std::flush;
|
|
PostQuitMessage(AbortExitCodeID);
|
|
return;
|
|
}
|
|
relayConsoleDialedOnce = True;
|
|
myConsoleHost = new L4Host(
|
|
FirstLegalHostID,
|
|
ConsoleHostType,
|
|
&relayConsoleAddress,
|
|
relay_console_socket,
|
|
"Console");
|
|
Register_Object(myConsoleHost);
|
|
myConsoleHost->SetConnectStatus(
|
|
(relay_console_socket == INVALID_SOCKET)
|
|
? L4Host::NoNetworkConnectionStatus // console-less continue
|
|
: L4Host::OpeningConnectionStatus);
|
|
Check(application);
|
|
Check(application->GetHostManager());
|
|
application->GetHostManager()->AdoptRemoteHost(myConsoleHost);
|
|
networkEggNotationFile = NULL;
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Post a listen for a console with an unknown network address
|
|
// this will return a stream that the console will eventually be on
|
|
//
|
|
//WinSock support :ADB 01/06/07
|
|
//console_socket = OpenConnection(
|
|
// NETNUB_TCP_LISTEN,
|
|
// CONSOLE_NET_PORT, // Local port
|
|
// 0, // Remote port (don't care in this case)
|
|
// 0); // Network address (don't care in this case)
|
|
consoleListenerSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
if(consoleListenerSocket == INVALID_SOCKET)
|
|
{
|
|
DEBUG_STREAM << "ERROR: Could not create console listener socket; socket() failed with " << WSAGetLastError() << "!\n" << std::flush;
|
|
WSACleanup();
|
|
PostQuitMessage(AbortExitCodeID);
|
|
return;
|
|
}
|
|
sockaddr_in localEndpoint;
|
|
memset(&localEndpoint, 0, sizeof(localEndpoint));
|
|
localEndpoint.sin_family = AF_INET;
|
|
localEndpoint.sin_port = htons(networkPort);
|
|
localEndpoint.sin_addr.S_un.S_addr = INADDR_ANY;
|
|
if(bind(consoleListenerSocket, (sockaddr*)&localEndpoint, sizeof(localEndpoint)))
|
|
{
|
|
DEBUG_STREAM << "ERROR: Could not bind console listener socket; bind() failed with " << WSAGetLastError() << "!\n" << std::flush;
|
|
WSACleanup();
|
|
PostQuitMessage(AbortExitCodeID);
|
|
return;
|
|
}
|
|
if(listen(consoleListenerSocket, 1))
|
|
{
|
|
DEBUG_STREAM << "ERROR: Could not listen on console listener socket; listen() failed with " << WSAGetLastError() << "!\n" << std::flush;
|
|
WSACleanup();
|
|
PostQuitMessage(AbortExitCodeID);
|
|
return;
|
|
}
|
|
//set to non blocking
|
|
unsigned long enable = 1;
|
|
if(ioctlsocket(consoleListenerSocket, FIONBIO, &enable))
|
|
{
|
|
DEBUG_STREAM << "ERROR: Could not set console listener socket to nonblocking; ioctlsocket() failed with " << WSAGetLastError() << "!\n" << std::flush;
|
|
WSACleanup();
|
|
PostQuitMessage(AbortExitCodeID);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Register the console host with the host manager, this will insure it gets
|
|
// polled by CheckBuffers for connection. On connect we'll get a message
|
|
// routed to the network manager. Note that this host has an unspecified
|
|
// net address since we won't know it till the console connects. This
|
|
// must be the ONLY host we are listening for or things will fail!!!
|
|
//
|
|
SOCKADDR_IN address;
|
|
address.sin_family = AF_INET;
|
|
address.sin_addr.S_un.S_addr = NullNetworkAddress;
|
|
myConsoleHost = new L4Host(
|
|
FirstLegalHostID,
|
|
ConsoleHostType,
|
|
&address, // Don't know the net address till it connects.
|
|
INVALID_SOCKET,
|
|
"Console");
|
|
|
|
//
|
|
// Do the usual stuff to the host, register it, and have the host manager adopt it
|
|
//
|
|
Register_Object(myConsoleHost);
|
|
myConsoleHost->SetConnectStatus(L4Host::ListeningConnectionStatus);
|
|
Check(application);
|
|
Check(application->GetHostManager());
|
|
application->GetHostManager()->AdoptRemoteHost(myConsoleHost);
|
|
networkEggNotationFile = NULL;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// L4NetworkManager::LoadMission This routine is called when a mission starts to
|
|
// allow the network management system to do stuff (potentially establishing
|
|
// communications with all the participants and such like)
|
|
//
|
|
void
|
|
L4NetworkManager::StartConnecting(Mission *mission)
|
|
{
|
|
int listen; // True if we should listen, false for connect
|
|
//WinSock support :ADB 01/06/07
|
|
//unsigned long
|
|
SOCKET socket_ptr; // temporary place to store the socket for a host
|
|
SOCKADDR_IN net_address; // temporary place to store resolved net address
|
|
net_address.sin_family = AF_INET;
|
|
L4Host *my_l4host;
|
|
// the game port is the console listening port + 1
|
|
unsigned short localGamePort = (unsigned short)((L4Application *)application)->GetNetworkCommonFlatAddress() + 1;
|
|
|
|
//
|
|
// This should be entered with no network connections to other game hosts up,
|
|
// so we initialize this count to zero.
|
|
//
|
|
numberOfMungaHostsConnected = 0;
|
|
|
|
//
|
|
// Create the host iterator for the mission egg
|
|
//
|
|
Mission::HostIterator mission_host_iterator(mission);
|
|
MissionHostData *mission_host_data;
|
|
|
|
//
|
|
// If there is no net common, set things up for single user mode
|
|
//
|
|
//WinSock support :ADB 01/06/07
|
|
//if (Net_Common_Ptr == NULL)
|
|
if(wsaData == NULL)
|
|
{
|
|
//
|
|
// Make the local host for single user testing, we give the host ID#1
|
|
// and assign the first symbolic name in the egg to it.
|
|
//
|
|
mission_host_data = mission_host_iterator.ReadAndNext();
|
|
Check(mission_host_data);
|
|
|
|
SOCKADDR_IN address;
|
|
address.sin_family = AF_INET;
|
|
address.sin_family = NullNetworkAddress;
|
|
my_l4host = new L4Host(
|
|
1, // Host ID
|
|
mission_host_data->GetHostType(), // Host Type
|
|
&address, // Net Address
|
|
INVALID_SOCKET, // Socket address
|
|
mission_host_data->GetAddressString());
|
|
Register_Object(my_l4host);
|
|
Check(application);
|
|
Check(application->GetHostManager());
|
|
application->GetHostManager()->AdoptLocalHost(my_l4host);
|
|
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// Now, since this host creator is for stand-alone mode, send the load
|
|
// mission message to the application
|
|
//--------------------------------------------------------------------
|
|
//
|
|
Check(application);
|
|
Application::Message
|
|
load_message(
|
|
Application::LoadMissionMessageID,
|
|
sizeof(Application::Message)
|
|
);
|
|
application->Post(DefaultEventPriority, application, &load_message);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Iterate through all the host addresses in the egg and create all the host
|
|
// structures, note that there MUST be an entry in the egg for us.
|
|
//
|
|
listen = False;
|
|
remoteHostCount = 0;
|
|
int bufferSize = sizeof(SOCKADDR_IN);
|
|
while ((mission_host_data = mission_host_iterator.ReadAndNext()) != NULL)
|
|
{
|
|
Check(mission_host_data);
|
|
|
|
//
|
|
// Try to resolve this address to an IP address
|
|
//
|
|
CString host_name(mission_host_data->GetAddressString());
|
|
//WinSock support :ADB 01/06/07
|
|
//VERIFY: are these IP addresses?
|
|
//net_address = ResolveAddress(host_name);
|
|
//net_address = inet_addr((char *)host_name);
|
|
WSAStringToAddressA((LPSTR)host_name, AF_INET, NULL, (LPSOCKADDR)&net_address, &bufferSize);
|
|
if (net_address.sin_port == 0)
|
|
net_address.sin_port = htons(localGamePort);
|
|
|
|
//
|
|
// See if this host is us or someone else
|
|
//
|
|
// D1 RELAY MODE: local-NIC matching cannot identify us across NAT (our
|
|
// public address differs from every NIC), so the pod self-identifies by
|
|
// the BT_SELF string matching its [pilots] entry EXACTLY (the entries
|
|
// become arbitrary unique ip:port-shaped tags; ports/NICs irrelevant --
|
|
// which also lets N instances share one box with identical -net).
|
|
//
|
|
//WinSock support :ADB 01/06/07
|
|
bool addressFound = false;
|
|
if (relayMode)
|
|
{
|
|
addressFound = (_stricmp((const char *)host_name, relaySelf) == 0);
|
|
}
|
|
else
|
|
for(int i=0; i<num_addresses; i++)
|
|
{
|
|
if(addresses[i] == net_address.sin_addr.S_un.S_addr && localGamePort == ntohs(net_address.sin_port))
|
|
{
|
|
addressFound = true;
|
|
break;
|
|
}
|
|
}
|
|
//if(net_address == address)
|
|
if(addressFound)
|
|
{
|
|
//
|
|
// Create the local host, socket is set to null since we can't send to
|
|
// ourselves.
|
|
//
|
|
my_l4host = new L4Host(
|
|
nextOpenHostID,
|
|
mission_host_data->GetHostType(),
|
|
//WinSock support :ADB 01/06/07
|
|
//address,
|
|
&net_address,
|
|
INVALID_SOCKET,
|
|
host_name);
|
|
Register_Object(my_l4host);
|
|
listen = True;
|
|
Check(application);
|
|
Check(application->GetHostManager());
|
|
application->GetHostManager()->AdoptLocalHost(my_l4host);
|
|
}
|
|
else if (relayMode)
|
|
{
|
|
//
|
|
// D1 RELAY MODE: peers are VIRTUAL hosts -- no per-peer socket, no
|
|
// connect/listen. They sit in NoNetworkConnectionStatus (which the
|
|
// CheckBuffers loop skips) until the relay's PEER_UP control frame
|
|
// synthesizes the HostConnectedMessage that flips them OnLine. The
|
|
// hostID assignment (egg order) and remoteHostCount stay identical
|
|
// to the mesh, so the "All connections completed!" gate and the
|
|
// whole app ladder run UNMODIFIED.
|
|
//
|
|
remoteHostCount++;
|
|
my_l4host = new L4Host(
|
|
nextOpenHostID,
|
|
mission_host_data->GetHostType(),
|
|
&net_address,
|
|
INVALID_SOCKET,
|
|
host_name);
|
|
Register_Object(my_l4host);
|
|
my_l4host->SetConnectStatus(L4Host::NoNetworkConnectionStatus);
|
|
Check(application);
|
|
Check(application->GetHostManager());
|
|
application->GetHostManager()->AdoptRemoteHost(my_l4host);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// See what we should do to connect to this remote host (open or listen)
|
|
//
|
|
remoteHostCount++;
|
|
if(listen)
|
|
{
|
|
//WinSock support :ADB 01/06/07
|
|
socket_ptr = OpenConnection(NETNUB_TCP_LISTEN, localGamePort, 0, net_address.sin_addr.S_un.S_addr);
|
|
}
|
|
else
|
|
socket_ptr = OpenConnection(NETNUB_TCP_OPEN, localGamePort, ntohs(net_address.sin_port), net_address.sin_addr.S_un.S_addr);
|
|
|
|
if (!listen && socket_ptr == INVALID_SOCKET)
|
|
DEBUG_STREAM << "Could not open connection to " << host_name << ".\n" << std::flush;
|
|
|
|
//
|
|
// Now we can create the remote host and get the application to adopt
|
|
// it
|
|
//
|
|
my_l4host = new L4Host(
|
|
nextOpenHostID,
|
|
mission_host_data->GetHostType(),
|
|
&net_address,
|
|
socket_ptr,
|
|
host_name);
|
|
Register_Object(my_l4host);
|
|
my_l4host->SetConnectStatus(
|
|
(listen)
|
|
? L4Host::ListeningConnectionStatus
|
|
: L4Host::OpeningConnectionStatus
|
|
);
|
|
Check(application);
|
|
Check(application->GetHostManager());
|
|
application->GetHostManager()->AdoptRemoteHost(my_l4host);
|
|
}
|
|
nextOpenHostID++;
|
|
}
|
|
|
|
//
|
|
// D1 RELAY MODE: one outbound game connection to the relay carries every
|
|
// peer. BT_SELF must have matched an egg entry (the local host drives our
|
|
// hostID + fromHost stamping).
|
|
//
|
|
if (relayMode)
|
|
{
|
|
if (!listen)
|
|
{
|
|
Fail("BT_SELF matched no [pilots] entry in the egg\n");
|
|
}
|
|
ConnectRelayGame(application->GetHostManager()->GetLocalHostID());
|
|
}
|
|
|
|
//
|
|
// All the hosts are created, the connects/listens done.
|
|
//
|
|
if(networkStartupMode == SlaveMode)
|
|
{
|
|
//
|
|
// We are a slave (got our egg from the network) so we will
|
|
// acknowledge the egg now and wait for the connects to finish
|
|
//
|
|
#if defined(LAB_ONLY)
|
|
DEBUG_STREAM << "Sending egg Acknowledgement\n" << flush;
|
|
#endif
|
|
AcknowledgeEggFileMessage myAcknowledgeEgg;
|
|
Send(
|
|
&myAcknowledgeEgg,
|
|
NetworkClient::NetworkManagerClientID,
|
|
myConsoleHost->GetHostID()
|
|
);
|
|
|
|
//
|
|
//-----------------------------------------------------------------------
|
|
// Send the load mission message to the application... if an egg was sent
|
|
//-----------------------------------------------------------------------
|
|
//
|
|
if (numberOfMungaHostsConnected >= remoteHostCount)
|
|
{
|
|
Check(application);
|
|
Application::Message
|
|
load_message(
|
|
Application::LoadMissionMessageID,
|
|
sizeof(Application::Message)
|
|
);
|
|
application->Post(DefaultEventPriority, application, &load_message);
|
|
}
|
|
}
|
|
|
|
// Console simulator that would be here has been moved to end of file if needed
|
|
|
|
else
|
|
{
|
|
Fail("host is in an illegal startup mode\n");
|
|
}
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// L4NetworkManager::Shutdown This routine is called when a mission ends to
|
|
// allow the network management system to do stuff (like disconnecting network
|
|
// connections between pods and so on)
|
|
//
|
|
Logical
|
|
L4NetworkManager::Shutdown()
|
|
{
|
|
Host
|
|
*base_host;
|
|
L4Host
|
|
*my_l4host;
|
|
unsigned long
|
|
network_common_flat_address;
|
|
|
|
network_common_flat_address = l4_application->GetNetworkCommonFlatAddress();
|
|
|
|
if (networkEggNotationFile)
|
|
{
|
|
Unregister_Object(networkEggNotationFile);
|
|
delete networkEggNotationFile;
|
|
networkEggNotationFile = NULL;
|
|
}
|
|
|
|
//
|
|
// If there is no net common, all we do is deregister the local host and return
|
|
//
|
|
if(network_common_flat_address == 0)
|
|
{
|
|
//
|
|
// Deregister local host
|
|
//
|
|
base_host = application->GetHostManager()->OrphanLocalHost();
|
|
my_l4host = Cast_Object(L4Host*, base_host);
|
|
Check(my_l4host);
|
|
Unregister_Object(my_l4host);
|
|
delete my_l4host;
|
|
return True;
|
|
}
|
|
|
|
//
|
|
// D1 RELAY MODE: drop the relay game/UDP sockets (virtual game hosts hold
|
|
// INVALID_SOCKET; the loop below CloseConnection()s them harmlessly).
|
|
//
|
|
if (relayMode)
|
|
{
|
|
if (relayGameSocket != INVALID_SOCKET)
|
|
{
|
|
closesocket(relayGameSocket);
|
|
relayGameSocket = INVALID_SOCKET;
|
|
}
|
|
if (relayUdpSocket != INVALID_SOCKET)
|
|
{
|
|
closesocket(relayUdpSocket);
|
|
relayUdpSocket = INVALID_SOCKET;
|
|
}
|
|
relayPadTail = 0;
|
|
udpUp = False;
|
|
}
|
|
|
|
//
|
|
// If we get here there was a network setup, we must drop all connections
|
|
// deregister all the remote hosts and then deregister the local host.
|
|
// Start by closing all connections.
|
|
//
|
|
Check(application);
|
|
HostManager *host_mgr = application->GetHostManager();
|
|
Check(host_mgr);
|
|
HostManager::RemoteHostIterator remote_hosts(host_mgr);
|
|
while ((base_host = remote_hosts.ReadAndNext()) != NULL)
|
|
{
|
|
my_l4host = Cast_Object(L4Host*, base_host);
|
|
Check(my_l4host);
|
|
|
|
//
|
|
//------------------------------
|
|
// Don't delete the console host
|
|
//------------------------------
|
|
//
|
|
if (my_l4host->GetHostType() == ConsoleHostType)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Don't do a close if the system says we're not connected (means close was
|
|
// allready done by disconnect handler
|
|
//
|
|
if(my_l4host->GetConnectStatus() != Host::NoNetworkConnectionStatus)
|
|
{
|
|
CloseConnection(my_l4host->GetNetworkSocket());
|
|
}
|
|
// deregister this host with the host manager
|
|
host_mgr->OrphanRemoteHost(my_l4host);
|
|
Unregister_Object(my_l4host);
|
|
delete my_l4host;
|
|
}
|
|
|
|
//
|
|
// Deregister local host
|
|
//
|
|
base_host = host_mgr->OrphanLocalHost();
|
|
if (base_host)
|
|
{
|
|
my_l4host = Cast_Object(L4Host*, base_host);
|
|
Check(my_l4host);
|
|
Unregister_Object(my_l4host);
|
|
delete my_l4host;
|
|
}
|
|
|
|
//
|
|
// Reinitialize this in case we start another game
|
|
//
|
|
remoteHostCount = 0x7FFFFFFF;
|
|
nextOpenHostID = FirstLegalHostID+1; // Reserve first legal id for console
|
|
networkStartupMode = SlaveMode; // We're a slave, (not a simulated console)
|
|
currentNetworkState = ConsoleOnly; // Only accept messages from the console
|
|
numberOfConsoleHostsConnected = 0; // Shouldn't be any consoles yet
|
|
numberOfMungaHostsConnected = 0; // No game machines either
|
|
return True;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Destructor for the L4NetworkManager
|
|
//
|
|
L4NetworkManager::~L4NetworkManager()
|
|
{
|
|
//WinSock support :ADB 01/06/07
|
|
//if(Net_Common_Ptr)
|
|
if(wsaData)
|
|
{
|
|
Check(application);
|
|
HostManager *host_mgr = application->GetHostManager();
|
|
Check(host_mgr);
|
|
HostManager::RemoteHostIterator remote_hosts(host_mgr);
|
|
Host *base_host;
|
|
while ((base_host = remote_hosts.ReadAndNext()) != NULL)
|
|
{
|
|
L4Host *my_l4host = Cast_Object(L4Host*, base_host);
|
|
Check(my_l4host);
|
|
|
|
//
|
|
// Don't do a close if the system says we're not connected (means close was
|
|
// allready done by disconnect handler
|
|
//
|
|
if(my_l4host->GetConnectStatus() != Host::NoNetworkConnectionStatus)
|
|
{
|
|
CloseConnection(my_l4host->GetNetworkSocket());
|
|
}
|
|
// deregister this host with the host manager
|
|
host_mgr->OrphanRemoteHost(my_l4host);
|
|
Unregister_Object(my_l4host);
|
|
delete my_l4host;
|
|
}
|
|
|
|
WSACleanup();
|
|
delete wsaData;
|
|
wsaData = NULL;
|
|
//Unregister_Pointer(Net_Common_Ptr);
|
|
//delete Net_Common_Ptr;
|
|
//Net_Common_Ptr = NULL;
|
|
}
|
|
|
|
if(addresses)
|
|
delete[] addresses;
|
|
addresses = NULL;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// L4NetworkManager::ReceiveEggFileMessageHandler
|
|
//
|
|
void
|
|
L4NetworkManager::ReceiveEggFileMessageHandler(
|
|
ReceiveEggFileMessage* EggMessage
|
|
)
|
|
{
|
|
#if defined(LAB_ONLY)
|
|
DEBUG_STREAM << "Received Egg message #" << EggMessage->sequenceNumber
|
|
<< endl;
|
|
#endif
|
|
|
|
//
|
|
// An egg sequence number of -1 means that the egg is posted locally, and
|
|
// should just call create mission
|
|
//
|
|
if (EggMessage->sequenceNumber == -1)
|
|
{
|
|
application->CreateMission(networkEggNotationFile);
|
|
return;
|
|
}
|
|
|
|
if (EggMessage->sequenceNumber == 0)
|
|
{
|
|
//
|
|
// Make a buffer
|
|
//
|
|
eggTempBuffer = new char[EggMessage->notationFileLength];
|
|
Register_Pointer(eggTempBuffer);
|
|
eggTempNext = 0;
|
|
}
|
|
|
|
//
|
|
// Write the egg data into the buffer
|
|
//
|
|
memcpy(
|
|
(eggTempBuffer+eggTempNext),
|
|
EggMessage->notationData,
|
|
EggMessage->thisMessageLength
|
|
);
|
|
eggTempNext += EggMessage->thisMessageLength;
|
|
|
|
//
|
|
// If we don't have all the data, return and wait for more
|
|
//
|
|
if(eggTempNext < EggMessage->notationFileLength)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//
|
|
// We've got all the data, make the notation file
|
|
//
|
|
networkEggNotationFile = new NotationFile();
|
|
Register_Object(networkEggNotationFile);
|
|
networkEggNotationFile->ReadText(eggTempBuffer, eggTempNext);
|
|
#if defined(LAB_ONLY)
|
|
DEBUG_STREAM << "Created egg\n";
|
|
#endif
|
|
networkEggNotationFile->WriteFile("last.egg");
|
|
currentNetworkState = NormalState;
|
|
|
|
//
|
|
// Now turn the notation file into a mission
|
|
//
|
|
application->CreateMission(networkEggNotationFile);
|
|
|
|
//
|
|
// Get rid of the ram buffer now that we're done with it
|
|
//
|
|
Unregister_Pointer(eggTempBuffer);
|
|
delete eggTempBuffer;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// L4NetworkManager::AcknowledgeEggFileMessageHandler
|
|
//
|
|
void
|
|
L4NetworkManager::AcknowledgeEggFileMessageHandler(
|
|
AcknowledgeEggFileMessage* )
|
|
{
|
|
#if defined(LAB_ONLY)
|
|
DEBUG_STREAM << "\nReceived egg acknowledged message\n";
|
|
#endif
|
|
eggAcknowledged = True;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// L4NetworkManager::HostConnectedMessageHandler
|
|
//
|
|
void
|
|
L4NetworkManager::HostConnectedMessageHandler(
|
|
HostConnectedMessage* HostConnected
|
|
)
|
|
{
|
|
//
|
|
// Get a pointer to the L4 host
|
|
//
|
|
Check(application);
|
|
Check(application->GetHostManager());
|
|
Host* connected_host =
|
|
application->GetHostManager()->GetRemoteHost(HostConnected->hostID);
|
|
L4Host* l4connected_host = Cast_Object(L4Host*, connected_host);
|
|
Check(l4connected_host);
|
|
|
|
//
|
|
// Set the host connection status to on line and increment the
|
|
// counter of hosts online.
|
|
//
|
|
l4connected_host->SetConnectStatus(L4Host::OnLineConnectionStatus);
|
|
switch(l4connected_host->GetHostType())
|
|
{
|
|
case GameMachineHostType:
|
|
numberOfMungaHostsConnected++;
|
|
DEBUG_STREAM << "Connected to GameMachineHost at ";
|
|
break;
|
|
case CameraShipHostType:
|
|
numberOfMungaHostsConnected++;
|
|
DEBUG_STREAM << "Connected to CameraShipHost at ";
|
|
break;
|
|
case MissionReviewHostType:
|
|
numberOfMungaHostsConnected++;
|
|
DEBUG_STREAM << "Connected to MissionReviewHost at ";
|
|
break;
|
|
case ConsoleHostType:
|
|
numberOfConsoleHostsConnected++;
|
|
DEBUG_STREAM << "Connected to ConsoleHost at ";
|
|
break;
|
|
default:
|
|
Fail("L4NetworkManager::HostConnectedMessageHandler - unknown host type");
|
|
break;
|
|
}
|
|
|
|
// commenting out because Verify doesn't do anything anymore --RB 10/28/08
|
|
////
|
|
//// Cross check the network address with the one in the host (just in case)
|
|
////
|
|
//Verify(
|
|
// l4connected_host->GetNetworkAddress() == HostConnected->networkAddress
|
|
//);
|
|
|
|
char addressString[44];
|
|
DWORD bufferSize = sizeof(addressString);
|
|
WSAAddressToStringA((LPSOCKADDR)&HostConnected->networkAddress, sizeof(SOCKADDR_IN), NULL, addressString, &bufferSize);
|
|
DEBUG_STREAM << addressString << std::endl << std::flush;
|
|
|
|
if (numberOfMungaHostsConnected >= remoteHostCount)
|
|
{
|
|
DEBUG_STREAM << "All connections completed!\n" << std::flush;
|
|
Marker("MUNGA MARKER--All connections completed!\n");
|
|
|
|
//
|
|
//--------------------------------------------------------------------
|
|
// Now, since this host creator is for stand-alone mode, send the load
|
|
// mission message to the application
|
|
//--------------------------------------------------------------------
|
|
//
|
|
Check(application);
|
|
Application::Message
|
|
load_message(
|
|
Application::LoadMissionMessageID,
|
|
sizeof(Application::Message)
|
|
);
|
|
application->Post(DefaultEventPriority, application, &load_message);
|
|
}
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// L4NetworkManager::HostDisconnectedMessageHandler
|
|
// This message should eventually be generated in CheckBuffers, right now it
|
|
// is actually sent over the network and is assumed only to be sent by the
|
|
// console just prior to it disconnecting. It clues us do locally close
|
|
// and destroy the console host so the game will start normally even if the
|
|
// console isn't around.
|
|
//
|
|
void L4NetworkManager::HostDisconnectedMessageHandler(HostDisconnectedMessage* HostDisconnected)
|
|
{
|
|
//
|
|
// Get a pointer to the L4 host
|
|
//
|
|
Check(application->GetHostManager());
|
|
Host* connected_host = application->GetHostManager()->GetRemoteHost(HostDisconnected->hostID);
|
|
L4Host* l4connected_host = Cast_Object(L4Host*, connected_host);
|
|
Check(l4connected_host);
|
|
//
|
|
// Set the host connection status to off line, get the network address so we
|
|
// can announce it later, then close the connection.
|
|
//
|
|
l4connected_host->SetConnectStatus(L4Host::NoNetworkConnectionStatus);
|
|
SOCKADDR_IN temp_net_address = *l4connected_host->GetNetworkAddress();
|
|
CloseConnection(HostDisconnected->streamPointer);
|
|
//
|
|
// See what type of host it was that disconnected
|
|
//
|
|
switch(l4connected_host->GetHostType())
|
|
{
|
|
case GameMachineHostType:
|
|
numberOfMungaHostsConnected--;
|
|
DEBUG_STREAM<<"\nDisconnected from GameMachineHost at ";
|
|
break;
|
|
case CameraShipHostType:
|
|
numberOfMungaHostsConnected--;
|
|
DEBUG_STREAM<<"\nDisconnected from CameraShipHost at ";
|
|
break;
|
|
case MissionReviewHostType:
|
|
numberOfMungaHostsConnected--;
|
|
DEBUG_STREAM<<"\nDisconnected from MissionReviewHost at ";
|
|
break;
|
|
case ConsoleHostType:
|
|
{
|
|
//
|
|
// Knock down the number of consoles online
|
|
//
|
|
numberOfConsoleHostsConnected--;
|
|
DEBUG_STREAM<<"\nDisconnected from ConsoleHost at ";
|
|
//
|
|
// Post a listen for a console so it can reconnect if it wants to.
|
|
//
|
|
// FIX (task #50, 2026-07-15): this used to close the GAME listener here
|
|
// (shutdown/closesocket(gameListenerSocket)) on a CONSOLE disconnect -- a
|
|
// naming bug: the comment above + the commented-out OpenConnection below
|
|
// intended to re-post a CONSOLE listen, which CreateConsoleHost() (below)
|
|
// already does. Closing the game listener on console loss needlessly
|
|
// bounced the game-accept path (harmless to already-established peer
|
|
// sockets -- which is why a live 2-node match keeps replicating through a
|
|
// console loss -- but wrong, and it would have blocked a NEW peer from
|
|
// joining after a console cycle). Removed; CreateConsoleHost re-listens.
|
|
//unsigned long console_socket = OpenConnection(
|
|
// NETNUB_TCP_LISTEN,
|
|
// ((L4Application *)application)->GetNetworkCommonFlatAddress(), // Local port
|
|
// 0, // Remote port (don't care in this case)
|
|
// 0); // Network address (don't care in this case)
|
|
//
|
|
// Destroy the console host and recreate it, this resets all the
|
|
// internal buffers and pointers so check_buffers won't go crazy
|
|
//
|
|
application->GetHostManager()->OrphanRemoteHost(l4connected_host);
|
|
Unregister_Object(l4connected_host);
|
|
delete l4connected_host;
|
|
#if 1
|
|
CreateConsoleHost();
|
|
/*SOCKADDR_IN address;
|
|
address.sin_family = AF_INET;
|
|
address.sin_family = NullNetworkAddress;
|
|
l4connected_host = new L4Host(
|
|
FirstLegalHostID,
|
|
ConsoleHostType,
|
|
&address,
|
|
console_socket,
|
|
"Console");
|
|
Register_Object(l4connected_host);
|
|
l4connected_host->SetConnectStatus(L4Host::ListeningConnectionStatus);
|
|
application->GetHostManager()->AdoptRemoteHost(l4connected_host);
|
|
myConsoleHost = l4connected_host;*/
|
|
#else
|
|
myConsoleHost = 0;
|
|
#endif
|
|
break;
|
|
}
|
|
default:
|
|
Fail("L4NetworkManager::HostDisconnectedMessageHandler - unknown host type");
|
|
break;
|
|
}
|
|
|
|
char addressString[44];
|
|
DWORD bufferSize = sizeof(addressString);
|
|
WSAAddressToStringA((LPSOCKADDR)&temp_net_address, sizeof(SOCKADDR_IN), NULL, addressString, &bufferSize);
|
|
DEBUG_STREAM << addressString << "\n" << std::flush;
|
|
}
|
|
|
|
//#############################################################################
|
|
// D1 RELAY MODE support units. Everything below is dead unless BT_RELAY set.
|
|
//#############################################################################
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// RelayRequestSeat -- SERVER-ASSIGNED SEATS: no BT_SELF means "give me any
|
|
// free seat". A short throwaway connection to the relay game port sends
|
|
// SEAT_REQUEST; the relay reserves the lowest unclaimed roster seat and
|
|
// answers SEAT_ASSIGN {int32 hostID, NUL-terminated tag}. The tag becomes
|
|
// relaySelf and everything downstream (egg self-match, HELLO) proceeds as if
|
|
// BT_SELF had been set. SEAT_FULL / timeout => False.
|
|
//
|
|
Logical L4NetworkManager::RelayRequestSeat()
|
|
{
|
|
SOCKET seat_socket = RelayDialTcp(relayGameAddress, 60);
|
|
if (seat_socket == INVALID_SOCKET)
|
|
{
|
|
return False;
|
|
}
|
|
Logical got_seat = False;
|
|
RelayTcpEnvelope request;
|
|
request.route = RELAY_ROUTE_SEAT_REQUEST;
|
|
request.length = 0;
|
|
if (RelaySendAll(seat_socket, (const char *)&request, sizeof(request)))
|
|
{
|
|
char reply[sizeof(RelayTcpEnvelope) + 80];
|
|
int have = 0;
|
|
unsigned long deadline = GetTickCount() + 10000UL;
|
|
while (GetTickCount() < deadline && !got_seat)
|
|
{
|
|
fd_set read_set;
|
|
FD_ZERO(&read_set);
|
|
FD_SET(seat_socket, &read_set);
|
|
timeval tv;
|
|
tv.tv_sec = 0;
|
|
tv.tv_usec = 200000;
|
|
if (select(0, &read_set, NULL, NULL, &tv) != 1)
|
|
{
|
|
continue;
|
|
}
|
|
int received = recv(seat_socket, reply + have,
|
|
(int)sizeof(reply) - have, 0);
|
|
if (received <= 0)
|
|
{
|
|
break;
|
|
}
|
|
have += received;
|
|
if (have < (int)sizeof(RelayTcpEnvelope))
|
|
{
|
|
continue;
|
|
}
|
|
RelayTcpEnvelope *envelope = (RelayTcpEnvelope *)reply;
|
|
if (envelope->route == RELAY_ROUTE_SEAT_FULL)
|
|
{
|
|
DEBUG_STREAM << "[relay] seat request: ROSTER FULL\n"
|
|
<< std::flush;
|
|
break;
|
|
}
|
|
if (envelope->route != RELAY_ROUTE_SEAT_ASSIGN)
|
|
{
|
|
break;
|
|
}
|
|
if (have < (int)(sizeof(RelayTcpEnvelope) + envelope->length))
|
|
{
|
|
continue; // partial payload; keep reading
|
|
}
|
|
const char *tag = reply + sizeof(RelayTcpEnvelope) + 4;
|
|
int tag_max = (int)envelope->length - 4;
|
|
if (tag_max > 0 && tag_max < (int)sizeof(relaySelf))
|
|
{
|
|
memcpy(relaySelf, tag, tag_max);
|
|
relaySelf[tag_max] = '\0';
|
|
DEBUG_STREAM << "[relay] seat ASSIGNED: '" << relaySelf
|
|
<< "'\n" << std::flush;
|
|
got_seat = True;
|
|
}
|
|
}
|
|
}
|
|
closesocket(seat_socket);
|
|
return got_seat;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// RelayDialTcp -- outbound TCP dial with a BOUNDED retry window (unlike
|
|
// OpenConnection's unbounded 10061 busy-loop), then nonblocking + NODELAY.
|
|
//
|
|
SOCKET L4NetworkManager::RelayDialTcp(
|
|
const SOCKADDR_IN &endpoint,
|
|
int timeout_seconds)
|
|
{
|
|
unsigned long deadline = GetTickCount() + (unsigned long)timeout_seconds * 1000UL;
|
|
for (;;)
|
|
{
|
|
SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
if (sock == INVALID_SOCKET)
|
|
{
|
|
return INVALID_SOCKET;
|
|
}
|
|
if (connect(sock, (const sockaddr *)&endpoint, sizeof(endpoint)) == 0)
|
|
{
|
|
unsigned long enable = 1;
|
|
ioctlsocket(sock, FIONBIO, &enable);
|
|
BOOL noDelay = TRUE;
|
|
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
|
|
(char *)&noDelay, sizeof(noDelay));
|
|
return sock;
|
|
}
|
|
int wsa_error = WSAGetLastError();
|
|
closesocket(sock);
|
|
if (GetTickCount() >= deadline
|
|
|| (wsa_error != WSAECONNREFUSED && wsa_error != WSAETIMEDOUT))
|
|
{
|
|
DEBUG_STREAM << "[relay] dial failed (WSA " << wsa_error
|
|
<< ")\n" << std::flush;
|
|
return INVALID_SOCKET;
|
|
}
|
|
Sleep(250);
|
|
}
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// RelaySendAll -- partial-send-safe transmit on the multiplexed relay socket.
|
|
// REQUIRED correctness over the mesh path's fire-and-forget send(): a partial
|
|
// write here would desync the envelope framing for every peer at once.
|
|
//
|
|
Logical L4NetworkManager::RelaySendAll(
|
|
SOCKET sock,
|
|
const char *buffer,
|
|
int length)
|
|
{
|
|
int sent_total = 0;
|
|
while (sent_total < length)
|
|
{
|
|
int sent = send(sock, buffer + sent_total, length - sent_total, 0);
|
|
if (sent > 0)
|
|
{
|
|
sent_total += sent;
|
|
continue;
|
|
}
|
|
int wsa_error = WSAGetLastError();
|
|
if (sent == SOCKET_ERROR && wsa_error == WSAEWOULDBLOCK)
|
|
{
|
|
// Kernel buffer full: wait (bounded) for writability.
|
|
fd_set write_set;
|
|
FD_ZERO(&write_set);
|
|
FD_SET(sock, &write_set);
|
|
timeval tv;
|
|
tv.tv_sec = 5;
|
|
tv.tv_usec = 0;
|
|
if (select(0, NULL, &write_set, NULL, &tv) == 1)
|
|
{
|
|
continue;
|
|
}
|
|
DEBUG_STREAM << "[relay] send stalled >5s\n" << std::flush;
|
|
return False;
|
|
}
|
|
DEBUG_STREAM << "[relay] send failed (WSA " << wsa_error << ")\n"
|
|
<< std::flush;
|
|
return False;
|
|
}
|
|
return True;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// BuildRelayPacket -- the classic frame builder (the malloc + header fill the
|
|
// live mesh Send() does inline; kept as a SEPARATE unit so the verified mesh
|
|
// path stays byte-for-byte untouched). Caller frees.
|
|
//
|
|
NetworkPacket *L4NetworkManager::BuildRelayPacket(
|
|
Message *message,
|
|
ClientID client,
|
|
int *packet_size)
|
|
{
|
|
Check_Pointer(message);
|
|
Verify(message->messageLength >= 8);
|
|
NetworkPacket *packet =
|
|
(NetworkPacket *)malloc(message->messageLength + sizeof(NetworkPacketHeader));
|
|
memcpy(&packet->messageData, message, message->messageLength);
|
|
packet->clientID = client;
|
|
packet->gameID = gameID;
|
|
packet->timeStamp = Now();
|
|
Host *local_host = application->GetHostManager()->GetLocalHost();
|
|
packet->fromHost = (local_host == NULL) ? 0 : local_host->GetHostID();
|
|
*packet_size = message->messageLength + sizeof(NetworkPacketHeader);
|
|
return packet;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// RelaySendFrame -- envelope + frame on the relay game connection.
|
|
//
|
|
Logical L4NetworkManager::RelaySendFrame(
|
|
int route,
|
|
const NetworkPacket *packet,
|
|
int packet_size)
|
|
{
|
|
if (relayGameSocket == INVALID_SOCKET)
|
|
{
|
|
return False;
|
|
}
|
|
char wire[sizeof(RelayTcpEnvelope) + NETWORKMANAGER_BUFFER_SIZE];
|
|
Verify(packet_size <= NETWORKMANAGER_BUFFER_SIZE);
|
|
RelayTcpEnvelope *envelope = (RelayTcpEnvelope *)wire;
|
|
envelope->route = route;
|
|
envelope->length = (unsigned int)packet_size;
|
|
memcpy(wire + sizeof(RelayTcpEnvelope), packet, packet_size);
|
|
if (!RelaySendAll(relayGameSocket,
|
|
wire, (int)sizeof(RelayTcpEnvelope) + packet_size))
|
|
{
|
|
RelayGameDown("send failure");
|
|
return False;
|
|
}
|
|
if (getenv("BT_NET_TRACE"))
|
|
{
|
|
DEBUG_STREAM << "[net-tx] relay route=" << route
|
|
<< " client=" << (int)packet->clientID
|
|
<< " msgID=" << (int)packet->messageData.messageID
|
|
<< " len=" << (int)packet->messageData.messageLength
|
|
<< "\n" << std::flush;
|
|
}
|
|
return True;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// ConnectRelayGame -- dial the relay's game port and register with HELLO.
|
|
// (UDP channel comes in the later phase; TCP carries everything until then.)
|
|
//
|
|
void L4NetworkManager::ConnectRelayGame(HostID local_host_ID)
|
|
{
|
|
relayGameSocket = RelayDialTcp(relayGameAddress, 60);
|
|
if (relayGameSocket == INVALID_SOCKET)
|
|
{
|
|
Fail("BT_RELAY set but the relay game port did not answer\n");
|
|
}
|
|
relayPadTail = 0;
|
|
struct
|
|
{
|
|
RelayTcpEnvelope envelope;
|
|
int magic;
|
|
unsigned int hostID;
|
|
} hello;
|
|
hello.envelope.route = RELAY_ROUTE_HELLO;
|
|
hello.envelope.length = 8;
|
|
hello.magic = RELAY_HELLO_MAGIC;
|
|
hello.hostID = (unsigned int)local_host_ID;
|
|
if (!RelaySendAll(relayGameSocket, (const char *)&hello, sizeof(hello)))
|
|
{
|
|
Fail("relay HELLO transmit failed\n");
|
|
}
|
|
relayLocalHostID = local_host_ID;
|
|
DEBUG_STREAM << "[relay] game connection up, HELLO sent (hostID "
|
|
<< (int)local_host_ID << ")\n" << std::flush;
|
|
|
|
// Bring up the UDP unreliable channel (a fully UDP-blocked network just
|
|
// leaves udpUp False and everything rides TCP -- the fallback is automatic).
|
|
ConnectRelayUdp(local_host_ID);
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// ConnectRelayUdp -- create the UDP socket, connect() it to the relay game
|
|
// port (so send/recv need no address), and send the first HELLO (the relay
|
|
// learns our public endpoint from it -- outbound NAT punch-through). udpUp
|
|
// flips True on the relay's HELLO-ACK (CheckRelayUdp). BT_RELAY_TCP_ONLY=1
|
|
// disables the whole channel.
|
|
//
|
|
void L4NetworkManager::ConnectRelayUdp(HostID local_host_ID)
|
|
{
|
|
const char *tcp_only = getenv("BT_RELAY_TCP_ONLY");
|
|
if (tcp_only != NULL && tcp_only[0] == '1')
|
|
{
|
|
DEBUG_STREAM << "[relay] BT_RELAY_TCP_ONLY: UDP channel disabled\n"
|
|
<< std::flush;
|
|
return;
|
|
}
|
|
relayUdpSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
if (relayUdpSocket == INVALID_SOCKET)
|
|
{
|
|
DEBUG_STREAM << "[relay] UDP socket() failed (WSA "
|
|
<< WSAGetLastError() << ") -- staying on TCP\n" << std::flush;
|
|
return;
|
|
}
|
|
if (connect(relayUdpSocket, (const sockaddr *)&relayGameAddress,
|
|
sizeof(relayGameAddress)) != 0)
|
|
{
|
|
DEBUG_STREAM << "[relay] UDP connect() failed (WSA "
|
|
<< WSAGetLastError() << ") -- staying on TCP\n" << std::flush;
|
|
closesocket(relayUdpSocket);
|
|
relayUdpSocket = INVALID_SOCKET;
|
|
return;
|
|
}
|
|
unsigned long enable = 1;
|
|
ioctlsocket(relayUdpSocket, FIONBIO, &enable);
|
|
// First HELLO (retried by RelayUdpKeepalive until acked).
|
|
RelayUdpEnvelope hello;
|
|
hello.route = RELAY_ROUTE_HELLO;
|
|
hello.fromHost = (int)local_host_ID;
|
|
hello.sequence = 0;
|
|
send(relayUdpSocket, (const char *)&hello, sizeof(hello), 0);
|
|
lastUdpHelloTick = GetTickCount();
|
|
DEBUG_STREAM << "[relay] UDP HELLO sent (hostID " << (int)local_host_ID
|
|
<< "); awaiting ACK\n" << std::flush;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// RelayUdpSendFrame -- one datagram = one enveloped frame on the unreliable
|
|
// channel. Best-effort: a failed sendto is simply dropped (the point of UDP).
|
|
//
|
|
Logical L4NetworkManager::RelayUdpSendFrame(
|
|
int route,
|
|
const NetworkPacket *packet,
|
|
int packet_size)
|
|
{
|
|
if (relayUdpSocket == INVALID_SOCKET || !udpUp)
|
|
{
|
|
return False;
|
|
}
|
|
char wire[sizeof(RelayUdpEnvelope) + NETWORKMANAGER_BUFFER_SIZE];
|
|
if (packet_size > NETWORKMANAGER_BUFFER_SIZE)
|
|
{
|
|
return False;
|
|
}
|
|
RelayUdpEnvelope *envelope = (RelayUdpEnvelope *)wire;
|
|
envelope->route = route;
|
|
envelope->fromHost = (int)relayLocalHostID;
|
|
envelope->sequence = ++udpTxSeq;
|
|
memcpy(wire + sizeof(RelayUdpEnvelope), packet, packet_size);
|
|
send(relayUdpSocket, wire, (int)sizeof(RelayUdpEnvelope) + packet_size, 0);
|
|
return True;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// RelayUdpKeepalive -- hold the NAT binding (~15s) and retry HELLO (~1s) until
|
|
// acked. Called from CheckBuffers. During play the 60Hz stream IS the
|
|
// keepalive; this covers idle/lobby time and the initial handshake.
|
|
//
|
|
void L4NetworkManager::RelayUdpKeepalive(HostID local_host_ID)
|
|
{
|
|
if (relayUdpSocket == INVALID_SOCKET)
|
|
{
|
|
return;
|
|
}
|
|
unsigned long now = GetTickCount();
|
|
if (!udpUp)
|
|
{
|
|
if (now - lastUdpHelloTick >= 1000)
|
|
{
|
|
RelayUdpEnvelope hello;
|
|
hello.route = RELAY_ROUTE_HELLO;
|
|
hello.fromHost = (int)local_host_ID;
|
|
hello.sequence = 0;
|
|
send(relayUdpSocket, (const char *)&hello, sizeof(hello), 0);
|
|
lastUdpHelloTick = now;
|
|
}
|
|
return;
|
|
}
|
|
if (now - lastUdpKeepaliveTick >= 15000)
|
|
{
|
|
RelayUdpEnvelope keepalive;
|
|
keepalive.route = RELAY_ROUTE_HELLO; // HELLO doubles as keepalive
|
|
keepalive.fromHost = (int)local_host_ID;
|
|
keepalive.sequence = 0;
|
|
send(relayUdpSocket, (const char *)&keepalive, sizeof(keepalive), 0);
|
|
lastUdpKeepaliveTick = now;
|
|
}
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// RelayShouldUseUdp -- the AUTHENTIC 1995 reliable/unreliable gate. Route to
|
|
// UDP only in the unreliable window AND for a message the engine itself
|
|
// flagged unreliable (messageFlags&ReliableFlag==0). This protects the
|
|
// map-stream creation messages (which also lack ReliableFlag but flow during
|
|
// the still-Reliable CreatingMission window) for free.
|
|
//
|
|
Logical L4NetworkManager::RelayShouldUseUdp(const Message *message) const
|
|
{
|
|
return (udpUp
|
|
&& currentNetworkMode == NetworkManager::UnreliableMode
|
|
&& (message->messageFlags & Message::ReliableFlag) == 0) ? True : False;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// CheckRelayUdp -- poll the UDP socket. HELLO-ACK sets udpUp; game datagrams
|
|
// pass the per-sender sequence gate (drop stale/out-of-order -- dead reckoning
|
|
// tolerates gaps), then hand the frame up envelope-stripped exactly like the
|
|
// TCP path. Returns True with one game frame.
|
|
//
|
|
Logical L4NetworkManager::CheckRelayUdp(NetworkPacket *network_packet)
|
|
{
|
|
if (relayUdpSocket == INVALID_SOCKET)
|
|
{
|
|
return False;
|
|
}
|
|
for (;;)
|
|
{
|
|
char datagram[sizeof(RelayUdpEnvelope) + NETWORKMANAGER_BUFFER_SIZE];
|
|
int received = recv(relayUdpSocket, datagram, sizeof(datagram), 0);
|
|
if (received == SOCKET_ERROR)
|
|
{
|
|
return False; // WSAEWOULDBLOCK etc: no more datagrams
|
|
}
|
|
if (received < (int)sizeof(RelayUdpEnvelope))
|
|
{
|
|
continue;
|
|
}
|
|
RelayUdpEnvelope *envelope = (RelayUdpEnvelope *)datagram;
|
|
if (envelope->route == RELAY_ROUTE_UDP_ACK)
|
|
{
|
|
if (!udpUp)
|
|
{
|
|
udpUp = True;
|
|
lastUdpKeepaliveTick = GetTickCount();
|
|
DEBUG_STREAM << "[relay] UDP channel UP (unreliable records now "
|
|
"ride UDP)\n" << std::flush;
|
|
}
|
|
continue;
|
|
}
|
|
int frame_length = received - (int)sizeof(RelayUdpEnvelope);
|
|
if (frame_length < (int)sizeof(NetworkPacketHeader))
|
|
{
|
|
continue;
|
|
}
|
|
// Per-sender sequence gate: drop stale/duplicate/out-of-order.
|
|
int from_host = envelope->fromHost;
|
|
if (from_host >= 0 && from_host < 64)
|
|
{
|
|
if (envelope->sequence != 0
|
|
&& envelope->sequence <= udpRxSeq[from_host])
|
|
{
|
|
continue; // stale -- newer state already applied
|
|
}
|
|
udpRxSeq[from_host] = envelope->sequence;
|
|
}
|
|
Mem_Copy(network_packet, datagram + sizeof(RelayUdpEnvelope),
|
|
frame_length, NETWORKMANAGER_BUFFER_SIZE);
|
|
if (getenv("BT_NET_TRACE"))
|
|
{
|
|
DEBUG_STREAM << "[net-rx] relay-udp client="
|
|
<< (int)network_packet->clientID
|
|
<< " msgID=" << (int)network_packet->messageData.messageID
|
|
<< " len=" << (int)network_packet->messageData.messageLength
|
|
<< " from host " << (int)network_packet->fromHost
|
|
<< " seq=" << envelope->sequence << "\n" << std::flush;
|
|
}
|
|
return True;
|
|
}
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// RelayGameDown -- the relay game connection died: close it and synthesize a
|
|
// HostDisconnectedMessage for every online game host (the match continues
|
|
// peer-less, exactly like an all-peers mesh disconnect; the pod never exits
|
|
// mid-match).
|
|
//
|
|
void L4NetworkManager::RelayGameDown(const char *why)
|
|
{
|
|
if (relayGameSocket == INVALID_SOCKET)
|
|
{
|
|
return;
|
|
}
|
|
DEBUG_STREAM << "[relay] game connection DOWN (" << why << ")\n" << std::flush;
|
|
closesocket(relayGameSocket);
|
|
relayGameSocket = INVALID_SOCKET;
|
|
relayPadTail = 0;
|
|
udpUp = False;
|
|
|
|
HostManager::RemoteHostIterator remote_hosts(application->GetHostManager());
|
|
Host *host;
|
|
while ((host = remote_hosts.ReadAndNext()) != NULL)
|
|
{
|
|
if (host->GetHostType() == ConsoleHostType)
|
|
{
|
|
continue;
|
|
}
|
|
L4Host *l4host = Cast_Object(L4Host*, host);
|
|
if (l4host->GetConnectStatus() != L4Host::OnLineConnectionStatus)
|
|
{
|
|
continue;
|
|
}
|
|
// INVALID_SOCKET stream pointer: the disconnect handler's
|
|
// CloseConnection(streamPointer) must not touch a real socket.
|
|
HostDisconnectedMessage host_down(host->GetHostID(),
|
|
(unsigned long)INVALID_SOCKET);
|
|
NetworkClient *client = GetNetworkClientPointer(NetworkManagerClientID);
|
|
Check(client);
|
|
client->ReceiveNetworkPacket(NULL, &host_down);
|
|
}
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// CheckRelay -- THE relay receive seam, called from the top of CheckBuffers.
|
|
// Drains complete envelopes from the relay pad: control frames (PEER_UP/DOWN)
|
|
// are handled inline by synthesizing the SAME HostConnected/Disconnected
|
|
// messages the mesh accept path routes, so the connection gate and app ladder
|
|
// run unmodified; game frames are copied out envelope-stripped (they
|
|
// self-identify via NetworkPacketHeader.fromHost -- every consumer routes by
|
|
// payload ids, never by arrival socket). Returns True with one game frame.
|
|
//
|
|
Logical L4NetworkManager::CheckRelay(NetworkPacket *network_packet)
|
|
{
|
|
if (!relayMode || relayGameSocket == INVALID_SOCKET)
|
|
{
|
|
return False;
|
|
}
|
|
for (;;)
|
|
{
|
|
//
|
|
// Drain complete envelopes already buffered.
|
|
//
|
|
while (relayPadTail >= (int)sizeof(RelayTcpEnvelope))
|
|
{
|
|
RelayTcpEnvelope *envelope = (RelayTcpEnvelope *)relayPad;
|
|
int frame_length = (int)envelope->length;
|
|
if (frame_length > NETWORKMANAGER_BUFFER_SIZE)
|
|
{
|
|
RelayGameDown("oversize frame (protocol desync)");
|
|
return False;
|
|
}
|
|
int whole = (int)sizeof(RelayTcpEnvelope) + frame_length;
|
|
if (relayPadTail < whole)
|
|
{
|
|
break; // partial frame; need more bytes
|
|
}
|
|
int route = envelope->route;
|
|
char *payload = relayPad + sizeof(RelayTcpEnvelope);
|
|
|
|
Logical is_game_frame = False;
|
|
if (route == RELAY_ROUTE_PEER_UP && frame_length >= 4)
|
|
{
|
|
HostID peer_id = (HostID)(*(int *)payload);
|
|
Host *peer = application->GetHostManager()->GetRemoteHost(peer_id);
|
|
if (peer != NULL)
|
|
{
|
|
L4Host *l4peer = Cast_Object(L4Host*, peer);
|
|
HostConnectedMessage peer_up(peer_id,
|
|
*l4peer->GetNetworkAddress(), // the egg-parsed tag address
|
|
(unsigned long)INVALID_SOCKET);
|
|
NetworkClient *client =
|
|
GetNetworkClientPointer(NetworkManagerClientID);
|
|
Check(client);
|
|
client->ReceiveNetworkPacket(NULL, &peer_up);
|
|
}
|
|
}
|
|
else if (route == RELAY_ROUTE_PEER_DOWN && frame_length >= 4)
|
|
{
|
|
HostID peer_id = (HostID)(*(int *)payload);
|
|
Host *peer = application->GetHostManager()->GetRemoteHost(peer_id);
|
|
if (peer != NULL
|
|
&& Cast_Object(L4Host*, peer)->GetConnectStatus()
|
|
== L4Host::OnLineConnectionStatus)
|
|
{
|
|
HostDisconnectedMessage peer_down(peer_id,
|
|
(unsigned long)INVALID_SOCKET);
|
|
NetworkClient *client =
|
|
GetNetworkClientPointer(NetworkManagerClientID);
|
|
Check(client);
|
|
client->ReceiveNetworkPacket(NULL, &peer_down);
|
|
}
|
|
}
|
|
else if (route >= FirstLegalHostID)
|
|
{
|
|
is_game_frame = True;
|
|
Mem_Copy(network_packet, payload, frame_length,
|
|
NETWORKMANAGER_BUFFER_SIZE);
|
|
}
|
|
// (unknown negative routes: ignore -- forward compatibility)
|
|
|
|
int remainder = relayPadTail - whole;
|
|
if (remainder > 0)
|
|
{
|
|
memmove(relayPad, relayPad + whole, remainder);
|
|
}
|
|
relayPadTail = remainder;
|
|
|
|
if (is_game_frame)
|
|
{
|
|
if (getenv("BT_NET_TRACE"))
|
|
{
|
|
DEBUG_STREAM << "[net-rx] relay client="
|
|
<< (int)network_packet->clientID
|
|
<< " msgID=" << (int)network_packet->messageData.messageID
|
|
<< " len=" << (int)network_packet->messageData.messageLength
|
|
<< " from host " << (int)network_packet->fromHost
|
|
<< "\n" << std::flush;
|
|
}
|
|
return True;
|
|
}
|
|
}
|
|
//
|
|
// Refill from the socket.
|
|
//
|
|
int space = (int)sizeof(relayPad) - relayPadTail;
|
|
int received = recv(relayGameSocket, relayPad + relayPadTail, space, 0);
|
|
if (received > 0)
|
|
{
|
|
relayPadTail += received;
|
|
continue;
|
|
}
|
|
if (received == 0)
|
|
{
|
|
RelayGameDown("closed by relay");
|
|
return False;
|
|
}
|
|
int wsa_error = WSAGetLastError();
|
|
if (wsa_error == WSAEWOULDBLOCK)
|
|
{
|
|
return False; // no more data this poll
|
|
}
|
|
if (wsa_error == WSAECONNRESET)
|
|
{
|
|
RelayGameDown("connection reset");
|
|
return False;
|
|
}
|
|
DEBUG_STREAM << "[relay] recv error WSA " << wsa_error << "\n" << std::flush;
|
|
return False;
|
|
}
|
|
}
|
|
|
|
#if MESSAGE_BUFFERING
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// L4NetworkManager::Send
|
|
//
|
|
void
|
|
L4NetworkManager::Send(
|
|
Message *message,
|
|
ClientID client_ID,
|
|
HostID host_ID
|
|
)
|
|
{
|
|
SET_SEND_PACKET();
|
|
Check(this);
|
|
Check(message);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// If the message buffer is not empty then add this new message into the
|
|
// buffer. Attempt to send a message.
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
if (!messageBuffer.IsEmpty())
|
|
{
|
|
messageBuffer.AddSendRequest(host_ID, client_ID, message);
|
|
messageBuffer.AttemptToSend();
|
|
}
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// The message buffer is empty so attempt to send the message.
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
else if (!SendMessageToNetnub(message, client_ID, host_ID))
|
|
{
|
|
//
|
|
// Message was dropped, add this new message into the buffer
|
|
//
|
|
messageBuffer.AddSendRequest(host_ID, client_ID, message);
|
|
}
|
|
CLEAR_SEND_PACKET();
|
|
}
|
|
#else
|
|
//WinSock support :ADB 01/06/07
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// L4NetworkManager::Send Handles sending a message to a specific network address
|
|
// which can NOT be us.
|
|
//
|
|
void L4NetworkManager::Send(
|
|
Message *message,
|
|
ClientID client,
|
|
HostID host_ID)
|
|
{
|
|
int packet_size;
|
|
Host *local_host;
|
|
Host *base_host;
|
|
L4Host *l4host;
|
|
//SendPacketRequestPtr send_packet_request; // Format of the request we send to the NetNub
|
|
//SendPacketReturnPtr send_packet_return;
|
|
HostManager *our_host_manager;
|
|
NetworkPacket *my_temp_packet; // pointer to a place to build a network message
|
|
//
|
|
// Mark entry to the routine with an analysis call
|
|
//
|
|
SET_SEND_PACKET();
|
|
//
|
|
// Check whatever incoming data needs checking, get a pointer to the host
|
|
// we are going to send to and make sure that host is online.
|
|
//
|
|
Check_Pointer(message);
|
|
Check(application);
|
|
our_host_manager = application->GetHostManager();
|
|
Check(our_host_manager);
|
|
base_host = our_host_manager->GetRemoteHost(host_ID);
|
|
l4host = Cast_Object(L4Host*, base_host);
|
|
if(l4host->GetConnectStatus() != L4Host::OnLineConnectionStatus)
|
|
{
|
|
CLEAR_SEND_PACKET();
|
|
return;
|
|
}
|
|
Verify(l4host->GetConnectStatus() == L4Host::OnLineConnectionStatus);
|
|
|
|
//
|
|
// D1 RELAY MODE: game-host traffic rides the ONE relay connection inside a
|
|
// {route, length} envelope (route = destination hostID; the relay demuxes).
|
|
// The CONSOLE host is excluded -- its connection speaks the legacy raw
|
|
// protocol which the relay TERMINATES rather than routes (egg ack, etc).
|
|
//
|
|
if (relayMode && l4host->GetHostType() != ConsoleHostType)
|
|
{
|
|
int relay_packet_size;
|
|
NetworkPacket *relay_packet =
|
|
BuildRelayPacket(message, client, &relay_packet_size);
|
|
if (RelayShouldUseUdp(message))
|
|
{
|
|
RelayUdpSendFrame((int)host_ID, relay_packet, relay_packet_size);
|
|
}
|
|
else
|
|
{
|
|
RelaySendFrame((int)host_ID, relay_packet, relay_packet_size);
|
|
}
|
|
free(relay_packet);
|
|
CLEAR_SEND_PACKET();
|
|
return;
|
|
}
|
|
//
|
|
// figure out where to put the packet we're assembling
|
|
//
|
|
//send_packet_request = (SendPacketRequestPtr)Net_Common_Ptr->Shared_Memory_Buffer;
|
|
//send_packet_return = (SendPacketReturnPtr)Net_Common_Ptr->Shared_Memory_Buffer;
|
|
//my_temp_packet = (NetworkPacket*)send_packet_request->Send_Data;
|
|
//
|
|
// Build a MUNGA network packet in the netnub common block
|
|
//
|
|
if((false) || (message->messageLength < 8) || (false))
|
|
//if((message->messageLength > MAX_SEND_DATA_SIZE) || (message->messageLength < 8) || (message->messageLength+sizeof(NetworkPacketHeader) > MAX_SEND_DATA_SIZE))
|
|
{
|
|
char addressString[44];
|
|
DWORD bufferSize = sizeof(addressString);
|
|
WSAAddressToStringA((LPSOCKADDR)l4host->GetNetworkAddress(), sizeof(SOCKADDR_IN), NULL, addressString, &bufferSize);
|
|
DEBUG_STREAM << addressString << " Requested to send a " << message->messageLength << " size packet\n" << std::flush;
|
|
Fail("Illegal transmit size\n");
|
|
}
|
|
//Verify(message->messageLength + sizeof(NetworkPacketHeader) <= MAX_SEND_DATA_SIZE);
|
|
Verify(message->messageLength >= 8);
|
|
my_temp_packet = (NetworkPacket*)malloc(message->messageLength + sizeof(NetworkPacketHeader));
|
|
memcpy(&my_temp_packet->messageData, message, message->messageLength);
|
|
my_temp_packet->clientID = client;
|
|
my_temp_packet->gameID = gameID;
|
|
my_temp_packet->timeStamp = Now();
|
|
|
|
//
|
|
// If the local host doesn't exist yet, we send back zero
|
|
// this lets the console get status before the local host is actually created
|
|
//
|
|
local_host = our_host_manager->GetLocalHost();
|
|
if(local_host == NULL)
|
|
{
|
|
my_temp_packet->fromHost = 0;
|
|
}
|
|
else
|
|
{
|
|
Check(local_host);
|
|
my_temp_packet->fromHost = local_host->GetHostID();
|
|
}
|
|
packet_size = message->messageLength + sizeof(NetworkPacketHeader);
|
|
//
|
|
// Fill in the NetNub common block with the rest of the data it needs for the call
|
|
//
|
|
//Net_Common_Ptr->Function = NETNUB_SEND_PACKET;
|
|
//Net_Common_Ptr->Buffer_Length = (short)SEND_BUFFER_SIZE(packet_size);
|
|
//send_packet_request->Socket_Ptr = l4host->GetNetworkSocket();
|
|
//NetNub::SendCommand();
|
|
send(l4host->GetNetworkSocket(), (char *)my_temp_packet, packet_size, 0);
|
|
free(my_temp_packet);
|
|
// Check for errors and abort if there were any
|
|
//#if defined(TRACE_SEND_BUFFER)
|
|
//l4host->sendBufferTrace->TakeSnapshot(send_packet_return->Send_Buffer_Used);
|
|
//#endif
|
|
//switch(Net_Common_Ptr->Status)
|
|
//{
|
|
// case NETNUB_OK:
|
|
// break;
|
|
// case NETNUB_DATA_DUMPED:
|
|
// {
|
|
// SET_LOST_DATA();
|
|
// #if REPORT_LOST_DATA
|
|
// unsigned long temp_net_address;
|
|
// temp_net_address = l4host->GetNetworkAddress();
|
|
// DEBUG_STREAM<<"Data lost to ";
|
|
// DEBUG_STREAM<<((temp_net_address>>24) & 0xff)<<"."<<((temp_net_address>>16) & 0xff)<<".";
|
|
// DEBUG_STREAM<<((temp_net_address>>8) & 0xff)<<"."<<(temp_net_address & 0xff)<<"\n";
|
|
// DEBUG_STREAM<<flush;
|
|
// #endif
|
|
// CLEAR_LOST_DATA();
|
|
// break;
|
|
// }
|
|
// case NETNUB_STREAM_DISCONNECTED:
|
|
// {
|
|
// DEBUG_STREAM<<"Disconnect detected in send\n";
|
|
// DEBUG_STREAM<<flush;
|
|
// HostDisconnectedMessage myHostDisconnected(
|
|
// l4host->GetHostID(),
|
|
// l4host->GetNetworkSocket());
|
|
// NetworkClient *client = GetNetworkClientPointer(NetworkManagerClientID);
|
|
// Check(client);
|
|
// client->ReceiveNetworkPacket(NULL, &myHostDisconnected);
|
|
// break;
|
|
// }
|
|
// default:
|
|
// {
|
|
// unsigned long temp_net_address;
|
|
// temp_net_address = l4host->GetNetworkAddress();
|
|
// DEBUG_STREAM<<"Error "<<Net_Common_Ptr->Status<<" on ";
|
|
// DEBUG_STREAM<<((temp_net_address>>24) & 0xff)<<"."<<((temp_net_address>>16) & 0xff)<<".";
|
|
// DEBUG_STREAM<<((temp_net_address>>8) & 0xff)<<"."<<(temp_net_address & 0xff)<<"\n";
|
|
// DEBUG_STREAM<<flush;
|
|
// Fail("NetNub error in transmit\n");
|
|
// }
|
|
//}
|
|
CLEAR_SEND_PACKET();
|
|
}
|
|
#endif
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// L4NetworkManager::SendMessageToNetnub
|
|
//
|
|
//WinSock support :ADB 01/06/07
|
|
Logical L4NetworkManager::SendMessageToNetnub(
|
|
Message *message,
|
|
ClientID client_ID,
|
|
HostID host_ID)
|
|
{
|
|
Check(this);
|
|
Check(message);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Get host manager, receiving host. Verify that the host is online.
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
HostManager *host_manager;
|
|
L4Host *receiving_host;
|
|
|
|
Check(application);
|
|
host_manager = application->GetHostManager();
|
|
Check(host_manager);
|
|
|
|
receiving_host = Cast_Object(L4Host*, host_manager->GetRemoteHost(host_ID));
|
|
if (receiving_host->GetConnectStatus() != L4Host::OnLineConnectionStatus)
|
|
{
|
|
// BT bring-up trace: this is a SILENT DROP in the original.
|
|
if (getenv("BT_NET_TRACE"))
|
|
{
|
|
DEBUG_STREAM << "[net-tx] DROP (host " << (int)host_ID
|
|
<< " status=" << (int)receiving_host->GetConnectStatus()
|
|
<< ") msgID=" << (int)message->messageID << "\n" << std::flush;
|
|
}
|
|
return True;
|
|
}
|
|
Verify(receiving_host->GetConnectStatus() == L4Host::OnLineConnectionStatus);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Get the local host ID. If the local host doesn't exist yet, we use zero,
|
|
// this lets the console get status before the local host is actually
|
|
// created.
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
Host *local_host;
|
|
HostID local_host_ID;
|
|
|
|
if((local_host = host_manager->GetLocalHost()) == NULL)
|
|
{
|
|
local_host_ID = 0;
|
|
}
|
|
else
|
|
{
|
|
Check(local_host);
|
|
local_host_ID = local_host->GetHostID();
|
|
}
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Verify that the size of the message is within bounds.
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
long munga_network_message_size;
|
|
|
|
munga_network_message_size = sizeof(NetworkPacketHeader) + message->messageLength;
|
|
if((message->messageLength < 8) || (false))
|
|
//if((message->messageLength < 8) || (munga_network_message_size > MAX_SEND_DATA_SIZE))
|
|
{
|
|
char addressString[44];
|
|
DWORD bufferSize = sizeof(addressString);
|
|
WSAAddressToStringA((LPSOCKADDR)local_host->GetNetworkAddress(), sizeof(SOCKADDR_IN), NULL, addressString, &bufferSize);
|
|
|
|
DEBUG_STREAM << "L4NetworkManager::SendMessageToNetnub - " << addressString << "Requested to send a " << munga_network_message_size << " size message\n" << std::flush;
|
|
Fail("L4NetworkManager::SendMessageToNetnub - Illegal transmit size\n");
|
|
return True;
|
|
}
|
|
Verify(SEND_BUFFER_SIZE(munga_network_message_size) <= SHARED_MEMORY_SIZE);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Build the send request and MUNGA network packet
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
//SendPacketRequestPtr send_packet_request;
|
|
NetworkPacket *network_packet;
|
|
|
|
Check_Pointer(Net_Common_Ptr);
|
|
//send_packet_request = (SendPacketRequestPtr)Net_Common_Ptr->Shared_Memory_Buffer;
|
|
//network_packet = (NetworkPacket*)send_packet_request->Send_Data;
|
|
network_packet = (NetworkPacket*)malloc(munga_network_message_size);
|
|
|
|
//send_packet_request->Socket_Ptr = receiving_host->GetNetworkSocket();
|
|
network_packet->clientID = client_ID;
|
|
network_packet->gameID = gameID;
|
|
network_packet->fromHost = local_host_ID;
|
|
network_packet->timeStamp = Now();
|
|
|
|
Mem_Copy(
|
|
&network_packet->messageData,
|
|
message,
|
|
message->messageLength,
|
|
MAX_SEND_DATA_SIZE-sizeof(NetworkPacketHeader)
|
|
);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Netnub call
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
//Check_Pointer(Net_Common_Ptr);
|
|
//#if defined(TRACE_SEND_BUFFER)
|
|
// SendPacketReturnPtr send_packet_return =
|
|
// (SendPacketReturnPtr)Net_Common_Ptr->Shared_Memory_Buffer;
|
|
//#endif
|
|
//Net_Common_Ptr->Buffer_Length =
|
|
// (unsigned short)SEND_BUFFER_SIZE(munga_network_message_size);
|
|
//Net_Common_Ptr->Function =
|
|
// NETNUB_SEND_PACKET;
|
|
//NetNub::SendCommand();
|
|
// BT bring-up trace (env BT_NET_TRACE): every point-to-point game send.
|
|
if (getenv("BT_NET_TRACE"))
|
|
{
|
|
DEBUG_STREAM << "[net-tx] client=" << (int)client_ID
|
|
<< " msgID=" << (int)message->messageID
|
|
<< " len=" << (int)message->messageLength
|
|
<< " -> host " << (int)host_ID << "\n" << std::flush;
|
|
}
|
|
send(receiving_host->GetNetworkSocket(), (char *)network_packet, munga_network_message_size, 0);
|
|
free(network_packet);
|
|
|
|
#if defined(TRACE_SEND_BUFFER)
|
|
receiving_host->sendBufferTrace->TakeSnapshot(
|
|
send_packet_return->Send_Buffer_Used
|
|
);
|
|
#endif
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Check for errors
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
//switch(Net_Common_Ptr->Status)
|
|
//{
|
|
//case NETNUB_OK:
|
|
// return True;
|
|
//
|
|
//case NETNUB_DATA_DUMPED:
|
|
// {
|
|
// SET_LOST_DATA();
|
|
// #if REPORT_LOST_DATA
|
|
// unsigned long
|
|
// temp_net_address = receiving_host->GetNetworkAddress();
|
|
// DEBUG_STREAM << "L4NetworkManager::SendMessageToNetnub - ";
|
|
// DEBUG_STREAM << "Data lost to ";
|
|
// DEBUG_STREAM << ((temp_net_address>>24) & 0xff)<<"."<<
|
|
// ((temp_net_address>>16) & 0xff)<<".";
|
|
// DEBUG_STREAM << ((temp_net_address>>8) & 0xff)<<"."<<
|
|
// (temp_net_address & 0xff)<<"\n";
|
|
// DEBUG_STREAM << flush;
|
|
// #endif
|
|
// CLEAR_LOST_DATA();
|
|
// }
|
|
// return False;
|
|
//
|
|
//case NETNUB_STREAM_DISCONNECTED:
|
|
// {
|
|
// DEBUG_STREAM << "L4NetworkManager::SendMessageToNetnub - ";
|
|
// DEBUG_STREAM<<"Disconnect detected in send\n";
|
|
// DEBUG_STREAM<<flush;
|
|
//
|
|
// HostDisconnectedMessage
|
|
// myHostDisconnected(
|
|
// receiving_host->GetHostID(),
|
|
// receiving_host->GetNetworkSocket()
|
|
// );
|
|
//
|
|
// NetworkClient
|
|
// *client = GetNetworkClientPointer(NetworkManagerClientID);
|
|
// Check(client);
|
|
// client->ReceiveNetworkPacket(NULL, &myHostDisconnected);
|
|
// }
|
|
// return True;
|
|
//
|
|
//default:
|
|
// {
|
|
// unsigned long
|
|
// temp_net_address = receiving_host->GetNetworkAddress();
|
|
|
|
// DEBUG_STREAM << "L4NetworkManager::SendMessageToNetnub - ";
|
|
// DEBUG_STREAM << "Error "<<Net_Common_Ptr->Status<<" on ";
|
|
// DEBUG_STREAM << ((temp_net_address>>24) & 0xff)<<"."<<
|
|
// ((temp_net_address>>16) & 0xff)<<".";
|
|
// DEBUG_STREAM << ((temp_net_address>>8) & 0xff)<<"."<<
|
|
// (temp_net_address & 0xff)<<"\n";
|
|
// DEBUG_STREAM << flush;
|
|
// Fail("NetNub error in transmit\n");
|
|
// }
|
|
// break;
|
|
//}
|
|
return True;
|
|
}
|
|
|
|
#if MESSAGE_BUFFERING
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// L4NetworkManager::ExclusiveBroadcast
|
|
//
|
|
void
|
|
L4NetworkManager::ExclusiveBroadcast(
|
|
Message *message,
|
|
ClientID client_ID
|
|
)
|
|
{
|
|
SET_SEND_PACKET();
|
|
Check(this);
|
|
Check(message);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// If the message buffer is not empty then add this new message into the
|
|
// buffer. One copy per host.
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
if (!messageBuffer.IsEmpty())
|
|
{
|
|
Check(application);
|
|
Check(application->GetHostManager());
|
|
|
|
HostManager::RemoteHostIterator
|
|
remote_hosts(application->GetHostManager());
|
|
Host
|
|
*host;
|
|
L4Host
|
|
*l4host;
|
|
|
|
while ((host = remote_hosts.ReadAndNext()) != NULL)
|
|
{
|
|
l4host = Cast_Object(L4Host*, host);
|
|
Check(l4host);
|
|
|
|
//
|
|
// only non-console hosts that are online
|
|
//
|
|
if(
|
|
(l4host->GetHostType() != ConsoleHostType) &&
|
|
(l4host->GetConnectStatus() == L4Host::OnLineConnectionStatus)
|
|
)
|
|
{
|
|
messageBuffer.AddSendRequest(
|
|
l4host->GetHostID(),
|
|
client_ID,
|
|
message
|
|
);
|
|
}
|
|
}
|
|
messageBuffer.AttemptToSend();
|
|
CLEAR_SEND_PACKET();
|
|
return;
|
|
}
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// The message buffer is empty so attempt to send the message.
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
DroppedMessageHostSocket
|
|
dropped_message_host_socket(NULL);
|
|
|
|
SendBatchedMessageToNetnub(
|
|
message,
|
|
client_ID,
|
|
&dropped_message_host_socket
|
|
);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// For each host the message was dropped, add a send request message
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
DroppedMessageHostIterator
|
|
iterator(&dropped_message_host_socket);
|
|
L4Host
|
|
*l4host;
|
|
|
|
while((l4host = iterator.ReadAndNext()) != NULL)
|
|
{
|
|
Check(l4host);
|
|
messageBuffer.AddSendRequest(
|
|
l4host->GetHostID(),
|
|
client_ID,
|
|
message
|
|
);
|
|
}
|
|
CLEAR_SEND_PACKET();
|
|
}
|
|
#else
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// NetworkManager::ExclusiveBroadcast Broadcasts a message to everyone on the
|
|
// network execpt us. !!!! Reliable broadcasting is currently used,
|
|
// implimented by sending a point-to-point message to every destination.
|
|
//
|
|
void
|
|
L4NetworkManager::ExclusiveBroadcast(
|
|
Message *message, // what,
|
|
ClientID client // to
|
|
)
|
|
{
|
|
#if !BATCHED_TRANSMIT
|
|
Host
|
|
*this_host;
|
|
|
|
//
|
|
// D1 RELAY MODE: build ONCE, send ONCE with the broadcast route -- the
|
|
// relay fans out server-side. This kills the mesh's (N-1)x upload
|
|
// duplication at the exact seam where EntityBroadcastToReplicants already
|
|
// serializes the update record once.
|
|
//
|
|
if (relayMode)
|
|
{
|
|
if (relayGameSocket != INVALID_SOCKET)
|
|
{
|
|
int relay_packet_size;
|
|
NetworkPacket *relay_packet =
|
|
BuildRelayPacket(message, client, &relay_packet_size);
|
|
if (RelayShouldUseUdp(message))
|
|
{
|
|
// The 60Hz update-record fan-out -- the whole point of the UDP
|
|
// channel (unreliable, latest-wins, no head-of-line blocking).
|
|
RelayUdpSendFrame(RELAY_ROUTE_BROADCAST, relay_packet,
|
|
relay_packet_size);
|
|
}
|
|
else
|
|
{
|
|
RelaySendFrame(RELAY_ROUTE_BROADCAST, relay_packet,
|
|
relay_packet_size);
|
|
}
|
|
free(relay_packet);
|
|
}
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Iterate through the host list, resolving network addresses, sending and
|
|
// making sure we don't send to ourselves.
|
|
//
|
|
Check(application);
|
|
Check(application->GetHostManager());
|
|
|
|
HostManager::RemoteHostIterator remote_hosts(application->GetHostManager());
|
|
|
|
while ((this_host = remote_hosts.ReadAndNext()) != NULL)
|
|
{
|
|
Check(this_host);
|
|
if(this_host->GetHostType() == ConsoleHostType)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Send(message, client, this_host->GetHostID());
|
|
}
|
|
#else
|
|
int
|
|
i,
|
|
host_count, // Number of hosts we will be sending to
|
|
packet_size;
|
|
Host
|
|
*this_host,
|
|
*local_host;
|
|
L4Host
|
|
*l4_host_list[MULTIPLE_SEND_PACKET_MAX],
|
|
*l4host;
|
|
MultipleSendPacketReturnPtr
|
|
multiple_send_packet_return; // Format of the reply we get from the NetNub
|
|
MultipleSendPacketRequestPtr
|
|
multiple_send_packet_request; // Format of the request we send to the NetNub
|
|
HostManager
|
|
*our_host_manager;
|
|
NetworkPacket
|
|
*my_temp_packet; // pointer to a place to build a network message
|
|
//
|
|
// Mark entry to the routine with an analysis call
|
|
//
|
|
SET_SEND_PACKET();
|
|
//
|
|
// Make sure the application and host manager are valid
|
|
//
|
|
Check(application);
|
|
Check_Pointer(message);
|
|
our_host_manager = application->GetHostManager();
|
|
Check(our_host_manager);
|
|
//
|
|
// Make a remote host iterator
|
|
//
|
|
HostManager::RemoteHostIterator remote_hosts(our_host_manager);
|
|
multiple_send_packet_request = (MultipleSendPacketRequestPtr)Net_Common_Ptr->Shared_Memory_Buffer;
|
|
multiple_send_packet_return = (MultipleSendPacketReturnPtr)Net_Common_Ptr->Shared_Memory_Buffer;
|
|
my_temp_packet = (NetworkPacket*)multiple_send_packet_request->Send_Data;
|
|
//
|
|
// Iterate through all the hosts we want to send to and add them to the
|
|
// NETNUB_MULTIPLE_SEND command structure
|
|
//
|
|
host_count = 0;
|
|
while ((this_host = remote_hosts.ReadAndNext()) != NULL)
|
|
{
|
|
// convert regular host to an L4host so we can get net info
|
|
l4host = Cast_Object(L4Host*, this_host);
|
|
Check(l4host);
|
|
// only broadcast to non-console hosts that are online
|
|
if((l4host->GetHostType() != ConsoleHostType) &&
|
|
(l4host->GetConnectStatus() == L4Host::OnLineConnectionStatus))
|
|
{
|
|
// Remember this host so we can match up errors with it later
|
|
l4_host_list[host_count] = l4host;
|
|
// Put the socket pointer for this host into the request structure
|
|
multiple_send_packet_request->Socket_Ptrs[host_count] =
|
|
l4host->GetNetworkSocket();
|
|
host_count++;
|
|
if(host_count >= MULTIPLE_SEND_PACKET_MAX)
|
|
Fail("Tried to send to too many destinations\n");
|
|
}
|
|
}
|
|
//
|
|
// Return if we didn't find anyone to send to
|
|
//
|
|
if(host_count == 0)
|
|
{
|
|
CLEAR_SEND_PACKET();
|
|
return;
|
|
}
|
|
multiple_send_packet_request->Socket_Count = host_count;
|
|
//
|
|
// Check the packet to make sure it is of legal size
|
|
//
|
|
if( (message->messageLength > MAX_SEND_DATA_SIZE) ||
|
|
(message->messageLength < 8) ||
|
|
(message->messageLength+sizeof(NetworkPacketHeader) > MAX_SEND_DATA_SIZE))
|
|
{
|
|
DEBUG_STREAM<<"Tried to send a "<<message->messageLength<<" size packet\n";
|
|
DEBUG_STREAM<<flush;
|
|
Fail("Illegal transmit size\n");
|
|
}
|
|
//
|
|
// Copy the data over into the command to netnub
|
|
//
|
|
memcpy(&my_temp_packet->messageData, message, message->messageLength);
|
|
my_temp_packet->clientID = client;
|
|
my_temp_packet->gameID = gameID;
|
|
my_temp_packet->timeStamp = Now();
|
|
//
|
|
// If the local host doesn't exist yet, we send back zero as the from address
|
|
// this lets the console get status before the local host is actually created
|
|
//
|
|
local_host = our_host_manager->GetLocalHost();
|
|
if(local_host == NULL)
|
|
{
|
|
my_temp_packet->fromHost = 0;
|
|
}
|
|
else
|
|
{
|
|
Check(local_host);
|
|
my_temp_packet->fromHost = local_host->GetHostID();
|
|
}
|
|
//
|
|
// Fill in the NetNub common block with the rest of the data it needs for the call
|
|
//
|
|
packet_size = message->messageLength + sizeof(NetworkPacketHeader);
|
|
Net_Common_Ptr->Function = NETNUB_MULTIPLE_SEND;
|
|
Net_Common_Ptr->Buffer_Length = (short)MULTIPLE_SEND_BUFFER_SIZE(packet_size);
|
|
NetNub::SendCommand();
|
|
#if !defined(TRACE_SEND_BUFFER)
|
|
if(Net_Common_Ptr->Status == NETNUB_OK)
|
|
{
|
|
CLEAR_SEND_PACKET();
|
|
return;
|
|
}
|
|
#endif
|
|
//
|
|
// If we get this far an error was reported on one of the streams
|
|
//
|
|
for(i = 0; i<host_count; i++)
|
|
{
|
|
l4host = l4_host_list[i];
|
|
#if defined(TRACE_SEND_BUFFER)
|
|
l4host->sendBufferTrace->TakeSnapshot(multiple_send_packet_return->Send_Buffer_Used[i]);
|
|
#endif
|
|
switch(multiple_send_packet_return->Errors[i])
|
|
{
|
|
case NETNUB_OK:
|
|
break;
|
|
case NETNUB_DATA_DUMPED:
|
|
{
|
|
SET_LOST_DATA();
|
|
#if REPORT_LOST_DATA
|
|
unsigned long temp_net_address = l4host->GetNetworkAddress();
|
|
DEBUG_STREAM<<"Data lost to ";
|
|
DEBUG_STREAM<<((temp_net_address>>24) & 0xff)<<"."<<((temp_net_address>>16) & 0xff)<<".";
|
|
DEBUG_STREAM<<((temp_net_address>>8) & 0xff)<<"."<<(temp_net_address & 0xff)<<"\n";
|
|
DEBUG_STREAM<<flush;
|
|
#endif
|
|
CLEAR_LOST_DATA();
|
|
break;
|
|
}
|
|
case NETNUB_STREAM_DISCONNECTED:
|
|
{
|
|
DEBUG_STREAM<<"Disconnect detected in send\n";
|
|
DEBUG_STREAM<<flush;
|
|
HostDisconnectedMessage myHostDisconnected(
|
|
l4host->GetHostID(),
|
|
l4host->GetNetworkSocket());
|
|
NetworkClient *client = GetNetworkClientPointer(NetworkManagerClientID);
|
|
Check(client);
|
|
client->ReceiveNetworkPacket(NULL, &myHostDisconnected);
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
unsigned long temp_net_address;
|
|
temp_net_address = l4host->GetNetworkAddress();
|
|
DEBUG_STREAM<<"Error "<<Net_Common_Ptr->Status<<" on ";
|
|
DEBUG_STREAM<<((temp_net_address>>24) & 0xff)<<"."<<((temp_net_address>>16) & 0xff)<<".";
|
|
DEBUG_STREAM<<((temp_net_address>>8) & 0xff)<<"."<<(temp_net_address & 0xff)<<"\n";
|
|
DEBUG_STREAM<<flush;
|
|
Fail("NetNub error in transmit\n");
|
|
}
|
|
}
|
|
}
|
|
CLEAR_SEND_PACKET();
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// L4NetworkManager::SendBatchedMessageToNetnub
|
|
//
|
|
//WinSock support :ADB 01/06/07
|
|
void
|
|
L4NetworkManager::SendBatchedMessageToNetnub(
|
|
Message *message,
|
|
ClientID client_ID,
|
|
DroppedMessageHostSocket *dropped_message_host_socket
|
|
)
|
|
{
|
|
Check(this);
|
|
Check(dropped_message_host_socket);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// If communication with netnub does not exist then return.
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
if(wsaData == NULL)
|
|
return;
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Get host manager.
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
HostManager
|
|
*host_manager;
|
|
|
|
Check(application);
|
|
host_manager = application->GetHostManager();
|
|
Check(host_manager);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Get the local host ID. If the local host doesn't exist yet, we use zero,
|
|
// this lets the console get status before the local host is actually
|
|
// created.
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
Host
|
|
*local_host;
|
|
HostID
|
|
local_host_ID;
|
|
|
|
if ((local_host = host_manager->GetLocalHost()) == NULL)
|
|
{
|
|
local_host_ID = 0;
|
|
}
|
|
else
|
|
{
|
|
Check(local_host);
|
|
local_host_ID = local_host->GetHostID();
|
|
}
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Verify that the size of the message is within bounds.
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
long
|
|
munga_network_message_size;
|
|
|
|
munga_network_message_size = sizeof(NetworkPacketHeader)+message->messageLength;
|
|
//if((message->messageLength < 8) || (munga_network_message_size > MAX_SEND_DATA_SIZE))
|
|
if((message->messageLength < 8) || (false))
|
|
{
|
|
DEBUG_STREAM << "L4NetworkManager::SendBatchedMessageToNetnub - ";
|
|
DEBUG_STREAM << "Requested to send a " <<
|
|
munga_network_message_size << " size message\n";
|
|
DEBUG_STREAM << std::flush;
|
|
Fail("L4NetworkManager::SendBatchedMessageToNetnub - Illegal transmit size\n");
|
|
return;
|
|
}
|
|
Verify(MULTIPLE_SEND_BUFFER_SIZE(munga_network_message_size) <= SHARED_MEMORY_SIZE);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Build the send request and MUNGA network packet
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
//MultipleSendPacketRequestPtr send_packet_request;
|
|
NetworkPacket *network_packet;
|
|
|
|
Check_Pointer(Net_Common_Ptr);
|
|
//send_packet_request = (MultipleSendPacketRequestPtr)Net_Common_Ptr->Shared_Memory_Buffer;
|
|
//network_packet = (NetworkPacket*)send_packet_request->Send_Data;
|
|
network_packet = (NetworkPacket*)malloc(munga_network_message_size);
|
|
|
|
//
|
|
// Fill in socket ptrs
|
|
//
|
|
HostManager::RemoteHostIterator remote_hosts(host_manager);
|
|
Host *host;
|
|
L4Host *l4host, *l4host_array[MULTIPLE_SEND_PACKET_MAX];
|
|
int host_count;
|
|
|
|
host_count = 0;
|
|
while ((host = remote_hosts.ReadAndNext()) != NULL)
|
|
{
|
|
l4host = Cast_Object(L4Host*, host);
|
|
Check(l4host);
|
|
|
|
//
|
|
// only broadcast to non-console hosts that are online
|
|
//
|
|
if(
|
|
(l4host->GetHostType() != ConsoleHostType) &&
|
|
(l4host->GetConnectStatus() == L4Host::OnLineConnectionStatus)
|
|
)
|
|
{
|
|
if (host_count >= MULTIPLE_SEND_PACKET_MAX)
|
|
{
|
|
DEBUG_STREAM << "L4NetworkManager::SendBatchedMessageToNetnub - ";
|
|
Fail("Tried to send to too many destinations\n");
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Remember this host so we can match up errors with it later
|
|
//
|
|
Verify(0 <= host_count && host_count < MULTIPLE_SEND_PACKET_MAX);
|
|
l4host_array[host_count] = l4host;
|
|
|
|
//
|
|
// Put the socket pointer for this host into the request structure
|
|
//
|
|
Verify(0 <= host_count && host_count < MULTIPLE_SEND_PACKET_MAX);
|
|
//send_packet_request->Socket_Ptrs[host_count] = l4host->GetNetworkSocket();
|
|
host_count++;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Return if there are no hosts to send to
|
|
//
|
|
//if((send_packet_request->Socket_Count = host_count) == 0)
|
|
if(host_count == 0)
|
|
return;
|
|
|
|
//
|
|
// Fill out the munga network packet
|
|
//
|
|
network_packet->clientID = client_ID;
|
|
network_packet->gameID = gameID;
|
|
network_packet->fromHost = local_host_ID;
|
|
network_packet->timeStamp = Now();
|
|
|
|
Mem_Copy(
|
|
&network_packet->messageData,
|
|
message,
|
|
message->messageLength,
|
|
MAX_SEND_DATA_SIZE-sizeof(NetworkPacketHeader)
|
|
);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Netnub call
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
//MultipleSendPacketReturn
|
|
// *send_packet_return;
|
|
//
|
|
//Check_Pointer(Net_Common_Ptr);
|
|
//send_packet_return =
|
|
// (MultipleSendPacketReturn*)Net_Common_Ptr->Shared_Memory_Buffer;
|
|
//Net_Common_Ptr->Buffer_Length =
|
|
// (unsigned short)MULTIPLE_SEND_BUFFER_SIZE(munga_network_message_size);
|
|
//Net_Common_Ptr->Function =
|
|
// NETNUB_MULTIPLE_SEND;
|
|
//NetNub::SendCommand();
|
|
for(int i=0; i<host_count; i++)
|
|
{
|
|
send(l4host_array[i]->GetNetworkSocket(), (char *)network_packet, sizeof(network_packet), 0);
|
|
}
|
|
free(network_packet);
|
|
|
|
//if (Net_Common_Ptr->Status == NETNUB_OK)
|
|
// return;
|
|
//
|
|
////
|
|
////--------------------------------------------------------------------------
|
|
//// Parse return
|
|
////--------------------------------------------------------------------------
|
|
////
|
|
//for (int i = 0; i < host_count; i++)
|
|
//{
|
|
// Verify(0 <= i && i < MULTIPLE_SEND_PACKET_MAX);
|
|
// l4host = l4host_array[i];
|
|
// Check(l4host);
|
|
//
|
|
// #if defined(TRACE_SEND_BUFFER)
|
|
// l4host->sendBufferTrace->TakeSnapshot(
|
|
// send_packet_return->Send_Buffer_Used[i]
|
|
// );
|
|
// #endif
|
|
|
|
// switch (send_packet_return->Errors[i])
|
|
// {
|
|
// case NETNUB_OK:
|
|
// break;
|
|
//
|
|
// case NETNUB_DATA_DUMPED:
|
|
// {
|
|
// SET_LOST_DATA();
|
|
// #if REPORT_LOST_DATA
|
|
// unsigned long
|
|
// temp_net_address = l4host->GetNetworkAddress();
|
|
//
|
|
// DEBUG_STREAM << "L4NetworkManager::SendBatchedMessageToNetnub - ";
|
|
// DEBUG_STREAM << "Data lost to ";
|
|
// DEBUG_STREAM << ((temp_net_address>>24) & 0xff)<<"."<<
|
|
// ((temp_net_address>>16) & 0xff)<<".";
|
|
// DEBUG_STREAM << ((temp_net_address>>8) & 0xff)<<"."<<
|
|
// (temp_net_address & 0xff)<<"\n";
|
|
// DEBUG_STREAM << flush;
|
|
// #endif
|
|
//
|
|
// //
|
|
// // Add this host to the dropped message host socket
|
|
// //
|
|
// Check(dropped_message_host_socket);
|
|
// dropped_message_host_socket->Add(l4host);
|
|
// CLEAR_LOST_DATA();
|
|
// }
|
|
// break;
|
|
//
|
|
// case NETNUB_STREAM_DISCONNECTED:
|
|
// {
|
|
// DEBUG_STREAM << "L4NetworkManager::SendBatchedMessageToNetnub - ";
|
|
// DEBUG_STREAM <<"Disconnect detected in send\n";
|
|
// DEBUG_STREAM <<flush;
|
|
//
|
|
// HostDisconnectedMessage
|
|
// myHostDisconnected(
|
|
// l4host->GetHostID(),
|
|
// l4host->GetNetworkSocket()
|
|
// );
|
|
// NetworkClient
|
|
// *client = GetNetworkClientPointer(NetworkManagerClientID);
|
|
// Check(client);
|
|
// client->ReceiveNetworkPacket(NULL, &myHostDisconnected);
|
|
// }
|
|
// break;
|
|
//
|
|
// default:
|
|
// {
|
|
// unsigned long
|
|
// temp_net_address = l4host->GetNetworkAddress();
|
|
// DEBUG_STREAM << "L4NetworkManager::SendBatchedMessageToNetnub - ";
|
|
// DEBUG_STREAM <<"Error "<<Net_Common_Ptr->Status<<" on ";
|
|
// DEBUG_STREAM <<((temp_net_address>>24) & 0xff)<<"."<<
|
|
// ((temp_net_address>>16) & 0xff)<<".";
|
|
// DEBUG_STREAM <<((temp_net_address>>8) & 0xff)<<"."<<
|
|
// (temp_net_address & 0xff)<<"\n";
|
|
// DEBUG_STREAM <<flush;
|
|
// Fail("NetNub error in transmit\n");
|
|
// }
|
|
// break;
|
|
// }
|
|
//}
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// L4NetworkManager::ExecuteBackground
|
|
//
|
|
Logical
|
|
L4NetworkManager::ExecuteBackground()
|
|
{
|
|
#if MESSAGE_BUFFERING
|
|
Check(this);
|
|
if (!messageBuffer.IsEmpty())
|
|
{
|
|
messageBuffer.AttemptToSend();
|
|
return True;
|
|
}
|
|
return False;
|
|
#else
|
|
return False;
|
|
#endif
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// L4NetworkManager::CheckBuffers Checks to see if there is a message for us
|
|
// buffered up in the network interface card and returns a pointer to it.
|
|
// NOTE: This thing should be using iterators to keep track of the people on
|
|
// line, waiting for connections and so on.
|
|
//
|
|
//WinSock support :ADB 01/06/07
|
|
Logical L4NetworkManager::CheckBuffers(NetworkPacket *network_packet)
|
|
{
|
|
//char *current_receive_ptr;
|
|
int
|
|
// i,
|
|
// status,
|
|
host_count;
|
|
Logical get_buffer_status;
|
|
short space_left;
|
|
Host *host;
|
|
L4Host
|
|
//*l4host,
|
|
*remote_host;
|
|
//*l4_host_list[MULTIPLE_SEND_PACKET_MAX];
|
|
|
|
//MultipleReceivePacketRequestPtr multiple_receive_packet;
|
|
//MultipleReceivePacketReturnPtr multiple_receive_packet_return;
|
|
|
|
//SOCKET next_socket_ptr;
|
|
|
|
SET_CHECK_BUFFERS();
|
|
//
|
|
// Get pointers to the receive packet request structure
|
|
//
|
|
//multiple_receive_packet = (MultipleReceivePacketRequestPtr)Net_Common_Ptr->Shared_Memory_Buffer;
|
|
//next_socket_ptr = multiple_receive_packet->Socket_Ptrs;
|
|
//multiple_receive_packet_return = (MultipleReceivePacketReturnPtr)Net_Common_Ptr->Shared_Memory_Buffer;
|
|
//current_receive_ptr = multiple_receive_packet_return->Received_Data;
|
|
//
|
|
// Check some of the pointers we plan to use
|
|
//
|
|
Check(application);
|
|
Check(application->GetHostManager());
|
|
|
|
//
|
|
// D1 RELAY MODE: the relay game connection is polled FIRST (control frames
|
|
// synthesize HostConnected/Disconnected inline; game frames return here).
|
|
// The classic per-host loop below still services the CONSOLE host; game
|
|
// hosts are virtual (skipped via the relay gate in the OnLine recv block).
|
|
//
|
|
if (relayMode)
|
|
{
|
|
RelayUdpKeepalive(relayLocalHostID);
|
|
if (CheckRelayUdp(network_packet)) // unreliable channel first
|
|
{
|
|
CLEAR_CHECK_BUFFERS();
|
|
return True;
|
|
}
|
|
if (CheckRelay(network_packet)) // reliable + control
|
|
{
|
|
CLEAR_CHECK_BUFFERS();
|
|
return True;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Make an iterator to take us through all the hosts
|
|
//
|
|
HostManager::RemoteHostIterator remote_hosts(application->GetHostManager());
|
|
//
|
|
// See if there is ANY data available from the MUNGA buffers before we go
|
|
// looking for it from the network.
|
|
//
|
|
if(GetNextMungaPacket(network_packet,&remote_hosts))
|
|
{
|
|
CLEAR_CHECK_BUFFERS();
|
|
return True;
|
|
}
|
|
//
|
|
// Reset to the first host, then attempt to read from every host that has
|
|
// room in it's buffers for a packet
|
|
//
|
|
host_count = 0;
|
|
remote_hosts.First();
|
|
while ((host = remote_hosts.ReadAndNext()) != NULL)
|
|
{
|
|
// get the host pointer and cast it over to an l4 host. This is done so we can
|
|
// bang directly on the pad buffers stored in the host. Yes, it's ugly but it will
|
|
// be fixed in the future !!!! GAC
|
|
remote_host = Cast_Object(L4Host*, host);
|
|
Check(remote_host);
|
|
//
|
|
// Our first priority is to check connection status on any hosts that we haven't
|
|
// connected to yet. If we encounter a host who has connected we immediately
|
|
// generate and return the appropriate message for transmission via routepacket
|
|
//
|
|
switch(remote_host->GetConnectStatus())
|
|
{
|
|
case L4Host::NoNetworkConnectionStatus:
|
|
//
|
|
// No connection at all, we skip polling this guy for data
|
|
//
|
|
continue;
|
|
case L4Host::OpeningConnectionStatus:
|
|
{
|
|
SOCKADDR_IN stream_net_address, host_net_address;
|
|
//
|
|
// This checks a connection that was created with an active open.
|
|
// If there wasn't a connection, continue on to the next guy
|
|
//
|
|
if(!CheckSocket(remote_host->GetNetworkSocket(), &stream_net_address))
|
|
continue;
|
|
|
|
//
|
|
// Because we opened to a specific host address we should ALWAYS connect
|
|
// to the host we expected. If the address of the computer on the remote
|
|
// doesn't match the one we opened to, we scream loudly and fail.
|
|
//
|
|
host_net_address = *remote_host->GetNetworkAddress();
|
|
if(!(stream_net_address == host_net_address))
|
|
{
|
|
char addressString[44];
|
|
DWORD bufferSize = sizeof(addressString);
|
|
WSAAddressToStringA((LPSOCKADDR)&stream_net_address, sizeof(SOCKADDR_IN), NULL, addressString, &bufferSize);
|
|
|
|
DEBUG_STREAM << "Host " << addressString << std::flush;
|
|
|
|
bufferSize = sizeof(addressString);
|
|
WSAAddressToStringA((LPSOCKADDR)&host_net_address, sizeof(SOCKADDR_IN), NULL, addressString, &bufferSize);
|
|
|
|
DEBUG_STREAM << " client " << addressString << "\n";
|
|
Fail("For an OPEN the host MUST match the client or something is really messed up\n");
|
|
}
|
|
//
|
|
// Finish the process of opening the port up, then return False
|
|
// as if no packet was received
|
|
//
|
|
HostConnectedMessage myHostConnect(
|
|
remote_host->GetHostID(),
|
|
stream_net_address,
|
|
remote_host->GetNetworkSocket());
|
|
NetworkClient *client = GetNetworkClientPointer(NetworkManagerClientID);
|
|
Check(client);
|
|
client->ReceiveNetworkPacket(NULL, &myHostConnect);
|
|
CLEAR_CHECK_BUFFERS();
|
|
return(False);
|
|
}
|
|
case L4Host::ListeningConnectionStatus:
|
|
{
|
|
SOCKET tempSocket = INVALID_SOCKET;
|
|
|
|
SOCKADDR_IN net_address;
|
|
memset(&net_address, 0, sizeof(SOCKADDR_IN));
|
|
net_address.sin_family = AF_INET;
|
|
|
|
if(remote_host->GetHostType() == ConsoleHostType)
|
|
{
|
|
if((tempSocket = accept(consoleListenerSocket, NULL, 0)) != INVALID_SOCKET)
|
|
{
|
|
closesocket(consoleListenerSocket);
|
|
consoleListenerSocket = INVALID_SOCKET;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if((tempSocket = accept(gameListenerSocket, NULL, 0)) == INVALID_SOCKET)
|
|
{
|
|
continue;
|
|
}
|
|
// TCP_NODELAY on the accepted GAME socket (not the console):
|
|
// without it Nagle coalesces the inbound per-frame update
|
|
// records into bursts -> the peer is received in lurches.
|
|
BOOL gameNoDelay = TRUE;
|
|
if (setsockopt(tempSocket, IPPROTO_TCP, TCP_NODELAY, (char*)&gameNoDelay, sizeof(gameNoDelay)))
|
|
DEBUG_STREAM << "WARN: could not set TCP_NODELAY on accepted game socket; setsockopt() failed with " << WSAGetLastError() << "\n" << std::flush;
|
|
}
|
|
|
|
remote_host->SetNetworkSocket(tempSocket);
|
|
if (!CheckSocket(remote_host->GetNetworkSocket(), &net_address))
|
|
continue;
|
|
|
|
char addressString[44];
|
|
DWORD bufferSize = sizeof(addressString);
|
|
WSAAddressToStringA((LPSOCKADDR)&net_address, sizeof(SOCKADDR_IN), NULL, addressString, &bufferSize);
|
|
DEBUG_STREAM << "STATUS: socket accepted from " << addressString << "!\n" << std::flush;
|
|
|
|
//
|
|
// The Waterloo TCP package has a problem with making connections. It always
|
|
// seems to connect people to the last socket that was listened on regardless
|
|
// of the port or net address settings. Because of this we have to make sure
|
|
// the connect we get belongs to the host we connected to, if it doesn't we
|
|
// have to find the right host and potentially swap the streams around. We
|
|
// also have to have a special case to handle listening for the console. Since
|
|
// we don't know the console machine's address in advance we (!!!!for now)
|
|
// assume we will only do a listen for the console if it's the only machine
|
|
// we're going to listen for.
|
|
//
|
|
Host *connect_host;
|
|
L4Host *connect_L4_host;
|
|
SOCKET swap_socket_ptr;
|
|
swap_socket_ptr = remote_host->GetNetworkSocket();
|
|
if(net_address == *remote_host->GetNetworkAddress())
|
|
{
|
|
//
|
|
// The address of the host that connected on this stream matched the one
|
|
// we were expecting (ie: port/network address filtering worked) so we
|
|
// just process it straight through. This should handle a host pretending
|
|
// to be a console and OPENing to another host.
|
|
//
|
|
HostConnectedMessage myHostConnect(
|
|
remote_host->GetHostID(),
|
|
net_address,
|
|
remote_host->GetNetworkSocket());
|
|
NetworkClient *client = GetNetworkClientPointer(NetworkManagerClientID);
|
|
Check(client);
|
|
client->ReceiveNetworkPacket(NULL, &myHostConnect);
|
|
}
|
|
else if(remote_host->GetHostType() == ConsoleHostType)
|
|
{
|
|
//
|
|
// This handles a LISTEN for a console with an unspecified net address
|
|
// Because the console uses a different port number than the game does
|
|
// whatever connects to a console host should be correct. So whoever
|
|
// shows up on this host should be a console (or thinks it is)
|
|
// NOTE: We actually set the host's net address here, since we didn't
|
|
// know it in advance.
|
|
//
|
|
remote_host->SetNetworkAddress(&net_address);
|
|
HostConnectedMessage myHostConnect(
|
|
remote_host->GetHostID(),
|
|
net_address,
|
|
remote_host->GetNetworkSocket());
|
|
NetworkClient *client = GetNetworkClientPointer(NetworkManagerClientID);
|
|
Check(client);
|
|
client->ReceiveNetworkPacket(NULL, &myHostConnect);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// The address of the host that connected didn't match the one we were
|
|
// expecting on this stream. So we have to find the right host and swap
|
|
// streams with them.
|
|
//
|
|
HostManager::RemoteHostIterator connect_hosts(application->GetHostManager());
|
|
while ((connect_host = connect_hosts.ReadAndNext()) != NULL)
|
|
{
|
|
connect_L4_host = Cast_Object(L4Host*, connect_host);
|
|
if(connect_L4_host->GetConnectStatus() != L4Host::ListeningConnectionStatus)
|
|
{
|
|
// skip hosts that are not currently listening since they are not
|
|
// candidates for swapping.
|
|
continue;
|
|
}
|
|
//
|
|
// If the address of this guy matches the address of the person who just
|
|
// connected, we swap their stream pointers
|
|
//
|
|
if(*connect_L4_host->GetNetworkAddress() == net_address)
|
|
{
|
|
remote_host->SetNetworkSocket(connect_L4_host->GetNetworkSocket());
|
|
connect_L4_host->SetNetworkSocket(swap_socket_ptr);
|
|
HostConnectedMessage myHostConnect(
|
|
connect_L4_host->GetHostID(),
|
|
net_address,
|
|
connect_L4_host->GetNetworkSocket());
|
|
NetworkClient *client = GetNetworkClientPointer(NetworkManagerClientID);
|
|
Check(client);
|
|
client->ReceiveNetworkPacket(NULL, &myHostConnect);
|
|
CLEAR_CHECK_BUFFERS();
|
|
return False;
|
|
}
|
|
}
|
|
}
|
|
CLEAR_CHECK_BUFFERS();
|
|
return False;
|
|
}
|
|
case L4Host::OnLineConnectionStatus:
|
|
// OnLine hosts just fall through so they can be polled
|
|
break;
|
|
default:
|
|
Fail("Host had illegal connection status\n");
|
|
break;
|
|
}
|
|
#if !BATCHED_RECEIVE
|
|
//
|
|
// D1 RELAY MODE: online GAME hosts are virtual (no socket of their own;
|
|
// their traffic arrives via CheckRelay above) -- recv'ing their
|
|
// INVALID_SOCKET would just spam WSAENOTSOCK. The console host keeps
|
|
// its real socket and is polled normally.
|
|
//
|
|
if (relayMode && remote_host->GetHostType() != ConsoleHostType)
|
|
{
|
|
continue;
|
|
}
|
|
//
|
|
// If we get this far the host is on line, if there is room to do a receive
|
|
// into this buffer then do one.
|
|
//
|
|
//unsigned long *next_status_ptr = &(multiple_receive_packet_return->Status[0]);
|
|
space_left = (short)(remote_host->pad_size - remote_host->pad_tail);
|
|
if(space_left >= MAX_RECEIVE_DATA_SIZE)
|
|
{
|
|
// there was sufficient room for a receive to happen, so do it
|
|
// Setup the request in the common area
|
|
//Net_Common_Ptr->Function = NETNUB_RECEIVE_PACKET;
|
|
//Net_Common_Ptr->Buffer_Length = sizeof(ReceivePacketRequest);
|
|
//receive_packet_request = (ReceivePacketRequestPtr)Net_Common_Ptr->Shared_Memory_Buffer;
|
|
//receive_packet_request->Socket_Ptr = remote_host->GetNetworkSocket();
|
|
// call the network server
|
|
//NetShare();
|
|
void* buffer = malloc(space_left);
|
|
int received = recv(remote_host->GetNetworkSocket(), (char*)buffer, space_left, 0);
|
|
// if we received data, copy it over into the buffer for this host
|
|
//switch(Net_Common_Ptr->Status)
|
|
if(received > 0)
|
|
{
|
|
//case NETNUB_RECEIVED_PACKET:
|
|
// copy the received data over to the host's data buffer
|
|
Mem_Copy(
|
|
&remote_host->pad_buffer[remote_host->pad_tail],
|
|
//Net_Common_Ptr->Shared_Memory_Buffer,
|
|
buffer,
|
|
//Net_Common_Ptr->Buffer_Length,
|
|
received,
|
|
space_left);
|
|
//remote_host->pad_tail += Net_Common_Ptr->Buffer_Length;
|
|
remote_host->pad_tail += received;
|
|
//break;
|
|
}
|
|
//case NETNUB_OK:
|
|
else if(received == SOCKET_ERROR)
|
|
{
|
|
//this indicates that no data is available and the socket is non blocking
|
|
//break;
|
|
DWORD error = WSAGetLastError();
|
|
switch (error)
|
|
{
|
|
case WSAECONNRESET:
|
|
// this will cause us to execute our disconnect code
|
|
received = 0;
|
|
break;
|
|
case WSAEWOULDBLOCK:
|
|
// this is expected when there is no data
|
|
break;
|
|
default:
|
|
DEBUG_STREAM << "L4NetworkManager::CheckBuffers: Socket recv returned an unexpected error: WSAGetLastError = " << error << std::endl << std::flush;
|
|
}
|
|
}
|
|
// case NETNUB_STREAM_DISCONNECTED:
|
|
if(received == 0)
|
|
{
|
|
{
|
|
HostDisconnectedMessage myHostDisconnected(
|
|
remote_host->GetHostID(),
|
|
remote_host->GetNetworkSocket());
|
|
NetworkClient *client = GetNetworkClientPointer(NetworkManagerClientID);
|
|
Check(client);
|
|
client->ReceiveNetworkPacket(NULL, &myHostDisconnected);
|
|
//break;
|
|
}
|
|
}
|
|
//default:
|
|
free(buffer);
|
|
}
|
|
}
|
|
#else
|
|
//
|
|
// If we get this far the host is on line, see if there is room to do a
|
|
// receive into it's buffer.
|
|
//
|
|
space_left = (short)( remote_host->pad_size - remote_host->pad_tail);
|
|
if(space_left >= MAX_RECEIVE_DATA_SIZE)
|
|
{
|
|
//
|
|
// There is room, add it to the list of hosts
|
|
//
|
|
l4_host_list[host_count++] = remote_host;
|
|
*(next_socket_ptr++) = remote_host->GetNetworkSocket();
|
|
// DEBUG_STREAM<<"Add stream "<<remote_host->GetNetworkSocket()<<"\n";
|
|
}
|
|
}
|
|
//
|
|
// Finish the request to netnub, then send it down
|
|
//
|
|
if(host_count == 0)
|
|
{
|
|
CLEAR_CHECK_BUFFERS();
|
|
return False;
|
|
}
|
|
// DEBUG_STREAM<<"Sending command to read "<<host_count<<" streams\n";
|
|
multiple_receive_packet->Socket_Count = host_count;
|
|
Net_Common_Ptr->Function = NETNUB_MULTIPLE_RECEIVE;
|
|
Net_Common_Ptr->Buffer_Length = sizeof(MultipleReceivePacketRequest);
|
|
NetNub::SendCommand();
|
|
//
|
|
// If NETNUB_OK comes back in status, it means we have no data and can quit
|
|
//
|
|
if(Net_Common_Ptr->Status == NETNUB_OK)
|
|
{
|
|
// DEBUG_STREAM<<"No data on any stream\n";
|
|
CLEAR_CHECK_BUFFERS();
|
|
return(False);
|
|
}
|
|
//
|
|
// One or more streams had data or errors
|
|
//
|
|
for(i = 0; i<host_count; i++)
|
|
{
|
|
l4host = l4_host_list[i];
|
|
status = multiple_receive_packet_return->Status[i];
|
|
// if(status != 0)
|
|
// DEBUG_STREAM<<"stream "<<l4host->GetNetworkSocket()<<" status "<<status<<"\n";
|
|
if(status == NETNUB_OK)
|
|
{
|
|
// No data or error here, go on to next host
|
|
continue;
|
|
}
|
|
else if(status == NETNUB_STREAM_DISCONNECTED)
|
|
{
|
|
HostDisconnectedMessage myHostDisconnected(
|
|
l4host->GetHostID(),
|
|
l4host->GetNetworkSocket());
|
|
NetworkClient *client = GetNetworkClientPointer(NetworkManagerClientID);
|
|
Check(client);
|
|
client->ReceiveNetworkPacket(NULL, &myHostDisconnected);
|
|
}
|
|
else if(status < 0)
|
|
{
|
|
// error of some kind, handle it
|
|
DEBUG_STREAM<<"MUNGA reported network error "<<status<<"\n";
|
|
Fail("MUNGA network error");
|
|
}
|
|
else
|
|
{
|
|
// there was data in the message
|
|
// copy the received data over to the host's data buffer
|
|
Mem_Copy(
|
|
&l4host->pad_buffer[l4host->pad_tail],
|
|
current_receive_ptr,
|
|
status,
|
|
(short)(l4host->pad_size - l4host->pad_tail));
|
|
l4host->pad_tail += (Word)status;
|
|
current_receive_ptr += status;
|
|
}
|
|
}
|
|
#endif
|
|
//
|
|
// Now loop through all the hosts again till we find one with a complete munga
|
|
// message to be returned for processing by the host.
|
|
//
|
|
get_buffer_status = GetNextMungaPacket(network_packet,&remote_hosts);
|
|
CLEAR_CHECK_BUFFERS();
|
|
return(get_buffer_status);
|
|
}
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// GetNextMungaPacket Checks the internal MUNGA packet assembly buffers to
|
|
// see if there are packets available on any open stream
|
|
//
|
|
Logical
|
|
L4NetworkManager::GetNextMungaPacket(
|
|
NetworkPacket *network_packet,
|
|
HostManager::RemoteHostIterator *remote_hosts)
|
|
{
|
|
short
|
|
i,
|
|
host_count,
|
|
move_size,
|
|
receive_packet_size;
|
|
Host
|
|
*host;
|
|
L4Host
|
|
*remote_host;
|
|
NetworkPacket
|
|
*incoming_packet;
|
|
|
|
//
|
|
// Loop through all the hosts and check their buffers
|
|
//
|
|
host_count = (short)remote_hosts->GetSize();
|
|
for(
|
|
i = 0;
|
|
i < host_count;
|
|
i++)
|
|
{
|
|
//
|
|
// This causes us to start checking hosts where we left off the last time
|
|
// it prevents any one host from always getting serviced first.
|
|
//
|
|
lastHostIteratorPosition++;
|
|
if(lastHostIteratorPosition >= remote_hosts->GetSize())
|
|
{
|
|
lastHostIteratorPosition = 0;
|
|
}
|
|
if((host = remote_hosts->GetNth(lastHostIteratorPosition)) == NULL)
|
|
{
|
|
break;
|
|
}
|
|
//
|
|
// get the host pointer and cast it over to an l4 host so we can look at it's buffers
|
|
//
|
|
remote_host = Cast_Object(L4Host*, host);
|
|
Check(remote_host);
|
|
//
|
|
// If we're in ConsoleOnly state, don't return messages for non-console streams
|
|
//
|
|
if((currentNetworkState == ConsoleOnly) &&
|
|
(remote_host->GetHostType() != ConsoleHostType))
|
|
continue;
|
|
//
|
|
// does this host data buffer contain a complete munga message header
|
|
//
|
|
if(remote_host->pad_tail >= sizeof(NetworkPacket))
|
|
{
|
|
//
|
|
// enough bytes for a complete message header, read the message length and
|
|
// figure how many bytes the complete packet should be
|
|
//
|
|
incoming_packet = (NetworkPacket*)remote_host->pad_buffer;
|
|
receive_packet_size = (short)(incoming_packet->messageData.messageLength + sizeof(NetworkPacketHeader));
|
|
Verify(receive_packet_size > 0);
|
|
// Are there enough bytes in the buffer to make up this packet?
|
|
if(remote_host->pad_tail >= receive_packet_size)
|
|
{
|
|
// we have a complete packet, copy it into the output buffer and rejustify the host's
|
|
// buffer (inefficient as hell but easy to write for now)
|
|
Mem_Copy(
|
|
network_packet,
|
|
remote_host->pad_buffer,
|
|
receive_packet_size,
|
|
NETWORKMANAGER_BUFFER_SIZE);
|
|
move_size = (short)(remote_host->pad_tail - receive_packet_size);
|
|
// don't do the next step if the buffer is empty
|
|
// we need to use memmove because the source and destination addresses overlap
|
|
// and memcopy doesn't know how to deal with that.
|
|
Verify(move_size < L4HOST_PAD_BUFFER_SIZE);
|
|
if(move_size != 0)
|
|
{
|
|
memmove(
|
|
remote_host->pad_buffer,
|
|
&remote_host->pad_buffer[receive_packet_size],
|
|
move_size);
|
|
}
|
|
remote_host->pad_tail -= receive_packet_size;
|
|
// BT bring-up trace (env BT_NET_TRACE): every received game packet.
|
|
if (getenv("BT_NET_TRACE"))
|
|
{
|
|
DEBUG_STREAM << "[net-rx] client=" << (int)network_packet->clientID
|
|
<< " msgID=" << (int)network_packet->messageData.messageID
|
|
<< " len=" << (int)network_packet->messageData.messageLength
|
|
<< " from host " << (int)network_packet->fromHost << "\n" << std::flush;
|
|
}
|
|
// Return true (packet received)
|
|
return True;
|
|
}
|
|
else
|
|
{
|
|
// cout<<"++++++++Waiting for "<<receive_packet_size<<" byte packet, got"<<remote_host->pad_tail<<"\n";
|
|
}
|
|
}
|
|
}
|
|
//
|
|
// If we get here, all the streams were empty
|
|
//
|
|
return False;
|
|
}
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// L4NetworkManager::RemovePacket If the network implimentation requires us to
|
|
// free up a packet buffer, this routine would do it.
|
|
//
|
|
void
|
|
L4NetworkManager::RemovePacket(NetworkPacket*)
|
|
{
|
|
//
|
|
// This function is not required for now
|
|
//
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// L4NetworkManager::NetShare This is Gene's implementation of the protected
|
|
// to real mode interface.
|
|
//
|
|
//WinSock support :ADB 01/06/07
|
|
//void NetNub::SendCommand()
|
|
//{
|
|
// struct RMREG
|
|
// rmreg;
|
|
// union REGS
|
|
// regs;
|
|
// struct SREGS
|
|
// sregs;
|
|
//
|
|
// SET_CALL_NETNUB();
|
|
// //
|
|
// // Check a few things before making the DPMI call to the interrupt
|
|
// //
|
|
// Verify(Net_Common_Ptr->Buffer_Length <= SHARED_MEMORY_SIZE);
|
|
// //
|
|
// // This assembly routine copies the contents of the NetCommon down into real mode
|
|
// //
|
|
// setRMBuff();
|
|
// //
|
|
// // Setup for the call to the DPMI to simulate real mode interrupt
|
|
// //
|
|
// regs.x.eax = 0x0300; // function of INT31
|
|
// regs.h.bl = intno; //
|
|
// regs.h.bh = 0; //flags
|
|
// regs.x.ecx = 0; //number of words to copy from protected mode to real mode stack
|
|
// regs.x.edi =(unsigned int)&rmreg; //es:edi is an address of RMREG structure
|
|
// segread(&sregs);
|
|
// rmreg.reserved = 0; //default settings:
|
|
// rmreg.fs = 0;
|
|
// rmreg.gs = 0;
|
|
// rmreg.flags = 0;
|
|
// rmreg.ss = 0; //real mode stack is provided by DPMI unless ss:sp != 0
|
|
// rmreg.sp = 0;
|
|
// //
|
|
// // Call the DPMI
|
|
// //
|
|
// int386x(0x31,®s,®s,&sregs);
|
|
// //
|
|
// // Check for an error
|
|
// //
|
|
// if (regs.x.cflag)
|
|
// {
|
|
// DEBUG_STREAM<<"int386x():ERROR="<<regs.x.eax<<endl;
|
|
// Fail("Error calling netnub\n");
|
|
// }
|
|
// //
|
|
// // Copy the real mode data back up into the protected mode common area
|
|
// //
|
|
// getRMBuff();
|
|
// //
|
|
// // Check the version number before returning
|
|
// //
|
|
// Verify(Net_Common_Ptr->Version_Number == NETCOM_VERSION);
|
|
// CLEAR_CALL_NETNUB();
|
|
//}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~Encapsulations of netnub calls~~~~~~~~~~~~~~~~~~~~~~
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// L4NetworkManager::GetMyAddress This routine calls the netnub and returns
|
|
// our network address if everything is ok or zero if it's not. It can also be
|
|
// used to tickle the netnub or to check status of the netnub.
|
|
//
|
|
//WinSock support :ADB 01/06/07
|
|
NetworkAddress* L4NetworkManager::GetMyAddress()
|
|
{
|
|
char name[255];
|
|
PHOSTENT hostinfo;
|
|
|
|
if (gethostname(name, sizeof(name)) != 0)
|
|
{
|
|
DEBUG_STREAM << "ERROR: gethostname() failed!" << std::endl << std::flush;
|
|
return NULL;
|
|
}
|
|
|
|
if ((hostinfo = gethostbyname(name)) == NULL)
|
|
{
|
|
DEBUG_STREAM << "ERROR: gethostbyname() failed!" << std::endl << std::flush;
|
|
return NULL;
|
|
}
|
|
|
|
// count how many addresses we have
|
|
for (num_addresses = 0; hostinfo->h_addr_list[num_addresses]; num_addresses++);
|
|
|
|
NetworkAddress* myAddresses = new NetworkAddress[num_addresses + 1];
|
|
for (int i=0; i<num_addresses; i++)
|
|
myAddresses[i] = *((NetworkAddress*)hostinfo->h_addr_list[i]);
|
|
|
|
// add 127.0.0.1 to list
|
|
myAddresses[num_addresses++] = (NetworkAddress)0x0100007F;
|
|
|
|
return myAddresses;
|
|
}
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// L4NetworkManager::ResolveAddress This routine takes a CString containing
|
|
// a host name and makes the appropriate network calls to resolve it to a
|
|
// binary internet address.
|
|
//
|
|
//WinSock support :ADB 01/06/07
|
|
bool L4NetworkManager::ResolveAddress(CString host_name, SOCKADDR_IN *address)
|
|
{
|
|
//if (strstr(host_name, ":"))
|
|
{
|
|
// this address contains a colon so we'll do things a little differently
|
|
int bufferSize = sizeof(SOCKADDR_IN);
|
|
WSAStringToAddressA((LPSTR)host_name, AF_INET, NULL, (LPSOCKADDR)address, &bufferSize);
|
|
if (address->sin_port == 0)
|
|
address->sin_port = htons(GAME_NET_PORT);
|
|
return true;
|
|
}
|
|
|
|
addrinfo* hostAddrinfo = NULL;
|
|
addrinfo aiHints;
|
|
memset(&aiHints, 0, sizeof(aiHints));
|
|
aiHints.ai_family = AF_INET;
|
|
aiHints.ai_socktype = SOCK_STREAM;
|
|
aiHints.ai_protocol = IPPROTO_TCP;
|
|
|
|
int iResult = getaddrinfo((char*)host_name, NULL, &aiHints , &hostAddrinfo);
|
|
if(iResult != 0)
|
|
{
|
|
DEBUG_STREAM<<"ERROR: getaddrinfo() failed with " << WSAGetLastError() << "!\n";
|
|
return NULL;
|
|
}
|
|
|
|
addrinfo* addr = hostAddrinfo;
|
|
while(addr != NULL)
|
|
{
|
|
if(addr->ai_addr->sa_family == AF_INET)
|
|
break;
|
|
addr = addr->ai_next;
|
|
}
|
|
|
|
memset(address, 0, sizeof(SOCKADDR_IN));
|
|
*address = *((sockaddr_in*)&addr->ai_addr->sa_data);
|
|
if (address->sin_port == 32778)
|
|
address->sin_port = htons(CONSOLE_NET_PORT);
|
|
|
|
freeaddrinfo(hostAddrinfo);
|
|
|
|
//
|
|
// Check the status returns and return the address if we got one ok
|
|
//
|
|
if(!address || address->sin_addr.S_un.S_addr == 0 || address->sin_port == 0)
|
|
{
|
|
DEBUG_STREAM << "Couldn't resolve " << host_name << " to a net address\n";
|
|
Fail("unresolvable network address\n");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// L4NetworkManager::CheckSocket This routine does checks a socket and returns
|
|
// the state of that socket (connected or not)
|
|
//
|
|
//WinSock support :ADB 01/06/07
|
|
bool L4NetworkManager::CheckSocket(SOCKET socket, SOCKADDR_IN *remoteEndpoint)
|
|
{
|
|
if (remoteEndpoint)
|
|
{
|
|
int size = sizeof(SOCKADDR_IN);
|
|
memset(remoteEndpoint, 0, size);
|
|
if(!getpeername(socket, (sockaddr*)remoteEndpoint, &size))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// L4NetworkManager::OpenConnection This routine does the appropriate calls to
|
|
// start the open process for another host. You can do an active or passive open
|
|
// with this routine by passing the appropriate parameters in. The arguments
|
|
// are geared to the typical TCP/IP connection parameters. After calling this
|
|
// routine you must create the host yourself.
|
|
//
|
|
//WinSock support :ADB 01/06/07
|
|
//unsigned long OpenConnection(
|
|
SOCKET L4NetworkManager::OpenConnection(
|
|
int connection_type, // NETNUB_TCP_LISTEN or NETNUB_TCP_OPEN
|
|
int local_port,
|
|
int remote_port,
|
|
int internet_address)
|
|
{
|
|
//TCPOpenRequestPtr
|
|
// tcp_open_request;
|
|
//TCPOpenReturnPtr
|
|
// tcp_open_return;
|
|
//
|
|
// Checkup everything
|
|
//
|
|
//Check_Pointer(Net_Common_Ptr);
|
|
//
|
|
// Make sure we are using a legal open mode
|
|
//
|
|
Verify( (connection_type == NETNUB_TCP_LISTEN) ||
|
|
(connection_type == NETNUB_TCP_OPEN));
|
|
//
|
|
// Format the open/listen request
|
|
//
|
|
//tcp_open_request = (TCPOpenRequestPtr)Net_Common_Ptr->Shared_Memory_Buffer;
|
|
//tcp_open_return = (TCPOpenReturnPtr)Net_Common_Ptr->Shared_Memory_Buffer;
|
|
//Net_Common_Ptr->Function = (short)connection_type;
|
|
//Net_Common_Ptr->Buffer_Length = sizeof(TCPOpenRequest);
|
|
//tcp_open_request->Local_Port = (short)local_port;
|
|
//tcp_open_request->Remote_Port = (short)remote_port;
|
|
//tcp_open_request->Internet_Address = internet_address;
|
|
//tcp_open_request->Socket_Ptr = 0; // !!! 0 causes netnub to allocate socket
|
|
//NetNub::SendCommand();
|
|
//if(Net_Common_Ptr->Status == NETNUB_ERROR)
|
|
//{
|
|
// DEBUG_STREAM<<"NetNub TCP Open/Listen error #"<<tcp_open_return->Socket_Ptr<<" listening for ";
|
|
// DEBUG_STREAM<<((internet_address>>24) & 0xff)<<"."<<((internet_address>>16) & 0xff)<<".";
|
|
// DEBUG_STREAM<<((internet_address>>8) & 0xff)<<"."<<(internet_address & 0xff)<<"\n";
|
|
// Fail("Netnub error during open/listen\n");
|
|
//}
|
|
//return(tcp_open_return->Socket_Ptr);
|
|
|
|
if(connection_type == NETNUB_TCP_OPEN)
|
|
{
|
|
DEBUG_STREAM << "Opening connection to " << inet_ntoa(*(in_addr*)&internet_address) << ":" << remote_port << "...\n" << std::flush;
|
|
|
|
SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
if(sock == INVALID_SOCKET)
|
|
{
|
|
DEBUG_STREAM << "ERROR: socket() failed with " << WSAGetLastError() << "!\n";
|
|
return INVALID_SOCKET;
|
|
}
|
|
|
|
bool reuseAddr = true;
|
|
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&reuseAddr, sizeof(bool)))
|
|
{
|
|
DEBUG_STREAM << "ERROR: Could not set SO_REUSEADDR on socket; setsockopt() failed with " << WSAGetLastError() << "!\n" << std::flush;
|
|
WSACleanup();
|
|
PostQuitMessage(AbortExitCodeID);
|
|
return INVALID_SOCKET;
|
|
}
|
|
|
|
sockaddr_in localEndpoint;
|
|
memset(&localEndpoint, 0, sizeof(localEndpoint));
|
|
localEndpoint.sin_family = AF_INET;
|
|
localEndpoint.sin_port = htons(local_port);
|
|
localEndpoint.sin_addr.S_un.S_addr = INADDR_ANY;
|
|
if(bind(sock, (sockaddr*)&localEndpoint, sizeof(localEndpoint)))
|
|
{
|
|
DEBUG_STREAM << "ERROR: Could not bind local socket; bind() failed with " << WSAGetLastError() << "!\n" << std::flush;
|
|
WSACleanup();
|
|
PostQuitMessage(AbortExitCodeID);
|
|
return INVALID_SOCKET;
|
|
}
|
|
|
|
sockaddr_in remoteEndpoint;
|
|
memset(&remoteEndpoint, 0, sizeof(remoteEndpoint));
|
|
remoteEndpoint.sin_family = AF_INET;
|
|
//VERIFY: Network vs Host byte order?
|
|
remoteEndpoint.sin_addr.S_un.S_addr = internet_address;
|
|
remoteEndpoint.sin_port = htons(remote_port);
|
|
int wsaError, iResult;
|
|
|
|
do
|
|
{
|
|
iResult = connect(sock, (sockaddr*)&remoteEndpoint, sizeof(remoteEndpoint));
|
|
|
|
if (iResult != 0)
|
|
{
|
|
wsaError = WSAGetLastError();
|
|
}
|
|
} while (iResult != 0 && wsaError == 10061);
|
|
|
|
if(iResult != 0)
|
|
{
|
|
DEBUG_STREAM << "ERROR: connect() failed with " << wsaError << "!\n" << std::flush;
|
|
return INVALID_SOCKET;
|
|
}
|
|
|
|
//set to non blocking
|
|
unsigned long enable = 1;
|
|
if(ioctlsocket(sock, FIONBIO, &enable))
|
|
{
|
|
DEBUG_STREAM << "ERROR: Could not set actively opened socket to nonblocking; ioctlsocket() failed with " << WSAGetLastError() << "!\n" << std::flush;
|
|
WSACleanup();
|
|
PostQuitMessage(AbortExitCodeID);
|
|
}
|
|
// TCP_NODELAY: disable Nagle so the small per-frame update records ship
|
|
// immediately instead of being coalesced. With Nagle on (the default),
|
|
// Nagle + delayed-ACK batch the tiny position packets into ~40-200ms
|
|
// bursts, so a peer that moves at a steady speed is RECEIVED in lurches
|
|
// (measured: dead-reckoned ground speed swinging 3x per 0.25s window).
|
|
// The pod net carries steady real-time state where latency, not
|
|
// throughput, is what matters -- exactly the case NODELAY is for.
|
|
{
|
|
BOOL noDelay = TRUE;
|
|
if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char*)&noDelay, sizeof(noDelay)))
|
|
DEBUG_STREAM << "WARN: could not set TCP_NODELAY on active socket; setsockopt() failed with " << WSAGetLastError() << "\n" << std::flush;
|
|
}
|
|
return sock;
|
|
}
|
|
else if(connection_type == NETNUB_TCP_LISTEN)
|
|
{
|
|
if(gameListenerSocket == NULL)
|
|
{
|
|
DEBUG_STREAM << "Starting to listen on port " << local_port << "...\n" << std::flush;
|
|
|
|
gameListenerSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
if(gameListenerSocket == INVALID_SOCKET)
|
|
{
|
|
DEBUG_STREAM << "ERROR: Could not create game listener socket; socket() failed with " << WSAGetLastError() << "!\n" << std::flush;
|
|
WSACleanup();
|
|
PostQuitMessage(AbortExitCodeID);
|
|
}
|
|
|
|
bool reuseAddr = true;
|
|
if (setsockopt(gameListenerSocket, SOL_SOCKET, SO_REUSEADDR, (char*)&reuseAddr, sizeof(bool)))
|
|
{
|
|
DEBUG_STREAM << "ERROR: Could not set SO_REUSEADDR on game listener socket; setsockopt() failed with " << WSAGetLastError() << "!\n" << std::flush;
|
|
WSACleanup();
|
|
PostQuitMessage(AbortExitCodeID);
|
|
return INVALID_SOCKET;
|
|
}
|
|
|
|
sockaddr_in localEndpoint;
|
|
memset(&localEndpoint, 0, sizeof(localEndpoint));
|
|
localEndpoint.sin_family = AF_INET;
|
|
localEndpoint.sin_port = htons(local_port);
|
|
localEndpoint.sin_addr.S_un.S_addr = INADDR_ANY;
|
|
if(bind(gameListenerSocket, (sockaddr*)&localEndpoint, sizeof(localEndpoint)))
|
|
{
|
|
DEBUG_STREAM << "ERROR: Could not bind game listener socket; bind() failed with " << WSAGetLastError() << "!\n" << std::flush;
|
|
WSACleanup();
|
|
PostQuitMessage(AbortExitCodeID);
|
|
return INVALID_SOCKET;
|
|
}
|
|
if(listen(gameListenerSocket, 25))
|
|
{
|
|
DEBUG_STREAM << "ERROR: Could not listen on game listener socket; listen() failed with " << WSAGetLastError() << "!\n" << std::flush;
|
|
WSACleanup();
|
|
PostQuitMessage(AbortExitCodeID);
|
|
return INVALID_SOCKET;
|
|
}
|
|
//set to non blocking
|
|
unsigned long enable = 1;
|
|
if(ioctlsocket(gameListenerSocket, FIONBIO, &enable))
|
|
{
|
|
DEBUG_STREAM << "ERROR: Could not set game listener socket to nonblocking; ioctlsocket() failed with " << WSAGetLastError() << "!\n" << std::flush;
|
|
WSACleanup();
|
|
PostQuitMessage(AbortExitCodeID);
|
|
return INVALID_SOCKET;
|
|
}
|
|
}
|
|
else
|
|
DEBUG_STREAM << "Listen requested on port " << local_port << " but gameListenerSocket already existed!\n" << std::flush;
|
|
|
|
return INVALID_SOCKET;
|
|
}
|
|
else
|
|
return INVALID_SOCKET;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// L4NetworkManager::CloseConnection This routine does the appropriate calls to
|
|
// close a connection to another host, it really should wait for an error return
|
|
// but doesn't right now.
|
|
//
|
|
//WinSock support :ADB 01/06/07
|
|
void L4NetworkManager::CloseConnection(SOCKET socket_ptr) // socket address from netnub (to close)
|
|
{
|
|
//TCPCloseRequestPtrclose_request;
|
|
//
|
|
// Checkup everything
|
|
//
|
|
Check_Pointer(Net_Common_Ptr);
|
|
//
|
|
// Do the actual close
|
|
//
|
|
//Net_Common_Ptr->Function = NETNUB_TCP_CLOSE;
|
|
//Net_Common_Ptr->Buffer_Length = sizeof(TCPCloseRequest);
|
|
//close_request = (TCPCloseRequestPtr)Net_Common_Ptr->Shared_Memory_Buffer;
|
|
//close_request->Socket_Ptr = socket_ptr;
|
|
//NetNub::SendCommand();
|
|
shutdown(socket_ptr, SD_BOTH);
|
|
closesocket(socket_ptr);
|
|
// Check for errors
|
|
//if(Net_Common_Ptr->Status != NETNUB_OK)
|
|
//{
|
|
// DEBUG_STREAM<<"NetNub close error "<<Net_Common_Ptr->Status<<" on stream "<<socket_ptr<<"\n";
|
|
// return;
|
|
//}
|
|
}
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// L4NetworkManager::Mode does mode settings on the network interface
|
|
//
|
|
//WinSock support :ADB 01/06/07
|
|
void L4NetworkManager::Mode(NetworkMode myMode)
|
|
{
|
|
//
|
|
// D1: the mode is now STORED (the 2007 port ignored it). The app ladder
|
|
// drives it authentically -- UnreliableMode at LoadingMission, back to
|
|
// Reliable at mission end (APP.cpp) -- and the relay UDP phase routes
|
|
// unreliable-window traffic by (mode == UnreliableMode && !ReliableFlag),
|
|
// restoring the 1995 NETNUB reliable/unreliable split.
|
|
//
|
|
currentNetworkMode = myMode;
|
|
|
|
//SetSwitchesRequestPtr set_switches_request;
|
|
//
|
|
// No netnub present? Just return
|
|
//
|
|
//if(Net_Common_Ptr == NULL)
|
|
if(wsaData == NULL)
|
|
{
|
|
return;
|
|
}
|
|
//
|
|
// Checkup everything
|
|
//
|
|
//Check_Pointer(Net_Common_Ptr);
|
|
//
|
|
// Copy the text over (including the 0 terminator)
|
|
//
|
|
//set_switches_request = (SetSwitchesRequestPtr)Net_Common_Ptr->Shared_Memory_Buffer;
|
|
//switch(myMode)
|
|
//{
|
|
// case ReliableMode:
|
|
// set_switches_request->blocking_switch = True;
|
|
// break;
|
|
// case UnreliableMode:
|
|
// set_switches_request->blocking_switch = False;
|
|
// break;
|
|
//}
|
|
//Net_Common_Ptr->Function = NETNUB_SET_SWITCHES;
|
|
//Net_Common_Ptr->Buffer_Length = sizeof(SetSwitchesRequest);
|
|
//NetNub::SendCommand();
|
|
//// Check for errors
|
|
//if(Net_Common_Ptr->Status != NETNUB_OK)
|
|
//{
|
|
// DEBUG_STREAM<<"NetNub mode call failed\n";
|
|
// return;
|
|
//}
|
|
}
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// L4NetworkManager::Marker This routine does the appropriate calls to send
|
|
// a text marker down to netnub
|
|
//
|
|
//WinSock support :ADB 01/06/07
|
|
void L4NetworkManager::Marker(char *marker_text) // text message to send down
|
|
{
|
|
//int
|
|
// marker_text_size;
|
|
////
|
|
//// No netnub present? Just return
|
|
// //
|
|
//if(Net_Common_Ptr == NULL)
|
|
//{
|
|
// return;
|
|
//}
|
|
////
|
|
//// Checkup everything
|
|
////
|
|
//Check_Pointer(Net_Common_Ptr);
|
|
////
|
|
//// Copy the text over (including the 0 terminator)
|
|
////
|
|
//marker_text_size = strlen(marker_text)+1;
|
|
//Mem_Copy(
|
|
// (char*)Net_Common_Ptr->Shared_Memory_Buffer,
|
|
// marker_text,
|
|
// marker_text_size,
|
|
// SHARED_MEMORY_SIZE);
|
|
//Net_Common_Ptr->Function = NETNUB_MARKER_TEXT;
|
|
//Net_Common_Ptr->Buffer_Length = (short)marker_text_size;
|
|
//NetNub::SendCommand();
|
|
//// Check for errors
|
|
//if(Net_Common_Ptr->Status != NETNUB_OK)
|
|
//{
|
|
// DEBUG_STREAM<<"NetNub marker text call failed\n";
|
|
// return;
|
|
//}
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
Logical
|
|
L4NetworkManager::TestInstance() const
|
|
{
|
|
return IsDerivedFrom(*GetClassDerivations());
|
|
}
|
|
|
|
#ifdef TEST_CLASS
|
|
#include "l4net.tcp"
|
|
#endif
|
|
|
|
|
|
#if 0
|
|
else if(networkStartupMode == MasterMode && remoteHostCount > 0)
|
|
{
|
|
enum ConsoleState
|
|
{
|
|
GetNextHostConsoleState,
|
|
OpenNextHostConsoleState,
|
|
WaitForConnectConsoleState,
|
|
SendEggConsoleState,
|
|
WaitForEggAckConsoleState,
|
|
WaitForGameConnectConsoleState,
|
|
CloseStreamConsoleState,
|
|
WaitForAllHostsConsoleState,
|
|
DoneConsoleState
|
|
};
|
|
long
|
|
size_of_notation_text;
|
|
int
|
|
game_machine_up_count;
|
|
ConsoleState
|
|
consoleState;
|
|
NetworkAddress
|
|
temp_net_address;
|
|
char
|
|
*temp_notation_text;
|
|
Host
|
|
*host;
|
|
NotationFile
|
|
*temp_notation_file;
|
|
//
|
|
// Master must send the egg to the other hosts and wait for each to acknowledge
|
|
// it's processing before going on to the next host. First we turn the notation
|
|
// file into text so we can send it.
|
|
//
|
|
temp_notation_file = new NotationFile(GlobalEggFileName);
|
|
Register_Object(temp_notation_file);
|
|
if(temp_notation_file->PageCount() == 0)
|
|
{
|
|
Fail("Tried to startup with an empty notation file\n");
|
|
}
|
|
size_of_notation_text = temp_notation_file->SizeOfText();
|
|
temp_notation_text = new char[size_of_notation_text];
|
|
Register_Pointer(temp_notation_text);
|
|
temp_notation_file->WriteText(
|
|
temp_notation_text,
|
|
size_of_notation_text);
|
|
//
|
|
// Now setup and run the state machine till everyone has been connected.
|
|
//
|
|
HostManager::RemoteHostIterator remote_hosts(application->GetHostManager());
|
|
currentNetworkState = ConsoleOnly;
|
|
eggAcknowledged = False;
|
|
consoleState = GetNextHostConsoleState;
|
|
while ( consoleState != DoneConsoleState && remoteHostCount > 0)
|
|
{
|
|
//
|
|
// Poll the network (the arrival times of these measages are complicated
|
|
// so I don't advise messing with the order of this loop casually)
|
|
//
|
|
RoutePacket();
|
|
//
|
|
// Do what we're supposed to based on our current state
|
|
//
|
|
switch(consoleState)
|
|
{
|
|
case GetNextHostConsoleState:
|
|
//
|
|
// Get the next game machine host
|
|
//
|
|
Tell("GetNextHostConsoleState--Getting next host\n");
|
|
game_machine_up_count = numberOfMungaHostsConnected;
|
|
remote_hosts.First();
|
|
while((host = remote_hosts.ReadAndNext()) != NULL)
|
|
{
|
|
L4Host* an_l4host = Cast_Object(L4Host*, host);
|
|
if(an_l4host->GetHostType() == ConsoleHostType ||
|
|
an_l4host->GetConnectStatus() == L4Host::NoNetworkConnectionStatus ||
|
|
an_l4host->GetConnectStatus() == L4Host::OnLineConnectionStatus)
|
|
continue;
|
|
if(an_l4host->GetHostType() == GameMachineHostType)
|
|
break;
|
|
}
|
|
if(host == NULL)
|
|
{
|
|
consoleState = WaitForAllHostsConsoleState;
|
|
}
|
|
else
|
|
{
|
|
consoleState = OpenNextHostConsoleState;
|
|
}
|
|
break;
|
|
case OpenNextHostConsoleState:
|
|
// Get the network address of this game machine host and open it
|
|
temp_net_address = host->GetNetworkAddress();
|
|
Tell("OpenNextHostConsoleState--Opening console to ");
|
|
Tell(((temp_net_address>>24) & 0xff)<<"."<<((temp_net_address>>16) & 0xff)<<".");
|
|
Tell(((temp_net_address>>8) & 0xff)<<"."<<(temp_net_address & 0xff)<<"\n");
|
|
socket_ptr = OpenConnection(
|
|
NETNUB_TCP_OPEN,
|
|
0,
|
|
CONSOLE_NET_PORT,
|
|
temp_net_address);
|
|
// Create a console host for this guy
|
|
myConsoleHost = new L4Host(
|
|
FirstLegalHostID,
|
|
ConsoleHostType,
|
|
temp_net_address,
|
|
socket_ptr,
|
|
"Console");
|
|
Register_Object(myConsoleHost);
|
|
// Register the console host with the host manager
|
|
Check(application);
|
|
Check(application->GetHostManager());
|
|
application->GetHostManager()->AdoptRemoteHost(myConsoleHost);
|
|
myConsoleHost->SetConnectStatus(L4Host::OpeningConnectionStatus);
|
|
numberOfConsoleHostsConnected = 0;
|
|
consoleState = WaitForConnectConsoleState;
|
|
break;
|
|
case WaitForConnectConsoleState:
|
|
Tell("C"<<numberOfMungaHostsConnected);
|
|
if(numberOfConsoleHostsConnected != 0)
|
|
{
|
|
consoleState = SendEggConsoleState;
|
|
Tell("\nWaitForConnectConsoleState--Connected\n");
|
|
}
|
|
break;
|
|
case SendEggConsoleState:
|
|
{
|
|
int
|
|
bytes_left,
|
|
next_send,
|
|
sequence;
|
|
next_send = 0;
|
|
sequence = 0;
|
|
// Verify(size_of_notation_text < 1000);
|
|
while(next_send < size_of_notation_text)
|
|
{
|
|
Tell("Sent Egg message #"<<sequence<<"\n");
|
|
bytes_left = size_of_notation_text - next_send;
|
|
if(bytes_left > 1000)
|
|
bytes_left = 1000;
|
|
ReceiveEggFileMessage myEggMessage(
|
|
sequence,
|
|
size_of_notation_text,
|
|
temp_notation_text + next_send,
|
|
bytes_left);
|
|
Send(
|
|
&myEggMessage,
|
|
NetworkClient::NetworkManagerClientID,
|
|
myConsoleHost->GetHostID());
|
|
next_send += bytes_left;
|
|
sequence++;
|
|
}
|
|
eggAcknowledged = False;
|
|
consoleState = WaitForEggAckConsoleState;
|
|
break;
|
|
}
|
|
case WaitForEggAckConsoleState:
|
|
Tell("E"<<numberOfMungaHostsConnected);
|
|
if(eggAcknowledged)
|
|
{
|
|
consoleState = WaitForGameConnectConsoleState;
|
|
DEBUG_STREAM<<"\nWaitForEggAckConsoleState--Got one from";
|
|
DEBUG_STREAM<<((temp_net_address>>24) & 0xff)<<"."<<((temp_net_address>>16) & 0xff)<<".";
|
|
DEBUG_STREAM<<((temp_net_address>>8) & 0xff)<<"."<<(temp_net_address & 0xff)<<"\n";
|
|
}
|
|
break;
|
|
case WaitForGameConnectConsoleState:
|
|
Tell("G"<<numberOfMungaHostsConnected);
|
|
if(game_machine_up_count < numberOfMungaHostsConnected)
|
|
{
|
|
consoleState = CloseStreamConsoleState;
|
|
Tell("\nWaitForGameConnectConsoleState--Got one\n");
|
|
}
|
|
break;
|
|
case CloseStreamConsoleState:
|
|
{
|
|
Tell("CloseStreamConsoleState -- closing console\n");
|
|
#if 0
|
|
HostDisconnectedMessage myHostDisconnected;
|
|
Send(
|
|
&myHostDisconnected,
|
|
NetworkClient::NetworkManagerClientID,
|
|
myConsoleHost->GetHostID());
|
|
#endif
|
|
Check(myConsoleHost);
|
|
CloseConnection(myConsoleHost->GetNetworkSocket());
|
|
// deregister this host with the host manager
|
|
application->GetHostManager()->OrphanRemoteHost(myConsoleHost);
|
|
Unregister_Object(myConsoleHost);
|
|
delete myConsoleHost;
|
|
myConsoleHost = NULL;
|
|
consoleState = GetNextHostConsoleState;
|
|
break;
|
|
}
|
|
case WaitForAllHostsConsoleState:
|
|
Tell("W"<<numberOfMungaHostsConnected);
|
|
if(numberOfMungaHostsConnected >= remoteHostCount)
|
|
{
|
|
consoleState = DoneConsoleState;
|
|
Tell("\WaitForAllHostsConsoleState--done connecting\n");
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
Unregister_Pointer(temp_notation_text);
|
|
delete temp_notation_text;
|
|
Unregister_Object(temp_notation_file);
|
|
delete temp_notation_file;
|
|
currentNetworkState = NormalState;
|
|
}
|
|
#endif
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~ MessageQueue__SendRequest ~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
MessageQueue__SendRequest::MessageQueue__SendRequest(
|
|
NetworkClient::ClientID client_ID,
|
|
Receiver::Message *message
|
|
)
|
|
{
|
|
//
|
|
// Set client ID
|
|
//
|
|
clientID = client_ID;
|
|
|
|
//
|
|
// Store the message
|
|
//
|
|
Check(message);
|
|
messageToSend = (Receiver::Message*)new char[message->messageLength];
|
|
Register_Pointer(messageToSend);
|
|
Mem_Copy(
|
|
messageToSend,
|
|
message,
|
|
message->messageLength,
|
|
message->messageLength
|
|
);
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
MessageQueue__SendRequest::~MessageQueue__SendRequest()
|
|
{
|
|
Unregister_Pointer(messageToSend);
|
|
delete messageToSend;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
Logical
|
|
MessageQueue__SendRequest::TestInstance() const
|
|
{
|
|
Check_Pointer(messageToSend);
|
|
return True;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~ HostMessageBuffer__MessageQueue ~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
HostMessageBuffer__MessageQueue::HostMessageBuffer__MessageQueue(
|
|
HostID host_ID
|
|
):
|
|
sendRequestSocket(NULL)
|
|
{
|
|
hostID = host_ID;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
HostMessageBuffer__MessageQueue::~HostMessageBuffer__MessageQueue()
|
|
{
|
|
ChainIteratorOf<SendRequest*>
|
|
iterator(&sendRequestSocket);
|
|
iterator.DeletePlugs();
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
Logical
|
|
HostMessageBuffer__MessageQueue::TestInstance() const
|
|
{
|
|
Check(&sendRequestSocket);
|
|
return True;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
HostMessageBuffer__MessageQueue::AddSendRequest(
|
|
NetworkClient::ClientID client_ID,
|
|
Receiver::Message *message
|
|
)
|
|
{
|
|
Check(this);
|
|
Check(message);
|
|
|
|
//
|
|
// Create new send request and add it to the socket
|
|
//
|
|
SendRequest
|
|
*send_request;
|
|
|
|
send_request = new SendRequest(client_ID, message);
|
|
Register_Object(send_request);
|
|
sendRequestSocket.Add(send_request);
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~ L4NetworkManager__MessageBuffer ~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
L4NetworkManager__MessageBuffer::L4NetworkManager__MessageBuffer(
|
|
L4NetworkManager *network_manager
|
|
):
|
|
messageQueueSocket(NULL, True)
|
|
{
|
|
networkManager = network_manager;
|
|
currentQueueIndex = 0;
|
|
bufferSize = 0;
|
|
#ifdef LAB_ONLY
|
|
messageCount = 0;
|
|
maxBufferSize = 0;
|
|
#endif
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
L4NetworkManager__MessageBuffer::~L4NetworkManager__MessageBuffer()
|
|
{
|
|
TableIteratorOf<MessageQueue*, HostID>
|
|
iterator(&messageQueueSocket);
|
|
iterator.DeletePlugs();
|
|
|
|
#ifdef LAB_ONLY
|
|
cout << "L4NetworkManager__MessageBuffer::" <<
|
|
"~L4NetworkManager__MessageBuffer" <<
|
|
" messageCount=" << messageCount << "\n";;
|
|
cout << "L4NetworkManager__MessageBuffer::" <<
|
|
"~L4NetworkManager__MessageBuffer" <<
|
|
" maxBufferSize=" << maxBufferSize << "\n";;
|
|
#endif
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
Logical
|
|
L4NetworkManager__MessageBuffer::TestInstance() const
|
|
{
|
|
Check(&messageQueueSocket);
|
|
return True;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
L4NetworkManager__MessageBuffer::AddSendRequest(
|
|
HostID host_ID,
|
|
NetworkClient::ClientID client_ID,
|
|
Receiver::Message *message
|
|
)
|
|
{
|
|
Check(this);
|
|
Check(message);
|
|
|
|
//
|
|
// Get this hosts message queue
|
|
//
|
|
MessageQueue
|
|
*message_queue;
|
|
|
|
if ((message_queue = messageQueueSocket.Find(host_ID)) == NULL)
|
|
{
|
|
message_queue = new MessageQueue(host_ID);
|
|
Register_Object(message_queue);
|
|
messageQueueSocket.AddValue(message_queue, host_ID);
|
|
}
|
|
Check(message_queue);
|
|
|
|
//
|
|
// Add the send request
|
|
//
|
|
message_queue->AddSendRequest(client_ID, message);
|
|
bufferSize++;
|
|
#ifdef LAB_ONLY
|
|
messageCount++;
|
|
maxBufferSize = Max(maxBufferSize, bufferSize);
|
|
#endif
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void
|
|
L4NetworkManager__MessageBuffer::AttemptToSend()
|
|
{
|
|
Check(this);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Verify that there exist non-empty queues and that the buffer size is
|
|
// correct
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
#if DEBUG_LEVEL>0
|
|
{
|
|
TableIteratorOf<MessageQueue*, HostID>
|
|
iterator(&messageQueueSocket);
|
|
MessageQueue
|
|
*message_queue;
|
|
CollectionSize
|
|
counter = 0;
|
|
|
|
while ((message_queue = iterator.ReadAndNext()) != NULL)
|
|
{
|
|
Check(message_queue);
|
|
ChainIteratorOf<MessageQueue::SendRequest*>
|
|
message_iterator(&message_queue->sendRequestSocket);
|
|
counter += message_iterator.GetSize();
|
|
}
|
|
Verify(counter > 0);
|
|
Verify(counter == bufferSize);
|
|
}
|
|
#endif
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Increment the index to the next non-empty queue
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
TableIteratorOf<MessageQueue*, HostID>
|
|
queue_iterator(&messageQueueSocket);
|
|
MessageQueue
|
|
*message_queue;
|
|
MessageQueue::SendRequest
|
|
*send_request;
|
|
|
|
do
|
|
{
|
|
//
|
|
// Increment the counter
|
|
//
|
|
if (++currentQueueIndex >= queue_iterator.GetSize())
|
|
currentQueueIndex = 0;
|
|
|
|
//
|
|
// Get the message queue
|
|
//
|
|
message_queue = queue_iterator.GetNth(currentQueueIndex);
|
|
Check(message_queue);
|
|
|
|
//
|
|
// Get the message
|
|
//
|
|
ChainIteratorOf<MessageQueue::SendRequest*>
|
|
message_iterator(&message_queue->sendRequestSocket);
|
|
send_request = message_iterator.GetCurrent();
|
|
}
|
|
while (send_request == NULL);
|
|
Check(send_request);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
// Attempt to send
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
Check(networkManager);
|
|
if (
|
|
networkManager->SendMessageToNetnub(
|
|
send_request->messageToSend,
|
|
send_request->clientID,
|
|
message_queue->hostID
|
|
)
|
|
)
|
|
{
|
|
Unregister_Object(send_request);
|
|
delete send_request;
|
|
bufferSize--;
|
|
}
|
|
}
|