/* * 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: int fd; string address; mutex write_mutex; thread t; void run(); /* This seems like a bit of a hack, but it solves a weird little problem. * We can't call run without an instance of a client. When we're in the * constructor's initialization list, we don't have an instance of the class, * but we also want to initialize t and start it running. So, we have to give * t a function that actually exists, and run doesn't, because there's not yet * a client for it to be associated with. A static method is just a member * of class client, and doesn't require any particular instance of class client. * So t is happy to accept it as a function it can run. But we still need a * reference to the class we're making. "this" is a self-reference, a reference * to the specific instance of the client class we're creating. So if we pass * it to start_own_thread, we can call the "run" method of the newly-created * class. This happens AFTER the initialization list is executed, so the class * does actually exist when us->run() is called. */ static void start_own_thread(client* us){ us->run(); } client(int our_fd, string a) : fd(our_fd), t(start_own_thread, this), 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::run() { // This is where handle_client goes write(fd, "Good Morning\n", 14); char buffer[1024]; ssize_t len; struct client_message cm; for(;;){ if(sizeof(cm) != recv(fd, &cm, sizeof(cm), MSG_WAITALL)) break; 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); break; } clients_mutex.lock(); /* Now we just loop through all the clients */ for(client* c : clients){ if(c->fd == -1) continue; c->write_mutex.lock(); /* The two-part write is just to expose the race condition */ write(c->fd, &sm, 10); sleep(1); write(c->fd, ((char*)&sm) + 10, sizeof(sm) - 10); /* Remember to unlock! (very important!) */ c->write_mutex.unlock(); } clients_mutex.unlock(); } 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 } void cleanup_clients(){ clients_mutex.lock(); 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(); } } clients_mutex.unlock(); } 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); while(1){ 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)); /* 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_mutex.lock(); clients.push_back(new client(client_fd, inet_ntoa(client_address.sin_addr))); clients_mutex.unlock(); printf("Currently we have %lu clients\n", clients.size()); cleanup_clients(); } return 0; }