NEXT UP previous
Next: Example Client

Example Server

There are many ways to transfer files between two machines; each has its own advantages and disadvantages. The majority require a user wishing to transfer files from a particular machine to have some kind of login to that machine before the transfers can take place. The following piece of C code illustrates the use of sockets and the tiny socket library to implement a simple server which allows clients to make requests to download files with a simpler protocol.

On the server machine, all the files that remote users can access are placed into a single directory. The server is then executed from that directory. Subsequent client requests for any files in that directory will be supplied by the server, without any kind of login procedure or security checks being made. In other words, the server provides access to a set of files that are offered freely to anyone with a copy of the client software and a connection to the network used by the server machine.

Because downloading a large file can take an appreciable amount of time, some mechanism is required to deal with subsequent client requests while the current request is still in progress. The example server given below, deals with this situation by calling sserver() in an infinite loop. As soon as a client connection is established and sserver() returns with a socket descriptor, the server uses the fork() system call to create a child process which, because it has access to all of the parent's open files and sockets, can deal with the file transfer request while the parent process loops back to await further client connections. A large number of clients can transfer files simultaneously by this method, as each has its own child process created specifically to deal with its requirements.

The simple request/response protocol used by the example client and server is that the client specifies a file name to the server and the server then sends the contents of that file back to the client. The required file name is given to the server in two parts: first a byte is sent which specifies to the server the length of the file name, and then the bytes of the file name itself are sent. The server makes some simple checks regarding the validity and availability of the file name and, if these are passed, the server then sends the bytes of the file to the client. At the end of the transfer, the server (child) process terminates breaking the connection with this client.

/*******************EXAMPLE SOCKET SERVER************************/

	#include <stdio.h>
	#include <string.h> 
	#include <signal.h>
	#include <fcntl.h> 
	#include "socklib.h"

	/* Server's communication port number */
	#define PORT 2121

/****************************************************************

MAIN - A very simple file tranfer program, which sets up a 
concurrent socket server on the host machine on port number 
PORT to transfer any files from the current directory on request.
*****************************************************************/

	main(void)
	{
		SOCKET *sp; 
		int sd;

		/* Set SIGCHLD for no zombies */ 
		signal (SIGCHLD, SIG_IGN);

		/* Create server's socket */ 
		if ((sp = sopen())==O)

			/* Exit server if this fails */ 
			fatal("sopen()");

		/* Repeatedly accept and service client requests */
		for (;;)
		{
			/* Connect to client */
			if ((sd = sserver(sp, PORT, S_DELAY))==-1)
				fatal("sserver()");

			/* ForkO a child process */
			switch (fork())
			{
				/* Deal with client request in child */
				case 0:
					do_service(sd);

				/* Deal with forkO failure */
 				case -1:
					fatal("fork()");
			}

			/* In parent, close client and loop for next connection */ 
			close(sd);
		}
	}



/***********************************************************************

FATAL - This routine is called when an error condition is returned to 
the daemon, usually fron one of the systen calls it makes.
The function first prints the message pointed to by <text> and then 
terminates the process.
************************************************************************/

	fatal(char *text)
	{
		fprintf(stderr, "Error in %s\n", text); 
		exit(i)
	}



/***********************************************************************

DO_SERVICE - This function is called in the child process that is created 
when a client makes a successful connection. Its task is to service the 
client's request and supervise the required file transfer. The parameter 
<sd> is the socket descriptor to use for the communication.
************************************************************************/

	do_service(int sd)
	{
		int i, fd; 
		char *name; 
		char c, namlen;

		/* Get length of file name */ 
		if (read(sd, &namlen, 1)!=1)
			fatal("namlen readO");

		/* Space for name + " open()" */ 
		if ((name = (char *)malloc(namlen+8))==O)
			fatal("malloc()");

		/* Get required file name into name[] */ 
		for (i = 0; i<namlen; ++i)
			if (read(sd, &name[i], 1)!=1) 
				fatal("file name read()");

		/* Make name[] a string */ 
		name[i] = '\0';

		/* Check name[] is in current directory */ 
		if (strchr(name, '/'))
			fatal("illegal file name");

		/* Open specified file */ 
		if ((fd = open(name, O_RDONLY))==-1)
			fatal(strcat(name, " open()"));

		/* Transfer contents to client */ 
		while ((i = read(fd, &c, 1))!=O)
			if (i==-i) 
				fatal("file read()");
			else 
				if (write(sd, &c, 1)!=1) 
					fatal("write()");

		/* Terminate child and with it the connection to this client */ 
		exit(O);
	}

Even though it works, the way the server is coded at the moment is not sufficiently robust to allow it to be used in anger. We shall, however, return to this code again after we have looked at daemon processes.


NEXT UP previous
Next: Example Client