NEXT UP previous
Next: Example Server

Library Functions

What is required is some sort of simple library of functions which will provide the standard, reliable, stream based, client/server communication facilities by default. Only if you require something out of the ordinary will you then need to delve deeper into the manual to sort out the options. This is a bit like the idea behind the standard I/O library functions for files (fopeno, fcloseO etc.).

The simplest arrangement is just to have a function to call if you want to set up a server and another function to call to set up a client. Two further calls will create and destroy the sockets over which the server and client communicate. In addition to this, you also want to make sure that as few parameters as possible need to be specified so that, where ever possible, sensible defaults are chosen instead.

The following code presents a tiny socket library, which you can include in your own code, and which provides exactly this functionality. After that, a simple application is also presented to demonstrate the use of the library.

The library itself consists of two files: the source code, which you compile and link into your applications, and a header file, to #include into your code, which declares some symbolic constants, a data structure and the prototypes for the library functions themselves.

The functions in the library are sopen(), sclose(), sserver() and schent(). In essence, a server process will call the sopen() function, which creates a socket and stores the socket descriptor, along with some administrative information, in a structure. The function then returns a pointer to this structure as its return value. You then pass the socket structure pointer, along with the port number that the server will use to offer connections to clients, as parameters to the sserver() call. When a client connects with the server the sserver() call returns with a socket descriptor which can then be used to communicate with the client using, for example, read( and write(). Connections with other clients can be made by further calls to sserver(), using the same socket structure pointer and port number as before. Socket descriptors returned by sserver() should be released with close() when the client communication is over, and the socket pointer returned by sopen() should be released with sclose() when the server shuts down.

At the client end, a call to sopen() will create a socket and return a socket pointer as before. This pointer can then be passed, along with the server's address and port number, into sclient(). When the connection is established, sclient() returns a socket descriptor to communicate with the server. As in the server case, close() and sciose() should be used when the client finishes its task.

The sopen() Function

	/* 1 */
	SOCKET *sopen(void)
	{
		/* 2 */
		SOCKET *sp;

		/* 3 */
		if ((sp = (SOCKET *)malloc(sizeof(SOCKET)))==O) 
			return 0;

		/* 4 */
		if ((sp->sd = socket(AF_INET, SOCK_STREAM, 0))==-1)
		{
			free(sp); 
			return 0;
		}
		/* 5 */ 
		sp->sinlen = sizeof(sp->sin); 
		sp->bindflag = S_RESET;
		/* 6 */
		return sp;
	}

Working down the numbered comments in turn:

  1. The sopen() function is called to create a socket. The socket descriptor, along with some internal administrative information, is stored in a SOCKET structure (defined in <socklib.h>). A pointer to the SOCKET structure is returned by sopen() if the call is successful (a zero is returned on error). The pointer should be saved, to pass into other socket library functions. The SOCKET structure is a defined type, as follows:
    	typedef struct
    	{
    		struct sockaddrin sin; 
    		int sinlen;
    		int bindflag;
    		int sd;
    	} SOCKET;
    
    Where sd is the socket descriptor returned when the socket is created, sin and sinlen are the address and length information of the server socket, and bindflag is used in the server to ensure that the bind() system call is only used once on the server's socket.

  2. A pointer to a SOCKET is declared, whose value will eventually be the return value from sopen().

  3. A call to malloc() is made to obtain a block of memory big enough to store a SOCKET struct. If malloc() fails then sopen() returns the value 0 here.

  4. This code section creates a TCP socket (SOCK_STREAM) in the IP address family (AF_INET). If there are no errors, the socket descriptor returned by the socket() call is stored in the sd field of the previously malloc()ed structure. Otherwise, the allocated memory is released with free() and an error value of zero is returned.

  5. Once a socket has been successfully created, the sinlen and bindflag fields of the allocated structure are initialized.

  6. Finally a pointer to the initialized SOCKET structure is returned by sopen() to be passed on to other socket library calls.

The sclose() Function

	/* 1 */
	sclose(SOCKET *sp)
	{
		int sd;

		/* 2 */
		sd = sp->sd; 
		free(sp);
		/* 3 */
		return close(sd);
	}

  1. The function sclose() closes the socket associated with a given SOCKET pointer. The pointer is passed to the function as a parameter. A value of 0 is returned if the call is successful, otherwise, a -1 is returned on error.

  2. The socket descriptor in the sd field of the socket pointer structure is saved in a local variable so that the memory block pointed to by sp can safely be released with free().

  3. The saved socket descriptor can now be closed with the close() system call. This system call returns 0 if successful or -1 on error, which is exactly what sclose() should return, so this can all be done in one line.

sserver() Function

	/* 1 */
	sserver(SOCKET *sp, int port, int sync)
	SOCKET *sp;
	int port, sync;
	{
		int flags; 
		struct hostent *hostent; 
		char localhost[S_NAMLEN+1];

		/* 2 */
		if (sp->bindflag==S_RESET)
		{
			/* 3 */
			if (gethostname(localhost, S_NAMLEN)==-1
			|| (hostent = gethostbyname(localhost))==0) 
				return -1;

			/* 4 */
			sp->sin.sin_family = (short)hostent->h_addrtype; 
			sp->sin.sin_port = htons((umsigned short)port); 
			sp-gt;sin.sin_addr.s_addr = *(unsigned long *)hostent->h_addr;
	
			/* 5 */
			if (bind(sp->sd, (struct sockaddr *)&sp->sin, sp->sinlen)==-1
			|| listen(sp->sd, 5)==-1) 
				return -1;

			/* 6 */
			sp->bindflag = S_SET;
			
			/* 7 */
			switch (sync)
			{
				case S_DELAY:
					if ((flags = fcntl(sp->sd, F_CETFL))==-l
					|| fcntl(sp->sd, F_SETFL, flags&~O_NDELAY)==-1) 
						return -1;

					break;

				case S_NDELAY:
					if ((flags = fcntl(sp->sd, F_CETFL))==-1
					|| fcntl(sp->sd, F_SETFL, flags|O_NDELAY)==-1) 
						return -1;

					break;

				default:
					return -1;
			}		

			/* 8 */

		}
		return accept(sp->sd, (struct sockaddr *)&sp->sin, &sp->sinlen);
	}

  1. The sserver() function sets up the calling process as a network server, and establishes client connections. The sp parameter is a SOCKET pointer previously returned by sopen(). The port parameter is the port number which should be bound with the local host IP address to the open socket, and the sync parameter specifies whether or not calls to sserver() will block if no client is waiting to establish a connection. The two possible values for sync are S_DELAY and S_NDELAY, which are defined in <socklib.h>. A call to sserver() will return -1 if an error occurs or if S_NDELAY is specified and no client connections are waiting to be accepted. In the latter case the externally declared integer ermo will be set to the value EWOULDBLOCK. If a connection with a client is established then the return value from sserver() will be a socket descriptor which can be used with read(), write(), etc., to communicate with the new client.

  2. The next task is to bind an address to the socket and then set up a queue to listen for connection requests. However, this should only be done the first time sserver() is called. In order to make sure that this is so, a flag called bindflag contained in the SOCKET structure associated with socket pointer sp, is tested, and only if it is reset will the body of the if construct be executed to call bind() and listen().

  3. Using the gethostname() function, the hostname for the local machine is copied into the localhost[] array. This is then passed into gethostbyname(), which returns a pointer to a hostent structure, filled with the appropriate information. If an error occurs in either of these function calls then a -1 is returned.

  4. The address family, port number and IP address are now copied from the hostent structure and the port parameter, into the socket address structure, which is stored in the SOCKET that is pointed to by the sp parameter.

  5. When everything is properly set up, the calls to bind() and listen() take place. If either of these calls generates an error then -1 is returned.

  6. To ensure that the preceding block of code can only execute once per socket, the bindflag is now set.

  7. The next job is to sort out the sync parameter. If sync has the value S_DELAY then fcntl() is used to reset the O_NDELAY flag in the associated socket descriptor. A sync value of S_NDELAY is used to set the associated O_NDELAY flag. Any other value of sync causes the default case to execute and return -1 as an error value.

  8. Finally, accept() is called to establish a connection with any waiting client. On success, the return value from accept() is a socket descriptor connected to the client and ready to go. This value is also returned by sserver(). If there is no client waiting, and the socket is set up to block, the accept() call will not return until a client makes a connection request, If there is no client and the socket is non-blocking, then accept will return -1 and set ermo to the value EWOULDBLOCK. This value is also returned by sserver().

The sclient() Function

	/* 1 */
	sclient(SOCKET *sp, char *name, int port)
	{
		struct hostent *hostent;

		/* 2 */
		if ((hostent = gethostbyname(name))==O) 
			return -1;

		/* 3 */
		sp->sin.sin_family = (short)hostent->h_addrtype; 
		sp->sin.sin_port = htons((unsigned short)port); 
		sp->sin.sin_addr.s_addr = *(unsigned long *)hostent->h_addr;
		/* 4 */
		if (cornect(sp->sd, (struct sockaddr *)&sp->sin, sp->sinlen)==-1) 
			return -1;

		/* 5 */
		return sp->sd;
	}

  1. The sclient() function tries to connect to a specified server on a given machine. Its three parameters are sp, which is a socket pointer returned from sopen(), name, which is the machine name of the server, and port, which is the server's port number on the machine. The function waits until a connection with the server is established and then returns a socket descriptor connected to the server, or -1 on error.

  2. The gethostbyname() function is used to fill a hostent structure given the server's hostname.

  3. The socket address structure associated with this SOCKET is filled in from the hostent structure and the port parameter.

  4. The connect() system call is now used to bind a local address to the local socket and attempts to make contact with the chosen server. If the server has got as far as doing a listen() call then the client's connect() request will be queued at the server, waiting for it to call accept().

  5. Once the connect() call returns without error, and the connection is established, the sclient() function returns the socket descriptor associated with the client's socket.

Library Preamble

	#include <stdio.h>
	#include <fcntl.h>
	#include <sys/types.h> 
	#include <sys/socket.h> 
	#include <netdb.h>
	#include <netinet/in.h>

	#define S_LIBRARY
	#include "socklib.h"

This is the set of standard #include files for socket library applications and also the definition of a symbol which will signify to the socket library header file that it is being included in the socket library code. This will cause the definition of some extra symbols in the header file that are only defined and used within the library routines themselves.

The socklib.h Header File

	#include <netinet/in.h>

	/* 1 */
	#define S_DELAY 0
	#define S_NDELAY 1

	/* 2 */
	#ifdef S_LIBRARY
	#define S_HESET 0
	#define S_SET 1
	#define S_NAMLEN 64
	#endif

	/* 3 */
	typedef struct
	{
		struct sockaddr_in sin; 
		int sinlen;
		int bindflag; 
		int sd;
	} SOCKET;

	/* 4 *1
	SOCKET *sopen(void);
	int sclose(SOCKET *);
	int sserver(SOCKET *, int, int);
	int sclient(SOCKET *, char *, int);

  1. These are the definitions of the S_OELAY and S_NDELAY symbolic constants used to select blocking or non-blocking server operation.

  2. Extra symbols only defined within the library itself.

  3. Definition of the SOCKET structure. This is the socket library equivalent of the FILE structure in the standard I/O library. The #include <netinet/in.h> line at the top of the file is for the definition of struct sockaddr_in used within the SOCKET structure.

  4. ANSI function protocols for the functions in the socket library so the compiler can check the correct usage of parameter numbers and types in application code.

NEXT UP previous
Next: Example Server