mirror of
https://github.com/ClassiCube/ClassiCube.git
synced 2025-01-23 01:21:57 -05:00
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:
parent
30406de3eb
commit
35583b4187
7 changed files with 65 additions and 126 deletions
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
118
src/Server.c
118
src/Server.c
|
@ -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 */
|
||||
|
|
|
@ -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];
|
||||
|
|
Loading…
Reference in a new issue