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

我们想只用一个服务器进程来接受请求,对它们进行处理,最后把结果数据返回给发送请求的一方:客户。允许多个客户进程都可以向服务器发送数据。为了使问题简单化,我们假设被处理的数据可以被拆分为一个个数据块,每个的长度都小于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调用处以等待客户的到来,就像它最初启动那样。

你可能感兴趣的:(fifo)