《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