Linux下实现简易客户/服务器(C/S)应用程序——基于有名管道FIFO

FIFO简介

有名管道FIFO:也称命名管道,是指可以在不相关的进程之间交换数据。
在命令行中可以用以下命令来创建有名管道。

mkfifo filename

在程序中,可以使用两个不同的函数调用,函数原型如下所示:

#include <sys/types.h>
#include <sys/stat.h>

int mkfifo(const char *filename, mode_t mode);
int mknod(const char *filename, mode_t mode | S_IFIFO, (dev_t) 0);

访问FIFO文件

在实现简易的客户端/服务器应用程序之前,我们首先要先说一下如何在程序中访问FIFO文件。
一般来说我们可以用open函数打开FIFO文件,但是打开FIFO文件有一个限制。
即程序不能以O_RDWR模式打开FIFO文件进行读写操作。因为通常使用FIFO只是为了单项传递数据,所以没有必要使用O_RDWR模式。如果一个管道以读/写方式打开,进程就会从这个管道读回它自己的输出。
打开FIFO文件与打开普通文件的区别:对open_flag(open函数的第二个参数)的O_NONBLOCK选项的用法。使用这个选项不仅改变open调用的处理方式,还会改变对这次open调用返回的文件描述符进行的读写请求的处理方式。

O_RDONLY、O_WRONLY和O_NONBLOCK标志共有4中合法的组合方式。

//open调用将阻塞,除非有一个进程以写方式打开同一个FIFO,否则它不返回
open(const char *path, O_RDONLY);
//open调用FIFO文件,并且是非阻塞的,如果没有读到文件中的内容则停止
open(const char *path, O_RDONLY | O_NONBLOCK);

open(const char *path, O_WRONLY);

open(const char *path, O_WRONLY | O_NONBLOCK);

客户端/服务器(C/S)应用程序

这个程序参考自《Linux程序设计 第四版》,感兴趣的可以去看一下,加一些自己的理解在里面。
程序实现的功能为:只用一个服务器进程来接收发自于各个客户端进程的数据,对相应数据进行处理之后,把结果数据返回给发送请求的客户端。
思路:

  • 假设被处理的数据可以被拆分为一个个数据块,每个的长度都小于PIPE_BUF字节。
  • 由于服务器每次只能处理一个数据块,所以只使用一个FIFO,服务器通过它读取数据,每个客户端向它写数据,FIFO需要以阻塞模式打开。
  • 在处理后的数据返回给客户端时,需要为每个客户端建立第二个管道来接收返回的数据。
  • 通过传递给服务器的原先数据中加上客户端的进程标识符(PID),就以为返回数据的管道生成一个唯一的名字。

接下来就展示程序细节:
建立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/serv_fifo"
#define CLIENT_FIFO_NAME "/tmp/cli_%d_fifo"
// 定义发送的数据块的大小
#define BUFFER_SIZE 20
// 定义要传输的数据,client_pid表示客户端的进程标识符,some_data
struct data_to_pass_st{
	pid_t client_pid;
	char some_data[BUFFER_SIZE - 1];
};

然后建立客户端处理函数server.c,如下所示:

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

/*
 创建并打开服务器管道。
 设置为只读的阻塞模式
 服务器开始读取客户发来的数据,这些数据采用的是
 data_to_pass_st结构
*/
int main()
{
	int server_fifo_fd, client_fifo_fd;	// 接收打开server和client FIFO文件的返回值
	struct data_to_pass_st my_data;	// 定义一个传输的数据结构体
	int read_res;	// 返回read函数的结果值,为0则读取成功,-1则失败
	char client_fifo[256];	// 储存单个客户端文件信息,因为可能会有多个客户端,每个客户端的进程号不一样
	char *tmp_char_ptr;	// 指向接收客户端发来的数据,方便对数据进行处理

	mkfifo(SERVER_FIFO_NAME, 0777);		// 创建一个FIFO管道,指向作为服务器的文件
	// 打开一个服务器FIFO文件
	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{
		// 读取服务器文件中的数据(接收自客户端),并将其传给my_data
		read_res = read(server_fifo_fd, &my_data, sizeof(my_data));
		if(read_res > 0)
		{
			tmp_char_ptr = my_data.some_data;	// 便于对数据进行处理
			while(*tmp_char_ptr)
			{	//处理数据
				// 对接收到的数据全部大写
				*tmp_char_ptr = toupper(*tmp_char_ptr);
				tmp_char_ptr++;
			}
			// 刚刚是哪个客户端发的数据,现在准备回送过去
			sprintf(client_fifo, CLIENT_FIFO_NAME, my_data.client_pid);	// 将客户端文件传给client_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(EXIT_SUCCESS);
}

最后是客户端文件client.c的操作,如下所示:

#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];

	server_fifo_fd = open(SERVER_FIFO_NAME, O_WRONLY);
	if(server_fifo_fd == -1)
	{
		fprintf(stderr, "sorry, no server\n");
		exit(EXIT_FAILURE);
	}

	my_data.client_pid = getpid();	// 获取进程标识符
	sprintf(client_fifo, CLIENT_FIFO_NAME, my_data.client_pid);	// client_fifo储存了文件名
	if(mkfifo(client_fifo, 0777) == -1){
		fprintf(stderr, "sorry, can't make %s\n", client_fifo);
		exit(EXIT_FAILURE);
	}
	// 有3次循环,每次循环客户将数据发送给服务器,然后打开客户FIFO(只读,阻塞模式)并读回数据。
	for(times_to_send = 0; times_to_send<3; times_to_send++)
	{
		sprintf(my_data.some_data, "Hello from %d", my_data.client_pid);
		printf("\n%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);
	unlink(client_fifo);
	exit(EXIT_SUCCESS);
}

首先,建立单个客户端的测试结果如下所示:

root@ubuntu:/home/linuxsystemcode/server_client# ./server & 
[3] 3531
root@ubuntu:/home/linuxsystemcode/server_client# ./client &
[4] 3532
root@ubuntu:/home/linuxsystemcode/server_client#
3532 sent Hello from 3532 received: HELLO FROM 3532

3532 sent Hello from 3532 received: HELLO FROM 3532

3532 sent Hello from 3532 received: HELLO FROM 3532

然后,再测试一下多个客户端,结果如下所示:

root@ubuntu:/home/linuxsystemcode/server_client# ./server &
[2] 3546
root@ubuntu:/home/linuxsystemcode/server_client# for i in 1 2 3
> do
> ./client &
> done
[3] 3547
[4] 3548
[5] 3549
root@ubuntu:/home/linuxsystemcode/server_client#
3547 sent Hello from 3547 received: HELLO FROM 3547
3548 sent Hello from 3548 received: HELLO FROM 3548
3548 sent Hello from 3548 received: HELLO FROM 3548
3547 sent Hello from 3547 received: HELLO FROM 3547
3548 sent Hello from 3548 received: HELLO FROM 3548
3547 sent Hello from 3547 received: HELLO FROM 3547
3549 sent Hello from 3549 received: HELLO FROM 3549
3549 sent Hello from 3549 received: HELLO FROM 3549
3549 sent Hello from 3549 received: HELLO FROM 3549

结果和我们想的一样,over~ 有什么问题,欢迎交流~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值