Each process ( parent and child ) should first close the ends of the pipe that they are not using. For example, if the parent is writing to the pipe and the child is reading, then the parent should close the reading end of its pipe after the fork and the child should close the writing end. Figure 3.22 shows an ordinary pipe in UNIX, and Figure 3.23 shows code in which it is used. Figure 3.24
79 Figure 3.25 and Figure 3.26 Ordinary pipes in Windows are very similar oWindows terms them anonymous pipes oThey are still limited to parent-child relationships. oThey are read from and written to as files.
80 oThey are created with CreatePipe( ) function, which takes additional arguments. oIn Windows it is necessary to specify what resources a child inherits, such as pipes. Figure 3.27 and Figure 3.28
81 Figure 3.29 188.8.131.52 Named Pipes Named pipes support bidirectional communication, communication between non parent-child related processes, and persistence after the process which created them exits. Multiple processes can also share a named pipe, typically one reader and multiple writers. In UNIX, named pipes are termed fifos, and appear as ordinary files in the file system. o( Recognizable by a "p" as the first character of a long listing, e.g. /dev/initctl ) oCreated with mkfifo( ) and manipulated with read( ), write( ), open( ), close( ), etc. oUNIX named pipes are bidirectional, but half-duplex, so two pipes are still typically used for bidirectional communications. oUNIX named pipes still require that all processes be running on the same machine. Otherwise sockets are used. Windows named pipes provide richer communications. Full-duplex is supported. Processes may reside on the same or different machines Created and manipulated using CreateNamedPipe( ), ConnectNamedPipe( ), ReadFile( ),and WriteFile( ). Race Conditions ( Not from the book ) Any time there are two or more processes or threads operating concurrently, there is potential for a particularly difficult class of problems known as race conditions. The identifying characteristic of race conditions is that the performance varies depending on which process or thread executes their instructions before the other one, and this becomes a problem when the program runs correctly in some instances and incorrectly in others.
82 Race conditions are notoriously difficult to debug, because they are unpredictable, unrepeatable, and may not exhibit themselves for years. Here is an example involving a server and a client communicating via sockets: 1. First the server writes a greeting message to the client via the socket: const int BUFFLENGTH = 100; char buffer[ BUFFLENGTH ]; sprintf( buffer, "Hello Client %d!", i ); write( clientSockets[ i ], buffer, strlen( buffer ) + 1 ); 2. The client then reads the greeting into its own buffer. The client does not know for sure how long the message is, so it allocates a buffer bigger than it needs to be. The following will read all available characters in the socket, up to a maximum of BUFFLENGTH characters: const int BUFFLENGTH = 100; char buffer[ BUFFLENGTH ]; read( mysocket, buffer, BUFFLENGTH );