Linux 命名管道 客户端服务器通信

一、程序说明

本程序需要使用Linux FIFO命名管道,实现客户端、服务端通信的效果
lesson11_flow

二、文件概述

本工程包含如下文件:

文件名 功能
server.c 服务端的源文件
client.c 客户端的源文件
fifo.c 功能接口函数定义
fifo.h 全局变量和功能函数声明
Makefile 工程编译规则

三、实现的功能

  1. 客户端与服务器通过命名管道进行通信
  2. 客户端循环发送数据,直到发送结束符退出
  3. 服务端不断接收来自客户端的消息,并发送回复信息
  4. 服务端通过捕获SIGINT信号,实现进程的退出
  5. 服务端理论上可以连接无数个客户端
  6. 使用make工具编译程序

四、代码流程简介

对于服务端而言

  1. 首先使用make工具编译生成客户端client和服务端server
  2. 运行服务端,服务端首先注册SIGINT的信号处理函数,用于实现服务端退出功能
  3. 服务端创建公有命名管道,并以只读方式打开管道文件
  4. 读取来自客户端的结构体数据信息
  5. 如果客户端的数据被标识为IS_NEW_CLIENT,说明该客户端为初次通信,服务端会为此客户端创建相应的私有命名管道,该命名管道以次客户端的PID为标识
  6. 接下来服务端继续判断客户端的消息,如果消息是CLIENT_QUIT,表示客户端已退出,此时服务端需要关闭该条私有管道链接。
  7. 如果客户端的数据为正常的消息,服务端会从私有管道回复客户端

对于客户端而言

  1. 运行客户端程序,客户端首先会将自己的标识信息PID通过共有管道发送给服务端
  2. 服务端会为新的客户端创建相应的私有管道
  3. 接下来客户端只需读取相应的私有管道中的数据即可

五、关键问题及解决方式

问题一、如何判断服务器接收到的数据来自于哪个客户端

  1. 这实际上就是要实现对客户端的标识,一种方式是在客户端传输的数据前后,加上报头报尾将数据封装成成块的信息,类似于TCP协议的层层封装;第二种方式是客户端每次传输的数据都做成结构体的形式,结构体中包含用于表示客户端标识的成员变量,服务端读取该成员变量的值,即可判断出是哪个客户端。
  2. 第一种方式应该需要对数据进行字符串合并拆分等操作,可能比较麻烦,所以本程序选择了使用第二种方式。定义一个结构体类型,其中的一个结构体成员为客户端的PID标识,另一个成员则是客户端实际要发送的数据。
  3. 在每个客户端启动后,都会首先将自己的PID标识连同IS_NEW_CLIENT信息发送给服务器,由服务器生成私有管道链路,相当于客户端去给自己做了通信链路的注册。由于私有管道文件名的生成规则已经在客户端和服务器之间制定了协议,因此服务端不必记住所有已注册的客户端,只需定向发送即可。

问题二、如何实现客户端和服务器的退出

  1. 对于每个客户端来说,向服务端发送CLIENT_QUIT标识的数据之后,客户端就直接结束进程。
  2. 对于服务器来说,当收到来自客户端的CLIENT_QUIT数据之后,就将相应客户端的私有管道断开,删除私有管道文件。
  3. 服务端启动后不能自行结束,需要按下CTRL+C产生SIGINT信号。服务端捕获到SIGINT信号后,将会断开共有管道,删除共有管道文件。

六、程序代码

您可以去本项目的GitHub仓库查看源代码

server.c

/********************************************************************
*   File Name: server.c
*   Description: \
*   1. Bind the SIGINT signal.
*   2. Create a public FIFO and open it with read-only mode.
*   3. Read the client's data, determine whether the client is new or not.
*   4. If the client is new, create a private pipe based on this client's PID.
*   5. Writes some message to client with the private FIFO.
*   6. Reads data from client with the public FIFO.
*   7. If "quit" is read from the client,\
*      close the private FIFO and end the session.
*   Author: Zhang Houjin
*   Date: 2020/04/06
*********************************************************************/

#include "fifo.h"

