命名管道:FIFO

news2024/12/23 17:51:46

至此,我们还只能在相关的程序之间传递数据,即这些程序是由一个共同的祖先进程启动的。但如果我们想在不相关的进程之间交换数据,这还不是很方便。

我们可以用FIFO文件来完成这项工作,它通常也被称为命名管道(named pipe)。命名管道是一种特殊类型的文件(别忘了Linux中的所有事物都是文件),它在文件系统中以文件名的形式存在,但它的行为却和我们已经见过的没有名字的管道类似。

我们可以在命令行上创建命名管道,也可以在程序中创建它。过去,命令行上用来创建命名管道的程序是mknod,如下所示:

mknod my_fifo_01 p
$ ls -lsF *fifo*
0 prw-rw-r-- 1 lkmao lkmao 0 6月  14 08:56 my_fifo_01|

但mknod命令并未出现在X/Open规范的命令列表中,所以可能并不是所有的类UNIX系统都可以这样做。我们推荐使用的命令行命令是:

mkfifo my_fifo_02

有些老版本的UNIX系统只有mknod命令。X/Open规范的第4期第2版中有mknod函数调用,但没有对应的命令行程序。Linux系统非常友好,它同时支持mknod和mkfifo。在程序中,我们可以使用两个不同的函数调用,如下所示:

#include <sys/types.h>
#include <sys/stat.h>

int mkfifo(const char *pathname, mode_t mode);
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int mknod(const char *pathname, mode_t mode, dev_t dev);

与mknod命令一样,我们可以用mknod函数建立许多特殊类型的文件。要想通过这个函数创建一个命名管道,唯一具有可移植性的方法是使用一个dev_t类型的值0,并将文件访问模式与S_IFIFO按位或。我们在下面的例子中将使用较简单的mkfifo函数。

实验 创建命名管道

下面是程序fifo1.c的代码:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>

#define DEBUG_INFO(format, ...) printf("%s - %d - %s :: "format"\n",__FILE__,__LINE__,__func__ ,##__VA_ARGS__)

int main(int argc, char **argv)
{
    int res = mkfifo("./my_fifo",0777);
    if(res == 0){
        DEBUG_INFO("mkfifo ok");
    }else{
        perror("mkfifo:");
    }
    return 0;
}

执行并查看管道信息

 注意,输出结果中的第一个字符为p,表示这是一个管道。最后的|符号是由ls命令的-F选项添加的,它也表示这是一个管道。

实验解析

这个程序用mkfifo函数创建一个特殊的文件。虽然我们要求的文件模式是0777,但它被用户掩码(umask)设置(在本例中是0002)给改变了,这与普通文件的创建是一样的,所以文件的最终模式是775。如果你的掩码设置与这里不同,比如是0022,那你将看到创建的文件拥有一个不同的权限。我们可以像删除一个普通文件那样用rm命令删除FIFO文件,或者也可以在程序中用unlink系统调用来删除它。

 unlink函数原型如下所示:

#include <unistd.h>
int unlink(const char *pathname);

访问FIFO文件

命名管道的一个非常有用的特点是:由于它们出现在文件系统中,所以它们可以像平常的文件名一样在命令中使用。在把创建的FIFO文件用在程序设计中之前,我们先通过普通的文件命令来观察FIFO文件的行为。

实验 访问FIFO文件

(1)首先,我们来尝试读这个(空的)FIFO文件:

cat my_fifo

(2)现在,尝试向FIFO写数据。你必须用另一个终端来执行下面的命令,因为第一个命令现在被挂起以等待数据出现在FIFO中。

echo "hello world" > my_fifo

你将看到cat命令产生输出。如果不向FIFO发送任何数据,cat命令将一直挂起,直到你中断它,常用的中断方式是使用组合键Ctrl+C。

(3)我们可以将第一个命令放在后台执行,这样即可一次执行两个命令:

 实验解析

因为FIFO中没有数据,所以cat和echo程序都阻塞了,cat等待数据的到来,而echo等待其他进程读取数据。

在上面的第三步中,cat进程一开始就在后台被阻塞了,当echo向它提供了一些数据后,cat命令读取这些数据并把它们打印到标准输出上,然后cat程序退出,不再等待更多的数据。它没有阻塞是因为当第二个命令将数据放入FIFO后,管道将被关闭,所以cat程序中的read调用返回0字节,表示已经到达文件尾。

现在我们已看过用命令行程序访问FIFO的情况,接下来我们将仔细分析FIFO的编程接口,它可以让我们在访问FIFO文件时更多地控制其读写行为。

