This section focuses on how to use select functions to improve our previous client-server model.
When we were dealing with the multi-client model, every time a client is connected, the server needs to open up a new process to handle the new client, which will consume a lot of memory resources.
The select function allows the process to instruct the kernel to wait for any one of multiple events to occur and to wake it up only after one or more events occur or after a specified period of time. In other words, select has the ability to manage multiple I/O. For multiple socket interfaces, once a socket interface has an event of interest to us, the select function returns, and the return value is the number of events monitored. Moreover, since the parameters of the select function are "value-result" type, we also know which sets of interfaces have events, and then traverse these sets of interfaces and deal with related events.
The specific usage of the select parameter is not explained here, you can refer to the online resources or "UNIX Network Programming: Volume 1" on the relevant content.
The code of the improved echo client-server model is given below.
Server side: echosrv
Client: echocli.c#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <errno.h> #include <unistd.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> #include <signal.h> #include <sys/wait.h> #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ }while(0) ssize_t readn(int fd, void* buf, size_t count) { //Because there is no guarantee that count s can be read at a time //So we need to read in a loop. //Until the number of bytes read is count size_t nleft = count; ssize_t nread; char* bufp = (char*)buf; while(nleft > 0) { if((nread = read(fd, bufp, nleft)) < 0) { if(errno == EINTR)//If the signal is interrupted continue; return -1; } if(nread == 0) //Represents peer closure, returned directly here return count-nleft; nleft -= nread;//The number of bytes left after each read bufp += nread; } return count; } ssize_t writen(int fd, void* buf, size_t count) { //The number of bytes we want to write each time is count size_t nleft = count; ssize_t nwritten; char* bufp = (char*)buf; while(nleft > 0) { if((nwritten = write(fd, bufp, nleft)) < 0) { if(errno == EINTR) continue; return -1; } if(nwritten == 0) //Nothing happened. continue; nleft -= nwritten;//The number of bytes left to write after each write bufp += nwritten; } return count; } ssize_t recv_peek(int sockfd, void* buf, size_t len) { //This function can receive data from a socket interface //But data is not removed from the buffer while(1) { int ret = recv(sockfd, buf, len, MSG_PEEK); //Read the data and return, otherwise return if(ret == -1 && errno == EINTR) continue; return ret; } } ssize_t readline(int sockfd, void*buf, size_t maxline) { //The read process does not necessarily read maxline bytes //Just encounter n to return int ret; int nread; char* bufp = buf; int nleft = maxline; while(1) { ret = recv_peek(sockfd, bufp, nleft); if(ret < 0) return ret; else if(ret == 0) return ret; nread = ret; int i; for(i = 0; i < nread; ++i) { if(bufp[i] == '\n') { //Our recv_peek is just peeping at the data. //There is no data left. //So here we use readn to remove peeped data from the buffer ret = readn(sockfd, bufp, i+1); if(ret != i+1) exit(EXIT_FAILURE); return ret; } } //No encounter \n if(nread > nleft) exit(EXIT_FAILURE); //Remove read data nread bytes from the buffer nleft -= nread; ret = readn(sockfd, bufp, nread); if(ret != nread) exit(EXIT_FAILURE); //The next peep needs to be offset. bufp += nread; } return -1; } void echo_srv(int conn) { char recvbuf[1024]; while(1) { memset(&recvbuf, 0, sizeof(recvbuf)); int ret = readline(conn, recvbuf, 1024); if(ret == -1) ERR_EXIT("read failure"); if(ret == 0) { printf("client close\n"); break; } fputs(recvbuf, stdout); writen(conn, recvbuf, strlen(recvbuf)); } } void handle_sigchld(int sig) { /* wait(NULL);*/ while(waitpid(-1, NULL, WNOHANG) > 0) ; } int main(void) { //Avoiding zombie processes /* signal(SIGCHLD, SIG_IGN);//ignore */ signal(SIGCHLD, handle_sigchld); //Create a socket int listenfd; if((listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) //Both AF_INET and PF_INET can be used. The first two parameters can be determined to be TCP, so the third parameter can be set to 0. // If ((listenfd = socket (AF_INET, SOCK_STREAM, 0)) < 0// / Both AF_INET and PF_INET can be used. The first two parameters can be determined to be TCP, so the third parameter can be set to 0. ERR_EXIT("socket_failure"); //Initialization address struct sockaddr_in servaddr; memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET;//Address family servaddr.sin_port = htons(5188); servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//Represents the arbitrary address of the machine, recommended for use (converted into network byte order) //You can also specify explicitly by yourself // servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //perhaps // inet_aton("127.0.0.1", &servaddr.sin_addr); //Open address reuse before binding int on = 1; if(setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) ERR_EXIT("setsockopt_failure"); //Next, bind the socket to a local address //The IPv4 address structure needs to be mandatory to be converted to a universal address structure if(bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) ERR_EXIT("bind_failure");//Binding failed //The next step is listening, which changes the socket from close state to listen state to accept the connection. if(listen(listenfd, SOMAXCONN) < 0) ERR_EXIT("listen_failure"); //Define the address of the other party struct sockaddr_in peeraddr; socklen_t peerlen; int conn; //A new socket, called a connected socket (active socket) /* pid_t pid; while(1) { if((conn = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen)) < 0) ERR_EXIT("accept_failure"); //The address and port of the output client printf("IP=%s port=%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port)); //Once you have a connection, create a process //Processing multiple clients to achieve concurrency pid = fork(); if(pid == -1) ERR_EXIT("fork_failure"); if(pid == 0) { //Let subprocesses handle existing communication processes //No longer need to listen socket interface close(listenfd); echo_srv(conn); //Once the do_service function returns, the process has no value in existence. exit(EXIT_SUCCESS);//At this point, the process of opening up for clients is also destroyed. } else //The parent process accept s //There is no need to connect socket interfaces anymore, namely conn (parent-child process shared file descriptor) close(conn); } //Implementing a Reflex Client/Server Model //That is, the client obtains data from standard input, sends it to the server, and then echoes back to the server. */ //Change to implement with select int client[FD_SETSIZE];//The maximum number of events that select can handle int i; for(i = 0; i < FD_SETSIZE; ++i) client[i] = -1;//Initialization, -1 indicates idle state int maxi = 0; //Maximum non-idle position int nready; int maxfd = listenfd;//3 fd_set rset;//Define a read set fd_set allset; FD_ZERO(&rset); FD_ZERO(&allset); FD_SET(listenfd, &allset);//First add the monitor socket interface while(1) { rset = allset; nready = select(maxfd+1, &rset, NULL, NULL, NULL); if(nready == -1) { if(errno == EINTR) continue; ERR_EXIT("select"); } if(nready == 0) continue; if(FD_ISSET(listenfd, &rset)) { peerlen = sizeof(peeraddr); //Monitor socket interface generates events conn = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen); if(conn == -1) ERR_EXIT("accept error"); for(i = 0; i < FD_SETSIZE; ++i) { if(client[i] < 0) { //Find a free place to put the interface in. client[i] = conn; if(maxi < i) maxi = i; break; } } if(i == FD_SETSIZE) { fprintf(stderr, "too many clients\n"); exit(EXIT_FAILURE); } printf("ip=%s port=%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port)); FD_SET(conn, &allset); if(conn > maxfd) maxfd = conn; if(--nready <= 0) continue; } //Connected socket interfaces may also generate events for(i = 0; i < FD_SETSIZE; ++i) { conn = client[i]; if(conn == -1) continue; if(FD_ISSET(conn, &rset)) { //It means that there are readable events. char recvbuf[1024] = {0}; int ret = readline(conn, recvbuf, 1024); if(ret == -1) ERR_EXIT("readline error"); if(ret == 0) { //Peer closure printf("client close\n"); FD_CLR(conn, &allset);//Clear this set of interfaces client[i] = -1; } fputs(recvbuf, stdout); writen(conn, recvbuf, strlen(recvbuf)); if(--nready <= 0) break; } } } return 0; }
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <errno.h> #include <unistd.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> #include <signal.h> #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ }while(0) ssize_t readn(int fd, void* buf, size_t count) { size_t nleft = count; //The number of bytes remaining to read ssize_t nread; //Number of bytes read at a time char* bufp = (char*)buf; while(nleft > 0) { if((nread = read(fd, bufp, nleft)) < 0) { if(errno == EINTR) continue; return -1; } if(nread == 0)//Peer closure return count-nleft; nleft -= nread; bufp += nread; } return count; } ssize_t writen(int fd, void* buf, size_t count) { size_t nleft = count; //The number of bytes remaining to read ssize_t nwritten; //Number of bytes read at a time char* bufp = (char*)buf; while(nleft > 0) { if((nwritten = write(fd, bufp, nleft)) < 0) { if(errno == EINTR) continue; return -1; } if(nwritten == 0) continue; nleft -= nwritten; bufp += nwritten; } return count; } ssize_t recv_peek(int sockfd, void* buf, size_t len) { while(1) { int ret = recv(sockfd, buf, len, MSG_PEEK); if(ret == -1 && errno == EINTR) continue; //Sneak at the data and return it directly return ret; } } ssize_t readline(int sockfd, void*buf, size_t maxline) { char* bufp = buf; int nleft = maxline; int nread; int ret; while(1) { ret = recv_peek(sockfd, bufp, nleft); if(ret < 0) return ret; if(ret == 0) return ret; nread = ret; int i; for(i = 0; i < nread; ++i) { if(bufp[i] == '\n') { ret = readn(sockfd, bufp, i+1); if(ret != i+1) exit(EXIT_FAILURE); return ret; } } //No encounter \n if(nread > nleft) exit(EXIT_FAILURE); nleft -= nread; ret = readn(sockfd, bufp, nread); if(ret != nread) exit(EXIT_FAILURE); //Keep on peeping bufp += nread; } return -1; } void echo_cli(int sock) { /* //If the connection is successful, you can communicate char sendbuf[1024] = {0}; char recvbuf[1024] = {0}; while(fgets(sendbuf, sizeof(sendbuf), stdin) != NULL) { writen(sock, sendbuf, strlen(sendbuf)); int ret = readline(sock, recvbuf, 1024); if(ret == -1) ERR_EXIT("read error"); else if(ret == 0) { printf("peer close\n"); break; } //Show up fputs(recvbuf, stdout); //Here we need to clear the buffer memset(sendbuf, 0, sizeof(sendbuf)); memset(recvbuf, 0, sizeof(recvbuf)); } close(sock); */ //This is the use of select fd_set rset; FD_ZERO(&rset); //Detect whether the standard input produces readable events int nready; int maxfd; int fd_stdin = fileno(stdin);//Standard input char recvbuf[1024] = {0}; char sendbuf[1024] = {0}; //There are two file descriptors, fd_stdin and sock if(fd_stdin > sock) maxfd = fd_stdin; else maxfd = sock; while(1) { FD_SET(fd_stdin, &rset); FD_SET(sock, &rset); //There's only a collection of readings. nready = select(maxfd+1, &rset, NULL, NULL, NULL); if(nready == -1) ERR_EXIT("select error"); if(nready == 0) continue; //If an event is detected, rset changes //Which sets of interfaces will be included in this package? if(FD_ISSET(sock, &rset)) { //Socket interface produces readability //Read by line int ret = readline(sock, recvbuf, sizeof(recvbuf)); if(ret == -1) ERR_EXIT("readline error"); else if(ret == 0) { printf("server close\n"); break; } //Show up fputs(recvbuf, stdout); memset(recvbuf, 0, sizeof(recvbuf)); } if(FD_ISSET(fd_stdin, &rset)) { //Standard input generates events, input buffer has contents, and fgets is used to clear them if(fgets(sendbuf, sizeof(sendbuf), stdin) == NULL) break; write(sock, sendbuf, strlen(sendbuf)); memset(sendbuf, 0, sizeof(sendbuf)); } } close(sock); } void handle_sigpipe(int sig) { printf("recv a sig=%d\n", sig); } int main(void) { signal(SIGPIPE, handle_sigpipe); int sock;//Create a socket if((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) //Both AF_INET and PF_INET can be used. The first two parameters can be determined to be TCP, so the third parameter can be set to 0. // If ((sock = socket (AF_INET, SOCK_STREAM, 0)< 0)// / Both AF_INET and PF_INET can be used. The first two parameters can be determined to be TCP, so the third parameter can be set to 0. ERR_EXIT("socket_failure"); struct sockaddr_in servaddr; memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET;//Address family servaddr.sin_port = htons(5188); //Explicitly specify the server-side address yourself servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //Clients do not need bind or listen. //Just connect directly to the past. if(connect(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) ERR_EXIT("connect_failure"); //Connect successfully, view the local port and address struct sockaddr_in localaddr; socklen_t addrlen = sizeof(localaddr); if(getsockname(sock, (struct sockaddr*)&localaddr, &addrlen) < 0) ERR_EXIT("getsockname error"); printf("IP=%s port=%d\n", inet_ntoa(localaddr.sin_addr), ntohs(localaddr.sin_port)); echo_cli(sock); return 0; }
Description: FD_SETSIZE is the maximum number of events that a system-specified select can monitor, usually 1024.