We now combine our concurrent TCP echo server from Chapter 5 with our iterative UDP echo server from this chapter into a single server that uses select to multiplex a TCP and UDP socket. Figure 8.24 is the first half of this server.
1422 A listening TCP socket is created that is bound to the server's well-known port. We set the SO_REUSEADDR socket option in case connections exist on this port.
2329 A UDP socket is also created and bound to the same port. Even though the same port is used for TCP and UDP sockets, there is no need to set the SO_REUSEADDR socket option before this call to bind, because TCP ports are independent of UDP ports.
Figure 8.25 shows the second half of our server.
30 A signal handler is established for SIGCHLD because TCP connections will be handled by a child process. We showed this signal handler in Figure 5.11.
3132 We initialize a descriptor set for select and calculate the maximum of the two descriptors for which we will wait.
udpcliserv/udpservselect01.c
1 #include "unp.h" 2 int 3 main(int argc, char **argv) 4 { 5 int listenfd, connfd, udpfd, nready, maxfdp1; 6 char mesg[MAXLINE]; 7 pid_t childpid; 8 fd_set rset; 9 ssize_t n; 10 socklen_t len; 11 const int on = 1; 12 struct sockaddr_in cliaddr, servaddr; 13 void sig_chld(int); 14 /* create listening TCP socket */ 15 listenfd = Socket(AF_INET, SOCK_STREAM, 0); 16 bzero(&servaddr, sizeof(servaddr)); 17 servaddr.sin_family = AF_INET; 18 servaddr.sin_addr.s_addr = htonl(INADDR_ANY); 19 servaddr.sin_port = htons(SERV_PORT); 20 Setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); 21 Bind(listenfd, (SA *) &servaddr, sizeof(servaddr)); 22 Listen(listenfd, LISTENQ); 23 /* create UDP socket */ 24 udpfd = Socket(AF_INET, SOCK_DGRAM, 0); 25 bzero(&servaddr, sizeof(servaddr)); 26 servaddr.sin_family = AF_INET; 27 servaddr.sin_addr.s_addr = htonl(INADDR_ANY); 28 servaddr.sin_port = htons(SERV_PORT); 29 Bind(udpfd, (SA *) &servaddr, sizeof(servaddr));
3441 We call select, waiting only for readability on the listening TCP socket or readability on the UDP socket. Since our sig_chld handler can interrupt our call to select, we handle an error of EINTR.
4251 We accept a new client connection when the listening TCP socket is readable, fork a child, and call our str_echo function in the child. This is the same sequence of steps we used in Chapter 5.
udpcliserv/udpservselect01.c
30 Signal(SIGCHLD, sig_chld); /* must call waitpid() */ 31 FD_ZERO(&rset); 32 maxfdp1 = max(listenfd, udpfd) + 1; 33 for ( ; ; ) { 34 FD_SET(listenfd, &rset); 35 FD_SET(udpfd, &rset); 36 if ( (nready = select(maxfdp1, &rset, NULL, NULL, NULL)) < 0) { 37 if (errno == EINTR) 38 continue; /* back to for() */ 39 else 40 err_sys("select error"); 41 } 42 if (FD_ISSET(listenfd, &rset)) { 43 len = sizeof(cliaddr); 44 connfd = Accept(listenfd, (SA *) &cliaddr, &len); 45 if ( (childpid = Fork()) == 0) { /* child process */ 46 Close(listenfd); /* close listening socket */ 47 str_echo(connfd); /* process the request */ 48 exit(0); 49 } 50 Close(connfd); /* parent closes connected socket */ 51 } 52 if (FD_ISSET(udpfd, &rset)) { 53 len = sizeof(cliaddr); 54 n = Recvfrom(udpfd, mesg, MAXLINE, 0, (SA *) &cliaddr, &len); 55 Sendto(udpfd, mesg, n, 0, (SA *) &cliaddr, len); 56 } 57 } 58 }
5257 If the UDP socket is readable, a datagram has arrived. We read it with recvfrom and send it back to the client with sendto.