Chap12-TcpManager

Chap12-TcpManager

上一节,我们完成了状态服务器,状态服务器用来获取可用的负载小的服务器的信息。

那么我们的Qt前端发送了ID_USER_LOGIN信号之后,posthttp发送登陆请求给GateWayServer。这一步主要是进行验证作用,比如密码邮箱什么的是否正确。正确之后,我们服务器这里看到,还要去调用状态服务器的服务,获取服务器信息。

得到这里信息之后,我们的前端需要主动的连接,因此我们需要封装Tcp管理类,用于和服务器进行连接。

// tcpmanager.h
#ifndef TCPMANAGER_H
#define TCPMANAGER_H

#include "Properties/singleton.h"
#include <QObject>
#include <memory>
#include <QTcpSocket>
#include <QHash>
#include <functional>
class TcpManager
    : public QObject
    , public Singleton<TcpManager>
    , public std::enable_shared_from_this<TcpManager>
{
    Q_OBJECT
    friend class Singleton<TcpManager>;
public:
    ~TcpManager();

private:
    TcpManager();
    // 注册回调
    void initHandlers();
    // 连接
    void connections();
    // 处理单个数据->hash找回调处理
    void handleMessage(RequestType requestType,int len,QByteArray data);
private:
    QTcpSocket _socket;
    QString _host;
    uint16_t _port;
    QByteArray _buffer;
    bool _recv_pending;
    quint16 _msg_id;
    quint16 _msg_len;
    // 存放请求和对应的回调函数
    QHash<RequestType,std::function<void(RequestType,int,QByteArray)>>_handlers;
public slots:
    void do_tcp_connect(ServerInfo); // from LoginScreen::on_tcp_connect
    void do_send_data(RequestType requestType,QString data); // from TcpManager::to_send_data
signals:
    void on_connect_success(bool success); // to LoginScreen::do_connect_success
    void on_send_data(RequestType requestType,QString data); // to TcpManager::do_send_data
    void on_switch_interface();
    void on_login_failed(int);
};

#endif // TCPMANAGER_H


// .cpp
#include "tcpmanager.h"
#include <QAbstractSocket>
#include <QDataStream>
#include <QJsonObject>

TcpManager::~TcpManager() = default;

TcpManager::TcpManager()
    : _host("")
    , _port(0)
    , _recv_pending(false)
    , _msg_id(0)
    , _msg_len(0)
{
    initHandlers();
    connections();
}

void TcpManager::initHandlers()
{
    _handlers[RequestType::ID_CHAT_LOGIN_RSP] = [this](RequestType requestType,int len,QByteArray data){
        QJsonDocument jsonDoc = QJsonDocument::fromJson(data);
        if (jsonDoc.isNull()){
            qDebug() << "Error occured about Json";
            return;
        }
        QJsonObject jsonObj = jsonDoc.object();
        if (!jsonObj.contains("error")){
            int err = static_cast<int>(ErrorCodes::ERROR_JSON);
            qDebug() << "Login Failed,Error Is Json Parse Error " <<err;
            emit on_login_failed(err);
            return;
        }

        int err = jsonObj["error"].toInt();
        if (err != static_cast<int>(ErrorCodes::SUCCESS)){
            qDebug() << "Login Failed,Error Is " << err;
            emit on_login_failed(err);
            return;
        }

        emit on_switch_interface();
    };
}

void TcpManager::connections()
{
    // 连接成功
    connect(&_socket,&QTcpSocket::connected,[&](){
        qDebug() << "Connected to Server.";
        emit on_connect_success(true);
    });
    // 读取数据
    connect(&_socket,&QTcpSocket::readyRead,[&](){
        _buffer.append(_socket.readAll());
        QDataStream stream(&_buffer,QIODevice::ReadOnly);
        stream.setByteOrder(QDataStream::BigEndian);
        forever{
            if (!_recv_pending){
                if (_buffer.size() < static_cast<int>(sizeof(qint16)*2)){
                    return;
                }
                stream >> _msg_id >> _msg_len;
                _buffer.remove(0,4);
                qDebug() << "Message Id:" << _msg_id << " len:" << _msg_len ;
            }

            if (_buffer.size() < _msg_len){
                _recv_pending = true;
                return;
            }
            _recv_pending = false;
            QByteArray msgBody = _buffer.mid(_msg_len);
            qDebug() << "Receive body:" <<msgBody;
            _buffer.remove(0,_msg_len);
            handleMessage(RequestType(_msg_id),_msg_len,msgBody);
        }
    });
    // 错误
    connect(&_socket,&QTcpSocket::errorOccurred,[&](QTcpSocket::SocketError socketError){
        qDebug() << "Socket Error["<<socketError<< "]:" << _socket.errorString();
    });
    // 断开连接
    connect(&_socket,&QTcpSocket::disconnected,[&](){
        qDebug() << "Disconnected from server - " << _socket.peerAddress().toString() <<":"<<_socket.peerPort();
    });
    // 发送数据
    connect(this,&TcpManager::on_send_data,this,&TcpManager::do_send_data);
}

void TcpManager::handleMessage(RequestType requestType, int len, QByteArray data)
{
    auto it = _handlers.find(requestType);
    if ( it == _handlers.end()){
        qDebug() <<  "Not Found[" << static_cast<int>(requestType) << "] to handle";
        return;
    }
    it.value()(requestType,len,data);
}

void TcpManager::do_tcp_connect(ServerInfo si)
{
    qDebug() << "Connecting to server...";
    _host = si.host;
    _port = static_cast<uint16_t>(si.port.toInt());
    _socket.connectToHost(_host,_port);
}

void TcpManager::do_send_data(RequestType requestType, QString data)
{
    auto id =static_cast<uint16_t>(requestType);

    QByteArray dataBytes = data.toUtf8();

    quint16 len = static_cast<quint16>(data.size());

    QByteArray block;
    QDataStream out(&block,QIODevice::WriteOnly);

    out.setByteOrder(QDataStream::BigEndian);

    out << id << len;

    block.append(dataBytes);

    _socket.write(block);
}

也就是在得到GateWayServer发来的信息之后,我们在回调处理,处理的时候对于这个登陆操作,我们发送一个on_tcp_connect信号,这样tcpmanager的槽函数do_tcp_connect开始工作前去连接服务器:

void TcpManager::do_tcp_connect(ServerInfo si)
{
    qDebug() << "Connecting to server...";
    _host = si.host;
    _port = static_cast<uint16_t>(si.port.toInt());
    _socket.connectToHost(_host,_port);
}

这个tcp管理类是真正和聊天服务器进行持久化连接数据交换的实际执行者:

connect(this,&TcpManager::on_send_data,this,&TcpManager::do_send_data);


void TcpManager::do_send_data(RequestType requestType, QString data)
{
    auto id =static_cast<uint16_t>(requestType);

    QByteArray dataBytes = data.toUtf8();

    quint16 len = static_cast<quint16>(data.size());

    QByteArray block;
    QDataStream out(&block,QIODevice::WriteOnly);

    out.setByteOrder(QDataStream::BigEndian);

    out << id << len;

    block.append(dataBytes);

    _socket.write(block);
}

我们注册了这样的信号和槽,只要我们制作好要发送的数据,然后把要发送的请求类型的数据通过信号on_send_data交给槽函数处理,就能沟通了。

posted @ 2025-12-24 23:16  大胖熊哈  阅读(3)  评论(0)    收藏  举报