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 鈥? 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
structures
11 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鈥?4 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.
Initialize
20鈥?4
We use the first entry in the client array for the
listening socket and set the descriptor for the remaining entries
to 鈥?. 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
connection
26鈥?2
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
connection
43鈥?3
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 鈥?.
|