- 命名管道通信
在这一部分中,我们使用命名管道(FIFO)来进行进程通信,并且以此关联客户和服务器,我们用一个服务器来接受请求并处理,最后把结果返回给客户,即发送请求的一方。
为了使问题简单化,我们假设被处理的数据可以被拆分成一个个数据块,每个的长度都取决于PIPE_BUF字节。最后,我们通过在传递给服务器的原先数据中加上客户的进程标识符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
struct data_to_pass_st{
pid_t client_pid;
char some_data[BUFFER_SIZE - 1];
};
然后,我们来编写服务器程序server.c,在这一部分当中,我们创建并打开服务器管道,并且将其设置为只读模式:
#include "client.h"
#include <ctype.h>
int main()
{
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);
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{
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_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);
}
在服务器端程序当中,我们堆刚从客户那里接收到的数据进行处理,将some_data中所有字符全部转化为大写,并且将CLIENT_FIFO_NAME和接收到的client pid联系在一起。完成这些之后,我们将经过处理之后的数据发送回去,然后关闭管道。
在下面的客户端程序client.c中,我们首先检查并打开服务器端文件,并获取相应的进程ID,然后创建客户FIFO准备工作。我们用5次循环来进行数据发送读取操作,最后删除文件。
#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);
if(mkfifo(client_fifo, 0777) == -1){
fprintf(stderr, "sorry, can't make %s\n", client_fifo);
exit(EXIT_FAILURE);
}
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);
}
对于整个流程,我们先创建只读模式FIFO并阻塞以等待连接,再打开了服务器端FIFO后,它创建自己唯一的一个命名管道来读取服务器返回的数据。之后服务端进行读取操作。服务器端在收到数据后,以写方式打开管道处理并将数据返回以接触阻塞状态.
分别运行命令gcc server.c -o server和gcc client.c -o client编译两个客户端和服务器端文件得到可执行文件,然后运行如下命令:
./ server &
For I in 1 2 3 4 5
Do
Client &
Done
运行结果:
我们可以看到有5个进程被创建,且不同的客户请求交错在一起,且每个客户都获得了相应的处理数据,而且他们的交错顺序是随机的。
- 消息队列通信
消息队列是一种在两进程之间通信的常用方法,本实验我们就来实现使用消息队列来进行进程通信。Linux系统中本身就为我们提供了相关的库<sys/sg.h>,它包含了几个十分重要的消息队列函数,msgct1,msgget,msgrcv和msgsnd。下面我们来编写接收者msgreceive.c文件:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/msg.h>
struct msg_st
{
long int msg_type;
char text[BUFSIZ];
};
int main()
{
int running = 1;
int msgid = -1;
struct msg_st data;
long int msgtype = 0;
msgid = msgget((key_t)1234, 0666 | IPC_CREAT);
if(msgid == -1)
{
fprintf(stderr, "msgget failed with error: %d\n", errno);
exit(EXIT_FAILURE);
}
while(running)
{
if(msgrcv(msgid, (void*)&data, BUFSIZ, msgtype, 0) == -1)
{
fprintf(stderr, "msgrcv failed with errno: %d\n", errno);
exit(EXIT_FAILURE);
}
printf("You wrote: %s\n",data.text);
if(strncmp(data.text, "end", 3) == 0)
running = 0;
}
if(msgctl(msgid, IPC_RMID, 0) == -1)
{
fprintf(stderr, "msgctl(IPC_RMID) failed\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
在接收者程序中,我们首先建立消息队列,然后循环从消息队列中读取数据,直到读取到end位置,然后删除队列。
以下是发送者程序msgsend.c:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/msg.h>
#include <errno.h>
#define MAX_TEXT 512
struct msg_st
{
long int msg_type;
char text[MAX_TEXT];
};
int main()
{
int running = 1;
struct msg_st data;
char buffer[BUFSIZ];
int msgid = -1;
//建立消息队列
msgid = msgget((key_t)1234, 0666 | IPC_CREAT);
if(msgid == -1)
{
fprintf(stderr, "msgget failed with error: %d\n", errno);
exit(EXIT_FAILURE);
}
//向消息队列中写消息,直到写入end
while(running)
{
//输入数据
printf("Enter some text: ");
fgets(buffer, BUFSIZ, stdin);
data.msg_type = 1; //注意2
strcpy(data.text, buffer);
//向队列发送数据
if(msgsnd(msgid, (void*)&data, MAX_TEXT, 0) == -1)
{
fprintf(stderr, "msgsnd failed\n");
exit(EXIT_FAILURE);
}
//输入end结束输入
if(strncmp(buffer, "end", 3) == 0)
running = 0;
sleep(1);
}
exit(EXIT_SUCCESS);
}
发送者的原理与接收者大致相同,因此不再赘述。
纵观整个流程,我们通过msgget来创建一个消息队列,然后用msgsnd向队列中增加消息,接收者使用msgget获得队列标识符开始接受消息,直到end为止,最后删除消息队列。
分别执行命令gcc msgsend.c -o msgsen.exe和gcc msgreceive.c -o msgreceiv.exe编译两个源程序,然后先执行./ msgsend.exe启动消息队列,发送消息,输入要发送的text文本,再执行./ msgreceiv.exe进行接收。
运行结果: