NetTransport seam: the wire moves behind an interface
The Steam-multiplayer prerequisite from the design doc: L4NET keeps hosts, message queues, and the deterministic mesh ordering, while connect/listen/accept/close, send/receive, startup/cleanup, the local interface list, and ip[:port] parsing move behind NetTransport (L4NETTRANSPORT.h). WinsockNetTransport carries the existing TCP behavior over verbatim - including the connect retry-while-refused loop the egg-ACK ordering relies on - and is the process default; NetTransport_Set installs a replacement before the network manager comes up. L4STEAMTRANSPORT.h documents the ISteamNetworkingSockets mapping per method (FakeIP keeps [pilots] entries as IPv4 strings) behind RP412_STEAM until the Steamworks SDK lands. Also fixed in passing: the ExclusiveBroadcast path sent sizeof(network_packet) - four bytes of pointer - instead of the message size, which would have sheared the stream framing had it ever fired. Verified: single-player race cycle (menu, race, results, menu, race) green; -net 8000 boots, listens for a console through the transport, and idles stable. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,325 @@
|
||||
#include "mungal4.h"
|
||||
#pragma hdrstop
|
||||
|
||||
#include "l4nettransport.h"
|
||||
#include <Ws2tcpip.h>
|
||||
|
||||
//########################################################################
|
||||
// WinsockNetTransport - the TCP wire the arcade always used, moved
|
||||
// verbatim out of L4NET.CPP behind the NetTransport seam. Behavior
|
||||
// notes preserved from the original:
|
||||
// - Connect retries while the remote refuses (WSAECONNREFUSED): the
|
||||
// console feeds eggs in [pilots] order with ACKs, so earlier pods
|
||||
// are listening before later pods open - the retry covers the race.
|
||||
// - Sockets go nonblocking once connected; listeners are nonblocking
|
||||
// from creation.
|
||||
//########################################################################
|
||||
|
||||
namespace
|
||||
{
|
||||
class WinsockNetTransport:
|
||||
public NetTransport
|
||||
{
|
||||
public:
|
||||
WinsockNetTransport():
|
||||
started(False)
|
||||
{
|
||||
}
|
||||
|
||||
Logical
|
||||
Startup()
|
||||
{
|
||||
if (started)
|
||||
{
|
||||
return True;
|
||||
}
|
||||
int result = WSAStartup(MAKEWORD(2, 2), &winsockData);
|
||||
if (result != NO_ERROR)
|
||||
{
|
||||
DEBUG_STREAM << "ERROR: WSAStartup() failed with " << result
|
||||
<< "!\n" << std::flush;
|
||||
return False;
|
||||
}
|
||||
started = True;
|
||||
return True;
|
||||
}
|
||||
|
||||
void
|
||||
Cleanup()
|
||||
{
|
||||
if (started)
|
||||
{
|
||||
WSACleanup();
|
||||
started = False;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
GetLocalAddresses(
|
||||
unsigned long *addresses,
|
||||
int max_count
|
||||
)
|
||||
{
|
||||
char name[255];
|
||||
PHOSTENT hostinfo;
|
||||
|
||||
if (gethostname(name, sizeof(name)) != 0)
|
||||
{
|
||||
DEBUG_STREAM << "ERROR: gethostname() failed!" << std::endl << std::flush;
|
||||
return 0;
|
||||
}
|
||||
if ((hostinfo = gethostbyname(name)) == NULL)
|
||||
{
|
||||
DEBUG_STREAM << "ERROR: gethostbyname() failed!" << std::endl << std::flush;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
for (int i = 0; hostinfo->h_addr_list[i] != NULL && count < max_count; ++i)
|
||||
{
|
||||
addresses[count++] = *((unsigned long *) hostinfo->h_addr_list[i]);
|
||||
}
|
||||
|
||||
// loopback rounds out the list (single-machine testing)
|
||||
if (count < max_count)
|
||||
{
|
||||
addresses[count++] = 0x0100007F;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
Logical
|
||||
Resolve(
|
||||
const char *host_name,
|
||||
SOCKADDR_IN *address
|
||||
)
|
||||
{
|
||||
// numeric ip[:port] only - the egg carries addresses, not
|
||||
// names; port stays 0 when absent (caller applies default)
|
||||
int buffer_size = sizeof(SOCKADDR_IN);
|
||||
return WSAStringToAddressA(
|
||||
(LPSTR) host_name, AF_INET, NULL,
|
||||
(LPSOCKADDR) address, &buffer_size) == 0;
|
||||
}
|
||||
|
||||
Connection
|
||||
Connect(
|
||||
const SOCKADDR_IN *remote,
|
||||
int local_port
|
||||
)
|
||||
{
|
||||
DEBUG_STREAM << "Opening connection to "
|
||||
<< inet_ntoa(remote->sin_addr) << ":" << ntohs(remote->sin_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 InvalidConnection;
|
||||
}
|
||||
|
||||
bool reuse_address = true;
|
||||
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
|
||||
(char *) &reuse_address, sizeof(bool)))
|
||||
{
|
||||
DEBUG_STREAM << "ERROR: Could not set SO_REUSEADDR on socket; setsockopt() failed with "
|
||||
<< WSAGetLastError() << "!\n" << std::flush;
|
||||
closesocket(sock);
|
||||
return InvalidConnection;
|
||||
}
|
||||
|
||||
// bind the game port: the mesh identifies peers by
|
||||
// address AND port, so the source port must be ours
|
||||
sockaddr_in local_endpoint;
|
||||
memset(&local_endpoint, 0, sizeof(local_endpoint));
|
||||
local_endpoint.sin_family = AF_INET;
|
||||
local_endpoint.sin_port = htons((unsigned short) local_port);
|
||||
local_endpoint.sin_addr.S_un.S_addr = INADDR_ANY;
|
||||
if (bind(sock, (sockaddr *) &local_endpoint, sizeof(local_endpoint)))
|
||||
{
|
||||
DEBUG_STREAM << "ERROR: Could not bind local socket; bind() failed with "
|
||||
<< WSAGetLastError() << "!\n" << std::flush;
|
||||
closesocket(sock);
|
||||
return InvalidConnection;
|
||||
}
|
||||
|
||||
int wsa_error = 0, result;
|
||||
do
|
||||
{
|
||||
result = connect(sock, (sockaddr *) remote, sizeof(SOCKADDR_IN));
|
||||
if (result != 0)
|
||||
{
|
||||
wsa_error = WSAGetLastError();
|
||||
}
|
||||
} while (result != 0 && wsa_error == WSAECONNREFUSED);
|
||||
|
||||
if (result != 0)
|
||||
{
|
||||
DEBUG_STREAM << "ERROR: connect() failed with "
|
||||
<< wsa_error << "!\n" << std::flush;
|
||||
closesocket(sock);
|
||||
return InvalidConnection;
|
||||
}
|
||||
|
||||
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;
|
||||
closesocket(sock);
|
||||
return InvalidConnection;
|
||||
}
|
||||
return (Connection) sock;
|
||||
}
|
||||
|
||||
Connection
|
||||
Listen(
|
||||
int local_port,
|
||||
int backlog
|
||||
)
|
||||
{
|
||||
DEBUG_STREAM << "Starting to listen on port " << local_port
|
||||
<< "...\n" << std::flush;
|
||||
|
||||
SOCKET listener = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
if (listener == INVALID_SOCKET)
|
||||
{
|
||||
DEBUG_STREAM << "ERROR: Could not create listener socket; socket() failed with "
|
||||
<< WSAGetLastError() << "!\n" << std::flush;
|
||||
return InvalidConnection;
|
||||
}
|
||||
|
||||
bool reuse_address = true;
|
||||
if (setsockopt(listener, SOL_SOCKET, SO_REUSEADDR,
|
||||
(char *) &reuse_address, sizeof(bool)))
|
||||
{
|
||||
DEBUG_STREAM << "ERROR: Could not set SO_REUSEADDR on listener socket; setsockopt() failed with "
|
||||
<< WSAGetLastError() << "!\n" << std::flush;
|
||||
closesocket(listener);
|
||||
return InvalidConnection;
|
||||
}
|
||||
|
||||
sockaddr_in local_endpoint;
|
||||
memset(&local_endpoint, 0, sizeof(local_endpoint));
|
||||
local_endpoint.sin_family = AF_INET;
|
||||
local_endpoint.sin_port = htons((unsigned short) local_port);
|
||||
local_endpoint.sin_addr.S_un.S_addr = INADDR_ANY;
|
||||
if (bind(listener, (sockaddr *) &local_endpoint, sizeof(local_endpoint)))
|
||||
{
|
||||
DEBUG_STREAM << "ERROR: Could not bind listener socket; bind() failed with "
|
||||
<< WSAGetLastError() << "!\n" << std::flush;
|
||||
closesocket(listener);
|
||||
return InvalidConnection;
|
||||
}
|
||||
if (listen(listener, backlog))
|
||||
{
|
||||
DEBUG_STREAM << "ERROR: Could not listen on listener socket; listen() failed with "
|
||||
<< WSAGetLastError() << "!\n" << std::flush;
|
||||
closesocket(listener);
|
||||
return InvalidConnection;
|
||||
}
|
||||
|
||||
unsigned long enable = 1;
|
||||
if (ioctlsocket(listener, FIONBIO, &enable))
|
||||
{
|
||||
DEBUG_STREAM << "ERROR: Could not set listener socket to nonblocking; ioctlsocket() failed with "
|
||||
<< WSAGetLastError() << "!\n" << std::flush;
|
||||
closesocket(listener);
|
||||
return InvalidConnection;
|
||||
}
|
||||
return (Connection) listener;
|
||||
}
|
||||
|
||||
Connection
|
||||
Accept(Connection listener)
|
||||
{
|
||||
SOCKET accepted = accept((SOCKET) listener, NULL, 0);
|
||||
if (accepted == INVALID_SOCKET)
|
||||
{
|
||||
return InvalidConnection;
|
||||
}
|
||||
return (Connection) accepted;
|
||||
}
|
||||
|
||||
void
|
||||
Close(Connection connection)
|
||||
{
|
||||
shutdown((SOCKET) connection, SD_BOTH);
|
||||
closesocket((SOCKET) connection);
|
||||
}
|
||||
|
||||
int
|
||||
Send(
|
||||
Connection connection,
|
||||
const void *data,
|
||||
int size
|
||||
)
|
||||
{
|
||||
return send((SOCKET) connection, (const char *) data, size, 0);
|
||||
}
|
||||
|
||||
int
|
||||
Receive(
|
||||
Connection connection,
|
||||
void *buffer,
|
||||
int size
|
||||
)
|
||||
{
|
||||
int received = recv((SOCKET) connection, (char *) buffer, size, 0);
|
||||
if (received > 0)
|
||||
{
|
||||
return received;
|
||||
}
|
||||
if (received == 0)
|
||||
{
|
||||
return ReceiveDisconnected;
|
||||
}
|
||||
|
||||
DWORD error = WSAGetLastError();
|
||||
switch (error)
|
||||
{
|
||||
case WSAECONNRESET:
|
||||
// hard drop reads the same as an orderly close upstairs
|
||||
return ReceiveDisconnected;
|
||||
case WSAEWOULDBLOCK:
|
||||
return ReceiveNoData;
|
||||
default:
|
||||
DEBUG_STREAM << "WinsockNetTransport::Receive: recv returned an unexpected error: WSAGetLastError = "
|
||||
<< error << std::endl << std::flush;
|
||||
return ReceiveNoData;
|
||||
}
|
||||
}
|
||||
|
||||
Logical
|
||||
GetRemoteAddress(
|
||||
Connection connection,
|
||||
SOCKADDR_IN *address
|
||||
)
|
||||
{
|
||||
int size = sizeof(SOCKADDR_IN);
|
||||
memset(address, 0, size);
|
||||
return getpeername((SOCKET) connection, (sockaddr *) address, &size) == 0;
|
||||
}
|
||||
|
||||
private:
|
||||
Logical started;
|
||||
WSADATA winsockData;
|
||||
};
|
||||
|
||||
WinsockNetTransport gWinsockTransport;
|
||||
NetTransport *gTransport = &gWinsockTransport;
|
||||
}
|
||||
|
||||
NetTransport *
|
||||
NetTransport_Get()
|
||||
{
|
||||
return gTransport;
|
||||
}
|
||||
|
||||
void
|
||||
NetTransport_Set(NetTransport *transport)
|
||||
{
|
||||
gTransport = (transport != NULL) ? transport : &gWinsockTransport;
|
||||
}
|
||||
Reference in New Issue
Block a user