//===========================================================================// // File: l4nettransport.h // // Project: MUNGA Brick: Network Transport Seam // // Contents: The wire interface under the network manager // //---------------------------------------------------------------------------// // Copyright (C) 1994-1995, Virtual World Entertainment, Inc. // // PROPRIETARY AND CONFIDENTIAL // //===========================================================================// #pragma once #include "..\munga\style.h" #include //######################################################################## // NetTransport - the seam between the network manager's mesh/console // logic and the wire (docs/RP412-FRONTEND-DESIGN.md section 3). Mirrors // the RIOBase pattern: L4NET keeps hosts, message queues, and the // deterministic mesh ordering; only connect/listen/send/recv live // behind this interface. // // Implementations: // WinsockNetTransport (l4nettransport.cpp) - TCP; the arcade/LAN wire // SteamNetTransport (future) - ISteamNetworkingSockets // P2P over SDR // // Addresses stay SOCKADDR_IN-shaped on purpose: Steam's FakeIP system // hands out fake IPv4 addresses for exactly this kind of engine, so // the [pilots] list keeps working as ip[:port] strings in both worlds. //######################################################################## class NetTransport { public: // Opaque connection handle. SOCKET for Winsock (SOCKET is UINT_PTR), // HSteamNetConnection for Steam - both fit; callers must not // interpret it. typedef UINT_PTR Connection; static const Connection InvalidConnection = (Connection) ~0; // == INVALID_SOCKET // Receive() results when no payload came back enum { ReceiveDisconnected = 0, // orderly close or connection reset ReceiveNoData = -1 // nothing pending (connections are nonblocking) }; virtual ~NetTransport() { } //--------------------------------------------------------------- // Lifecycle //--------------------------------------------------------------- virtual Logical Startup() = 0; virtual void Cleanup() = 0; //--------------------------------------------------------------- // Addressing: the local interface list (the mesh identifies // "which [pilots] entry is me" against it) and numeric ip[:port] // parsing. Port 0 in the result means "caller applies default". //--------------------------------------------------------------- virtual int GetLocalAddresses( unsigned long *addresses, int max_count ) = 0; virtual Logical Resolve( const char *host_name, SOCKADDR_IN *address ) = 0; //--------------------------------------------------------------- // Connections (the deterministic mesh + the console channel). // Connect blocks until the remote end accepts (the mesh relies // on retry-until-up ordering), then goes nonblocking. Listeners // are nonblocking from the start; Accept polls one. //--------------------------------------------------------------- virtual Connection Connect( const SOCKADDR_IN *remote, int local_port ) = 0; virtual Connection Listen( int local_port, int backlog ) = 0; virtual Connection Accept(Connection listener) = 0; virtual void Close(Connection connection) = 0; //--------------------------------------------------------------- // Data plane (nonblocking) //--------------------------------------------------------------- virtual int Send( Connection connection, const void *data, int size ) = 0; virtual int Receive( Connection connection, void *buffer, int size ) = 0; // Retry anything a connection could not take when Send was called. // The engine calls this at its flush points (before the render, at // the top of the receive pump, before a connect sequence); default // is a no-op so a transport without queues needs nothing. virtual void FlushSends() { } // remote endpoint of a live connection (mesh identity checks) virtual Logical GetRemoteAddress( Connection connection, SOCKADDR_IN *address ) = 0; }; // The process-wide transport. Defaults to Winsock TCP; a Steam build // installs its transport BEFORE the network manager comes up. NetTransport * NetTransport_Get(); void NetTransport_Set(NetTransport *transport); //######################################################################## // Outbound send queue (RP412NETSENDQ). // // Historically every send result was discarded. On the 1 ms arcade LAN // the socket buffer never filled, so nothing was ever lost - but over // the internet a peer stalled in its 10-30 s mission load stops reading, // its window closes, and our nonblocking send starts returning // would-block (message silently vanished) or a PARTIAL count. A partial // send is the worse of the two: framing on the stream is recovered // purely from each message's length prefix, so the bytes that never // followed shear the stream for good. // // The queue closes both holes: a connection that cannot take the bytes // now keeps them, byte-exact, and FlushSends() retries at the engine's // flush points. Nothing is ever dropped from the middle of the stream - // these are reliable, ordered, non-idempotent messages, so the only // legal overflow response is to declare the connection dead and let the // normal disconnect path run (Receive() reports it). // // RP412NETSENDQ=0 restores the old direct sends but keeps counting and // logging the losses it would have prevented. //######################################################################## Logical NetTransport_SendQueueEnabled(); // RP412NETSENDQ, default on int NetTransport_SendQueueCapBytes(); // RP412NETQUEUEKB * 1024, default 256K Logical NetTransport_StatsEnabled(); // RP412NETSTATS, default off: 5 s // per-connection rollups (and, on // Steam, a 1 Hz ping/quality poll) // printed from FlushSends class NetSendQueue { public: NetTransport::Connection connection; // InvalidConnection marks a free slot unsigned char *buffer; // allocated on first append int head, // first unsent byte tail; // one past the last queued byte Logical dead; // overflow or hard error; Receive() turns this // into ReceiveDisconnected // telemetry, printed under RP412NETSTATS unsigned long sentMessages, // engine Send() calls accepted sentBytes, wireWrites, // actual writes on the wire partialWrites, // wire writes that took only part of the bytes wouldBlocks, // wire writes refused outright queuedHighWater; // worst PendingBytes() seen int PendingBytes() const { return tail - head; } }; class NetSendQueueTable { public: enum { MaxQueues = 24 }; // 8 pods' mesh + console + listeners + slack NetSendQueueTable(); ~NetSendQueueTable(); NetSendQueue * Find(NetTransport::Connection connection); NetSendQueue * FindOrAdopt(NetTransport::Connection connection); void Release(NetTransport::Connection connection); void ReleaseAll(); // Append bytes to a queue. False = overflow: the pending bytes are // freed and the queue is marked dead (see the block comment above). Logical Append( NetSendQueue *queue, const void *data, int size); NetSendQueue queues[MaxQueues]; }; //######################################################################## // Waiting for a peer without hanging the window. // // Connect retries until the peer answers, because the egg-ACK ordering // means it may still be booting - that part is deliberate. What was not // deliberate is that the wait slept without pumping messages, so Windows // saw a process that had stopped answering and painted the whole thing // "Not responding" for up to two minutes. It happens before the engine // block, so there is no render loop keeping the window alive either. // // Sleep through this instead. It pumps, so the window keeps drawing and // can be moved; it watches for a cancel; and it is re-entrancy guarded, // because dispatching a message can run application code that reaches a // connect of its own. //######################################################################## enum NetWaitResult { NetWaitContinue, // carry on waiting NetWaitCancelled, // the user pressed escape NetWaitQuit // WM_QUIT arrived; the caller must unwind }; NetWaitResult NetTransport_PumpAndSleep(int milliseconds); // How long Connect keeps redialling, in seconds. RP412CONNECTWAIT // overrides; see the definition for why the default came down from 120. int NetTransport_ConnectWaitSeconds(); // Progress, shown in the window title while a connect is outstanding. // Pass NULL to put the original title back. void NetTransport_SetWaitProgress(const char *text); // Forget a previous escape, so one race's cancel cannot cancel the next. // Call before starting a connect sequence. void NetTransport_ClearWaitCancel();