做網(wǎng)站的例子設(shè)計(jì)網(wǎng)站官網(wǎng)
文章目錄
- TCP服務(wù)器設(shè)計(jì)
- 客戶端TCP管理者
- ChatServer
- AsioIOServicePool
- Session層
- LogicSystem
- 總結(jié)
- token驗(yàn)證模塊
- 完善proto
- StatusServer驗(yàn)證token
- 客戶端處理登陸回包
- 用戶管理
- 登陸界面
本篇完成的模塊是TCP服務(wù)器的設(shè)計(jì)和token驗(yàn)證
TCP服務(wù)器設(shè)計(jì)
客戶端TCP管理者
因?yàn)榱奶旆?wù)要維持一個(gè)長(zhǎng)鏈接,方便服務(wù)器和客戶端雙向通信,那么就需要一個(gè)TCPMgr來(lái)管理TCP連接
而實(shí)際開(kāi)發(fā)中網(wǎng)絡(luò)模塊一般以單例模式使用,那我們就基于單例基類(lèi)和可被分享類(lèi)創(chuàng)建一個(gè)自定義的TcpMgr類(lèi),在QT工程中新建TcpMgr類(lèi),會(huì)生成頭文件和源文件,頭文件修改如下
#ifndef TCPMGR_H
#define TCPMGR_H
#include <QTcpSocket>
#include "singleton.h"
#include "global.h"
class TcpMgr:public QObject, public Singleton<TcpMgr>,public std::enable_shared_from_this<TcpMgr>
{Q_OBJECT
public:TcpMgr();
private:QTcpSocket _socket;QString _host;uint16_t _port;QByteArray _buffer;bool _b_recv_pending;quint16 _message_id;quint16 _message_len;
public slots:void slot_tcp_connect(ServerInfo);void slot_send_data(ReqId reqId, QString data);
signals:void sig_con_success(bool bsuccess);void sig_send_data(ReqId reqId, QString data);
};#endif // TCPMGR_H
接下來(lái)我們?cè)跇?gòu)造函數(shù)中連接網(wǎng)絡(luò)請(qǐng)求的各種信號(hào)
TcpMgr::TcpMgr():_host(""),_port(0),_b_recv_pending(false),_message_id(0),_message_len(0)
{QObject::connect(&_socket, &QTcpSocket::connected, [&]() {qDebug() << "Connected to server!";// 連接建立后發(fā)送消息emit sig_con_success(true);});QObject::connect(&_socket, &QTcpSocket::readyRead, [&]() {// 當(dāng)有數(shù)據(jù)可讀時(shí),讀取所有數(shù)據(jù)// 讀取所有數(shù)據(jù)并追加到緩沖區(qū)_buffer.append(_socket.readAll());QDataStream stream(&_buffer, QIODevice::ReadOnly);stream.setVersion(QDataStream::Qt_5_0);forever {//先解析頭部if(!_b_recv_pending){// 檢查緩沖區(qū)中的數(shù)據(jù)是否足夠解析出一個(gè)消息頭(消息ID + 消息長(zhǎng)度)if (_buffer.size() < static_cast<int>(sizeof(quint16) * 2)) {return; // 數(shù)據(jù)不夠,等待更多數(shù)據(jù)}// 預(yù)讀取消息ID和消息長(zhǎng)度,但不從緩沖區(qū)中移除stream >> _message_id >> _message_len;//將buffer 中的前四個(gè)字節(jié)移除_buffer = _buffer.mid(sizeof(quint16) * 2);// 輸出讀取的數(shù)據(jù)qDebug() << "Message ID:" << _message_id << ", Length:" << _message_len;}//buffer剩余長(zhǎng)讀是否滿足消息體長(zhǎng)度,不滿足則退出繼續(xù)等待接受if(_buffer.size() < _message_len){_b_recv_pending = true;return;}_b_recv_pending = false;// 讀取消息體QByteArray messageBody = _buffer.mid(0, _message_len);qDebug() << "receive body msg is " << messageBody ;_buffer = _buffer.mid(_message_len);}});// 處理錯(cuò)誤(適用于Qt 5.15之前的版本)QObject::connect(&_socket, static_cast<void (QTcpSocket::*)(QTcpSocket::SocketError)>(&QTcpSocket::error),[&](QTcpSocket::SocketError socketError) {qDebug() << "Error:" << _socket.errorString() ;switch (socketError) {case QTcpSocket::ConnectionRefusedError:qDebug() << "Connection Refused!";emit sig_con_success(false);break;case QTcpSocket::RemoteHostClosedError:qDebug() << "Remote Host Closed Connection!";break;case QTcpSocket::HostNotFoundError:qDebug() << "Host Not Found!";emit sig_con_success(false);break;case QTcpSocket::SocketTimeoutError:qDebug() << "Connection Timeout!";emit sig_con_success(false);break;case QTcpSocket::NetworkError:qDebug() << "Network Error!";break;default:qDebug() << "Other Error!";break;}});// 處理連接斷開(kāi)QObject::connect(&_socket, &QTcpSocket::disconnected, [&]() {qDebug() << "Disconnected from server.";});QObject::connect(this, &TcpMgr::sig_send_data, this, &TcpMgr::slot_send_data);
}
連接對(duì)端服務(wù)器
void TcpMgr::slot_tcp_connect(ServerInfo si)
{qDebug()<< "receive tcp connect signal";// 嘗試連接到服務(wù)器qDebug() << "Connecting to server...";_host = si.Host;_port = static_cast<uint16_t>(si.Port.toUInt());_socket.connectToHost(si.Host, _port);
}
因?yàn)榭蛻舳税l(fā)送數(shù)據(jù)可能在任何線程,為了保證線程安全,我們?cè)谝l(fā)送數(shù)據(jù)時(shí)發(fā)送TcpMgr的sig_send_data信號(hào),然后實(shí)現(xiàn)接受這個(gè)信號(hào)的槽函數(shù)
void TcpMgr::slot_send_data(ReqId reqId, QString data)
{uint16_t id = reqId;// 將字符串轉(zhuǎn)換為UTF-8編碼的字節(jié)數(shù)組QByteArray dataBytes = data.toUtf8();// 計(jì)算長(zhǎng)度(使用網(wǎng)絡(luò)字節(jié)序轉(zhuǎn)換)quint16 len = static_cast<quint16>(data.size());// 創(chuàng)建一個(gè)QByteArray用于存儲(chǔ)要發(fā)送的所有數(shù)據(jù)QByteArray block;QDataStream out(&block, QIODevice::WriteOnly);// 設(shè)置數(shù)據(jù)流使用網(wǎng)絡(luò)字節(jié)序out.setByteOrder(QDataStream::BigEndian);// 寫(xiě)入ID和長(zhǎng)度out << id << len;// 添加字符串?dāng)?shù)據(jù)block.append(data);// 發(fā)送數(shù)據(jù)_socket.write(block);
}
然后修改LoginDialog中的initHandlers中的收到服務(wù)器登陸回復(fù)后的邏輯,這里發(fā)送信號(hào)準(zhǔn)備發(fā)起長(zhǎng)鏈接到聊天服務(wù)器
void LoginDialog::initHttpHandlers()
{//注冊(cè)獲取登錄回包邏輯_handlers.insert(ReqId::ID_LOGIN_USER, [this](QJsonObject jsonObj){int error = jsonObj["error"].toInt();if(error != ErrorCodes::SUCCESS){showTip(tr("參數(shù)錯(cuò)誤"),false);enableBtn(true);return;}auto user = jsonObj["user"].toString();//發(fā)送信號(hào)通知tcpMgr發(fā)送長(zhǎng)鏈接ServerInfo si;si.Uid = jsonObj["uid"].toInt();si.Host = jsonObj["host"].toString();si.Port = jsonObj["port"].toString();si.Token = jsonObj["token"].toString();_uid = si.Uid;_token = si.Token;qDebug()<< "user is " << user << " uid is " << si.Uid <<" host is "<< si.Host << " Port is " << si.Port << " Token is " << si.Token;emit sig_connect_tcp(si);});
}
在LoginDialog構(gòu)造函數(shù)中連接信號(hào),包括建立tcp連接,以及收到TcpMgr連接成功或者失敗的信號(hào)處理
//連接tcp連接請(qǐng)求的信號(hào)和槽函數(shù)connect(this, &LoginDialog::sig_connect_tcp, TcpMgr::GetInstance().get(), &TcpMgr::slot_tcp_connect);
//連接tcp管理者發(fā)出的連接成功信號(hào)
connect(TcpMgr::GetInstance().get(), &TcpMgr::sig_con_success, this, &LoginDialog::slot_tcp_con_finish);
LoginDialog收到連接結(jié)果的槽函數(shù)
void LoginDialog::slot_tcp_con_finish(bool bsuccess)
{if(bsuccess){showTip(tr("聊天服務(wù)連接成功,正在登錄..."),true);QJsonObject jsonObj;jsonObj["uid"] = _uid;jsonObj["token"] = _token;QJsonDocument doc(jsonObj);QString jsonString = doc.toJson(QJsonDocument::Indented);//發(fā)送tcp請(qǐng)求給chat serverTcpMgr::GetInstance()->sig_send_data(ReqId::ID_CHAT_LOGIN, jsonString);}else{showTip(tr("網(wǎng)絡(luò)異常"),false);enableBtn(true);}}
在這個(gè)槽函數(shù)中我們發(fā)送了sig_send_data信號(hào)并且通知TcpMgr將數(shù)據(jù)發(fā)送給服務(wù)器。
ChatServer
一個(gè)TCP服務(wù)器必然會(huì)有連接的接收,維持,收發(fā)數(shù)據(jù)等邏輯。那我們就要基于asio完成這個(gè)服務(wù)的搭建。主服務(wù)是這個(gè)樣子的
#include "LogicSystem.h"
#include <csignal>
#include <thread>
#include <mutex>
#include "AsioIOServicePool.h"
#include "CServer.h"
#include "ConfigMgr.h"
using namespace std;
bool bstop = false;
std::condition_variable cond_quit;
std::mutex mutex_quit;int main()
{try {auto &cfg = ConfigMgr::Inst();auto pool = AsioIOServicePool::GetInstance();boost::asio::io_context io_context;boost::asio::signal_set signals(io_context, SIGINT, SIGTERM);signals.async_wait([&io_context, pool](auto, auto) {io_context.stop();pool->Stop();});auto port_str = cfg["SelfServer"]["Port"];CServer s(io_context, atoi(port_str.c_str()));io_context.run();}catch (std::exception& e) {std::cerr << "Exception: " << e.what() << endl;}}
CServer類(lèi)的聲明
#include <boost/asio.hpp>
#include "CSession.h"
#include <memory.h>
#include <map>
#include <mutex>
using namespace std;
using boost::asio::ip::tcp;
class CServer
{
public:CServer(boost::asio::io_context& io_context, short port);~CServer();void ClearSession(std::string);
private:void HandleAccept(shared_ptr<CSession>, const boost::system::error_code & error);void StartAccept();boost::asio::io_context &_io_context;short _port;tcp::acceptor _acceptor;std::map<std::string, shared_ptr<CSession>> _sessions;std::mutex _mutex;
};
構(gòu)造函數(shù)中監(jiān)聽(tīng)對(duì)方連接
CServer::CServer(boost::asio::io_context& io_context, short port):_io_context(io_context), _port(port),
_acceptor(io_context, tcp::endpoint(tcp::v4(),port))
{cout << "Server start success, listen on port : " << _port << endl;StartAccept();
}
接受連接的函數(shù)
void CServer::StartAccept() {auto &io_context = AsioIOServicePool::GetInstance()->GetIOService();shared_ptr<CSession> new_session = make_shared<CSession>(io_context, this);_acceptor.async_accept(new_session->GetSocket(), std::bind(&CServer::HandleAccept, this, new_session, placeholders::_1));
}
AsioIOServicePool
從AsioIOServicePool中返回一個(gè)可用的iocontext構(gòu)造Session,然后將接受的新鏈接的socket寫(xiě)入這個(gè)Session保管
AsioIOServicePool已經(jīng)在前面講解很多次了,它的聲明如下
#include <vector>
#include <boost/asio.hpp>
#include "Singleton.h"
class AsioIOServicePool:public Singleton<AsioIOServicePool>
{friend Singleton<AsioIOServicePool>;
public:using IOService = boost::asio::io_context;using Work = boost::asio::io_context::work;using WorkPtr = std::unique_ptr<Work>;~AsioIOServicePool();AsioIOServicePool(const AsioIOServicePool&) = delete;AsioIOServicePool& operator=(const AsioIOServicePool&) = delete;// 使用 round-robin 的方式返回一個(gè) io_serviceboost::asio::io_context& GetIOService();void Stop();
private:AsioIOServicePool(std::size_t size = std::thread::hardware_concurrency());std::vector<IOService> _ioServices;std::vector<WorkPtr> _works;std::vector<std::thread> _threads;std::size_t _nextIOService;
};
AsioIOServicePool具體實(shí)現(xiàn)
#include "AsioIOServicePool.h"
#include <iostream>
using namespace std;
AsioIOServicePool::AsioIOServicePool(std::size_t size):_ioServices(size),
_works(size), _nextIOService(0){for (std::size_t i = 0; i < size; ++i) {_works[i] = std::unique_ptr<Work>(new Work(_ioServices[i]));}//遍歷多個(gè)ioservice,創(chuàng)建多個(gè)線程,每個(gè)線程內(nèi)部啟動(dòng)ioservicefor (std::size_t i = 0; i < _ioServices.size(); ++i) {_threads.emplace_back([this, i]() {_ioServices[i].run();});}
}AsioIOServicePool::~AsioIOServicePool() {std::cout << "AsioIOServicePool destruct" << endl;
}boost::asio::io_context& AsioIOServicePool::GetIOService() {auto& service = _ioServices[_nextIOService++];if (_nextIOService == _ioServices.size()) {_nextIOService = 0;}return service;
}void AsioIOServicePool::Stop(){//因?yàn)閮H僅執(zhí)行work.reset并不能讓iocontext從run的狀態(tài)中退出//當(dāng)iocontext已經(jīng)綁定了讀或?qū)懙谋O(jiān)聽(tīng)事件后,還需要手動(dòng)stop該服務(wù)for (auto& work : _works) {//把服務(wù)先停止work->get_io_context().stop();work.reset();}for (auto& t : _threads) {t.join();}
}
CServer的處理連接邏輯
void CServer::HandleAccept(shared_ptr<CSession> new_session, const boost::system::error_code& error){if (!error) {new_session->Start();lock_guard<mutex> lock(_mutex);_sessions.insert(make_pair(new_session->GetUuid(), new_session));}else {cout << "session accept failed, error is " << error.what() << endl;}StartAccept();
}
Session層
上面的邏輯接受新鏈接后執(zhí)行Start函數(shù),新鏈接接受數(shù)據(jù),然后Server繼續(xù)監(jiān)聽(tīng)新的連接
void CSession::Start(){AsyncReadHead(HEAD_TOTAL_LEN);
}
先讀取頭部數(shù)據(jù)
void CSession::AsyncReadHead(int total_len)
{auto self = shared_from_this();asyncReadFull(HEAD_TOTAL_LEN, [self, this](const boost::system::error_code& ec, std::size_t bytes_transfered) {try {if (ec) {std::cout << "handle read failed, error is " << ec.what() << endl;Close();_server->ClearSession(_uuid);return;}if (bytes_transfered < HEAD_TOTAL_LEN) {std::cout << "read length not match, read [" << bytes_transfered << "] , total ["<< HEAD_TOTAL_LEN << "]" << endl;Close();_server->ClearSession(_uuid);return;}_recv_head_node->Clear();memcpy(_recv_head_node->_data, _data, bytes_transfered);//獲取頭部MSGID數(shù)據(jù)short msg_id = 0;memcpy(&msg_id, _recv_head_node->_data, HEAD_ID_LEN);//網(wǎng)絡(luò)字節(jié)序轉(zhuǎn)化為本地字節(jié)序msg_id = boost::asio::detail::socket_ops::network_to_host_short(msg_id);std::cout << "msg_id is " << msg_id << endl;//id非法if (msg_id > MAX_LENGTH) {std::cout << "invalid msg_id is " << msg_id << endl;_server->ClearSession(_uuid);return;}short msg_len = 0;memcpy(&msg_len, _recv_head_node->_data + HEAD_ID_LEN, HEAD_DATA_LEN);//網(wǎng)絡(luò)字節(jié)序轉(zhuǎn)化為本地字節(jié)序msg_len = boost::asio::detail::socket_ops::network_to_host_short(msg_len);std::cout << "msg_len is " << msg_len << endl;//id非法if (msg_len > MAX_LENGTH) {std::cout << "invalid data length is " << msg_len << endl;_server->ClearSession(_uuid);return;}_recv_msg_node = make_shared<RecvNode>(msg_len, msg_id);AsyncReadBody(msg_len);}catch (std::exception& e) {std::cout << "Exception code is " << e.what() << endl;}});
}
上面的邏輯里調(diào)用asyncReadFull讀取整個(gè)長(zhǎng)度,然后解析收到的數(shù)據(jù),前兩個(gè)字節(jié)為id,之后兩個(gè)字節(jié)為長(zhǎng)度,最后n個(gè)長(zhǎng)度字節(jié)為消息內(nèi)容
//讀取完整長(zhǎng)度
void CSession::asyncReadFull(std::size_t maxLength, std::function<void(const boost::system::error_code&, std::size_t)> handler )
{::memset(_data, 0, MAX_LENGTH);asyncReadLen(0, maxLength, handler);
}
讀取指定長(zhǎng)度
//讀取指定字節(jié)數(shù)
void CSession::asyncReadLen(std::size_t read_len, std::size_t total_len, std::function<void(const boost::system::error_code&, std::size_t)> handler)
{auto self = shared_from_this();_socket.async_read_some(boost::asio::buffer(_data + read_len, total_len-read_len),[read_len, total_len, handler, self](const boost::system::error_code& ec, std::size_t bytesTransfered) {if (ec) {// 出現(xiàn)錯(cuò)誤,調(diào)用回調(diào)函數(shù)handler(ec, read_len + bytesTransfered);return;}if (read_len + bytesTransfered >= total_len) {//長(zhǎng)度夠了就調(diào)用回調(diào)函數(shù)handler(ec, read_len + bytesTransfered);return;}// 沒(méi)有錯(cuò)誤,且長(zhǎng)度不足則繼續(xù)讀取self->asyncReadLen(read_len + bytesTransfered, total_len, handler);});
}
讀取頭部成功后,其回調(diào)函數(shù)內(nèi)部調(diào)用了讀包體的邏輯
void CSession::AsyncReadBody(int total_len)
{auto self = shared_from_this();asyncReadFull(total_len, [self, this, total_len](const boost::system::error_code& ec, std::size_t bytes_transfered) {try {if (ec) {std::cout << "handle read failed, error is " << ec.what() << endl;Close();_server->ClearSession(_uuid);return;}if (bytes_transfered < total_len) {std::cout << "read length not match, read [" << bytes_transfered << "] , total ["<< total_len<<"]" << endl;Close();_server->ClearSession(_uuid);return;}memcpy(_recv_msg_node->_data , _data , bytes_transfered);_recv_msg_node->_cur_len += bytes_transfered;_recv_msg_node->_data[_recv_msg_node->_total_len] = '\0';cout << "receive data is " << _recv_msg_node->_data << endl;//此處將消息投遞到邏輯隊(duì)列中LogicSystem::GetInstance()->PostMsgToQue(make_shared<LogicNode>(shared_from_this(), _recv_msg_node));//繼續(xù)監(jiān)聽(tīng)頭部接受事件AsyncReadHead(HEAD_TOTAL_LEN);}catch (std::exception& e) {std::cout << "Exception code is " << e.what() << endl;}});
}
讀取包體完成后,在回調(diào)中繼續(xù)讀包頭。以此循環(huán)往復(fù)直到讀完所有數(shù)據(jù)。如果對(duì)方不發(fā)送數(shù)據(jù),則回調(diào)函數(shù)就不會(huì)觸發(fā)。不影響程序執(zhí)行其他工作,因?yàn)槲覀儾捎玫氖莂sio異步的讀寫(xiě)操作
當(dāng)然我們解析完包體后會(huì)調(diào)用LogicSystem單例將解析好的消息封裝為邏輯節(jié)點(diǎn)傳遞給邏輯層進(jìn)行處理
LogicSystem
我們?cè)谶壿媽犹幚?/p>
void LogicSystem::RegisterCallBacks() {_fun_callbacks[MSG_CHAT_LOGIN] = std::bind(&LogicSystem::LoginHandler, this,placeholders::_1, placeholders::_2, placeholders::_3);
}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);std::cout << "user login uid is " << root["uid"].asInt() << " user token is "<< root["token"].asString() << endl;std::string return_str = root.toStyledString();session->Send(return_str, msg_id);
}
并在構(gòu)造函數(shù)中注冊(cè)這些處理流程
LogicSystem::LogicSystem():_b_stop(false){RegisterCallBacks();_worker_thread = std::thread (&LogicSystem::DealMsg, this);
}
總結(jié)
到此,完成了ChatServer收到QT客戶端發(fā)送過(guò)來(lái)的長(zhǎng)鏈接請(qǐng)求,并解析讀取的數(shù)據(jù),將收到的數(shù)據(jù)通過(guò)tcp發(fā)送給對(duì)端
token驗(yàn)證模塊
完善proto
在proto文件里新增登陸驗(yàn)證服務(wù)
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);
}
接下來(lái)是調(diào)用grpc命令生成新的pb文件覆蓋原有的,并且也拷貝給StatusServer一份
我們完善登陸邏輯,先去StatusServer驗(yàn)證token是否合理,如果合理再?gòu)膬?nèi)存中尋找用戶信息,如果沒(méi)找到則從數(shù)據(jù)庫(kù)加載一份
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();std::cout << "user login uid is " << uid << " user token is "<< root["token"].asString() << endl;//從狀態(tài)服務(wù)器獲取token匹配是否準(zhǔn)確auto rsp = StatusGrpcClient::GetInstance()->Login(uid, root["token"].asString());Json::Value rtvalue;Defer defer([this, &rtvalue, session]() {std::string return_str = rtvalue.toStyledString();session->Send(return_str, MSG_CHAT_LOGIN_RSP);});rtvalue["error"] = rsp.error();if (rsp.error() != ErrorCodes::Success) {return;}//內(nèi)存中查詢用戶信息auto find_iter = _users.find(uid);std::shared_ptr<UserInfo> user_info = nullptr;if (find_iter == _users.end()) {//查詢數(shù)據(jù)庫(kù)user_info = MysqlMgr::GetInstance()->GetUser(uid);if (user_info == nullptr) {rtvalue["error"] = ErrorCodes::UidInvalid;return;}_users[uid] = user_info;}else {user_info = find_iter->second;}rtvalue["uid"] = uid;rtvalue["token"] = rsp.token();rtvalue["name"] = user_info->name;
}
StatusServer驗(yàn)證token
在StatusServer驗(yàn)證token之前,我們需要在StatusServer中的GetServer的服務(wù)里將token寫(xiě)入內(nèi)存
Status StatusServiceImpl::GetChatServer(ServerContext* context, const GetChatServerReq* request, GetChatServerRsp* reply)
{std::string prefix("llfc status server has received : ");const auto& server = getChatServer();reply->set_host(server.host);reply->set_port(server.port);reply->set_error(ErrorCodes::Success);reply->set_token(generate_unique_string());insertToken(request->uid(), reply->token());return Status::OK;
}
接下來(lái)我們實(shí)現(xiàn)登陸驗(yàn)證服務(wù)
Status StatusServiceImpl::Login(ServerContext* context, const LoginReq* request, LoginRsp* reply)
{auto uid = request->uid();auto token = request->token();std::lock_guard<std::mutex> guard(_token_mtx);auto iter = _tokens.find(uid);if (iter == _tokens.end()) {reply->set_error(ErrorCodes::UidInvalid);return Status::OK;}if (iter->second != token) {reply->set_error(ErrorCodes::TokenInvalid);return Status::OK;}reply->set_error(ErrorCodes::Success);reply->set_uid(uid);reply->set_token(token);return Status::OK;
}
這樣當(dāng)GateServer訪問(wèn)StatusServer的Login服務(wù)做驗(yàn)證后,就可以將數(shù)據(jù)返回給QT前端了
客戶端處理登陸回包
QT 的客戶端TcpMgr收到請(qǐng)求后要進(jìn)行對(duì)應(yīng)的邏輯處理。所以我們?cè)赥cpMgr的構(gòu)造函數(shù)中調(diào)用initHandlers注冊(cè)消息
void TcpMgr::initHandlers()
{//auto self = shared_from_this();_handlers.insert(ID_CHAT_LOGIN_RSP, [this](ReqId id, int len, QByteArray data){qDebug()<< "handle id is "<< id << " data is " << data;// 將QByteArray轉(zhuǎn)換為QJsonDocumentQJsonDocument jsonDoc = QJsonDocument::fromJson(data);// 檢查轉(zhuǎn)換是否成功if(jsonDoc.isNull()){qDebug() << "Failed to create QJsonDocument.";return;}QJsonObject jsonObj = jsonDoc.object();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;}UserMgr::GetInstance()->SetUid(jsonObj["uid"].toInt());UserMgr::GetInstance()->SetName(jsonObj["name"].toString());UserMgr::GetInstance()->SetToken(jsonObj["token"].toString());emit sig_swich_chatdlg();});
}
并且增加處理請(qǐng)求
void TcpMgr::handleMsg(ReqId id, int len, QByteArray data)
{auto find_iter = _handlers.find(id);if(find_iter == _handlers.end()){qDebug()<< "not found id ["<< id << "] to handle";return ;}find_iter.value()(id,len,data);
}
用戶管理
為管理用戶數(shù)據(jù),需要?jiǎng)?chuàng)建一個(gè)UserMgr類(lèi),統(tǒng)一管理用戶數(shù)據(jù),我們這么聲明
#ifndef USERMGR_H
#define USERMGR_H
#include <QObject>
#include <memory>
#include <singleton.h>class UserMgr:public QObject,public Singleton<UserMgr>,public std::enable_shared_from_this<UserMgr>
{Q_OBJECT
public:friend class Singleton<UserMgr>;~ UserMgr();void SetName(QString name);void SetUid(int uid);void SetToken(QString token);
private:UserMgr();QString _name;QString _token;int _uid;
};#endif // USERMGR_H
簡(jiǎn)單實(shí)現(xiàn)幾個(gè)功能
#include "usermgr.h"UserMgr::~UserMgr()
{}void UserMgr::SetName(QString name)
{_name = name;
}void UserMgr::SetUid(int uid)
{_uid = uid;
}void UserMgr::SetToken(QString token)
{_token = token;
}UserMgr::UserMgr()
{}
詳細(xì)和復(fù)雜的管理后續(xù)不斷往這里補(bǔ)充就行了
登陸界面
登陸界面響應(yīng)TcpMgr返回的登陸請(qǐng)求,在其構(gòu)造函數(shù)中添加
//連接tcp管理者發(fā)出的登陸失敗信號(hào)connect(TcpMgr::GetInstance().get(), &TcpMgr::sig_login_failed, this, &LoginDialog::slot_login_failed);
并實(shí)現(xiàn)槽函數(shù)
void LoginDialog::slot_login_failed(int err)
{QString result = QString("登錄失敗, err is %1").arg(err);showTip(result,false);enableBtn(true);
}
到此完成了登陸的請(qǐng)求和響應(yīng),接下來(lái)要實(shí)現(xiàn)響應(yīng)登陸成功后跳轉(zhuǎn)到聊天界面。下一篇先實(shí)現(xiàn)聊天布局。