进程间通信: 管道

管道

管道可以实现在两个进程间通信, 通过使用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  57 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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值