一、msgsnd 和 msgrcv 函数

#include <sys/types.h>
  #include <sys/ipc.h>
  #include <sys/msg.h>

功能:把一条消息添加到消息队列中
原型 int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
参数
msgid: 由msgget函数返回的消息队列标识码
msgp:是一个指针,指针指向准备发送的消息结构体
msgsz:是msgp指向的消息长度,这个长度不含保存消息类型的那个long int长整型
msgflg:控制着当前消息队列满或到达系统上限时将要发生的事情
返回值:成功返回0;失败返回-1

msgflg=IPC_NOWAIT表示队列满不等待,返回EAGAIN错误。为0表示阻塞等待
消息结构在两方面受到制约。首先,它的具体数据必须小于系统规定的上限值MSGMAX;其次,它必须以一个long int长整数开始,接收者函数将利用这个长整数确定消息的类型。

消息结构参考形式如下:
struct msgbuf {
long  mtype;
char mtext[1];
};

The  mtext  field  is an array (or other
structure) whose size is specified by msgsz, a nonnegative integer
value.Messages of zero length (i.e., no mtext field) are permitted.

即mtex 这块区域可以是个数组或者结构体,大小由参数msgsz 指明。

功能:是从一个消息队列接收消息
原型 ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
参数
msgid: 由msgget函数返回的消息队列标识码
msgp:是一个指针,指针指向准备接收的消息结构体
msgsz:是msgp指向的最大消息长度,这个长度不含保存消息类型的那个long int长整型
msgtype:它可以实现接收优先级的简单形式
msgflg:控制着队列中没有相应类型的消息可供接收时将要发生的事
返回值:成功返回实际放到接收缓冲区里去的字符个数,失败返回-1

msgtype=0返回队列第一条信息
msgtype>0返回队列第一条类型等于msgtype的消息 
msgtype<0返回队列第一条类型小于等于msgtype绝对值的消息,并且是满足条件的消息类型最小的消息
msgflg=IPC_NOWAIT,队列没有可读消息不等待,返回ENOMSG错误。
msgflg=MSG_NOERROR,消息大小超过msgsz时被截断
msgtype>0且msgflg=MSG_EXCEPT,接收类型不等于msgtype的第一条消息。

二、消息队列实现回射客户/服务器

在前面的系列文章中,我们都是使用socket 套接字来实现回射客户/服务器程序,现在尝试使用消息队列来实现,主要就是利用上面介绍的两个函数msgsnd,msgrcv 。

对于服务器端来说,接收到一个消息结构体的类型如果为1,表示是客户请求,而mtex
字段的前4个字节存放着不同进程的pid ,后续字节才是真正的数据,服务器回射客户端时,将pid 作为类型,mtex
为实际数据,客户端只接收对应类型的数据,故可以区分不同客户端。

程序如下:

echoser.c

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
 
#include<stdlib.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<sys/types.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>

