Helping ordinary people create extraordinary websites!
HOME TUTORIALS SCRIPTS WEB HOSTING BLOG FORUM
Get Our Newsletter
Email:

Understanding Sockets in Unix, NT, and Java

By Ken Nordby
2003-05-26


Server program (Unix)

The server program creates a continuously-running process that listens on a known port for requests from client machines. The port is known in the sense that all client programs written for this application know the specific network address of the server. When a client request arrives, the server invokes the man command and transmits the output back to the client. The server then resumes listening.

Environment
The server program was developed in the C language on AIX 4.2 with the vi editor and the IBM xlC compiler. The server program is invoked from the command line.

Header files
The following header files are required for this program:

  • sys/types.h
    General-purpose data definitions
  • sys/socket.h
    Socket data definitions
  • netdb.h
    Additional socket data definitions
  • netinet/in.h
    Internet data definitions
  • signal.h
    Required for signal ( ) system call
  • setjmp.h
    Required for setjmp ( ) system call
  • stdio.h
    Input/output data definitions

Depending on the Unix implementation, the following header files may also be required: arpa/inet.h, arpa/nameser.h, resolv.h, sys/un.h, sys/uio.h.

The symbolic constant MAX_IDLE specifies the number of minutes the server process is allowed to be idle before issuing a warning message to the user.

Creating a Socket
The server socket is created with the socket ( ) call, which takes the following arguments:

  • AF_INET
    The first argument, socket domain, selects the family of communication protocols that will be used to control the data flowing through the socket. AF_INET is a symbolic constant representing the Internet family of protocols. If the value of this argument is AF_UNIX, the socket will operate in the "Unix domain." This means it will communicate with other processes on the same Unix system only, and will not support communication across the network. Note that there is an equivalent set of symbolic constants beginning with PF (Protocol Family) rather than AF (Address Family).

  • SOCK_STREAM
    The symbolic constant SOCK_STREAM provides a value for socket type, which indicates whether communication through the socket will be connection-oriented or connectionless. SOCK_STREAM signifies that the communication will be connection-oriented, whereas SOCK_DGRAM signifies that communication will consist of the connectionless transmission of data packets called datagrams.

  • 0
    The protocol argument allows the programmer to specify a specific protocol within the protocol family. For example, the symbolic constant IPPROTO_TCP specifies the Transmission Control Protocol (TCP). Typically this argument is set to zero, allowing the system to select a protocol.

The next step is to initialize the socket address structure. Three important data fields are:

  • Address family
    AF_INET specifies the Internet family of protocols.

  • Network address
    INADDR_ANY indicates that the server will accept messages using any Internet address available on the machine (for machines with multiple network connections, this field allows you to single out a specific IP address).

  • Port number
    This integer identifies the server process to the network. The IP address identifies the Unix host on the Internet; the port number identifies a specific process running on the Unix host. Port numbers below 1025 are restricted (for example, the telnet protocol has permanently reserved port number 23). The sample program arbitrarily uses port number 10001.

The htonl ( ) and htons ( ) functions convert long and short integer values from local host byte ordering to network byte ordering.

Once the socket address structure has been properly initialized, the bind ( ) call associates the socket address structure with the socket descriptor, making the socket network-addressable. Any messages arriving at the specified Internet host machine and marked with the specified port number are delivered to the specified socket.

At this point the server process is ready to receive messages from the network, but it must first invoke listen ( ) and accept ( ) . Invoking listen ( ) is a preliminary step that informs the operating system that the server is ready to listen for messages. The listen ( ) call also allows a backlog limit to be specified. The usual value is 5, which indicates that the socket will allow 5 incoming messages to be queued up if they arrive faster than the server process can respond to them.

Once the server has notified the operating system that it is ready to listen for messages, the server process goes into waiting mode by issuing the accept ( ) system call. The accept ( ) call blocks until a message arrives.

The sample program uses signal and setjmp logic to display a user message at fixed intervals determined by MAX_IDLE. This is not required for sockets programming but was added as a user convenience-- it at least assures the user that the server process is still alive.

Connecting to a Client
When a connection request arrives from the network, the accept ( ) system call awakens from its blocked state and performs two key services: (1) it initiates a data connection with the requesting client, and (2) it creates an entirely new socket to support communications with the requesting client. Creation of a second socket relieves the dependency on the original socket, so that the server process is free to listen through the original socket for requests from other clients. Note that the socket address structure argument to the accept ( ) call, sas2, specifies a new, uninitialized socket address structure and the return value from the accept ( ) call is a new socket descriptor, sd2. Thus the accept ( ) call not only establishes a connection with a requesting client machine, but also creates a new socket to support communication with the requesting client. This frees up the original socket, sd1, to receive messages from other client machines.

Automatic creation of a second socket is necessary to support concurrent servers, which support many clients simultaneously. In contrast, the sample server program is an iterative server, which processes client requests serially. This iterative server is useful for tutorial purposes, but production servers typically follow the concurrent model. In the Unix world, concurrent servers traditionally use the fork ( ) system call to spawn an independent process to handle each client request. Each child process only lives long enough to process its request and transmit appropriate data back to the requesting client, while the parent process lives indefinitely. On non-Unix systems threads, rather than separate processes, are typically used to handle client requests. For simplicity, the sample server does not include any multiprocessing or multithreading capability, although a real server probably would.

Exchanging Data with the Client
The server process attempts to read bytes from the sd2 socket using the recv ( ) call. The hex value ff was chosen to indicate end-of-transmission, so the recv ( ) routine tests for this value as well as a negative return code. In addition to the recv ( ) socket system call for receiving data over a socket, there are variants such as recvmsg ( ) and recvfrom ( ) . The regular read ( ) system call can be used to read data over a socket, but its semantics are slightly different from when it is used to read data from a file.

