As you may know, I have been ta-ing a course in operating systems. We just finished covering sockets and in the last lab I gave a socket demo where I show three different ways a server can listen on a socket. First is a very basic case where the server can only accept and process one connection at a time. Second I show the fork()ing case where multiple connections can be processed concurrently using multiple processes. Lastly, the multiple connection case is handled with select() so that everything can be handled in a single connection.
**Single connection server**
This server is a basic echo server. A client connects to it on a specific port and enters strings on the stdin. The server maintains the connection until the client terminates the connection with a blank line.
The major weakness of this server is that it can only handle a single connection at once. So if the first connection is very slow, any subsequent connections must wait, even if they are ready to be handled. In the case of this server, the first user to connect may be very slow to type their strings, and even if the second user has already entered a string, it will not be displayed until the first user connection in completed.
```
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <netdb.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERROR(x) do { perror(x); exit(1); } while(0)
#define NAME 257
#define BUFSIZE 257
#define MAXPENDING 1
int main(int argc, char *argv[])
{
unsigned short port; /* Port to which server will bind */
char servhost[NAME]; /* Local host name */
struct sockaddr_in sock; /* INTERNET socket space */
struct hostent *server; /* Local host information */
int S; /* fd for socket */
int NS; /* fd for connected socket */
char buf[BUFSIZE]; /* Input buffer */
FILE *fp; /* Stream (converted file des.) */
if (argc != 2)
{
fprintf(stderr,"usage: server <port>\n");
exit(1);
}
port = atoi(argv[1]);
/*
* Get socket - INTERNET DOMAIN - TCP
*/
if ((S = socket(AF_INET, SOCK_STREAM, 0)) < 0)
ERROR("server: socket");
/*
* Obtain host name & network address
*/
gethostname(servhost, sizeof(servhost));
if ((server = gethostbyname(servhost)) == NULL)
{
fprintf(stderr,"%s: unknown host\n",servhost);
exit(1);
}
/*
* Bind to server address - in network byte order
*/
sock.sin_family = AF_INET;
sock.sin_port = htons(port);
memcpy(&sock.sin_addr, server->h_addr, server->h_length);
/*
* Bind socket to port/addr
*/
if (bind(S, (struct sockaddr *)&sock, sizeof(sock)) < 0)
ERROR("server: bind");
/*
* Listen on this socket
*/
if (listen(S,MAXPENDING) < 0)
ERROR("server: listen");
//loop to continue handling connections
while(1)
{
/*
* Accept connections. Once connected, the client will be
* connected on fd NS, a second and third parameter may be passed
* to accept which will be filled in with information regarding the
* client connection if desired.
*
* In this example, once connected the server is done with the
* master socket (so closes it).
*/
if ((NS = accept(S,NULL,NULL)) < 0)
ERROR("server: accept");
/*
* Using stdio library to read from socket
*/
if (!(fp = fdopen(NS,"r")))
{
fprintf(stderr,">>> Error converting file des. to stream <<<\n");
exit(1);
}
while (fgets(buf,BUFSIZE,fp))
printf("%s", buf);
/*
* DONE - simply close() connection
*/
fclose(fp);
close(NS);
}
return(0);
}
```
**fork() – Multiple connection, multiple process**
In this case, the server can now handle multiple connections successfully. If the first connection has a long processing time or the user is slow at entering data, the second connection may now continue freely without waiting in line. The downside to this approach is: 1) If the server must handle a large number of connections simultaneously, the server may run out of processes since each connection fork()s. 2) Since a fork() call duplicates variables, file descriptors etc., the server may run out of memory if each connection requires any significant processing.
Notice that the processing portion of the code has been moved into a function which is called when the fork() is executing within the child processes.
```
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <netdb.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERROR(x) do { perror(x); exit(1); } while(0)
#define NAME 257
#define BUFSIZE 257
#define MAXPENDING 1
void handle_connection(int NS);
int main(int argc, char *argv[])
{
unsigned short port; /* Port to which server will bind */
char servhost[NAME]; /* Local host name */
struct sockaddr_in sock; /* INTERNET socket space */
struct hostent *server; /* Local host information */
int S; /* fd for socket */
int NS; /* fd for connected socket */
int pid; /* used to determine parent or child */
if (argc != 2)
{
fprintf(stderr,"usage: fork-server <port>\n");
exit(1);
}
port = atoi(argv[1]);
/*
* Get socket - INTERNET DOMAIN - TCP
*/
if ((S = socket(AF_INET, SOCK_STREAM, 0)) < 0)
ERROR("fork-server: socket");
/*
* Obtain host name & network address
*/
gethostname(servhost, sizeof(servhost));
if ((server = gethostbyname(servhost)) == NULL)
{
fprintf(stderr,"%s: unknown host\n",servhost);
exit(1);
}
/*
* Bind to server address - in network byte order
*/
sock.sin_family = AF_INET;
sock.sin_port = htons(port);
memcpy(&sock.sin_addr, server->h_addr, server->h_length);
/*
* Bind socket to port/addr
*/
if (bind(S, (struct sockaddr *)&sock, sizeof(sock)) < 0)
ERROR("server: bind");
/*
* Listen on this socket
*/
if (listen(S,MAXPENDING) < 0)
ERROR("server: listen");
while(1)
{
/*
* Accept connections. Once connected, the client will be
* connected on fd NS, a second and third parameter may be passed
* to accept which will be filled in with information regarding the
* client connection if desired.
*
* In this example, once connected the server is done with the
* master socket (so closes it).
*/
if ((NS = accept(S,NULL,NULL)) < 0)
ERROR("server: accept");
if((pid = fork()) < 0)
ERROR("server: fork");
if(pid == 0)
{
handle_connection(NS);
exit(0);
}
else
close(NS);
}
return(0);
}
void handle_connection(int NS)
{
char buf[BUFSIZE]; /* Input buffer */
FILE *fp; /* Stream (converted file des.) */
/*
* Using stdio library to read from socket
*/
if (!(fp = fdopen(NS,"r")))
{
fprintf(stderr,">>> Error converting file des. to stream <<<\n");
exit(1);
}
while (fgets(buf,BUFSIZE,fp))
printf("%s", buf);
/*
* DONE - simply close() connection
*/
fclose(fp);
close(NS);
}
```
**select() – Multiple connection, single process**
The last case still has the benefits of fork() but does so in a single process. In this case, select() is used. In this case, each socket descriptor is monitored by select which determines if any of the sockets are ready for I/O. Rather than sitting idly waiting for input on sockets that are not ready, data is processed as it arrives. The drawback to this approach is that it is little trickier to implement and understand, but if you start with a basic case like this it can be quite simple to get the hang of it. When using select(), we need to specify the groups of sockets we wish to monitor. This is done using the fd_set which you can see in the source.
```
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <netdb.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERROR(x) do { perror(x); exit(1); } while(0)
#define NAME 257
#define BUFSIZE 257
#define MAXPENDING 1
void handle_connection(int NS, fd_set * activefds);
int main(int argc, char *argv[])
{
fd_set readfds, activefds; /* the set of read descriptors */
unsigned short port; /* Port to which server will bind */
char servhost[NAME]; /* Local host name */
struct sockaddr_in sock; /* INTERNET socket space */
struct hostent *server; /* Local host information */
int S; /* fd for socket */
int NS; /* fd for connected socket */
int i; /* counter to go through FDs in fdset */
if (argc != 2)
{
fprintf(stderr,"usage: select-server <port>\n");
exit(1);
}
port = atoi(argv[1]);
/*
* Get socket - INTERNET DOMAIN - TCP
*/
if ((S = socket(AF_INET, SOCK_STREAM, 0)) < 0)
ERROR("select-server: socket");
/*
* Obtain host name & network address
*/
gethostname(servhost, sizeof(servhost));
if ((server = gethostbyname(servhost)) == NULL)
{
fprintf(stderr,"%s: unknown host\n",servhost);
exit(1);
}
/*
* Bind to server address - in network byte order
*/
sock.sin_family = AF_INET;
sock.sin_port = htons(port);
memcpy(&sock.sin_addr, server->h_addr, server->h_length);
/*
* Bind socket to port/addr
*/
if (bind(S, (struct sockaddr *)&sock, sizeof(sock)) < 0)
ERROR("server: bind");
/*
* Listen on this socket
*/
if (listen(S,MAXPENDING) < 0)
ERROR("server: listen");
/* Initialize the set of active sockets. */
FD_ZERO (&activefds);
FD_SET (S, &activefds);
while(1)
{
/* Block until input arrives on one or more active sockets. */
readfds = activefds;
if (select (FD_SETSIZE, &readfds, NULL, NULL, NULL) < 0)
{
perror ("select");
exit (EXIT_FAILURE);
}
/* Service all the sockets with input pending. */
for (i = 0; i < FD_SETSIZE; ++i)
{
if (FD_ISSET (i, &readfds))
{
if (i == S)
{
/* Connection request on original socket. */
/*
* Accept connections. Once connected, the client will be
* connected on fd NS, a second and third parameter may be passed
* to accept which will be filled in with information regarding the
* client connection if desired.
*
* In this example, once connected the server is done with the
* master socket (so closes it).
*/
if ((NS = accept(S,NULL,NULL)) < 0)
ERROR("server: accept");
FD_SET(NS, &activefds); //add the new socket desc to our active connections set
}
else
{
/* Data arriving on an already-connected socket. */
handle_connection(i, &activefds);
}
}
} /* //end of for */
} /* //end of while */
return(0);
}
void handle_connection(int NS, fd_set * activefds)
{
char buf[BUFSIZE]; /* Input buffer */
FILE *fp; /* Stream (converted file des.) */
/*
* Using stdio library to read from socket
*/
if (!(fp = fdopen(NS,"r")))
{
fprintf(stderr,">>> Error converting file des. to stream <<<\n");
exit(1);
}
//if fgets fails we have end of line, quit
if(!(fgets(buf,BUFSIZE,fp)))
{
/*
* DONE - simply close() connection
*/
fclose(fp);
close(NS);
FD_CLR(NS, activefds);
}
else
printf("%s", buf);
}
```
**Additional Information**
[http://www.uoguelph.ca/~jernst/3110/cis3310-lab4.pdf](https://web.archive.org/web/20141108152309/http://www.uoguelph.ca/~jernst/cis3110/cis3310-lab4.pdf)
[http://www.socs.uoguelph.ca/~dbm/teaching/CIS3110/](https://web.archive.org/web/20141108152309/http://www.socs.uoguelph.ca/~dbm/teaching/CIS3110/)
[https://beej.us/guide/bgnet/](https://beej.us/guide/bgnet/)
manpages!