目錄
一、程序說明
本程序需要使用Linux FIFO命名管道,實現客戶端、服務端通信的效果
二、文件概述
本工程包含如下文件:
文件名 | 功能 |
---|---|
server.c | 服務端的源文件 |
client.c | 客戶端的源文件 |
fifo.c | 功能接口函數定義 |
fifo.h | 全局變量和功能函數聲明 |
Makefile | 工程編譯規則 |
三、實現的功能
- 客戶端與服務器通過命名管道進行通信
- 客戶端循環發送數據,直到發送結束符退出
- 服務端不斷接收來自客戶端的消息,併發送回覆信息
- 服務端通過捕獲SIGINT信號,實現進程的退出
- 服務端理論上可以連接無數個客戶端
- 使用make工具編譯程序
四、代碼流程簡介
對於服務端而言
- 首先使用make工具編譯生成客戶端client和服務端server
- 運行服務端,服務端首先註冊SIGINT的信號處理函數,用於實現服務端退出功能
- 服務端創建公有命名管道,並以只讀方式打開管道文件
- 讀取來自客戶端的結構體數據信息
- 如果客戶端的數據被標識爲IS_NEW_CLIENT,說明該客戶端爲初次通信,服務端會爲此客戶端創建相應的私有命名管道,該命名管道以次客戶端的PID爲標識
- 接下來服務端繼續判斷客戶端的消息,如果消息是CLIENT_QUIT,表示客戶端已退出,此時服務端需要關閉該條私有管道鏈接。
- 如果客戶端的數據爲正常的消息,服務端會從私有管道回覆客戶端
對於客戶端而言
- 運行客戶端程序,客戶端首先會將自己的標識信息PID通過共有管道發送給服務端
- 服務端會爲新的客戶端創建相應的私有管道
- 接下來客戶端只需讀取相應的私有管道中的數據即可
五、關鍵問題及解決方式
問題一、如何判斷服務器接收到的數據來自於哪個客戶端
- 這實際上就是要實現對客戶端的標識,一種方式是在客戶端傳輸的數據前後,加上報頭報尾將數據封裝成成塊的信息,類似於TCP協議的層層封裝;第二種方式是客戶端每次傳輸的數據都做成結構體的形式,結構體中包含用於表示客戶端標識的成員變量,服務端讀取該成員變量的值,即可判斷出是哪個客戶端。
- 第一種方式應該需要對數據進行字符串合併拆分等操作,可能比較麻煩,所以本程序選擇了使用第二種方式。定義一個結構體類型,其中的一個結構體成員爲客戶端的PID標識,另一個成員則是客戶端實際要發送的數據。
- 在每個客戶端啓動後,都會首先將自己的PID標識連同IS_NEW_CLIENT信息發送給服務器,由服務器生成私有管道鏈路,相當於客戶端去給自己做了通信鏈路的註冊。由於私有管道文件名的生成規則已經在客戶端和服務器之間制定了協議,因此服務端不必記住所有已註冊的客戶端,只需定向發送即可。
問題二、如何實現客戶端和服務器的退出
- 對於每個客戶端來說,向服務端發送CLIENT_QUIT標識的數據之後,客戶端就直接結束進程。
- 對於服務器來說,當收到來自客戶端的CLIENT_QUIT數據之後,就將相應客戶端的私有管道斷開,刪除私有管道文件。
- 服務端啓動後不能自行結束,需要按下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 */
八、測試過程演示
- 點擊鏈接觀看本項目發佈在B站的測試視頻
BiliBili - 命名管道_客戶端服務器模型_測試視頻
九、開源協議
- 本項目遵循 GPL 開源許可協議
- GitHub 倉庫地址 https://github.com/ZHJ0125/Embedded_Linux/tree/master/Homework/lesson11
- Gitee 倉庫地址 https://gitee.com/zhj0125/Embedded_Linux/tree/master/Homework/lesson11