8.Linux网络编程-System V消息队列

一:消息队列
消息队列是进程间通信的一种手段,进程产生的数据块以链表的形式存储在消息队列中,每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型。
消息队列有以下的几个限制,每个消息有最大长度不能超过MSGMAX,每个消息队列的总的字节数不能超过MSGMNB,消息队列的总数不能超过MSGMNI。

//查看消息队列限制的值
root@epc:~# cat /proc/sys/kernel/msgmax
8192
root@epc:~# cat /proc/sys/kernel/msgmnb
16384
root@epc:~# cat /proc/sys/kernel/msgmni
32000
root@epc:~# 

二:消息队列的数据结构
常用的IPC相关linux命令:
查看ipc:ipcs
删除:ipcrm -Q //只能删除非私有ipc,私有的ipc的key为0x0
ipcrm -q

//内核为每个IPC对象维护一个数据结构
struct ipc_perm {
   key_t          __key;       /* Key supplied to xxxget(2) */
   uid_t          uid;         /* Effective UID of owner */
   gid_t          gid;         /* Effective GID of owner */
   uid_t          cuid;        /* Effective UID of creator */
   gid_t          cgid;        /* Effective GID of creator */
   unsigned short mode;        /* Permissions */
   unsigned short __seq;       /* Sequence number */
};

//linux查看IPC的命令
root@epc:~# ipcs

------ Message Queues --------	=>消息队列
key        msqid      owner      perms      used-bytes   messages    

------ Shared Memory Segments --------	=>共享内存
key        shmid      owner      perms      bytes      nattch     status      

------ Semaphore Arrays --------	=>信号量
key        semid      owner      perms      nsems 

//消息队列结构
struct msqid_ds {
   struct ipc_perm msg_perm;     /* Ownership and permissions */
   time_t     msg_stime;    /* Time of last msgsnd(2) */
   time_t     msg_rtime;    /* Time of last msgrcv(2) */
   time_t     msg_ctime;    /* Time of last change */
   unsigned long    __msg_cbytes; /* Current number of bytes inqueue (nonstandard) */
   msgqnum_t     msg_qnum;     /* Current number of messages in queue */
   msglen_t     msg_qbytes;   /* Maximum number of bytes
                                                allowed in queue */
   pid_t                  msg_lspid;      /* PID of last msgsnd(2) */
   pid_t                  msg_lrpid;      /* PID of last msgrcv(2) */
};

三:消息队列的函数

//头文件
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
//msgget
int msgget(key_t key, int msgfl_g); 
/*
@msgget:用来创建和访问一个消息队列
@int:成功返回非负整数,是该消息队列的标识码,即id,失败返回-1
当返回-1时,errno的值有以下情况:
	EACCESS(权限不允许)
	EEXIST(队列已经存在,无法创建)
	EIDRM(队列标志为删除)
	ENOENT(队列不存在)
	ENOMEM(创建队列时内存不够)
	ENOSPC(超出最大队列限制)
@key:整数或者IPC_PRIVATE(0),消息队列的名字。
	IPC_PRIVATE,表示私有的,意味消息队列只能用于亲缘进程通信。
@msgfl_g:九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的;
如果单独使用IPC_CREAT,则msgget()要么返回一个新创建的消息队列的标识符,要么返回具有相同关键字值的队列的标识符;
如果IPC_EXCL和IPC_CREAT一起使用,如果队列已经存在则返回一个失败值-1,否则创建一个新的消息队列,IPC_EXCL单独使用是没有用处的。
 */
//msgctl
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
/*
@msgget:用来查看/设置/删除消息队列
@int:成功返回0,失败返回-1
@cmd:操作类型,可取值IPC_STAT或者IPC_SET或者IPC_RMID
	IPC_STAT:获取消息队列的状态,并将结果输出到buf中
	IPC_SET:在进程有足够权限的条件下,把消息队列的当前关联值设置为msqid_ds数据结构中给出的值,通过导入buf的值进行设置
	IPC_RMID:删除消息队列。(此时buf可置为null)
@buf:输出输入消息队列状态的空间
 */
//msgsnd
int msgsnd(int msqid, const void *msgp,size_t msgsz, int msgfl_g);
/*
@msgsnd:把一条消息添加到消息队列中去返回值
@int:成功返回0,失败返回-1
@msqid:消息队列的标志,即id
@msgp:数据块内容
	数据块内容必须是如下结构:
	struct msgbuf{
		long mtype; //接收者函数通过这个长整数确定消息的类型        
	  char mtext[X]; // 这里存放具体的消息内容,X必须小于等于MSGMAX
	};
@msgsz:msgp中mtext长度,这个长度不含保存消息类型的那个long int长整型,此值必须小于MSGMAX,否则返回-1
@msgfl_g:控制位,控制当前消息队列满或到达系统上限时将要发生的事情。
   为0表示等待;msgfl_g =IPC_NOWAIT。表示队列满不等待,返回EAGAIN。
 */
