IPC之共享内存·即时通讯小程序(二)

发布时间:2020-07-19 16:29:59 作者:SherryX
来源:网络 阅读:5444

上次说到,使用消息队列可以做到简易的登录、退出功能。那么,该思考一下,聊天的用户列表和聊天记录应该存在哪儿呢?当然是服务器上,那么,就需要用到共享内存了。

共享内存

client.cpp:
#include "public.h"

char *userAddr;
USER_T *pUser;

void PrtUserList(int sig_no)
{
    //读取共享内存里的用户列表数据
    cout<<"==== online users ===="<<endl;
    for (int i = 0 ;i < MAX_USER ; i++)
    {
        if(*(userAddr + i) == 1)
        {
            cout<<(pUser + i)->uname<<endl;
        }
    }
}

int main()
{
    char acBuf[20] = "";
    int msg_id;
    LMSG_T loginMsg = {0};
    char uname[10] = "";
    int shm_id;

    cout<<"------------onlineChat-------------"<<endl;
    cout<<"username:";
    cin>>uname;

    //2.3 注册消息(放在最前面)
    signal(SIGNAL_USERS,PrtUserList);

    /*2、打开用户列表共享内存(要比写消息队列早)*/
    shm_id = shmget(SHM_USER_KEY,0,0);
    if (shm_id == -1)
    {
        perror("client shmget");
        return -1;
    }
    userAddr = (char*)shmat(shm_id,NULL,0);
    pUser = (USER_T*)(userAddr + MAX_USER);

    /*1、打开消息队列*/
    msg_id = msgget(MSG_KEY,0);
    if(msg_id == -1)
    {
        perror("client msgget");
        return -1;
    }
    //登录,写消息队列
    loginMsg.type = LOGIN_TYPE;     //设置登录的消息类型为1
    loginMsg.user.pid = getpid();
    memcpy(loginMsg.user.uname,uname,strlen(uname));
    cout<<loginMsg.user.uname<<" is logining..."<<endl;
    msgsnd(msg_id,&loginMsg,MSG_SIZE,0);

    //等待写
    while(1)
    {
        putchar('#');
        fflush(stdout);
        scanf("%s",acBuf);
        if (strcmp(acBuf,"exit") == 0)
        {
            cout<<loginMsg.user.uname<<" is exiting..."<<endl;
            break;
        }
    }

    loginMsg.type = EXIT_TYPE;      //设置退出的消息类型为2
    msgsnd(msg_id,&loginMsg,MSG_SIZE,0);

    return 0;
}

运行结果:
首先运行服务器,然后登陆Julia,再登陆Tomy,再退出Tomy。
IPC之共享内存·即时通讯小程序(二)
IPC之共享内存·即时通讯小程序(二)
IPC之共享内存·即时通讯小程序(二)

至此,可以进行聊天的功能设计了,聊天内容同样存储在共享内存。由于私聊和群聊的功能,我们需要获取在线用户列表,所以同样,客户端也需要一个容器来暂时存储用户信息。(list也可以,直接存放用户结构体)
代码如下:
public.h
#ifndef _PUBLICH
#define _PUBLICH

    #include < stdio.h>
    #include < string.h>
    #include < sys/types.h>
    #include < sys/ipc.h>
    #include < sys/msg.h>
    #include < sys/shm.h>
    #include < signal.h>
    #include < string>
    #include < map>
    #include < iostream>
    using namespace std;

    //用户信息结构体
    typedef struct user_t
    {
        pid_t pid;
        char uname[10]; //后面加上用户名不重名、密码验证
    }USER_T;

    //登录消息队列结构体
    typedef struct login_msg_t
    {
        long type;
        USER_T user;
    }LMSG_T;

    //聊天消息结构体
    typedef struct msg_t
    {
        USER_T user;
        char acMsg[100];
    }MSG_T;

    //消息队列:用户登录
    #define LOGIN_TYPE          1
    #define EXIT_TYPE           2
    #define MSG_KEY             1000
    #define MSG_SIZE            sizeof(LMSG_T)-sizeof(long)

    //共享内存:用户列表(空闲块:0-空闲,1-占用)
    #define SHM_USER_KEY        1001
    #define MAX_USER            100
    #define SHM_USER_SIZE       MAX_USER + MAX_USER * sizeof(USER_T)

    //共享内存:聊天内容
    #define SHM_MSG_KEY         1002
    #define SHM_MSG_SIZE        sizeof(MSG_T)

    //信号:更新用户列表,读消息
    #define SIGNAL_USERS        34
    #define SIGNAL_CHAT         35

    #endif

server.cpp
服务器这边只需要再开一块共享内存就可以了

/*3、创建共享内存:聊天信息*/
int shm_msg_id = shmget(SHM_MSG_KEY,0,0);
if (shm_msg_id != -1)
{
    shmctl(shm_msg_id,IPC_RMID,NULL);
}
shm_msg_id = shmget(SHM_MSG_KEY,SHM_MSG_SIZE,IPC_CREAT);
char *msgAddr = (char *)shmat(shm_msg_id,NULL,0);
memset(msgAddr,0,SHM_MSG_SIZE);

client.cpp

#include "public.h"

char *userAddr;
USER_T *pUser;

char *msgAddr;
MSG_T *pMsg;

