Tuesday, September 27, 2011

Tutorial on Sockets

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 { 
} 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");

port = atoi(argv[1]); 
/* * 

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 

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.

Fork Server
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.

Select Server

No comments:

Post a Comment