读书笔记:第6章 System V消息队列 (6)

《UNIX网络编程:卷2》P118-P120:图6-20、6-21、6-22、6-23

往服务器的所有客户请求使用一个队列,给每个客户使用一个队列接收去往各个客户的服务器应答。

服务器的队列有一个众所周知的键,但是每个客户以IPC_PRIVATE键创建各自的队列。由客户把自己的私用队列标识符传递给服务器,服务器把自己的应答发送到由客户指出的队列中。

--------------------------------------------

#ifndef SVMSG_H__
#define SVMSG_H__
/*
 * svmsg.h
 * P112 图6-9 使用消息队列的客户-服务器程序的头文件
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/msg.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <limits.h>

#define MSG_R	0400
#define MSG_W	0200
#define SVMSG_MODE	(MSG_R | MSG_W | MSG_R>>3 | MSG_R>>6)

#define MQ_KEY1	1234L
#define MQ_KEY2	2345L

/* P52 mesg.h */
#define MAXMESGDATA (PIPE_BUF - 2 * sizeof(long))
#define MESGHDRSIZE (sizeof(struct mymesg) - MAXMESGDATA)

struct mymesg {
	long	mesg_len;				// 消息长度
	long	mesg_type;				// 消息类型
	char	mesg_data[MAXMESGDATA];
};

void client(int readfd, int writefd);
void server(int readfd, int writefd);

ssize_t mesg_send(int fd, struct mymesg *mptr);
ssize_t mesg_recv(int fd, struct mymesg *mptr);

#endif

--------------------------------------------

/*
 * client_main.c
 * P118 图6-20 客户程序main函数
 */
#include "svmsg.h"

int main(int argc, char *argv[])
{
	int		readid, writeid;

	// 打开服务器消息队列
	if ((writeid = msgget(MQ_KEY1, 0)) < 0) {
		fprintf(stderr, "msgget error: %s\n", strerror(errno));
		exit(1);
	}

	// 创建客户私用消息队列
	if ((readid = msgget(IPC_PRIVATE, SVMSG_MODE | IPC_CREAT)) < 0) {
		fprintf(stderr, "msgget error: %s\n", strerror(errno));
		exit(1);
	}

	client(readid, writeid);

	// 删除可以私用消息队列
	msgctl(readid, IPC_RMID, NULL);

	exit(0);
}

/*
 * client.c
 * P118 图6-21 client函数
 */
void client(int readid, int writeid)
{
	size_t		len;
	ssize_t		n;
	char		*ptr;
	struct mymesg	mesg;

	// 将客户私用消息队列ID填入消息中
	snprintf(mesg.mesg_data, MAXMESGDATA, "%d ", readid);
	len = strlen(mesg.mesg_data);

	ptr = mesg.mesg_data + len;

	// 从标准输入读取路径名
	fgets(ptr, MAXMESGDATA - len, stdin);
	len = strlen(mesg.mesg_data);
	if (mesg.mesg_data[len-1] == '\n')
		len --;							// 删除换行符
	mesg.mesg_len = len;				// 消息长度
	mesg.mesg_type = 1;					// 从客户到服务器的消息类型为1

	if(mesg_send(writeid, &mesg) < 0) {
		fprintf(stderr, "mesg_send error: %s\n", strerror(errno));
		exit(1);
	}

	while ((n = mesg_recv(readid, &mesg)) > 0)		// 从消息队列取出消息
		write(STDOUT_FILENO, mesg.mesg_data, n);
}

/*
 * mesg_send.c
 * P113 图6-12 用于消息队列的mesg_send函数
 */
ssize_t mesg_send(int id, struct mymesg *mptr)
{
	return(msgsnd(id, &(mptr->mesg_type), mptr->mesg_len, 0));
}

/*
 * mesg_recv.c
 * P113 图6-13 用于消息队列的mesg_recv函数
 */
ssize_t mesg_recv(int id, struct mymesg *mptr)
{
	ssize_t n;

	n = msgrcv(id, &(mptr->mesg_type), MAXMESGDATA, mptr->mesg_type, 0); 
	mptr->mesg_len = n;

	return n;
}

--------------------------------------------

/*
 * server_main.c
 * P115 图6-15 服务器程序main函数
 */