与通过pipe调用创建管道不同,FIFO是以命名文件的形式存在,而不是打开的文件描述符,所以在对它进行读写操作之前必须先打开它。FIFO也用open和close函数打开和关闭,这与我们前面看到的对文件的操作一样,但它多了一些其他的功能。对FIFO来说,传递给open调用的是FIFO的路径名,而不是一个正常的文件。

1.使用open打开FIFO文件

打开FIFO的一个主要限制是,程序不能以O_RDWR模式打开FIFO文件进行读写操作,这样做的后果并未明确定义。但这个限制是有道理的,因为我们通常使用FIFO只是为了单向传递数据,所以没有必要使用O_RDWR模式。如果一个管道以读/写方式打开,进程就会从这个管道读回它自己的输出。

如果确实需要在程序之间双向传递数据,最好使用一对FIFO或管道,一个方向使用一个,或者(但并不常用)采用先关闭再重新打开FIFO的方法来明确地改变数据流的方向。我们将在本章后面部分再讨论用FIFO进行双向数据交换的问题。

打开FIFO文件和打开普通文件的另一点区别是,对open_flag(open函数的第二个参数)的O_NONBLOCK选项的用法。使用这个选项不仅改变open调用的处理方式,还会改变对这次open调用返回的文件描述符进行的读写请求的处理方式。

O_RDONLY、O_WRONLY和O_NONBLOCK标志共有4种合法的组合方式,我们将逐个介绍它们。

open(const char *path,O_RDONLY);

在这种情况下,open调用将阻塞,除非有一个进程以写方式打开同一个FIFO,否则它不会返回。这与前面第一个cat命令的例子类似。

open(const char *path,O_RDONLY | O_NONBLOCK);

即使没有其他进程以写方式打开FIFO,这个open调用也将成功并立刻返回。 

open(const char *path,O_WRONLY);

 在这种情况下,open调用将阻塞,直到有一个进程以读方式打开同一个FIFO为止。

open(const char *path,O_WRONLY | O_NONBLOCK);

这个函数调用总是立刻返回,但如果没有进程以读方式打开FIFO文件,open调用将返回一个错误-1并且FIFO也不会被打开。如果确实有一个进程以读方式打开FIFO文件,那么我们就可以通过它返回的文件描述符对这个FIFO文件进行写操作。

请注意O_NONBLOCK分别搭配O_RDONLY和O_WRONLY在效果上的不同,如果没有进程以读方式打开管道,非阻塞写方式的open调用将失败,但非阻塞读方式的open调用总是成功。close调用的行为并不受O_NONBLOCK标志的影响。

实验 打开FIFO文件

下面我们来看,如何通过使用带O_NONBLOCK标志的open调用的行为来同步两个进程。我们在这里并没有选择使用多个示例程序的做法,而是只使用一个测试程序fifo2.c,通过给该程序传递不同的参数的方法来观察FIFO的行为。本例用到了access函数,原型如下所示:

#include <unistd.h>
int access(const char *pathname, int mode);

判断文件是否存在:

    res = access(fifo_name,F_OK);
    if(res != 0){
        perror("access F_OK:");
        return res;
    }

判断文件是否可读、可写和可执行

access(fifo_name,R_OK);
access(fifo_name,W_OK);
access(fifo_name,X_OK);

