// Client will be nc (netcat) // We only receive text that should be relayed to everybody else // The first thing the client says will be their name from then forward! #include #include #include #include #include #include #include #include #include #include #include using namespace std; #define BUFSIZE 1024 struct client { char name[32]; int fd; client(char *n, int f) : fd(f) { strcpy(name, n); } }; vector clients; struct game { uint8_t type; uint16_t initial_points, stat_limit, description_length; const char *description; } __attribute__((packed)); struct game our_game = {11, 0, 0, 0, "This is actually a chat server, not a real game. Send a character to set your name. To message everybody on the server, send a message and leave the recipient blank"}; uint8_t not_named_error[] = " Must send a character before sending messages. You can't talk unless you have a name"; struct error { uint8_t type = 7; uint8_t errorcode = 0; uint16_t desclen; char error_message[46] = "This chat server only supports types 10 and 1"; error() { desclen = strlen(error_message); } } __attribute__((packed)); struct error unsupported_type; void init(){ our_game.description_length = strlen(our_game.description); not_named_error[0] = 7; not_named_error[1] = 0; // One way (difficult to expand since you have to think about byte order yourself not_named_error[2] = strlen((char*)not_named_error + 4); not_named_error[3] = 0; // Another way, but you have to do some casting *((uint16_t*)(not_named_error + 2)) = strlen((char*)not_named_error + 4); } void client_thread(int client_fd){ char message_buffer[1 + 2 + 32 + 32 + 32768]; uint16_t* message_length = (uint16_t*)(message_buffer + 1); char* recipient = message_buffer + 3; char* sender = message_buffer + 35; char* message = message_buffer + 67; ssize_t readlen; uint8_t type; char name[32] = "unnamed client"; char buffer[1024]; /* Send a game that explains what this server is * Receive a character so we know what the client's name is * Question: Should we let the client change their name? * Enter a loop, where we receive messages (type 1), and relay them */ printf("Starting thread for client_fd %d\n", client_fd); /* Send a game message */ if(write(client_fd, &our_game, 7) != 7) goto exit_and_close; if(write(client_fd, our_game.description, our_game.description_length) != our_game.description_length) goto exit_and_close; if(read(client_fd, &type, 1) != 1) goto exit_and_close; if(type == 10){ if(recv(client_fd, name, 32, MSG_WAITALL) != 32) goto exit_and_close; if(recv(client_fd, buffer, 15, MSG_WAITALL) != 15) goto exit_and_close; uint16_t desclen; memcpy(&desclen, buffer + 13, 2); if(recv(client_fd, buffer, (desclen < 1024)? desclen:1024, MSG_WAITALL) != desclen) goto exit_and_close; printf("Client %s described themselves as %s\n", name, buffer); } else if (type == 1){ if(write(client_fd, ¬_named_error, sizeof(not_named_error)) != sizeof(not_named_error)) goto exit_and_close; } else { if(write(client_fd, &unsupported_type, sizeof(unsupported_type)) != sizeof(unsupported_type)) goto exit_and_close; } printf("Client formerly known as fd %d is named %s\n", client_fd, name); clients.push_back(client(name, client_fd)); while(true){ if(read(client_fd, message_buffer, 1) != 1) goto exit_and_remove; if(message_buffer[0] != 1) { write(client_fd, &unsupported_type, sizeof(unsupported_type)); goto exit_and_remove; } // Note: If a second message is read at the same time, it will be lost! // To fix this, we'd have to see if we read more than the expected length readlen = read(client_fd, message_buffer + 1, sizeof(message_buffer) - 1); // Did we get both the message and the description? while(readlen < *message_length + 66) { printf("Message not fully received, waiting for more!\n"); ssize_t new_data = read(client_fd, message_buffer + 1 + readlen, sizeof(message_buffer) - 1 - readlen); if(new_data < 1) goto exit_and_remove; readlen += new_data; } if(readlen > *message_length + 66) printf("We read more than we expected - was there a second message we're ignoring?\n"); message[*message_length] = 0; if(!strlen(recipient)) { // send to everybody for(auto &c : clients) if(c.fd != client_fd) write(c.fd, message_buffer, 67 + *message_length); } else { for(auto &c : clients) if(!strcmp(c.name, recipient)) write(c.fd, message_buffer, 67 + *message_length); } } exit_and_remove: printf("Removing client named %s\n", name); for(int i = 0; i < clients.size(); i++){ if(clients[i].fd == client_fd){ // Then it's our entry clients[i] = clients[clients.size() - 1]; clients.pop_back(); } } exit_and_close: printf("Closing connection to client %s\n", name); close(client_fd); } int main(int argc, char ** argv){ struct sockaddr_in sad; sad.sin_port = htons(5143); sad.sin_addr.s_addr = INADDR_ANY; sad.sin_family = AF_INET; if(argc > 1){ sad.sin_port = htons(atoi(argv[1])); } printf("Listening on port %d\n", ntohs(sad.sin_port)); init(); int skt = socket(AF_INET, SOCK_STREAM, 0); // Step 1 if(skt == -1){ perror("socket"); return 1; } if( bind(skt, (struct sockaddr*)(&sad), sizeof(struct sockaddr_in)) ){ // step 2 perror("bind"); return 1; } if( listen(skt, 5) ){ // step 3 perror("listen"); return 1; } // vector threads; while(1){ // Thread cleanup routine /* for(auto &t : threads){ if(t is done){ remove t from the list of threads } } */ int client_fd; struct sockaddr_in client_address; socklen_t address_size = sizeof(struct sockaddr_in); client_fd = accept(skt, (struct sockaddr *)(&client_address), &address_size); // step 4 printf("Connection made from address %s\n", inet_ntoa(client_address.sin_addr)); thread new_thread(client_thread, client_fd); new_thread.detach(); // threads.push_back(thread(client_thread, client_fd)); } return 0; }