linux程序设计——使用FIFO的客户/服务器的应用程序(第十三章)

时间:2022-10-15 10:23:21

13.6.2    使用FIFO的客户/服务器应用程序

作为学习FIFO的最后一部分内容,现在考虑怎样通过命名管道来编写一个非常简答的客户/服务器应用程序。用一个服务器进程来接受请求,对它们进行处理,最后把结果数据返回给发送请求的一方:客户。
如果想要允许多个客户进行都可以向服务器发送数据,假设被处理的数据可以被拆分为一个个数据块,每个的长度都小于PIPE_BUF字节。
因为服务器每次只能处理一个数据块,所以只使用一个FIFO应该是合乎逻辑的,服务器通过它读取数据,每个客户向它写数据。只要将FIFO以阻塞模式打开,服务器和客户就会根据需要自动被阻塞。
将处理后的数据返回给客户稍微困难,需要为每个客户安排第二个管道来接受返回的数据。通过在传递给服务器的原先数据中加上客户的进程标识符PID,双方就可以使用它来为返回数据的管道生成一个唯一的名字。
首先编写头文件client.h,它定义了客户和服务器程序都会用到的数据。
/*************************************************************************
> File Name: client.h
> Description: client.h定义了客户和服务器程序都会用到的数据.
> Author: Liubingbing
> Created Time: 2015年07月15日 星期三 22时21分46秒
> Other: client.h
************************************************************************/

#ifndef _CLIENT_H
#define _CLIENT_H
#endif

#include <stdio.h>
#include <stdlib.h>
#include <unistd.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

struct data_to_pass_st {
pid_t client_pid;
char some_data[BUFFER_SIZE - 1];
};
编写服务器程序server.c,它创建并打开服务器管道,设置为只读的阻塞模式。接着,服务器开始读取客户发送来的数据,这些数据采用的data_to_pass_st结构
/*************************************************************************
> File Name: server.c
> Description: server.c程序创建并打开服务器管道,设置为只读的阻塞模式.
> Author: Liubingbing
> Created Time: 2015年07月15日 星期三 22时22分49秒
> Other: server.c
************************************************************************/

#include "client.h"
#include <ctype.h>

int main()
{
int server_fifo_fd, client_fifo_fd;
struct data_to_pass_st my_data;
int read_res;
char client_fifo[256];
char *tmp_char_ptr;

/* mkfifo函数创建命名管道(即特殊类型的文件SERVER_FIFO_NAME) */
mkfifo(SERVER_FIFO_NAME, 0777);
/* open函数打开SERVER_FIFO_NAME文件,设置为O_RDONLY的阻塞模式
* 如果成功,则返回文件描述符 */
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 {
/* read函数从server_fifo_)fd指向的文件读入sizeof(my_data)个字节的数据到my_data指向的内存
* 如果成功,则返回实际读入数据的字节数 */
read_res = read(server_fifo_fd, &my_data, sizeof(my_data));
if (read_res > 0) {
/* 对刚从客户那里读到的数据进行处理,把some_data中的所有字符全部转换为大写*/
tmp_char_ptr = my_data.some_data;
while (*tmp_char_ptr) {
*tmp_char_ptr = toupper(*tmp_char_ptr);
tmp_char_ptr++;
}
/* sprintf把格式化的数据写入到client_fifo指向的内存中 */
sprintf(client_fifo, CLIENT_FIFO_NAME, my_data.client_pid);
/* open函数打开文件client_fifo,以O_WRONLY只写的阻塞模式 */
client_fifo_fd = open(client_fifo, O_WRONLY);
if (client_fifo_fd != -1) {
/* write函数从my_data指向的内存写入sizeof(my_data)个字节的数据到client_fifo_fd指向的文件中
* 如果成功,返回实际写入数据的字节数 */
write(client_fifo_fd, &my_data, sizeof(my_data));
close(client_fifo_fd);
}
}
} while (read_res > 0);
close(server_fifo_fd);
/* 关闭服务器管道的文件描述符,删除FIFO文件 */
unlink(SERVER_FIFO_NAME);
exit(EXIT_SUCCESS);
}
编写客户程序client.c,这个程序的第一部分先检查服务器FIFO文件是否存在,如果存在就打开它,然后它获取自己的进程ID,该进程ID构成要发送给服务器的数据的一部分。接下来,它创建客户FIFO,为下一部分内容做好准备
/*************************************************************************
> File Name: client.c
> Description: client.c程序先检查服务器FIFO文件是否存在,如果存在就打开,然后获取自己的进程ID,该进程ID构成要发送给服务器的数据的一部分.
> Author: Liubingbing
> Created Time: 2015年07月15日 星期三 22时23分55秒
> Other: client.c
************************************************************************/

#include <stdio.h>
#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];

/* open函数打开文件SERVER_FIFO_NAME,设置为O_WRONLY的阻塞模式
* 如果成功,则返回文件描述符 */
server_fifo_fd = open(SERVER_FIFO_NAME, O_WRONLY);
if (server_fifo_fd == -1) {
fprintf(stderr, "Sorry, no server\n");
exit(EXIT_FAILURE);
}

/* 获得进程ID */
my_data.client_pid = getpid();
/* sprintf函数写入格式的数据到client_fifo指向的内存中 */
sprintf(client_fifo, CLIENT_FIFO_NAME, my_data.client_pid);
if (mkfifo(client_fifo, 0777) == -1) {
fprintf(stderr, "Sorry, can't make %s\n", client_fifo);
exit(EXIT_FAILURE);
}
for (times_to_send = 0; times_to_send < 5; times_to_send++) {
sprintf(my_data.some_data, "hello from %d", my_data.client_pid);
printf("%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("reveived: %s\n", my_data.some_data);
}
close(client_fifo_fd);
}
}
close(server_fifo_fd);
unlink(client_fifo);
exit(EXIT_SUCCESS);
}

结果如下所示:
linux程序设计——使用FIFO的客户/服务器的应用程序(第十三章)
不同的客户请求交错在一起,但每个客户都获得了正确的服务器返回给它的处理数据.要注意的是客户请求的交错顺序是随机的,服务器接收到客户请求的顺序随机器的不同而不同,即使在同一台机器上,每次运行的情况也可能发生变化.
程序解析
服务器以只读模式创建它的FIFO并阻塞,直到第一个客户以写方式打开同一个FIFO来简历连接为止.此时,服务器进程解除阻塞并执行sleep语句,这使得来自客户的数据排队等候
.在实际的应用程序中,应该把sleep与几乎删除.这里使用它只是为了演示有多个客户的请求同时到达时程序的正确操作方法.
与此同时,在客户打开了服务器FIFO后,它创建自己唯一的一个命名管道来读取服务器返回的数据.完成这些工作后,客户发送数据给服务器(如果管道满或服务器仍在休眠中就阻塞),然后阻塞在对自己的FIFO的read调用上,等待服务器的响应.
接收到来自客户的数据后,服务器处理它,然后以写方式打开客户管道并将处理后的数据返回,这将接触客户的阻塞状态.客户被接触阻塞后,它即可从自己的管道中读取服务器返回的数据.
整个处理过程不断重复,直到最后一个客户关闭服务器管道为止,这将使服务器的read调用失败(返回0),因为已经没有进程以写方式打开服务器管道了.
如果这是一个真正的服务器进程,它还需要继续等待客户的请求,就需要对它进行修改,有两种方法,如下所示:
1.对它自己的服务器管道打开一个文件描述符,这样read调用将总是阻塞而不是返回0
2.当read调用返回0时,关闭并重新打开服务器管道,使服务器进程阻塞在open调用处以等待客户的到来,就像它最初启动那样.