Redesign socket reading to not depend on checking for data available (ioctl(FIONREAD)) or select/poll readability anymore

This commit should not make the client socket any more quick to disconnect than it did before - I tested unplugging and reconnecting an ethernet cable, and the game survived the brief dropout and remained connected to the server
This commit is contained in:
UnknownShadow200 2022-12-31 23:26:55 +11:00
parent 30406de3eb
commit 35583b4187
7 changed files with 65 additions and 126 deletions

View file

@ -233,19 +233,18 @@ CC_API void Waitable_WaitFor(void* handle, cc_uint32 milliseconds);
/* Calls SysFonts_Register on each font that is available on this platform. */
void Platform_LoadSysFonts(void);
/* Checks if data is available to be read from the given socket */
cc_result Socket_CheckAvailable(cc_socket s, int* available);
/* Checks if the given socket is readable (i.e. has data available to read) */
/* Checks if the given socket is currently readable (i.e. has data available to read) */
/* NOTE: A closed socket is also considered readable */
cc_result Socket_CheckReadable(cc_socket s, cc_bool* readable);
/* Checks if the given socket is writable (i.e. has finished connecting) */
/* Checks if the given socket is currently writable (i.e. has finished connecting) */
cc_result Socket_CheckWritable(cc_socket s, cc_bool* writable);
/* Returns non-zero if the given address is valid for a socket to connect to */
int Socket_ValidAddress(const cc_string* address);
/* Allocates a new non-blocking socket and then begins connecting to the given address:port. */
cc_result Socket_Connect(cc_socket* s, const cc_string* address, int port);
/* Attempts to read data from the given socket. */
/* NOTE: A closed socket may set modified to 0, but still return 'success' (i.e. 0) */
cc_result Socket_Read(cc_socket s, cc_uint8* data, cc_uint32 count, cc_uint32* modified);
/* Attempts to write data to the given socket. */
cc_result Socket_Write(cc_socket s, const cc_uint8* data, cc_uint32 count, cc_uint32* modified);

View file

@ -602,10 +602,6 @@ static cc_result Socket_Poll(cc_socket s, int mode, cc_bool* success) {
}
#endif
cc_result Socket_CheckAvailable(cc_socket s, int* available) {
return ioctl(s, FIONREAD, available);
}
cc_result Socket_CheckReadable(cc_socket s, cc_bool* readable) {
return Socket_Poll(s, SOCKET_POLL_READ, readable);
}

View file

