登录成功后的各个业务
当客户端登录上服务器后,可以选择不同的功能比如一对一聊天,添加好友,创建群组,加入群组,群聊,注销等功能。
客户端是怎么发送每个业务的
客户端会在登录成功后的界面,显示帮助文档,比如添加好友业务输入:“addfriend:friendid”,然后输入这样的指令就可以添加好友了,服务器把每一个业务函数用unordered_map表存储到了一起,当你输入类似添加好友的代码后,用string的find方法找到’:’,然后利用string的substr方法提取业务名称,然后根据业务名称在map表里查找相应业务函数,然后把参数传进业务函数,
// 调用相应命令的事件处理回调,mainMenu对修改封闭,添加新功能不需要修改该函数
it->second(clientfd, commandbuf.substr(idx + 1, commandbuf.size() - idx)); // 调用命令处理方法
1对1聊天
函数名为chat,传入参数为客户端socket套接字clientfd和输入的命令“chat:friendid:message"中的friendid:message,然后用一个atoi函数得到int型friendid,通过substr得到message(要发送的信息),然后创建json对象封装以下信息
json js;
js["msgid"] = ONE_CHAT_MSG;
js["id"] = g_currentUser.getId();
js["name"] = g_currentUser.getName();
js["toid"] = friendid;
js["msg"] = message;
js["time"] = getCurrentTime();
string buffer = js.dump();
接下来就是服务器处理相应的信息
服务器端处理函数名为oneChat,根据传递过来的msgid找到unordered_map里对应的事件处理函数,用int toid = js[“toid”].get();得到int型friendid,然后对接下来的在线用户通信表查询加锁(同一服务器下),通过在线用户通信表里存储的TcpConnectionPtr类型的变量向对应的用户传递信息,再在数据库中查询,这里用到了redis,解决了不同服务器上的用户通信。
// 一对一聊天业务
void ChatService::oneChat(const TcpConnectionPtr &conn, json &js, Timestamp time)
{
int toid = js["toid"].get<int>();
{
lock_guard<mutex> lock(_connMutex);
auto it = _userConnMap.find(toid);
if (it != _userConnMap.end())
{
// toid在线,转发消息 服务器主动推送消息给toid用户
it->second->send(js.dump());
return;
}
}
// 查询toid是否在线
User user = _userModel.query(toid);
if (user.getState() == "online")
{
_redis.publish(toid, js.dump());
return;
}
// toid不在线,存储离线消息
_offlineMsgModel.insert(toid, js.dump());
}
添加好友功能
客户端中函数名addfreind,这个功能比较简单,以下是客户端中实现
// "addfriend" command handler
void addfriend(int clientfd, string str)
{
int friendid = atoi(str.c_str());
json js;
js["msgid"] = ADD_FRIEND_MSG;
js["id"] = g_currentUser.getId();
js["friendid"] = friendid;
string buffer = js.dump();
int len = send(clientfd, buffer.c_str(), strlen(buffer.c_str()) + 1, 0);
if (-1 == len)
{
cerr << "send addfriend msg error -> " << buffer << endl;
}
}
在服务器端的处理函数
函数名addFriend
// 添加好友业务 msgid id friendid
void ChatService::addFriend(const TcpConnectionPtr &conn, json &js, Timestamp time)
{
int userid = js["id"].get<int>();
int friendid = js["friendid"].get<int>();
// 存储好友信息
_friendModel.insert(userid, friendid);
}
_friendModel是专门对好友表操作的类,可以添加好友和查询好友
创建群组
一个群组肯定有一个群组id和群组描述
创建群组,格式creategroup:groupname:groupdesc
在客户端对应函数名为creatgroup
相应代码
// "creategroup" command handler groupname:groupdesc
void creategroup(int clientfd, string str)
{
int idx = str.find(":");
if (-1 == idx)
{
cerr << "creategroup command invalid!" << endl;
return;
}
string groupname = str.substr(0, idx);
string groupdesc = str.substr(idx + 1, str.size() - idx);
json js;
js["msgid"] = CREATE_GROUP_MSG;
js["id"] = g_currentUser.getId();
js["groupname"] = groupname;
js["groupdesc"] = groupdesc;
string buffer = js.dump();
int len = send(clientfd, buffer.c_str(), strlen(buffer.c_str()) + 1, 0);
if (-1 == len)
{
cerr << "send creategroup msg error -> " << buffer << endl;
}
}
服务器端对此业务的处理
// 创建群组业务
void ChatService::createGroup(const TcpConnectionPtr &conn, json &js, Timestamp time)
{
int userid = js["id"].get<int>();
string name = js["groupname"];
string desc = js["groupdesc"];
// 存储新创建的群组信息
Group group(-1, name, desc);
if (_groupModel.createGroup(group))
{
// 存储群组创建人信息
_groupModel.addGroup(userid, group.getId(), "creator");
}
}
_groupModel是群组表的操作类对象,里面封装的有各种操作表的方法。这几个操作类对象都是一开始就存在的业务处理类的成员,里面都是对相关表的操作方法。这里就是向allgroup表里添加了一个群组(name和desc)
加入群组业务
在客户端上的函数名是addgroup,输入的指令是addgroup:群组号,根据群组groupid添加群组,以下是客户端代码实现
// "addgroup" command handler
void addgroup(int clientfd, string str)
{
int groupid = atoi(str.c_str());
json js;
js["msgid"] = ADD_GROUP_MSG;
js["id"] = g_currentUser.getId();
js["groupid"] = groupid;
string buffer = js.dump();
int len = send(clientfd, buffer.c_str(), strlen(buffer.c_str()) + 1, 0);
if (-1 == len)
{
cerr << "send addgroup msg error -> " << buffer << endl;
}
}
在服务器端上对发送过来的添加群组信息进行处理,
// 加入群组业务
void ChatService::addGroup(const TcpConnectionPtr &conn, json &js, Timestamp time)
{
int userid = js["id"].get<int>();
int groupid = js["groupid"].get<int>();
_groupModel.addGroup(userid, groupid, "normal");
}
将接受后的信息传到群组表操作类的处理函数
// 加入群组
void GroupModel::addGroup(int userid, int groupid, string role)
{
// 1.组装sql语句
char sql[1024] = {0};
sprintf(sql, "insert into groupuser values(%d, %d, '%s')",
groupid, userid, role.c_str());
MySQL mysql;
if (mysql.connect())
{
mysql.update(sql);
}
}
群聊业务
在客户端中群聊业务函数名为groupchat,指令是groupchat:groupid:message,获取到groupid和message后将其与用户基本信息一同发送给服务器,
// "groupchat" command handler groupid:message
void groupchat(int clientfd, string str)
{
int idx = str.find(":");
if (-1 == idx)
{
cerr << "groupchat command invalid!" << endl;
return;
}
int groupid = atoi(str.substr(0, idx).c_str());
string message = str.substr(idx + 1, str.size() - idx);
json js;
js["msgid"] = GROUP_CHAT_MSG;
js["id"] = g_currentUser.getId();
js["name"] = g_currentUser.getName();
js["groupid"] = groupid;
js["msg"] = message;
js["time"] = getCurrentTime();
string buffer = js.dump();
int len = send(clientfd, buffer.c_str(), strlen(buffer.c_str()) + 1, 0);
if (-1 == len)
{
cerr << "send groupchat msg error -> " << buffer << endl;
}
}
服务器端对群聊信息的业务处理
// 群组聊天业务
void ChatService::groupChat(const TcpConnectionPtr &conn, json &js, Timestamp time)
{
int userid = js["id"].get<int>();
int groupid = js["groupid"].get<int>();
vector<int> useridVec = _groupModel.queryGroupUsers(userid, groupid);
lock_guard<mutex> lock(_connMutex);
for (int id : useridVec)
{
auto it = _userConnMap.find(id);
if (it != _userConnMap.end())
{
// 转发群消息
it->second->send(js.dump());
}
else
{
// 查询toid是否在线
User user = _userModel.query(id);
if (user.getState() == "online")
{
_redis.publish(id, js.dump());
}
else
{
// 存储离线群消息
_offlineMsgModel.insert(id, js.dump());
}
}
}
}
获取群组里的用户id
/ 根据指定的groupid查询群组用户id列表,除userid自己,主要用户群聊业务给群组其它成员群发消息
vector<int> GroupModel::queryGroupUsers(int userid, int groupid)
{
char sql[1024] = {0};
sprintf(sql, "select userid from groupuser where groupid = %d and userid != %d", groupid, userid);
vector<int> idVec;
MySQL mysql;
if (mysql.connect())
{
MYSQL_RES *res = mysql.query(sql);
if (res != nullptr)
{
MYSQL_ROW row;
while ((row = mysql_fetch_row(res)) != nullptr)
{
idVec.push_back(atoi(row[0]));
}
mysql_free_result(res);
}
}
return idVec;
}
注销业务
客户端处理的代码如下,就简单的向服务器发送了一个标志位和id,然后将bool类型的变量设为false,这样聊天就不会再等待你输入了,直接退出。
// "loginout" command handler
void loginout(int clientfd, string)
{
json js;
js["msgid"] = LOGINOUT_MSG;
js["id"] = g_currentUser.getId();
string buffer = js.dump();
int len = send(clientfd, buffer.c_str(), strlen(buffer.c_str()) + 1, 0);
if (-1 == len)
{
cerr << "send loginout msg error -> " << buffer << endl;
}
else
{
isMainMenuRunning = false;
}
}
服务器端处理注销业务,注销一个用户首先要把用户在线状态改为离线,然后把在线用户通信表里对应用户删除。
// 处理注销业务
void ChatService::loginout(const TcpConnectionPtr &conn, json &js, Timestamp time)
{
int userid = js["id"].get<int>();
{
lock_guard<mutex> lock(_connMutex);
auto it = _userConnMap.find(userid);
if (it != _userConnMap.end())
{
_userConnMap.erase(it);
}
}
// 用户注销,相当于就是下线,在redis中取消订阅通道
_redis.unsubscribe(userid);
// 更新用户的状态信息
User user(userid, "", "", "offline");
_userModel.updateState(user);
}
以上就是客户端与服务器之间的所有业务了,但其中还有好多的细节没有列出来,比如客户端异常处理,还有redis的原理啊