我们想只用一个服务器进程来接受请求,对它们进行处理,最后把结果数据返回给发送请求的一方:客户。允许多个客户进程都可以向服务器发送数据。为了使问题简单化,我们假设被处理的数据可以被拆分为一个个数据块,每个的长度都小于PIPE_BUF字节。当然我们可以用很多方法实现这个系统,但在这里我们只考虑一种方法,即可以体现如何使用命名管道的方法。
因为服务器每次只能处理一个数据块,所以只使用一个FIFO应该是合理的,服务器通过它读取数据,每个客户向它写数据。只要将FIFO以阻塞模式打开,服务器和客户就会根据需要自动被阻塞。
将处理后的数据返回给客户稍微有些困难。我们需要为每个客户安排第二个管道来接收返回的数据。通过在传递给服务器的原先数据中加上客户的进程标识符,双方就可以使用它来为返回数据的管道生成一个唯一的名字。
实验:一个客户/服务器应用程序
1 首先,需要一个头文件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/server_fifo" #define CLIENT_FIFO_NAME "/tmp/client_%d_fifo" #define BUFFER_SIZE 20 struct data_to_pass_st{ pid_t client_pid; char some_data[BUFFER_SIZE-1]; };
注意结构体结束要加逗号,否则会出现如下错误
In file included from client.c:2:
/usr/include/ctype.h:62: error: two or more data types in declaration specifiers
2 现在是服务器程序server.c。在这一部分,我们创建并打开服务器管道。它被设置为只读的阻塞模式。在稍作休息之后,服务器开始读取客户发送来的数据,这些数据采用的是data_to_pass_st结构。
//server.c #include "client.h" #include <ctype.h> int main(int argc, char **argv) { 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,0777); //创建FIFO server_fifo_fd=open(SERVER_FIFO_NAME,O_RDONLY); if(server_fifo_fd==-1) //如果打开FIFO错误 { perror("Server fifo failed"); exit(1); } sleep(10); do { read_res=read(server_fifo_fd,&my_data,sizeof(my_data)); if(read_res>0) { tmp_char_ptr=my_data.some_data; /*从客户那里读到的数据 进行处理,把some_data中的所有字符转换为大写,并 且把CLIENT_FIFO_NAME和接收到的client_pid结合在一起*/ while(*tmp_char_ptr) { *tmp_char_ptr=toupper(*tmp_char_ptr); tmp_char_ptr++; } sprintf(client_fifo,CLIENT_FIFO_NAME,my_data.client_pid); /* * 我们以只写的阻塞模式打开客户管道,把经过处理的数据发送回去。最好关闭服务器 * 管道的文件描述符,删除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(0); }
//client.c #include "client.h" #include <ctype.h> int main(int argc, char **argv) { 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);//检查服务器FIFO文件存在否 if(server_fifo_fd==-1) { perror("no server"); exit(1); } my_data.client_pid=getpid();//获取自己的进程ID sprintf(client_fifo,CLIENT_FIFO_NAME,my_data.client_pid); if(mkfifo(client_fifo,0777)==-1)//创建客户FIFO { perror("mkfifo failed"); exit(1); } 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("received:%s\n",my_data.some_data); } close(client_fifo_fd); } } close(server_fifo_fd); //关闭服务器FIFO unlink(client_fifo); //删除客户FIFO文件 exit(0); }
测试:
[root@localhost ser_cli_app]# ./server & [4] 11754 [root@localhost ser_cli_app]# for((i=0;i<3;i++);do (./client &);done bash: syntax error near `;d' [root@localhost ser_cli_app]# for((i=0;i<3;i++));do (./client &);done //过10秒输出 [root@localhost ser_cli_app]# 11770 sent Hello from 11770received:HELLO FROM 11770 11768 sent Hello from 11768received:HELLO FROM 11768 11772 sent Hello from 11772received:HELLO FROM 11772 11772 sent Hello from 11772received:HELLO FROM 11772 11768 sent Hello from 11768received:HELLO FROM 11768 11770 sent Hello from 11770received:HELLO FROM 11770 11772 sent Hello from 11772received:HELLO FROM 11772 11768 sent Hello from 11768received:HELLO FROM 11768 11770 sent Hello from 11770received:HELLO FROM 11770 11772 sent Hello from 11772received:HELLO FROM 11772 11768 sent Hello from 11768received:HELLO FROM 11768 11770 sent Hello from 11770received:HELLO FROM 11770 11768 sent Hello from 11768received:HELLO FROM 11768 11772 sent Hello from 11772received:HELLO FROM 11772 11770 sent Hello from 11770received:HELLO FROM 11770 [4]+ Done ./server
上述命令启动了一个服务器和3个客户进程。不同的客户请求交错在一起,但每个客户都获得了正确的服务器返回给它的数据。
实验解析:
服务器以只读模式创建它的FIFO并阻塞,直到第一个客户以写方式打开同一个FIFO来建立连接为止。此时,服务器进程解除阻塞并执行sleep语句,这使得来自客户的数据排队等候。在实际的应用程序中,应该把sleep语句删除。这里使用它只是为了演示当有多个客户的请求同时到达时,程序的正确操作方法。
与此同时,在客户打开了服务器FIFO后,它创建自己唯一的一个命名管道来读取服务器返回的数据。完成这些工作后,客户发送数据给服务器(如果管道满或服务器仍在休眠中就阻塞),然后阻塞在对自己的FIFO的read调用上,等待服务器的响应。
接收到来自客户的数据后,服务器处理它,然后以写方式打开客户管道并将处理后的数据返回,这将解除客户的阻塞状态。客户被解除阻塞后,它即可从自己的管道中读取服务器返回的数据。
整个处理过程不断重复,直到最好一个客户关闭服务器管道为止,这使服务器的read调用失败(返回0),因为已经没有进程以写方式打开服务器管道了。如果这是一个真正的服务器进程,它还需要继续等待客户的请求,我们就需要对它进行修改,有两种方法:
1 对它自己的服务器管道打开一个文件描述符,这样read调用将总是阻塞而不是返回0。
2 当read调用返回0时,关闭并重新打开服务器管道,使服务器进程阻塞在open调用处以等待客户的到来,就像它最初启动那样。