IPC-管道
内容提要:
- 管道简介
- 使用无名管道实现一个简单的本地文件服务器
- 使用标准I/O函数库提供的管道实现
- 使用popen实现本地文件服务器
- 有名管道-FIFO
- 使用FIFO改写本地文件服务器
- 管道和FIFO的额外属性
- 使用FIFO将本地文件服务器改写成单服务器多客户端
- FIFO和NFS的关系
- 字节流和消息
- 使用自定义的结构化接口改写单服务多客户端程序
- 管道和FIFO的限制
1.管道简介
Unix中的进程间通信方式之一是通过管道实现的,管道分为有名管道和无名管道,对于有名管道FIFO,可以实现没有亲缘关系的进程间通信,而对于无名管道,可以实现父子进程间的通信。
管道这种IPC存在的意义是为了实现进程间消息的传递。无名管道是Unix最初的IPC形式,但是由于无名管道的局限性,后来出现了有名管道FIFO,这种管道由于可以在文件系统中创建一个名字,所以可以被没有亲缘关系的进程访问。
管道打开后的标识是以文件描述符的形式提供的,可以使用Unix系统中的read和write系统调用访问。
管道的实现形式有多种,在一些系统中,管道被实现为全双工的,在管道的一端既可以读也可以写,但是Posix.1和Unix 98只要求半双工管道,在Linux系统中,管道是半双工的。
IPC类型 |
持续性 |
用于打开或创建IPC的名字空间 |
IPC打开后的标识 |
fork, exec和exit对IPC对象的影响 |
||
fork |
exec |
_exit |
||||
管道 |
随进程 |
没有名字 |
描述符 |
子进程取得父进程的所有打开着的描述符的副本 |
所有打开着的描述符继续打开着,除非已经设置描述符的FD_CLOEXEC位 |
关闭所有打开着的描述符,最后一个关闭时删除管道或FIFO中残留的所有数据 |
FIFO |
路径名 |
Unix中的无名管道是通过 pipe 函数创建的,该函数创建了一个半双工的管道。
1 #include <unistd.h> 2 3 int pipe(int fd[2]); 4 5 返回值:成功返回0,出错返回-1
函数通过参数fd[2]返回两个描述符,fd[0]表示管道的读端,fd[1]表示管道的写端。
管道一般是由一个父进程创建,然后被用来在父子进程间进行通信:
在父子进程通过管道进行通信的程序中,一般在父进程中先创建一个管道,然后 fork 出一个子进程,然后在两个进程中关闭不写和不读的两端。
由于Unix中的管道默认实现是单向的,为了实现双向的,可以用两个单向的管道模拟:
2.使用无名管道实现一个简单的本地文件服务器
使用两个管道实现一个客户端-服务器程序,客户端向服务器请求文件,服务器向客户端输出请求文件的内容:
main函数:
1 /* 2 * use pipo to communicate between server and client 3 * */ 4 5 #include <stdio.h> 6 #include <unistd.h> 7 #include <sys/wait.h> 8 #include <sys/types.h> 9 #include <string.h> 10 #include <stdlib.h> 11 #include <errno.h> 12 #include <fcntl.h> 13 14 #define _err(msg) \ 15 {\ 16 fprintf(stderr, "error : %s -- %s\n", msg, strerror(errno)); \ 17 exit(-1); \ 18 } 19 20 #define MAX_SIZE 1024 21 22 int 23 main(void) 24 { 25 int fd1[2], fd2[2]; 26 pid_t pid; 27 28 pipe(fd1); 29 pipe(fd2); 30 31 if((pid = fork()) < 0) 32 _err("fork fail"); 33 if(pid > 0) 34 { 35 /*parent*/ 36 close(fd1[1]); 37 close(fd2[0]); 38 server(fd1[0], fd2[1]); 39 printf("server stoped\n"); 40 waitpid(pid, NULL, 0); 41 } 42 else 43 { 44 close(fd1[0]); 45 close(fd2[1]); 46 client(fd2[0], fd1[1]); 47 printf("client stoped\n"); 48 exit(0); 49 } 50 return 0; 51 }
client 函数:
1 void 2 client(int readfd, int writefd) 3 { 4 char buffer[MAX_SIZE]; 5 size_t size; 6 printf("client is start ...\n"); 7 printf("input the filename : "); 8 fgets(buffer, MAX_SIZE, stdin); 9 size = strlen(buffer); 10 if(buffer[size - 1] == '\n') 11 size--; 12 if(write(writefd, buffer, size) <= 0) 13 _err("write to server fail"); 14 while((size = read(readfd, buffer, MAX_SIZE)) > 0) 15 { 16 buffer[size] = '\0'; 17 printf("%s", buffer); 18 fflush(stdout); 19 } 20 close(readfd); 21 close(writefd); 22 }
server函数:
1 void 2 server(int readfd, int writefd) 3 { 4 char buffer[MAX_SIZE]; 5 int fd; 6 size_t size; 7 printf("server start ...\n"); 8 if((size = read(readfd, buffer, MAX_SIZE)) <= 0) 9 _err("read pipe fail"); 10 buffer[size] = '\0'; 11 if(access(buffer, F_OK | R_OK) == -1) 12 { 13 strcpy(buffer, "file is not exists or can not be read"); 14 if(write(writefd, buffer, strlen(buffer)) <= 0) 15 _err("test file fail"); 16 } 17 else 18 { 19 printf("file name = %s\n", buffer); 20 if((fd = open(buffer, O_RDONLY)) == -1) 21 _err("open file fail"); 22 while((size = read(fd, buffer, MAX_SIZE)) > 0) 23 { 24 if(write(writefd, buffer, size) <= 0) 25 _err("write to client fail"); 26 } 27 close(fd); 28 } 29 close(writefd); 30 close(readfd); 31 }
3. 使用标准I/O函数库提供的管道实现
标准I/O函数库提供了另一种使用管道的形式,标准函数库通过 popen 函数创建一个管道,并且启动另外一个进程,该进程可以读或写这个管道,可以在使用popen打开时指定是从管道中读还是写。
使用popen函数创建的进程,如果在调用popen的时候指定了'r',则该进程将标准输出写到管道中,如果指定了'w',则将从管道中读出的数据作为标准输入。
1 #include <stdio.h> 2 3 FILE *popen(const char *command, const char *type); 4 5 返回:成功返回文件指针,出错返回NULL 6 7 int pclose(FILE *stream); 8 9 返回:成功返回shell的终止状态,出错返回-1
其中的command是一个shell命令,该命令是使用sh程序处理的,所以可以通过PATH环境变量定位到该命令。popen函数在调用进程和指定的命令之间创建一个管道,由popen函数返回一个标准I/O 文件指针,该指针用于输入输出,具体取决于参数type:
- 如果type为r,那么调用进程读command的标准输出
- 如果type为w,那么调用进程写到command的标准输入
pclose函数关闭popen函数创建的I/O流,等待其中的命令终止,然后返回shell的终止状态。
4.使用popen实现本地文件服务器
1 /* 2 * file server implementation by popen 3 * */ 4 5 #include <stdio.h> 6 #include <stdlib.h> 7 #include <sys/stat.h> 8 #include <fcntl.h> 9 #include <unistd.h> 10 #include <errno.h> 11 #include <string.h> 12 13 #define MAX_SIZE 1024 14 #define _err(msg) \ 15 {\ 16 fprintf(stderr, "Error: %s -- %s\n", msg, strerror(errno)); \ 17 exit(-1); \ 18 } 19 20 int 21 main(void) 22 { 23 char filename[MAX_SIZE]; 24 char buffer[MAX_SIZE]; 25 char cmd[100]; 26 FILE *file; 27 int len; 28 29 printf("Input the filename > "); 30 fgets(filename, sizeof(filename), stdin); 31 if(strcmp(filename, "") == 0) 32 _err("file name is illegal"); 33 len = strlen(filename); 34 if(filename[len-1] == '\n') 35 filename[len-1] = '\0'; 36 snprintf(cmd, sizeof(cmd), "cat %s", filename); 37 if((file = popen(cmd, "r")) == NULL) 38 _err("popen fail"); 39 while(fgets(buffer, sizeof(buffer), file) != NULL) 40 printf("%s", buffer); 41 return 0; 42 }
5. 有名管道-FIFO
无名管道由于没有名字,所以只能用于具有共同祖先进程的各个进程之间,无法用于在没有亲缘关系的进程之间创建无名管道(除非将管道的描述符保存在一个文件中被另一个进程使用),这造成了无名管道使用的局限性。
FIFO(first-in-first-out)在Unix中的行为类似于无名管道,它是一个单向的数据流,不同于管道,FIFO和一个和路径名关联的,这使得FIFO可以用于不具有亲缘关系的进行之间,只要知道路径名就可以访问该FIFO,FIFO所以又称为有名管道。
FIFO使用mkfifo函数创建:
1 #include <sys/types.h> 2 #include <sys/stat.h> 3 4 int mkfifo(const char *pathname, mode_t mode); 5 6 返回:成功返回0,出错返回-1
其中pathname是一个普通的Unix路径名,它是该FIFO在文件系统中的名字。
mode参数指定了文件访问权限位,类似于open函数的mode参数,这些访问权限位定义在<sys/stat.h>头文件中。
mkfifo函数已经隐含指定了O_CREAT | O_EXCL ,所以调用mkfifo函数的时候,要么创建了一个新的FIFO,要么返回一个EEXIST错误表示创建的这个文件已经存在。
在创建一个FIFO后,必须使用读打开或者使用写打开,不能使用读写方式打开,因为FIFO是半双工的。打开一个FIFO可以使用Unix的open系统函数,也可以使用标准I/O函数库的fopen函数打开。
在对一个管道或FIFO进行write写的时候,总是将写的数据添加到尾部,对于他们的read读总是从头部开始,所以不能对一个管道或FIFO调用lseek,会返回ESPIPE错误。
6.使用FIFO改写本地文件服务器
main函数:
1 /* 2 * a file server use fifo as a IPC 3 * */ 4 5 #include <stdio.h> 6 #include <stdlib.h> 7 #include <string.h> 8 #include <unistd.h> 9 #include <sys/stat.h> 10 #include <fcntl.h> 11 #include <errno.h> 12 13 #define MAX_SIZE 1024 14 #define FIFO_FILE_1 "/tmp/fifo_1" 15 #define FIFO_FILE_2 "/tmp/fifo_2" 16 17 #define _err(msg) \ 18 {\ 19 fprintf(stderr, "Error : %s -- %s\n", msg, strerror(errno));\ 20 exit(-1); \ 21 } 22 23 int 24 main(void) 25 { 26 int sfdread, sfdwrite, cfdread, cfdwrite; 27 pid_t pid; 28 29 if(mkfifo(FIFO_FILE_1, O_CREAT | O_EXCL) < 0 && errno != EEXIST) 30 _err("make or visit a fifo fail"); 31 if(mkfifo(FIFO_FILE_2, O_CREAT | O_EXCL) < 0 && errno != EEXIST) 32 _err("make or visit a fifo fail"); 33 34 if((pid = fork()) < 0) 35 _err("fork fail"); 36 if(pid) 37 { 38 /*parent*/ 39 if((sfdread = open(FIFO_FILE_1, O_RDONLY)) < 0) 40 _err("open a fifo fail"); 41 if((sfdwrite = open(FIFO_FILE_2, O_WRONLY)) < 0) 42 _err("open a fifo file"); 43 44 server(sfdread, sfdwrite); 45 waitpid(pid, NULL, 0); 46 close(sfdwrite); 47 close(sfdread); 48 } 49 else 50 { 51 /*child*/ 52 if((cfdwrite = open(FIFO_FILE_1, O_WRONLY)) < 0) 53 _err("open a fifo file"); 54 if((cfdread = open(FIFO_FILE_2, O_RDONLY)) < 0) 55 _err("open a fifo fail"); 56 57 client(cfdread, cfdwrite); 58 close(cfdwrite); 59 close(cfdread); 60 } 61 return 0; 62 }
client 函数
1 void 2 client(int fdread, int fdwrite) 3 { 4 char filename[MAX_SIZE]; 5 char buffer[MAX_SIZE]; 6 size_t size; 7 8 printf("input the filename > "); 9 fgets(filename, sizeof(filename), stdin); 10 size = strlen(filename); 11 if(filename[size - 1] == '\n') 12 filename[size - 1] = '\0'; 13 14 if(write(fdwrite, filename, size) <= 0) 15 _err("write to server fail"); 16 17 while((size = read(fdread, buffer, sizeof(buffer))) > 0) 18 { 19 write(STDOUT_FILENO, buffer, size); 20 } 21 exit(0); 22 }
server 函数
1 void 2 server(int fdread, int fdwrite) 3 { 4 char filename[MAX_SIZE]; 5 char buffer[MAX_SIZE]; 6 int fd; 7 size_t size; 8 9 if((size = read(fdread, filename, sizeof(filename))) <= 0) 10 _err("can not get the file name"); 11 12 if(access(filename, F_OK | R_OK) == -1) 13 _err("test file fail"); 14 if((fd = open(filename, O_RDONLY)) == -1) 15 _err("open file fail"); 16 17 while((size = read(fd, buffer, sizeof(buffer))) > 0) 18 if(write(fdwrite, buffer, size) <= 0) 19 _err("fail to write to client"); 20 exit(0); 21 }
7. 管道和FIFO的额外属性
一个文件描述符可以通过两种方式设置成非阻塞:
- 调用open时可以指定O_NONBLOCK标识
- 如果一个描述符已经打开,则可以使用fcntl函数设置描述符的O_NONBLOCK标识
对于管道来说,由于管道是通过pipe函数创建的,不能使用open函数打开,所以不能使用open方式设置O_NONBLOCK标识,只能使用fcntl函数设置描述符的O_NONBLOCK标识,而对于FIFO来说,FIFO的打开可以使用open实现的,所以对于FIFO设置O_NONBLOCK标识可以使用open也可以使用fcntl实现。
打开了O_NONBLOCK标识的描述符对FIFO和管道会有影响:
当前操作 |
管道或FIFO的现有打开状态 |
返回 |
|
阻塞(默认设置) |
O_NONBLOCK设置 |
||
只读方式open FIFO |
FIFO以写方式打开 |
成功返回 |
成功返回 |
FIFO 不是以写方式打开 |
阻塞到FIFO打开来写为止 |
成功返回 |
|
只写方式open FIFO |
FIFO以读方式打开 |
成功返回 |
成功返回 |
FIFO不是以读方式打开 |
阻塞到FIFO打开来读为止 |
返回ENXIO错误 |
|
从空管道或空FIFO读 |
管道或FIFO以写方式打开 |
阻塞到管道或FIFO中有数据或者管道或FIFO被关闭 |
返回EAGAIN错误 |
管道或FIFO不是以写方式打开 |
read操作返回0(文件结束) |
read操作返回0(文件结束) |
|
向管道或FIFO写 |
管道或FIFO打开来读 |
和PIPE_BUF有关(见下文) |
和PIPE_BUF有关(见下文) |
管道或FIFO不是打开来读 |
给线程产生SIGPIPE信号 |
给线程产生SIGPIPE信号 |
对于管道和FIFO的读出和写入存在如下的一些规则:
- 如果请求读出的数据量多于管道或者FIFO中当前可用数据量,那么只返回这些可用的数据。
- 如果请求写入的数据的字节数小于或等于PIPE_BUF(在<limits.h>头文件中定义),那么write操作保证写入是原子操作,这意味着如果有两进程同时写一个管道或FIFO的时候,写入操作保证只有在一个进程写完所有请求写入的数据后,才会运行另一进行进行写入操作。但是如果进行请求写入的数据PIPE_BUF的值,那么write操作对写入的原子性没有保证。
- O_NONBLOCK标识对写操作的原子性没有影响,写操作的原子性取决于写入数据请求写入的数据是否超过PIPE_BUF。但是设置为O_NONBLOCK标识的管道或FIFO,对其写操作的返回值取决于待写的字节数和管道或FIFO中当前可用的空间的大小:
- 如果待写字节数小于等于PIPE_BUF:
- 如果该管道或FIFO中有足以存放请求数据的空间,那么所有的数据都将原子地写入。
- 如果该管道或FIFO中没有足够的空间存放请求的数据,那么不写入数据并且立即返回一个EAGAIN错误。因为设置了O_NONBLOCK标识,调用进程不希望自己被阻塞,但是内核无法在接受部分数据的时候保证write操作的原子性,所以会返回一个错误,告诉进程下一次在尝试
-
- 如果待写的数据大于PIPE_BUF:
- 如果管道或FIFO中至少有1字节空间,那么内核写入该管道或FIFO当前可以容难的数据,写入的数据量将作为写操作的返回值返回。
- 如果该管道或FIFO已满,则写操作立即返回一个EAGAIN错误
- 如果向一个没有为读而打开着的管道或FIFO写入,那么内核将产生一个SIGPIPE信号:
- 如果调用进程没有捕捉也没有忽略SIGPIPE信号,则采取的默认行为是终止该进程
- 如果调用进程忽略了SIGPIPE信号,或者捕获了该信号并从其信号处理程序中返回,那么write返回一个EPIPE错误
8.使用FIFO将本地文件服务器改写成单服务器多客户端
FIFO的优势表现在服务器可以是一个长期运行的进程(如守护进程),而且可以与其客户端可以没有亲缘关系。
对于服务器所在的进程,可以使用一个众所周知的路径名创建一个FIFO,以供所有的客户端进程使用。而对于每一个客户端进程,可以通过服务器提供的FIFO给服务器发送每个客户进程自己创建的FIFO路径名,以实现客户进程和服务进程之间的通信。
服务器实现:
1 /* 2 * multiply client connect to the server and communicate use fifo 3 * */ 4 5 #include <stdio.h> 6 #include <sys/types.h> 7 #include <unistd.h> 8 #include <sys/stat.h> 9 #include <fcntl.h> 10 #include <string.h> 11 #include <errno.h> 12 #include <stdlib.h> 13 #include <error.h> 14 15 #define FIFO_FILE "/tmp/server_fifo" 16 #define MAX_SIZE 1024 17 #define FILE_NAME 256 18 19 void _err(char *message) 20 { 21 fprintf(stderr, "error %s -- %s\n", message, strerror(errno)); 22 exit(-1); 23 } 24 25 int main(int argc, char *argv[]) 26 { 27 int readno, writeno, dummyfd, fd; 28 char *ptr, buffer[MAX_SIZE], fifoname[FILE_NAME]; 29 pid_t pid; 30 ssize_t size; 31 32 if(mkfifo(FIFO_FILE, 0666) == -1 && errno != EEXIST) 33 _err("fifo file create fail"); 34 35 if((readno = open(FIFO_FILE, O_RDONLY)) < 0) 36 _err("fifo file open fail"); 37 if((dummyfd = open(FIFO_FILE, O_WRONLY)) < 0) 38 _err("fifo file open fail"); /*never use*/ 39 40 while((size = read(readno, buffer, sizeof(buffer))) > 0) 41 { 42 if(buffer[size-1] == '\n') 43 buffer[size - 1] = 0, size--; 44 if((ptr = strchr(buffer, ' ')) == NULL) 45 _err("message's format is invalid"); 46 *ptr++ = 0; 47 snprintf(fifoname, sizeof(fifoname), "/tmp/fifo.%s", buffer); 48 if((writeno = open(fifoname, O_WRONLY)) < 0) 49 _err("open fifo fail"); 50 if((fd = open(ptr, O_RDONLY)) < 0) 51 /*tell the client the error message*/ 52 { 53 snprintf(buffer + size, sizeof(buffer) - size, ": can't open, %s\n", strerror(errno)); 54 size = strlen(buffer); 55 write(writeno, buffer, size); 56 close(writeno); 57 } 58 else 59 { 60 while((size = read(fd, buffer, MAX_SIZE)) > 0) 61 write(writeno, buffer, size); 62 close(fd); 63 close(writeno); 64 } 65 } 66 exit(0); 67 }
客户端实现:
1 /* 2 * multipy client to connect to server 3 * */ 4 5 #include <stdio.h> 6 #include <string.h> 7 #include <stdlib.h> 8 #include <sys/stat.h> 9 #include <sys/types.h> 10 #include <unistd.h> 11 #include <errno.h> 12 #include <fcntl.h> 13 14 #define MAX_SIZE 1024 15 #define FIFO_FILE "/tmp/server_fifo" 16 #define FILE_NAME 256 17 18 void _err(char *message) 19 { 20 fprintf(stderr, "error: %s -- %s\n", message, strerror(errno)); 21 exit(-1); 22 } 23 24 int main(int argc, char *argv[]) 25 { 26 int readno, writeno; 27 char buffer[FILE_NAME], fifoname[FILE_NAME], *ptr; 28 pid_t pid; 29 int fd; 30 ssize_t size; 31 32 pid = getpid(); 33 snprintf(fifoname, sizeof(fifoname), "/tmp/fifo.%d", pid); 34 if(mkfifo(fifoname, 0666) < 0) 35 _err("create fifo fail"); 36 snprintf(buffer, sizeof(buffer), "%d ", pid); 37 printf("Input the file name > "); 38 size = strlen(buffer); 39 ptr = buffer + size; 40 fgets(ptr, MAX_SIZE - size, stdin); 41 42 if((writeno = open(FIFO_FILE, O_WRONLY)) < 0) 43 _err("open fifo fail"); 44 45 size = strlen(buffer); 46 if(write(writeno, buffer, size) != size) 47 _err("write to fifo fail"); 48 if((readno = open(fifoname, O_RDONLY)) < 0) 49 _err("open fifo fail"); 50 while((size = read(readno, buffer, sizeof(buffer))) > 0) 51 write(STDOUT_FILENO, buffer, size); 52 close(readno); 53 close(writeno); 54 exit(0); 55 }
9.FIFO和NFS的关系
FIFO是一种只能在单台主机上使用的IPC形式。尽管在文件系统中有名字,它们也只能用在本地文件系统上,而不能用在通过NFS安装的文件系统上。
虽然有些系统允许在通过NFS安装的文件系统上创建FIFO,但是数据无法在这样的两个系统之间通过这些FIFO传递。这意味着FIFO只能用于处在同一台主机上的客户机和服务器之间使用,即使可以在不同的主机上通过NFS打开同一个FIFO,也不能通过该FIFO进行通信
10.字节流和消息
管道和FIFO都是使用字节流I/O模型的,这是Unix的原生I/O模型。字节流I/O模型不存在记录边界,也就是说读写操作不检查数据。例如:如果从某个FIFO中读出100个字节的进程无法判断向该FIFO进行写入这100个字节数据的进程执行了怎样的操作来写入这100个字节,可能是一次写入了100个字节,也可能是通过写入5次20字节的数据。还可能的情况可能是这100个字节的数据根本来自两个不同的进程。这样的数据没有明确的边界,称为字节流,系统不对这些数据进行解释。
有时候,如果应用程序需要对这些数据加上某种结构,或者希望使用一种数据结构作为传输数据的单位,则需要对这种类型的数据进行处理。有三种方式可以处理这些情况:
- 带内特殊终止序列:很多的Unix应用程序使用换行符来分隔每个消息。写进程会给每一个消息添加一个换行符,读进程则每次读出一行。这中方式一般要求数据中任何出现分隔符的位置都需要进行转义处理。很多的因特网程序(FTP、SMTP、HTTP、NNTP)使用一个回车符后跟一个换行符构成双字符序列来分隔记录。
- 显示长度:每个记录前冠以它的长度,当在TCP上时,Sun PRC也使用这种技巧,这种技巧的优势在于不再需要通过转义出现在数据中的分隔符,因为接收这明确知道了数据的长度。
- 每次连接一个记录:应用通过关闭与其对等端的连接来指示一个记录的结束,这要求为每一个记录都建立一个连接。
标准I/O库可以用于读写一个管道或者FIFO,既然打开一个管道的唯一方法是使用pipe,那么为了创建一个标准I/O流,可以使用标准I/O函数fdopen函数将标准I/O流和一个pipe打开的文件描述符相关联。
也可以创建一个结构化的消息,这种能力是由Posix消息队列和System V消息队列提供的。每个消息都有一个长度和一个优先级(System V中称为类型),长度和优先级通过发送发指定,消息被读出后,接收方可以从消息中获取消息的长度和优先级
类似于消息队列,可以给FIFO或管道人为的增加一些结构,实现结构化的通信。
自定义消息的结构:
1 /* get the max size of the date */ 2 #define MAXMESGDATE (PIPE_BUF - 2 * sizeof(long)) 3 4 /* get the size of the mesg_len and mesg_type */ 5 #define MESGHDRSIZE (sizeof(struct mymesg) - MAXMESGDATE) 6 7 /* message struct */ 8 struct mymesg { 9 long mesg_len; 10 long mesg_type; 11 char mesg_date[MAXMESGDATE]; 12 }; 13 14 /* error process */ 15 void 16 _err(char *message) 17 { 18 fprintf(stderr, "error:%s -- %s\n", message, strerror(errno)); 19 } 20 21 /* send message message to fifo */ 22 ssize_t 23 mesg_send(int fd, struct mymesg *mptr) 24 { 25 return write(fd, mptr, MESGHDRSIZE + mptr -> mesg_len); 26 } 27 28 /* recive message from fifo */ 29 ssize_t 30 mesg_recv(int fd, struct mymesg *mptr) 31 { 32 size_t len; 33 ssize_t size; 34 35 if((size = read(fd, mptr, MESGHDRSIZE)) <= 0) 36 _err("recive date error"); 37 if(size != MESGHDRSIZE) 38 _err("recived date's head is error"); 39 40 if((len = mptr -> mesg_len) > 0) 41 { 42 if((size = read(fd, mptr -> mesg_date, len)) != len) 43 _err("read error"); 44 } 45 return size; 46 }
11.使用自定义的结构化接口改写单服务多客户端程序
服务器程序:
1 /* 2 * server use struct to send and recive message 3 * */ 4 5 #include <stdio.h> 6 #include <string.h> 7 #include <stdlib.h> 8 #include <unistd.h> 9 #include <sys/stat.h> 10 #include <sys/types.h> 11 #include <errno.h> 12 #include <limits.h> 13 #include <fcntl.h> 14 15 #define FIFO_NAME "/tmp/server.fifo" 16 #define FILE_NAME 256 17 18 /* server */ 19 int 20 main(void) 21 { 22 struct mymesg buffer; 23 size_t size; 24 char fifo_file_name[FILE_NAME]; 25 char *ptr, fd; 26 27 int readfd, writefd, dummpy; /* read and write interface */ 28 29 if(mkfifo(FIFO_NAME, 0666) < 0 && errno != EEXIST) 30 _err("fifo file create fail"); 31 32 if((readfd = open(FIFO_NAME, O_RDONLY)) == -1) 33 _err("open fifo fail"); 34 35 /* keep the fifo always can be read */ 36 if((dummpy = open(FIFO_NAME, O_WRONLY)) == -1) 37 _err("open file fail"); 38 39 40 while((size = mesg_recv(readfd, &buffer)) > 0) 41 { 42 buffer.mesg_date[size] = 0; 43 44 /* get fifo name and request file name from buffer */ 45 if((ptr = strchr(buffer.mesg_date, ' ')) == NULL) 46 _err("message is invalid"); 47 48 *ptr++ = 0; /* sprit the fifo name and request file name*/ 49 snprintf(fifo_file_name, FILE_NAME, "/tmp/fifo.%s", buffer.mesg_date); 50 51 /* open client fifo file */ 52 if((writefd = open(fifo_file_name, O_WRONLY)) < 0) 53 _err("open client fifo fail"); 54 55 /* open request file */ 56 if((fd = open(ptr, O_RDONLY)) < 0) 57 _err("open request file fail"); 58 59 /* send file content to client */ 60 while((size = read(fd, buffer.mesg_date, MAXMESGDATE)) > 0) 61 { 62 buffer.mesg_len = size; 63 mesg_send(writefd, &buffer); 64 } 65 close(fd); 66 close(writefd); 67 } 68 close(readfd); 69 return 0; 70 }
客户端程序:
1 /* 2 * client use struct to send and recive message 3 * */ 4 5 #include <stdio.h> 6 #include <string.h> 7 #include <stdlib.h> 8 #include <unistd.h> 9 #include <sys/stat.h> 10 #include <sys/types.h> 11 #include <errno.h> 12 #include <limits.h> 13 #include <fcntl.h> 14 15 #define FIFO_NAME "/tmp/server.fifo" 16 #define FILE_NAME 256 17 18 /* client */ 19 20 int 21 main(void) 22 { 23 int readfd, writefd; 24 char fifoname[FILE_NAME]; 25 char filename[FILE_NAME]; 26 size_t len, size; 27 struct mymesg buffer; 28 pid_t pid; 29 30 printf("Input the file name > "); 31 fgets(filename, FILE_NAME, stdin); 32 len = strlen(filename); 33 filename[--len] = 0; /* remove the suffix '\n' */ 34 35 pid = getpid(); 36 37 snprintf(fifoname, FILE_NAME, "/tmp/fifo.%d", pid); 38 39 /* create client fifo file */ 40 if(mkfifo(fifoname, 0666) < 0 && errno != EEXIST) 41 _err("create client fifo fail"); 42 43 /* create message to send to server */ 44 snprintf(buffer.mesg_date, MAXMESGDATE, "%d %s", pid, filename); 45 len = strlen(buffer.mesg_date); 46 buffer.mesg_len = len; 47 48 /* open the server fifo to send message */ 49 if((writefd = open(FIFO_NAME, O_WRONLY)) < 0) 50 _err("open server fifo fail"); 51 52 /*send message to server */ 53 if((size = mesg_send(writefd, &buffer)) < 0) 54 _err("send to server fail"); 55 56 /* open client fifo to read message */ 57 if((readfd = open(fifoname, O_RDONLY)) < 0) 58 _err("open client fifo fail"); 59 60 while((size = mesg_recv(readfd, &buffer)) > 0) 61 { 62 write(STDOUT_FILENO, buffer.mesg_date, buffer.mesg_len); 63 } 64 close(readfd); 65 close(writefd); 66 return 0; 67 }
12. 管道和FIFO的限制
系统对管道和FIFO的唯一限制为:
- OPEN_MAX 一个进程在任意时刻打开的最大描述符数(Posix要求至少为16)
- PIPE_BUF 可以原子地往一个管道或FIFO写的最大数据量(Posix要求至少为512,在Linux 2.6.11以前,管道的容量是和系统的页长(system page)一样的(在i386中为4096字节,即4k),从Linux 2.6.11开始,管道的容量改为65536字节。)
posted on 2013-11-13 18:26 Now&Fight 阅读(1876) 评论(0) 编辑 收藏 举报