I came across a program (code given below) which implements select() in a scenario where we needed to combine a concurrent TCP echo server and an iterative UDP server into a single server; inorder to multiplex both the TCP and UDP clients.
I am still unable to understand the purpose of using the fork() alongwith select()? And is there any other way to implement the same code only with select().
Edit- From the comments, I understood that fork() DOESN'T have anything to do with IO. And the use of Non-Blocking IO doesn't mean necessarily to use or not to use fork() or select(). But I still can't wrap my head around the use of fork() in the given code.
Code-
// Server program
#include <arpa/inet.h>
#include <errno.h>
#include <netinet/in.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#define PORT 5000
#define MAXLINE 1024
int max(int x, int y)
{
if (x > y)
return x;
else
return y;
}
int main()
{
int listenfd, connfd, udpfd, nready, maxfdp1;
char buffer[MAXLINE];
pid_t childpid;
fd_set rset;
ssize_t n;
socklen_t len;
const int on = 1;
struct sockaddr_in cliaddr, servaddr;
char* message = "Hello Client";
void sig_chld(int);
/* create listening TCP socket */
listenfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(PORT);
// binding server addr structure to listenfd
bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
listen(listenfd, 10);
/* create UDP socket */
udpfd = socket(AF_INET, SOCK_DGRAM, 0);
// binding server addr structure to udp sockfd
bind(udpfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
// clear the descriptor set
FD_ZERO(&rset);
// get maxfd
maxfdp1 = max(listenfd, udpfd) + 1;
for (;;) {
// set listenfd and udpfd in readset
FD_SET(listenfd, &rset);
FD_SET(udpfd, &rset);
// select the ready descriptor
nready = select(maxfdp1, &rset, NULL, NULL, NULL);
// if tcp socket is readable then handle
// it by accepting the connection
if (FD_ISSET(listenfd, &rset)) {
len = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr*)&cliaddr, &len);
if ((childpid = fork()) == 0) {
close(listenfd);
bzero(buffer, sizeof(buffer));
printf("Message From TCP client: ");
read(connfd, buffer, sizeof(buffer));
puts(buffer);
write(connfd, (const char*)message, sizeof(buffer));
close(connfd);
exit(0);
}
close(connfd);
}
// if udp socket is readable receive the message.
if (FD_ISSET(udpfd, &rset)) {
len = sizeof(cliaddr);
bzero(buffer, sizeof(buffer));
printf("\nMessage from UDP client: ");
n = recvfrom(udpfd, buffer, sizeof(buffer), 0,
(struct sockaddr*)&cliaddr, &len);
puts(buffer);
sendto(udpfd, (const char*)message, sizeof(buffer), 0,
(struct sockaddr*)&cliaddr, sizeof(cliaddr));
}
}
}
I am still unable to understand the purpose of using the fork() alongwith select()?
select
is only used here to manage the TCP listening socket and the UDP socket together. It is assumed here that
accept
will not block. It is also (wrongly) assumed that accept
might not fail and will always return a new file descriptor.recvfrom
will not blocksendto
will not blockThe handling of the accepted TCP client is done within a child process in order to keep the code simpler - that's why the fork
.
And is there any other way to implement the same code only with select().
Yes, but it is more complex. If done properly it has to deal with the case of multiple established client connections at the same time, with TCP possibly blocking on write etc.