int main(){

    /* Bind the SIGINT signal */
    if(signal(SIGINT, &sigcatch) == SIG_ERR){
        printf("Couldn't register signal handler\n");
        exit(1);
    }

    Create_FIFO(PUBLIC_FIFO);

    while(1){

        if((PublicFd = open(PUBLIC_FIFO, O_RDONLY)) < 0){
            printf("Fail to open PUBLIC_FIFO\n");
            exit(1);
        }
        //printf("PUBLIC_FIFO has been opened\n");

        /* Read the strcut data of the public FIFO */
        if(read(PublicFd, &Client_to_Server, sizeof(struct FIFO_Data)) > 0){
            
            printf("Client Pid is : %d\n", Client_to_Server.client_pid);
            printf("Client Message is : %s", Client_to_Server.message);
            Private_FIFO_Name = Get_Private_FIFO_Name(Client_to_Server.client_pid);
            
            /* Create the private FIFO for a new client */
            if(strcmp(Client_to_Server.message, IS_NEW_CLIENT) == 0){
                printf("This is a new client!\n");
                Create_FIFO(Private_FIFO_Name);
            }
            
            /* If the client exits, cut off communication */
            if(strcmp(Client_to_Server.message, CLIENT_QUIT) == 0){
                unlink(Private_FIFO_Name);
                printf("Closed Client_%d Private FIFO\n\n", Client_to_Server.client_pid);
            }
            else{
                /* Server send a reply message to the client */
                if((PrivateFd = open(Private_FIFO_Name, O_WRONLY)) > 0){
                    Server_to_Client.client_pid = Client_to_Server.client_pid;
                    sprintf(Server_to_Client.message, "Hello,Client_%d!\nI Received your message: ",\
                     Client_to_Server.client_pid);
                    strcat(Server_to_Client.message, Client_to_Server.message);
                    if(write(PrivateFd, &Server_to_Client, sizeof(struct FIFO_Data))){
                        printf("Write message to Client Success!\n\n");
                        close(PrivateFd);
                    }
                }
            }
            
        }
        else{
            printf("Read Date error!\n");
            exit(1);
        }
    }
    
    return 0;
}

client.c

/********************************************************************
*   File Name: client.c
*   Description: \
*   This program passes the client's pid to the server, \
*   writes data to the server with the public FIFO, \
*   and reads data from server with the private FIFO.
*   Author: Zhang Houjin
*   Date: 2020/04/06
*********************************************************************/

#include "fifo.h"

int main(){

    /* Initial handshake message */
    Client_to_Server.client_pid = getpid();
    strcpy(Client_to_Server.message, IS_NEW_CLIENT);

    /* Get private FIFO name */
    Private_FIFO_Name = Get_Private_FIFO_Name(getpid());
    printf("Client PID is: %d\nClient Message is: %s\n", \
    Client_to_Server.client_pid, Client_to_Server.message);

    /* Client handshake with server, make a communication link */
    Send_and_Recive_Message();

    /* Keep communication */
    while(1){
        printf("Please input message: ");
        fgets(Client_to_Server.message, 60, stdin);
        Client_to_Server.client_pid = getpid();
        Send_and_Recive_Message();
    }
}

fifo.c

/********************************************************************
*   File Name: fifo.c
*   Description: This program contains API interface for FIFO function.
*   Author: Zhang Houjin
*   Date: 2020/04/06
*********************************************************************/

#include "fifo.h"

/********************************************************************
*   Function Name: void Create_FIFO(char *FIFO_Name)
*   Description: Create a FIFO file based on the string "FIFO_Name".
*   Called By: server.c[main]
*   Input: char *FIFO_Name -> Use to represent FIFO file name
*   Author: Zhang Houjin
*   Date: 2020/04/05
*********************************************************************/
void Create_FIFO(char *FIFO_Name){
    int TempFd;
    if((TempFd = open(FIFO_Name, O_RDONLY)) == -1){
        umask(0);
        mknod(FIFO_Name, S_IFIFO|0666, 0);
        printf("%s has been bulid\n", FIFO_Name);
    }
    else{
        close(TempFd);
    }
}

