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;
};