管道提供单向数据流,fd[0]读,fd[1]写
创建全双工IPC管道的方法是 socketpair().
2. 示例
由于管道是字符流,所以需要用 分隔符 或 格式化的 方式 避免粘包
#include <linux/limits.h>
#include <sys/uio.h>
#include <signal.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#include <stdio.h>
#include <limits.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
typedef struct msg_hdr {
int len;
} msg_hdr_t;
int msg_read(int fd, char *buf, int buf_size)
{
msg_hdr_t msg_hdr;
int nbytes;
nbytes = read(fd, &msg_hdr, sizeof(msg_hdr_t));
if (nbytes < 0) {
perror("read");
return -1;
}
if (nbytes == 0) { // 管道写端全关闭,说明对方已经停止会话
return -2;
}
if (msg_hdr.len == 0) { // 一次 单向通信 结束
return 0;
}
nbytes = read(fd, buf, msg_hdr.len);
if (nbytes < 0) {
return -1;
}
if (nbytes != msg_hdr.len) {
return -1;
}
return msg_hdr.len;
}
void client(int readfd, int writefd)
{
char buf[LINE_MAX]; // buf 大小设置为LINE_MAX,让程序不需要担心输入限制
int len, nbytes;
while (fgets(buf, sizeof(buf), stdin)) { // 行输入, 用户输入EOF,退出
len = strlen(buf);
if (len > PIPE_BUF) { // 若输入长度大于 PIPE_BUF,导致pipe不能保证原子性
printf("filename is too long\n");
continue;
}
if (buf[0] == '\n') {
continue;
}
nbytes = write(writefd, buf, len); // 输入的数据以\n为分隔符
if (nbytes < 0) {
perror("write");
continue;
}
if (nbytes != len) { // 因为保证了输入长度小于PIPE_BUF,所以pipe必须保证输入的原子性,所以nbytes != len 是不应该的
printf("error in pipe write\n");
}
while ((nbytes = msg_read(readfd, buf, sizeof(buf))) > 0) {
fwrite(buf, 1, nbytes, stdout);
}
if (nbytes == -1) {
perror("msg_read error\n");
continue;
}
if (nbytes == -2) {
break;
}
}
printf("client quit\n");
close(readfd); // 向对方发送 EOF
close(writefd);
return;
}
int msg_send(int fd, char *buf, int n)
{
char *p;
int len, max;
msg_hdr_t msg;
max = PIPE_BUF - sizeof(msg_hdr_t); // pipe只保证 数据大小小于 PIPE_BUF 的原子性,为了保证 数据报的完整,必须 小于等于 PIPE_BUF
p = buf;
if (n == 0 || buf == NULL) {
msg.len = 0;
if (write(fd, &msg, sizeof(msg)) < 0) {
perror("write");
return -1;
}
return 0;
}
while (n > 0) {
len = n > max ? max : n;
msg.len = len;
struct iovec iov[2]; // 向量IO能保证原子性,并避免多余的内存消耗
iov[0].iov_base = &msg;
iov[0].iov_len= sizeof(msg);
iov[1].iov_base = p;
iov[1].iov_len = msg.len;
if (writev(fd, iov, sizeof(iov)/sizeof(*iov)) < 0) {
perror("writev");
return -1;
}
n -= len;
p += len;
}
return 0;
}
void server(int readfd, int writefd)
{
FILE *fp;
int fd, nbytes;
char buf[LINE_MAX];
fp = fdopen(readfd, "r"); // readfd的数据流,使用\n作为分隔符,所以利用标准IO的行缓存机制
if (fp == NULL) {
perror("fdopen");
return;
}
while (fgets(buf, sizeof(buf), fp)) { // 以\n为分隔符
char *ptr = strchr(buf, '\n'); // 去掉分隔符
*ptr = '\0';
fd = open(buf, O_RDONLY);
if (fd < 0) { // 若错误,则返回报错信息
strcpy(buf, strerror(errno));
if (msg_send(writefd, buf, strlen(buf)) < 0) {
perror("server write");
break;
}
msg_send(writefd, NULL, 0);
continue;
}
while ((nbytes = read(fd, buf, sizeof(buf))) > 0) {
msg_send(writefd, buf, nbytes); // 报文头显示说明数据长度
}
if (nbytes < 0) {
strcpy(buf, strerror(errno));
if (msg_send(writefd, buf, strlen(buf)) < 0) {
perror("server write");
break;
}
msg_send(writefd, NULL, 0);
}
msg_send(writefd, NULL, 0);
close(fd);
}
printf("server quit\n");
close(writefd);
close(fd);
fclose(fp);
}
int main()
{
pid_t pid;
int fd[4];
if ((pipe(fd) < 0) || (pipe(fd + 2) < 0)) {
perror("pipe");
return -1;
}
signal(SIGPIPE, SIG_IGN); // 写 无读端 的pipe 导致 SIGPIPE, SIGPIPE 默认动作为终止进程,忽略该信号,让write返回 EPIPE
pid = fork();
switch (pid) {
case 0:
close(fd[2]);
close(fd[1]); // 必须把 不需要的描述符关闭,否则 关闭 写端,对方无法受到 EOF
client(fd[0], fd[3]);
exit(0);
break;
case -1:
break;
default:
close(fd[0]);
close(fd[3]);
server(fd[2], fd[1]);
}
wait(NULL);
return 0;
}