#define ERR_EXIT(m) \
    do { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

#define MSGMAX 8192
struct msgbuf
{
    long mtype;
    char mtext[MSGMAX];
};

void echo_ser(int msgid)
{
    struct msgbuf msg;
    memset(&msg, 0, sizeof(msg));
    int nrcv = 0;
    while (1)
    {

if ((nrcv = msgrcv(msgid, &msg, MSGMAX, 1, 0)) < 0);
        int pid = *((int *)msg.mtext);
        fputs(msg.mtext + 4, stdout);
        msg.mtype = pid;
        msgsnd(msgid, &msg, nrcv, 0);
        memset(&msg, 0, sizeof(msg));

}
}

int main(int argc, char *argv[])
{
    int msgid;
    msgid = msgget(1234, IPC_CREAT | 0666);
    if (msgid == -1)
        ERR_EXIT("msgget");

echo_ser(msgid);

return 0;
}

echocli.c

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
 
#include<stdio.h>
#include<stdlib.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<sys/types.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>

#define ERR_EXIT(m) \
    do { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

#define MSGMAX 8192

struct msgbuf
{
    long mtype;
    char mtext[MSGMAX];
};

void echo_cli(int msgid)
{
    int nrcv;
    int pid = getpid();
    struct msgbuf msg;
    memset(&msg, 0, sizeof(msg));
    msg.mtype = 1;
    *((int *)msg.mtext) = pid;
    while (fgets(msg.mtext + 4, MSGMAX, stdin) != NULL)
    {

if (msgsnd(msgid, &msg, 4 + strlen(msg.mtext + 4), IPC_NOWAIT) < 0)
            ERR_EXIT("msgsnd");

memset(msg.mtext + 4, 0, MSGMAX - 4);
        if ((nrcv = msgrcv(msgid, &msg, MSGMAX, pid, 0)) < 0)
            ERR_EXIT("msgsnd");
        fputs(msg.mtext + 4, stdout);
        memset(msg.mtext + 4, 0, MSGMAX - 4);

}
}

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

int msgid;
    msgid = msgget(1234, 0);
    if (msgid == -1)
        ERR_EXIT("msgget");

echo_cli(msgid);

return 0;
}

程序逻辑不复杂,就不多说了,编译运行服务器端,再开两个客户端,可以看到正常回射输出。

但上述程序是存在死锁的风险的,当开了多个客户端,将队列写满了,此时服务器端想要写入就会阻塞,而因为客户端一旦发送了数据就阻塞等待服务器端回射类型为pid的消息,即队列的消息不会减少,此时就会形成死锁,即使服务器端是非阻塞地写入,此时会返回EAGAIN
的错误,程序逻辑来说我们也会使其不断地尝试去写入,而不是粗暴地将其退出进程,这样还是会死锁。

对此问题可以多开几个私有的队列进行服务,如下:

即某个客户端先创建一个私有消息队列,然后将私有消息队列标识符和具体数据发到共享的队列,服务器fork 出一个子进程,此时根据私有队列标识符就可以将数据回射到这个队列,这个客户端就可以从私有队列读取到回射的数据。

参考:

《UNP》

消息队列实现回射客户/服务器和 msgsnd、msgrcv 函数的更多相关文章

  1. 利用System V消息队列实现回射客户/服务器

    一.介绍 在学习UNIX网络编程 卷1时,我们当时可以利用Socket套接字来实现回射客户/服务器程序,但是Socket编程是存在一些不足的,例如: 1. 服务器必须启动之时,客户端才能连上服务端,并 ...

  2. 用system v消息队列实现回射客户/服务器程序

    客户端程序 #include<unistd.h> #include<sys/types.h> #include<sys/socket.h> #include< ...

  3. 第二十二篇:基于UDP的一对回射客户/服务器程序

    前言 之前曾经学习过一对回射客户/服务器程序的例子,不过那个是基于TCP协议的.本文将讲解另一对回射客户/服务器程序,该程序基于UDP协议. 由于使用的协议不同,因此编写出的程序也有本质上的区别,应将 ...

  4. 基于UDP的一对回射客户/服务器程序

    前言 之前曾经学习过一对回射客户/服务器程序的例子,不过那个是基于TCP协议的.本文将讲解另一对回射客户/服务器程序,该程序基于UDP协议.由于使用的协议不同,因此编写出的程序也有本质上的区别,应将它 ...

  5. 第十篇:基于TCP的一对回射客户/服务器程序及其运行过程分析( 上 )

    前言 本文将讲解一对经典的客户/服务器回射程序,感受网络编程的大致框架( 该程序稍作改装即可演变成各种提供其他服务的程序 ):同时,还将对其运行过程加以分析,观察程序背后协议的执行细节,学习调试网络程 ...

  6. 最简单的回射客户/服务器程序、time_wait 状态

    下面通过最简单的客户端/服务器程序的实例来学习socket API. echoser.c 程序的功能是从客户端读取字符然后直接回射回去.  C++ Code  1 2 3 4 5 6 7 8 9 10 ...

  7. 第十一篇:基于TCP的一对回射客户/服务器程序及其运行过程分析( 下 )

    执行分析 1. 打开服务器进程: 2. 执行netstat -a命令观察当前的连接状态: 第1条连接记录说明:绑定了本地主机的任意IP,端口为9877,目前处于监听状态. 3. 打开客户进程: 4. ...

  8. 基于TCP的一对回射客户/服务器程序及其运行过程分析( 下 )

    执行分析 1. 打开服务器进程: 2. 执行netstat -a命令观察当前的连接状态: 第1条连接记录说明:绑定了本地主机的任意IP,端口为9877,目前处于监听状态. 3. 打开客户进程: 4. ...

  9. TCP回射客户服务器模型(01 socket bind listen accept connect)

    socket函数(安装电话机)头文件:#include<sys/socket.h> int socket(int family,  int type, int protocol); //返 ...

随机推荐

  1. 倒计时实现方案总结 Timer Handler

    利用Timer实现倒计时 @BindView(R.id.send) Button send;//发送验证码 private int time = 60;//倒计时 private Timer time ...

  2. nodejs自动热加载文件的做法

    https://stackoverflow.com/questions/1972242/how-to-auto-reload-files-in-node-js -------------------- ...

  3. Cognos审核模块的导入与设置

    Cognos审核模块:就是指针对Cognos在运行过程中的对象被执行和访问的日志记录做了一个对象包,在该包里面我们可以从报表里面看到一些日志记录.以方便我们对Cognos的执行记录进行查看,下面我来说 ...

  4. JAVA 读取计算机中相关信息

    java读取 计算机 cup号 读取版本号 显卡 .. . . ........ .. . . . package com.swt.common.util; import java.io.Buffer ...

  5. CAD批量合并文件

    要求:将整饰完成504幅单独的宗地图合并成一张总图,合并后,去掉其他要素,只保留毕合的权属线. 解决: 1.合并dwg文件,除了手工粘贴复制外,最先想到的是插入块,即用Insert命令插入,测试结果可 ...

  6. 【转载】如何在Oracle中复制表结构和表数据

    1. 复制表结构及其数据: create table table_name_new as select * from table_name_old 2. 只复制表结构: create table ta ...

  7. STL - 容器 - Array

    Array是C++ 11给STL新增加的容器 ArrayTest.cpp #include <array> #include <algorithm> #include < ...

  8. ZH奶酪:VirtualBox虚拟机与主机ping不通

    6.进入虚拟机的Ubuntu(以下简称VBUbuntu),在VBUbuntu中用ifconfig查看ip地址,在Windows7中用ipconfig查看ip地址. 在VBUbuntu中ping Win ...

  9. JSP实现数据传递(web基础学习笔记三)

    get和post的区别: JSP内置对象: 1)out内置对象:out内置对象是在JSP开发过程中使用得最为频繁的对象,然而其使用起来也是最简单的.out对象用于向客户端浏览器输出数         ...

  10. java面试第十六天

    国际化:让世界上每个人都能看懂. Locale类(java.util包下):包括所有国家.地区.语言 存在很多的静态属性,来表示国家.语言 三种构造方法: Locale(String language ...