/* * Math problem server * Client can send: * types: add = 1, multiply = 2 * Format for either type: * type (1 byte) * operand 1 (8-byte floating point, C double) * operand 2 (8-byte floating point, C double) * Total size: 17 bytes * * Server can send: * types: add_result = 20, multiple_result = 21 * Format for either type: * type (1 byte) * operand 1 (8-byte floating point, C double) * operand 2 (8-byte floating point, C double) * result (8-byte floating point, C double) * Total size: 25 bytes */ #include #include #include #include #include #include #include #include #include #include #include #include "math_lib.h" using namespace std; int skt; /* This was getting messy! */ /* vector client_fds; vector client_mutexes; vector threads; */ /* Unifying all client information in one class like this will * prevent us from needing three vectors. We're ok with one now. * The client includes its own thread object, so everything * for one client is here. We have to remember to lock the client's * mutex before sending to them. */ class client { public: char buffer[1024]; struct client_message cm; int fd; string address; mutex write_mutex; thread t; // needs to return right away if we don't have input void check_for_input(); void process_input(); client(int our_fd, string a) : fd(our_fd), address(a) {} }; vector clients; /* This will be used when we can't go changing around the list of clients */ mutex clients_mutex; void cleanup_clients(); void client::check_for_input(){ static size_t sofar = 0; ssize_t readlen = recv(fd, (char*)(&cm) + sofar, sizeof(cm) - sofar, MSG_DONTWAIT); if(readlen == -1){ printf("Client on %d had nothing to say\n", fd); return; } sofar += readlen; printf("Received %d bytes so far for client on %d\n", sofar, fd); if(sofar == 17){ process_input(); sofar = 0; } } void client::process_input() { // This is where handle_client goes ssize_t len; struct server_message sm; sm.operand_1 = cm.operand_1; sm.operand_2 = cm.operand_2; if(cm.type == CLIENT_ADD){ sm.type = SERVER_ADD; sm.result = cm.operand_1 + cm.operand_2; } else if(cm.type == CLIENT_MULTIPLY){ sm.type = SERVER_MULTIPLY; sm.result = cm.operand_1 * cm.operand_2; } else { printf("Client speaks nonsense type %d\n", cm.type); } /* Now we just loop through all the clients */ for(client* c : clients){ if(c->fd == -1) continue; /* The two-part write is just to expose the race condition */ write(c->fd, &sm, 10); write(c->fd, ((char*)&sm) + 10, sizeof(sm) - 10); /* Remember to unlock! (very important!) */ } // printf("Done with client %d(from address %s)\n", fd, address.c_str()); // it'd be cool if we actually freed up this place in client_fds // close(fd); // fd = -1; /* This is actually kind of a problem! */ // cleanup_clients(); // would join us from inside ourselves } // This function will only run once at a time! // It receives for ALL clients, not just one void receive_thread(){ for(;;){ printf("Checking for input from %d clients\n", clients.size()); for(auto c : clients){ c->check_for_input(); } sleep(1); } } void cleanup_clients(){ for(int i = 0; i < clients.size(); i++){ if(clients[i]->fd == -1){ // delete that client clients[i]->t.join(); delete clients[i]; clients[i] = clients[clients.size() - 1]; clients.pop_back(); } } } void shut_down_server(int signal){ printf("\nDoing everything we need to do to shut down the server\n"); close(skt); exit(0); } void handle_sigpipe(int signal){ printf("Received a sigpipe\n"); } int main(int argc, char ** argv){ uint16_t listen_port = 5141; // 5141 is the default if(argc > 1){ listen_port = atoi(argv[1]); if(listen_port < 1){ printf("Invalid port: %s\n", argv[1]); exit(1); } printf("Will listen on port %d\n", listen_port); } else { printf("No port specified, defaulting to %d\n", listen_port); } struct sigaction sa; sa.sa_handler = shut_down_server; sigaction(SIGINT, &sa, 0); struct sigaction sapipe; sapipe.sa_handler = handle_sigpipe; sigaction(SIGPIPE, &sapipe, 0); struct sockaddr_in sad; sad.sin_port = htons(listen_port); sad.sin_addr.s_addr = INADDR_ANY; sad.sin_family = AF_INET; 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; } int client_fd; struct sockaddr_in client_address; socklen_t address_size = sizeof(struct sockaddr_in); thread receiver(receive_thread); while(1){ client_fd = accept4(skt, (struct sockaddr *)(&client_address), &address_size, SOCK_NONBLOCK); if(client_fd == -1){ printf("There wasn't a client right now\n"); sleep(1); continue; } printf("Connection made from address %s\n", inet_ntoa(client_address.sin_addr)); write(client_fd, "Good Morning", 13); /* On this next bit: clients is a list of references to clients, not actual clients. * We could keep a vector of actual clients, but it would make things odd. See, if we * did this: * clients.push_back(client(client_fd, inet_ntoa(client_address.sin_addr))); * We'd actually be making TWO clients! We'd make one, than make a copy of it that * goes in the vector. They each have a thread, so I don't think it's a good idea. There * is a way to move these, and we could do that, but the list of references helps me keep * my head screwed on straight. Not everyone does it this way. */ clients.push_back(new client(client_fd, inet_ntoa(client_address.sin_addr))); printf("Currently we have %lu clients\n", clients.size()); cleanup_clients(); } return 0; }