/* * c++ version of multithreaded_server.c * Comments here mostly give a comparison with the C version * See that one for comments on the network code, etc. */ #include #include #include #include #include #include #include #include #include #include using namespace std; int skt; /* Unlike the way I did the C version, we can keep adding clients until the * server finally runs out of memory (that'll take a really long time)! * The threads vector will hold a handle for each thread. We have to keep * these in C++, because if we let them go out of scope, the threads will be * stopped. threads could be local to main instead of global if we prefer. * * We're still not cleaning out the invalid file descriptors. So we should * probably have some kind of cleanup routine for those. The other thing we * lack is a write mutex. If two clients send at the same time, it's possible * we'll end up relaying both messages to all clients at the same time, which * could mean calling write() on the same file descriptor twice at the same * time. That causes a race condition. More on mutexes and multithreading * issues sometime in the future - for now, we're just sweeping the issue * under the rug. */ vector client_fds; vector threads; 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"); } void handle_client(size_t cfd_index){ int client_fd = client_fds[cfd_index]; write(client_fd, "Good Morning\n", 14); char buffer[1024]; ssize_t len; for(;;){ len = read(client_fd, buffer, 1024); if(len < 1) break; // write to everybody, not just ourselves! // for each client which is connected: // write to that client for(int i = 0; i < client_fds.size(); i++) if(client_fds[i] != -1) if(write(client_fds[i], buffer, len) < 1) client_fds[i] = -1; // Same system as the C version if(len < 1) break; } printf("Done with client %d\n", cfd_index); // it'd be cool if we actually freed up this place in client_fds client_fds[cfd_index] = -1; close(client_fd); } 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)); /* Add to our vector. We never actually remove things from this vector, just mark them * as -1 when we don't like them anymore */ client_fds.push_back(client_fd); /* This does two things: * 1. thread(handle_client, client_fds.size() - 1) * This is like handle_client(client_fds.size() - 1), but will run it in a new thread * 2. threads.push_back will add the new thread to the vector "threads". This preserves * the thread object that thread() returns. If we let it go out of scope instead of * storing it in a vector, the thread will get stopped when we hit the closing brace * of the loop. */ threads.push_back(thread(handle_client, client_fds.size() - 1)); } return 0; }