diff --git a/src/base/all.h b/src/base/all.h index eb45fe8..d673d99 100644 --- a/src/base/all.h +++ b/src/base/all.h @@ -238,6 +238,10 @@ // only valid if there's an in-scope variable bool `debug_mode` #define dbg(fmt, ...) osDebugPrint(debug_mode, fmt, ##__VA_ARGS__) +///// SYSTEM INCLUDES I always want +#include +#include + ///// TYPES // integer types typedef unsigned char u8; diff --git a/src/base/linux_os.c b/src/base/linux_os.c index b3f6f4b..ed19cba 100644 --- a/src/base/linux_os.c +++ b/src/base/linux_os.c @@ -2,10 +2,8 @@ #define _POSIX_C_SOURCE 200809L #include #include -#include #include #include -#include #include "all.h" global pthread_barrier_t linux_thread_barrier; diff --git a/src/base/mac_os.c b/src/base/mac_os.c index ede0437..e36faf8 100644 --- a/src/base/mac_os.c +++ b/src/base/mac_os.c @@ -1,10 +1,8 @@ #include #include #include -#include #include #include -#include #include "all.h" #include "pthread_barrier.h" diff --git a/src/base/win32_os.c b/src/base/win32_os.c index c92441b..35b09ea 100644 --- a/src/base/win32_os.c +++ b/src/base/win32_os.c @@ -1,6 +1,5 @@ #include "string.h" #include -#include static u64 w32_ticks_per_sec = 1; static u32 w32_thread_context_index; diff --git a/src/client.c b/src/client.c index 16db241..73c48fd 100644 --- a/src/client.c +++ b/src/client.c @@ -13,9 +13,6 @@ #include "lib/network.c" #include "lib/tui.c" #include "string_chunk.c" -//#include "assets/asset1.h" -//#include "assets/asset2.h" -//#include "assets/asset3.h" ///// #define a bunch of client-only tunable game constants #define SYSTEM_MESSAGES_LEN 32 @@ -167,8 +164,7 @@ typedef struct GameState { MenuState menu; MenuState row; // for tracking which row of a table the user has selected MenuState modal_choice; - UDPMessage keep_alive_msg; - UDPClient client; + TCPClient client; StringChunkList message_input; StarSystem map[STAR_SYSTEM_COUNT]; Pos2 pos; @@ -277,23 +273,6 @@ fn void renderSystemMessages(Pixel* buf, Dim2 screen_dimensions, Box sys_msg_box } } -fn void renderStaticAssetToPixelBuffer(TuiState* tui, u8* asset, u32 len, u16 x, u16 y) { - u16 line = 0; - u16 x_in_line = 0; - u16 pos = x + (tui->screen_dimensions.width * y); - for (u32 i = 0; i < len; i++, x_in_line++) { - if (asset[i] == '\n') { - line += 1; - x_in_line = 0; - pos = x + (tui->screen_dimensions.width * (y+line)); - } else if (asset[i] == ' ') { - // do nothing, we skip spaces in our assets - } else { - tui->frame_buffer[pos+x_in_line].bytes[0] = asset[i]; - } - } -} - fn void resetTabRow(Tab tab) { if (state.menu.selected_index == TabMarket) { state.market_tab_state = MarketTabStateTable; @@ -339,10 +318,17 @@ fn void moveRowUpDown(bool user_pressed_up, bool user_pressed_down) { } } -fn void handleIncomingMessage(u8* message, u32 len, SocketAddress sender, i32 socket) { +fn void handleIncomingMessage(u8* message, i32 len) { + assert(len >= 0); // TODO actually handle this error u64 msg_pos = 0; Message msg_type = message[msg_pos++]; - dbg("handleIncomingMessage() of len=%d, message=%s\n", len, MESSAGE_STRINGS[msg_type]); + + char sbuf[SBUFLEN] = {0}; + sprintf(sbuf, "handleIncomingMessage() of len=%d, message=%s\n", len, MESSAGE_STRINGS[msg_type]); + if (msg_type != MessageSystemPassengers && msg_type != MessageSystemCommodities) { + addSystemMessage((u8*)sbuf); + } + u8List bytes = {len, len, message}; ParsedServerMessage parsed = {0}; parsed.type = msg_type; @@ -462,29 +448,24 @@ fn void handleIncomingMessage(u8* message, u32 len, SocketAddress sender, i32 so psmThreadSafeQueuePush(network_recv_queue, &parsed); } -fn void* receiveNetworkUpdates(void* udp) { - UDPClient client = *(UDPClient*)udp; +fn void* receiveNetworkUpdates(void* net_client) { + TCPClient client = *(TCPClient*)net_client; dbg("receiveNetworkUpdates() sock=%d\n", client.socket); - UDPServer server = { - .ready = true, - .server_address = client.server_address, - .server_socket = client.socket - }; - infiniteReadUDPServer(&server, handleIncomingMessage); + infiniteReadTCPClient(client.socket, &should_quit, handleIncomingMessage, addSystemMessage); return NULL; } -fn void* sendNetworkUpdates(void* udp) { - i32 socket_fd = ((UDPClient*)udp)->socket; - dbg("sendNetworkUpdates() sock=%d\n", socket_fd); - u8List bytes_list = {0}; +fn void* sendNetworkUpdates(void* net_client) { + TCPClient* client = (TCPClient*)net_client; + dbg("sendNetworkUpdates() sock=%d\n", client->socket); + NetworkMessage msg = { + .use_socket = true, + .socket_fd = client->socket, + .address = client->server_address, + }; while (!should_quit) { - UDPMessage msg = {0}; outgoingMessageQueuePop(network_send_queue, &msg); - bytes_list.items = msg.bytes; - bytes_list.length = msg.bytes_len; - bytes_list.capacity = msg.bytes_len; - sendUDPu8List(socket_fd, &msg.address, &bytes_list); + sendTCPMessage(msg); } return NULL; } @@ -528,15 +509,16 @@ fn Box defaultModal(TuiState* tui) { return modal_outline; } -fn void sendLoginCommand(SocketAddress addr, u16 client_port) { +fn void sendLoginCommand(TCPClient* net_client) { u32 msg_idx = 0; // drop the login message into the network_send_queue - UDPMessage msg = { .address = addr }; + NetworkMessage msg = { .use_socket = true, .socket_fd = net_client->socket, .address = net_client->server_address }; // 1. msg type/CommandType msg.bytes[msg_idx++] = CommandLogin; // 2. our LAN-IP to handle the case where we are on the same LAN as the guy we are trying to fight // and our "listened" UDP port - msg_idx += writeU16ToBufferLE(msg.bytes + msg_idx, ~client_port); + //msg_idx += writeU16ToBufferLE(msg.bytes + msg_idx, ~client_port); + msg_idx += writeU16ToBufferLE(msg.bytes + msg_idx, 9999); // we aren't actually doing this right now msg_idx += writeI32ToBufferLE(msg.bytes + msg_idx, ~osLanIPAddress()); // 2. how long is the name msg.bytes[msg_idx++] = state.login_state.name.length; @@ -551,7 +533,6 @@ fn void sendLoginCommand(SocketAddress addr, u16 client_port) { fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count) { GameState* state = (GameState*) s; state->loop_count = loop_count; - UDPClient* udp = &state->client; state->old_screen = state->screen; Dim2 screen_dimensions = tui->screen_dimensions; bool game_screen_changed = state->old_screen != state->screen; // detect new screens @@ -594,7 +575,7 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count // auto-re-login if (state->me.base_cost > 0) { addSystemMessage((u8*)"auto re logging in due to MessageNotAlive"); - sendLoginCommand(udp->server_address, udp->client_port); + sendLoginCommand(&state->client); state->screen = ScreenLogin; state->login_state.state = LoginScreenStateLoading; } @@ -859,9 +840,12 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count state->me.remaining_mortgage = template.base_cost - STARTING_DOWN_PAYMENT; state->me.interest_rate = calcInterestRate(template.base_cost, STARTING_DOWN_PAYMENT); // send character ship details to server - UDPMessage msg = {0}; - msg.address = udp->server_address; - msg.bytes_len = 2; + NetworkMessage msg = { + .use_socket = true, + .address = state->client.server_address, + .socket_fd = state->client.socket, + .bytes_len = 2, + }; // 1. msg type/CommandType msg.bytes[0] = CommandCreateCharacter; // 2. chosen ShipType @@ -1114,14 +1098,16 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count state->destination_sys_idx = i; // send the destination to the server u32 msg_idx = 0; - UDPMessage msg = {0}; - msg.address = udp->server_address; + NetworkMessage msg = { + .use_socket = true, + .address = state->client.server_address, + .socket_fd = state->client.socket, + }; // 1. msg type/CommandType msg.bytes[msg_idx++] = CommandSetDestination; // 2. buy? msg.bytes[msg_idx++] = state->destination_sys_idx; msg.bytes_len = msg_idx; - outgoingMessageQueuePush(network_send_queue, &msg); } } @@ -1186,14 +1172,16 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count if (user_pressed_enter && state->ship_tab_states == ShipTabStateMain) { // send the "ready" or "not ready" message to the server u32 msg_idx = 0; - UDPMessage msg = {0}; - msg.address = udp->server_address; + NetworkMessage msg = { + .use_socket = true, + .address = state->client.server_address, + .socket_fd = state->client.socket, + }; // 1. msg type/CommandType msg.bytes[msg_idx++] = CommandReadyStatus; // 2. buy? msg.bytes[msg_idx++] = !state->me.ready_to_depart; msg.bytes_len = msg_idx; - outgoingMessageQueuePush(network_send_queue, &msg); } } @@ -1276,14 +1264,16 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count if (user_pressed_enter) { // send the "payoff" message to server u32 msg_idx = 0; - UDPMessage msg = {0}; - msg.address = udp->server_address; + NetworkMessage msg = { + .use_socket = true, + .address = state->client.server_address, + .socket_fd = state->client.socket, + }; // 1. msg type/CommandType msg.bytes[msg_idx++] = CommandPayMortgage; // 2. buy? msg_idx += writeU64ToBufferLE(msg.bytes + msg_idx, input_quantity); msg.bytes_len = msg_idx; - outgoingMessageQueuePush(network_send_queue, &msg); state->ship_tab_states = ShipTabStateLoading; @@ -1344,8 +1334,11 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count if (user_pressed_enter) { // send the "buy this auction" message to the server u32 msg_idx = 0; - UDPMessage msg = {0}; - msg.address = udp->server_address; + NetworkMessage msg = { + .use_socket = true, + .address = state->client.server_address, + .socket_fd = state->client.socket, + }; msg.bytes[msg_idx++] = CommandBuyAuction; msg.bytes_len = msg_idx; outgoingMessageQueuePush(network_send_queue, &msg); @@ -1550,8 +1543,11 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count // send the "purchase" or "sell" message to server // get back new "status" of things u32 msg_idx = 0; - UDPMessage msg = {0}; - msg.address = udp->server_address; + NetworkMessage msg = { + .use_socket = true, + .address = state->client.server_address, + .socket_fd = state->client.socket, + }; // 1. msg type/CommandType msg.bytes[msg_idx++] = CommandTransact; // 2. buy? @@ -1561,7 +1557,6 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count // 4. commodity msg.bytes[msg_idx++] = selected_commodity.type; msg.bytes_len = msg_idx; - outgoingMessageQueuePush(network_send_queue, &msg); state->market_tab_state = MarketTabStateLoading; @@ -1701,8 +1696,11 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count // send the "job accept" message to server // get back new "status" of things u32 msg_idx = 0; - UDPMessage msg = {0}; - msg.address = udp->server_address; + NetworkMessage msg = { + .use_socket = true, + .address = state->client.server_address, + .socket_fd = state->client.socket, + }; msg.bytes[msg_idx++] = CommandAcceptPassengerJob; msg.bytes[msg_idx++] = current_row.goal_system_idx; msg.bytes[msg_idx++] = current_row.people; @@ -1787,7 +1785,7 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count state->login_state.selected_field = 1; state->login_state.field_index = 0; } else { - sendLoginCommand(udp->server_address, udp->client_port); + sendLoginCommand(&state->client); // show loading signal, stop accepting input state->login_state.state = LoginScreenStateLoading; addSystemMessage((u8*)state->login_state.name.bytes); @@ -1867,11 +1865,6 @@ fn bool updateAndRender(TuiState* tui, void* s, u8* input_buffer, u64 loop_count break; } - if (loop_count % 10 == 0) { - outgoingMessageQueuePush(network_send_queue, &state->keep_alive_msg); - //outgoingMessageQueuePush(network_send_queue, &testm); - } - return should_quit; } @@ -1913,23 +1906,22 @@ i32 main(i32 argc, ptr argv[]) { exit(1); } ptr server_address = NULL; + char input_string[16] = { 0 }; if (argc > 1) { server_address = argv[1]; } else { printf("Server IP Address: "); - char input_string[16] = { 0 }; scanf("%15s", input_string); printf("%s", input_string); if (input_string[0] && input_string[1] && input_string[2] && input_string[3]) { server_address = input_string; } } - state.client = createUDPClient(7777, server_address); - - // "hardcoded" keep alive message to periodically send to server - state.keep_alive_msg.address = state.client.server_address; - state.keep_alive_msg.bytes[0] = CommandKeepAlive; - state.keep_alive_msg.bytes_len = 1; + state.client = createTCPClient(SERVER_PORT, server_address); + if (!state.client.ready) { + printf("network could not connect() to server"); + exit(1); + } Thread recv_thread = spawnThread(&receiveNetworkUpdates, &state.client); Thread send_thread = spawnThread(&sendNetworkUpdates, &state.client); diff --git a/src/lib/network.c b/src/lib/network.c index 9278e89..52830be 100644 --- a/src/lib/network.c +++ b/src/lib/network.c @@ -1,4 +1,5 @@ #include "../base/all.h" +#include // https://stackoverflow.com/questions/1098897/what-is-the-largest-safe-udp-packet-size-on-the-internet #define UDP_MAX_MESSAGE_LEN 508 @@ -6,14 +7,42 @@ #define NET_OUTGOING_MESSAGE_QUEUE_LEN 16 #endif +#ifndef NET_SERVER_MAX_CLIENTS +#define NET_SERVER_MAX_CLIENTS (16) +#endif + typedef struct sockaddr_in SocketAddress; +typedef struct MultiServer { + bool ready; + i32 udp_socket_fd; + i32 tcp_socket_fd; + SocketAddress address; +} MultiServer; + +typedef struct TCPServer { + bool ready; + i32 socket_fd; + SocketAddress address; +} TCPServer; + typedef struct UDPServer { bool ready; SocketAddress server_address; i32 server_socket; } UDPServer; +typedef struct ServableUDPInfo { + UDPServer server; + void (*callback)(u8* udp_message, u32 udp_len, SocketAddress sending_address, i32 socket); +} ServableUDPInfo; + +typedef struct TCPClient { + bool ready; + SocketAddress server_address; + i32 socket; +} TCPClient; + typedef struct UDPClient { bool ready; SocketAddress server_address; @@ -21,14 +50,16 @@ typedef struct UDPClient { i32 socket; } UDPClient; -typedef struct UDPMessage { +typedef struct NetworkMessage { + bool use_socket; // default = false = UDP, just sendto(address) u16 bytes_len; SocketAddress address; + i32 socket_fd; u8 bytes[UDP_MAX_MESSAGE_LEN]; -} UDPMessage; +} NetworkMessage; typedef struct OutgoingMessageQueue { - UDPMessage items[NET_OUTGOING_MESSAGE_QUEUE_LEN]; + NetworkMessage items[NET_OUTGOING_MESSAGE_QUEUE_LEN]; u32 head; u32 tail; u32 count; @@ -46,7 +77,7 @@ fn OutgoingMessageQueue* newOutgoingMessageQueue(Arena* a) { return result; } -fn void outgoingMessageQueuePush(OutgoingMessageQueue* queue, UDPMessage* msg) { +fn void outgoingMessageQueuePush(OutgoingMessageQueue* queue, NetworkMessage* msg) { lockMutex(&queue->mutex); { while (queue->count == NET_OUTGOING_MESSAGE_QUEUE_LEN) { waitForCondSignal(&queue->not_full, &queue->mutex); @@ -60,11 +91,11 @@ fn void outgoingMessageQueuePush(OutgoingMessageQueue* queue, UDPMessage* msg) { } unlockMutex(&queue->mutex); } -fn UDPMessage* outgoingMessageNonblockingQueuePop(OutgoingMessageQueue* q, UDPMessage* copy_target) { +fn NetworkMessage* outgoingMessageNonblockingQueuePop(OutgoingMessageQueue* q, NetworkMessage* copy_target) { // immediately returns NULL if there's nothing in the ThreadQueue // copies the ParsedClientCommand into `copy_target` if there is something in the queue // and marks it as popped from the queue - UDPMessage* result = NULL; + NetworkMessage* result = NULL; lockMutex(&q->mutex); { if (q->count > 0) { @@ -80,8 +111,8 @@ fn UDPMessage* outgoingMessageNonblockingQueuePop(OutgoingMessageQueue* q, UDPMe return result; } -fn UDPMessage* outgoingMessageQueuePop(OutgoingMessageQueue* q, UDPMessage* copy_target) { - UDPMessage* result = NULL; +fn NetworkMessage* outgoingMessageQueuePop(OutgoingMessageQueue* q, NetworkMessage* copy_target) { + NetworkMessage* result = NULL; lockMutex(&q->mutex); { while (q->count == 0) { @@ -99,7 +130,6 @@ fn UDPMessage* outgoingMessageQueuePop(OutgoingMessageQueue* q, UDPMessage* copy return result; } - fn bool socketAddressEqual(SocketAddress a, SocketAddress b) { return a.sin_addr.s_addr == b.sin_addr.s_addr && a.sin_port == b.sin_port; @@ -109,6 +139,15 @@ fn bool socketAddressEqual(SocketAddress a, SocketAddress b) { // the only real difference between a udp "server" and a "client" is the bind() syscall // that the server makes in order to specify a port/address that it's listening on +i32 recv_exact(i32 socket, void* buf, u16 bytes_to_recv) { + i32 got, got_this_iter; + for (got = 0; got < bytes_to_recv; got += got_this_iter) { + got_this_iter = recv(socket, (char*)buf + got, bytes_to_recv - got, 0); + if (got_this_iter <= 0) return got_this_iter; + } + return got; +} + UDPServer createUDPServer(u16 server_port) { UDPServer result = {0}; // define the address we'll be listening on @@ -166,21 +205,247 @@ void infiniteReadUDPServer(UDPServer* server, void (*handleMessage)(u8* udp_mess i32 addrlen = sizeof(struct sockaddr); while (true) { bytes_recieved = recvfrom(server->server_socket, message_buffer, UDP_MAX_MESSAGE_LEN, 0, (struct sockaddr *)&client_address, (socklen_t*)&addrlen); - - //gethostbyaddr: determine who sent the datagram - //struct hostent* hostp = gethostbyaddr( - // (const char *)&client_address.sin_addr.s_addr, - // sizeof(client_address.sin_addr.s_addr), - // AF_INET - //); - - //ptr printable_host_IP_address_string = inet_ntoa(client_address.sin_addr); - handleMessage(message_buffer, bytes_recieved, client_address, server->server_socket); MemoryZero(message_buffer, UDP_MAX_MESSAGE_LEN); } } +void* infinitelyServeUDPSocket(void* servable) { + ServableUDPInfo info = *(ServableUDPInfo*)servable; + infiniteReadUDPServer(&info.server, info.callback); + return NULL; +} + +TCPClient createTCPClient(u16 server_port, str addr) { + TCPClient result = {0}; + // define the address we'll be listening on + result.server_address.sin_family = AF_INET; + if (addr == 0) { + result.server_address.sin_addr.s_addr = inet_addr("127.0.0.1"); + } else { + result.server_address.sin_addr.s_addr = inet_addr(addr); + } + result.server_address.sin_port = htons(server_port); + + // get a FileDescriptor number from the OS to use for our socket + result.socket = socket(PF_INET, SOCK_STREAM, 0); + if (result.socket < 0) { + return result; + } + socklen_t addr_len = sizeof(struct sockaddr_in); + i32 connect_result = connect(result.socket, (struct sockaddr *)&result.server_address, addr_len); + result.ready = connect_result != -1; + + return result; +} + +TCPServer createTCPServer(u16 server_port) { + TCPServer result = {0}; + // define the address we'll be listening on + result.address.sin_family = AF_INET; + result.address.sin_addr.s_addr = inet_addr("0.0.0.0");//htonl(INADDR_ANY); + result.address.sin_port = htons(server_port); + + // get a FileDescriptor number from the OS to use for our TCP socket + result.socket_fd = socket(PF_INET, SOCK_STREAM, 0); + if (result.socket_fd < 0) { + return result; + } + // to let us immediately kill and restart server + i32 optval = 1; + setsockopt(result.socket_fd, SOL_SOCKET, SO_REUSEADDR, (const void *)&optval, sizeof(i32)); + + // bind() the TCP + result.ready = bind(result.socket_fd, (struct sockaddr *)&result.address, sizeof(result.address)) >= 0; + if (result.ready) { + result.ready = result.ready && (listen(result.socket_fd, 10) >= 0); + } + + return result; +} + +i32 recvMessage(i32 socket, u8* message_buffer, void (*addSystemMessage)(u8* msg)) { + i32 bytes_recieved; + u16 msg_len; + i32 first_recv_got = recv_exact(socket, &msg_len, 2); + if (first_recv_got <= 0) { + return first_recv_got; + } + msg_len = ntohs(msg_len); // parse it to our correct byte order + + bytes_recieved = recv_exact(socket, message_buffer, msg_len); + if (addSystemMessage != NULL) { + char sbuf[128] = {0}; + sprintf(sbuf, "bytes_recieved=%d\n", bytes_recieved); + addSystemMessage((u8*)sbuf); + } + return bytes_recieved; +} + +void infiniteReadTCPServer( + TCPServer* server, + void (*handleMessage)(u8* udp_message, u32 udp_len, SocketAddress sending_address, i32 socket), + void (*closeConnection)(i32 socket_fd), + void (*addSystemMessage)(u8* msg) +) { + u8 message_buffer[UDP_MAX_MESSAGE_LEN] = {0}; + i32 bytes_recieved = 0; + SocketAddress client_address = {0}; + i32 addrlen = sizeof(struct sockaddr); + i32 new_fd; + struct pollfd pollable_fds[NET_SERVER_MAX_CLIENTS+1] = {0}; // +1 for the listener socket + // poll the `listen()`ed socket_fd + pollable_fds[0].fd = server->socket_fd; + pollable_fds[0].events = POLLIN; + u32 pollable_fd_count = 1; + + i32 poll_event_count; + bool should_keep_serving = true; + while (should_keep_serving) { + poll_event_count = poll(pollable_fds, pollable_fd_count, -1); + if (poll_event_count == -1) { + // TODO handle error better + should_keep_serving = false; + continue; + } + for(u32 i = 0; i < pollable_fd_count; i++) { + bool is_fd_readable = pollable_fds[i].revents & (POLLIN | POLLHUP); + if (is_fd_readable) { + bool is_fd_main_server_listener = pollable_fds[i].fd == server->socket_fd; + if (is_fd_main_server_listener) { // it's a new connection + new_fd = accept(server->socket_fd, (struct sockaddr *)&client_address, (socklen_t*)&addrlen); + if (new_fd == -1) { + if (addSystemMessage != NULL) { + addSystemMessage((u8*)"TODO: handle this accept() error for real, bitch"); + } + } else { + if (pollable_fd_count < NET_SERVER_MAX_CLIENTS+1) { + pollable_fds[pollable_fd_count].fd = new_fd; + pollable_fds[pollable_fd_count].events = POLLIN | POLLHUP; + pollable_fds[pollable_fd_count].revents = 0; + pollable_fd_count++; + if (addSystemMessage != NULL) { + char sbuf[128] = {0}; + sprintf(sbuf, "new connection on socket=%d, total=%d\n", new_fd, pollable_fd_count); + addSystemMessage((u8*)sbuf); + } + } else { + send(new_fd, "server full", 11, 0); // go away sir, we are out of space to keep track of this socket + close(new_fd); + } + } + } else {// Otherwise we're just a regular client + bytes_recieved = recvMessage(pollable_fds[i].fd, message_buffer, addSystemMessage); + if (bytes_recieved <= 0) { // error condition + bool client_hung_up = bytes_recieved == 0; + // TODO do something with the error case + closeConnection(pollable_fds[i].fd); + close(pollable_fds[i].fd); + if (addSystemMessage != NULL) { + char sbuf[256] = {0}; + sprintf(sbuf, "closed connection on socket=%d, client_hung_up? %s\n", pollable_fds[i].fd, client_hung_up ? "yes" : "no"); + addSystemMessage((u8*)sbuf); + } + // copy the last one over the current one and "forget" the last one by decrementing the count + pollable_fds[i] = pollable_fds[--pollable_fd_count]; + } else { // we got an actual message from this guy + handleMessage(message_buffer, bytes_recieved, client_address, pollable_fds[i].fd); + } + MemoryZero(message_buffer, UDP_MAX_MESSAGE_LEN); + } + } + } + } +} + +void infiniteReadTCPClient( + i32 socket, + bool* should_quit, + void (*handleMessage)(u8* udp_message, i32 bytes_recieved), + void (*addSystemMessage)(u8* msg) +) { + u8 message_buffer[UDP_MAX_MESSAGE_LEN] = {0}; + i32 bytes_recieved = 0; + while ((*should_quit) != true) { + bytes_recieved = recvMessage(socket, message_buffer, addSystemMessage); + if (bytes_recieved == -1) { + if (addSystemMessage != NULL) { + char sbuf[128] = {0}; + sprintf(sbuf, "TODO handle this error, bytes_recieved=%d\n", bytes_recieved); + addSystemMessage((u8*)sbuf); + } + continue; + } + + handleMessage(message_buffer, bytes_recieved); + MemoryZero(message_buffer, UDP_MAX_MESSAGE_LEN); + } +} + +MultiServer createMultiServer(u16 server_port) { + MultiServer result = {0}; + // define the address we'll be listening on + result.address.sin_family = AF_INET; + result.address.sin_addr.s_addr = inet_addr("0.0.0.0");//htonl(INADDR_ANY); + result.address.sin_port = htons(server_port); + + // get a FileDescriptor number from the OS to use for our UDP socket + result.udp_socket_fd = socket(PF_INET, SOCK_DGRAM, 0); + if (result.udp_socket_fd < 0) { + return result; + } + // get a FileDescriptor number from the OS to use for our TCP socket + result.tcp_socket_fd = socket(PF_INET, SOCK_STREAM, 0); + if (result.tcp_socket_fd < 0) { + return result; + } + // to let us immediately kill and restart server + i32 optval = 1; + setsockopt(result.udp_socket_fd, SOL_SOCKET, SO_REUSEADDR, (const void *)&optval, sizeof(i32)); + setsockopt(result.tcp_socket_fd, SOL_SOCKET, SO_REUSEADDR, (const void *)&optval, sizeof(i32)); + + // bind() the UDP + result.ready = bind(result.udp_socket_fd, (struct sockaddr *)&result.address, sizeof(result.address)) >= 0; + + // bind() the TCP + result.ready = result.ready && (bind(result.tcp_socket_fd, (struct sockaddr *)&result.address, sizeof(result.address)) >= 0); + + if (result.ready) { + result.ready = result.ready && (listen(result.tcp_socket_fd, 10) >= 0); + } + + return result; +} + +void infiniteReadMultiServer(MultiServer* server, void (*handleMessage)(u8* udp_message, u32 udp_len, SocketAddress sending_address, i32 socket)) { + UDPServer udp_server = { + .ready = server->ready, + .server_address = server->address, + .server_socket = server->udp_socket_fd, + }; + ServableUDPInfo udp_info = { + .server = udp_server, + .callback = handleMessage, + }; + Thread udp_listener_thread = spawnThread(&infinitelyServeUDPSocket, &udp_info); + + u8 message_buffer[UDP_MAX_MESSAGE_LEN] = {0}; + SocketAddress client_address = {0}; + i32 addrlen = sizeof(struct sockaddr); + i32 new_fd; + i32 bytes_recieved = 0; + while (true) { + // TCP + new_fd = accept(server->tcp_socket_fd, (struct sockaddr *)&client_address, (socklen_t*)&addrlen); + bytes_recieved = recv(new_fd, message_buffer, UDP_MAX_MESSAGE_LEN, 0); + if (bytes_recieved > 0) { + handleMessage(message_buffer, bytes_recieved, client_address, new_fd); + } + MemoryZero(message_buffer, UDP_MAX_MESSAGE_LEN); + } + osThreadJoin(udp_listener_thread, MAX_u64); +} + // TODO: sendall() to handle cases when the sendto() bytes return value is less than the intended bytes to send... stupid kernel fuckin wit us. i32 sendUDPu8List(i32 using_socket, SocketAddress* to, u8List* message) { return sendto( @@ -196,3 +461,14 @@ i32 sendUDPu8List(i32 using_socket, SocketAddress* to, u8List* message) { i32 sendUDPMessage(UDPServer* to, u8* message, u32 len) { return sendto(to->server_socket, message, len, 0, (struct sockaddr *)&to->server_address, sizeof(struct sockaddr)); } +i32 sendTCPMessage(NetworkMessage msg) { + assert(msg.use_socket); + assert(msg.socket_fd >= 0); + u16 msg_len = htons(msg.bytes_len); + i32 result = send(msg.socket_fd, &msg_len, 2, 0); + if (result == -1) { + return result; + } + return send(msg.socket_fd, msg.bytes, msg.bytes_len, 0); +} + diff --git a/src/lib/tui.c b/src/lib/tui.c index 064c5b2..7f52305 100644 --- a/src/lib/tui.c +++ b/src/lib/tui.c @@ -1,8 +1,6 @@ #include "../base/all.h" #include "../string_chunk.h" #include -#include -#include #include #define UTF8_MAX_WIDTH 4 diff --git a/src/server.c b/src/server.c index 0b11a5f..81f96c1 100644 --- a/src/server.c +++ b/src/server.c @@ -6,13 +6,13 @@ * my_variable * MY_CONSTANT * */ -#include #include #include #include #include "shared.h" #include "base/impl.c" -#define NET_OUTGOING_MESSAGE_QUEUE_LEN 64 +#define NET_OUTGOING_MESSAGE_QUEUE_LEN (64) +#define NET_SERVER_MAX_CLIENTS (16) #include "lib/network.c" #include "lib/tui.c" #include "string_chunk.c" @@ -21,9 +21,7 @@ #define LEFT_ROOM_ENTITES_LEN (KB(1)) #define ROOM_MAP_COLLISIONS_LEN MAX_ROOMS/8 #define CLIENT_COMMAND_LIST_LEN 8 -#define SERVER_PORT 7777 #define SERVER_MAX_HEAP_MEMORY MB(256) -#define SERVER_MAX_CLIENTS 16 #define GAME_THREAD_CONCURRENCY 2 #define GOAL_NETWORK_SEND_LOOPS_PER_S 8 #define GOAL_NETWORK_SEND_LOOP_US 1000000/GOAL_NETWORK_SEND_LOOPS_PER_S @@ -53,6 +51,7 @@ typedef struct ParsedClientCommand { u16 alt_port; u32 sender_ip; u32 alt_ip; + i32 socket_fd; u32 qty; CommodityType commodity; StringChunkList name; @@ -80,12 +79,11 @@ typedef struct Account { } Account; typedef struct Client { - u16 lan_port; - i32 lan_ip; + bool active; + i32 socket_fd; u64 account_id; SocketAddress address; CommandType commands[CLIENT_COMMAND_LIST_LEN]; - u64 last_ping; } Client; typedef struct ClientList { @@ -214,14 +212,16 @@ fn void exitWithErrorMessage(ptr msg) { exit(1); } -fn u32 pushClient(ClientList* clients, SocketAddress addr) { - Client new_client = {0}; - new_client.last_ping = state.frame; - new_client.address = addr; +fn u32 pushClient(ClientList* clients, SocketAddress addr, i32 socket_fd) { + Client new_client = { + .active = true, + .socket_fd = socket_fd, + .address = addr, + }; // first, try to overwrite an old dc'ed client for (u32 i = 1; i < clients->length; i++) { - if (clients->items[i].last_ping == 0) { + if (clients->items[i].active == false) { clients->items[i] = new_client; return i; } @@ -248,20 +248,10 @@ fn bool deleteClientByAccountId(ClientList* clients, u64 id) { return succeeded; } -fn u32 findClientHandleByAccountId(ClientList* clients, u64 id) { +fn u32 findClientHandle(ClientList* clients, SocketAddress address, i32 socket_fd) { for (u32 i = 0; i < clients->length; i++) { Client c = clients->items[i]; - if (c.account_id == id) { - return i; - } - } - return 0; -} - -fn u32 findClientHandleBySocketAddress(ClientList* clients, SocketAddress address) { - for (u32 i = 0; i < clients->length; i++) { - Client c = clients->items[i]; - if (socketAddressEqual(address, c.address)) { + if (socketAddressEqual(address, c.address) && socket_fd == c.socket_fd) { return i; } } @@ -295,8 +285,8 @@ fn bool shipIsNull(PlayerShip* ship) { return ship->id == 0 && ship->base_cost == 0; } -fn UDPMessage makeMessageSystemPassengers(StarSystem* sys) { - UDPMessage outgoing_message = {0}; +fn NetworkMessage makeMessageSystemPassengers(StarSystem* sys) { + NetworkMessage outgoing_message = { .use_socket = true }; u32 msg_i = 0; outgoing_message.bytes[msg_i++] = (u8)MessageSystemPassengers; outgoing_message.bytes[msg_i++] = (u8)sys->idx; @@ -312,8 +302,8 @@ fn UDPMessage makeMessageSystemPassengers(StarSystem* sys) { return outgoing_message; } -fn UDPMessage makeMessageSystemCommodities(StarSystem* sys) { - UDPMessage outgoing_message = {0}; +fn NetworkMessage makeMessageSystemCommodities(StarSystem* sys) { + NetworkMessage outgoing_message = { .use_socket = true }; u32 planet_count = starSystemPlanetCount(sys); u32 msg_i = 0; outgoing_message.bytes[msg_i++] = (u8)MessageSystemCommodities; @@ -328,8 +318,8 @@ fn UDPMessage makeMessageSystemCommodities(StarSystem* sys) { return outgoing_message; } -fn void sendMessageTransactionResult(SocketAddress addr, u32 qty, bool buying, u32 credits) { - UDPMessage outgoing_message = {.address = addr}; +fn void sendMessageTransactionResult(i32 socket_fd, SocketAddress addr, u32 qty, bool buying, u32 credits) { + NetworkMessage outgoing_message = { .address = addr, .use_socket = true, .socket_fd = socket_fd }; u32 msg_i = 0; outgoing_message.bytes[msg_i++] = (u8)MessageTransactionResult; outgoing_message.bytes[msg_i++] = buying; @@ -340,8 +330,8 @@ fn void sendMessageTransactionResult(SocketAddress addr, u32 qty, bool buying, u addSystemMessage((u8*)"Message TransactionResult sent"); } -fn UDPMessage makeMessagePlayerDetails(PlayerShip ship) { - UDPMessage outgoing_message = {0}; +fn NetworkMessage makeMessagePlayerDetails(PlayerShip ship) { + NetworkMessage outgoing_message = {0}; u32 msg_i = 0; outgoing_message.bytes[msg_i++] = (u8)MessagePlayerDetails; outgoing_message.bytes[msg_i++] = ship.type; @@ -373,9 +363,8 @@ fn UDPMessage makeMessagePlayerDetails(PlayerShip ship) { return outgoing_message; } -fn void sendMessageJobAcceptResult(SocketAddress addr, bool result) { - UDPMessage outgoing_message = {0}; - outgoing_message.address = addr; +fn void sendMessageJobAcceptResult(i32 socket_fd, SocketAddress addr, bool result) { + NetworkMessage outgoing_message = { .address = addr, .use_socket = true, .socket_fd = socket_fd }; u32 msg_i = 0; outgoing_message.bytes[msg_i++] = (u8)MessageJobAcceptResult; outgoing_message.bytes[msg_i++] = result; @@ -384,9 +373,8 @@ fn void sendMessageJobAcceptResult(SocketAddress addr, bool result) { addSystemMessage((u8*)"MessageJobAcceptResult sent"); } -fn void sendMessagePayoffResult(SocketAddress addr) { - UDPMessage outgoing_message = {0}; - outgoing_message.address = addr; +fn void sendMessagePayoffResult(i32 socket_fd, SocketAddress addr) { + NetworkMessage outgoing_message = { .address = addr, .use_socket = true, .socket_fd = socket_fd }; u32 msg_i = 0; outgoing_message.bytes[msg_i++] = (u8)MessagePayoffResult; outgoing_message.bytes_len = msg_i; @@ -394,9 +382,8 @@ fn void sendMessagePayoffResult(SocketAddress addr) { addSystemMessage((u8*)"MessagePayoffResult sent"); } -fn void sendMessageStarPositions(SocketAddress addr) { - UDPMessage outgoing_message = {0}; - outgoing_message.address = addr; +fn void sendMessageStarPositions(i32 socket_fd, SocketAddress addr) { + NetworkMessage outgoing_message = { .use_socket = true, .address = addr, .socket_fd = socket_fd }; // tell the client about the map u32 star_msg_size = 2+MAX_PLANETS; outgoing_message.bytes_len = 1+(star_msg_size*STAR_SYSTEM_COUNT); @@ -409,18 +396,24 @@ fn void sendMessageStarPositions(SocketAddress addr) { } } outgoingMessageQueuePush(state.network_send_queue, &outgoing_message); - addSystemMessage((u8*)"MessageStarPositions sent"); + char sbuf[SBUFLEN] = {0}; + sprintf(sbuf, "MessageStarPositions sent socket=%d, message_len=%d", socket_fd, outgoing_message.bytes_len); + addSystemMessage((u8*)sbuf); } -fn void sendMessagePlayerDetails(PlayerShip ship, SocketAddress addr) { - UDPMessage outgoing_message = makeMessagePlayerDetails(ship); +fn void sendMessagePlayerDetails(i32 socket_fd, PlayerShip ship, SocketAddress addr) { + NetworkMessage outgoing_message = makeMessagePlayerDetails(ship); outgoing_message.address = addr; + outgoing_message.use_socket = true; + outgoing_message.socket_fd = socket_fd; outgoingMessageQueuePush(state.network_send_queue, &outgoing_message); - addSystemMessage((u8*)"MessagePlayerDetails sent"); + char sbuf[SBUFLEN] = {0}; + sprintf(sbuf, "MessagePlayerDetails sent socket=%d, message_len=%d", socket_fd, outgoing_message.bytes_len); + addSystemMessage((u8*)sbuf); } -fn void sendMessageAuctionBidResult(SocketAddress addr, AuctionBidResult result) { - UDPMessage outgoing_message = {.address = addr}; +fn void sendMessageAuctionBidResult(i32 socket_fd, SocketAddress addr, AuctionBidResult result) { + NetworkMessage outgoing_message = { .address = addr, .use_socket = true, .socket_fd = socket_fd }; u32 msg_i = 0; outgoing_message.bytes[msg_i++] = (u8)MessageAuctionBidResult; outgoing_message.bytes[msg_i++] = result; @@ -430,11 +423,13 @@ fn void sendMessageAuctionBidResult(SocketAddress addr, AuctionBidResult result) } fn void handleIncomingMessage(u8* message, u32 len, SocketAddress sender, i32 socket) { - dbg("%d: %s from %s:%d\n", len, command_type_strings[message[0]], inet_ntoa(sender.sin_addr), sender.sin_port); char sbuf[SBUFLEN] = {0}; + sprintf(sbuf, "%d: %s from %s:%d\n", len, command_type_strings[message[0]], inet_ntoa(sender.sin_addr), sender.sin_port); + addSystemMessage((u8*)sbuf); u32 msg_idx = 0; ParsedClientCommand parsed = { .type = (CommandType)message[msg_idx++], + .socket_fd = socket, .sender_ip = sender.sin_addr.s_addr, .sender_port = sender.sin_port, }; @@ -482,7 +477,6 @@ fn void handleIncomingMessage(u8* message, u32 len, SocketAddress sender, i32 so sprintf(sbuf, "Logging in player: %s %d %s\n", command_type_strings[parsed.type], name_len, message + 7); addSystemMessage((u8*)sbuf); } break; - case CommandKeepAlive: break; case CommandPayMortgage: { parsed.id = readU64FromBufferLE(message + msg_idx); MemoryZero(sbuf, SBUFLEN); @@ -521,18 +515,30 @@ fn void handleIncomingMessage(u8* message, u32 len, SocketAddress sender, i32 so pccThreadSafeQueuePush(state.network_recv_queue, &parsed); } -fn void* receiveNetworkUpdates(void* udp) { - UDPServer server = *(UDPServer*)udp; - dbg("receiveNetworkUpdates() sock=%d\n", server.server_socket); - infiniteReadUDPServer(&server, handleIncomingMessage); +fn void removeClientBySocketFd(i32 socket_fd) { + Client blank_client = {0}; + // i=1 because first client is null-client + for (u32 i = 1; i < state.clients.length; i++) { + if (state.clients.items[i].socket_fd == socket_fd) { + state.clients.items[i] = blank_client; + } + } +} + +fn void* receiveNetworkUpdates(void* tcp_server) { + TCPServer server = *(TCPServer*)tcp_server; + char sbuf[SBUFLEN] = {0}; + sprintf(sbuf, "receiveNetworkUpdates() sock=%d\n", server.socket_fd); + addSystemMessage((u8*)sbuf); + infiniteReadTCPServer(&server, handleIncomingMessage, removeClientBySocketFd, addSystemMessage); return NULL; } -fn void* sendNetworkUpdates(void* sock) { +fn void* sendNetworkUpdates(void* tcp_server) { + TCPServer server = *(TCPServer*)tcp_server; ThreadContext tctx = {0}; tctxInit(&tctx); - i32* socket_ptr = (i32*)sock; - i32 socket = *socket_ptr; + i32 socket = server.socket_fd; char sbuf[SBUFLEN] = {0}; u64 send_loop = 0; while (!should_quit) { @@ -541,103 +547,96 @@ fn void* sendNetworkUpdates(void* sock) { // 1. clear out our "outgoingMessage" queue { - UDPMessage to_send = { 0 }; - UDPMessage* next_to_send = outgoingMessageNonblockingQueuePop(state.network_send_queue, &to_send); + NetworkMessage to_send = { 0 }; + NetworkMessage* next_to_send = outgoingMessageNonblockingQueuePop(state.network_send_queue, &to_send); u8List bytes_list = { 0 }; while (next_to_send != NULL) { - bytes_list.items = to_send.bytes; - bytes_list.length = to_send.bytes_len; - bytes_list.capacity = to_send.bytes_len; - sendUDPu8List(socket, &to_send.address, &bytes_list); + if (to_send.use_socket) { + char sbuf[SBUFLEN] = {0}; + sprintf(sbuf, "sendTCPMessage() sock=%d msg=%s len=%d\n", to_send.socket_fd, MESSAGE_STRINGS[to_send.bytes[0]], to_send.bytes_len); + addSystemMessage((u8*)sbuf); + sendTCPMessage(to_send); + } else { // UDP + bytes_list.items = to_send.bytes; + bytes_list.length = to_send.bytes_len; + bytes_list.capacity = to_send.bytes_len; + sendUDPu8List(socket, &to_send.address, &bytes_list); + } next_to_send = outgoingMessageNonblockingQueuePop(state.network_send_queue, &to_send); } } - UDPMessage sys_udp_msg = { 0 }; - sys_udp_msg = makeMessageSystemCommodities(&state.map[send_loop/2 % STAR_SYSTEM_COUNT]); - u8List sys_msg = { - .capacity = UDP_MAX_MESSAGE_LEN, - .items = sys_udp_msg.bytes, - .length = sys_udp_msg.bytes_len, - }; - UDPMessage sys_pass_udp_msg = { 0 }; - sys_pass_udp_msg = makeMessageSystemPassengers(&state.map[send_loop/2 % STAR_SYSTEM_COUNT]); - u8List sys_pass_msg = { - .capacity = UDP_MAX_MESSAGE_LEN, - .items = sys_pass_udp_msg.bytes, - .length = sys_pass_udp_msg.bytes_len, - }; + NetworkMessage base_sys_msg = makeMessageSystemCommodities(&state.map[send_loop/2 % STAR_SYSTEM_COUNT]); + NetworkMessage base_pass_msg = makeMessageSystemPassengers(&state.map[send_loop/2 % STAR_SYSTEM_COUNT]); + lockMutex(&state.client_mutex); { // WARNING the `i` starts at 1 here because state.clients.items[0] is a "null" Client for (u32 i = 1; i < state.clients.length; i++) { Client client = state.clients.items[i]; - if (client.last_ping+CLIENT_TIMEOUT_FRAMES < state.frame) { - memset(&state.clients.items[i], 0, sizeof(Client)); + if (client.active == false) { continue; } if (send_loop % 2 == 0) { // every other "send-frame" we send each connnected client the current prices for a different system // so that the prices mostly stay up to date pretty quickly without having to track changes - sendUDPu8List(socket, &client.address, &sys_msg); + base_sys_msg.socket_fd = client.socket_fd; + sendTCPMessage(base_sys_msg); // and the passenger offers - sendUDPu8List(socket, &client.address, &sys_pass_msg); + base_pass_msg.socket_fd = client.socket_fd; + sendTCPMessage(base_pass_msg); // send the client the current auction details for their current system Account* account = &state.accounts[client.account_id]; if (!accountIsEmpty(account)) { StarSystem curr = state.map[account->ship.system_idx]; - MemoryZero(sbuf, SBUFLEN); - u32 msgidx = 0; - sbuf[msgidx++] = MessageAuctionDetails; - sbuf[msgidx++] = curr.auction.type; - sbuf[msgidx++] = curr.auction.qty; - msgidx += writeU32ToBufferLE((u8*)sbuf + msgidx, curr.auction.price); - msgidx += writeU32ToBufferLE((u8*)sbuf + msgidx, curr.auction.started_at); - msgidx += writeU32ToBufferLE((u8*)sbuf + msgidx, curr.auction.finished_at); - u8List msg = { - .capacity = UDP_MAX_MESSAGE_LEN, - .items = (u8*)sbuf, - .length = msgidx, + NetworkMessage auction_msg = { + .use_socket = true, + .socket_fd = client.socket_fd, + .address = client.address, }; - sendUDPu8List(socket, &client.address, &msg); + u32 msgidx = 0; + auction_msg.bytes[msgidx++] = MessageAuctionDetails; + auction_msg.bytes[msgidx++] = curr.auction.type; + auction_msg.bytes[msgidx++] = curr.auction.qty; + msgidx += writeU32ToBufferLE((u8*)auction_msg.bytes + msgidx, curr.auction.price); + msgidx += writeU32ToBufferLE((u8*)auction_msg.bytes + msgidx, curr.auction.started_at); + msgidx += writeU32ToBufferLE((u8*)auction_msg.bytes + msgidx, curr.auction.finished_at); + auction_msg.bytes_len = msgidx; + sendTCPMessage(auction_msg); } } // update all the changed systems - UDPMessage msg_data; + NetworkMessage msg_data; for (u32 ii = 0; ii < STAR_SYSTEM_COUNT; ii++) { if (state.map[ii].changed == true) { // send commodities msg_data = makeMessageSystemCommodities(&state.map[ii]); - u8List msg = { - .capacity = UDP_MAX_MESSAGE_LEN, - .items = msg_data.bytes, - .length = msg_data.bytes_len, - }; - sendUDPu8List(socket, &client.address, &msg); + msg_data.address = client.address; + msg_data.socket_fd = client.socket_fd; + msg_data.use_socket = true; + sendTCPMessage(msg_data); // send passenger jobs MemoryZero(sbuf, SBUFLEN); sprintf(sbuf, "sending passenger jobs for %s\n", STAR_NAMES[ii]); addSystemMessage((u8*)sbuf); msg_data = makeMessageSystemPassengers(&state.map[ii]); - msg.capacity = UDP_MAX_MESSAGE_LEN; - msg.items = msg_data.bytes; - msg.length = msg_data.bytes_len; - sendUDPu8List(socket, &client.address, &msg); + msg_data.address = client.address; + msg_data.socket_fd = client.socket_fd; + msg_data.use_socket = true; + sendTCPMessage(msg_data); } } // update all the changed accounts for (u32 ii = 0; ii < ACCOUNT_LEN; ii++) { if (state.accounts[ii].changed == true) { msg_data = makeMessagePlayerDetails(state.accounts[ii].ship); - u8List msg = { - .capacity = UDP_MAX_MESSAGE_LEN, - .items = msg_data.bytes, - .length = msg_data.bytes_len, - }; - sendUDPu8List(socket, &client.address, &msg); + msg_data.address = client.address; + msg_data.socket_fd = client.socket_fd; + msg_data.use_socket = true; + sendTCPMessage(msg_data); } } } @@ -670,7 +669,7 @@ fn void* gameLoop(void* params) { char sbuf[SBUFLEN] = {0}; sprintf(sbuf, "Lane %lld (%lld) of %lld starting.", lane_ctx->lane_idx, LaneIdx(), lane_ctx->lane_count); addSystemMessage((u8*)sbuf); - UDPMessage outgoing_message = {0}; + NetworkMessage outgoing_message = { .use_socket = true }; u64 loop_start; u64 last_burn = 0; u64 last_hp_regen = 0; @@ -698,7 +697,7 @@ fn void* gameLoop(void* params) { sender.sin_addr.s_addr = msg.sender_ip; sender.sin_port = msg.sender_port; // find which client it is - u32 client_handle = findClientHandleBySocketAddress(&state.clients, sender); + u32 client_handle = findClientHandle(&state.clients, sender, msg.socket_fd); Client* client = &state.clients.items[client_handle]; switch (msg.type) { case CommandBuyAuction: { @@ -713,15 +712,15 @@ fn void* gameLoop(void* params) { player_sys->auction.finished_at = state.frame; account->ship.credits -= player_sys->auction.price; account->ship.commodities[player_sys->auction.type] += player_sys->auction.qty; - sendMessageAuctionBidResult(sender, AuctionBidResultPurchased); - sendMessagePlayerDetails(account->ship, sender); + sendMessageAuctionBidResult(msg.socket_fd, sender, AuctionBidResultPurchased); + sendMessagePlayerDetails(msg.socket_fd, account->ship, sender); } else if (!player_has_money_for_purchase) { - sendMessageAuctionBidResult(sender, AuctionBidResultNotEnoughMoney); + sendMessageAuctionBidResult(msg.socket_fd, sender, AuctionBidResultNotEnoughMoney); } else { - sendMessageAuctionBidResult(sender, AuctionBidResultNotEnoughCargoSpace); + sendMessageAuctionBidResult(msg.socket_fd, sender, AuctionBidResultNotEnoughCargoSpace); } } else { - sendMessageAuctionBidResult(sender, AuctionBidResultAuctionAlreadyFinished); + sendMessageAuctionBidResult(msg.socket_fd, sender, AuctionBidResultAuctionAlreadyFinished); } } break; case CommandAcceptPassengerJob: { @@ -751,7 +750,7 @@ fn void* gameLoop(void* params) { MemoryZero(&player_sys->offers[i], sizeof(PassengerJobOffer)); player_sys->changed = true; succeeded = true; - sendMessageJobAcceptResult(sender, succeeded); + sendMessageJobAcceptResult(client->socket_fd, sender, succeeded); // end both for loops "break break;" ii = MAX_PASSENGER_BERTHS; i = MAX_PASSENGER_JOB_OFFERS; @@ -762,7 +761,7 @@ fn void* gameLoop(void* params) { } } if (!succeeded) { - sendMessageJobAcceptResult(sender, succeeded); + sendMessageJobAcceptResult(client->socket_fd, sender, succeeded); } } break; case CommandPayMortgage: { @@ -778,7 +777,7 @@ fn void* gameLoop(void* params) { account->ship.credits -= amount_to_pay; account->ship.remaining_mortgage -= amount_to_pay; account->changed = true; - sendMessagePayoffResult(sender); + sendMessagePayoffResult(client->socket_fd, sender); } break; case CommandSetDestination: { if (client_handle == 0) break; @@ -789,7 +788,7 @@ fn void* gameLoop(void* params) { if (client_handle == 0) break; Account* account = &state.accounts[client->account_id]; account->ship.ready_to_depart = msg.byte; - sendMessagePlayerDetails(account->ship, sender); + sendMessagePlayerDetails(client->socket_fd, account->ship, sender); state.all_accounts_ready = true; for (u32 i = 0; i < ACCOUNT_LEN; i++) { if (!accountIsEmpty(&state.accounts[i])) { @@ -853,33 +852,17 @@ fn void* gameLoop(void* params) { } } sys->changed = true; - sendMessagePlayerDetails(account->ship, sender); - sendMessageTransactionResult(sender, qty_traded, is_buying_from_system, credit_value); - } break; - case CommandKeepAlive: { - //MemoryZero(sbuf, SBUFLEN); - //sprintf(sbuf, "KeepAlive for client_handle=%d on frame %lld", client_handle, state.frame); - //addSystemMessage((u8*)sbuf); - if (client_handle == 0) { - outgoing_message.bytes[0] = (u8)MessageNotAlive; - outgoing_message.bytes_len = 1; - outgoing_message.address = sender; - outgoingMessageQueuePush(state.network_send_queue, &outgoing_message); - } else { - state.clients.items[client_handle].last_ping = state.frame; - } + sendMessagePlayerDetails(client->socket_fd, account->ship, sender); + sendMessageTransactionResult(client->socket_fd, sender, qty_traded, is_buying_from_system, credit_value); } break; case CommandLogin: { if (client_handle == 0) { - client_handle = pushClient(&state.clients, sender); + client_handle = pushClient(&state.clients, sender, msg.socket_fd); client = &state.clients.items[client_handle]; MemoryZero(sbuf, SBUFLEN); sprintf(sbuf, "pushed new client handle = %d\n", client_handle); addSystemMessage((u8*)sbuf); } - // update/set the lan_ip/port info for p2p connections - client->lan_ip = htonl(msg.alt_ip); - client->lan_port = htons(msg.alt_port); /* struct in_addr ipaddr; @@ -906,12 +889,13 @@ fn void* gameLoop(void* params) { if (pw_matches) { addSystemMessage((u8*)" pw matched\n"); existing_account->changed = true; - sendMessageStarPositions(sender); + sendMessageStarPositions(client->socket_fd, sender); } else { // tell the client they did a bad pw outgoing_message.bytes[0] = (u8)MessageBadPw; outgoing_message.bytes_len = 1; outgoing_message.address = sender; + outgoing_message.socket_fd = client->socket_fd; outgoingMessageQueuePush(state.network_send_queue, &outgoing_message); addSystemMessage((u8*)"MessageBadPw sent\n"); break; @@ -937,6 +921,7 @@ fn void* gameLoop(void* params) { writeU64ToBufferLE(outgoing_message.bytes + 1, existing_account->ship.id); outgoing_message.bytes_len = 9; outgoing_message.address = sender; + outgoing_message.socket_fd = client->socket_fd; outgoingMessageQueuePush(state.network_send_queue, &outgoing_message); addSystemMessage((u8*)"MessageCharacterId sent\n"); } else { @@ -944,6 +929,7 @@ fn void* gameLoop(void* params) { outgoing_message.bytes[0] = (u8)MessageNewAccountCreated; outgoing_message.bytes_len = 1; outgoing_message.address = sender; + outgoing_message.socket_fd = client->socket_fd; outgoingMessageQueuePush(state.network_send_queue, &outgoing_message); addSystemMessage((u8*)"MessageNewAccountCreated sent\n"); } @@ -971,7 +957,7 @@ fn void* gameLoop(void* params) { .base_cost = template.base_cost, .remaining_mortgage = template.base_cost - STARTING_DOWN_PAYMENT, .interest_rate = calcInterestRate(template.base_cost, STARTING_DOWN_PAYMENT), - .credits = 20000.0, + .credits = 2000.0, .cu_m_fuel = template.cu_m_fuel, .cu_m_o2 = template.cu_m_o2, .id = account->id, @@ -990,18 +976,20 @@ fn void* gameLoop(void* params) { // tell the client their account id outgoing_message.address = sender; + outgoing_message.socket_fd = client->socket_fd; outgoing_message.bytes_len = 9; outgoing_message.bytes[0] = (u8)MessageCharacterId; writeU64ToBufferLE(outgoing_message.bytes + 1, account->ship.id); outgoingMessageQueuePush(state.network_send_queue, &outgoing_message); addSystemMessage((u8*)"MessageCharacterId sent\n"); - sendMessagePlayerDetails(account->ship, sender); + sendMessagePlayerDetails(client->socket_fd, account->ship, sender); - sendMessageStarPositions(sender); + sendMessageStarPositions(client->socket_fd, sender); outgoing_message = makeMessageSystemCommodities(&starting_system); outgoing_message.address = sender; + outgoing_message.socket_fd = client->socket_fd; outgoingMessageQueuePush(state.network_send_queue, &outgoing_message); MemoryZero(sbuf, SBUFLEN); sprintf(sbuf, "%s sent\n", MESSAGE_STRINGS[outgoing_message.bytes[0]]); @@ -1020,9 +1008,10 @@ fn void* gameLoop(void* params) { } if (state.all_accounts_ready) { - for (u32 i = 1; i < SERVER_MAX_CLIENTS; i++) { - if (state.clients.items[i].last_ping != 0) { + for (u32 i = 1; i < NET_SERVER_MAX_CLIENTS; i++) { + if (state.clients.items[i].active) { outgoing_message.address = state.clients.items[i].address; + outgoing_message.socket_fd = state.clients.items[i].socket_fd; outgoing_message.bytes_len = 1; outgoing_message.bytes[0] = (u8)MessageTurnTick; outgoingMessageQueuePush(state.network_send_queue, &outgoing_message); @@ -1031,9 +1020,10 @@ fn void* gameLoop(void* params) { } if (state.someone_won) { - for (u32 i = 1; i < SERVER_MAX_CLIENTS; i++) { - if (state.clients.items[i].last_ping != 0) { + for (u32 i = 1; i < NET_SERVER_MAX_CLIENTS; i++) { + if (state.clients.items[i].active) { outgoing_message.address = state.clients.items[i].address; + outgoing_message.socket_fd = state.clients.items[i].socket_fd; outgoing_message.bytes_len = 2; outgoing_message.bytes[0] = (u8)MessageGameOver; outgoing_message.bytes[1] = state.winner_id; @@ -1074,7 +1064,7 @@ fn void* gameLoop(void* params) { f32 t_sec = ((f32)state.frame - (f32)grace_period_ends_at) / (f32)GOAL_GAME_LOOPS_PER_S; // t is in minutes f32 t = t_sec / 60; - f32 decay = -0.60 * t; + f32 decay = -0.65 * t; f32 floor_price = COMMODITIES[sys->auction.type].price * 0.9; sys->auction.price = Max((initial_price * pow(EULERS_E, decay)), floor_price); } @@ -1179,8 +1169,9 @@ fn void* gameLoop(void* params) { addSystemMessage((u8*)sbuf); // send a job completion message? for (u32 i = 1; i < state.clients.length; i++) { - if (state.clients.items[i].last_ping != 0 && state.clients.items[i].account_id == acct->id) { - outgoing_message.address = state.clients.items[i].address; + if (state.clients.items[i].active && state.clients.items[i].account_id == acct->id) { + outgoing_message.address = state.clients.items[i].address; + outgoing_message.socket_fd = state.clients.items[i].socket_fd; outgoing_message.bytes_len = 2; outgoing_message.bytes[0] = (u8)MessageJobComplete; outgoing_message.bytes[1] = true; @@ -1201,8 +1192,9 @@ fn void* gameLoop(void* params) { addSystemMessage((u8*)sbuf); // send a job failed message? for (u32 i = 1; i < state.clients.length; i++) { - if (state.clients.items[i].last_ping != 0 && state.clients.items[i].account_id == acct->id) { - outgoing_message.address = state.clients.items[i].address; + if (state.clients.items[i].active && state.clients.items[i].account_id == acct->id) { + outgoing_message.address = state.clients.items[i].address; + outgoing_message.socket_fd = state.clients.items[i].socket_fd; outgoing_message.bytes_len = 2; outgoing_message.bytes[0] = (u8)MessageJobComplete; outgoing_message.bytes[1] = false; @@ -1456,9 +1448,9 @@ i32 main(i32 argc, ptr argv[]) { state.network_send_queue = newOutgoingMessageQueue(&permanent_arena); // alloc the global hashmap of rooms // init + alloc clients - state.clients.capacity = SERVER_MAX_CLIENTS; + state.clients.capacity = NET_SERVER_MAX_CLIENTS; state.clients.length = 1; // making entry 0 to be a "null" client - state.clients.items = (Client*)arenaAllocArray(&permanent_arena, Client, SERVER_MAX_CLIENTS); + state.clients.items = (Client*)arenaAllocArray(&permanent_arena, Client, NET_SERVER_MAX_CLIENTS); for (i32 i = 0; i < SYSTEM_MESSAGES_LEN; i++) { system_messages[i].capacity = MAX_SYSTEM_MESSAGE_LEN; system_messages[i].length = 0; @@ -1657,13 +1649,13 @@ i32 main(i32 argc, ptr argv[]) { } // 2. spin off sendNetworkUpdates() infinite loop thread - UDPServer listener = createUDPServer(SERVER_PORT); - if (!listener.ready) { - exitWithErrorMessage("Couldn't start the udp server"); + TCPServer server = createTCPServer(SERVER_PORT); + if (!server.ready) { + exitWithErrorMessage("Couldn't start the server"); } - Thread send_thread = spawnThread(&sendNetworkUpdates, &listener.server_socket); + Thread send_thread = spawnThread(&sendNetworkUpdates, &server); // 3. infinitely wait for incoming UDP messages and process them (usually by just dropping user-commands into the relevant block of shared memory) - Thread recv_thread = spawnThread(&receiveNetworkUpdates, &listener); + Thread recv_thread = spawnThread(&receiveNetworkUpdates, &server); u64 lane_broadcast_val = 0; Barrier barrier = osBarrierAlloc(GAME_THREAD_CONCURRENCY); diff --git a/src/shared.h b/src/shared.h index c089081..34b35f7 100644 --- a/src/shared.h +++ b/src/shared.h @@ -18,6 +18,7 @@ #define AUCTION_COMMODITY_CUTOFF (7) #define MAX_SCREEN_HEIGHT 300 #define MAX_SCREEN_WIDTH 800 +#define SERVER_PORT 7777 #define AUCTION_PRICE_START_MULTIPLE (12) @@ -366,7 +367,6 @@ typedef enum Direction { typedef enum CommandType { CommandInvalid, - CommandKeepAlive, CommandLogin, CommandCreateCharacter, CommandTransact, @@ -380,7 +380,6 @@ typedef enum CommandType { static const char* command_type_strings[CommandType_Count] = { "Invalid", - "KeepAlive", "Login", "CreateCharacter", "ReadyStatus",