一、简介
管道是没有名字的,管道创建的资源由内核管理,单个程序中不同进程通过管道描述符fd进行通信,对于程序和程序之间是无法通信的。
FIFO是有名字的(也称为 有名管道),每一个FIFO都有一个文件与之关联,但仅限于同一主机程序与程序之间通信,无法通过在NFS上创建FIFO通信。
二、管道
所有管道都是半双工的即单向的,只提供一个方向的数据流。管道的经典用途是为两个不同的进程(一个父进程,一个子进程)提供进程间通信手段。
案例示意图:
pipe_example.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <errno.h>
#define MAX_LINE 1024
void client(int read_fd, int write_fd)
{
size_t len;
ssize_t n;
char buff[MAX_LINE];
/* read pathname for user input */
fgets(buff, MAX_LINE, stdin);
len = strlen(buff);
if (buff[len - 1] == '\n')
len--;
/* write pathname to IPC channel */
write(write_fd, buff, len);
/* read for IPC channel, write to standard output */
while ((n = read(read_fd, buff, MAX_LINE)) > 0)
{
write(STDOUT_FILENO, buff, n);
}
}
void server(int read_fd, int write_fd)
{
int fd;
ssize_t n;
char buff[MAX_LINE];
/* read pathname for IPC channel. if not any message arrived, block at here. */
if ((n = read(read_fd, buff, MAX_LINE)) == 0)
{
printf("error: end-of-file while reading pathname\n");
exit(EXIT_FAILURE);
}
buff[n] = '\0'; // null terminate pathname
if ((fd = open(buff, O_RDONLY)) < 0) // error must tell client
{
snprintf(buff + n, sizeof(buff) - n, ":can't open, %s\n", strerror(errno));
n = strlen(buff);
write(write_fd, buff, n);
}
else
{
/* read file , then write to pipe channel.
* if the last character of file is EOF, break while, exit server.
*/
while ((n = read(fd, buff, MAX_LINE)) > 0)
{
write(write_fd, buff, n);
}
close(fd);
}
}
int main(int argc, char **argv)
{
int pipe1[2], pipe2[2];
pid_t child_pid;
/* create 2 pipes */
if (pipe(pipe1) != 0)
{
printf("failed to create pipe1\n");
exit(EXIT_FAILURE);
}
if (pipe(pipe2) != 0)
{
printf("failed to create pipe2\n");
exit(EXIT_FAILURE);
}
child_pid = fork();
if (child_pid == 0) /* child */
{
close(pipe1[1]);
close(pipe2[0]);
server(pipe1[0], pipe2[1]);
exit(0);
}
else if (child_pid > 0) /* parent */
{
close(pipe1[0]);
close(pipe2[1]);
client(pipe2[0], pipe1[1]);
waitpid(child_pid, NULL, 0);
}
else
{
printf("failed to fork process\n");
exit(EXIT_FAILURE);
}
exit (0);
}
运行测试:
三、FIFO
3.1 进程间通信(FIFO)
对于管道案例进行改写为fifo,修改部分不涉及 server(int, int) and client(int, int)
。
程序(FIFO)示意图:
fifo_example.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <errno.h>
#define MAX_LINE 1024
#define FIFO1 "/tmp/fifo.1"
#define FIFO2 "/tmp/fifo.2"
/* default permission for new files. */
#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
void client(int read_fd, int write_fd)
{
size_t len;
ssize_t n;
char buff[MAX_LINE];
/* read pathname for user input */
fgets(buff, MAX_LINE, stdin);
len = strlen(buff);
if (buff[len - 1] == '\n')
len--;
/* write pathname to IPC channel */
write(write_fd, buff, len);
/* read for IPC channel, write to standard output */
while ((n = read(read_fd, buff, MAX_LINE)) > 0)
{
write(STDOUT_FILENO, buff, n);
}
}
void server(int read_fd, int write_fd)
{
int fd;
ssize_t n;
char buff[MAX_LINE];
/* read pathname for IPC channel. if not any message arrived, block at here. */
if ((n = read(read_fd, buff, MAX_LINE)) == 0)
{
printf("error: end-of-file while reading pathname\n");
exit(EXIT_FAILURE);
}
buff[n] = '\0'; // null terminate pathname
if ((fd = open(buff, O_RDONLY)) < 0) // error must tell client
{
snprintf(buff + n, sizeof(buff) - n, ":can't open, %s\n", strerror(errno));
n = strlen(buff);
write(write_fd, buff, n);
}
else
{
/* read file , then write to pipe channel.
* if the last character of file is EOF, break while, exit server.
*/
while ((n = read(fd, buff, MAX_LINE)) > 0)
{
write(write_fd, buff, n);
}
close(fd);
}
}
int main(int argc, char **argv)
{
int read_fd, write_fd;
pid_t child_pid;
/* create 2 FIFOs. */
if ((mkfifo(FIFO1, FILE_MODE)) < 0 && (errno != EEXIST))
{
printf("error: can't create %s\n", FIFO1);
exit(EXIT_FAILURE);
}
if ((mkfifo(FIFO2, FILE_MODE)) < 0 && (errno != EEXIST))
{
unlink(FIFO1);
printf("error: can't create %s\n", FIFO2);
exit(EXIT_FAILURE);
}
child_pid = fork();
if (child_pid == 0) /* child */
{
read_fd = open(FIFO1, O_RDONLY, 0);
write_fd = open(FIFO2, O_WRONLY, 0);
server(read_fd, write_fd);
exit(0);
}
else if (child_pid > 0) /* parent */
{
write_fd = open(FIFO1, O_WRONLY, 0);
read_fd = open(FIFO2, O_RDONLY, 0);
client(read_fd, write_fd);
waitpid(child_pid, NULL, 0);
close(read_fd);
close(write_fd);
}
else
{
printf("failed to fork process\n");
exit(EXIT_FAILURE);
}
unlink(FIFO1);
unlink(FIFO2);
exit (0);
}
运行测试:
3.2 程序间通信(FIFO)
FIFO更多使用在程序和程序间,例如程序A往FIFO写数据,程序B往FIFO读数据。
fifo_client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <errno.h>
#define MAX_LINE 1024
#define FIFO1 "/tmp/fifo.1"
#define FIFO2 "/tmp/fifo.2"
/* default permission for new files. */
#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
void client(int read_fd, int write_fd)
{
size_t len;
ssize_t n;
char buff[MAX_LINE];
/* read pathname for user input */
fgets(buff, MAX_LINE, stdin);
len = strlen(buff);
if (buff[len - 1] == '\n')
len--;
/* write pathname to IPC channel */
write(write_fd, buff, len);
/* read for IPC channel, write to standard output */
while ((n = read(read_fd, buff, MAX_LINE)) > 0)
{
write(STDOUT_FILENO, buff, n);
}
}
int main(int argc, char **argv)
{
int read_fd, write_fd;
write_fd = open(FIFO1, O_WRONLY, 0);
read_fd = open(FIFO2, O_RDONLY, 0);
client(read_fd, write_fd);
close(read_fd);
close(write_fd);
unlink(FIFO1);
unlink(FIFO2);
exit(0);
}
fifo_server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <errno.h>
#define MAX_LINE 1024
#define FIFO1 "/tmp/fifo.1"
#define FIFO2 "/tmp/fifo.2"
/* default permission for new files. */
#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
void server(int read_fd, int write_fd)
{
int fd;
ssize_t n;
char buff[MAX_LINE];
/* read pathname for IPC channel. if not any message arrived, block at here. */
if ((n = read(read_fd, buff, MAX_LINE)) == 0)
{
printf("error: end-of-file while reading pathname\n");
exit(EXIT_FAILURE);
}
buff[n] = '\0'; // null terminate pathname
if ((fd = open(buff, O_RDONLY)) < 0) // error must tell client
{
snprintf(buff + n, sizeof(buff) - n, ":can't open, %s\n", strerror(errno));
n = strlen(buff);
write(write_fd, buff, n);
}
else
{
/* read file , then write to pipe channel.
* if the last character of file is EOF, break while, exit server.
*/
while ((n = read(fd, buff, MAX_LINE)) > 0)
{
write(write_fd, buff, n);
}
close(fd);
}
}
int main(int argc, char **argv)
{
int read_fd, write_fd;
if ((mkfifo(FIFO1, FILE_MODE)) < 0 && (errno != EEXIST))
{
printf("error: can't create %s\n", FIFO1);
exit(EXIT_FAILURE);
}
if ((mkfifo(FIFO2, FILE_MODE)) < 0 && (errno != EEXIST))
{
unlink(FIFO1);
printf("error: can't create %s\n", FIFO2);
exit(EXIT_FAILURE);
}
read_fd = open(FIFO1, O_RDONLY, 0);
write_fd = open(FIFO2, O_WRONLY, 0);
server(read_fd, write_fd);
exit(0);
}
运行测试:
先启动 fifo_server程序,再启动 fifo_client程序。
3.3 单服务器-多客户端通信(FIFO)
FIFO的真正优势表现在服务器可以是一个长期运行的进程(例如 守护进程),而且与其客户可以无情缘关系。
如下例子:客户端通过 众所周知的路径名创建的FIFO
发送一个请求,服务器解析请求并通过创建一个与客户端pid关联的FIFO,将响应写入这个FIFO。客户端发送请求后,打开 一个与自己pid关联的FIFO
,等待服务器的响应。
服务端代码
server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <errno.h>
#define MAX_LINE 1024
#define SERV_FIFO "/tmp/fifo.serv"
/* default permission for new files. */
#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
int read_cnt = 0;
char *read_ptr = NULL;
char read_buf[MAX_LINE] = {0};
static ssize_t my_read(int fd, char *ptr)
{
if (read_cnt <= 0)
{
again:
if ((read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0)
{
if (errno == EINTR)
goto again;
return -1;
}
else if (read_cnt == 0)
{
return 0;
}
read_ptr = read_buf;
}
read_cnt--;
*ptr = *read_ptr++;
return 1;
}
/*
* @return if the return value greater than or equal to 0, result is successful.
* if 0, read EOF. if -1, error occur.
*/
ssize_t readline(int fd, void *vptr, size_t max_len)
{
ssize_t i = 0;
ssize_t ret = 0;
char ch = '\0';
char *ptr = NULL;
ptr = (char *)vptr;
for (i = 1; i < max_len; i++)
{
if ((ret = my_read(fd, &ch)) == 1)
{
*ptr++ = ch;
if (ch == '\n') // newline is stored, return
break;
}
else if (ret == 0) // EOF, n - 1 bytes were read
{
*ptr = 0;
return (i - 1);
}
else
{
return -1; // error, errno set by read()
}
}
*ptr = 0; // null terninate
return i;
}
int main(int argc, char **argv)
{
int read_fifo, write_fifo, dummy_fd, fd;
char *ptr, buff[MAX_LINE + 1], fifo_name[MAX_LINE];
pid_t pid;
ssize_t n;
/* create server's well-known FIFO, server read-only */
if ((mkfifo(SERV_FIFO, FILE_MODE)) < 0 && errno != EEXIST)
{
printf("error: can't create %s\n", SERV_FIFO);
exit(EXIT_FAILURE);
}
read_fifo = open(SERV_FIFO, O_RDONLY, 0);
dummy_fd = open(SERV_FIFO, O_WRONLY, 0); // never use
/* receive request from client, and respond to client */
while ((n = readline(read_fifo, buff, MAX_LINE)) > 0)
{
printf("server read (%s) fifo msg:%s", SERV_FIFO, buff);
if (buff[n - 1] == '\n')
n--;
buff[n] = '\0';
if ((ptr = strchr(buff, ' ')) == NULL)
{
printf("bogus request: %s\n", buff);
continue;
}
*ptr++ = 0; // null terminate PID, ptr = pathname
pid = atol(buff);
snprintf(fifo_name, sizeof(fifo_name), "/tmp/fifo.%ld", (long)pid);
if ((write_fifo = open(fifo_name, O_WRONLY, 0)) < 0)
{
printf("error: can't open %s\n", fifo_name);
continue;
}
if ((fd = (open(ptr, O_NDELAY))) < 0)
{
/* error must tell client */
snprintf(buff + n, sizeof(buff) - n, ": can't open, %s\n", strerror(errno));
n = strlen(ptr);
write(write_fifo, ptr, n);
close(write_fifo);
}
else
{
/* open succeeded: copy file to FIFO */
while ((n = read(fd, buff, MAX_LINE)) > 0)
{
write(write_fifo, buff, n);
}
close(fd);
close(write_fifo);
}
}
exit(0);
}
客户端代码
client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <errno.h>
#define MAX_LINE 1024
#define SERV_FIFO "/tmp/fifo.serv"
#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
int main(int argc, char **argv)
{
int read_fifo, write_info;
size_t len;
ssize_t n;
char *ptr, fifo_name[MAX_LINE], buff[MAX_LINE];
pid_t pid;
/* create FIFO with our PID as part of name */
pid = getpid();
snprintf(fifo_name, sizeof(fifo_name), "/tmp/fifo.%ld", (long)pid);
if ((mkfifo(fifo_name, FILE_MODE) < 0) && (errno != EEXIST))
{
printf("error: can't create %s\n", fifo_name);
exit(EXIT_FAILURE);
}
snprintf(buff, sizeof(buff), "%ld ", (long)pid);
len = strlen(buff);
ptr = buff + len;
if (write_info = open(SERV_FIFO, O_WRONLY, 0) < 0)
{
printf("error: can't open %s\n", SERV_FIFO);
unlink(fifo_name);
exit(EXIT_FAILURE);
}
/* read pathname from stdin, and append buff string.
* read string from stdin include '\n'
*/
fgets(ptr, MAX_LINE - len, stdin);
len = strlen(buff);
write(write_info, buff, len);
printf("client write (%s)fifo msg:%s", SERV_FIFO, buff);
/* open our FIFO, blocks until server opens for writing */
read_fifo = open(fifo_name, O_RDONLY, 0);
while ((n = read(read_fifo, buff, MAX_LINE)) > 0)
{
write(STDOUT_FILENO, buff, n);
}
close(read_fifo);
unlink(fifo_name);
exit(0);
}
运行测试:
若服务端未启动,客户端启动时,无法发送请求。
启动服务端后,再启动客户端
通过 shell命令(echo)
给服务端发送请求,然后读取响应内容。
四、管道和FIFO限制
系统对于管道和FIFO的唯一限制为:
- OPEN_MAX : 一个进程在任意时刻打开的最大描述符数(Posix要求至少为16)
- PIPE_BUF : 可原子地写往管道或FIFO的最大数据量(Posix要求至少为512)