An important lesson to learn about socket input and output is that I/O operations are relatively low-level and put responsibility on the programmer for the technical details of the data flow. For example, it is the programmer's responsibility to ensure that input and output routines can support different processing speeds between servers and clients, fluctuations in data transfer across the network, differences in buffer sizes between servers and clients, and so on. The combination of sockets technology and the TCP protocols, used pervasively in communications programming, provides a reliable data transfer system with a consistent programming interface at both end points. However, it does not automatically take care of all the details. For example, the programmer must choose a method for detecting end-of-transmission.

Once the end of the input data stream has been reached, as indicated by hex ff, the server processes the request. The request data is added to a command string containing the man command. The command string is passed to the operating system by means of the system ( ) call. Whatever output is generated by the man command is written to the temporary file tfile, which is then opened for reading, and the resulting data is written to the socket with send ( ) . When all strings contained in tfile have been written to sd2, the hex value ff is sent to signify end-of-transmission. The send ( ) operation is slowed by adding sleep ( ) to give the client process enough time to process each string of data, preventing buffer overflow. When recv ( ) finally detects hex ff or zero data, the server process interprets this as a disconnect from the current client and issues the accept ( ) call again to resume waiting for a new client. This cycle continues until the user terminates the server process.

Code for server program



<xmp>
/*Server Program - Unix*/
#include
#include
#include
#include
#include
#include
#include
#include
#define MAX_IDLE 3
jmp_buf stack;
void handler(int);

main()
{
/* sd1=server socket descriptor */
/* sd2=client socket descriptor */
/* sas1=server socket structure */
/* sas2=client socket structure */
int sd1,sd2,rc,ctr;
struct sockaddr_in sas1,sas2;
unsigned long size;
char buffer&amp;lbracket.256&amp;rbracket.,command&amp;lbracket.256&amp;rbracket.;
char fox&amp;lbracket.&amp;rbracket.={'\xff'};
char stars&amp;lbracket.&amp;rbracket.="********************\n";
char *ptr;
FILE* tfile;

signal(SIGALRM,handler);
/* create socket */
sd1=socket(AF_INET,SOCK_STREAM,0);
if (sd1&amp;lt;0)
{
perror("error creating server socket");
exit(1);
}
printf("\nServer socket created\n");
/* initialize socket structure */
sas1.sin_family=AF_INET;
sas1.sin_len=sizeof(sas1);
sas1.sin_addr.s_addr=htonl(INADDR_ANY);
sas1.sin_port=htons(10001);
/* bind socket */
rc=bind(sd1,(struct sockaddr *)&amp;
if (rc!=0)
{
perror("Error binding server socket");
exit(1);
}
printf("Server socket bound\n");
/* start listening */
rc=listen(sd1,5);
if (rc!=0)
{
perror("Error listening");
exit(1);
}
printf("Server socket listening\n");
size=sizeof(sas2);
/* loop indefinitely, waiting for incoming connection requests */
while (1)
{
setjmp(stack);
alarm((MAX_IDLE * 60));
sd2=accept(sd1,(struct sockaddr *)&amp;sas2,&amp;size);
if (sd2&lt;0)
{
perror("Error issuing accept call");
exit(1);
}
printf("Server socket connected to client socket\n\n");
alarm(0);
/* clear receive buffer */
for (ctr=0;ctr&lt;256;ctr++)
buffer&amp;lbracket.ctr&amp;rbracket.='\0';
rc=1;
ptr=buffer;
/* receive data from client */
while (rc&gt;0)
{
rc=recv(sd2,ptr,1,0);
if (rc&gt;0)
{
if (*ptr!=0xff) ptr++;
else
{
printf("Received request\n");
/* generate response for client */
*ptr='\0';
strcpy(command,"man ");
strcat(command,buffer);
strcat(command," &gt; tfile 2&gt;&amp;1");
system(command);
tfile=fopen("tfile","r");
/* send data to client */
send(sd2,stars,strlen(stars),0);
while (fgets(command,256,tfile))
{
send(sd2,command,strlen(command),0);
sleep(1);
}
fclose(tfile);
send(sd2,stars,strlen(stars),0);
printf("Sent response\n\n");
send(sd2,fox,1,0);
ptr=buffer;
}
}
else
{
printf("Client socket disconnected\n\n\n");
printf("Server socket listening\n\n");
alarm((MAX_IDLE * 60));
break;
}
} /* end while */
} /*end while */
} /* end main() */
void handler(int s)
{
char inchar;
signal(s,SIG_IGN);
fflush(stdin);
printf("\nServer idle for %i minutes, continue? (y/n)",MAX_IDLE);
if (inchar=getchar()!='y') exit(0);
printf("\n");
printf("Server socket listening\n\n");
signal(SIGALRM,handler);
longjmp(stack,-1);
} /* end handler() */
&lt;/xmp&gt;

To cut and paste this code, click here.



Tutorial Pages:
» Understanding Sockets in Unix, NT, and Java
» Basic sockets concepts
» A simple example
» Sample sockets application
» Server program (Unix)
» Client program (NT)
» Client program (Java)
» Further study


First published by IBM DeveloperWorks


 | Bookmark
Related Tutorials:
» All about JAXP, Part 1
» Make Database Queries Without the Database
» Load List Values for Improved Efficiency
» 2 Ways To Implement Session Tracking
» A Simple Way to Read an XML File in Java
» Develop Aspect-Oriented Java Applications with Eclipse and AJDT