进程间通信之Linux C命名管道编程

版权声明:本文为博主原创文章,转载请注明出处。 https://blog.csdn.net/qq_37653144/article/details/83932845

命名管道

管道(匿名管道)的使用局限性大,这与管道的实现机制有关。而命名管道(Named Pipe)不仅可在同一台计算机的任意不同进程之间通信,而且还可以在跨越一个网络的不同计算机的不同进程之间,支持可靠的、单向或双向的数据通信。

命名管道不同于管道之处在于它提供一个路径名与之关联,以命名管道的文件形式存在于文件系统中。这样,即使与命名管道的创建进程不存在亲缘关系的进程,只要能够访问该路径,就能够彼此通过命名管道相互通信。简单来说,虽然管道和命名管道都是实实在在的文件,但前者没有公开的文件名,用户在文件系统中不能直接观察并访问到它;命名管道则是存在于文件系统中的,任何具有访问权限的进程都可以访问。

管道和命名管道的区别总结如下:

·命名管道可以用于任意两个进程间的通信,并不限制这两个进程同源,因此命名管道的使用比管道的使用要灵活方便得多。

·命名管道作为一种特殊的文件存在于文件系统中,而不像管道一样只存在于内存中(使用完毕就会消失)。当进程对命名管道的使用结束后,命名管道依然存在于与文件系统中,除非对其进行删除操作,否则不会消失。

需要注意的是,命名管道是一种严格遵循FIFO规则,不支持lseek函数等文件定位操作。

命名管道操作

创建

Linux提供了mkfifo函数用于创建命名管道,其标准调用格式如下:

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

int mkfifo(const char *pathname, mode_t mode);

mkfifo函数的第一个参数是路径名,也就是创建后命名管道的路径及文件名,如果参数pathname所指示的文件已经存在,则会返回EEXIST错误;mode参数是文件的操作权限,与Linux文件的权限设置与umask相同。如果调用成功则返回0,否则返回-1。 

需要注意的是,命名管道可以以只读模式或者只写模式打开,但不能以读写模式打开命名管道。命名管道还可以设置为阻塞式或非阻塞式(默认为阻塞)。

读写管道规则

向命名管道中读数据时必须遵循以下准则:

·如果有进程为写操作打开命名管道,且当前命名管道内没有数据,则对于设置了阻塞标志的读操作来说,将会阻塞。

·对于设置了阻塞标志的读操作来说,造成阻塞的原因有两种:当前命名管道内有数据,但有其他进程在读这些数据;或者命名管道内没有数据,解阻塞的原因则是命名管道中有新的数据写入,无论新写入数据量的的大小,也不论读操作请求多少数据量。

·读打开的阻塞标志只对本进程的第一个读操作施加作用,如果本进程内有多个读操作,则在第一个读操作被唤醒并完成读后,其他将要执行的读操作将不再阻塞,即使在执行读操作时,命名管道中没有数据也一样(此时读操作返回0)。

·如果没有进程为写操作打开命名管道,则设置了阻塞标志的读操作会阻塞。

向命名管道中写入数据必须符合以下准则:

·对于设置了阻塞标志的写操作,当要写入的数据量不大于PIPE_BUF时,Linux将保证写入的原子性。如果此时管道的空闲缓冲区不足以容纳要写入的字节数,则进入睡眠(非阻塞模式下出错),直到当缓冲区中能够容纳要写入的数据时,才被唤醒并一次性写入。

·当要写入的数据量大于PIPE_BUF时,Linux将不再保证写入的原子性,命名管道缓冲区一旦有空闲区域,写进程就会试图向其中写入数据,写操作在写完所有数据后返回。

示例

下面用两个进程通过命名管道进行通信,一个进程写,另一个进程读并输出:

#include <iostream>
#include <fstream>
#include <string>
#include <cstdlib>


int main(int argc, char *argv[])
{
    if (argc < 2)
    {
        std::cerr << "参数错误\n";
        exit(1);
    }
    std::string s("abc");
    std::ofstream fp(*(argv+1), std::ios::out);
    if (!fp.is_open())
    {
        cerr << "文件打开失败\n";
        exit(1);
    }
    fp << s;
    fp.close();
}
#include <iostream>
#include <fstream>
#include <string>
#include <cstdlib>

int main(int argc, char *argv[])
{
    if (argc < 2)
    {
        std::cerr << "参数错误\n";
        exit(1);
    }
    std::ifstream fp(*(argv+1), std::ios::in);
    std::string s;
    if (fp.is_open())
    {
        std::cerr << "文件打开失败\n";
        exit(1);
    }
    fp >> s;
    std::cout << s << std::endl;
    return 0;
}

当我们执行第一段程序时会发现程序一直在等待,说明写操作阻塞,只有当我们运行第二段程序后输出了第一段程序写入的数据,这验证了命名管道的读写特点。 

 总结

命名管道是进程间通信的常用方法,它特别适合两个进程间的通信,例如一个服务器进程和多个客户端进程的通信就可以通过一个公共的命名管道来实现(服务器在同一时间只需要和一个客户端通信,若需要并发处理则可以使用多个命名管道)。

不同于管道(匿名管道)只存在于内核中,命名管道是一个实实在在的文件,只要进程可以访问该命名管道,就可以使用它与其他进程进行通信。命名管道的特殊性使得在读写操作时需要注意以下几点:

·如果没有其他写进程打开命名管道就对其进行读操作时,会产生SIGPIPE信号;如果所有的写进程都关闭命名管道,对其的读操作就会认为已到达文件末尾。

·在多个写进程的情况下,有可能发生数据交错的现象。

·命名管道常常使进程阻塞,如果一个读进程打开命名管道,那么这个进程就要进入阻塞状态,直到其他写进程打开这个命名管道为止。同样,如果一个写进程打开命名管道但没有其他进程以读模式打开,这个进程也会出现阻塞状态。

·如果不希望出现阻塞状态,可以通过设置O_NONBLOCK标志来实现,这样不管有没有写进程,读打开操作都会立即返回。但是如果没有读进程,写进程的打开操作就会产生错误。

猜你喜欢

转载自blog.csdn.net/qq_37653144/article/details/83932845