C++服务开发入门指南
  • 序言
  • 前言
  • 一、一个简单的服务
    • 1 什么是服务
    • 2 服务可以用来做什么
    • 3 简单服务框架
  • 二、网络通信服务框架
    • 1 网络服务的基本概念
    • 2 增加监听端口
    • 3 处理客户端会话
    • 小结
  • 三、添加基础模块
    • 1 日志模块
    • 2 定时器
    • 3 事件机制
    • 4 线程池
    • 5 线程安全
    • 小结
  • 四、一个聊天服务
    • 1 需求描述及分析
    • 2 概要设计
    • 3 创建服务项目
    • 4 ClientUser实现
    • 5 RoomMgr实现
    • 6 ChatRoom实现
    • 7 RoomIDMgr实现
    • 小结
  • 五、测试、迭代及重构
    • 1 测试
    • 2 迭代
    • 3 重构
    • 4 版本号
  • 六、架构设计
    • 1 单点服务
    • 2 分布式服务
  • 七、部署及发布
    • 1 部署环境
    • 2 编译环境
    • 3 部署服务
    • 4 发布服务
  • 八、线上问题处理
    • 1 线上问题
    • 2 问题处理
  • 九、程序员的职业规划
    • 职业规划
Powered by GitBook
On this page
  • 维护房间状态
  • 管理聊天用户
  • 房间消息发送
  • 房间删除逻辑
  1. 四、一个聊天服务

6 ChatRoom实现

ChatRoom是聊天室最核心的业务单元,核心逻辑聊天就是在聊天室内完成。

聊天室的主要职责是维护房间自身状态,管理聊天用户,以及转发聊天消息。

维护房间状态

房间本身有一些属性信息,包括房间ID、创建者ID,这些信息用来进行身份识别和查询。

还有房间创建时间、房间有效生存时间(过期后需要销毁),房间人数上限,房间当前状态等。这些信息主要用于控制聊天室生命周期。

enum ROOM_STATUS
{
    RS_NORMAL = 1,
    RS_DESTROY,
};

public:
    int m_id;
    int64_t m_nCreaterID;
    
private:
    time_t m_timeCreate;
    int m_nLiftTime;
    int m_nMaxPlayerCount;
    ROOM_STATUS m_eStatus;

房间ID得范围室100000-999999,所以使用int表示即可。用户id使用64位。

房间状态暂时只设置了两个,一个是正常状态,一个是即将销毁得状态。当房间处于销毁状态时,不再接收新的用户加入房间。

房间的初始状态在初始化时设置

const int TIME_LIFE_ROOM = 30 * 60 * 1000 * 1000; //30分钟
const int MAX_PLAYER_IN_ROOM_DEFAULT = 10;

const int SEND_MSG_USER_NOT_FIND = 2;

ChatRoom::ChatRoom()
{
    
}

void ChatRoom::Init(int nRoomID, int64_t nCreaterID)
{
    m_nCreaterID =  nCreaterID;
    m_id = nRoomID;
    m_timeCreate = GetTimeStamp();
    m_nLiftTime = TIME_LIFE_ROOM;
    m_nMaxPlayerCount = MAX_PLAYER_IN_ROOM_DEFAULT;
    m_eStatus = RS_NORMAL;
}

管理聊天用户

聊天室要维护一个用户列表,用户进入聊天室,离开聊天室,用户列表都会发生变化。

我们使用unordered_map维护用户列表,key为用户id,value为用户user指针。并且使用互斥锁保护列表在多线程下得访问安全。

private:    
    std::unordered_map<int64_t, ClientUser*>m_users;
    Mutex m_lock;

管理用户的几个业务场景如下

加入房间

int ChatRoom::JoinRoom(int64_t id, ClientUser* user)
{
    AutoLock lk(m_lock);
    if (m_users.size() >= m_nMaxPlayerCount)
    {
        return JOIN_ROOM_MAX_USER;
    }

    if (m_eStatus == RS_DESTROY)
    {
        return JOIN_ROOM_DESTORY;
    }
    
    m_users[id] = user;

    return JOIN_ROOM_SUCC;
}

加入房间的判断,先确定房间是否达到人数上限,再判断是否是销毁状态,如果都没问题的话,将用户加入到用户map中。

在RoomMgr里,加入房间的请求会先检测是否已经在某个房间,因此这里并没有进行重复加入的判断。

离开房间

int ChatRoom::LeaveRoom(int64_t sesid)
{
    LogInfo("LeaveRoom %d : %lld",m_id, sesid);
    AutoLock lk(m_lock);
    m_users.erase(sesid);
    return 0;
}

离开房间直接移除关系即可,注意对map进行了保护。

查询是否在房间

bool ChatRoom::HasUser(int64_t userid)
{
    AutoLock lk(m_lock);
    if (m_users.count(userid) > 0)
    {
        return true;
    }
    
    return false;
}

检测map中是否存在用户id即可。

房间消息发送

服务收到用户发送聊天室消息的请求,会通过RoomMgr转到对应的聊天室,聊天室负责将消息发送到聊天室内每个人那里。

