1、概述和一个简单的客户-服务器例子
管道是最初的Unix IPC的形式,其局限性在于没有名字,从而只能由有亲缘关系的进程使用。FIFO有时又称有名管道,其最大好处可用于无亲缘关系的进程间通信。
利用客户与服务器利用IPC通道通信样例
2、管道(一般指半双工模式)
a、pipe函数及实现方式
#include<unistd.h>
int pipe(int fd[2]);//返回:若成功则为0,若出错则为-1
pipe函数返回两个文件描述符:fd[0]和fd[1],前者打开来读,后者打开来写。
上图中由左边到右边的实际步骤如下:
i、创建管道1(fd1[0]和fd1[1])和管道2(fd2[0]和fd2[1]);
ii、fork;
iii、父进程关闭管道1的读入端(fd1[0]);
iv、父进程关闭管道2的写入端(fd2[1]);
v、子进程关闭管道1的写入端(fd2[1]);
vi、子进程关闭管道2的读入端(fd2[0]);
实际代码参见如下,
#include "unpipc.h"
void client(int, int), server(int, int);
int
main(int argc, char **argv)
{
int pipe1[2], pipe2[2];
pid_t childpid;
Pipe(pipe1); /* create two pipes */
Pipe(pipe2);
if ((childpid = Fork()) == 0) { /* child */
Close(pipe1[1]);//关闭管道1的写出
Close(pipe2[0]);//关闭管道2的读入
server(pipe1[0], pipe2[1]);
exit(0);
}
/* 4parent */
Close(pipe1[0]);//关闭管道1的读入
Close(pipe2[1]);//关闭管道2的写出
client(pipe2[0], pipe1[1]);
Waitpid(childpid, NULL, 0); /* wait for child to terminate */
exit(0); //waitpid调用是防止出现僵尸进程占用资源
}
void
client(int readfd, int writefd)
{
size_t len;
ssize_t n;
char buff[MAXLINE];
/* 4read pathname */
Fgets(buff, MAXLINE, stdin);
len = strlen(buff); /* fgets() guarantees null byte at end */
if (buff[len - 1] == '\n')
len--; /* delete newline from fgets() */
/* 4write pathname to IPC channel */
Write(writefd, buff, len);
/* 4read from IPC, write to standard output */
while ((n = Read(readfd, buff, MAXLINE)) > 0)
Write(STDOUT_FILENO, buff, n);
}
void
server(int readfd, int writefd)
{
int fd;
ssize_t n;
char buff[MAXLINE + 1];
/* 4read pathname from IPC channel */
if ((n = Read(readfd, buff, MAXLINE)) == 0)
err_quit("end-of-file while reading pathname");
buff[n] = '\0'; /* null terminate pathname */
if ((fd = open(buff, O_RDONLY)) < 0) {
/* 4error: must tell client */
snprintf(buff + n, sizeof(buff) - n, ": can't open, %s\n",
strerror(errno));
n = strlen(buff);
Write(writefd, buff, n);
}
else {
/* 4open succeeded: copy file to IPC channel */
while ((n = Read(fd, buff, MAXLINE)) > 0)
Write(writefd, buff, n);
Close(fd);
}
}
b、全双工管道
测试全双工管道的双向通信能力举例,
#include "unpipc.h"
int
main(int argc, char **argv)
{
int fd[2], n;
char c;
pid_t childpid;
Pipe(fd); /* assumes a full-duplex pipe (e.g., SVR4) */
if ( (childpid = Fork()) == 0) { /* child */
sleep(3);//先睡3s,让父进程先写入数据
if ( (n = Read(fd[0], &c, 1)) != 1)
err_quit("child: read returned %d", n);
printf("child read %c\n", c);
Write(fd[0], "c", 1);
exit(0);
}
/* 4parent */
Write(fd[1], "p", 1);//写入一个字节
if ( (n = Read(fd[1], &c, 1)) != 1)//由于是全双工,写出与读入处于两个关双工管道,两者之间互不干扰
err_quit("parent: read returned %d", n);
printf("parent read %c\n", c);
exit(0);
}
3、FIFO
FIFO指代先进先出,Unix中的FIFO类似于管道。它是一个单向(半双工)数据流。每个FIFO有一个路径名与之关联,从而允许无亲缘关系的进程访问同一个FIFO。FIFO也称为有名管道。
FIFO创建函数如下,
#include<sys/types.h>
#include<sys/stat.h>
int mkfifo(const char* pathname, mode_t mode);//返回:若成功则为0,若出错则为-1
mode参数指定文件权限位,如S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH。
mkfifo函数已隐含指定O_CREAT | O_EXCL。也就是说该函数要么创建一个新的FIFO,要么返回一个EEXIST错误(可以忽略)。mkfifo函数调用完后,再就是调用open创建FIFO描述符。具体举例如下,
以下是FIFO运用,举例,
#include "unpipc.h"
#define FIFO1 "/tmp/fifo.1"
#define FIFO2 "/tmp/fifo.2"
void client(int, int), server(int, int);
int
main(int argc, char **argv)
{
int readfd, writefd;
pid_t childpid;
/* 4create two FIFOs; OK if they already exist */
if ((mkfifo(FIFO1, FILE_MODE) < 0) && (errno != EEXIST))//此处FILE_MODE(即权限)设为644
err_sys("can't create %s", FIFO1);
if ((mkfifo(FIFO2, FILE_MODE) < 0) && (errno != EEXIST)) {
unlink(FIFO1);
err_sys("can't create %s", FIFO2);
}
if ((childpid = Fork()) == 0) { /* child */
readfd = Open(FIFO1, O_RDONLY, 0);//先读后写
writefd = Open(FIFO2, O_WRONLY, 0);
server(readfd, writefd);
exit(0);
}
/* 4parent */
writefd = Open(FIFO1, O_WRONLY, 0);//先写后读,预防死锁
readfd = Open(FIFO2, O_RDONLY, 0);//因为没有任何进程打开某FIFO写,则打开该FIFO读的进程将阻塞
client(readfd, writefd);
Waitpid(childpid, NULL, 0); /* wait for child to terminate */
Close(readfd);
Close(writefd);
Unlink(FIFO1);//删除管道FIFO的名字
Unlink(FIFO2);
exit(0);
}
4、利用FIFO实现单个服务器与多个客户
上图的代码实现,
服务器程序:
#include "unpipc.h"
#define SERV_FIFO "/tmp/fifo.serv"
void server(int, int);
int
main(int argc, char **argv)
{
int readfifo, writefifo, dummyfd, fd;
char *ptr, buff[MAXLINE], fifoname[MAXLINE];
pid_t pid;
ssize_t n;
/* 4create server's well-known FIFO; OK if already exists */
if ((mkfifo(SERV_FIFO, FILE_MODE) < 0) && (errno != EEXIST))
err_sys("can't create %s", SERV_FIFO);
/* 4open server's well-known FIFO for reading and writing */
readfifo = Open(SERV_FIFO, O_RDONLY, 0);
dummyfd = Open(SERV_FIFO, O_WRONLY, 0); /* never used *///由于此描述符存在,当不再有客户存在时,服务器一定不会返回0以指示读到一个EOF
while ((n = Readline(readfifo, buff, MAXLINE)) > 0) {
if (buff[n - 1] == '\n')
n--; /* delete newline from readline() */
buff[n] = '\0'; /* null terminate pathname */
if ((ptr = strchr(buff, ' ')) == NULL) {//strchr函数返回赋给ptr的指针指向客户请求行中的空格
err_msg("bogus request: %s", buff);
continue;
}
*ptr++ = 0; /* null terminate PID, ptr = pathname */
pid = atol(buff);//转换PID号到整型
snprintf(fifoname, sizeof(fifoname), "/tmp/fifo.%ld", (long)pid);//提取出客户的fifo名称
if ((writefifo = open(fifoname, O_WRONLY, 0)) < 0) {//以只写打开客户端的fifo
err_msg("cannot open: %s", fifoname);
continue;
}
if ((fd = open(ptr, O_RDONLY)) < 0) {
/* 4error: must tell client */
snprintf(buff + n, sizeof(buff) - n, ": can't open, %s\n",
strerror(errno));
n = strlen(ptr);
Write(writefifo, ptr, n);
Close(writefifo);
}
else {
/* 4open succeeded: copy file to FIFO */
while ((n = Read(fd, buff, MAXLINE)) > 0)//读入文个把信息,当FIFO变为空时,服务器的read返回0,表示是个EOF
Write(writefifo, buff, n);//往writefifo管道中写入文件信息
Close(fd);
Close(writefifo);
}
}
}
客户端程序:
#include "unpipc.h"
#define SERV_FIFO "/tmp/fifo.serv"
int
main(int argc, char **argv)
{
int readfifo, writefifo;
size_t len;
ssize_t n;
char *ptr, fifoname[MAXLINE], buff[MAXLINE];
pid_t pid;
/* 4create FIFO with our PID as part of name */
pid = getpid();
snprintf(fifoname, sizeof(fifoname), "/tmp/fifo.%ld", (long)pid);
if ((mkfifo(fifoname, FILE_MODE) < 0) && (errno != EEXIST))
err_sys("can't create %s", fifoname);
/* 4start buffer with pid and a blank */
snprintf(buff, sizeof(buff), "%ld ", (long)pid);//往buf中写入pid号
len = strlen(buff);
ptr = buff + len;
/* 4read pathname */
Fgets(ptr, MAXLINE - len, stdin);//从标准输入中获取需要打开的文件
len = strlen(buff); /* fgets() guarantees null byte at end */
/* 4open FIFO to server and write PID and pathname to FIFO */
writefifo = Open(SERV_FIFO, O_WRONLY, 0);//打开服务器众所周知的FIFO管道(只写)
Write(writefifo, buff, len);//写入进程号及需要打开的文件名+路径名
/* 4now open our FIFO; blocks until server opens for writing */
readfifo = Open(fifoname, O_RDONLY, 0);
/* 4read from IPC, write to standard output */
while ((n = Read(readfifo, buff, MAXLINE)) > 0)
Write(STDOUT_FILENO, buff, n);//写出标准输出
Close(readfifo);//关闭描述符
Unlink(fifoname);//删除该FIFO
exit(0);
}
5、字节流和消息
本章使用管道和FIFO的例子都使用了字节流I/O模型,也即Unix的原生I/O模型。
当数据由长度可变消息构成,且读出者必须知道这些消息的边界以判定何时已读出单个消息时,便需要对所传送的数据加上某种结构而实现,三种技巧常用于此目的。
i、带内特殊终止序列:许多Unix应用程序使用换行符来分隔每个消息。写进程会给每个消息添加一个换行符,读进程则每次读出一行。
ii、显式长度:每个记录前冠以它的长度。其优势是不再需要通过转义出现在数据中的分隔符,因为接收者不必扫描整个数据以寻找每个记录的结束位置。(具体代码实现见p52-p55)
iii、每次连接一个记录:应用通过关闭与其对端的连接(网络应用时为tcp连接,IPC时为IPC连接)来指示一个记录的结束。
以上知识点来均来自steven先生所著UNP卷二(version2),刚开始学习网络编程,如有不正确之处请大家多多指正。