[ Team LiB ] |
6.11 TCP Echo Server (Revisited Again)We now redo our TCP echo server from Section 6.8 using poll instead of select. In the previous version using select, we had to allocate a client array along with a descriptor set named rset (Figure 6.15). With poll, we must allocate an array of pollfd structures to maintain the client information instead of allocating another array. We handle the fd member of this array the same way we handled the client array in Figure 6.15: a value of –1 means the entry is not in use; otherwise, it is the descriptor value. Recall from the previous section that any entry in the array of pollfd structures passed to poll with a negative value for the fd member is just ignored. Figure 6.25 shows the first half of our server. Figure 6.25 First half of TCP server using poll.tcpcliserv/tcpservpoll01.c 1 #include "unp.h" 2 #include <limits.h> /* for OPEN_MAX */ 3 int 4 main(int argc, char **argv) 5 { 6 int i, maxi, listenfd, connfd, sockfd; 7 int nready; 8 ssize_t n; 9 char buf[MAXLINE]; 10 socklen_t clilen; 11 struct pollfd client[OPEN_MAX]; 12 struct sockaddr_in cliaddr, servaddr; 13 listenfd = Socket(AF_INET, SOCK_STREAM, 0); 14 bzero(&servaddr, sizeof(servaddr)); 15 servaddr.sin_family = AF_INET; 16 servaddr.sin_addr.s_addr = htonl(INADDR_ANY); 17 servaddr.sin_port = htons(SERV_PORT); 18 Bind(listenfd, (SA *) &servaddr, sizeof(servaddr)); 19 Listen(listenfd, LISTENQ); 20 client[0].fd = listenfd; 21 client[0].events = POLLRDNORM; 22 for (i = 1; i < OPEN_MAX; i++) 23 client[i].fd = -1; /* -1 indicates available entry */ 24 maxi = 0; /* max index into client[] array */ Allocate array of pollfd structures11 We declare OPEN_MAX elements in our array of pollfd structures. Determining the maximum number of descriptors that a process can have open at any one time is difficult. We will encounter this problem again in Figure 13.4. One way is to call the POSIX sysconf function with an argument of _SC_OPEN_MAX (as described on pp. 42–44 of APUE) and then dynamically allocate an array of the appropriate size. But one of the possible returns from sysconf is "indeterminate," meaning we still have to guess a value. Here, we just use the POSIX OPEN_MAX constant. Initialize20–24 We use the first entry in the client array for the listening socket and set the descriptor for the remaining entries to –1. We also set the POLLRDNORM event for this descriptor, to be notified by poll when a new connection is ready to be accepted. The variable maxi contains the largest index of the client array currently in use. The second half of our function is shown in Figure 6.26. Figure 6.26 Second half of TCP server using poll.tcpcliserv/tcpservpoll01.c 25 for ( ; ; ) { 26 nready = Poll(client, maxi + 1, INFTIM); 27 if (client[0].revents & POLLRDNORM) { /* new client connection */ 28 clilen = sizeof(cliaddr); 29 connfd = Accept(listenfd, (SA *) &cliaddr, &clilen); 30 for (i = 1; i < OPEN_MAX; i++) 31 if (client[i].fd < 0) { 32 client[i].fd = connfd; /* save descriptor */ 33 break; 34 } 35 if (i == OPEN_MAX) 36 err_quit("too many clients"); 37 client[i].events = POLLRDNORM; 38 if (i > maxi) 39 maxi = i; /* max index in client[] array */ 40 if (--nready <= 0) 41 continue; /* no more readable descriptors */ 42 } 43 for (i = 1; i <= maxi; i++) { /* check all clients for data */ 44 if ( (sockfd = client[i].fd) < 0) 45 continue; 46 if (client[i].revents & (POLLRDNORM | POLLERR)) { 47 if ( (n = read(sockfd, buf, MAXLINE)) < 0) { 48 if (errno == ECONNRESET) { 49 /* connection reset by client */ 50 Close(sockfd); 51 client[i].fd = -1; 52 } else 53 err_sys("read error"); 54 } else if (n == 0) { 55 /* connection closed by client */ 56 Close(sockfd); 57 client[i].fd = -1; 58 } else 59 Writen(sockfd, buf, n); 60 if (--nready <= 0) 61 break; /* no more readable descriptors */ 62 } 63 } 64 } 65 } Call poll, check for new connection26–42 We call poll to wait for either a new connection or data on existing connection. When a new connection is accepted, we find the first available entry in the client array by looking for the first one with a negative descriptor. Notice that we start the search with the index of 1, since client[0] is used for the listening socket. When an available entry is found, we save the descriptor and set the POLLRDNORM event. Check for data on an existing connection43–63 The two return events that we check for are POLLRDNORM and POLLERR. The second of these we did not set in the events member because it is always returned when the condition is true. The reason we check for POLLERR is because some implementations return this event when an RST is received for a connection, while others just return POLLRDNORM. In either case, we call read and if an error has occurred, it will return an error. When an existing connection is terminated by the client, we just set the fd member to –1. |
[ Team LiB ] |