完整测试代码:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define DEBUG_INFO(format, ...) printf("%s - %d - %s :: "format"\n",__FILE__,__LINE__,__func__ ,##__VA_ARGS__)

#define FIFO_NAME "./my_fifo"

int delete_fifo(const char *fifo_name){
    int res = 0;
    res = unlink(fifo_name);
    if(res != 0) {
        perror("unlink:");
    }
    DEBUG_INFO("res = %d",res);
    return res;
}
int is_write(const char *fifo_name){
    int res = 0;
    res = access(fifo_name,W_OK);
    if(res != 0){
        perror("access W_OK:");
        return res;
    }
    DEBUG_INFO("%s can write",fifo_name);
    return res;
}
int is_execute(const char *fifo_name){
    int res = 0;
    res = access(fifo_name,X_OK);
    if(res != 0){
        perror("access X_OK:");
         DEBUG_INFO("%s can't execute",fifo_name);
        return res;
    }
    DEBUG_INFO("%s can execute",fifo_name);

    return res;
}
int is_exist(const char *fifo_name){
    int res = 0;
    res = access(fifo_name,F_OK);
    if(res != 0){
        perror("access F_OK:");
        return res;
    }
    DEBUG_INFO("%s exist",fifo_name);
    return res;
}
int is_read(const char *fifo_name){
    int res = 0;
    res = access(fifo_name,R_OK);
    if(res != 0){
        perror("access R_OK:");
        return res;
    }
    DEBUG_INFO("%s can read",fifo_name);
    return res;
}

int create_fifo(const char *fifo_name){
    int res = 0;
    res = mkfifo(fifo_name,0777);
    if(res == 0){
        DEBUG_INFO("mkfifo ok");
    }else{
        perror("mkfifo:");
    }
    return res;
}

int main(int argc, char *argv[])
{
    int res = 0;
    int open_mode = 0;
    int i = 0;
    char **p = argv;
    if(argc < 2){
        DEBUG_INFO("no arg");
        return -1;
    }
    while(*argv != NULL){
        DEBUG_INFO("argv[%d] = %s",i++,*argv++);
    }
    for(int i = 1;i < argc;i++){
        if(strncmp(p[i],"O_RDONLY",8) == 0){
            open_mode |= O_RDONLY; 
        }
        if(strncmp(p[i],"O_WRONLY",8) == 0){
            open_mode |= O_WRONLY; 
        }
        if(strncmp(p[i],"O_NONBLOCK",10) == 0){
            open_mode |= O_NONBLOCK; 
        }
    }
    DEBUG_INFO("open_mode = %x",open_mode);
    if(is_exist(FIFO_NAME)){
        res = create_fifo(FIFO_NAME);
        if(res != 0){
            exit(-1);
        }
    }
    DEBUG_INFO("pid = %d",getpid());
    res = open(FIFO_NAME,open_mode);
    DEBUG_INFO("pis = %d,res=%d",getpid(),res);
    sleep(5);
    if(res != -1){
        close(res);
    }
    DEBUG_INFO("pid = %d,byebye",getpid());

    return 0;
}

实验解析

这个程序能够在命令行上指定我们希望使用的O_RDONLY、O_WRONLY和O_NONBLOCK的组合方式。它会把命令行参数与程序中的常量字符串进行比较,如果匹配,就(用|=操作符)设置相应的标志。程序用access函数来检查FIFO文件是否存在,如果不存在就创建它。

在程序中,一直到最后都没有删除这个FIFO文件,因为我们没办法知道是否有其他程序正在使用它。

2.不带O_NONBLOCK标志的O_RDONLY和O_WRONLY

我们现在有了测试程序,可以逐个尝试标志的不同组合方式。注意,我们将第一个程序(读取者)放在后台运行:

 这可能是命名管道最常见的用法了。它允许先启动读进程,并在open调用中等待,当第二个程序打开FIFO文件时,两个程序继续运行。注意,读进程和写进程在open调用处取得同步。

当一个Linux进程被阻塞时,它并不消耗CPU资源,所以这种进程的同步方式对CPU来说是非常有效率的。

3.带O_NONBLOCK标志的O_RDONLY和不带该标志的O_WRONLY

这次,读进程执行open调用并立刻继续执行,即使没有写进程的存在。随后写进程开始执行,它也在执行open调用后立刻继续执行,但这次是因为FIFO已被读进程打开。

 这两个例子可能是open模式的最常见的组合形式。你还可以用这个示例程序随意尝试其他组合方式。

4.对FIFO进行读写操作

使用O_NONBLOCK模式会影响到对FIFO的read和write调用。

对一个空的、阻塞的FIFO(即没有用O_NONBLOCK标志打开)的read调用将等待,直到有数据可以读时才继续执行。与此相反,对一个空的、非阻塞的FIFO的read调用将立刻返回0字节。

对一个满的、阻塞FIFO的write调用将等待,直到数据可以被写入时才继续执行。如果FIFO不能接收所有写入的数据[插图],它将按下面的规则执行。

❑ 如果请求写入的数据的长度小于等于PIPE_BUF字节,调用失败,数据不能写入。

❑ 如果请求写入的数据的长度大于PIPE_BUF字节,将写入部分数据,返回实际写入的字节数,返回值也可能是0。

FIFO的长度是需要考虑的一个很重要的因素。系统对任一时刻在一个FIFO中可以存在的数据长度是有限制的。它由#define PIPE_BUF语句定义,通常可以在头文件limits.h中找到它。在Linux和许多其他类UNIX系统中,它的值通常是4096字节,但在某些系统中它可能会小到512字节。系统规定:在一个以O_WRONLY方式(即阻塞方式)打开的FIFO中,如果写入的数据长度小于等于PIPE_BUF,那么或者写入全部字节,或者一个字节都不写入。

虽然,对只有一个FIFO写进程和一个FIFO读进程的简单情况来说,这个限制并不是非常重要,但只使用一个FIFO并允许多个不同的程序向一个FIFO读进程发送请求的情况是很常见的。如果几个不同的程序尝试同时向FIFO写数据,能否保证来自不同程序的数据块不相互交错就非常关键了。也就是说,每个写操作都必须是“原子化”的。怎样才能做到这一点呢?

如果你能保证所有的写请求是发往一个阻塞的FIFO的,并且每个写请求的数据长度小于等于PIPE_BUF字节,系统就可以确保数据决不会交错在一起。通常将每次通过FIFO传递的数据长度限制为PIPE_BUF字节是个好方法,除非你只使用一个写进程和一个读进程。

实验 使用FIFO实现进程间通信

为了演示不相关的进程是如何使用命名管道进行通信的,我们需要用到两个独立的程序fifo3.c和fifo4.c。

(1)第一个程序是生产者程序。它在需要时创建管道,然后尽可能快地向管道中写入数据。

注意,出于演示的目的,我们并不关心写入数据的内容,所以我们并未对缓冲区进行初始化。完整代码如下:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <limits.h>
#define DEBUG_INFO(format, ...) printf("%s - %d - %s :: "format"\n",__FILE__,__LINE__,__func__ ,##__VA_ARGS__)

#define FIFO_NAME "./my_fifo"

int is_exist(const char *fifo_name){
    int res = 0;
    res = access(fifo_name,F_OK);
    if(res != 0){
        perror("access F_OK:");
        return res;
    }
    DEBUG_INFO("%s exist",fifo_name);
    return res;
}

int create_fifo(const char *fifo_name){
    int res = 0;
    res = mkfifo(fifo_name,0777);
    if(res == 0){
        DEBUG_INFO("mkfifo ok");
    }else{
        perror("mkfifo:");
    }
    return res;
}
#define TEN_MSG (1024*1024*10)
int main(int argc, char *argv[])
{
    int pipe_fd = 0;
    int res;
    int open_mode = O_WRONLY;
    int i = 0;
    int bytes_send = 0;
    char buf[PIPE_BUF + 1];
    DEBUG_INFO("PIPE_BUF = %d",PIPE_BUF);
    if(is_exist(FIFO_NAME)){
        res = create_fifo(FIFO_NAME);
        if(res != 0){
            exit(-1);
        }
    }
    DEBUG_INFO("pid = %d is writer",getpid());
    pipe_fd = open(FIFO_NAME,open_mode);
    DEBUG_INFO("pis = %d,pipe_fd = %d",getpid(),pipe_fd);
    if(pipe_fd == -1){
        perror("open:");
        exit(-1);
    }
    while(bytes_send < TEN_MSG){
        res = write(pipe_fd,buf,PIPE_BUF);
        if(res == -1){
            perror("write:");
            exit(-1);
        }
        bytes_send += PIPE_BUF;
    }
    DEBUG_INFO("pid = %d,byebye",getpid());
    close(pipe_fd);
    return 0;
}

(2)第二个程序是消费者程序,它的代码要简单得多,它从FIFO读取数据并丢弃它们。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <limits.h>
#define DEBUG_INFO(format, ...) printf("%s - %d - %s :: "format"\n",__FILE__,__LINE__,__func__ ,##__VA_ARGS__)

#define FIFO_NAME "./my_fifo"

int main(int argc, char *argv[])
{
    int pipe_fd = 0;
    int res;
    int open_mode = O_RDONLY;
    int i = 0;
    int bytes_read = 0;
    char buf[PIPE_BUF + 1];
    memset(buf, 0, sizeof(buf));
    DEBUG_INFO("pid = %d is reader",getpid());
    pipe_fd = open(FIFO_NAME,open_mode);
    DEBUG_INFO("pis = %d,pipe_fd = %d",getpid(),pipe_fd);
    if(pipe_fd == -1){
        perror("open:");
        exit(-1);
    }
    do{
        res = read(pipe_fd,buf,PIPE_BUF);
        if(res == -1){
            perror("read:");
            break;
        }
        if(res == 0){
            DEBUG_INFO("read finish");
            break;
        }
        bytes_read += res;
    }while(1);
    DEBUG_INFO("pid = %d finish,bytes_read = %d",getpid(),bytes_read);
    close(pipe_fd);
    return 0;
}

 

我们在运行这两个程序的同时,用time命令对读进程进行计时。输出结果如下所示

 实验解析

两个程序使用的都是阻塞模式的FIFO。我们首先启动fifo3(写进程/生产者),它将阻塞以等待读进程打开这个FIFO。fifo4(消费者)启动以后,写进程解除阻塞并开始向管道写数据。同时,读进程也开始从管道中读取数据。

Linux会安排好这两个进程之间的调度,使它们在可以运行的时候运行,在不能运行的时候阻塞。因此,写进程将在管道满时阻塞,读进程将在管道空时阻塞。

time命令的输出显示,读进程只运行了不到0.1秒的时间,却读取了10 MB的数据。这说明管道(至少在现代Linux系统中的实现)在程序之间传递数据是很有效率的。

高级主题:使用FIFO的客户/服务器应用程序

作为学习FIFO的最后一部分内容,我们来考虑怎样通过命名管道来编写一个非常简单的客户/服务器应用程序。我们想只用一个服务器进程来接受请求,对它们进行处理,最后把结果数据返回给发送请求的一方:客户。

我们想允许多个客户进程都可以向服务器发送数据。为了使问题简单化,我们假设被处理的数据可以被拆分为一个个数据块,每个的长度都小于PIPE_BUF字节。当然,我们可以用很多方法来实现这个系统,但在这里我们只考虑一种方法,即可以体现如何使用命名管道的方法。

因为服务器每次只能处理一个数据块,所以只使用一个FIFO应该是合乎逻辑的,服务器通过它读取数据,每个客户向它写数据。只要将FIFO以阻塞模式打开,服务器和客户就会根据需要自动被阻塞。

将处理后的数据返回给客户稍微有些困难。我们需要为每个客户安排第二个管道来接收返回的数据。通过在传递给服务器的原先数据中加上客户的进程标识符(PID),双方就可以使用它来为返回数据的管道生成一个唯一的名字。

实验 一个客户/服务器应用程序的例子

(1)首先,我们需要一个头文件client.h,它定义了客户和服务器程序都会用到的数据。为了方便使用,它还包含了必要的系统头文件。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <limits.h>
#define DEBUG_INFO(format, ...) printf("%s - %d - %s :: "format"\n",__FILE__,__LINE__,__func__ ,##__VA_ARGS__)

#define SERVER_FIFO_NAME "./server_fifo"
#define CLIENT_FIFO_NAME "./client_%d_fifo"

#define BUFFER_SIZE 128
struct data_to_pass_st
{
    /* data */
    pid_t client_id;
    char some_data[BUFFER_SIZE];  
};

(2)现在是服务器程序server.c。在这一部分,我们创建并打开服务器管道。它被设置为只读的阻塞模式。在稍作休息(这是出于演示的目的)之后,服务器开始读取客户发送来的数据,这些数据采用的是data_to_pass_st结构。

(3)在接下来的这一部分中,我们对刚从客户那里读到的数据进行处理,把some_data中的所有字符全部转换为大写,并且把CLIENT_FIFO_NAME和接收到的client_pid结合在一起。

(4)然后,我们以只写的阻塞模式打开客户管道,把经过处理的数据发送回去。最后,关闭服务器管道的文件描述符,删除FIFO文件,退出程序。

服务器端的完整代码:

#include "fifo_client.h"

int main(int argc, char**argv){
    int server_fifo_fd = -1;
    int client_fifo_fd = -1;
    struct data_to_pass_st mydata;
    int read_result;
    char client_fifo[256];
    char *tmp_char_ptr;
    mkfifo(SERVER_FIFO_NAME,0777);
    server_fifo_fd = open(SERVER_FIFO_NAME,O_RDONLY);
    if(server_fifo_fd == -1){
        perror("open:");
        exit(-1);
    }
    sleep(10);
    do{
        read_result = read(server_fifo_fd,&mydata,sizeof(struct data_to_pass_st));
        if(read_result == -1){
            perror("read:");
            goto OUT;
        }
        if(read_result == 0){
            DEBUG_INFO("read finish");
            goto OUT;
        }
        tmp_char_ptr = mydata.some_data;
        while(*tmp_char_ptr){
            *tmp_char_ptr = toupper(*tmp_char_ptr);
            tmp_char_ptr++;
        }
        sprintf(client_fifo,CLIENT_FIFO_NAME,mydata.client_pid);
        client_fifo_fd = open(client_fifo,O_WRONLY);
        if(client_fifo_fd == -1){
            perror("open:");
            goto OUT;
        }
        write(client_fifo_fd,&mydata,sizeof(mydata));
        close(client_fifo_fd);
    }while(1);
OUT:
    DEBUG_INFO("fifo server exit");
    close(server_fifo_fd);
    unlink(SERVER_FIFO_NAME);
    return 0;
}

(5)下面是客户程序client.c。这个程序的第一部分先检查服务器FIFO文件是否存在,如果存在就打开它。然后它获取自己的进程ID,该进程ID构成要发送给服务器的数据的一部分。接下来,它创建客户FIFO,为下一部分内容做好准备。

(6)这部分有5次循环,在每次循环中,客户将数据发送给服务器,然后打开客户FIFO(只读,阻塞模式)并读回数据。在程序的最后,关闭服务器FIFO并将客户FIFO从文件系统中删除。

客户端的完整代码:
 

#include "fifo_client.h"

int is_exist(const char *fifo_name){
    int res = 0;
    res = access(fifo_name,F_OK);
    if(res != 0){
        perror("access F_OK:");
        return res;
    }
    DEBUG_INFO("%s exist",fifo_name);
    return res;
}

int main(int argc, char**argv){
    int server_fifo_fd = -1;
    int client_fifo_fd = -1;
    struct data_to_pass_st mydata;
    int times_to_send;
    char client_fifo[256];
    int res = 0;

    if(is_exist(SERVER_FIFO_NAME)){
        exit(-1);
    }

    server_fifo_fd = open(SERVER_FIFO_NAME,O_WRONLY);
    if(server_fifo_fd == -1){
        perror("open:");
        exit(-1);
    }
    mydata.client_pid = getpid();
    sprintf(client_fifo,CLIENT_FIFO_NAME,mydata.client_pid);
    res = mkfifo(client_fifo,0777);
    if(res == -1){
        perror("mkfifo");
        close(server_fifo_fd);
        exit(-1);
    }
    for(times_to_send = 0;times_to_send < 5;times_to_send++){
        sprintf(mydata.some_data,"Hello from %d",mydata.client_pid);
        DEBUG_INFO("%d sent %s",mydata.client_pid,mydata.some_data);
        write(server_fifo_fd,&mydata,sizeof(mydata));
        client_fifo_fd = open(client_fifo,O_RDONLY);
        if(client_fifo_fd == -1){
            perror("open:");
            close(server_fifo_fd);
            break;
        }
        if(read(client_fifo_fd,&mydata,sizeof(mydata)) > 0){
            DEBUG_INFO("received : %s",mydata.some_data);
        }
        close(client_fifo_fd);
    }
    DEBUG_INFO("fifo client exit");
    close(server_fifo_fd);
    unlink(client_fifo);
    return 0;
}

测试这个程序时,我们需要运行一个服务器程序和多个客户程序。为了让多个客户程序尽可能在同一时间启动,我们使用如下所示的shell命令:

首先启动服务器

./fifo_server &

然后启动5个客户端

$ for i in 1 2 3 4 5
> do
> ./fifo_client &
> done

如下图

 上述命令启动了一个服务器进程和5个客户进程。客户的输出如下所示(为了简洁起见,我们做了一些修改):

 如你所见,不同的客户请求交错在一起,但每个客户都获得了正确的服务器返回给它的处理数据。要注意的是客户请求的交错顺序是随机的,服务器接收到客户请求的顺序随机器的不同而不同,即使是在同一台机器上,每次运行的情况也可能发生变化。

实验解析

现在,我们将解释客户和服务器在交互时各种操作的执行顺序,这是我们以前未涉及的。

服务器以只读模式创建它的FIFO并阻塞,直到第一个客户以写方式打开同一个FIFO来建立连接为止。此时,服务器进程解除阻塞并执行sleep语句,这使得来自客户的数据排队等候。在实际的应用程序中,应该把sleep语句删除。我们在这里使用它只是为了演示当有多个客户的请求同时到达时,程序的正确操作方法。

与此同时,在客户打开了服务器FIFO后,它创建自己唯一的一个命名管道来读取服务器返回的数据。完成这些工作后,客户发送数据给服务器(如果管道满或服务器仍在休眠中就阻塞),然后阻塞在对自己的FIFO的read调用上,等待服务器的响应。

接收到来自客户的数据后,服务器处理它,然后以写方式打开客户管道并将处理后的数据返回,这将解除客户的阻塞状态。客户被解除阻塞后,它即可从自己的管道中读取服务器返回的数据。

 整个处理过程不断重复,直到最后一个客户关闭服务器管道为止,这将使服务器的read调用失败(返回0),因为已经没有进程以写方式打开服务器管道了。如果这是一个真正的服务器进程,它还需要继续等待客户的请求,我们就需要对它进行修改,有两种方法,如下所示。

❑ 对它自己的服务器管道打开一个文件描述符,这样read调用将总是阻塞而不是返回0。

❑ 当read调用返回0时,关闭并重新打开服务器管道,使服务器进程阻塞在open调用处以等待客户的到来,就像它最初启动时那样。

小结 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/645309.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一文看尽所有生成式模型:9大类别21个模型全回顾! DALL-E 2、Text-to-3D模型

DALL-E 2 由OpenAI开发的DALL-E 2能够从由文本描述组成的提示中生成原始、真实、逼真的图像和艺术&#xff0c;而且OpenAI已经对外提供了API来访问该模型。 DALL-E 2特别之处在于它能够将概念、属性和不同风格结合起来&#xff0c;其能力源于语言-图像预训练模型CLIP神经网络…

【SCI/EI录用案例】2区快刊1个月22天录用,新增5篇录用、1篇见刊、8篇检索

2023年6月3日-2023年6月9日 经核实&#xff0c;由我处Unionpub学术推荐的5篇论文已被期刊部录用、1篇见刊、8篇检索&#xff0c;以下是部分案例展示&#xff1a; 2区智能算法类SCI&EI 【期刊简介】IF:4.0-5.0&#xff0c;JCR2区&#xff0c;中科院3区 【检索情况】SCI&…

Win10底部任务栏无响应,跟着这3个方法做!

我的电脑型号是win10的&#xff0c;但最近我使用电脑时&#xff0c;出现了一件非常奇怪的事情&#xff0c;我点击桌面底部的任务栏&#xff0c;它不仅无响应&#xff0c;有时还会卡顿很久&#xff0c;不知道遇到win10底部任务栏无响应的情况应该怎么做呢&#xff1f;希望大家给…

AIGC繁花,绽放在精耕的算力土壤之上

2023年的春天&#xff0c;称得上一句AI之春。大模型成为技术力量新的爆发点&#xff0c;生成式AI&#xff08;AIGC&#xff09;应用风起云涌。 产业界争先恐后训大模型之际&#xff0c;广大用户最大的困惑是——发布了&#xff0c;但就是玩不到啊&#xff01; OpenAI有地域限制…

618有哪些数码好物值得入手?盘点几款618值得买的数码好物分享

距离618大促结束还有几天&#xff0c;还有谁没有入手几款数码好物的&#xff01;眼下618还没有彻底结束&#xff0c;现在上车还来得及。下面来给大家盘点几款数码好物&#xff0c;话不多说&#xff0c;一起来看看吧。 1.南卡OE不入耳蓝牙耳机 南卡OE不入耳蓝牙耳机采用了不入耳…

WDM波分复用技术:TFF(薄膜滤波) AWG(阵列波导光栅)介绍

WDM &#xff08;Wavelength Division Multiplexing&#xff09;技术是通过在光纤中传输多个不同波长的光信号来扩大光纤传输带宽并提高网络传输能力的一种技术&#xff0c;而TFF(薄膜滤波)和AWG&#xff08;阵列波导光栅&#xff09;则是两种常用的WDM技术。 TFF技术 TFF &a…

【统计模型】缺失数据处理方法

目录 一、缺失数据定义 二、缺失数据原因 三、缺失数据处理步骤 四、数据缺失机制 1.完全随机缺失&#xff08;MCAR&#xff09; 2.随机缺失&#xff08;MAR&#xff09; 3.非随机、不可忽略缺失&#xff08;NMAR&#xff09; 五、缺失数据处理方法 1.直接删除 2.缺失值…

耳朵小戴什么耳机合适,耳朵小佩戴都舒适的几款骨传导耳机分享

​骨传导耳机是通过骨骼震动来传递声音&#xff0c;不用经过外耳道和鼓膜&#xff0c;因此不会损伤听力&#xff0c;同时佩戴也更加舒适&#xff0c;不会影响对周围环境的感知。并且骨传导耳机的核心技术在于骨传导&#xff0c;它通过耳机后挂部分将声音直接传递到耳道&#xf…

skywalking-agent-java默认不支持spring cloud gateway问题

解决skywalking-agent-java不支持spring cloud gateway问题 开发环境&#xff1a;Windows10 、JDK17 下载解压Java Agent skywalking官网下载地址&#xff1a;https://skywalking.apache.org/downloads/ 解压目录为&#xff1a;D:\Programs\soft-Plugins\apache-skywalking…

CSS查缺补漏之常用文本属性、列表属性、表格属性、鼠标属性

文本属性 letter-spacing&#xff1a; 表示字母或汉字间距&#xff1b; word-spacing&#xff1a;表示单词之间或汉字之间空格的间距 <div>Loremip sumdolors itametconsecteturadipisicingelit.Voluptas.</div> <div>这是一首简单的小情歌 唱着我们心肠的曲…

探索小程序开发:2023年的全面指南

小程序是一种无需下载的应用程序&#xff0c;它可以在您的手机上打开&#xff0c;而无需在计算机上打开。小程序通过其快速&#xff0c;低成本&#xff0c;功能强大且易于使用的优势而成为更广泛的互联网趋势。 那么&#xff0c;您为什么探索小程序开发&#xff1f;开发过程可…

Unity Class深拷贝问题分析

Unity Class深拷贝问题分析 前言常用解决方案1.手动复制字段2.使用序列化工具3.使用Instantiate方法(只能用于MonoBehaviour)4.重写运算符赋值5.使用Visual Scripting中提供的拷贝函数&#xff08;推荐&#xff09; 前言 在Unity项目中&#xff0c;我们面临一个读取数据表并深…

3--Gradle入门 - 创建普通的Java工程

3--Gradle入门 - 创建普通的Java工程 前言 前面我们以及尝试过使用 Gradle 创建项目&#xff0c;下面我们使用 Idea 来创建普通的Java工程 创建普通的Java工程 1. 创建 Gradle 管理依赖的项目 语言设置 Java 依赖设置 Gradle DSL 使用 Groovy 2. 设置项目使用本地的 gradle …

B树和B+树索引

B树索引 磁盘IO读数据到内存 malloc/new 向 kernel申请4B空间&#xff0c;实际系统可能给2个页面空间即8KB&#xff0c;剩下8KB-4B空间由 lib.so 或libc.so 的 ptmalloc 或tcmalloc管理 。这样就不需要每次去kernel申请内存 磁盘IO同理&#xff0c;我们读一部分数据会把一整…

2023年6月DAMA-CDGP数据治理专家认证,这家口碑好

DAMA认证为数据管理专业人士提供职业目标晋升规划&#xff0c;彰显了职业发展里程碑及发展阶梯定义&#xff0c;帮助数据管理从业人士获得企业数字化转型战略下的必备职业能力&#xff0c;促进开展工作实践应用及实际问题解决&#xff0c;形成企业所需的新数字经济下的核心职业…

研一,有点迷茫。

作者&#xff1a;阿秀 校招八股文学习网站&#xff1a;https://interviewguide.cn 这是阿秀的第「277」篇原创 小伙伴们大家好&#xff0c;我是阿秀。 最近回答了不少大一大二研一在读的学习圈中学弟学妹的咨询问题&#xff0c;基本都是计算机学习、进度、疑惑等等相关的问题&a…

chatgpt赋能python:Python怎么批量注释

Python怎么批量注释 Python是一种高级编程语言&#xff0c;广泛应用于数据科学、人工智能、Web开发等领域。在编写Python代码时&#xff0c;注释是非常重要的一部分。注释可以帮助程序员更好地理解代码&#xff0c;便于代码的维护和修改。但有时候&#xff0c;我们需要批量注释…

华为OD机试真题 JavaScript 实现【火星文计算】【2022Q2 100分】,附详细解题思路

一、题目描述 已知火星人使用的运算符为#、$&#xff0c;其与地球人的等价公式如下&#xff1a; x#y 2*x3*y4 x$y 3*xy2 其中x、y是无符号整数&#xff1b;地球人公式按C语言规则计算&#xff1b;火星人公式中&#xff0c;$的优先级高于#&#xff0c;相同的运算符&#x…

YOLOv5/7 更换 DIoU-NMS

文章目录 NMS原理介绍DIoU-NMS效果展示NMS 耗时对比YOLOv5 更换方式YOLOv7 更换方式NMS原理介绍 在执行目标检测任务时,算法可能对同一目标有多次检测。NMS 是一种让你确保算法只对每个对象得到一个检测框的方法。 在正式使用NMS之前,通常会有一个候选框预清理的工作(简单引…

二、微机保护的结构框图原理

在实际应用中&#xff0c;微机保护装置分为单CPU和多CPU的结构方式。在中、低压变电所中多数简单的保护装置采用单CPU结构&#xff0c;而在高压及超高压变电所中复杂保护装置广泛采用多CPU的结构方式。 &#xff08;一&#xff09;单 CPU的结构原理 单CPU的微机保护装置是指整套…