Linux-C 编程 / 进程通信 / 实现基于SysV消息队列的文件服务器

哈喽,我是老吴。最近比较懒,虽然一直在学习,但是没什么动力写文章,为了不让这个好习惯中止,就把自己最近复习到的东西总结一下分享出来,希望大佬们不要打我。

一、简介

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

觉得文章对你有价值,不妨点个 在看和赞

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值