进程间通信简介(三)——命名管道
来源:互联网 发布:佳为软件称软件 编辑:程序博客网 时间:2024/06/05 10:21
4 命名管道
命名管道(named pipe)也称为FIFO,它是一种文件类型,在文件系统中可以看到它,创建一个FIFO文件类似于创建一个普通文件。在程序中可以通过查看文件stat结构中st_mode成员的值来判断该文件是否为FIFO。
4.1 命名管道的概念
管道应用的一个重大限制就是它没有名字,因此,只能用于具有亲缘关系的进程间通信,在命名管道(或FIFO)提出后,该限制得到了克服。FIFO不同于管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中。这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO互相通信(能够访问该路径的进程及FIFO的创建进程之间),因此,通过FIFO不相关的进程也能交换数据。
命名管道区别于管道主要体现在以下两点:
- 命名管道可以用于任何两个进程间的通信,而并不限制这两个进程同源,因此命名管道的使用比管道的使用更灵活方便的多。
- 命名管道作为一种特殊的文件存放于文件系统中,而不是像管道一样存放于内存(使用完毕后消失)。当进程对命名管道的使用结束后,命名管道依然存在于文件系统中,除非对其进行删除操作,否则该命名管道不会消失。
FIFO的出现也极好地解决了系统在应用过程中产生的大量的中间临时文件的问题。FIFO可以被shell调用使数据从一个进程到另一个进程,系统不必为该中间通道而清理不必要的垃圾,或者去释放该通道的资源,它可以被留作后来的进程使用。另外,需要注意的是,FIFO严格遵循先进先出(first in first out)的规则,对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。它们不支持注入lseek等文件定位操作。
在shell中可以使用mkfifo命令创建一个命名管道,mkfifo命令的格式如下:
mkfifo [option] name...
其中option选项中可以选择要创建FIFO的模式,使用形式为-m mode,这里mode指出将要创建FIFO的八进制模式,注意,这里新创建的FIFO会像普通文件一样受到创建进程的umask修正。name表示所要创建的FIFO管道的名称。
过去,命令行上用来创建命名管道的程序是mknod,如下所示:
mknod filename p
4.2 命名管道的创建
创建一个FIFO文件类似于创建一个普通文件,并且FIFO文件和其他文件一样,也是可以通过路径名来访问的。FIFO管道通过函数mkfifo创建,函数原型如下:
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo (const char *pathname, mode_t mode);
返回值:若成功则返回0,失败返回-1。
该函数的第一个参数是一个普通的路径名,也就是创建后FIFO文件的名字。第二个参数与打开普通文件的open函数中的mode参数相同。如果mkfifo的第一个参数是一个已经存在的路径名,则返回EEXIST错误,所以一般典型的调用代码首先会检查是否返回该错误,如果确实返回该错误,那么只要调用打开FIFO的函数就可以了。一般文件的I/O函数都可以用于FIFO,如close、read、write等。
还有一种函数调用也可以创建命名管道文件,但是不是所有系统都能使用,所以一般就用mkfifo进行命名管道的创建。
#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可以创建许多特殊类型的文件,想要通过这个函数创建一个命名管道,唯一具有可移植性的方法是使用一个dev_t类型的值0,并将访问模式与S_IFIFO按位或,所以使用mknod创建一个命名管道的函数形式可以固定为:
int mknod (const char *pathname, mode_t mode | S_IFIFO, (dev_t) 0);
接下来演示使用mkfifo函数创建一个FIFO。
#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <errno.h>#include <stdlib.h>int main(int argc, char **argv){mode_t mode = 0666;if (argc != 2){printf("USEMSG: FIFO_create { FIFO name }\n");exit(1);}if ((mkfifo(argv[1], mode)) < 0){perror("failed to mkfifo!\n");exit(1);}else{printf("you successfully create a FIFO name is: %s\n", argv[1]);}exit(EXIT_SUCCESS);}
编译并运行:
$ ./FIFO_create
USEMSG: FIFO_create { FIFO name }
$
缺少创建的FIFO的名称,程序自动提示。再次运行:
$ ./FIFO_create test
you successfully create a FIFO name is: test
$
成功创建一个名字为test的FIFO。再次运行:
$ ./FIFO_create test
failed to mkfifo!
: File exists
$
还想创建一个叫test的FIFO的时侯,它会检测到文件已存在并且报错。
值得一提的是,命名管道FIFO比管道多了一个打开操作open,这是因为命名管道是Linux下的一种文件类型,而管道不是。然而,FIFO的打开与其他文件的打开是不同的,FIFO的打开规则是:
- 如果当前打开操作是为读而打开FIFO时,若已经有相应进程为写而打开该FIFO,则当前打开操作将成功返回;否则,可能阻塞直到有相应进程为写而打开该FIFO(当前打开操作设置了阻塞标志);或者,成功返回(当前打开操作没有设置阻塞标志)。
- 如果当前打开操作是为写而打开FIFO时,若已经有相应进程为读而打开该FIFO,则当前打开操作将成功返回;否则,可能阻塞直到有相应进程为读而打开该FIFO(当前打开操作设置了阻塞标志);或者,饭或ENXIO错误(当前打开操作没有设置阻塞标志)。
4.3 命名管道的读写
使用命名管道的操作和使用普通文件十分相似,可以使用系统调用open打开一个命名管道,使用read和write函数对命名管道进行读写,使用close关闭一个命名管道,若要删除一个命名管道,则使用系统调用unlink。
从FIFO中读取数据的规则是:
- 如果一个进程为了从FIFO中读取数据而阻塞打开FIFO,那么称该进程内的读操作为设置了阻塞标志的读操作。
- 如果有劲诚写打开FIFO,且当前FIFO内没有数据,则对于设置了阻塞表示的符操作来说,将一直阻塞。对于没有设置阻塞表示的读操作来说则返回-1,当前errno值为EAGAIN,提醒以后再试。
- 对于设置了阻塞表示的读操作来说,造成阻塞的原因有两种:当前FIFO内有数据,但有其他进程在读这些数据;另外就是FIFO内没有数据。解阻塞的原因则是FIFO中有新的数据写入,不论新写入数据量的大小,也不论读操作请求多少数据量。
- 读打开的阻塞表示只对本进程第一个读操作施加作用,如果本进程内有多个读操作序列,则在第一个读操作被唤醒并完成读操作后,其他将要执行的读操作将不再阻塞,即使在执行符操作时,FIFO中没有数据也一样(此时,读操作返回0)。
- 如果没有进程写打开FIFO,则设置了阻塞表示的读操作会阻塞。
注意,如果FIFO中有数据,则设置了阻塞标志的读操作不会因为FIFO中的字节数小于请求读的字节数而阻塞,此时,读操作会返回FIFO中现有的数据量。
向FIFO中写入数据的规则是:
- 如果一个进程为了向FIFO中写入数据而阻塞打开FIFO,那么称该进程内的写操作为设置了阻塞标志的写操作。
- 对于设置了阻塞标志的写操作,当要写入的数据量不大于PIPE_BUF时,Linux将保证写入的原子性。如果此时管道空闲缓冲区不足以容纳要写入的字节数,则进入睡眠,直到当缓冲区中能够容纳要写入的字节数时,才开始进行一次性写操作。
- 当要写入的数据量大于PIPE_BUF时,Linux将不再保证写入的原子性。FIFO缓冲区一有空闲区域,写进程就会试图向管道写入数据,写操作在写完所有请求写的数据后返回。
- 对于没有设置阻塞标志的写操作,当要写入的数据量大于PIPE_BUF时,Linux将不再保证写入的原子性。在写满所有FIFO空闲缓冲区后,写操作返回。
- 当要写入的数据量不大于PIPE_BUF时,Linux将保证写入的原子性。如果当前FIFO空闲缓冲区能够容纳请求写入的字节数,写完后成功返回;如果当前FIFO空闲缓冲区不能够容纳请求写入的字节数,则返回EAGAIN错误,提醒以后再写。
注意,原子操作(atomic opeartion)指的是由多步组成的操作。简单来讲,操作的原子性是指某一事务中的所有操作要么全部执行,要么全部不执行,不可能只执行所有步骤的一个子集。
接下来演示通过FIFO管道传输数据。
fifo_write程序源码:
#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <unistd.h>#include <limits.h>#include <stdlib.h>#include <time.h>#define BUFS PIPE_BUFint main(){int fd;int n, i;char buf[BUFS];time_t tp;printf("I am %d\n", getpid());if (access("fifo1", F_OK) != 0){if (mkfifo("fifo1", 0666)){printf("fifo1 not existed, and create fifo1 failed!\n");exit(1);}}if ((fd = open("fifo1", O_WRONLY)) < 0){printf("Open failed!\n");exit(1);}for (i = 0; i < 10; i++){time(&tp);n = sprintf(buf, "write_fifo %d sends %s", getpid(), ctime(&tp));printf("Send msg:%s", buf);if ((write(fd, buf, n + 1)) < 0){printf("Write failed!\n");close(fd);exit(1);}sleep(1);}close(fd);exit(0);}fifo_read程序源码:
#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <unistd.h>#include <fcntl.h>#include <limits.h>#include <stdlib.h>#define BUFS PIPE_BUFint main(){int fd;int len;char buf[BUFS];if ((fd = open("fifo1", O_RDONLY)) < 0){printf("Open failed\n");exit(1);}while ((len = read(fd, buf, BUFS)) > 0)printf("Read_fifo read: %s", buf);close(fd);exit(0);}
开启两个shell,一个运行fifo_write,一个运行fifo_read,fifo_write运行结果如下:
$ ./fifo_write
I am 9267
Send msg:write_fifo 9267 sends Mon Oct 26 09:45:34 2015
Send msg:write_fifo 9267 sends Mon Oct 26 09:45:35 2015
Send msg:write_fifo 9267 sends Mon Oct 26 09:45:36 2015
Send msg:write_fifo 9267 sends Mon Oct 26 09:45:37 2015
Send msg:write_fifo 9267 sends Mon Oct 26 09:45:38 2015
Send msg:write_fifo 9267 sends Mon Oct 26 09:45:39 2015
Send msg:write_fifo 9267 sends Mon Oct 26 09:45:40 2015
Send msg:write_fifo 9267 sends Mon Oct 26 09:45:41 2015
Send msg:write_fifo 9267 sends Mon Oct 26 09:45:42 2015
Send msg:write_fifo 9267 sends Mon Oct 26 09:45:43 2015
$
fifo_read运行结果如下:
$ ./fifo_read
Read_fifo read: write_fifo 9267 sends Mon Oct 26 09:45:34 2015
Read_fifo read: write_fifo 9267 sends Mon Oct 26 09:45:35 2015
Read_fifo read: write_fifo 9267 sends Mon Oct 26 09:45:36 2015
Read_fifo read: write_fifo 9267 sends Mon Oct 26 09:45:37 2015
Read_fifo read: write_fifo 9267 sends Mon Oct 26 09:45:38 2015
Read_fifo read: write_fifo 9267 sends Mon Oct 26 09:45:39 2015
Read_fifo read: write_fifo 9267 sends Mon Oct 26 09:45:40 2015
Read_fifo read: write_fifo 9267 sends Mon Oct 26 09:45:41 2015
Read_fifo read: write_fifo 9267 sends Mon Oct 26 09:45:42 2015
Read_fifo read: write_fifo 9267 sends Mon Oct 26 09:45:43 2015
$
程序fifo_write使用open函数以写方式打开一个名为fifo1的FIFO管道(当然,之前还要检测是否存在此文件,不存在则创建一个),并分10次向fifo1中写入字符串,其中的数据有当前进程ID及写入时的系统时间,并把这个数据传输出到标准输出,然后程序自动睡眠1秒。程序fifo_read使用open函数以读方式打开一个名为fifo1的FIFO管道,并循环读出管道的数据,这里使用while循环的作用就是确保数据可以全部读出,因为在读FIFO管道数据时,默认的是一次性读取PIPE_BUF个字节,当管道中数据多于PIPE_BUF个字节时,一次性读出PIPE_BUF - 1个字节,然后read函数返回,再打印数据到标准输出。
以上例子可以扩展成客户端与服务器通信的实例,fifo_write的作用类似于客户端,可以打开多个客户端向一个服务器发送请求信息,fifo_read类似于服务器,它适时监控着FIFO的读出端,当有数据时,读出并运行处理,但是有一个关键的问题是,每一个客户端必须预先知道服务器提供的FIFO接口。
4.4 访问FIFO文件
2. 现在,尝试向FIFO写数据。必须用另一个终端来执行下面的命令,因为第一个命令现在被挂起以等待数据出现在FIFO中。
$ echo "Hello World" > /tmp/my_fifo
你将看到cat命令产生输出。如果不向FIFO发送任何数据,cat命令将一直挂起,直到你中断它,常用的中断方式是使用组合键“Ctrl+c”。
3. 我们可以将第一个命令放在后台执行,这样即可一次执行两个命令:
$ cat < /tmp/my_fifo &
[1] 20321
$ echo "Hello World" > /tmp/my_fifo
Hello World
[1]+ Done cat < /tmp/my_fifo
$
解析:因为FIFO中没有数据,所以cat和echo程序都阻塞了,cat等待数据的到来,而echo等待其它进程读取数据。在上面的第三步中,cat进程一开始就在后台被阻塞了,当echo向它提供了一些数据后,cat命令读取这些数据并把它们打印到标准输出上,然后cat程序退出,不再等待更多的数据。它没有阻塞是因为当第二个命令将数据放入FIFO后,管道将被关闭,所以cat程序中的read调用返回0字节,表示已经到达文件尾。
现在我们已看过用命令行程序访问FIFO的情况,接下来我们将仔细分析FIFO的编程接口,它可以让我们在访问FIFO文件时更多地控制其读写行为。与通过pipe调用创建管道不同,FIFO是以命名文件的形式存在,而不是打开的文件描述符,所以在对它进行读写操作之前必须先打开它。FIFO也用open和close函数打开和关闭,这与我们前面看到的对文件的操作一样,但它多了一些其它的功能,传递给open调用的是FIFO的路径名,而不是一个正常的文件。
4.4.1 使用open打开FIFO文件
open(const char *path, O_RDONLY);
open(const char *path, O_RDONLY | O_NONBLOCK);
open(const char *path, O_WRONLY);
open(const char *path, O_WRONLY | O_NONBLOCK);
#include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <string.h>#include <fcntl.h>#include <sys/types.h>#include <sys/stat.h>#define FIFO_NAME "/tmp/my_fifo"int main(int argc, char **argv){ int res; int open_mode = 0; int i; if (argc < 2) { fprintf(stderr, "Usage: %s <some combination of O_RDONLY O_WRONLY O_NONBLOCK>\n", *argv); exit(EXIT_FAILURE); } for (i = 1; i < argc; i++) { if (strncmp(*++argv, "O_RDONLY", 8) == 0) open_mode |= O_RDONLY; if (strncmp(*argv, "O_WRONLY", 8) == 0) open_mode |= O_WRONLY; if (strncmp(*argv, "O_NONBLOCK", 10) == 0) open_mode |= O_NONBLOCK; } if (access(FIFO_NAME, F_OK) == -1) { res = mkfifo(FIFO_NAME, 0777); if (res != 0) { fprintf(stderr, "Could not create fifo %s\n", FIFO_NAME); exit(EXIT_FAILURE); } } printf("Process %d opening FIFO\n", getpid()); res = open(FIFO_NAME, open_mode); printf("Process %d result %d\n", getpid(), res); sleep(5); if (res != -1) close(res); printf("Process %d finished\n", getpid()); exit(EXIT_SUCCESS);}编译并运行发现,当运行时传入参数为O_RDONLY或者O_WRONLY的时候,程序在打开FIFO_NAME的时候就阻塞了。当运行参数传入O_NONBLOCK的时候,程序执行没有遇到阻塞的情况。打印结果略。
4.4.2 不带O_NONBLOCK标志的O_RDONLY和O_WRONLY
[1] 3682
$ Process 3682 opening FIFO
$ ./fifo_behaviour O_WRONLY
Process 3684 opening FIFO
Process 3684 result 3
Process 3682 result 3
Process 3684 finished
$ Process 3682 finished
4.4.3 带O_NONBLOCK标志的O_RDONLY和不带该标志的O_WRONLY
Process 3713 result 3
./fifo_behaviour O_WRONLY
Process 3714 opening FIFO
Process 3714 result 3
Process 3713 finished
Process 3714 finished
[1]+ Done ./fifo_behaviour O_RDONLY O_NONBLOCK
$
4.4.4 对FIFO进行读写操作
- 如果请求写入的数据长度小于等于PIPE_BUF字节,调用失败,数据不能写入。
- 如果请求写入的数据长度大于PIPE_BUF字节,将写入部分数据,返回实际写入的字节数,返回值也可能是0。
#include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <string.h>#include <fcntl.h>#include <limits.h>#include <sys/types.h>#include <sys/stat.h>#define FIFO_NAME "/tmp/my_fifo"#define BUFFER_SIZE PIPE_BUF#define TEN_MSG (1024 * 1024 * 10)int main(){ int fd; int res; int open_mode = O_WRONLY; int bytes_sent = 0; char buffer[BUFFER_SIZE + 1]; if (access(FIFO_NAME, F_OK) == -1) { res = mkfifo(FIFO_NAME, 0777); if (res != 0) { fprintf(stderr, "Could not create fifo %s\n", FIFO_NAME); exit(EXIT_FAILURE); } } printf("Process %d opening FIFO O_WRONLY\n", getpid()); fd = open(FIFO_NAME, open_mode); printf("Process %d result %d\n", getpid(), fd); if (fd != -1) { while (bytes_sent < TEN_MSG) { res = write(fd, buffer, BUFFER_SIZE); if (res == -1) { fprintf(stderr, "Write error on pipe\n"); exit(EXIT_FAILURE); } bytes_sent += res; } close(fd); } else exit(EXIT_FAILURE); printf("Process %d finished\n", getpid()); exit(EXIT_SUCCESS);}
#include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <string.h>#include <fcntl.h>#include <limits.h>#include <sys/types.h>#include <sys/stat.h>#define FIFO_NAME "/tmp/my_fifo"#define BUFFER_SIZE PIPE_BUFint main(){ int fd; int res; int open_mode = O_RDONLY; char buffer[BUFFER_SIZE + 1]; int bytes_read = 0; memset(buffer, '\0', sizeof(buffer)); printf("Process %d opening FIFO O_RDONLY\n", getpid()); fd = open(FIFO_NAME, open_mode); printf("Process %d result %d\n", getpid(), fd); if (fd != -1) { do { res = read(fd, buffer, BUFFER_SIZE); bytes_read += res; } while (res > 0); close(fd); } else exit(EXIT_FAILURE); printf("Process %d finished, %d bytes read\n", getpid(), bytes_read); exit(EXIT_SUCCESS);}
[1] 3913
Process 3913 opening FIFO O_WRONLY
$ time ./fifo_customer
Process 3914 opening FIFO O_RDONLY
Process 3913 result 3
Process 3914 result 3
Process 3914 finished, 10485760 bytes read
Process 3913 finished
real 0m0.061s
user 0m0.003s
sys 0m0.049s
[1]+ Done ./fifo_producer
$
4.4.5 高级主题:使用FIFO的客户/服务器应用程序
#include <unistd.h>#include <stdlib.h>#include <stdio.h>#include <string.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 20struct data_to_pass_st{ pid_t client_pid; char data[BUFFER_SIZE - 1];};服务端代码:
#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 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) { fprintf(stderr, "Server fifo failure\n"); exit(EXIT_FAILURE);} sleep(5); do { read_res = read(server_fifo_fd, &my_data, sizeof(my_data)); if (read_res > 0) { tmp_char_ptr = my_data.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);}客户端代码:
#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, "Sorry, no server\n"); exit(EXIT_FAILURE); } my_data.client_pid = getpid(); sprintf(client_fifo, CLIENT_FIFO_NAME, my_data.client_pid); if (mkfifo(client_fifo, 0777) == -1) { fprintf(stderr, "Sorry, can't make %s\n", client_fifo); exit(EXIT_FAILURE); } for (times_to_send = 0; times_to_send < 5; times_to_send++) { sprintf(my_data.data, "Hello from %d", my_data.client_pid); printf("%d sent %s, ", my_data.client_pid, my_data.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.data); close(client_fifo_fd); } } close(server_fifo_fd); unlink(client_fifo); exit(EXIT_SUCCESS);}为了测试这个程序,需要运行一个服务器和多个客户程序,为了让多个客户程序尽可能在同一时间启动,可以使用shell命令:
4331 sent Hello from 4331, received: HELLO FROM 4331
4332 sent Hello from 4332, received: HELLO FROM 4332
4328 sent Hello from 4328, received: HELLO FROM 4328
4332 sent Hello from 4332, received: HELLO FROM 4332
4328 sent Hello from 4328, received: HELLO FROM 4328
4332 sent Hello from 4332, received: HELLO FROM 4332
4328 sent Hello from 4328, received: HELLO FROM 4328
4332 sent Hello from 4332, received: HELLO FROM 4332
4328 sent Hello from 4328, received: HELLO FROM 4328
4332 sent Hello from 4332, received: HELLO FROM 4332
4331 sent Hello from 4331, received: HELLO FROM 4331
4331 sent Hello from 4331, received: HELLO FROM 4331
4331 sent Hello from 4331, received: HELLO FROM 4331
4331 sent Hello from 4331, received: HELLO FROM 4331
4329 sent Hello from 4329, received: HELLO FROM 4329
4330 sent Hello from 4330, received: HELLO FROM 4330
4330 sent Hello from 4330, received: HELLO FROM 4330
4329 sent Hello from 4329, received: HELLO FROM 4329
4330 sent Hello from 4330, received: HELLO FROM 4330
4329 sent Hello from 4329, received: HELLO FROM 4329
4329 sent Hello from 4329, received: HELLO FROM 4329
4330 sent Hello from 4330, received: HELLO FROM 4330
4330 sent Hello from 4330, received: HELLO FROM 4330
4329 sent Hello from 4329, received: HELLO FROM 4329
- 对它自己的服务器管道打开一个文件描述符,这样read调用将总是阻塞而不是返回0。
- 当read调用返回0时,关闭并重新打开服务器管道,使服务器进程阻塞在open调用处以等待客户的到来,就像它最初启动时那样。
- 进程间通信简介(三)——命名管道
- 进程间通信 —— 命名管道
- 进程间通信——命名管道
- 进程间通信——命名管道
- Windows进程间通信—命名管道
- 进程间通信—命名管道
- 进程通信—命名管道
- 进程间通信 —— 命名管道(实例)
- 进程间通信(三):利用命名管道
- Linux进程间通信—无名管道和命名管道
- 进程间通信简介(二)——管道
- linux进程间通信(命名管道)
- 进程间通信--命名管道(fifo)
- 进程间通信(2):命名管道
- 进程通信——命名管道
- 【进程通信】——命名管道
- Windows进程通信——命名管道
- Linux进程间通信——使用命名管道
- SimpleJdbc: 用于简化数据库操作的Java库
- 操作系统哲学家就餐问题——定义一个互斥信号量mutex
- 华为之系统架构师作用的浅谈
- androidStudio 多渠道打包
- ArcGIS API for Silverlight动态标绘的实现
- 进程间通信简介(三)——命名管道
- 【利用Python给MySQL解压包快速瘦身】
- 用生命去守护
- 关于地址总线,字长,内存容量,寻址范围的计算
- 黑马程序员——Java 基础:异常
- JAVA的异常机制
- MacOSX环境用Docker安装gitlab
- 最新hexo3和Github搭建个人博客遇到问题和解决方法汇总!
- flowerboard游戏MFC+GDIPLUS实现