哈喽,我是老吴。最近比较懒,虽然一直在学习,但是没什么动力写文章,为了不让这个好习惯中止,就把自己最近复习到的东西总结一下分享出来,希望大佬们不要打我。
一、简介
3 种 System V IPC:
消息队列,用来在进程之间传递消息;
信号量允许多个进程同步它们的动作;
共享内存使得多个进程能够共享内存;
System V 消息队列的特点:
面向消息,即每次读写的对象都是消息,区别于管道、FIFO 以及 UNIX domain 流 socket;
消息具有类型;
具有内核持久性,只有当显式地删除或系统关闭时,消息队列对象才会被销毁;
具有自同步机制;
只能在本地使用,不能跨主机;
System V 消息队列的优缺点:
优点:
历史悠久、应用广泛、可移植性好,这也是为什么我们仍要学习此通信机制的原因;
缺点:
通过标识符引用,而不是文件描述符,不符合 unix 一切皆文件的设计;
使用键而不是文件名来标识消息队列,程序编写比较复杂;
无连接,内核不会像对待管道、FIFO 以及 socket 那样维护引用队列的 进程数,需要程序员谨慎考虑何时删除一个消息队列才是安全的;
相关API
创建一个新消息队列或取得一个既有队列的标识符:
int msgget(key_t key, int msgflg);
向消息队列写入一条消息:
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
从消息队列中读取(以及删除)一条消息并将其内容复制进 msgp 指向 的缓冲区中:
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
在标识符为 msqid 的消息队列上执行控制操作:
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
具体的参数说明,大佬们自行查看查阅 man 手册吧。
二、实现思路
服务器和各个客户端使用单独的消息队列,服务器上的队列用来接收进入的客户端请求,相应的响应则通过各个客户端队列来发送给客户端。
server 端:
1. 创建公用的服务器消息队列:
msgget(SERVER_KEY, IPC_CREAT)
2. 进入 for 循环:
阻塞等待客户端的请求:msgrcv(server_id, &req, REQ_MSG_SIZE, 0, 0);
创建子进程:pid = fork();
子进程处理请求:server_req(&req);
父进程:啥事不做
3. 实现 server_req(&req):
解析请求,获得客户端的消息队列标识符,并尝试打开请求的文件:open(req->pathname, O_RDONLY);
循环 read 文件并将数据通过客户端专用的消息队列发送给客户端:read() && msgsnd(req->client_id, &resp, nread, 0)
发送结束消息:msgsnd(req->client_id, &resp, 0, 0);
client 端:
1. 打开服务器的消息队列:
msgget(SERVER_KEY, S_IWUSR)
2. 创建客户端专用的消息队列:
msgget(IPC_PRIVATE, S_IRUSR | S_IWUSR | S_IWGRP);
3. 构建并通过服务器消息队列发送读文件请求:
msgsnd(server_id, &req, REQ_MSG_SIZE, 0)
4. 从客户端专用的消息队列先读一条消息,检查文件是否可被读:
msgrcv(client_id, &resp, RESP_MSG_SIZE, 0, 0);
5. 从客户端专用的消息队列中循环读取整个文件:
while (has_data)) && msgrcv(client_id, &resp, RESP_MSG_SIZE, 0, 0);
三、完整代码
sysv_mq_file.h:
客户端发送给服务器的请求 msg:
struct request_msg {
long mtype;
int client_id;
char pathname[PATH_MAX];
};
服务器发送给客户端的响应 msg:
struct response_msg {
long mtype;
char data[RESP_MSG_SIZE];
};
// 服务器支持发送 3 种类型的消息:
#define RESP_MT_FAILURE 1 /* File couldn't be opened */
#define RESP_MT_DATA 2 /* Message contains file data */
#define RESP_MT_END 3 /* File data complete */
sysv_mq_fileserver.c:
为了便于阅读,我删除了返回值的判断:
static int server_id;
static void server_req(const struct request_msg *req)
{
struct response_msg resp;
int fd, nread;
// open file
fd = open(req->pathname, O_RDONLY);
if (fd == -1) {
resp.mtype = RESP_MT_FAILURE;
snprintf(resp.data, sizeof(resp.data), "sever couldn't open %s", req->pathname);
msgsnd(req->client_id, &resp, strlen(resp.data)+1, 0);
exit(EXIT_FAILURE);
}
// send file data msg
resp.mtype = RESP_MT_DATA;
printf("sever sending data to cliend %d\n", req->client_id);
while((nread = read(fd, resp.data, RESP_MSG_SIZE)) > 0) {
if (msgsnd(req->client_id, &resp, nread, 0) == -1) {
break;
}
}
// send end msg
resp.mtype = RESP_MT_END;
msgsnd(req->client_id, &resp, 0, 0);
}
int main(int argc, char **argv)
{
struct request_msg req;
pid_t pid;
int msglen;
server_id = msgget(SERVER_KEY, IPC_CREAT | IPC_EXCL | \
S_IRUSR | S_IWUSR | S_IWGRP);
for(;;) {
printf("server waiting, pid=%d...\n", getpid());
msglen = msgrcv(server_id, &req, REQ_MSG_SIZE, 0, 0);
pid = fork();
if(pid == 0) {
server_req(&req);
exit(0);
}
printf("\n");
}
if (msgctl(server_id, IPC_RMID, NULL) == -1) {
oops("msgctl() / IPC_RMID", errno)
}
exit(0);
}
sysv_mq_fileclient.c:
static int client_id;
int main(int argc, char **argv)
{
int server_id;
struct request_msg req;
struct response_msg resp;
int total_bytes, msg_len;
int index;
server_id = msgget(SERVER_KEY, S_IWUSR);
client_id = msgget(IPC_PRIVATE, S_IRUSR | S_IWUSR | S_IWGRP);
// any type will do
req.mtype = 1;
req.client_id = client_id;
strncpy(req.pathname, argv[1], strlen(argv[1]));
req.pathname[strlen(argv[1])] = '\0';
// send request: filename
if (msgsnd(server_id, &req, REQ_MSG_SIZE, 0) == -1)
oops("msgsnd() / filename", 5);
// get first respone
msg_len = msgrcv(client_id, &resp, RESP_MSG_SIZE, 0, 0);
if (resp.mtype == RESP_MT_FAILURE) {
printf("%s\n", resp.data);
exit(EXIT_FAILURE);
} else if (resp.mtype == RESP_MT_DATA) {
index = 0;
while(msg_len--) {
fputc(resp.data[index++], stdout);
}
}
total_bytes = msg_len;
while (resp.mtype == RESP_MT_DATA) {
msg_len = msgrcv(client_id, &resp, RESP_MSG_SIZE, 0, 0);
} else {
index = 0;
while(msg_len--) {
fputc(resp.data[index++], stdout);
}
}
total_bytes += msg_len;
}
return 0;
}
运行效果:
$ ./sysv_mq_fileserver
$ ./sysv_mq_fileclient /etc/services >/tmp/out
$ diff /etc/services /tmp/out
diff 没有任何输出,表示两个文件是相同的,说明文件传输成功。
四、相关参考
《Linux-UNIX 系统编程手册》 / 43、45、46章节
《UNIX 环境高级编程》 / 15.7 章节
思考技术,也思考人生
要学习技术,更要学习如何生活。
你和我各有一个苹果,如果我们交换苹果的话,我们还是只有一个苹果。但当你和我各有一个想法,我们交换想法的话,我们就都有两个想法了。
对 嵌入式系统 (Linux、OpenWrt、Android) 和 开源软件 感兴趣,关注公众号:嵌入式Hacker。
觉得文章对你有价值,不妨点个 在看和赞。