#include "svmsg.h"
#include <signal.h>

int main(int argc, char *argv[])
{
	int		readid;

	// 创建一个消息队列
	if ((readid = msgget(MQ_KEY1, SVMSG_MODE | IPC_CREAT)) < 0) {
		fprintf(stderr, "msgget error\n");
		exit(1);
	}

	server(readid, readid);

	exit(0);
}

/*
 * sigchldwaitpid.c
 * 图6-22 调用waitpid的SIGCHL信号处理程序
 */

void sig_chld(int signo)
{
	pid_t	pid;
	int		stat;

	// 获取子进程的终止状态,防止产生僵尸进程
	while ((pid = waitpid(-1, &stat, WNOHANG)) > 0)
		;
	return;
}

/*
 * server.c
 * 图6-16 server函数
 */
void server(int readid, int writeid)
{
	FILE		*fp;
	char		*ptr;
	ssize_t		n;
	struct mymesg	mesg;
	pid_t		childid;

	signal(SIGCHLD, sig_chld);						// 建立信号处理程序

	for ( ; ; ) {
		// 从众所周知的消息队列中读取路径名
		mesg.mesg_type = 1;
		if ((n = mesg_recv(readid, &mesg)) == 0) {
			fprintf(stderr, "pathname missing\n");
			continue;
		}
		mesg.mesg_data[n] = '\0';		// 添加空字符

		// 查找空格
		if ((ptr = strchr(mesg.mesg_data, ' ')) == NULL) {
			fprintf(stderr, "bogus reques: %s\n", mesg.mesg_data);
			continue;
		}

		*ptr++ = 0;								// 使其指向路径名
		writeid = atoi(mesg.mesg_data);			// 客户私有消息队列ID

		// 创建一个新进程
		if ((childid = fork()) < 0) {
			fprintf(stderr, "fork error: %s\n", strerror(errno));
			exit(1);
		} else if (childid == 0) {				
			// 子进程
			if ((fp = fopen(ptr, "r")) == NULL) {	// 打开文件
				// 打开文件失败
				// 构建出错消息
				snprintf(mesg.mesg_data + n, sizeof(mesg.mesg_data) - n,
						": can't open, %s\n", strerror(errno));
				mesg.mesg_len = strlen(ptr);
				memmove(mesg.mesg_data, ptr, mesg.mesg_len);		// 移动数据
				mesg_send(writeid, &mesg);
			} else {
				// 打开文件成功,读出该文件并发送给客户
				while (fgets(mesg.mesg_data, MAXMESGDATA, fp) != NULL) {
					mesg.mesg_len = strlen(mesg.mesg_data);
					mesg_send(writeid, &mesg);
				}
				fclose(fp);					// 关闭打开的文件
			}
			mesg.mesg_len = 0;
			mesg_send(writeid, &mesg);		// 发送长度为0的消息表示已到达文件结尾
		
			exit(0);
		}
		// 父进程
	}
}

/*
 * mesg_send.c
 * 图6-12 用于消息队列的mesg_send函数
 */
ssize_t mesg_send(int id, struct mymesg *mptr)
{
	    return(msgsnd(id, &(mptr->mesg_type), mptr->mesg_len, 0));
}

/*
 * mesg_recv.c
 * 图6-24 用于消息队列的mesg_recv函数
 */
ssize_t mesg_recv(int id, struct mymesg *mptr)
{
	ssize_t n;

	do {
		// 当msgrcv阻塞时,可能被中断
		n = msgrcv(id, &(mptr->mesg_type), MAXMESGDATA, mptr->mesg_type, 0);
		mptr->mesg_len = n;
	} while (n == -1 && errno == EINTR);

	if (n == -1)
		fprintf(stderr, "mesg_recv error\n");

	return n;
}

--------------------------------------------

运行程序:

在一个窗口启动服务器程序:

$ ./main_server

在另一个窗口启动客户程序:

$ ./main_client 
Makefile
main_client:
	gcc main_client.c -o main_client -Wall
main_server:
	gcc main_server.c -o main_server -Wall
all:
	make main_client main_server
clean:
	rm main_client main_server

你可能感兴趣的:(读书笔记,《UNIX网络编程》)