管道
管道可以实现在两个进程间通信, 通过使用popen和pclose函数, 其原型如下:
#include <stdio.h>
FILE *popen(const char* command, const char *open_mode);
int pclose(FILE *steam_to_close);
open函数允许一个程序将另一个程序作为新的进程启动, 并传递数据给它或者接收它传递的数据。
command是要运行的程序名和相应的参数。
open_mode 必须是”r” 或者”w”; r表示获取到一个可读的FILE, 可以从中读取command程序的数据。 w表示获取到一个可写的FILE,可以向其写数据, command程序可以读取写过来的数据。
每个popen调用必须是r或者w,不能同时进行读写, 如果要想实现双向通信, 可以使用两个管道。
pipe调用
#include <unistd.h>
int pipe(int file_descriptor[2]);
file_descriptor是两个整数类型的文件描述符组成的数组指针。调用成功后返回0, 失败返回-1,并设置errno。
pipe将两个文件描述符以一种特殊的方式连接起来, 写到file_descriptor[1]中的数据都可以从file_descriptor[0]中读出来。而且只能这样读写, 反过来调用将失败。数据基于FIFO原则进行处理, 即读出顺序是按写入顺序来的。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(){
int data_processed;
int file_pipes[2];
const char some_data[] = "123";
char buffer[BUFSIZ + 1];
memset(buffer, '\0', sizeof(buffer));
if (pipe(file_pipes) == 0) {
data_processed = write(file_pipes[1], some_data, strlen(some_data));
printf("Wrote %d bytes\n", data_processed);
data_processed = read(file_pipes[0], buffer, BUFSIZ);
printf("Read %d bytes: %s\n", data_processed, buffer);
exit(EXIT_SUCCESS);
}
exit(EXIT_FAILURE);
}
这个程序创建一个管道, 然后用文件描述符file_pipes[1]写数据, 用file_pipes[0]中读出数据。
编译输出结果如下:
$ ./pipe1
Wrote 3 bytes
Read 3 bytes: 123
fork创建新进程时, 原先打开的文件描述符仍是打开的状态。 如果在原先的进程中创建一个管道, 然后再调用fork创建新进程,即可通过管道在两个进程间通信。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main()
{
int data_processed;
int file_pipes[2];
const char some_data[] = "123";
char buffer[BUFSIZ + 1];
pid_t child_pid;
memset(buffer, '\0', sizeof(buffer));
if (pipe(file_pipes) == 0) {
child_pid = fork();
if (child_pid == -1) {
fprintf(stderr, "fork failure...");
exit(EXIT_FAILURE);
}
if (child_pid == 0) {
data_processed = read(file_pipes[0], buffer, BUFSIZ);
printf("Read %d bytes: %s\n", data_processed, buffer);
exit(EXIT_SUCCESS);
} else {
data_processed = write(file_pipes[1], some_data, strlen(some_data));
printf("Wrote %d bytes\n", data_processed);
}
}
exit(EXIT_SUCCESS);
}
上面这个程序通过pipe调用创建一个管道,fork创建一个子进程,在主线程中写入数据,并在子线程中读出。
$ ./pipe2
Wrote 3 bytes
Read 3 bytes: 123
命名管道
命名管道是特殊类型的文件, 在文件系统中文件名的形式存在, 其行为与上面的管道类似。 如果想在不相关的进程间通信, 可以采用命名管道。
命令行创建命名管道方法是mkfifo filename。 在程序中可以通过调用函数mkfilo。其原型是
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *filename, mode_t mode);
程序:
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
int res = mkfifo("/tmp/test_fifo", 0777);
if (res == 0) {
printf("FIFO created\n");
exit(EXIT_SUCCESS);
}
}
$ ls -lF /tmp/
prwxrwxr-x 1 wlyuan wlyuan 0 5月 7 17:25 test_fifo|
输出结果中第一字符p表示这是一个管道, 删除可以通过rm或者unlink调用。
使用test_fifo
$ cat < /tmp/test_fifo &
[2] 5629
$ echo "test fifo" > /tmp/test_fifo
$ test fifo
open打开fifo命名管道
与打开普通文件的区别:
1. 不能以O_RDWR方式打开fifo,结果未定义。如果想实现在进程间双向传递数据, 最好使用一对fifo。
2. open_flag(open函数的第二个参数) O_NONBLOCK的用法。
open(const char *path, O_RDONLY);
open调用将阻塞, 除非有一个进程以写的方式打开同一个FIFO,否则它不会返回。
open(const char *path, O_RDONLY | O_NONBLOCK);
这个open调用将成功并立即返回,即使没有其他进程以写的方式打开fifo。
open(const char *path, O_WRONLY);
这种方式将阻塞,直到有进程以读的方式打开同一个fifo。
open(const char *path, O_WRONLY | O_NONBLOCK);
立即返回, 如果没有进程以读的方式打开fifo, 将返回错误-1, fifo也不会打开。
一个客户服务器例子
该例子是通过客户端向服务器server管道写入数据, 服务器端将读取到客户端写入的数据, 将其转换成大写字母, 再写入客户端的管道, 客户端读取结果显示。
client.h
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#define SERVER_FIFO_NAME "/tmp/serv_fifo"
#define CLIENT_FIFO_NAME "/tmp/cli_%d_fifo"
#define BUFFER_SIZE 20
struct data_to_pass_st {
pid_t client_pid;
char some_data[BUFFER_SIZE-1];
};
server.c
#include "client.h"
#include <ctype.h>
int main()
{
int server_fifo_fd, client_fifo_fd;
struct data_to_pass_st my_data;
int read_res;
char *tmp_char_ptr;
char client_fifo[256];
mkfifo(SERVER_FIFO_NAME, 0777);
server_fifo_fd = open(SERVER_FIFO_NAME, O_RDONLY);
if (server_fifo_fd == -1) {
fprintf(stderr, "server fifo open failure...");
exit(EXIT_FAILURE);
}
sleep(10);
do {
read_res = read(server_fifo_fd, &my_data, sizeof(my_data));
if (read_res > 0) {
tmp_char_ptr = my_data.some_data;
while(*tmp_char_ptr) {
*tmp_char_ptr = toupper(*tmp_char_ptr);
tmp_char_ptr++;
}
}
sprintf(client_fifo, CLIENT_FIFO_NAME, my_data.client_pid);
client_fifo_fd = open(client_fifo, O_WRONLY);
if (client_fifo_fd != -1) {
write(client_fifo_fd, &my_data, sizeof(my_data));
close(client_fifo_fd);
}
} while(read_res > 0);
close(server_fifo_fd);
unlink(SERVER_FIFO_NAME);
exit(EXIT_SUCCESS);
}
client.c
#include "client.h"
#include <ctype.h>
int main()
{
int server_fifo_fd, client_fifo_fd;
struct data_to_pass_st my_data;
int times_to_send;
char client_fifo[256];
server_fifo_fd = open(SERVER_FIFO_NAME, O_WRONLY);
if (server_fifo_fd == -1) {
fprintf(stderr, "open server fifo failure...");
exit(EXIT_FAILURE);
}
my_data.client_pid = getpid();
sprintf(client_fifo, CLIENT_FIFO_NAME, getpid());
if (mkfifo(client_fifo, 0777) == -1) {
fprintf(stderr, "client open fifo failure...");
exit(EXIT_FAILURE);
}
for (times_to_send = 0; times_to_send < 5; times_to_send++) {
sprintf(my_data.some_data, "data from %d", my_data.client_pid);
printf("%d send %s, ", my_data.client_pid, my_data.some_data);
write(server_fifo_fd, &my_data, sizeof(my_data));
client_fifo_fd = open(client_fifo, O_RDONLY);
if (client_fifo_fd != -1) {
if (read(client_fifo_fd, &my_data, sizeof(my_data)) > 0) {
printf("received: %s\n", my_data.some_data);
}
close(client_fifo_fd);
}
}
close(server_fifo_fd);
unlink(client_fifo);
exit(EXIT_SUCCESS);
}
编译完成后, 通过下面shell脚本进行测试
#!/bin/sh
./server &
for i in 1 2 3 4 5
do
./client &
done