//msgrcv
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgfl_g)
/*
@msgrcv:从一个消息队列中接收消息
@ssize_t :成功返回实际接收到放到接收缓冲区里去的字符个数,失败返回-1
@msqid:消息队列的标志,即id
@msgp:数据块内容输出
@msgsz:msgp指向的消息长度,不包含消息类型的长度
@msgtyp:接收的消息类型
	=0:表示返回队列里的第一条消息
	>0:返回队列第一条类型等于msgtype的消息
	<0:返回队列第一条类型小于等于msgtype绝对值的消息
@msgfl_g:控制位,控制着队列中没有相应类型的消息可工接收时将要发生的事情。
	msgfl_g=IPC_NOWAIT:队列没有可读消息不等待,返回ENOMSG错误
	msgfl_g=MSG_NOERROR:消息大小超过msgsz时被截断
	msgtype>0且msgfl_g=MSG_EXCEPT:接收类型不等于msgtype的第一条消息
 */

四:利用System V消息队列实现简单的回射客户端/服务器

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

//server
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<stdlib.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)
        	ERR_EXIT("msgrcv");
        int pid = *((int *)msg.mtext);
        printf("recv from pid=%d, length=%d, buf=%s\n", pid, nrcv, msg.mtext+4);
        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;
}

//client
#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));
    while (fgets(msg.mtext+4, MSGMAX, stdin) != NULL)
    {
    	msg.mtype = 1;
    	*((int *)msg.mtext) = pid;
        if (msgsnd(msgid, &msg, 4 + strlen(msg.mtext + 4), IPC_NOWAIT) < 0)
            ERR_EXIT("msgsnd");

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

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

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

    echo_cli(msgid);

    return 0;
}

五:实现每个客户一个队列
在这里插入图片描述

//server
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<stdlib.h>
#include<string.h>
#include<signal.h>
#include<wait.h>
#include<unistd.h>
#include<errno.h>

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

#define MSGMAX 8192
#define MSGQ_KEY 1234
#define MTYPE_GLOBAL 1

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

void Handler(int signo)
{
	pid_t pid;
	int stat;
	while ((pid = waitpid(-1, &stat, WNOHANG)) > 0);
	return;
}

/*
 * void echo_ser(int msgid);
 * @echo_ser:实现用单独的队列和每个客户进行交互,为了避免单线程处理一个客户信息等待时间过长,引入了用子进程进行处理的机制;
 *
 * */
void echo_ser(int msgid)
{
	signal(SIGCHLD, Handler);
    struct Msgbuf msg;
    memset(&msg, 0, sizeof(msg));
    int nrcv = 0;
    while (1)
    {
    	do
    	{
    	//处理子进程退出时,会发生中断,导致msgrcvc返回错误;
    		nrcv = msgrcv(msgid, &msg, MSGMAX, MTYPE_GLOBAL, 0);
    	}while(nrcv<0 && errno==EINTR);
        if (nrcv < 0)
        	ERR_EXIT("msgrcv");
        int clientMsgid = *((int *)msg.mtext);
        printf("recv from clientMsgid=%d, length=%d, buf=%s", clientMsgid, nrcv, msg.mtext+4);

        pid_t pid = fork();
        if (pid < 0)
        	ERR_EXIT("fork");
        else if (pid == 0)
        {
            msg.mtype = MTYPE_GLOBAL;
            msgsnd(clientMsgid, &msg, nrcv, IPC_NOWAIT);
            memset(&msg, 0, sizeof(msg));
            break;
        }
        memset(&msg, 0, sizeof(msg));
    }
}

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

    echo_ser(msgid);
    return 0;
}

//客户端
#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
#define MSGQ_KEY 1234
#define MTYPE_GLOBAL 1
struct Msgbuf
{
    long mtype;
    char mtext[MSGMAX];
};

void echo_cli(int msgidForWr, int msgidForRead)
{
    int nrcv;
    int pid = getpid();
    struct Msgbuf msg;
    memset(&msg, 0, sizeof(msg));
    while (fgets(msg.mtext+4, MSGMAX, stdin) != NULL)
    {
    	msg.mtype = MTYPE_GLOBAL;
    	*((int *)msg.mtext) = msgidForRead;
        if (msgsnd(msgidForWr, &msg, 4 + strlen(msg.mtext + 4), IPC_NOWAIT) < 0)
            ERR_EXIT("msgsnd");

        if ((nrcv = msgrcv(msgidForRead, &msg, MSGMAX, MTYPE_GLOBAL, 0)) < 0)
            ERR_EXIT("msgsnd");
        fputs(msg.mtext + 4, stdout);
        if (msg.mtext[4] == '\n') break;
        memset(&msg, 0, sizeof(msg));
    }
    msgctl(msgidForRead, IPC_RMID, NULL);
}

int main(int argc, char *argv[])
{
    int msgidForWr, msgidForRead;
    msgidForWr = msgget(MSGQ_KEY, IPC_CREAT);
    if (msgidForWr == -1)
        ERR_EXIT("msgget");

    msgidForRead = msgget(IPC_PRIVATE, 0666 | IPC_CREAT |IPC_EXCL);
    if (msgidForWr == -1)
        ERR_EXIT("msgget");

    echo_cli(msgidForWr, msgidForRead);

    return 0;
}

发布了32 篇原创文章 · 获赞 3 · 访问量 1402

猜你喜欢

转载自blog.csdn.net/m0_37582216/article/details/104544146