... this page is part of the Web Site of George North ...

BSD Unix Sockets

by: George North
439-68-5643

Program Assignment 2 Principles of Operating Systems I
CSCI 4401
Fall 1995
Instructor: M. Rasit Eskicioglu




TABLE OF CONTENTS

System Logic Diagram

What I have Learned

Program Design

Program Analysis

Some Code



System Logic Design



What I have learned

Increasingly, interprocess communication (IPC) is a requirement of system and applications software developers. Workstations and servers connected by a high speed network is the most common computing environment. These systems share the fact that there is no global system clock and no shared memory. The potential hazards of making these systems work together are deadlock and blocking.

The Cigarette Smokers problem generalizes to the client - server model. Common examples of client server systems are: Finger, Telnet, ftp, NFS (Network File System), X Window System, Gopher. During this last year, the World Wide Web has become the most popular of these type of systems. In this model, the (one or more) server process provides a service to the (one or more) client processes. When service is available, the server process listens on a known communications link, and the client process uses a connect method to reach the server. The pipe is the IPC mechanism most characteristic of UNIX. A pipe permits a reliable unidirectional byte stream between two processes. Implemented as an ordinary file, it is of a small fixed size. A benefit to this structure of pipes is that data are seldom actually written to disk -- kept in memory by the normal block buffer cache. BSD sockets are Unix system calls for this type of protocol. They are part of the Unix kernel and are the lowest practical level of implementing client/server systems. Sockets have been adopted by the PC world and form the highest common denominator for IPC between PC and Unix platforms. As low level protocols, sockets are powerful, flexible, and system dependent. The low level and concurrent nature of sockets makes coding tedious and debugging difficult.

Network Support (especially over dial-up telephone lines) may be the most common systems implemented using the client - server model. User processes communicate with network protocols (and thus with other processes on other machines) via the socket facility. The server usually uses fork to produce a new process after accepting a request for service. In this way the original server process continues to listen for more connection request. When a connection needs to be closed, the child process closes its socket and terminates -- effectively destroying its associated socket. This method allows for acquisition of finite supply of resources one at a time. And allows access to these resources in different quantities.
For example:
Many clients may request service at the same time
Client connect to the service only one at a time
Once established, multiple client connections can be services.

The UNIX socket protocols provide the tools needed by software developers to implement interprocess communications and to avoid the potential hazards of deadlock and blocking.



Program Design

shop.c
The server procedure, shop.c will randomly supply 2 of three components needed to make a cigarette. This will be simulated by reading a file from standard input with the next components to make available.

smoker.c
The three (3) client procedures will each have one of the three components needed to complete a cigarette.

Shop process will assume that three (3) smoker process exist and are requesting connection and access to the two missing components needed to make a cigarette. Shop will provide ONLY one pair of components at a time. As follows:

Shop listens (select) for a connection then accepts maximum of three connections from Smoker processes. Using a loop until end of file, Shop reads next two components from standard input. These components are (broadcast) output (write) to all Smokers. Shop than selects the smoker with missing component, than (using read()) waits for Smoker to signal that he is finished. Shop now loops to get next two components.

Each Smoker process establishes their own socket connection with the shop. In the main loop, Smoker uses select to insure a message is available for reading. Than reading the two components available decides if to respond to this message. If Smoker holds missing component, he responds by writing a message with missing component, than sleeps a while (smokes) before writing a message that he is finished. Smoker now loops to await next components.

Refer to the Shop - Smoker logic diagram above ...



Program Analysis

Establishing a socket connection between Shop (server) and Smoker (client) is accomplished using connect - select - accept. Smokers request a socket connection by using connect(). Shop listens for requested connections using select(), then establishes a socket connection with accept(). A loop can be used in process Shop to establish the maximum number of connections declared using listen().

In their main loops, Shop and Smokers processes use write(), select(), read() to control communications. Shop broadcasts to all Smokers the two available components using write(). Smokers use select() to insure a message is available, then read(). Shop waits for a response, using select() to detect which Smoker is responding. Since read() is a blocking construct, select() is used to prevent a process from entering a blocked state without expectation of completion. Write() is not a blocking construct and does not need similar protection. When two processes will exchange a known number of messages, only the first read() needs to be protected with select() -- as is the case at the end of Shop's main loop.

Both deadlock and starvation are easily possible in a client/server message exchange system like the one described above. Every critical construct needs to be checked for possible error conditions and provided with safe exit methods.

Proper termination of all processes is also important. In above system, Shop notifies all Smokers with a special message that communications is termination. The Smoker process uses one read() construct to detect both next components and end of communications. This insures that Shop and Smoker processes will coordinate termination.




Code Example

Shop -- Main Loop

    while(not end of file)
    get(2components);
    {
        for(every smoker process)   /* broadcast available components */
        {
            if (wirte(smokerSock(#), ³2components², sizeof(2components)) < 0)       {
                perror(³??? no smokers²);
                exit (?);           }
        }

        successfulRead = 0;
        do              /* loop waiting a response */
        {

                        /* select a responding smoker */
            if (select(smokerSock(#)+1, &readset, NULL, etc,)) == -1        {
                perror(³??? no smokers²);
                exit (?);           }
                        /* read smoker reply */
            if (FD_ISSET(smokerSock(#), &readset))          {
                if (read(smokerSock(#), etc.)) < 0      {
                    perror(³??? no smokers²);
                    exit (?);       }
                        /* wait for smoker to finish */
                if (read(smokerSock(#), etc.)) < 0      {
                    perror(³??? no smokers²);
                    exit (?);       }   }
                successfulRead = 1;

        } while(successfulRead == 0);

    }
    
    
    
Smoker -- Main Loop

    while(message NOT equal to ³DN²)    /* loop until shop closes */
    {

                        /* select a message from shop */
        if (select(shopSock+1, &readset, NULL, etc,)) == -1             {
            perror(³??? no shop²);
            exit (?);           }

                        /* read shop message */
        if (FD_ISSET(shopSock, &readset))           {
            if (read(shopSock, etc.)) < 0       {
                perror(³??? no shop²);
                exit (?);       }
                                }

                        /* is this my missing component */
        if (this is my missing component)           {
            if (wirte(shopSock, ³myComponents², sizeof(myComponents)) < 0)  {
                perror(³??? no shop²);
                exit (?);       }
            sleep(1);

                        /* let shop know I¹m done */
            if (wirte(shopSock, ³finished², sizeof(³finished²)) < 0)        {
                perror(³??? no shop²);
                exit (?);       }
                                }

    }