17.2. STREAMS-Based PipesA STREAMS-based pipe ("STREAMS pipe," for short) is a bidirectional (full-duplex) pipe. To obtain bidirectional data flow between a parent and a child, only a single STREAMS pipe is required.Recall from Section 15.1 that STREAMS pipes are supported by Solaris and are available in an optional add-on package with Linux.Figure 15.2 is that the arrows have heads on both ends; since the STREAMS pipe is full duplex, data can flow in both directions. Figure 17.1. Two ways to view a STREAMS pipe[View full size image] ![]() Figure 17.2. Inside a STREAMS pipe![]() Figure 17.3. Inside a STREAMS pipe with a module![]() ExampleLet's redo the coprocess example, Figure 15.18, with a single STREAMS pipe. Figure 15.17). We call a new function, s_pipe, to create a single STREAMS pipe. (We show versions of this function for both STREAMS pipes and UNIX domain sockets shortly.)The parent uses only fd[0], and the child uses only fd[1]. Since each end of the STREAMS pipe is full duplex, the parent reads and writes fd[0], and the child duplicates fd[1] to both standard input and standard output. 1993] covers STREAMS-based pipes in more detail. Recall from Figure 15.1 that FreeBSD supports full-duplex pipes, but these pipes are not based on the STREAMS mechanism. Figure 17.4. Program to drive the add2 filter, using a STREAMS pipe#include "apue.h" static void sig_pipe(int); /* our signal handler */ int main(void) { int n; int fd[2]; pid_t pid; char line[MAXLINE]; if (signal(SIGPIPE, sig_pipe) == SIG_ERR) err_sys("signal error"); if (s_pipe(fd) < 0) /* need only a single stream pipe */ err_sys("pipe error"); if ((pid = fork()) < 0) { err_sys("fork error"); } else if (pid > 0) { /* parent */ close(fd[1]); while (fgets(line, MAXLINE, stdin) != NULL) { n = strlen(line); if (write(fd[0], line, n) != n) err_sys("write error to pipe"); if ((n = read(fd[0], line, MAXLINE)) < 0) err_sys("read error from pipe"); if (n == 0) { err_msg("child closed pipe"); break; } line[n] = 0; /* null terminate */ if (fputs(line, stdout) == EOF) err_sys("fputs error"); } if (ferror(stdin)) err_sys("fgets error on stdin"); exit(0); } else { /* child */ close(fd[0]); if (fd[1] != STDIN_FILENO && dup2(fd[1], STDIN_FILENO) != STDIN_FILENO) err_sys("dup2 error to stdin"); if (fd[1] != STDOUT_FILENO && dup2(fd[1], STDOUT_FILENO) != STDOUT_FILENO) err_sys("dup2 error to stdout"); if (execl("./add2", "add2", (char *)0) < 0) err_sys("execl error"); } exit(0); } static void sig_pipe(int signo) { printf("SIGPIPE caught\n"); exit(1); } Figure 17.5. Arrangement of descriptors for coprocess![]() ExampleSTREAMS-Based s_pipe FunctionFigure 17.6 shows the STREAMS-based version of the s_pipe function. This version simply calls the standard pipe function, which creates a full-duplex pipe. Figure 17.6. STREAMS version of the s_pipe function#include "apue.h" /* * Returns a STREAMS-based pipe, with the two file descriptors * returned in fd[0] and fd[1]. */ int s_pipe(int fd[2]) { return(pipe(fd)); } 17.2.1. Naming STREAMS PipesNormally, pipes can be used only between related processes: child processes inheriting pipes from their parent processes. In Section 15.5, we saw that unrelated processes can communicate using FIFOs, but this provides only a one-way communication path. The STREAMS mechanism provides a way for processes to give a pipe a name in the file system. This bypasses the problem of dealing with unidirectional FIFOs.We can use the fattach function to give a STREAMS pipe a name in the file system.
Figure 17.7. A pipe mounted on a name in the file system![]()
17.2.2. Unique ConnectionsAlthough we can attach one end of a STREAMS pipe to the file system namespace, we still have problems if multiple processes want to communicate with a server using the named STREAMS pipe. Data from one client will be interleaved with data from the other clients writing to the pipe. Even if we guarantee that the clients write less than PIPE_BUF bytes so that the writes are atomic, we have no way to write back to an individual client and guarantee that the intended client will read the message. With multiple clients reading from the same pipe, we cannot control which one will be scheduled and actually read what we send.The connld STREAMS module solves this problem. Before attaching a STREAMS pipe to a name in the file system, a server process can push the connld module on the end of the pipe that is to be attached. This results in the configuration shown in Figure 17.8. Figure 17.8. Setting up connld for unique connections![]() Figure 17.9. Using connld to make unique connectionsSection 17.4.1.1990] for the Research UNIX system. These mechanisms were then picked up by SVR4.We will now develop three functions that can be used to create unique connections between unrelated processes. These functions mimic the connection-oriented socket functions discussed in Section 16.4. We use STREAMS pipes for the underlying communication mechanism here, but we'll see alternate implementations of these functions that use UNIX domain sockets in Section 17.3.
Figure 17.10. The serv_listen function using STREAMS pipes#include "apue.h" #include <fcntl.h> #include <stropts.h> /* pipe permissions: user rw, group rw, others rw */ #define FIFO_MODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH) /* * Establish an endpoint to listen for connect requests. * Returns fd if all OK, <0 on error */ int serv_listen(const char *name) { int tempfd; int fd[2]; /* * Create a file: mount point for fattach(). */ unlink(name); if ((tempfd = creat(name, FIFO_MODE)) < 0) return(-1); if (close(tempfd) < 0) return(-2); if (pipe(fd) < 0) return(-3); /* * Push connld & fattach() on fd[1]. */ if (ioctl(fd[1], I_PUSH, "connld") < 0) { close(fd[0]); close(fd[1]); return(-4); } if (fattach(fd[1], name) < 0) { close(fd[0]); close(fd[1]); return(-5); } close(fd[1]); /* fattach holds this end open */ return(fd[0]); /* fd[0] is where client connections arrive */ } The serv_accept function (Figure 17.11) is used by a server to wait for a client's connect request to arrive. When one arrives, the system automatically creates a new STREAMS pipe, and the function returns one end to the server. Additionally, the effective user ID of the client is stored in the memory to which uidptr points. Figure 17.11. The serv_accept function using STREAMS pipes#include "apue.h" #include <stropts.h> /* * Wait for a client connection to arrive, and accept it. * We also obtain the client's user ID. * Returns new fd if all OK, <0 on error. */ int serv_accept(int listenfd, uid_t *uidptr) { struct strrecvfd recvfd; if (ioctl(listenfd, I_RECVFD, &recvfd) < 0) return(-1); /* could be EINTR if signal caught */ if (uidptr != NULL) *uidptr = recvfd.uid; /* effective uid of caller */ return(recvfd.fd); /* return the new descriptor */ } A client calls cli_conn (Figure 17.12) to connect to a server. The name argument specified by the client must be the same name that was advertised by the server's call to serv_listen. On return, the client gets a file descriptor connected to the server. Figure 17.12. The cli_conn function using STREAMS pipes#include "apue.h" #include <fcntl.h> #include <stropts.h> /* * Create a client endpoint and connect to a server. * Returns fd if all OK, <0 on error. */ int cli_conn(const char *name) { int fd; /* open the mounted stream */ if ((fd = open(name, O_RDWR)) < 0) return(-1); if (isastream(fd) == 0) { close(fd); return(-2); } return(fd); } Section 17.6, we'll see how these three functions are used. |