FIFO简介
有名管道FIFO:也称命名管道,是指可以在不相关的进程之间交换数据。
在命令行中可以用以下命令来创建有名管道。
mkfifo filename
在程序中,可以使用两个不同的函数调用,函数原型如下所示:
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *filename, mode_t mode);
int mknod(const char *filename, mode_t mode | S_IFIFO, (dev_t) 0);
访问FIFO文件
在实现简易的客户端/服务器应用程序之前,我们首先要先说一下如何在程序中访问FIFO文件。
一般来说我们可以用open函数打开FIFO文件,但是打开FIFO文件有一个限制。
即程序不能以O_RDWR模式打开FIFO文件进行读写操作。因为通常使用FIFO只是为了单项传递数据,所以没有必要使用O_RDWR模式。如果一个管道以读/写方式打开,进程就会从这个管道读回它自己的输出。
打开FIFO文件与打开普通文件的区别:对open_flag(open函数的第二个参数)的O_NONBLOCK选项的用法。使用这个选项不仅改变open调用的处理方式,还会改变对这次open调用返回的文件描述符进行的读写请求的处理方式。
O_RDONLY、O_WRONLY和O_NONBLOCK标志共有4中合法的组合方式。
//open调用将阻塞,除非有一个进程以写方式打开同一个FIFO,否则它不返回
open(const char *path, O_RDONLY);
//open调用FIFO文件,并且是非阻塞的,如果没有读到文件中的内容则停止
open(const char *path, O_RDONLY | O_NONBLOCK);
open(const char *path, O_WRONLY);
open(const char *path, O_WRONLY | O_NONBLOCK);
客户端/服务器(C/S)应用程序
这个程序参考自《Linux程序设计 第四版》,感兴趣的可以去看一下,加一些自己的理解在里面。
程序实现的功能为:只用一个服务器进程来接收发自于各个客户端进程的数据,对相应数据进行处理之后,把结果数据返回给发送请求的客户端。
思路:
- 假设被处理的数据可以被拆分为一个个数据块,每个的长度都小于PIPE_BUF字节。
- 由于服务器每次只能处理一个数据块,所以只使用一个FIFO,服务器通过它读取数据,每个客户端向它写数据,FIFO需要以阻塞模式打开。
- 在处理后的数据返回给客户端时,需要为每个客户端建立第二个管道来接收返回的数据。
- 通过传递给服务器的原先数据中加上客户端的进程标识符(PID),就以为返回数据的管道生成一个唯一的名字。
接下来就展示程序细节:
建立client.h,如下所示:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
/*
定义客户和服务器程序都会用到的文件
*/
#define SERVER_FIFO_NAME "/tmp/serv_fifo"
#define CLIENT_FIFO_NAME "/tmp/cli_%d_fifo"
// 定义发送的数据块的大小
#define BUFFER_SIZE 20
// 定义要传输的数据,client_pid表示客户端的进程标识符,some_data
struct data_to_pass_st{
pid_t client_pid;
char some_data[BUFFER_SIZE - 1];
};
然后建立客户端处理函数server.c,如下所示:
#include "client.h"
#include <ctype.h>
/*
创建并打开服务器管道。
设置为只读的阻塞模式
服务器开始读取客户发来的数据,这些数据采用的是
data_to_pass_st结构
*/
int main()
{
int server_fifo_fd, client_fifo_fd; // 接收打开server和client FIFO文件的返回值
struct data_to_pass_st my_data; // 定义一个传输的数据结构体
int read_res; // 返回read函数的结果值,为0则读取成功,-1则失败
char client_fifo[256]; // 储存单个客户端文件信息,因为可能会有多个客户端,每个客户端的进程号不一样
char *tmp_char_ptr; // 指向接收客户端发来的数据,方便对数据进行处理
mkfifo(SERVER_FIFO_NAME, 0777); // 创建一个FIFO管道,指向作为服务器的文件
// 打开一个服务器FIFO文件
server_fifo_fd = open(SERVER_FIFO_NAME, O_RDONLY);
if(server_fifo_fd == -1)
{
fprintf(stderr, "server fifo failure\n");
exit(EXIT_FAILURE);
}
sleep(10); // 等待接收客户端的数据,这里可以有多种实现方法
do{
// 读取服务器文件中的数据(接收自客户端),并将其传给my_data
read_res = read(server_fifo_fd, &my_data, sizeof(my_data));
if(read_res > 0)
{
tmp_char_ptr = my_data.some_data; // 便于对数据进行处理
while(*tmp_char_ptr)
{ //处理数据
// 对接收到的数据全部大写
*tmp_char_ptr = toupper(*tmp_char_ptr);
tmp_char_ptr++;
}
// 刚刚是哪个客户端发的数据,现在准备回送过去
sprintf(client_fifo, CLIENT_FIFO_NAME, my_data.client_pid); // 将客户端文件传给client_fifo。
// 以阻塞模式打开客户管道,并将经过处理的数据发送回去
client_fifo_fd = open(client_fifo, O_WRONLY);
if(client_fifo_fd != -1)
{// 打开对应的客户端文件成功啦
write(client_fifo_fd, &my_data, sizeof(my_data)); // 回送数据给对应的客户端文件
close(client_fifo_fd); // 关闭对应的客户端文件
}
}
} while (read_res > 0);
close(server_fifo_fd); // 所有的都传输完毕,关掉服务器文件
unlink(SERVER_FIFO_NAME); // 删除服务器文件
exit(EXIT_SUCCESS);
}
最后是客户端文件client.c的操作,如下所示:
#include "client.h"
#include <ctype.h>
int main()
{
int server_fifo_fd, client_fifo_fd;
struct data_to_pass_st my_data;
int times_to_send; // 发送的次数,每次发送,进程号都不同
char client_fifo[256];
server_fifo_fd = open(SERVER_FIFO_NAME, O_WRONLY);
if(server_fifo_fd == -1)
{
fprintf(stderr, "sorry, no server\n");
exit(EXIT_FAILURE);
}
my_data.client_pid = getpid(); // 获取进程标识符
sprintf(client_fifo, CLIENT_FIFO_NAME, my_data.client_pid); // client_fifo储存了文件名
if(mkfifo(client_fifo, 0777) == -1){
fprintf(stderr, "sorry, can't make %s\n", client_fifo);
exit(EXIT_FAILURE);
}
// 有3次循环,每次循环客户将数据发送给服务器,然后打开客户FIFO(只读,阻塞模式)并读回数据。
for(times_to_send = 0; times_to_send<3; times_to_send++)
{
sprintf(my_data.some_data, "Hello from %d", my_data.client_pid);
printf("\n%d sent %s ", my_data.client_pid, my_data.some_data);
write(server_fifo_fd, &my_data, sizeof(my_data));
// 打开客户端的文件设置只读属性,准备读服务器的数据
client_fifo_fd = open(client_fifo, O_RDONLY);
if(client_fifo_fd != -1)
{
if(read(client_fifo_fd, &my_data, sizeof(my_data)) > 0)
{ // 只有等接收完了,才不会堵塞了
printf("received: %s\n", my_data.some_data);
}
close(client_fifo_fd);
}
}
close(server_fifo_fd);
unlink(client_fifo);
exit(EXIT_SUCCESS);
}
首先,建立单个客户端的测试结果如下所示:
root@ubuntu:/home/linuxsystemcode/server_client# ./server &
[3] 3531
root@ubuntu:/home/linuxsystemcode/server_client# ./client &
[4] 3532
root@ubuntu:/home/linuxsystemcode/server_client#
3532 sent Hello from 3532 received: HELLO FROM 3532
3532 sent Hello from 3532 received: HELLO FROM 3532
3532 sent Hello from 3532 received: HELLO FROM 3532
然后,再测试一下多个客户端,结果如下所示:
root@ubuntu:/home/linuxsystemcode/server_client# ./server &
[2] 3546
root@ubuntu:/home/linuxsystemcode/server_client# for i in 1 2 3
> do
> ./client &
> done
[3] 3547
[4] 3548
[5] 3549
root@ubuntu:/home/linuxsystemcode/server_client#
3547 sent Hello from 3547 received: HELLO FROM 3547
3548 sent Hello from 3548 received: HELLO FROM 3548
3548 sent Hello from 3548 received: HELLO FROM 3548
3547 sent Hello from 3547 received: HELLO FROM 3547
3548 sent Hello from 3548 received: HELLO FROM 3548
3547 sent Hello from 3547 received: HELLO FROM 3547
3549 sent Hello from 3549 received: HELLO FROM 3549
3549 sent Hello from 3549 received: HELLO FROM 3549
3549 sent Hello from 3549 received: HELLO FROM 3549
结果和我们想的一样,over~ 有什么问题,欢迎交流~