@ -253,7 +253,6 @@ void Platform_LoadSysFonts(void) { }
*#########################################################################################################################*/
extern void interop_InitSockets(void);
int Socket_ValidAddress(const cc_string* address) { return true; }
extern int interop_SocketGetPending(int sock);
extern int interop_SocketCreate(void);
extern int interop_SocketConnect(int sock, const char* addr, int port);
@ -272,20 +271,22 @@ cc_result Socket_Connect(cc_socket* s, const cc_string* address, int port) {
}
extern int interop_SocketRecv(int sock, void* data, int len);
cc_result Socket_Read(cc_socket s, cc_uint8* data, cc_uint32 count, cc_uint32* modified) {
/* recv only reads one WebSocket frame at most, hence call it multiple times */
int res; *modified = 0;
cc_result Socket_Read(cc_socket s, cc_uint8* data, cc_uint32 count, cc_uint32* read) {
int res;
*read = 0;
while (count && interop_SocketGetPending(s) > 0) {
/* interop_SocketRecv only reads one WebSocket frame at most, hence call it multiple times */
while (count) {
/* returned result is negative for error */
res = interop_SocketRecv(s, data, count);
if (res >= 0) {
*modified += res;
data += res; count -= res;
*read += res;
data += res; count -= res;
} else {
/* EAGAIN when no data available */
if (res == -_EAGAIN) break;
/* EAGAIN when no more data available */
if (res == -_EAGAIN) return *read == 0 ? _EAGAIN : 0;
return -res;
}
}
@ -309,23 +310,6 @@ void Socket_Close(cc_socket s) {
interop_SocketClose(s);
}
cc_result Socket_CheckAvailable(cc_socket s, int* available) {
int res = interop_SocketGetPending(s);
/* returned result is negative for error */
if (res >= 0) {
*available = res; return 0;
} else {
*available = 0; return -res;
}
}
extern int interop_SocketReadable(int sock, cc_bool* readable);
cc_result Socket_CheckReadable(cc_socket s, int mode, cc_bool* readable) {
/* returned result is negative for error */
return -interop_SocketReadable(s, readable);
}
extern int interop_SocketWritable(int sock, cc_bool* writable);
cc_result Socket_CheckWritable(cc_socket s, cc_bool* writable) {
/* returned result is negative for error */

View file

@ -552,10 +552,6 @@ static cc_result Socket_Poll(cc_socket s, int mode, cc_bool* success) {
*success = set.fd_count != 0; return 0;
}
cc_result Socket_CheckAvailable(cc_socket s, int* available) {
return _ioctlsocket(s, FIONREAD, available);
}
cc_result Socket_CheckReadable(cc_socket s, cc_bool* readable) {
return Socket_Poll(s, SOCKET_POLL_READ, readable);
}

View file

@ -76,9 +76,9 @@ static int RunProgram(int argc, char** argv) {
int argsCount = Platform_GetCommandLineArgs(argc, argv, args);
#ifdef _MSC_VER
/* NOTE: Make sure to comment this out before pushing a commit */
cc_string rawArgs = String_FromConst("UnknownShadow200 fffff 127.0.0.1 25565");
//cc_string rawArgs = String_FromConst("UnknownShadow200 fffff 127.0.0.1 25565");
//cc_string rawArgs = String_FromConst("UnknownShadow200");
argsCount = String_UNSAFE_Split(&rawArgs, ' ', args, 4);
//argsCount = String_UNSAFE_Split(&rawArgs, ' ', args, 4);
#endif
if (argsCount == 0) {

View file

@ -326,19 +326,13 @@ static void MPConnection_Disconnect(void) {
Game_Disconnect(&title, &reason);
}
static void MPConnection_CheckDisconnection(void) {
cc_result readableRes;
cc_bool readable;
static void DisconnectReadFailed(cc_result res) {
cc_string msg; char msgBuffer[STRING_SIZE * 2];
String_InitArray(msg, msgBuffer);
String_Format3(&msg, "Error reading from %s:%i: %i" _NL, &Server.Address, &Server.Port, &res);
/* poll read returns true when either: */
/* a) data is available to read */
/* b) socket is closed */
/* since a) is already handled in MPConnection_Tick, this only handles b) */
readableRes = Socket_CheckReadable(net_socket, &readable);
if (net_writeFailure || readableRes || readable) {
MPConnection_Disconnect();
}
Logger_Log(&msg);
MPConnection_Disconnect();
}
static void DisconnectInvalidOpcode(cc_uint8 opcode) {
@ -351,81 +345,67 @@ static void DisconnectInvalidOpcode(cc_uint8 opcode) {
}
static void MPConnection_Tick(struct ScheduledTask* task) {
static const cc_string title_lost = String_FromConst("&eLost connection to the server");
static const cc_string reason_err = String_FromConst("I/O error when reading packets");
cc_string msg; char msgBuffer[STRING_SIZE * 2];
cc_uint32 pending;
cc_uint8* readEnd;
Net_Handler handler;
cc_uint32 read;
int i, remaining;
cc_result res;
if (Server.Disconnected) return;
if (net_connecting) { MPConnection_TickConnect(); return; }
/* Over 30 seconds since last packet, connection probably dropped */
if (net_lastPacket + 30 < Game.Time) MPConnection_CheckDisconnection();
if (Server.Disconnected) return;
pending = 0;
res = Socket_CheckAvailable(net_socket, &pending);
readEnd = net_readCurrent; /* todo change to int remaining instead */
if (!res && pending) {
/* NOTE: Always using a read call that is a multiple of 4096 (appears to?) improve read performance */
res = Socket_Read(net_socket, net_readCurrent, 4096 * 4, &pending);
/* Ignore errors for 'no data available for non-blocking read' */
if (res) {
if (res == ReturnCode_SocketInProgess) return;
if (res == ReturnCode_SocketWouldBlock) return;
}
readEnd += pending;
net_lastPacket = Game.Time;
}
/* NOTE: using a read call that is a multiple of 4096 (appears to?) improve read performance */
res = Socket_Read(net_socket, net_readCurrent, 4096 * 4, &read);
if (res) {
String_InitArray(msg, msgBuffer);
String_Format3(&msg, "Error reading from %s:%i: %i" _NL, &Server.Address, &Server.Port, &res);
/* 'no data available for non-blocking read' is an expected error */
if (res == ReturnCode_SocketInProgess) res = 0;
if (res == ReturnCode_SocketWouldBlock) res = 0;
Logger_Log(&msg);
Game_Disconnect(&title_lost, &reason_err);
return;
}
if (res) { DisconnectReadFailed(res); return; }
} else if (read == 0) {
/* recv only returns 0 read when socket is closed.. probably? */
/* Over 30 seconds since last packet, connection probably dropped */
/* TODO: Should this be checked unconditonally instead of just when read = 0 ? */
if (net_lastPacket + 30 < Game.Time) { MPConnection_Disconnect(); return; }
} else {
readEnd = net_readCurrent + read;
net_lastPacket = Game.Time;
net_readCurrent = net_readBuffer;
net_readCurrent = net_readBuffer;
while (net_readCurrent < readEnd) {
cc_uint8 opcode = net_readCurrent[0];
while (net_readCurrent < readEnd) {
cc_uint8 opcode = net_readCurrent[0];
/* Workaround for older D3 servers which wrote one byte too many for HackControl packets */
if (cpe_needD3Fix && lastOpcode == OPCODE_HACK_CONTROL && (opcode == 0x00 || opcode == 0xFF)) {
Platform_LogConst("Skipping invalid HackControl byte from D3 server");
net_readCurrent++;
LocalPlayer_ResetJumpVelocity();
continue;
/* Workaround for older D3 servers which wrote one byte too many for HackControl packets */
if (cpe_needD3Fix && lastOpcode == OPCODE_HACK_CONTROL && (opcode == 0x00 || opcode == 0xFF)) {
Platform_LogConst("Skipping invalid HackControl byte from D3 server");
net_readCurrent++;
LocalPlayer_ResetJumpVelocity();
continue;
}
if (net_readCurrent + Protocol.Sizes[opcode] > readEnd) break;
handler = Protocol.Handlers[opcode];
if (!handler) { DisconnectInvalidOpcode(opcode); return; }
lastOpcode = opcode;
handler(net_readCurrent + 1); /* skip opcode */
net_readCurrent += Protocol.Sizes[opcode];
}
if (net_readCurrent + Protocol.Sizes[opcode] > readEnd) break;
handler = Protocol.Handlers[opcode];
if (!handler) { DisconnectInvalidOpcode(opcode); return; }
lastOpcode = opcode;
handler(net_readCurrent + 1); /* skip opcode */
net_readCurrent += Protocol.Sizes[opcode];
/* Protocol packets might be split up across TCP packets */
/* If so, copy last few unprocessed bytes back to beginning of buffer */
/* These bytes are then later combined with subsequently read TCP packet data */
remaining = (int)(readEnd - net_readCurrent);
for (i = 0; i < remaining; i++) {
net_readBuffer[i] = net_readCurrent[i];
}
net_readCurrent = net_readBuffer + remaining;
}
/* Protocol packets might be split up across TCP packets */
/* If so, copy last few unprocessed bytes back to beginning of buffer */
/* These bytes are then later combined with subsequently read TCP packet data */
remaining = (int)(readEnd - net_readCurrent);
for (i = 0; i < remaining; i++) {
net_readBuffer[i] = net_readCurrent[i];
}
net_readCurrent = net_readBuffer + remaining;
if (net_writeFailure) {
Platform_Log1("Error from send: %i", &net_writeFailure);
MPConnection_Disconnect();
MPConnection_Disconnect(); return;
}
/* Network is ticked 60 times a second. We only send position updates 20 times a second */

View file

@ -667,22 +667,6 @@ mergeInto(LibraryManager.library, {
HEAPU8.set(msg, dst);
return msg.byteLength;
},
interop_SocketGetPending: function(sockFD) {
var sock = SOCKETS.sockets[sockFD];
if (!sock) return SOCKETS.EBADF;
return sock.recv_queue.length;
},
interop_SocketReadable: function(sockFD, readable) {
HEAPU8[readable|0] = 0;
var sock = SOCKETS.sockets[sockFD];
if (!sock) return SOCKETS.EBADF;
var ws = sock.socket;
if (!ws) return SOCKETS.ENOTCONN;
if (sock.recv_queue.length || (ws.readyState === ws.CLOSING || ws.readyState === ws.CLOSED)) HEAPU8[readable|0] = 1
return 0;
},
interop_SocketWritable: function(sockFD, writable) {
HEAPU8[writable|0] = 0;
var sock = SOCKETS.sockets[sockFD];