/********************************************************************
*   Function Name: char* Get_Private_FIFO_Name(int Client_PID)
*   Description: Generate FIFO file name based on Client PID.
*   Called By: server.c[main] client.c[main]
*   Input: int Client_PID -> Used to identify the client
*   Author: Zhang Houjin
*   Date: 2020/04/05
*********************************************************************/
char* Get_Private_FIFO_Name(int Client_PID){
    char TempBuffer[6];
    strcpy(Private_Name, PRIVATE_FIFO);
    sprintf(TempBuffer, "%d", Client_PID);
    //printf("TempBuffer is : %s\n", TempBuffer);
    strcat(Private_Name, TempBuffer);
    return Private_Name;
}

/********************************************************************
*   Function Name: void sigcatch(int num)
*   Description: Register SIGINT signal processing function.
*   Called By: server.c[main]
*   Input: int num -> Signal value
*   Author: Zhang Houjin
*   Date: 2020/04/05
*********************************************************************/
void sigcatch(int num){
    printf("\nServer is exiting...\n");
    unlink(PUBLIC_FIFO);
    printf("Removed %s\nSee you again 😉\n\n", PUBLIC_FIFO);
    exit(0);
}

/********************************************************************
*   Function Name: void Send_and_Recive_Message(void)
*   Description: Receive and send data between client and server.
*   Called By: client.c[main]
*   Author: Zhang Houjin
*   Date: 2020/04/05
*********************************************************************/
void Send_and_Recive_Message(void){
    if((PublicFd = open(PUBLIC_FIFO, O_WRONLY)) > 0){
        if(write(PublicFd, &Client_to_Server, sizeof(struct FIFO_Data)) > 0){
            //printf("Success to write client_%d message to server!\n", getpid());
            close(PublicFd);
        }
        else{
            printf("Fail to write client data\n");
        }
        usleep(200000);

        /* Client exits communication */
        if(strcmp(Client_to_Server.message, CLIENT_QUIT) == 0){
            printf("Client_%d exit\n", Client_to_Server.client_pid);
            printf("Removed %s\n", Private_FIFO_Name);
            exit(0);
        }

        /* Read server struct data */
        if((PrivateFd = open(Private_FIFO_Name, O_RDONLY)) > 0){
            if(read(PrivateFd, &Server_to_Client, sizeof(struct FIFO_Data)) > 0){
                printf("Recive from Server: 👇\n%s\n", Server_to_Client.message);
                close(PrivateFd);
            }
        }
    }
    else{
        printf("Fail to open PUBLIC_FIFO\n");
        exit(1);
    }
}

fifo.h

/********************************************************************
*   File Name: fifo.h
*   Description: Contains variable definitions and declarations
*   Others: Header file used by server.c client.c fifo.c
*   Author: Zhang Houjin
*   Date: 2020/04/06
*********************************************************************/

#ifndef __MYFIFO__
#define __MYFIFO__

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>

#define PUBLIC_FIFO "Server_FIFO"
#define PRIVATE_FIFO "Client_FIFO_"
#define IS_NEW_CLIENT "handshake\n"
#define CLIENT_QUIT "quit\n"

struct FIFO_Data{
    int client_pid;
    char message[100];
};

void Create_FIFO(char *FIFO_Name);
char* Get_Private_FIFO_Name(int Client_PID);
void Send_and_Recive_Message(void);
void sigcatch(int signum);
char Private_Name[20];
int PublicFd, PrivateFd;
char* Private_FIFO_Name;
struct FIFO_Data Client_to_Server, Server_to_Client;

#endif  // __MYFIFO__

Makfile

#*****************************************************************
#   File Name: Makefile
#   Description: Compile to generate executable file.
#   Instructions: \
#	 $ make			-> Generate server and client, and delete the object files.
#	 $ make clean	-> Clear server and client.
#	Others: \
#	 $^		-> Collection of all target-dependent.
#	 -rm	-> Ignore the error message during the execution of "rm".
#	 @		-> Do not echo commands
#   Author: Zhang Houjin
#   Date: 2020/04/06
#*****************************************************************

client_objects = client.o fifo.o
server_objects = server.o fifo.o

.PHONY: all clean
all: client server clean_o

client: $(client_objects)
	@gcc -o client $^

server: $(server_objects)
	@gcc -o server $^

clean_o:
	rm ./*.o

clean:
	-rm client server

七、代码构建方法

// In the project root directory
make            /* Build client and server */
make clean      /* Clear object files and executable files */

八、测试过程演示

九、开源协议

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章