TCP servers with POSIX
The simplest form of a TCP server is one which accepts clients sequentially.
/* server_seq.c ------------ TCP server accepting clients sequentially. Compile with gcc -oserver_seq server_seq.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netdb.h> #include <arpa/inet.h> #include <netinet/in.h> int main() { const char* server_address_s = "127.0.0.1"; const int PORT = 7000; struct sockaddr_in server_address; server_address.sin_family = AF_INET; server_address.sin_port = htons(PORT); if (inet_pton(AF_INET, server_address_s, &(server_address.sin_addr)) <= 0) { fprintf(stderr, "main(): error calling 'inet_pton()', exiting program\n"); exit(EXIT_FAILURE); } memset(&(server_address.sin_zero), '\0', 8); int server_fd = socket(PF_INET, SOCK_STREAM, 0); if (server_fd == -1) { fprintf(stderr, "main(): cannot create socket, exiting program"); exit(EXIT_FAILURE); } if (bind(server_fd, (struct sockaddr*)&server_address, sizeof(server_address)) == -1) { fprintf(stderr, "main(): cannot bind the address, exiting program\n"); exit(EXIT_FAILURE); } const unsigned int PENDING_SIZE = 5; if (listen(server_fd, PENDING_SIZE) == -1) { fprintf(stderr, "main(): cannot listen the address, exiting program\n"); exit(EXIT_FAILURE); } while (1) { struct sockaddr_in client_address; socklen_t client_address_len = sizeof(client_address); int client_fd = accept(server_fd, (struct sockaddr*)&client_address, &client_address_len); if (client_fd == -1) { printf("main(): cannot accept client, sleeping...\n"); sleep(5); } char addr_buf[INET_ADDRSTRLEN]; const char* client_address_s = inet_ntop(AF_INET, (const void*)&client_address, addr_buf, INET_ADDRSTRLEN); if (client_address_s == NULL) printf("main(): cannot read client address\n"); else printf("main(): client accepted from %s\n", client_address_s); const int REQUEST_LEN = 1000; char request[REQUEST_LEN]; memset(request, '\0', REQUEST_LEN); if (recv(client_fd, request, REQUEST_LEN, 0) == -1) { printf("main(): cannot receive request\n"); close(client_fd); continue; } printf("main(): request=%s\n", request); if (send(client_fd, request, strlen(request), 0) == -1) { printf("main(): cannot send response\n"); close(client_fd); continue; } printf("main(): echo sent\n"); close(client_fd); } return EXIT_SUCCESS; }
Usually, server should accept multiple clients at the same time. For that purpose the function select() can be used. It traces which sockets are ready to read/write. For the server socket, ready to read means that it has new client to accept. For a client socket, that means that the request is ready to read. Such approach is called multiplexing.
/* server_mplex.c -------------- Single threaded TCP server which uses demultiplexing via 'select' function. Compile with gcc -oserver_mplex server_mplex.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netdb.h> #include <arpa/inet.h> #include <netinet/in.h> int main() { struct addrinfo hint; hint.ai_family = 0; hint.ai_socktype = SOCK_STREAM; hint.ai_flags = AI_PASSIVE; hint.ai_protocol = 0; hint.ai_addrlen = 0; hint.ai_canonname = NULL; hint.ai_addr = NULL; hint.ai_next = NULL; const char* server_address_s = "localhost"; const char* PORT = "7000"; struct addrinfo* server_address; if (getaddrinfo(server_address_s, PORT, &hint, &server_address) != 0) { fprintf(stderr, "main(): cannot locate host, exiting program\n"); exit(EXIT_FAILURE); } int server_fd = socket(server_address->ai_family, server_address->ai_socktype, server_address->ai_protocol); if (server_fd == -1) { fprintf(stderr, "main(): cannot create socket, exiting program\n"); exit(EXIT_FAILURE); } int yes = 1; setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)); if (bind(server_fd, server_address->ai_addr, server_address->ai_addrlen) == -1) { fprintf(stderr, "main(): cannot bind, exiting program\n"); exit(EXIT_FAILURE); } freeaddrinfo(server_address); const unsigned int PENDING_SIZE = 5; if (listen(server_fd, PENDING_SIZE) == -1) { fprintf(stderr, "main(): cannot listen, exiting program\n"); exit(EXIT_FAILURE); } fd_set read_fds; FD_ZERO(&read_fds); FD_SET(server_fd, &read_fds); int fd_last = server_fd; while (1) { fd_set read_fds_copy; FD_ZERO(&read_fds_copy); read_fds_copy = read_fds; if (select(fd_last + 1, &read_fds_copy, NULL, NULL, NULL) == -1) { fprintf(stderr, "main(): error calling 'select()', exiting program\n"); exit(EXIT_FAILURE); } int fd; for (fd = 0; fd <= fd_last; fd++) { if (FD_ISSET(fd, &read_fds_copy)) { if (fd == server_fd) { // new client accepted struct sockaddr_in client_address; socklen_t client_address_len = sizeof(client_address); int client_fd = accept(server_fd, (struct sockaddr*)&client_address, &client_address_len); if (client_fd == -1) { printf("main(): cannot accept client, sleeping\n"); sleep(1); continue; } char addr_buf[INET_ADDRSTRLEN]; const char* client_address_s = inet_ntop(AF_INET, (const void*)&client_address, addr_buf, INET_ADDRSTRLEN); if (client_address_s == NULL) printf("main(): cannot read client address\n"); else printf("main(): client accepted from %s\n", client_address_s); FD_SET(client_fd, &read_fds); if (client_fd > fd_last) fd_last = client_fd; } else { // client request const int REQUEST_LEN = 1000; char request[REQUEST_LEN]; int result; memset(request, '\0', REQUEST_LEN); result = recv(fd, request, REQUEST_LEN, 0); if (result <= 0) { // no request from client if (result == -1) fprintf(stderr, "main(): cannot receive request\n"); else if (result == 0) printf("main(): client with file descriptor %d closed connection\n", fd); close(fd); FD_CLR(fd, &read_fds); continue; } else { // echo client's request printf("main(): request=%s\n", request); result = send(fd, request, strlen(request), 0); if (result == -1) { fprintf(stderr, "main(): cannot send response\n"); close(fd); FD_CLR(fd, &read_fds); continue; } printf("main(): echo sent\n"); } } } } } return EXIT_SUCCESS; }
Server can process multiple requests by forking process for each connected client. One server instance deals with a client until the connection is closed. Then, the process exits. Each server process is independent of the other server instances. If one of them crashes, others continue to work. The problem is large number of connected clients, which can cause server to consume lot of resources.
/* server_forked.c --------------- Forked TCP server, each client is accepted by a separate process. Client gets request echo until 'quit' is typed. Compile with gcc -oserver_forked server_forked.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netdb.h> #include <sys/wait.h> #include <signal.h> #include <arpa/inet.h> #include <netinet/in.h> /** waits all children to finish @param sig_no not used @return none **/ void wait_children(int sig_no) { while (waitpid((pid_t)-1, NULL, WNOHANG) > 0) ; } /** processes one client @param client_fd client socket **/ void process(int client_fd) { const char* QUIT_MESSAGE = "quit"; const int REQUEST_LEN = 1000; char request[REQUEST_LEN]; memset((void*)request, '\0', REQUEST_LEN); while (strncmp(request, QUIT_MESSAGE, 4)) { memset((void*)request, '\0', REQUEST_LEN); int result = recv(client_fd, request, REQUEST_LEN, 0); if (result == -1) { fprintf(stderr, "process(): getpid()=%d, cannot receive request, ending chat\n", getpid()); break; } else if (result == 0) { printf("process(): getpid()=%d, client quitted, ending chat\n", getpid()); break; } printf("process(): getpid()=%d, request=%s\n", getpid(), request); if (send(client_fd, request, strlen(request), 0) == -1) { fprintf(stderr, "process(): getpid()=%d, cannot send response, ending chat\n", getpid()); break; } printf("process(): getpid()=%d, echo sent\n", getpid()); } close(client_fd); } int main() { struct addrinfo hint; hint.ai_family = 0; hint.ai_socktype = SOCK_STREAM; hint.ai_flags = AI_PASSIVE; hint.ai_protocol = 0; hint.ai_addrlen = 0; hint.ai_canonname = NULL; hint.ai_addr = NULL; hint.ai_next = NULL; const char* server_address_s = "localhost"; const char* PORT = "7000"; struct addrinfo* server_address; if (getaddrinfo(server_address_s, PORT, &hint, &server_address) != 0) { fprintf(stderr, "main(): cannot locate host, exiting program\n"); exit(EXIT_FAILURE); } int server_fd = socket(server_address->ai_family, server_address->ai_socktype, server_address->ai_protocol); if (server_fd == -1) { fprintf(stderr, "main(): cannot create socket, exiting program\n"); exit(EXIT_FAILURE); } if (bind(server_fd, server_address->ai_addr, server_address->ai_addrlen) == -1) { fprintf(stderr, "main(): cannot bind, exiting program\n"); exit(EXIT_FAILURE); } freeaddrinfo(server_address); const unsigned int PENDING_SIZE = 5; if (listen(server_fd, PENDING_SIZE) == -1) { fprintf(stderr, "main(): cannot listen, exiting program\n"); exit(EXIT_FAILURE); } struct sigaction signal_action; signal_action.sa_handler = wait_children; sigemptyset(&signal_action.sa_mask); signal_action.sa_flags = SA_RESTART; if (sigaction(SIGCHLD, &signal_action, NULL) == -1) { fprintf(stderr, "main(): 'sigaction()' failed, exiting program\n"); exit(EXIT_FAILURE); } while (1) { struct sockaddr_in client_address; socklen_t client_address_len = sizeof(client_address); int client_fd = accept(server_fd, (struct sockaddr*)&client_address, &client_address_len); if (client_fd == -1) { printf("main(): cannot accept client, sleeping\n"); sleep(5); } char addr_buf[INET_ADDRSTRLEN]; const char* client_address_s = inet_ntop(AF_INET, (const void*)&client_address, addr_buf, INET_ADDRSTRLEN); if (client_address_s == NULL) printf("main(): cannot read client address\n"); else printf("main(): client accepted from %s\n", client_address_s); //printf("main():client accepted from:%s\n", inet_ntop(AF_INET, &client_address, addr_buf, INET_ADDRSTRLEN)); // fork child process for a new client int pid = fork(); if (pid == 0) // child { process(client_fd); close(client_fd); printf("main(): ending child\n"); exit(EXIT_SUCCESS); } else if (pid < 0) { fprintf(stderr, "main(): cannot fork, sleeping\n"); sleep(5); } close(client_fd); } return EXIT_SUCCESS; }
Server can process multiple requests by creating thread for each connected client. One thread deals with a client until the connection is closed. Then, the thread is destroyed. This approach consumes less machine resources, but it is potentially less stable then the forked version.
/* server_threaded.c ----------------- Threaded TCP server, each client is accepted by a separate thread. Number of threads is limited. Client talks with server and gets echo of the request, until 'quit' is typed. Compile with gcc -lpthread -oserver_threaded server_threaded.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netdb.h> #include <pthread.h> #include <arpa/inet.h> #include <netinet/in.h> static int threads_no = 0; const int THREADS_MAX = 5; // protects 'threads_no' pthread_mutex_t threads_no_mutex = PTHREAD_MUTEX_INITIALIZER; /** processes one client @param client_fd client socket @return NULL **/ void* process(void* client_fd) { int* client = (int*)client_fd; const char* QUIT_MESSAGE = "quit"; const int REQUEST_LEN = 1000; char request[REQUEST_LEN]; memset((void*)request, '\0', REQUEST_LEN); while (strncmp(request, QUIT_MESSAGE, strlen(QUIT_MESSAGE))) { memset((void*)request, '\0', REQUEST_LEN); int result = recv(*client, request, REQUEST_LEN, 0); if (result == -1) { fprintf(stderr, "process(): thread id=%ld, cannot receive request\n", (int)pthread_self()); continue; } else if (result == 0) { fprintf(stderr, "process(): thread id=%ld, client with file descriptor %d closed connection\n", (int)pthread_self(), client_fd); break; } printf("process(): thread id=%ld, request=%s", (int)pthread_self(), request); result = send(*client, request, strlen(request), 0); if (result == -1) { fprintf(stderr, "process(): thread id=%ld, cannot send response\n", (int)pthread_self()); break; } printf("process(): thread id=%ld, echo sent\n", (int)pthread_self()); } if (close(*client) == -1) fprintf(stderr, "process(): thread id=%ld, cannot close client socket\n", (int)pthread_self()); // thread frees client file descriptor memory free(client); // decrease number of threads int status = pthread_mutex_lock(&threads_no_mutex); if (status != 0) { fprintf(stderr, "process(): cannot lock 'threads_no_mutex', exiting program\n"); exit(EXIT_FAILURE); } threads_no--; status = pthread_mutex_unlock(&threads_no_mutex); if (status != 0) { fprintf(stderr, "process(): cannot unlock 'threads_no_mutex', exiting program\n"); exit(EXIT_FAILURE); } return NULL; } int main() { // bind server to an address found by 'getaddrinfo()' struct addrinfo hint; hint.ai_family = 0; hint.ai_socktype = SOCK_STREAM; hint.ai_flags = AI_PASSIVE; hint.ai_protocol = 0; hint.ai_addrlen = 0; hint.ai_canonname = NULL; hint.ai_addr = NULL; hint.ai_next = NULL; const char* server_address_s = "localhost"; const char* PORT = "7000"; struct addrinfo* server_address; if (getaddrinfo(server_address_s, PORT, &hint, &server_address) != 0) { fprintf(stderr, "main(): cannot locate host, exiting program\n"); exit(EXIT_FAILURE); } int server_fd = socket(server_address->ai_family, server_address->ai_socktype, server_address->ai_protocol); if (server_fd == -1) { fprintf(stderr, "main(): cannot create socket, exiting program\n"); exit(EXIT_FAILURE); } if (bind(server_fd, server_address->ai_addr, server_address->ai_addrlen) == -1) { fprintf(stderr, "main(): cannot bind, exiting program\n"); exit(EXIT_FAILURE); } freeaddrinfo(server_address); const unsigned int PENDING_SIZE = 5; if (listen(server_fd, PENDING_SIZE) == -1) { fprintf(stderr, "main(): cannot listen, exiting program\n"); exit(EXIT_FAILURE); } // server loop while (1) { struct sockaddr_in client_address; socklen_t client_address_len = sizeof(client_address); int client_fd = accept(server_fd, (struct sockaddr*)&client_address, &client_address_len); if (client_fd == -1) { printf("main(): cannot accept client, sleeping\n"); sleep(5); } char addr_buf[INET_ADDRSTRLEN]; const char* client_address_s = inet_ntop(AF_INET, (const void*)&client_address, addr_buf, INET_ADDRSTRLEN); if (client_address_s == NULL) printf("main(): cannot read client address\n"); else printf("main(): client accepted from %s\n", client_address_s); //printf("main():client accepted from:%s\n", inet_ntop(AF_INET, &client_address, addr_buf, INET_ADDRSTRLEN)); int status = pthread_mutex_lock(&threads_no_mutex); if (status != 0) { fprintf(stderr, "main(): cannot lock 'threads_no_mutex', exiting program\n"); exit(EXIT_FAILURE); } if (threads_no < THREADS_MAX) { threads_no++; printf("main(): total clients connected=%d\n", threads_no); status = pthread_mutex_unlock(&threads_no_mutex); if (status != 0) { fprintf(stderr, "main(): cannot unlock 'threads_no_mutex', exiting program\n"); exit(EXIT_FAILURE); } // copy client file descriptor to give it to a thread // thread itself will be responsible for freeing memory of the client file descriptor int* client_fd_copy = (int*)malloc(sizeof(client_fd)); if (client_fd_copy == NULL) { fprintf(stderr, "main(): cannot allocate memory, exiting program\n"); exit(EXIT_FAILURE); } *client_fd_copy = client_fd; pthread_attr_t thread_attr; status = pthread_attr_init(&thread_attr); if (status != 0) { fprintf(stderr, "main(): cannot initialize thread attribute, exiting program\n"); exit(EXIT_FAILURE); } status = pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED); if (status != 0) { fprintf(stderr, "main(): cannot detach attribute, exiting program\n"); exit(EXIT_FAILURE); } pthread_t thread; status = pthread_create(&thread, &thread_attr, &process, (void*)client_fd_copy); if (status != 0) { fprintf(stderr, "main(): cannot create thread, exiting program\n"); exit(EXIT_FAILURE); } status = pthread_attr_destroy(&thread_attr); if (status != 0) fprintf(stderr, "main(): cannot destroy thread attribute\n"); } else // too many threads { status = pthread_mutex_unlock(&threads_no_mutex); if (status != 0) { fprintf(stderr, "main(): cannot unlock 'threads_no_mutex', exiting program\n"); exit(EXIT_FAILURE); } printf("main(): too many clients, refusing this one\n"); close(client_fd); } } return EXIT_SUCCESS; }
Instead of forking process for each client at connection time, fixed number of processes can be forked at the server startup. When a client connects, the process is already forked and ready to deal with the client. Of course, number of forked processes determines maximum number of clients to connect.
/* server_preforked.c --------------- Preforked TCP server, each client is accepted by a separate process. Client gets request echo until 'quit' is typed. Compile with gcc -oserver_preforked server_preforked.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netdb.h> #include <sys/wait.h> #include <signal.h> #include <arpa/inet.h> #include <netinet/in.h> /** waits for any child to finish @param sig_no not used **/ void wait_children(int sig_no) { while (waitpid((pid_t)-1, NULL, WNOHANG) > 0) ; } /** processes one client @param client_fd client socket **/ void process(int client_fd) { const char* QUIT_MESSAGE = "quit"; const int REQUEST_LEN = 1000; char request[REQUEST_LEN]; memset((void*)request, '\0', REQUEST_LEN); while (strncmp(request, QUIT_MESSAGE, 4)) { memset((void*)request, '\0', REQUEST_LEN); int result = recv(client_fd, request, REQUEST_LEN, 0); if (result == -1) { fprintf(stderr, "process(): getpid()=%d, cannot receive request, ending chat\n", getpid()); break; } else if (result == 0) { printf("process(): getpid()=%d, client quitted, ending chat\n", getpid()); break; } printf("process(): getpid()=%d, request=%s\n", getpid(), request); if (send(client_fd, request, strlen(request), 0) == -1) { fprintf(stderr, "process(): getpid()=%d, cannot send response, ending chat\n", getpid()); break; } printf("process(): getpid()=%d, echo sent\n", getpid()); } } /** executed in a separate process, accepts new clients @param server_fd server socket **/ void child(int server_fd) { while (1) { struct sockaddr_in client_address; socklen_t client_address_len = sizeof(client_address); int client_fd = accept(server_fd, (struct sockaddr*)&client_address, &client_address_len); if (client_fd == -1) { fprintf(stderr, "child(): getpid()=%d, cannot accept client, sleeping\n", getpid()); sleep(5); } char addr_buf[INET_ADDRSTRLEN]; const char* client_address_s = inet_ntop(AF_INET, (const void*)&client_address, addr_buf, INET_ADDRSTRLEN); if (client_address_s == NULL) printf("child(): cannot read client address\n"); else printf("child(): client accepted from %s\n", client_address_s); //printf("child():getpid()=%d,client accepted from:%s\n", getpid(), inet_ntop(AF_INET, &client_address, addr_buf, INET_ADDRSTRLEN)); process(client_fd); printf("child(): getpid()=%d, ending child\n", getpid()); close(client_fd); } } int main() { struct addrinfo hint; hint.ai_family = 0; hint.ai_socktype = SOCK_STREAM; hint.ai_flags = AI_PASSIVE; hint.ai_protocol = 0; hint.ai_addrlen = 0; hint.ai_canonname = NULL; hint.ai_addr = NULL; hint.ai_next = NULL; const char* server_address_s = "localhost"; const char* PORT = "7000"; struct addrinfo* server_address; if (getaddrinfo(server_address_s, PORT, &hint, &server_address) != 0) { fprintf(stderr, "main(): cannot locate host, exiting program\n"); exit(EXIT_FAILURE); } int server_fd = socket(server_address->ai_family, server_address->ai_socktype, server_address->ai_protocol); if (server_fd == -1) { fprintf(stderr, "main(): cannot create socket, exiting program\n"); exit(EXIT_FAILURE); } if (bind(server_fd, server_address->ai_addr, server_address->ai_addrlen) == -1) { fprintf(stderr, "main():cannot bind, exiting program\n"); exit(EXIT_FAILURE); } freeaddrinfo(server_address); const unsigned int PENDING_SIZE = 5; if (listen(server_fd, PENDING_SIZE) == -1) { fprintf(stderr, "main(): cannot listen, exiting program\n"); exit(EXIT_FAILURE); } struct sigaction signal_action; signal_action.sa_handler = wait_children; sigemptyset(&signal_action.sa_mask); signal_action.sa_flags = SA_RESTART; if (sigaction(SIGCHLD, &signal_action, NULL) == -1) { fprintf(stderr, "main(): 'sigaction()' failed, exiting program\n"); exit(EXIT_FAILURE); } // create fixed number of child processes const unsigned int MAX_PROCESSES = 3; int i; for (i = 1; i <= MAX_PROCESSES; i++) { int pid = fork(); if (pid == 0) // child { child(server_fd); } else if (pid < 0) { fprintf(stderr, "main(): cannot fork, sleeping\n"); sleep(5); } } wait(NULL); return EXIT_SUCCESS; }
Similarly, instead of creating thread for each client at connection time, fixed number of threads can be created at the server startup.
/* server_prethreaded.c -------------------- Prethreaded TCP server, each client is accepted by a separate thread. Client talks with server and gets echo of the request, until 'quit' is typed. Compile with gcc -lpthread -oserver_prethreaded server_prethreaded.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netdb.h> #include <pthread.h> #include <arpa/inet.h> #include <netinet/in.h> /** processes one client @param client_fd client socket **/ void process(int client_fd) { const char* QUIT_MESSAGE = "quit"; const int REQUEST_LEN = 1000; char request[REQUEST_LEN]; memset(request, '\0', REQUEST_LEN); while (strncmp(request, QUIT_MESSAGE, strlen(QUIT_MESSAGE))) { memset(request, '\0', REQUEST_LEN); int result = recv(client_fd, request, REQUEST_LEN, 0); if (result == -1) { fprintf(stderr, "process(): thread number=%d, cannot receive request\n", (int)pthread_self()); continue; } else if (result == 0) { fprintf(stderr, "process(): thread number=%d, client with file descriptor %d closed connection\n", (int)pthread_self(), client_fd); break; } printf("process(): thread number=%d, request=%s", (int)pthread_self(), request); result = send(client_fd, request, strlen(request), 0); if (result == -1) { // bind server to an address found by 'getaddrinfo()' fprintf(stderr, "process(): thread number=%d, cannot send response\n", (int)pthread_self()); break; } printf("process(): thread number=%d, echo sent\n", (int)pthread_self()); } } /** thread function @param server_fd server socket @return NULL **/ void* thread_f(void* server_fd) { int* server = (int*)server_fd; while (1) { struct sockaddr_in client_address; socklen_t client_address_len = sizeof(client_address); int client_fd = accept(*server, (struct sockaddr*)&client_address, &client_address_len); if (client_fd == -1) { printf("thread_f(): cannot accept client, sleeping\n"); sleep(5); } char addr_buf[INET_ADDRSTRLEN]; const char* client_address_s = inet_ntop(AF_INET, (const void*)&client_address, addr_buf, INET_ADDRSTRLEN); if (client_address_s == NULL) printf("thread_f(): cannot read client address\n"); else printf("thread_f(): client accepted from %s\n", client_address_s); //printf("thread_f():client accepted from:%s\n", inet_ntop(AF_INET, &client_address, addr_buf, INET_ADDRSTRLEN)); process(client_fd); if (close(client_fd) == -1) fprintf(stderr, "thread_f(): cannot close client socket\n"); } return NULL; } int main() { struct addrinfo hint; hint.ai_family = 0; hint.ai_socktype = SOCK_STREAM; hint.ai_flags = AI_PASSIVE; hint.ai_protocol = 0; hint.ai_addrlen = 0; hint.ai_canonname = NULL; hint.ai_addr = NULL; hint.ai_next = NULL; const char* server_address_s = "localhost"; const char* PORT = "7000"; struct addrinfo* server_address; if (getaddrinfo(server_address_s, PORT, &hint, &server_address) != 0) { fprintf(stderr, "main():cannot locate host,exiting program...\n"); exit(EXIT_FAILURE); } int server_fd = socket(server_address->ai_family, server_address->ai_socktype, server_address->ai_protocol); if (server_fd == -1) { fprintf(stderr, "main():cannot create socket,exiting program...\n"); exit(EXIT_FAILURE); } if (bind(server_fd, server_address->ai_addr, server_address->ai_addrlen) == -1) { fprintf(stderr, "main():cannot bind,exiting program...\n"); exit(EXIT_FAILURE); } freeaddrinfo(server_address); const unsigned int PENDING_SIZE = 5; if (listen(server_fd, PENDING_SIZE) == -1) { fprintf(stderr, "main():cannot listen,exiting program...\n"); exit(EXIT_FAILURE); } // create fixed number of threads const unsigned int MAX_THREADS = 3; int i; for (i = 1; i <= MAX_THREADS; i++) { pthread_t thread_id; int ret_status; ret_status = pthread_create(&thread_id, NULL, &thread_f, (void*)&server_fd); if (ret_status != 0) { fprintf(stderr, "main():cannot create thread,exiting program...\n"); exit(EXIT_FAILURE); } } while (1) ; return EXIT_SUCCESS; }
The two approaches of preforking/prethreading can be combined, so server forks several processes at the startup, and each of them creates fixed number of threads. Stability of independent processes is combined with efficiency of threads.
/* server_preforkthr.c ------------------- Preforked and prethreaded TCP server. Several processes are created, each of them creates several threads which itself accept clients. Client gets request echo until 'quit' is typed. Compile with gcc -lpthread -oserver_preforkthr server_preforkthr.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netdb.h> #include <sys/wait.h> #include <signal.h> #include <arpa/inet.h> #include <netinet/in.h> /** waits for any childr to finish @param sig_no not used @return none **/ void wait_children(int sig_no) { while (waitpid((pid_t)-1, NULL, WNOHANG) > 0) ; } /** processes one client @param client_fd client socket @return none **/ void process(int client_fd) { const char* QUIT_MESSAGE = "quit"; const int REQUEST_LEN = 1000; char request[REQUEST_LEN]; memset((void*)request, '\0', REQUEST_LEN); while (strncmp(request, QUIT_MESSAGE, 4)) { memset((void*)request, '\0', REQUEST_LEN); int result = recv(client_fd, request, REQUEST_LEN, 0); if (result == -1) { fprintf(stderr, "process(): getpid()=%d, pthread_self()=%ld, cannot receive request, ending chat\n", getpid(), pthread_self()); break; } else if (result == 0) { printf("process(): getpid()=%d, pthread_self()=%ld, client quitted, ending chat\n", getpid(), pthread_self()); break; } printf("process(): getpid()=%d, pthread_self()=%ld, request=%s\n", getpid(), pthread_self(), request); if (send(client_fd, request, strlen(request), 0) == -1) { fprintf(stderr, "process(): getpid()=%d, pthread_self()=%ld, cannot send response, ending chat\n", getpid(), pthread_self()); break; } printf("process(): getpid()=%d, pthread_self()=%d, echo sent\n", getpid(), pthread_self()); } } /** thread function @param server_fd server socket @return NULL **/ void* thread_f(void* server_fd) { int* server = (int*)server_fd; while (1) { struct sockaddr_in client_address; socklen_t client_address_len = sizeof(client_address); int client_fd = accept(*server, (struct sockaddr*)&client_address, &client_address_len); if (client_fd == -1) { fprintf(stderr, "thread_f(): getpid()=%d, pthread_self()=%ld, cannot accept client, sleeping\n", getpid(), (int)pthread_self()); sleep(5); } char addr_buf[INET_ADDRSTRLEN]; const char* client_address_s = inet_ntop(AF_INET, (const void*)&client_address, addr_buf, INET_ADDRSTRLEN); if (client_address_s == NULL) printf("thread_f(): getpid()=%d, pthread_self()=%ld, cannot read client address\n", getpid(), (int)pthread_self()); else printf("thread_f(): getpid()=%d, pthread_self()=%ld, client accepted from %s\n", getpid(), (int)pthread_self(), client_address_s); //printf("thread_f(): getpid()=%d, pthread_self()=%ld, client accepted from: %s\n", getpid(), (int)pthread_self(), // inet_ntop(AF_INET, &client_address, addr_buf, INET_ADDRSTRLEN)); process(client_fd); if (close(client_fd) == -1) fprintf(stderr, "thread_f(): getpid()=%d, pthread_self()=%ld, cannot close client socket\n", getpid(), (int)pthread_self()); } return NULL; } /** creates several threads @param server_fd server socket **/ void child(int server_fd) { // create fixed number of threads const unsigned int MAX_THREADS = 2; int i; for (i = 1; i <= MAX_THREADS; i++) { pthread_t thread_id; int status = pthread_create(&thread_id, NULL, &thread_f, (void*)&server_fd); if (status != 0) { fprintf(stderr, "child(): cannot create thread, exiting program\n"); exit(EXIT_FAILURE); } } // instead of joining on threads while (1) ; } int main() { struct addrinfo hint; hint.ai_family = 0; hint.ai_socktype = SOCK_STREAM; hint.ai_flags = AI_PASSIVE; hint.ai_protocol = 0; hint.ai_addrlen = 0; hint.ai_canonname = NULL; hint.ai_addr = NULL; hint.ai_next = NULL; const char* server_address_s = "localhost"; const char* PORT = "7000"; struct addrinfo* server_address; if (getaddrinfo(server_address_s, PORT, &hint, &server_address) != 0) { fprintf(stderr, "main(): cannot locate host, exiting program\n"); exit(EXIT_FAILURE); } int server_fd = socket(server_address->ai_family, server_address->ai_socktype, server_address->ai_protocol); if (server_fd == -1) { fprintf(stderr, "main(): cannot create socket, exiting program\n"); exit(EXIT_FAILURE); } if (bind(server_fd, server_address->ai_addr, server_address->ai_addrlen) == -1) { fprintf(stderr, "main(): cannot bind, exiting program\n"); exit(EXIT_FAILURE); } freeaddrinfo(server_address); const unsigned int PENDING_SIZE = 5; if (listen(server_fd, PENDING_SIZE) == -1) { fprintf(stderr, "main(): cannot listen, exiting program\n"); exit(EXIT_FAILURE); } struct sigaction signal_action; signal_action.sa_handler = wait_children; sigemptyset(&signal_action.sa_mask); signal_action.sa_flags = SA_RESTART; if (sigaction(SIGCHLD, &signal_action, NULL) == -1) { fprintf(stderr, "main(): 'sigaction()' failed, exiting program\n"); exit(EXIT_FAILURE); } // create fixed number of child processes const unsigned int MAX_PROCESSES = 2; int i; for (i = 1; i <= MAX_PROCESSES; i++) { int pid; pid = fork(); if (pid == 0) // child { child(server_fd); } else if (pid < 0) { fprintf(stderr, "main(): cannot fork, sleeping\n"); sleep(5); } } wait(NULL); return EXIT_SUCCESS; }
Multiplexing enable to accept multiple clients at once. Threading enables processing several requests at the same time. So, prethreaded server can use multiplexing to accept and process several clients at the same time. Each thread accepts multiple clients and processes requests one at the time. Thus, multiple clients are accepted and several requests are processed.
/* server_prethrmplex.c -------------------- Prethreaded TCP server with multiplexing. Several threads are created, each of them multiplexes set of file descriptors. Client gets echo of the request until it closes the connection. Compile with gcc -lpthread -oserver_prethrmplex server_prethrmplex.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netdb.h> #include <pthread.h> #include <arpa/inet.h> #include <netinet/in.h> // set of available file descriptors is shared among threads static fd_set read_fds; static int fd_last; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; /** thread function also does multiplexing for readable sockets (but not for writable ones), request is echoed immediately which is not the best way for processing @param server_fd server socket @return NULL **/ void* thread_f(void* server_fd) { int* server = (int*)server_fd; pthread_mutex_lock(&mutex); fd_last = *server; pthread_mutex_unlock(&mutex); while (1) { fd_set read_fds_copy; FD_ZERO(&read_fds_copy); pthread_mutex_lock(&mutex); read_fds_copy = read_fds; int fd_last_copy = fd_last; pthread_mutex_unlock(&mutex); if (select(fd_last_copy + 1, &read_fds_copy, NULL, NULL, NULL) == -1) { fprintf(stderr, "thread_f(): pthread_self()=%ld, error calling 'select()', exiting program\n", (int)pthread_self()); exit(EXIT_FAILURE); } int fd; for (fd = 0; fd <= fd_last_copy; fd++) { if (FD_ISSET(fd, &read_fds_copy)) { if (fd == *server) { // new client accepted struct sockaddr_in client_address; socklen_t client_address_len = sizeof(client_address); int client_fd = accept(*server, (struct sockaddr*)&client_address, &client_address_len); if (client_fd == -1) { printf("thread_f(): pthread_self()=%ld, cannot accept client, sleeping\n", (int)pthread_self()); sleep(1); continue; } char addr_buf[INET_ADDRSTRLEN]; const char* client_address_s = inet_ntop(AF_INET, (const void*)&client_address, addr_buf, INET_ADDRSTRLEN); if (client_address_s == NULL) printf("thread_f(): pthread_self()=%ld, cannot read client address\n", (int)pthread_self()); else printf("thread_f(): pthread_self()=%ld, client accepted from %s\n", (int)pthread_self(), client_address_s); pthread_mutex_lock(&mutex); FD_SET(client_fd, &read_fds); if (client_fd > fd_last) fd_last = client_fd; pthread_mutex_unlock(&mutex); } else { // client request const int REQUEST_LEN = 1000; char request[REQUEST_LEN]; int result; memset(request, '\0', REQUEST_LEN); result = recv(fd, request, REQUEST_LEN, 0); if (result <= 0) { // no request from client if (result == -1) fprintf(stderr, "thread_f(): pthread_self()=%ld, cannot receive request\n", (int)pthread_self()); else if (result == 0) printf("thread_f(): pthread_self()=%ld, client_fd=%d closed\n", (int)pthread_self(), fd); close(fd); pthread_mutex_lock(&mutex); FD_CLR(fd, &read_fds); pthread_mutex_unlock(&mutex); continue; } else { // echo client's request printf("thread_f(): pthread_self()=%ld, fd=%d, request=%s\n", (int)pthread_self(), fd, request); pthread_mutex_unlock(&mutex); result = send(fd, request, strlen(request), 0); if (result == -1) { fprintf(stderr, "thread_f(): pthread_self()=%ld, cannot send response\n", (int)pthread_self()); close(fd); pthread_mutex_lock(&mutex); FD_CLR(fd, &read_fds); pthread_mutex_unlock(&mutex); continue; } printf("thread_f(): pthread_self()=%ld, fd=%d,echo sent\n", (int)pthread_self(), fd); } } } } } return NULL; } int main() { struct addrinfo hint; hint.ai_family = 0; hint.ai_socktype = SOCK_STREAM; hint.ai_flags = AI_PASSIVE; hint.ai_protocol = 0; hint.ai_addrlen = 0; hint.ai_canonname = NULL; hint.ai_addr = NULL; hint.ai_next = NULL; const char* server_address_s = "localhost"; const char* PORT = "7000"; struct addrinfo* server_address; if (getaddrinfo(server_address_s, PORT, &hint, &server_address) != 0) { fprintf(stderr, "main(): cannot locate host, exiting program\n"); exit(EXIT_FAILURE); } int server_fd = socket(server_address->ai_family, server_address->ai_socktype, server_address->ai_protocol); if (server_fd == -1) { fprintf(stderr, "main(): cannot create socket, exiting program\n"); exit(EXIT_FAILURE); } if (bind(server_fd, server_address->ai_addr, server_address->ai_addrlen) == -1) { fprintf(stderr, "main(): cannot bind, exiting program\n"); exit(EXIT_FAILURE); } freeaddrinfo(server_address); const unsigned int PENDING_SIZE = 5; if (listen(server_fd, PENDING_SIZE) == -1) { fprintf(stderr, "main(): cannot listen, exiting program\n"); exit(EXIT_FAILURE); } // put server file descriptor into common set of file descriptors FD_ZERO(&read_fds); FD_SET(server_fd, &read_fds); // create fixed number of threads const unsigned int MAX_THREADS = 2; int i; for (i = 1; i <= MAX_THREADS; i++) { pthread_t thread_id; int ret_status; ret_status = pthread_create(&thread_id, NULL, &thread_f, (void*)&server_fd); if (ret_status != 0) { fprintf(stderr, "main(): cannot create thread, exiting program\n"); exit(EXIT_FAILURE); } } // instead of joining on threads while (1) ; return EXIT_SUCCESS; }
Preforked prethreaded server can use multiplexing. Several processes are created within each of them several threads are created. Each thread uses multiplexing. Thus, the robustness of forked server is achieved together with ability to process several requests.
/* server_preforkthrmplex.c -------------------------- Preforked and prethreaded TCP server with demultiplexing. Several processes are created, each of them creates several threads which itself accept clients. Each thread multiplexes set of file descriptors. Client gets echo of the request until it closes the connection. Compile with gcc -lpthread -oserver_preforkthrmplex server_preforkthrmplex.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netdb.h> #include <sys/wait.h> #include <signal.h> #include <pthread.h> #include <arpa/inet.h> #include <netinet/in.h> // set of available file descriptors is shared among threads (in one process) static fd_set read_fds; static int fd_last; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; /** waits for any child to finish @param sig_no not used **/ void wait_children(int sig_no) { while (waitpid((pid_t)-1, NULL, WNOHANG) > 0) ; } /** thread function also does multiplexing for readable sockets (but not for writable ones), request is echoed immediately which is not the best way for processing @param server_fd server socket @return NULL **/ void* thread_f(void* server_fd) { int* server = (int*)server_fd; pthread_mutex_lock(&mutex); fd_last = *server; pthread_mutex_unlock(&mutex); while (1) { fd_set read_fds_copy; FD_ZERO(&read_fds_copy); pthread_mutex_lock(&mutex); read_fds_copy = read_fds; int fd_last_copy = fd_last; pthread_mutex_unlock(&mutex); if (select(fd_last_copy + 1, &read_fds_copy, NULL, NULL, NULL) == -1) { fprintf(stderr, "thread_f(): getpid()=%d, pthread_self()=%ld, error calling 'select()', exiting program\n", getpid(), (int)pthread_self()); exit(EXIT_FAILURE); } int fd; for (fd = 0; fd <= fd_last_copy; fd++) { if (FD_ISSET(fd, &read_fds_copy)) { if (fd == *server) { // new client accepted struct sockaddr_in client_address; socklen_t client_address_len = sizeof(client_address); int client_fd = accept(*server, (struct sockaddr*)&client_address, &client_address_len); if (client_fd == -1) { printf("thread_f(): getpid()=%d, pthread_self()=%ld, cannot accept client, sleeping\n", getpid(), (int)pthread_self()); sleep(1); continue; } char addr_buf[INET_ADDRSTRLEN]; const char* client_address_s = inet_ntop(AF_INET, (const void*)&client_address, addr_buf, INET_ADDRSTRLEN); if (client_address_s == NULL) printf("thread_f(): getpid()=%d, pthread_self()=%ld, cannot read client address\n", getpid(), (int)pthread_self()); else printf("thread_f(): getpid()=%d, pthread_self()=%ld, client accepted from %s\n", getpid(), (int)pthread_self(), client_address_s); pthread_mutex_lock(&mutex); FD_SET(client_fd, &read_fds); if (client_fd > fd_last) fd_last = client_fd; pthread_mutex_unlock(&mutex); } else { // client request const int REQUEST_LEN = 1000; char request[REQUEST_LEN]; memset(request, '\0', REQUEST_LEN); int result = recv(fd, request, REQUEST_LEN, 0); if (result <= 0) { // no request from client if (result == -1) fprintf(stderr, "thread_f(): getpid()=%d, pthread_self()=%ld, cannot receive request\n", getpid(), (int)pthread_self()); else if (result == 0) printf("thread_f(): getpid()=%d, pthread_self()=%ld, client_fd=%d closed\n", getpid(), (int)pthread_self(), fd); close(fd); pthread_mutex_lock(&mutex); FD_CLR(fd, &read_fds); pthread_mutex_unlock(&mutex); continue; } else { // echo client's request printf("thread_f(): getpid()=%d, pthread_self()=%ld, fd=%d, request=%s\n", getpid(), (int)pthread_self(), fd, request); result = send(fd, request, strlen(request), 0); if (result == -1) { fprintf(stderr, "thread_f(): cannot send response\n"); close(fd); pthread_mutex_lock(&mutex); FD_CLR(fd, &read_fds); pthread_mutex_unlock(&mutex); continue; } printf("thread_f(): getpid()=%d, pthread_self()=%ld, fd=%d, echo sent\n", getpid(), (int)pthread_self(), fd); } } } } } return NULL; } /** creates several threads @param server_fd server socket **/ void child(int server_fd) { // put server file descriptor into common set of file descriptors // after forking, each process instance will have its own set, shared between threads in one process FD_ZERO(&read_fds); FD_SET(server_fd, &read_fds); // create fixed number of threads const unsigned int MAX_THREADS = 2; int i; for (i = 1; i <= MAX_THREADS; i++) { pthread_t thread_id; int status; status = pthread_create(&thread_id, NULL, &thread_f, (void*)&server_fd); if (status != 0) { fprintf(stderr, "child(): getpid()=%d, cannot create thread, exiting program\n", getpid()); exit(EXIT_FAILURE); } } // instead of joining on threads while (1) ; } int main() { struct addrinfo hint; hint.ai_family = 0; hint.ai_socktype = SOCK_STREAM; hint.ai_flags = AI_PASSIVE; hint.ai_protocol = 0; hint.ai_addrlen = 0; hint.ai_canonname = NULL; hint.ai_addr = NULL; hint.ai_next = NULL; const char* server_address_s = "localhost"; const char* PORT = "7000"; struct addrinfo* server_address; if (getaddrinfo(server_address_s, PORT, &hint, &server_address) != 0) { fprintf(stderr, "main(): cannot locate host, exiting program\n"); exit(EXIT_FAILURE); } int server_fd = socket(server_address->ai_family, server_address->ai_socktype, server_address->ai_protocol); if (server_fd == -1) { fprintf(stderr, "main(): cannot create socket, exiting program\n"); exit(EXIT_FAILURE); } if (bind(server_fd, server_address->ai_addr, server_address->ai_addrlen) == -1) { fprintf(stderr, "main(): cannot bind, exiting program\n"); exit(EXIT_FAILURE); } freeaddrinfo(server_address); const unsigned int PENDING_SIZE = 5; if (listen(server_fd, PENDING_SIZE) == -1) { fprintf(stderr, "main(): cannot listen, exiting program\n"); exit(EXIT_FAILURE); } struct sigaction signal_action; signal_action.sa_handler = wait_children; sigemptyset(&signal_action.sa_mask); signal_action.sa_flags = SA_RESTART; if (sigaction(SIGCHLD, &signal_action, NULL) == -1) { fprintf(stderr, "main(): 'sigaction()' failed, exiting program\n"); exit(EXIT_FAILURE); } // create fixed number of child processes const unsigned int MAX_PROCESSES = 2; int i; for (i = 1; i <= MAX_PROCESSES; i++) { int pid; pid = fork(); if (pid == 0) // child { child(server_fd); } else if (pid < 0) { fprintf(stderr, "main(): cannot fork, sleeping\n"); sleep(5); } } wait(NULL); return EXIT_SUCCESS; }
The examples have been tested on gcc 4.3.3/Linux 2.6.29 64bit, gcc 4.2.1/FreeBSD 8.0 64bit.