int ChatRoom::SendRoomMsg(int64_t sesID, std::string& msg)
{
    AutoLock lk(m_lock);
    CHYBuffer buf;
    if (m_users.count(sesID) == 0)
    {
        LogInfo("SendRoomMsg can not find user %lld %d %s", sesID, m_id, msg.c_str());
        return SEND_MSG_USER_NOT_FIND;
    }

    ClientUser* pUser = m_users[sesID];
    buf.SetHeaderLen(HYHEADERSIZE);
    buf.AppendFormatString("{\"sender\":\"%s\", \"msg\":\"%s\"}", pUser->m_strNickName.c_str(), msg.c_str());
    
    PHYHEADER pHeader = (PHYHEADER)buf.GetBufPtr();
    pHeader->dwLength = buf.GetDataLen();
    pHeader->wOrigine = ORIGINE_CHATSVR;
    pHeader->wType = MSG_TYPE_BROADCAST_MSG | HY_ACK;
    
    for (auto itr : m_users)
    {
        LogInfo("Room %d Send Room msg to %lld, %s", m_id, itr.first, msg.c_str());
        itr.second->SendMsg(buf.GetBufPtr(), buf.GetBufLen());
    }
    
    return 0;
}

首先,判断发送消息的人,是否室聊天室内的用户,不是的话不处理。

组装消息,采用json格式,将发送者昵称赋值给sender,消息内容赋值给msg,HYHEADER类型的消息拼装之前介绍过,这里不再重复。

最后,遍历聊天室的用户列表,调用每个user的SendMsg方法,将消息广播到聊天室内每个人的客户端。

这是最普通的消息发送,我们通过修改消息参数,可以做到给聊天室内的某个人或某些人发送消息,而对其他人屏蔽,当然这得看具体的业务需求。

房间删除逻辑

房间内部,在定时Check的时候,检测是否达到删除的标准。

当房间有人时,房间会一直保持,只有房间过期后,房间里没人了(无论是从来没有人进入,还是进入的人都离开了),会触发删除逻辑。

聊天室检测到符合删除的逻辑时,需要通知房间管理类,此处使用了发送事件的方式PostEvent。

当RoomMgr收到聊天室的删除通知时,会再调用聊天室的DestroyRoom方法,执行房间清理逻辑。

void ChatRoom::Check()
{
    AutoLock lk(m_lock);
    if (m_users.size() > 0)
    {
        // 房间有人,不用管
        return;
    }

    if (GetTimeStamp() - m_timeCreate > m_nLiftTime)
    {
        // 禁止进人,通知上层可以销毁房间
        if (m_eStatus != RS_DESTROY)
        {
            m_eStatus = RS_DESTROY;
            DestroyRoomEvent event;
            event.nType = EVENT_TYPE_DESTORY_ROOM;
            event.nDataLen = sizeof(DestroyRoomEvent) - sizeof(CHYEvent);
            event.nRoomID = m_id;
            EventMgr::Instance()->PostEvent(&event);
        }
    }
}

int ChatRoom::DestoryRoom()
{
    AutoLock lk(m_lock);
    m_users.clear();
    return 0;
}

在上一节中,我们实现RoomMgr时,在构造函数中监听了销毁房间的事件,并注册了对应的处理函数。两处逻辑结合在一起,形成了完整的闭环。

RoomMgr::RoomMgr()
{
    EventMgr::Instance()->RegisterEvent(EVENT_TYPE_DESTORY_ROOM, this, [&](CHYEvent* event){
		OnDestroyRoomEvent(event);
	});
}

到这里ChatRoom的实现就完成了。

当然,细心的朋友会发现,还少一个获取房间内用户列表的一个消息请求和处理,不过,数据都有,消息通道也完成了,实现还有什么难度吗?这就交给聪明的你加上吧。

最后,附上完整的ChatRoom头文件

#pragma once
#include <unordered_map>
#include <string>
#include "ClientUser.h"
#include <mutex>

typedef std::mutex Mutex;
typedef std::lock_guard<Mutex> AutoLock;

enum ROOM_STATUS
{
    RS_NORMAL = 1,
    RS_DESTROY,
};

class ChatRoom
{
public:
    ChatRoom();

    void Init(int nRoomID, int64_t nCreaterID);

public:
    int JoinRoom(int64_t id, ClientUser* user);

    int LeaveRoom(int64_t id);

    int DestoryRoom();

    void Check();

    int SendRoomMsg(int64_t sesID, std::string& msg);

    bool HasUser(int64_t userid);

public:
    int m_id;
    int64_t m_nCreaterID;

private:    
    std::unordered_map<int64_t, ClientUser*>m_users;
    Mutex m_lock;
    
private:
    time_t m_timeCreate;
    int m_nLiftTime;
    int m_nMaxPlayerCount;
    ROOM_STATUS m_eStatus;
};
Previous5 RoomMgr实现Next7 RoomIDMgr实现

Last updated 2 years ago