map<int,string> userMap;    //用户列表
map<int,string>::iterator it;

void PrtUserList(int sig_no)
{
    //读取共享内存里的用户列表数据
    userMap.clear();
    cout<<"==== online users ===="<<endl;
    for (int i = 0 ;i < MAX_USER ; i++)
    {
        if(*(userAddr + i) == 1)
        {
            cout<<(pUser + i)->uname<<endl;
            userMap.insert(pair<int,string>( (pUser+i)->pid, (pUser+i)->uname ));
        }
    }
}

void GetChatMsg(int sig_no)
{
    //读取共享内存里的聊天内容
    MSG_T msg = {0};
    msg = *pMsg;
    cout<<"receive msg from "<<msg.user.uname<<" : "<<msg.acMsg<<endl;
}

int main()
{
    char acOrder[20] = "";
    int msg_id;
    LMSG_T loginMsg = {0};
    char uname[10] = "";
    int shm_id;
    char toWho[10] = "";    //聊天对象
    MSG_T msg = {0};    //聊天消息结构体
    char acMsg[100] = "";   //聊天内容

    cout<<"------------onlineChat-------------"<<endl;
    cout<<"username:";
    cin>>uname;

    //2.3 注册消息(放在最前面)
    signal(SIGNAL_USERS,PrtUserList);
    signal(SIGNAL_CHAT,GetChatMsg);

    /*2、打开用户列表共享内存(要比写消息队列早)*/
    shm_id = shmget(SHM_USER_KEY,0,0);
    if (shm_id == -1)
    {
        perror("client userlist shmget");
        return -1;
    }
    userAddr = (char*)shmat(shm_id,NULL,0);
    pUser = (USER_T*)(userAddr + MAX_USER);

    /*3、打开聊天信息共享内存*/
    int shm_msg_id = shmget(SHM_MSG_KEY,0,0);
    if (shm_msg_id == -1)
    {
        perror("client chatmsg shmget");
        return -1;
    }
    msgAddr = (char *)shmat(shm_msg_id,NULL,0);
    pMsg = (MSG_T *)msgAddr;

    /*1、打开消息队列*/
    msg_id = msgget(MSG_KEY,0);
    if(msg_id == -1)
    {
        perror("client msgget");
        return -1;
    }
    //登录,写消息队列
    loginMsg.type = LOGIN_TYPE;     //设置登录的消息类型为1
    loginMsg.user.pid = getpid();
    memcpy(loginMsg.user.uname,uname,strlen(uname));
    cout<<loginMsg.user.uname<<" is logining..."<<endl;
    msgsnd(msg_id,&loginMsg,MSG_SIZE,0);

    //等待写
    while(1)
    {
        putchar('#');
        fflush(stdout);
        scanf("%s",acOrder);
        if (strcmp(acOrder,"exit") == 0)    //退出
        {
            cout<<loginMsg.user.uname<<" is exiting..."<<endl;
            loginMsg.type = EXIT_TYPE;      //设置退出的消息类型为2
            msgsnd(msg_id,&loginMsg,MSG_SIZE,0);
            break;
        }
        else if (strcmp(acOrder,"users") == 0)  //查看在线用户列表
        {
            kill(getpid(),SIGNAL_USERS);
        }
        else if (strcmp(acOrder,"chat") == 0)   //进入聊天模式
        {
            cout<<"to who: ";
            cin>>toWho;
            cout<<"say: ";
            memset(acMsg,0,100);
            cin>>acMsg;

            // 3.1 把聊天内容写进共享内存
            memcpy(msg.acMsg,acMsg,strlen(acMsg));
            msg.user = loginMsg.user;
            memcpy(msgAddr,&msg,SHM_MSG_SIZE);

            if (strcmp(toWho,"all") == 0)   //群聊
            {
                //通知所有人去读
                for (it = userMap.begin();it != userMap.end();it++)
                {
                    if ((*it).first != getpid())
                    {
                        kill((*it).first,SIGNAL_CHAT);
                    }
                }
            }
            else    //私聊
            {
                for (it = userMap.begin();it != userMap.end();it++)
                {
                    if (strcmp((*it).second.c_str() , toWho) == 0)
                    {
                        kill((*it).first,SIGNAL_CHAT);
                        break;
                    }
                }
            }
        }
        memset(acOrder,0,sizeof(acOrder));
    }

    //解除映射
    shmdt(&userAddr);
    shmdt(&msgAddr);

    return 0;
}

运行结果:
IPC之共享内存·即时通讯小程序(二)

那么,共享内存里开辟一块空间用来存储一条聊天记录的话,考虑并发的问题,如果消息发送的太快,还来不及读取,那么消息就会被覆盖。如图:让读取消息的函数sleep一下,连续发送两条消息,结果第一条消息的内容会被覆盖。这是我们不想看到的。
IPC之共享内存·即时通讯小程序(二)
所以,又需要用到信号量的知识了,下次复习(先写着聊天试试)~

推荐阅读:
  1. 「小程序JAVA实战」小程序视图之条件判断(15)
  2. 「小程序JAVA实战」小程序视图之细说列表渲染(14)

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

ipc 共享内存

上一篇:jquery(插件)开发相关资料

下一篇:2_Oracle_Admin_PFILE和SID

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》