好友认证
服务器响应
服务器接受客户端发送过来的好友认证请求
void LogicSystem::AuthFriendApply(std::shared_ptr<CSession> session, const short& msg_id, const string& msg_data) {Json::Reader reader;Json::Value root;reader.parse(msg_data, root);auto uid = root["fromuid"].asInt();auto touid = root["touid"].asInt();auto back_name = root["back"].asString();std::cout << "from " << uid << " auth friend to " << touid << std::endl;Json::Value rtvalue;rtvalue["error"] = ErrorCodes::Success;auto user_info = std::make_shared<UserInfo>();std::string base_key = USER_BASE_INFO + std::to_string(touid);bool b_info = GetBaseInfo(base_key, touid, user_info);if (b_info) {rtvalue["name"] = user_info->name;rtvalue["nick"] = user_info->nick;rtvalue["icon"] = user_info->icon;rtvalue["sex"] = user_info->sex;rtvalue["uid"] = touid;}else {rtvalue["error"] = ErrorCodes::UidInvalid;}Defer defer([this, &rtvalue, session]() {std::string return_str = rtvalue.toStyledString();session->Send(return_str, ID_AUTH_FRIEND_RSP);});//先更新数据库MysqlMgr::GetInstance()->AuthFriendApply(uid, touid);//更新数据库添加好友MysqlMgr::GetInstance()->AddFriend(uid, touid,back_name);//查询redis 查找touid对应的server ipauto to_str = std::to_string(touid);auto to_ip_key = USERIPPREFIX + to_str;std::string to_ip_value = "";bool b_ip = RedisMgr::GetInstance()->Get(to_ip_key, to_ip_value);if (!b_ip) {return;}auto& cfg = ConfigMgr::Inst();auto self_name = cfg["SelfServer"]["Name"];//直接通知对方有认证通过消息if (to_ip_value == self_name) {auto session = UserMgr::GetInstance()->GetSession(touid);if (session) {//在内存中则直接发送通知对方Json::Value notify;notify["error"] = ErrorCodes::Success;notify["fromuid"] = uid;notify["touid"] = touid;std::string base_key = USER_BASE_INFO + std::to_string(uid);auto user_info = std::make_shared<UserInfo>();bool b_info = GetBaseInfo(base_key, uid, user_info);if (b_info) {notify["name"] = user_info->name;notify["nick"] = user_info->nick;notify["icon"] = user_info->icon;notify["sex"] = user_info->sex;}else {notify["error"] = ErrorCodes::UidInvalid;}std::string return_str = notify.toStyledString();session->Send(return_str, ID_NOTIFY_AUTH_FRIEND_REQ);}return ;}AuthFriendReq auth_req;auth_req.set_fromuid(uid);auth_req.set_touid(touid);//发送通知ChatGrpcClient::GetInstance()->NotifyAuthFriend(to_ip_value, auth_req);}
将请求注册到map里,在LogicSystem::RegisterCallBacks中添加
_fun_callbacks[ID_AUTH_FRIEND_REQ] = std::bind(&LogicSystem::AuthFriendApply, this,placeholders::_1, placeholders::_2, placeholders::_3);
因为上面的逻辑调用了grpc发送通知,所以实现grpc发送认证通知的逻辑
AuthFriendRsp ChatGrpcClient::NotifyAuthFriend(std::string server_ip, const AuthFriendReq& req) {AuthFriendRsp rsp;rsp.set_error(ErrorCodes::Success);Defer defer([&rsp, &req]() {rsp.set_fromuid(req.fromuid());rsp.set_touid(req.touid());});auto find_iter = _pools.find(server_ip);if (find_iter == _pools.end()) {return rsp;}auto& pool = find_iter->second;ClientContext context;auto stub = pool->getConnection();Status status = stub->NotifyAuthFriend(&context, req, &rsp);Defer defercon([&stub, this, &pool]() {pool->returnConnection(std::move(stub));});if (!status.ok()) {rsp.set_error(ErrorCodes::RPCFailed);return rsp;}return rsp;}
这里注意,stub之所以能发送通知,是因为proto里定义了认证通知等服务,大家记得更新proto和我的一样,这事完整的proto
syntax = "proto3";package message;service VarifyService {rpc GetVarifyCode (GetVarifyReq) returns (GetVarifyRsp) {}}message GetVarifyReq {string email = 1;}message GetVarifyRsp {int32 error = 1;string email = 2;string code = 3;}message GetChatServerReq {int32 uid = 1;}message GetChatServerRsp {int32 error = 1;string host = 2;string port = 3;string token = 4;}message LoginReq{int32 uid = 1;string token= 2;}message LoginRsp {int32 error = 1;int32 uid = 2;string token = 3;}service StatusService {rpc GetChatServer (GetChatServerReq) returns (GetChatServerRsp) {}rpc Login(LoginReq) returns(LoginRsp);}message AddFriendReq {int32 applyuid = 1;string name = 2;string desc = 3;string icon = 4;string nick = 5;int32 sex = 6;int32 touid = 7;}message AddFriendRsp {int32 error = 1;int32 applyuid = 2;int32 touid = 3;}message RplyFriendReq {int32 rplyuid = 1;bool agree = 2;int32 touid = 3;}message RplyFriendRsp {int32 error = 1;int32 rplyuid = 2;int32 touid = 3;}message SendChatMsgReq{int32 fromuid = 1;int32 touid = 2;string message = 3;}message SendChatMsgRsp{int32 error = 1;int32 fromuid = 2;int32 touid = 3;}message AuthFriendReq{int32 fromuid = 1;int32 touid = 2;}message AuthFriendRsp{int32 error = 1;int32 fromuid = 2;int32 touid = 3;}message TextChatMsgReq {int32 fromuid = 1;int32 touid = 2;repeated TextChatData textmsgs = 3;}message TextChatData{string msgid = 1;string msgcontent = 2;}message TextChatMsgRsp {int32 error = 1;int32 fromuid = 2;int32 touid = 3;repeated TextChatData textmsgs = 4;}service ChatService {rpc NotifyAddFriend(AddFriendReq) returns (AddFriendRsp) {}rpc RplyAddFriend(RplyFriendReq) returns (RplyFriendRsp) {}rpc SendChatMsg(SendChatMsgReq) returns (SendChatMsgRsp) {}rpc NotifyAuthFriend(AuthFriendReq) returns (AuthFriendRsp) {}rpc NotifyTextChatMsg(TextChatMsgReq) returns (TextChatMsgRsp){}}
为了方便生成grpcpb文件,我写了一个start.bat批处理文件
@echo offset PROTOC_PATH=D:\cppsoft\grpc\visualpro\third_party\protobuf\Debug\protoc.exeset GRPC_PLUGIN_PATH=D:\cppsoft\grpc\visualpro\Debug\grpc_cpp_plugin.exeset PROTO_FILE=message.protoecho Generating gRPC code...%PROTOC_PATH% -I="." --grpc_out="." --plugin=protoc-gen-grpc="%GRPC_PLUGIN_PATH%" "%PROTO_FILE%"echo Generating C++ code...%PROTOC_PATH% --cpp_out=. "%PROTO_FILE%"echo Done.
执行这个批处理文件就能生成最新的pb文件了。
接下来实现grpc服务对认证的处理
Status ChatServiceImpl::NotifyAuthFriend(ServerContext* context, const AuthFriendReq* request,AuthFriendRsp* reply) {//查找用户是否在本服务器auto touid = request->touid();auto fromuid = request->fromuid();auto session = UserMgr::GetInstance()->GetSession(touid);Defer defer([request, reply]() {reply->set_error(ErrorCodes::Success);reply->set_fromuid(request->fromuid());reply->set_touid(request->touid());});//用户不在内存中则直接返回if (session == nullptr) {return Status::OK;}//在内存中则直接发送通知对方Json::Value rtvalue;rtvalue["error"] = ErrorCodes::Success;rtvalue["fromuid"] = request->fromuid();rtvalue["touid"] = request->touid();std::string base_key = USER_BASE_INFO + std::to_string(fromuid);auto user_info = std::make_shared<UserInfo>();bool b_info = GetBaseInfo(base_key, fromuid, user_info);if (b_info) {rtvalue["name"] = user_info->name;rtvalue["nick"] = user_info->nick;rtvalue["icon"] = user_info->icon;rtvalue["sex"] = user_info->sex;}else {rtvalue["error"] = ErrorCodes::UidInvalid;}std::string return_str = rtvalue.toStyledString();session->Send(return_str, ID_NOTIFY_AUTH_FRIEND_REQ);return Status::OK;}
所以A认证B为好友,A所在的服务器会给A回复一个ID_AUTH_FRIEND_RSP的消息,B所在的服务器会给B回复一个ID_NOTIFY_AUTH_FRIEND_REQ消息。
客户端响应
客户端需要响应服务器发过来的ID_AUTH_FRIEND_RSP和ID_NOTIFY_AUTH_FRIEND_REQ消息
客户端响应ID_AUTH_FRIEND_RSP,在initHandlers中添加
_handlers.insert(ID_AUTH_FRIEND_RSP, [this](ReqId id, int len, QByteArray data) {Q_UNUSED(len);qDebug() << "handle id is " << id << " data is " << data;// 将QByteArray转换为QJsonDocumentQJsonDocument jsonDoc = QJsonDocument::fromJson(data);// 检查转换是否成功if (jsonDoc.isNull()) {qDebug() << "Failed to create QJsonDocument.";return;}QJsonObject jsonObj = jsonDoc.object();if (!jsonObj.contains("error")) {int err = ErrorCodes::ERR_JSON;qDebug() << "Auth Friend Failed, err is Json Parse Err" << err;return;}int err = jsonObj["error"].toInt();if (err != ErrorCodes::SUCCESS) {qDebug() << "Auth Friend Failed, err is " << err;return;}auto name = jsonObj["name"].toString();auto nick = jsonObj["nick"].toString();auto icon = jsonObj["icon"].toString();auto sex = jsonObj["sex"].toInt();auto uid = jsonObj["uid"].toInt();auto rsp = std::make_shared<AuthRsp>(uid, name, nick, icon, sex);emit sig_auth_rsp(rsp);qDebug() << "Auth Friend Success " ;});
在initHandlers中添加ID_NOTIFY_AUTH_FRIEND_REQ
_handlers.insert(ID_NOTIFY_AUTH_FRIEND_REQ, [this](ReqId id, int len, QByteArray data) {Q_UNUSED(len);qDebug() << "handle id is " << id << " data is " << data;// 将QByteArray转换为QJsonDocumentQJsonDocument jsonDoc = QJsonDocument::fromJson(data);// 检查转换是否成功if (jsonDoc.isNull()) {qDebug() << "Failed to create QJsonDocument.";return;}QJsonObject jsonObj = jsonDoc.object();if (!jsonObj.contains("error")) {int err = ErrorCodes::ERR_JSON;qDebug() << "Auth Friend Failed, err is " << err;return;}int err = jsonObj["error"].toInt();if (err != ErrorCodes::SUCCESS) {qDebug() << "Auth Friend Failed, err is " << err;return;}int from_uid = jsonObj["fromuid"].toInt();QString name = jsonObj["name"].toString();QString nick = jsonObj["nick"].toString();QString icon = jsonObj["icon"].toString();int sex = jsonObj["sex"].toInt();auto auth_info = std::make_shared<AuthInfo>(from_uid,name,nick, icon, sex);emit sig_add_auth_friend(auth_info);});
客户端ChatDialog中添加对sig_add_auth_friend响应,实现添加好友到聊天列表中
void ChatDialog::slot_add_auth_friend(std::shared_ptr<AuthInfo> auth_info) {qDebug() << "receive slot_add_auth__friend uid is " << auth_info->_uid<< " name is " << auth_info->_name << " nick is " << auth_info->_nick;//判断如果已经是好友则跳过auto bfriend = UserMgr::GetInstance()->CheckFriendById(auth_info->_uid);if(bfriend){return;}UserMgr::GetInstance()->AddFriend(auth_info);int randomValue = QRandomGenerator::global()->bounded(100); // 生成0到99之间的随机整数int str_i = randomValue % strs.size();int head_i = randomValue % heads.size();int name_i = randomValue % names.size();auto* chat_user_wid = new ChatUserWid();auto user_info = std::make_shared<UserInfo>(auth_info);chat_user_wid->SetInfo(user_info);QListWidgetItem* item = new QListWidgetItem;//qDebug()<<"chat_user_wid sizeHint is " << chat_user_wid->sizeHint();item->setSizeHint(chat_user_wid->sizeHint());ui->chat_user_list->insertItem(0, item);ui->chat_user_list->setItemWidget(item, chat_user_wid);_chat_items_added.insert(auth_info->_uid, item);}
客户端ChatDialog中添加对sig_auth_rsp响应, 实现添加好友到聊天列表中
void ChatDialog::slot_auth_rsp(std::shared_ptr<AuthRsp> auth_rsp){qDebug() << "receive slot_auth_rsp uid is " << auth_rsp->_uid<< " name is " << auth_rsp->_name << " nick is " << auth_rsp->_nick;//判断如果已经是好友则跳过auto bfriend = UserMgr::GetInstance()->CheckFriendById(auth_rsp->_uid);if(bfriend){return;}UserMgr::GetInstance()->AddFriend(auth_rsp);int randomValue = QRandomGenerator::global()->bounded(100); // 生成0到99之间的随机整数int str_i = randomValue % strs.size();int head_i = randomValue % heads.size();int name_i = randomValue % names.size();auto* chat_user_wid = new ChatUserWid();auto user_info = std::make_shared<UserInfo>(auth_rsp);chat_user_wid->SetInfo(user_info);QListWidgetItem* item = new QListWidgetItem;//qDebug()<<"chat_user_wid sizeHint is " << chat_user_wid->sizeHint();item->setSizeHint(chat_user_wid->sizeHint());ui->chat_user_list->insertItem(0, item);ui->chat_user_list->setItemWidget(item, chat_user_wid);_chat_items_added.insert(auth_rsp->_uid, item);}
因为认证对方为好友后,需要将申请页面的添加按钮变成已添加,所以ApplyFriendPage响应sig_auth_rsp信号
void ApplyFriendPage::slot_auth_rsp(std::shared_ptr<AuthRsp> auth_rsp) {auto uid = auth_rsp->_uid;auto find_iter = _unauth_items.find(uid);if (find_iter == _unauth_items.end()) {return;}find_iter->second->ShowAddBtn(false);}
同意并认证对方为好友后,也需要将对方添加到联系人列表,ContactUserList响应sig_auth_rsp信号
void ContactUserList::slot_auth_rsp(std::shared_ptr<AuthRsp> auth_rsp){qDebug() << "slot auth rsp called";bool isFriend = UserMgr::GetInstance()->CheckFriendById(auth_rsp->_uid);if(isFriend){return;}// 在 groupitem 之后插入新项int randomValue = QRandomGenerator::global()->bounded(100); // 生成0到99之间的随机整数int str_i = randomValue%strs.size();int head_i = randomValue%heads.size();auto *con_user_wid = new ConUserItem();con_user_wid->SetInfo(auth_rsp->_uid ,auth_rsp->_name, heads[head_i]);QListWidgetItem *item = new QListWidgetItem;//qDebug()<<"chat_user_wid sizeHint is " << chat_user_wid->sizeHint();item->setSizeHint(con_user_wid->sizeHint());// 获取 groupitem 的索引int index = this->row(_groupitem);// 在 groupitem 之后插入新项this->insertItem(index + 1, item);this->setItemWidget(item, con_user_wid);}
登录加载好友
因为添加好友后,如果客户端重新登录,服务器LoginHandler需要加载好友列表,所以服务器要返回好友列表
void LogicSystem::LoginHandler(shared_ptr<CSession> session, const short &msg_id, const string &msg_data) {Json::Reader reader;Json::Value root;reader.parse(msg_data, root);auto uid = root["uid"].asInt();auto token = root["token"].asString();std::cout << "user login uid is " << uid << " user token is "<< token << endl;Json::Value rtvalue;Defer defer([this, &rtvalue, session]() {std::string return_str = rtvalue.toStyledString();session->Send(return_str, MSG_CHAT_LOGIN_RSP);});//从redis获取用户token是否正确std::string uid_str = std::to_string(uid);std::string token_key = USERTOKENPREFIX + uid_str;std::string token_value = "";bool success = RedisMgr::GetInstance()->Get(token_key, token_value);if (!success) {rtvalue["error"] = ErrorCodes::UidInvalid;return ;}if (token_value != token) {rtvalue["error"] = ErrorCodes::TokenInvalid;return ;}rtvalue["error"] = ErrorCodes::Success;std::string base_key = USER_BASE_INFO + uid_str;auto user_info = std::make_shared<UserInfo>();bool b_base = GetBaseInfo(base_key, uid, user_info);if (!b_base) {rtvalue["error"] = ErrorCodes::UidInvalid;return;}rtvalue["uid"] = uid;rtvalue["pwd"] = user_info->pwd;rtvalue["name"] = user_info->name;rtvalue["email"] = user_info->email;rtvalue["nick"] = user_info->nick;rtvalue["desc"] = user_info->desc;rtvalue["sex"] = user_info->sex;rtvalue["icon"] = user_info->icon;//从数据库获取申请列表std::vector<std::shared_ptr<ApplyInfo>> apply_list;auto b_apply = GetFriendApplyInfo(uid,apply_list);if (b_apply) {for (auto & apply : apply_list) {Json::Value obj;obj["name"] = apply->_name;obj["uid"] = apply->_uid;obj["icon"] = apply->_icon;obj["nick"] = apply->_nick;obj["sex"] = apply->_sex;obj["desc"] = apply->_desc;obj["status"] = apply->_status;rtvalue["apply_list"].append(obj);}}//获取好友列表std::vector<std::shared_ptr<UserInfo>> friend_list;bool b_friend_list = GetFriendList(uid, friend_list);for (auto& friend_ele : friend_list) {Json::Value obj;obj["name"] = friend_ele->name;obj["uid"] = friend_ele->uid;obj["icon"] = friend_ele->icon;obj["nick"] = friend_ele->nick;obj["sex"] = friend_ele->sex;obj["desc"] = friend_ele->desc;obj["back"] = friend_ele->back;rtvalue["friend_list"].append(obj);}auto server_name = ConfigMgr::Inst().GetValue("SelfServer", "Name");//将登录数量增加auto rd_res = RedisMgr::GetInstance()->HGet(LOGIN_COUNT, server_name);int count = 0;if (!rd_res.empty()) {count = std::stoi(rd_res);}count++;auto count_str = std::to_string(count);RedisMgr::GetInstance()->HSet(LOGIN_COUNT, server_name, count_str);//session绑定用户uidsession->SetUserId(uid);//为用户设置登录ip server的名字std::string ipkey = USERIPPREFIX + uid_str;RedisMgr::GetInstance()->Set(ipkey, server_name);//uid和session绑定管理,方便以后踢人操作UserMgr::GetInstance()->SetUserSession(uid, session);return;}
客户端在initHandlers中加载聊天列表
_handlers.insert(ID_CHAT_LOGIN_RSP, [this](ReqId id, int len, QByteArray data){Q_UNUSED(len);qDebug()<< "handle id is "<< id ;// 将QByteArray转换为QJsonDocumentQJsonDocument jsonDoc = QJsonDocument::fromJson(data);// 检查转换是否成功if(jsonDoc.isNull()){qDebug() << "Failed to create QJsonDocument.";return;}QJsonObject jsonObj = jsonDoc.object();qDebug()<< "data jsonobj is " << jsonObj ;if(!jsonObj.contains("error")){int err = ErrorCodes::ERR_JSON;qDebug() << "Login Failed, err is Json Parse Err" << err ;emit sig_login_failed(err);return;}int err = jsonObj["error"].toInt();if(err != ErrorCodes::SUCCESS){qDebug() << "Login Failed, err is " << err ;emit sig_login_failed(err);return;}auto uid = jsonObj["uid"].toInt();auto name = jsonObj["name"].toString();auto nick = jsonObj["nick"].toString();auto icon = jsonObj["icon"].toString();auto sex = jsonObj["sex"].toInt();auto user_info = std::make_shared<UserInfo>(uid, name, nick, icon, sex);UserMgr::GetInstance()->SetUserInfo(user_info);UserMgr::GetInstance()->SetToken(jsonObj["token"].toString());if(jsonObj.contains("apply_list")){UserMgr::GetInstance()->AppendApplyList(jsonObj["apply_list"].toArray());}//添加好友列表if (jsonObj.contains("friend_list")) {UserMgr::GetInstance()->AppendFriendList(jsonObj["friend_list"].toArray());}emit sig_swich_chatdlg();});
好友聊天
客户端发送聊天消息
客户端发送聊天消息,在输入框输入消息后,点击发送回执行下面的槽函数
void ChatPage::on_send_btn_clicked(){if (_user_info == nullptr) {qDebug() << "friend_info is empty";return;}auto user_info = UserMgr::GetInstance()->GetUserInfo();auto pTextEdit = ui->chatEdit;ChatRole role = ChatRole::Self;QString userName = user_info->_name;QString userIcon = user_info->_icon;const QVector<MsgInfo>& msgList = pTextEdit->getMsgList();QJsonObject textObj;QJsonArray textArray;int txt_size = 0;for(int i=0; i<msgList.size(); ++i){//消息内容长度不合规就跳过if(msgList[i].content.length() > 1024){continue;}QString type = msgList[i].msgFlag;ChatItemBase *pChatItem = new ChatItemBase(role);pChatItem->setUserName(userName);pChatItem->setUserIcon(QPixmap(userIcon));QWidget *pBubble = nullptr;if(type == "text"){//生成唯一idQUuid uuid = QUuid::createUuid();//转为字符串QString uuidString = uuid.toString();pBubble = new TextBubble(role, msgList[i].content);if(txt_size + msgList[i].content.length()> 1024){textObj["fromuid"] = user_info->_uid;textObj["touid"] = _user_info->_uid;textObj["text_array"] = textArray;QJsonDocument doc(textObj);QByteArray jsonData = doc.toJson(QJsonDocument::Compact);//发送并清空之前累计的文本列表txt_size = 0;textArray = QJsonArray();textObj = QJsonObject();//发送tcp请求给chat serveremit TcpMgr::GetInstance()->sig_send_data(ReqId::ID_TEXT_CHAT_MSG_REQ, jsonData);}//将bubble和uid绑定,以后可以等网络返回消息后设置是否送达//_bubble_map[uuidString] = pBubble;txt_size += msgList[i].content.length();QJsonObject obj;QByteArray utf8Message = msgList[i].content.toUtf8();obj["content"] = QString::fromUtf8(utf8Message);obj["msgid"] = uuidString;textArray.append(obj);auto txt_msg = std::make_shared<TextChatData>(uuidString, obj["content"].toString(),user_info->_uid, _user_info->_uid);emit sig_append_send_chat_msg(txt_msg);}else if(type == "image"){pBubble = new PictureBubble(QPixmap(msgList[i].content) , role);}else if(type == "file"){}//发送消息if(pBubble != nullptr){pChatItem->setWidget(pBubble);ui->chat_data_list->appendChatItem(pChatItem);}}qDebug() << "textArray is " << textArray ;//发送给服务器textObj["text_array"] = textArray;textObj["fromuid"] = user_info->_uid;textObj["touid"] = _user_info->_uid;QJsonDocument doc(textObj);QByteArray jsonData = doc.toJson(QJsonDocument::Compact);//发送并清空之前累计的文本列表txt_size = 0;textArray = QJsonArray();textObj = QJsonObject();//发送tcp请求给chat serveremit TcpMgr::GetInstance()->sig_send_data(ReqId::ID_TEXT_CHAT_MSG_REQ, jsonData);}
TcpMgr响应发送信号
void TcpMgr::slot_send_data(ReqId reqId, QByteArray dataBytes){uint16_t id = reqId;// 计算长度(使用网络字节序转换)quint16 len = static_cast<quint16>(dataBytes.length());// 创建一个QByteArray用于存储要发送的所有数据QByteArray block;QDataStream out(&block, QIODevice::WriteOnly);// 设置数据流使用网络字节序out.setByteOrder(QDataStream::BigEndian);// 写入ID和长度out << id << len;// 添加字符串数据block.append(dataBytes);// 发送数据_socket.write(block);qDebug() << "tcp mgr send byte data is " << block ;}
服务器响应
服务器响应客户端发送过来文本消息,在initHandlers中添加处理文本消息的逻辑
void LogicSystem::DealChatTextMsg(std::shared_ptr<CSession> session, const short& msg_id, const string& msg_data) {Json::Reader reader;Json::Value root;reader.parse(msg_data, root);auto uid = root["fromuid"].asInt();auto touid = root["touid"].asInt();const Json::Value arrays = root["text_array"];Json::Value rtvalue;rtvalue["error"] = ErrorCodes::Success;rtvalue["text_array"] = arrays;rtvalue["fromuid"] = uid;rtvalue["touid"] = touid;Defer defer([this, &rtvalue, session]() {std::string return_str = rtvalue.toStyledString();session->Send(return_str, ID_TEXT_CHAT_MSG_RSP);});//查询redis 查找touid对应的server ipauto to_str = std::to_string(touid);auto to_ip_key = USERIPPREFIX + to_str;std::string to_ip_value = "";bool b_ip = RedisMgr::GetInstance()->Get(to_ip_key, to_ip_value);if (!b_ip) {return;}auto& cfg = ConfigMgr::Inst();auto self_name = cfg["SelfServer"]["Name"];//直接通知对方有认证通过消息if (to_ip_value == self_name) {auto session = UserMgr::GetInstance()->GetSession(touid);if (session) {//在内存中则直接发送通知对方std::string return_str = rtvalue.toStyledString();session->Send(return_str, ID_NOTIFY_TEXT_CHAT_MSG_REQ);}return ;}TextChatMsgReq text_msg_req;text_msg_req.set_fromuid(uid);text_msg_req.set_touid(touid);for (const auto& txt_obj : arrays) {auto content = txt_obj["content"].asString();auto msgid = txt_obj["msgid"].asString();std::cout << "content is " << content << std::endl;std::cout << "msgid is " << msgid << std::endl;auto *text_msg = text_msg_req.add_textmsgs();text_msg->set_msgid(msgid);text_msg->set_msgcontent(content);}//发送通知 todo...ChatGrpcClient::GetInstance()->NotifyTextChatMsg(to_ip_value, text_msg_req, rtvalue);}
服务器实现发送消息的rpc客户端
TextChatMsgRsp ChatGrpcClient::NotifyTextChatMsg(std::string server_ip,const TextChatMsgReq& req, const Json::Value& rtvalue) {TextChatMsgRsp rsp;rsp.set_error(ErrorCodes::Success);Defer defer([&rsp, &req]() {rsp.set_fromuid(req.fromuid());rsp.set_touid(req.touid());for (const auto& text_data : req.textmsgs()) {TextChatData* new_msg = rsp.add_textmsgs();new_msg->set_msgid(text_data.msgid());new_msg->set_msgcontent(text_data.msgcontent());}});auto find_iter = _pools.find(server_ip);if (find_iter == _pools.end()) {return rsp;}auto& pool = find_iter->second;ClientContext context;auto stub = pool->getConnection();Status status = stub->NotifyTextChatMsg(&context, req, &rsp);Defer defercon([&stub, this, &pool]() {pool->returnConnection(std::move(stub));});if (!status.ok()) {rsp.set_error(ErrorCodes::RPCFailed);return rsp;}return rsp;}
服务器实现rpc服务端处理消息通知
Status ChatServiceImpl::NotifyTextChatMsg(::grpc::ServerContext* context,const TextChatMsgReq* request, TextChatMsgRsp* reply) {//查找用户是否在本服务器auto touid = request->touid();auto session = UserMgr::GetInstance()->GetSession(touid);reply->set_error(ErrorCodes::Success);//用户不在内存中则直接返回if (session == nullptr) {return Status::OK;}//在内存中则直接发送通知对方Json::Value rtvalue;rtvalue["error"] = ErrorCodes::Success;rtvalue["fromuid"] = request->fromuid();rtvalue["touid"] = request->touid();//将聊天数据组织为数组Json::Value text_array;for (auto& msg : request->textmsgs()) {Json::Value element;element["content"] = msg.msgcontent();element["msgid"] = msg.msgid();text_array.append(element);}rtvalue["text_array"] = text_array;std::string return_str = rtvalue.toStyledString();session->Send(return_str, ID_NOTIFY_TEXT_CHAT_MSG_REQ);return Status::OK;}
客户端响应通知
客户端响应服务器返回的消息,包括两种:
- A给B发送文本消息,A所在的服务器会给A发送ID_TEXT_CHAT_MSG_RSP消息。
- B所在的服务器会通知B,告诉B有来自A的消息,通知消息为ID_NOTIFY_TEXT_CHAT_MSG_REQ
所以在tcpmgr的initHandlers中添加响应ID_TEXT_CHAT_MSG_RSP消息
_handlers.insert(ID_TEXT_CHAT_MSG_RSP, [this](ReqId id, int len, QByteArray data) {Q_UNUSED(len);qDebug() << "handle id is " << id << " data is " << data;// 将QByteArray转换为QJsonDocumentQJsonDocument jsonDoc = QJsonDocument::fromJson(data);// 检查转换是否成功if (jsonDoc.isNull()) {qDebug() << "Failed to create QJsonDocument.";return;}QJsonObject jsonObj = jsonDoc.object();if (!jsonObj.contains("error")) {int err = ErrorCodes::ERR_JSON;qDebug() << "Chat Msg Rsp Failed, err is Json Parse Err" << err;return;}int err = jsonObj["error"].toInt();if (err != ErrorCodes::SUCCESS) {qDebug() << "Chat Msg Rsp Failed, err is " << err;return;}qDebug() << "Receive Text Chat Rsp Success " ;//ui设置送达等标记 todo...});
在TcpMgr的initHandlers中添加ID_NOTIFY_TEXT_CHAT_MSG_REQ
_handlers.insert(ID_NOTIFY_TEXT_CHAT_MSG_REQ, [this](ReqId id, int len, QByteArray data) {Q_UNUSED(len);qDebug() << "handle id is " << id << " data is " << data;// 将QByteArray转换为QJsonDocumentQJsonDocument jsonDoc = QJsonDocument::fromJson(data);// 检查转换是否成功if (jsonDoc.isNull()) {qDebug() << "Failed to create QJsonDocument.";return;}QJsonObject jsonObj = jsonDoc.object();if (!jsonObj.contains("error")) {int err = ErrorCodes::ERR_JSON;qDebug() << "Notify Chat Msg Failed, err is Json Parse Err" << err;return;}int err = jsonObj["error"].toInt();if (err != ErrorCodes::SUCCESS) {qDebug() << "Notify Chat Msg Failed, err is " << err;return;}qDebug() << "Receive Text Chat Notify Success " ;auto msg_ptr = std::make_shared<TextChatMsg>(jsonObj["fromuid"].toInt(),jsonObj["touid"].toInt(),jsonObj["text_array"].toArray());emit sig_text_chat_msg(msg_ptr);});
客户端ChatDialog添加对sig_text_chat_msg的响应
void ChatDialog::slot_text_chat_msg(std::shared_ptr<TextChatMsg> msg){auto find_iter = _chat_items_added.find(msg->_from_uid);if(find_iter != _chat_items_added.end()){qDebug() << "set chat item msg, uid is " << msg->_from_uid;QWidget *widget = ui->chat_user_list->itemWidget(find_iter.value());auto chat_wid = qobject_cast<ChatUserWid*>(widget);if(!chat_wid){return;}chat_wid->updateLastMsg(msg->_chat_msgs);//更新当前聊天页面记录UpdateChatMsg(msg->_chat_msgs);UserMgr::GetInstance()->AppendFriendChatMsg(msg->_from_uid,msg->_chat_msgs);return;}//如果没找到,则创建新的插入listwidgetauto* chat_user_wid = new ChatUserWid();//查询好友信息auto fi_ptr = UserMgr::GetInstance()->GetFriendById(msg->_from_uid);chat_user_wid->SetInfo(fi_ptr);QListWidgetItem* item = new QListWidgetItem;//qDebug()<<"chat_user_wid sizeHint is " << chat_user_wid->sizeHint();item->setSizeHint(chat_user_wid->sizeHint());chat_user_wid->updateLastMsg(msg->_chat_msgs);UserMgr::GetInstance()->AppendFriendChatMsg(msg->_from_uid,msg->_chat_msgs);ui->chat_user_list->insertItem(0, item);ui->chat_user_list->setItemWidget(item, chat_user_wid);_chat_items_added.insert(msg->_from_uid, item);}
效果展示

源码连接
https://gitee.com/secondtonone1/llfcchat
视频连接
https://www.bilibili.com/video/BV1ib421J745/?vd_source=8be9e83424c2ed2c9b2a3ed1d01385e9