From 20f9856b4794666ebc1a8eeac78fecfed63c7338 Mon Sep 17 00:00:00 2001 From: p2r3 Date: Mon, 18 Aug 2025 03:01:41 +0300 Subject: [PATCH] support multiple simultaneous connections --- src/main.c | 159 +++++++++++++++++++++++++++++++++----------------- src/packets.c | 20 +++++-- src/packets.h | 1 + src/tools.c | 63 +++++++++++++++++--- src/tools.h | 2 + 5 files changed, 179 insertions(+), 66 deletions(-) diff --git a/src/main.c b/src/main.c index dd8676a..729e0ec 100644 --- a/src/main.c +++ b/src/main.c @@ -2,6 +2,8 @@ #include #include #include +#include +#include #ifndef CLOCK_REALTIME #define CLOCK_REALTIME 0 @@ -266,16 +268,30 @@ void handlePacket (int client_fd, int length, int packet_id) { } +void disconnectClient (int *client_fd, int cause) { + setClientState(*client_fd, STATE_NONE); + clearPlayerFD(*client_fd); + close(*client_fd); + *client_fd = -1; + printf("Disconnected client %d, cause: %d, errno: %d\n\n", *client_fd, cause, errno); +} + int main () { for (int i = 0; i < sizeof(block_changes) / sizeof(BlockChange); i ++) { block_changes[i].block = 0xFF; } - int server_fd, client_fd, opt = 1; + int server_fd, opt = 1; struct sockaddr_in server_addr, client_addr; socklen_t addr_len = sizeof(client_addr); + int clients[MAX_PLAYERS], client_index = 0; + for (int i = 0; i < MAX_PLAYERS; i ++) { + clients[i] = -1; + client_states[i * 2] = -1; + } + // Create socket server_fd = socket(AF_INET, SOCK_STREAM, 0); if (server_fd == -1) { @@ -305,62 +321,99 @@ int main () { close(server_fd); exit(EXIT_FAILURE); } - printf("Server listening on port %d...\n", PORT); + // Set non-blocking socket flag + int flags = fcntl(server_fd, F_GETFL, 0); + fcntl(server_fd, F_SETFL, flags | O_NONBLOCK); + + // Track client keep-alives + struct timespec time_now; + struct timespec keepalive_last; + clock_gettime(CLOCK_REALTIME, &time_now); + clock_gettime(CLOCK_REALTIME, &keepalive_last); + + /** + * Cycles through all connected clients, handling one packet at a time + * from each player. With every iteration, attempts to accept a new + * client connection. + */ while (true) { - - // Accept a connection - client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &addr_len); - if (client_fd < 0) { - perror("accept failed"); - close(server_fd); - exit(EXIT_FAILURE); - } - - printf("Client connected.\n"); - - struct timespec time_now; - struct timespec keepalive_last; - clock_gettime(CLOCK_REALTIME, &time_now); - clock_gettime(CLOCK_REALTIME, &keepalive_last); - - while (true) { - - if (getClientState(client_fd) == STATE_PLAY) { - clock_gettime(CLOCK_REALTIME, &time_now); - if (time_now.tv_sec - keepalive_last.tv_sec > 10) { - sc_keepAlive(client_fd); - sc_updateTime(client_fd, world_time += 200); - clock_gettime(CLOCK_REALTIME, &keepalive_last); - /** - * If the RNG seed ever hits 0, it'll never generate anything - * else. This is because the fast_rand function uses a simple - * XORshift. This isn't a common concern, so we only check for - * this periodically. If it does become zero, we reset it to - * the world seed as a good-enough fallback. - */ - if (rng_seed == 0) rng_seed = world_seed; - } - } - - int length = readVarInt(client_fd); - if (length == VARNUM_ERROR) break; - int packet_id = readVarInt(client_fd); - if (packet_id == VARNUM_ERROR) break; - handlePacket(client_fd, length - sizeVarInt(packet_id), packet_id); - if (recv_count == -1) break; - - wdt_reset(); - } - - setClientState(client_fd, STATE_NONE); - clearPlayerFD(client_fd); - - close(client_fd); - printf("Connection closed.\n"); - wdt_reset(); + + // Attempt to accept a new connection + for (int i = 0; i < MAX_PLAYERS; i ++) { + if (clients[i] != -1) continue; + clients[i] = accept(server_fd, (struct sockaddr *)&client_addr, &addr_len); + // If the accept was successful, make the client non-blocking too + if (clients[i] != -1) { + printf("New client, fd: %d\n", clients[i]); + int flags = fcntl(clients[i], F_GETFL, 0); + fcntl(clients[i], F_SETFL, flags | O_NONBLOCK); + } + break; + } + + // Look for valid connected clients + client_index ++; + if (client_index == MAX_PLAYERS) client_index = 0; + if (clients[client_index] == -1) continue; + + // Handle infrequent periodic events every 10 seconds + clock_gettime(CLOCK_REALTIME, &time_now); + time_t seconds_since_update = time_now.tv_sec - keepalive_last.tv_sec; + if (seconds_since_update > 10) { + // Send Keep Alive and Update Time packets to all in-game clients + world_time += 20 * seconds_since_update; + for (int i = 0; i < MAX_PLAYERS; i ++) { + if (clients[i] == -1) continue; + if (getClientState(clients[i]) != STATE_PLAY) continue; + sc_keepAlive(clients[i]); + sc_updateTime(clients[i], world_time); + } + // Reset keep-alive timer + clock_gettime(CLOCK_REALTIME, &keepalive_last); + /** + * If the RNG seed ever hits 0, it'll never generate anything + * else. This is because the fast_rand function uses a simple + * XORshift. This isn't a common concern, so we only check for + * this periodically. If it does become zero, we reset it to + * the world seed as a good-enough fallback. + */ + if (rng_seed == 0) rng_seed = world_seed; + } + + // Handle this individual client + int client_fd = clients[client_index]; + + // Check if at least 2 bytes are available for reading + ssize_t recv_count = recv(client_fd, &recv_buffer, 2, MSG_PEEK); + if (recv_count < 2) { + if (recv_count == 0 || (recv_count < 0 && errno != EAGAIN && errno != EWOULDBLOCK)) { + disconnectClient(&clients[client_index], 1); + } + continue; + } + + // Read packet length + int length = readVarInt(client_fd); + if (length == VARNUM_ERROR) { + disconnectClient(&clients[client_index], 2); + continue; + } + // Read packet ID + int packet_id = readVarInt(client_fd); + if (packet_id == VARNUM_ERROR) { + disconnectClient(&clients[client_index], 3); + continue; + } + // Handle packet data + handlePacket(client_fd, length - sizeVarInt(packet_id), packet_id); + if (recv_count == 0 || (recv_count == -1 && errno != EAGAIN && errno != EWOULDBLOCK)) { + disconnectClient(&clients[client_index], 4); + continue; + } + } close(server_fd); diff --git a/src/packets.c b/src/packets.c index dd077de..5628025 100644 --- a/src/packets.c +++ b/src/packets.c @@ -43,7 +43,7 @@ int cs_loginStart (int client_fd) { readString(client_fd); if (recv_count == -1) return 1; printf(" Player name: %s\n", recv_buffer); - recv_count = recv(client_fd, recv_buffer + 17, 16, MSG_WAITALL); + recv_count = recv_all(client_fd, recv_buffer + 17, 16, false); if (recv_count == -1) return 1; printf(" Player UUID: "); for (int i = 17; i < 33; i ++) printf("%x", recv_buffer[i]); @@ -413,6 +413,14 @@ int sc_blockUpdate (int client_fd, int64_t x, int64_t y, int64_t z, uint8_t bloc writeVarInt(client_fd, block_palette[block]); } +// S->C Acknowledge Block Change +int sc_acknowledgeBlockChange (int client_fd, int sequence) { + writeVarInt(client_fd, 1 + sizeVarInt(sequence)); + writeByte(client_fd, 0x04); + writeVarInt(client_fd, sequence); + return 0; +} + // C->S Player Action int cs_playerAction (int client_fd) { @@ -426,6 +434,7 @@ int cs_playerAction (int client_fd) { readByte(client_fd); // ignore face int sequence = readVarInt(client_fd); + sc_acknowledgeBlockChange(client_fd, sequence); if ((action == 0 && GAMEMODE == 1)) { // block was mined in creative @@ -488,6 +497,7 @@ int cs_useItemOn (int client_fd) { readByte(client_fd); int sequence = readVarInt(client_fd); + sc_acknowledgeBlockChange(client_fd, sequence); uint8_t target = getBlockAt(x, y, z); if (target == B_crafting_table) { @@ -572,9 +582,9 @@ int cs_clickContainer (int client_fd) { // ignore components tmp = readVarInt(client_fd); - recv(client_fd, recv_buffer, tmp, MSG_WAITALL); + recv_all(client_fd, recv_buffer, tmp, false); tmp = readVarInt(client_fd); - recv(client_fd, recv_buffer, tmp, MSG_WAITALL); + recv_all(client_fd, recv_buffer, tmp, false); if (count > 0) { player->inventory_items[slot] = item; @@ -599,9 +609,9 @@ int cs_clickContainer (int client_fd) { readVarInt(client_fd); readVarInt(client_fd); tmp = readVarInt(client_fd); - recv(client_fd, recv_buffer, tmp, MSG_WAITALL); + recv_all(client_fd, recv_buffer, tmp, false); tmp = readVarInt(client_fd); - recv(client_fd, recv_buffer, tmp, MSG_WAITALL); + recv_all(client_fd, recv_buffer, tmp, false); } return 0; diff --git a/src/packets.h b/src/packets.h index ce4cd92..9f1dbc5 100644 --- a/src/packets.h +++ b/src/packets.h @@ -29,6 +29,7 @@ int sc_setContainerSlot (int client_fd, int window_id, uint16_t slot, uint8_t co int sc_setHeldItem (int client_fd, uint8_t slot); int sc_blockUpdate (int client_fd, int64_t x, int64_t y, int64_t z, uint8_t block); int sc_openScreen (int client_fd, uint8_t window, const char *title, uint16_t length); +int sc_acknowledgeBlockChange (int client_fd, int sequence); int sc_registries(int client_fd); #endif diff --git a/src/tools.c b/src/tools.c index 106f018..4ba1dc1 100644 --- a/src/tools.c +++ b/src/tools.c @@ -1,6 +1,7 @@ #include #include #include +#include #ifdef ESP_PLATFORM #include "lwip/sockets.h" @@ -26,6 +27,42 @@ static uint64_t htonll (uint64_t value) { #endif } +ssize_t recv_all (int client_fd, void *buf, size_t n, uint8_t require_first) { + char *p = buf; + size_t total = 0; + + // First-byte check if requested + if (require_first) { + ssize_t r = recv(client_fd, p, 1, MSG_PEEK); + if (r <= 0) { + if (r < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) { + return 0; // no first byte available yet + } + return -1; // error or connection closed + } + } + + // Busy-wait until we get exactly n bytes + while (total < n) { + ssize_t r = recv(client_fd, p + total, n - total, 0); + if (r < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + // spin until data arrives + wdt_reset(); + continue; + } else { + return -1; // real error + } + } else if (r == 0) { + // connection closed before full read + return total; + } + total += r; + } + + return total; // got exactly n bytes +} + ssize_t writeByte (int client_fd, uint8_t byte) { return send(client_fd, &byte, 1, 0); } @@ -55,22 +92,22 @@ ssize_t writeDouble (int client_fd, double num) { } uint8_t readByte (int client_fd) { - recv_count = recv(client_fd, recv_buffer, 1, MSG_WAITALL); + recv_count = recv_all(client_fd, recv_buffer, 1, false); return recv_buffer[0]; } uint16_t readUint16 (int client_fd) { - recv_count = recv(client_fd, recv_buffer, 2, MSG_WAITALL); + recv_count = recv_all(client_fd, recv_buffer, 2, false); return ((uint16_t)recv_buffer[0] << 8) | recv_buffer[1]; } uint32_t readUint32 (int client_fd) { - recv_count = recv(client_fd, recv_buffer, 4, MSG_WAITALL); + recv_count = recv_all(client_fd, recv_buffer, 4, false); return ((uint32_t)recv_buffer[0] << 24) | ((uint32_t)recv_buffer[1] << 16) | ((uint32_t)recv_buffer[2] << 8) | ((uint32_t)recv_buffer[3]); } uint64_t readUint64 (int client_fd) { - recv_count = recv(client_fd, recv_buffer, 8, MSG_WAITALL); + recv_count = recv_all(client_fd, recv_buffer, 8, false); return ((uint64_t)recv_buffer[0] << 56) | ((uint64_t)recv_buffer[1] << 48) | ((uint64_t)recv_buffer[2] << 40) | @@ -81,7 +118,7 @@ uint64_t readUint64 (int client_fd) { ((uint64_t)recv_buffer[7]); } int64_t readInt64 (int client_fd) { - recv_count = recv(client_fd, recv_buffer, 8, MSG_WAITALL); + recv_count = recv_all(client_fd, recv_buffer, 8, false); return ((int64_t)recv_buffer[0] << 56) | ((int64_t)recv_buffer[1] << 48) | ((int64_t)recv_buffer[2] << 40) | @@ -106,7 +143,7 @@ double readDouble (int client_fd) { void readString (int client_fd) { uint32_t length = readVarInt(client_fd); - recv_count = recv(client_fd, recv_buffer, length, MSG_WAITALL); + recv_count = recv_all(client_fd, recv_buffer, length, false); recv_buffer[recv_count] = '\0'; } @@ -127,10 +164,18 @@ uint64_t splitmix64 (uint64_t state) { int client_states[MAX_PLAYERS * 2]; void setClientState (int client_fd, int new_state) { + // Look for a client state with a matching file descriptor for (int i = 0; i < MAX_PLAYERS * 2; i += 2) { - if (client_states[i] != client_fd && client_states[i] != 0) continue; + if (client_states[i] != client_fd) continue; + client_states[i + 1] = new_state; + return; + } + // If the above failed, look for an unused client state slot + for (int i = 0; i < MAX_PLAYERS * 2; i += 2) { + if (client_states[i] != -1) continue; client_states[i] = client_fd; client_states[i + 1] = new_state; + return; } } @@ -299,8 +344,10 @@ uint8_t getBlockChange (short x, short y, short z) { void makeBlockChange (short x, short y, short z, uint8_t block) { - // Transmit block update to all managed clients + // Transmit block update to all in-game clients for (int i = 0; i < MAX_PLAYERS; i ++) { + if (player_data[i].client_fd == -1) continue; + if (getClientState(player_data[i].client_fd) != STATE_PLAY) continue; sc_blockUpdate(player_data[i].client_fd, x, y, z, block); } diff --git a/src/tools.h b/src/tools.h index b30f070..3b3200c 100644 --- a/src/tools.h +++ b/src/tools.h @@ -6,6 +6,8 @@ #include "globals.h" +ssize_t recv_all (int client_fd, void *buf, size_t n, uint8_t require_first); + ssize_t writeByte (int client_fd, uint8_t byte); ssize_t writeUint16 (int client_fd, uint16_t num); ssize_t writeUint32 (int client_fd, uint32_t num);