管道通信
管道通信(Communication Pipeline)即发送进程以字符流形式将大量数据送入管道,接收进程可从管道接收数据,二者利用管道进行通信。无论是SQL Server用户,还是PB用户,作为C/S结构开发环境,他们在网络通信的实现上,都有一种共同的方法——命名管道。由于当前操作系统的不惟一性,各个系统都有其独自的通信协议,导致了不同系统间通信的困难。尽管TCP/IP协议目前已发展成为Internet的标准,但仍不能保证C/S应用程序的顺利进行。命名管道作为一种通信方法,有其独特的优越性,这主要表现在它不完全依赖于某一种协议,而是适用于任何协议——只要能够实现通信。
管道的创建和读写
#include <stdio.h>
#define SIZE 1024*100
int main()
{
FILE *fp = popen("ps -ef", "r");
if (fp == NULL)
{
perror ("popen");
return -1;
}
char buf[SIZE] = {0};
int ret = fread(buf, sizeof(char), SIZE-1, fp);
// printf ("读到的数据:\n %s\n", buf);
FILE *fp2 = popen("grep a.out", "w");
if (fp2 == NULL)
{
perror ("popen");
return -1;
}
fwrite (buf, sizeof(char), ret, fp2);
printf ("写入完成\n");
pclose (fp);
pclose (fp2);
return 0;
}
使用灵活性
命名管道具有很好的使用灵活性,表现在:
1) 既可用于本地,又可用于网络。
2) 可以通过它的名称而被引用。
3) 支持多客户机连接。
4) 支持双向通信。
5) 支持异步重叠I/O操作。
单个进程中的管道
#include <stdio.h>
#include <unistd.h>
#define SIZE 1024*100
int main()
{
int fd[2];
int ret = pipe(fd);
if (ret == -1)
{
perror ("pipe");
return -1;
}
ret = write (fd[1], "hello", 5);
printf ("写入 %d 个字节\n", ret);
char ch;
while (1)
{
// 如果管道里面没有数据可读,read会阻塞
ret = read (fd[0], &ch, 1);
if (ret == -1)
{
perror ("read");
break;
}
printf ("读到 %d 字节: %c\n", ret, ch);
}
close (fd[0]);
close (fd[1]);
return 0;
}
父子进程中管道通信
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
#define SIZE 1024
// 子进程通过管道从父进程接收数据
void child_do(int *fd)
{
// 将管道的写端关闭
close (fd[1]);
char buf [SIZE];
while (1)
{
// 从父进程读取数据
int ret = read (fd[0], buf, SIZE-1);
if (ret == -1)
{
perror ("read");
break;
}
buf[ret] = '\0';
printf ("子进程读到 %d 字节数据: %s\n", ret, buf);
}
// 关闭读端
close (fd[0]);
}
// 父进程通过管道向子进程发送数据
void father_do(int *fd)
{
// 将管道读端关闭
close (fd[0]);
char buf[SIZE];
while (1)
{
fgets (buf, SIZE, stdin);
// 向子进程发送数据
int ret = write (fd[1], buf, strlen(buf));
printf ("父进程发送了 %d 字节数据\n", ret);
}
// 关闭写端
close (fd[1]);
}
int main()
{
int fd[2];
// 创建管道
int ret = pipe(fd);
if (ret == -1)
{
perror ("pipe");
return -1;
}
// 创建子进程
pid_t pid = fork();
switch (pid)
{
case -1:
perror ("fork");
break;
case 0: // 子进程
child_do(fd);
break;
default:
father_do(fd);
break;
}
return 0;
}
父子进程中管道通信实现文件复制
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#define SIZE 1024
// 子进程通过管道从父进程接收数据
void child_do(int *fd)
{
// 将管道的写端关闭
close (fd[1]);
int fd_write = open ("2.mmap", O_WRONLY|O_CREAT, 0777);
if (fd_write == -1)
{
perror ("open");
return;
}
int ret;
char buf [SIZE];
// read 从管道读数据,如果管道没有数据可读,read 会阻塞
// 如果 管道的写端 被关闭, read 返回 0
while (ret = read (fd[0], buf, SIZE))
{
if (ret == -1)
{
perror ("read");
break;
}
// 把从父进程接收的数据写入到新文件中
write (fd_write, buf, ret);
}
printf ("文件复制完成\n");
// 关闭读端
close (fd[0]);
close (fd_write);
}
// 父进程通过管道向子进程发送数据
void father_do(int *fd)
{
// 将管道读端关闭
close (fd[0]);
int fd_read = open ("1.mmap", O_RDONLY);
if (fd_read == -1)
{
perror ("open");
return;
}
int ret;
char buf[SIZE];
while (ret = read (fd_read, buf, SIZE))
{
if (ret == -1)
{
perror ("read");
break;
}
// 把读到的内容发送给子进程
write (fd[1], buf, ret);
}
// 关闭写端
close (fd[1]);
close (fd_read);
}
int main()
{
int fd[2];
// 创建管道
int ret = pipe(fd);
if (ret == -1)
{
perror ("pipe");
return -1;
}
// 创建子进程
pid_t pid = fork();
switch (pid)
{
case -1:
perror ("fork");
break;
case 0: // 子进程
child_do(fd);
break;
default:
father_do(fd);
break;
}
return 0;
}
管道读端关闭,写端继续写
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
#define SIZE 1024
// 子进程通过管道从父进程接收数据
void child_do(int *fd)
{
close (fd[1]);
close (fd[0]);
}
void father_do(int *fd)
{
// 将管道读端关闭
close (fd[0]);
printf ("等待子进程关闭读端\n");
sleep(2);
// 所有读端都关闭了,写端继续往管道写入数据
// 如果管道所有的读端都被关闭,继续写数据系统默认的操作是使程序退出
write (fd[1], "hello", 5);
printf ("11111111111111111111111111111111\n");
// 关闭写端
close (fd[1]);
}
int main()
{
int fd[2];
// 创建管道
int ret = pipe(fd);
if (ret == -1)
{
perror ("pipe");
return -1;
}
// 创建子进程
pid_t pid = fork();
switch (pid)
{
case -1:
perror ("fork");
break;
case 0: // 子进程
child_do(fd);
break;
default:
father_do(fd);
break;
}
return 0;
}
程序设计的注意事项
2. ReadFile和WriteFile的hFile句柄是由CreateFile及ConnectNamedPipe返回得到。
3.一个已被某客户端连接的管道句柄在被另一客户通过ConnectNamedPipe建立连接之前,服务端必须用DisconnectNamedPipe函数对已存在的连接进行强行拆离。服务端拆离管道会造成管道中数据的丢失,用
FlushFileBuffers函数可以保证数据不被丢失。