|
FIFO常用在服务器和客户机之间发送数据。这时,几台客户机和服务器之间需要有一个共知 FIFO ( 所有连接到其上的客户机都知道其路径名 )。由于有多个客户机,为了保证原子性,客户机的每一个请求都要少于 PIPE_BUF 个字节。如果客户机的请求超出 PIPE_FIFO 个字节,那么来自多个客户机的请求可能会发生交叉。
PIPE_FIFO 定义在:
/usr/include/linux/limits.h:#define PIPE_BUF 4096 /* # bytes in atomic write to a pipe */ ![]()
这种模型带来的问题是,如何把来自服务器的响应发送到发出请求的客户机? 使用单一的 FIFO ,客户机就不知道要何时读取 FIFO。也没有办法保证客户机读取的响应就是它自己所希望的读取响应,因为此响应可能是服务器给另外一台客户机准备的。
下图的模型提供了解决这个问题的方法:
![]()
上面模型所用的通讯方法是,把客户机的唯一识别符( 如客户机的进程ID )连同每个请求一起发送到服务器。当服务器第一次从一个新的客户机那里收到一个请求时,它使用基于客户机传送过来的唯一标识符为客户机创建一个特定的新的 FIFO。
此方法的一个缺点是,服务器没法检测客户机是否已经终止。这就可能导致文件系统被特定于客户机的 FIFO 弄得混乱( 如果客户机很多,那么生成的特定FIFO也会很多,假设大量的客户机都莫名其妙的终止,那么这些残留的 FIFO 也会相当多 )。虽然解决该问题的方法是,一旦服务器打开了 FIFO,就解开 ( unlink ) 特定于此客户机的 FIFO。这也意味着服务器必须捕捉或忽略 SIGPIPE 信号;也有可能发生的情况是,客户机发送一个请求到服务器,却在服务器做出响应之前就终止了,这将使得 FIFO 有写入程序 ( 服务器进程写 ),而没有读取程序。
下面的程序使用 FIFO 进行客户机与服务器通信。通信的协议是:
客户机发送下面一个字符串到服务器:
PID command [number]
字符串的第一个标记是客户机的进程 ID;
第二个标记是客户机希望发送的指令;
第三个标记是一个由 sqr 指令使用的可选参数。
程序中简单的服务器实现了 3 条指令:
open 此指令是客户机发送到服务器的第一个指令。它使服务器为写入创建和大开特定于此客户机的 FIFO 。
quit 此指令应该是客户机发送给服务器的最后一个指令,它使服务器关闭 FIFO 自己这一端。
sqrt 这条指令使服务器计算客户机传递过来的数字的平方根,服务器然后将结果写到特定于客户机的 FIFO 的客户机那一端。
服务器端程序:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/param.h>
#include <errno.h>
#include <string.h>
#include <math.h>
#include <signal.h>
#include "npipe.h"
#define WELL_KNOWN_FIFO "/tmp/ssp_fifo"
#define LINE_LEN 256
#define MAXPID 512
static void rm_fifo (void);
int main (void)
{
int n;
int fd;
int *fd_pid;
char buf [LINE_LEN];
char cmd [LINE_LEN];
char path [MAXPATHLEN];
pid_t pid;
double num;
double res;
if (unlink (WELL_KNOWN_FIFO) == -1) {
if (errno != ENOENT)
perror("unlink failed"); /*没有权限删除*/
}
/*创建一个共知的 FIFO*/
if (mkfifo (WELL_KNOWN_FIFO, S_IFIFO|0777) == -1) {
perror("mkfifo failed");
exit(1);
}
/*注册退出处理程序.当服务器终止时,退出处理程序确保共知FIFO不会搞乱文件系统,因为有名管道是以文件形式存储在文件系统中而不是和无名管道那样在内存中*/
if (atexit (rm_fifo) == -1) {
perror("atexit failed");
exit(1);
}
/*读共知FIFO以查客户机送来的消息,若客户机没有写FIFO或以写方式大开FIFO,服务器在此阻塞*/
if ((fd = open (WELL_KNOWN_FIFO, O_RDONLY)) == -1) {
perror("open");
exit(1);
}
if ((fd_pid = calloc (MAXPID, sizeof (int))) == NULL) {
perror("calloc failed");
exit(1);
}
/*忽略 SIGPIPE 信号以防止客户端提交了请求但在我们有机会应答之前服务器被终止*/
if (sigset (SIGPIPE, SIG_IGN) == SIG_ERR) {
perror("sigset");
exit(1);
}
/*从共知FIFO读取一行,遇到NULL时终止.使用 sscanf() 解析指令并提取出进程ID,命令字符串,可选数字*/
while ((n = read (fd, buf, LINE_LEN)) > 0) {
buf [n] = '\0';
sscanf (buf, "%d %s %lf", &pid, cmd, &num);
if (strcmp (cmd, "open") == 0) {
if (fd_pid [pid] == 0) {
snprintf (path, MAXPATHLEN, "%s.%ld", WELL_KNOWN_FIFO, (long)pid);
if (unlink (path) == -1) { /*如果存在则先删除*/
if (errno != ENOENT) {
printf("unlink failed\n");
continue;
}
}
if (mkfifo (path, S_IFIFO|0777) == -1) { /*建立特定于客户机的FIFO*/
printf("mkfifo failed\n");
continue;
}
if ((fd_pid [pid] = open (path, O_WRONLY)) == -1) { /*服务器只写客户机特定FIFO*/
printf("open failed\n");
fd_pid [pid] = 0;
continue;
}
}
}
if (strcmp (cmd, "sqrt") == 0) { /*客户端送来 sqrt 求平方根请求*/
res = sqrt (num);
snprintf (buf, LINE_LEN, "%lf\n", res);
n = strlen (buf);
if (writen (fd_pid [pid], buf, n) == -1) {
printf ("writen failed\n");
close (fd_pid [pid]);
fd_pid [pid] = 0;
}
}
if (strcmp (cmd, "quit") == 0) { /*客户机退出,使服务器关闭FIFO自己这一端*/
close (fd_pid [pid]);
fd_pid [pid] = 0;
}
}
return (0);
}
static void rm_fifo (void)
{
unlink (WELL_KNOWN_FIFO);
}
客户端程序:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/param.h>
#include <errno.h>
#include <string.h>
#include "npipe.h"
#include <stdlib.h>
#define WELL_KNOWN_FIFO "/tmp/ssp_fifo"
#define LINE_LEN 256
/*客户机发送格式字符串到服务器*/
/*简单协议: PID command [number]*/
int main (void)
{
pid_t pid;
int wk_fd;
int my_fd;
int n;
char buf [LINE_LEN];
char path [MAXPATHLEN];
/*只写方式打开有名管道*/
if ((wk_fd = open(WELL_KNOWN_FIFO, O_WRONLY)) == -1) {
perror("open FIFO");
exit(1);
}
pid = getpid();
snprintf(buf, LINE_LEN, "%d open \n", pid);
n = strlen (buf);
if (writen (wk_fd, buf, n) == -1) { /*写 "pid open \n" 到FIFO,请求建立连接 */
perror("write failed");
exit(1);
}
/*path中: /tmp/ssp_fifo.客户机pid*/
snprintf(path, MAXPATHLEN, "%s.%d", WELL_KNOWN_FIFO, pid);
while ((my_fd = open (path, O_RDONLY)) == -1) { /*特定的客户机FIFO不存在则连续读取(等待服务器创建)*/
if (errno != ENOENT) {
printf("Can't open my fifo");
exit(1); /*存在而不能打开则错误退出*/
}
}
if (unlink (path) == -1) { /*服务器已经建立客户机FIFO确认连接,客户端将其删除*/
perror("unlink"); /*!!!--文件在程序退出前并没有真正删除--!!!*/
exit(1);
}
/*提示输入要求平方根的数字*/
printf ("Enter a number: ");
fflush (stdout);
while (fgets (path, LINE_LEN, stdin) != NULL) {
snprintf (buf, LINE_LEN, "%d sqrt %s", pid, path); /* pid sqrt 数字*/
n = strlen (buf);
if (writen (wk_fd, buf, n) == -1) { /*写到共知 FIFO*/
perror("writen");
exit(1);
}
if ((n = read (my_fd, buf, LINE_LEN)) == -1) { /*读特定客户端FIFO*/
printf("Can't read my FIFO");
exit(1);
}
if (n == 0) {
printf("Server closed my pipe");
exit(1);
}
buf [n] = '\0'; /*输出到标准输出*/
if (fputs (buf, stdout) == EOF) {
printf("fputs failed");
exit(1);
}
printf ("Enter a number: ");
fflush (stdout);
}
if (ferror (stdin)) {
printf("fgets from standard input failed");
exit(1);
}
snprintf (buf, LINE_LEN, "%d close\n", pid);
n = strlen (buf);
if (writen (wk_fd, buf, n) == -1) {
printf("writen failed");
exit(1);
}
printf("\n");
close (wk_fd);
close (my_fd);
return (0);
} |
|