Level: Intermediate
Wu Yuhao (
ADAH@sh163.net)
This article describes a method of using the SocketPAIR system to implement a two-way process communication pipeline on Linux, and provides an implementation.
Problem and common methods
Linux provides Popen and PCLOSE functions (1) for creating and closing the pipe to communicate with another process. The interface is as follows:
FILE * POPEN (Const Char * Command, Const Char * Mode); int PClose (file * stream);
Unfortunately, the pipe created by POPEN can only be unidirerative - MODE can only be "r" or "w" without a certain combination - users can only choose either to write, either read, not It also reads and written in a pipe. In practical applications, there is often a requirement to read and write simultaneously, for example, we may want to send text data to the sort tool to retrieve the result. At this point, Popen cannot be used. We need to find other solutions.
One solution is to create two one-way pipes using the PIPE function (2). The code that has no error detection is as follows:
INT PIPE_IN [2], PIPE_OUT [2]; PID_T PID; PIPE (& PIPE_IN); // Creating a Parent Processing Pipe PIPE (& PIPE_OUT); // Creating Pipes used to write data in the parent process IF ((pID = fork ()) == 0) {// sub-process close (PIPE_IN [0]); // Close the child process read end close (PIPE_OUT [1]); // Close The sub-process written DUP2 (PIPE_IN [1], stdout_fileno) of the parent process; // copy the read pipe of the parent process to the standard output DUP2 (PIPE_OUT [0], stdin_fileno); // Copy the parent process Write a standard input close (PIPE_IN [1]); // Turn off the copy of the read pipeline (PIPE_OUT [0]); // Close the copied write-down / * Use the exec execute the command * /} else {// Parent Process Close (PIPE_IN [1]); // Close the write end close (PIPE_OUT [0]); // Close the read end / * of the write pipe / * can now write data to PIPE_OUT [1], And read the result from PIPE_IN [0] * / close (PIPE_OUT [1]); // Close the remaining data in PIPE_IN [0] * / close (PIPE_IN [0]); // Close Read Pipeline / * Use the wait series to wait for the sub-process to exit and obtain an exit code * /}
Of course, the readability of such code (especially after the error handling code) is less, it is not easy to encapsulate functions similar to the POPEN / PCLOSE, which is convenient for high-level code. The reason for the reason is that the pair of file descriptors returned by the PIPE function can only be read from the first one, and the second write is written (at least for Linux). In order to read and write at the same time, only two PIPE calls, two file descriptors can be taken.
A better solution
Use PIPE only. However, Linux implements a SocketPAIR call (3) derived from the BSD, which can achieve the function of reading and writing in the same file descriptor (this call is currently part of the POSIX specification). This system call can create a pair of connected (UNIX) unknown Socket. In Linux, you can use this pair of Socket as the file descriptor returned by the PIPE, the only difference is that any of the pair of file descriptors can be readable and writable. This seems to be a good way to implement inter-process communication pipelines. However, pay attention to, in order to solve the application problem of the use of Sort, we need to turn off the sub-process's standard input notification sub-process data has been sent, and then read data from the standard output of the sub-process until encountered EOF. If you use two one-way pipes, each conduit can be closed separately, so there is no problem; while when using a bidirectional pipe, if the pipe is not closed, the peer data has been sent, but closed the pipe and cannot be read from it. Result data. - If this problem does not resolve, the idea of using SocketPair will become meaningless.
What is happy is that the shutdown call (5) can resolve this issue. After all, the file descriptor generated by SocketPair is a standard operation on the Socket, and the standard operation on the socket can also be used, including Shutdown. - Using ShutDown, you can implement one and a semi-closed operation, notifying the end process no longer sends data while still using the file descriptor to receive data from the peer. The code that has no error detection is as follows:
INT FD [2]; PID_T PID; SocketPair (AF_UNIX, SOCKET_STREAM, 0, FD); // Creating Pipe IF ((PID = fork ()) == 0) {// Sub Process CLOSE (FD [0]); / / Turn off the parent process of the pipe (FD [1], stdout_fileno); / / copy the child process of the duct to the standard output DUP2 (FD [1], stdin_fileno); // Copy the child process of the pipe to standard input Close (fd [1]); // Close the copied read pipe / * Use the exec execute the command * /} else {// Parent process Close (fd [1]); // Turn off the sub-process end of the pipeline / * now You can read the data in FD [0] * / shutdown (fd [0], shut_wr); // Notify the peer data to send / * read the remaining data * / close (FD [0]); // Turn off the pipeline / * Use the wait series function waiting for the sub-process to exit and get an exit code * /}
It is very clear, which is a simple amount than the use of two one-way pipelines. I will make further packages and improvements on this basis.
Package and implementation
Use the above method directly, no matter how it looks, at least ugly and inconvenient. The maintainer of the program wants to see the logic of the program, not a variety of cumbersome details that complete a task. We need a good package.
The package can be used using C or C . Here, in accordance with UNIX, I provide a C package similar to the POSIX / PCLOSE function called in the POSIX standard to ensure maximum availability. The interface is as follows:
FILE * DPOPEN (Const Char * Command); Int Dpclose (file * stream); int dphalfclose (file * stream);
About the interface, the following points need to pay attention to:
- Similar to the PIPE function, DPOPEN returns a pointer to the file structure instead of a file descriptor. This means that we can use functions such as FPRINTF, and the file buffer caches data written to the pipeline (unless you use the SetBuf function to turn off the file buffer), to ensure that the data is required to write into the pipe to use the fflush function. - Since DPOPEN returns a readable and writable pipe, the second representation of POPEN means that the read / write parameters are no longer needed.
- In the two-way pipe, we need to notify the end write data, which is completed by the DPHALFCLOSE function.
Specific implementation, view the program source code directly, which has detailed comments and Doxygen document comments (6). I only make a few more ways:
- This implementation uses a linked list to record all DPOpen's corresponding relationships of the file pointer and sub-process ID, so the speed at the same time when using DPOPEN opens, the speed of DPClose is slightly slower. I think this will not have any problems during usual use. If this is a problem in some special cases, consider changing the return value type of DPOPEN and the type of DPCLOSE (less convenient to use, but simply), or use hash table / balance tree instead The list currently used to accelerate the lookup (the interface is constant, but the implementation is more complicated).
- When using the "-pthread" command line parameter in the GCC while compiling, this implementation enables POSIX thread support, using mutex to protect access to the list. Therefore this implementation can be safely used in the POSIX multi-threaded environment.
- Similar to POPEN (7), DPOPEN closes the pipe that opens with DPOpen in the child process generated by the Fork.
- If the parameter transmitted to DPClose is not a non-null value returned by DPOPEN, the current implementation is in addition to returning -1 means an error, and Errno is set to eBadf. For PCLOSE, this situation is considered an uncecified behavior (8) in the POSIX specification.
- There is no platform related characteristics in the implementation to facilitate porting to other POSIX platforms.
The following code shows a simple example, send multi-line text to sort, then retrieve the result, displayed:
#include
Apple Orange Pear
to sum up
This article describes a method of using the SocketPAIR system to implement a two-way process communication pipeline on Linux, and provides an implementation. The implementation provided is close to the POSE / PCLOSE function in the POSIX specification, which is very easy to use. This implementation does not use the platform-related feature, so it can be ported to the POSIX system that supports socketpair calls without modifying or only a small amount of modifications.
This article source code download:
DPOPEN.ZIP
Reference
1. The corresponding Man (3) page. Online viewing:
http://www.die.net/doc/linux/man/man3/popen.3.html
2. The corresponding Man (2) page. Online viewing:
http://www.die.net/doc/linux/man/man2/pipe.2.html
3. The corresponding Man (2) page. Online viewing:
http://www.die.net/doc/linux/man/man2/socketpair.2.html
4. POSIX specification:
Http://www.opengroup.org/onlinepubs/009695399/functions/socketpair.html
5. The corresponding Man (2) page. Online viewing:
http://www.die.net/doc/linux/man/man2/shutdown.2.html
6. Doxygen Homepage:
http://www.stack.nl/~dimitri/doxygen/
7. POSIX specification:
Http://www.opengroup.org/onlinepubs/009695399/functions/popen.html
8. POSIX specification:
Http://www.opengroup.org/onlinepubs/009695399/functions/pclose.html
About author
Wu Yizhen, currently engaged in research and development of high-performance intrusion detection systems on Linux. There is a strong interest in developing cross-platform, high performance, reusable C code.
Adah@sh163.net can contact him.
Full article:
IBM DeveloperWorks China