客户端登录逻辑

将网关服务器发送的数据通过信号传递给 TcpMgr 中定义的槽函数

void LoginDialog::initHttpHandlers()
{
    // 注册获取登录回包逻辑
    m_handlers.insert(ReqId::ID_LOGIN_USER, [this](QJsonObject jsonObj){
        int error = jsonObj["error"].toInt();
        if(error != ErrorCodes::SUCCESS){
            showTip(tr("参数错误"),false);
            enableBtn(true);
            return;
        }
        auto email = jsonObj["email"].toString();

        // 发送信号通知tcpMgr发送长链接
        ServerInfo si;
        si.Uid = jsonObj["uid"].toInt();
        si.Host = jsonObj["host"].toString();
        si.Port = jsonObj["port"].toString();
        si.Token = jsonObj["token"].toString();

        m_uid = si.Uid;
        m_token = si.Token;
        qDebug()<< "email is " << email << " uid is " << si.Uid <<" host is "
                << si.Host << " Port is " << si.Port << " Token is " << si.Token;
        emit sig_connect_tcp(si); // 发送登录成功信号,开始连接tcp服务器
    });
}

这是 sig_connect_tcp 对应的槽函数,调用套接字中的 connectToHost 传入获取的 hostport 连接 tcp 聊天服务器。

// 接收到登录回包后就会调用该函数
void TcpMgr::slot_tcp_connect(ServerInfo si)
{
    qDebug() << "receive tcp connect signal";
    // 尝试连接到服务器
    qDebug() << "Connecting to server...";
    m_host = si.Host;
    m_port = static_cast<uint16_t>(si.Port.toUInt());
    m_socket.connectToHost(si.Host, m_port);
}

TcpMgr 中的构造函数中连接,当成功与聊天服务器建立连接后发出 sig_con_success(true) 信号,意味着可以发送消息。

QObject::connect(&m_socket, &QTcpSocket::connected, [&]() {
           qDebug() << "Connected to server!";
           // 连接建立后发送消息
           emit sig_con_success(true);
});

将网关服务器传来的用户 uidtoken 写入 json 对象中,发送 sig_send_data(ReqId::ID_CHAT_LOGIN, jsonString) 信号发送 tcp 请求给聊天服务器。使用槽函数来发送数据主要是为了保证数据的有序,而且槽函数保证线程安全。

void LoginDialog::slot_tcp_con_finish(bool bsuccess)
{
   if(bsuccess){
      showTip(tr("聊天服务连接成功,正在登录..."), true);
      QJsonObject jsonObj;
      jsonObj["uid"] = m_uid;
      jsonObj["token"] = m_token;

      QJsonDocument doc(jsonObj);
      QByteArray jsonString = doc.toJson(QJsonDocument::Indented);

      // 发送tcp请求给chat server
      emit TcpMgr::Getinstance()->sig_send_data(ReqId::ID_CHAT_LOGIN, jsonString);

   }else{
      showTip(tr("网络异常"), false);
      enableBtn(true);
   }
}

在向 tcp 服务器发送数据时,就需要将数据设置为大端序。使用 tlv 消息格式,使用数据流发送数据,是为了保证数据的准确性。

在这里插入图片描述

// 槽函数中有一个队列可以保证数据有序,所以使用槽函数来发送数据
// Qt中槽函数默认是在发出信号的线程中回调(直连)
void TcpMgr::slot_send_data(ReqId reqId, QByteArray dataBytes)
{
    qDebug() << "向tcp发送数据 " << dataBytes;
    uint16_t id = reqId;

    // 计算长度(使用网络字节序转换)
    quint16 len = static_cast<quint16>(dataBytes.size());

    // 创建一个QByteArray用于存储要发送的所有数据
    QByteArray block;
    QDataStream out(&block, QIODevice::WriteOnly);

    // 设置数据流使用网络字节序(大端序)
    out.setByteOrder(QDataStream::BigEndian);

    // 写入ID和长度
    out << id << len;

    // 添加字符串数据
    block.append(dataBytes);

    // 发送数据
    m_socket.write(block);
}

判断大端序和小端序:

例如,如果当前系统为大端序,则输出结果为:

原始数据:12345678
当前系统为大端序
字节序为:12 34 56 78

如果当前系统为小端序,则输出结果为:

原始数据:12345678
当前系统为小端序
字节序为:78 56 34 12

在这里插入图片描述

如何区分本机字节序,可以通过判断低地址存储的数据是否为低字节数据,如果是则为小端,否则为大端,下面写一段代码讲述这个逻辑:

#include <iostream>
using namespace std;
// 判断当前系统的字节序是大端序还是小端序
bool is_big_endian() {
    int num = 1;
    if (*(char*)&num == 1) {
        // 当前系统为小端序
        return false;
    } else {
        // 当前系统为大端序
        return true;
    }
}
int main() {
    int num = 0x12345678;
    char* p = (char*)&num;
    cout << "原始数据:" << hex << num << endl;
    if (is_big_endian()) {
        cout << "当前系统为大端序" << endl;
        cout << "字节序为:";
        for (int i = 0; i < sizeof(num); i++) {
            cout << hex << (int)*(p + i) << " ";
        }
        cout << endl;
    } else {
        cout << "当前系统为小端序" << endl;
        cout << "字节序为:";
        for (int i = sizeof(num) - 1; i >= 0; i--) {
            cout << hex << (int)*(p + i) << " ";
        }
        cout << endl;
    }
    return 0;
}
  • 然后就是前端接收到聊天服务器发送过来的数据:

  • 先将获取的数据加入缓冲区,通过数据流读取数据,m_b_recv_pending变量判断是否接收完全,可以保证数据有序。

  • 读取完的数据会从缓冲区中移除,然后将获取的消息id和消息长度以及消息内容传入处理数据。

QObject::connect(&m_socket, &QTcpSocket::readyRead, [&]() {
       // 当有数据可读时,读取所有数据
       // 读取所有数据并追加到缓冲区
       m_buffer.append(m_socket.readAll());

       QDataStream stream(&m_buffer, QIODevice::ReadOnly);
       stream.setVersion(QDataStream::Qt_5_0);

       forever {
            // 先解析头部
           if(!m_b_recv_pending){
               // 检查缓冲区中的数据是否足够解析出一个消息头(消息ID + 消息长度)
               if (m_buffer.size() < static_cast<int>(sizeof(quint16) * 2)) {
                   return; // 数据不够,等待更多数据
               }

               // 预读取消息ID和消息长度,但不从缓冲区中移除
               stream >> m_message_id >> m_message_len;

               // 将buffer 中的前四个字节移除
               m_buffer = m_buffer.mid(sizeof(quint16) * 2);

               // 输出读取的数据
               qDebug() << "Message ID:" << m_message_id << ", Length:" << m_message_len;

           }

           // buffer剩余长读是否满足消息体长度,不满足则退出继续等待接受
           if(m_buffer.size() < m_message_len){
                m_b_recv_pending = true;
                return;
           }

           m_b_recv_pending = false;
           // 读取消息体
           QByteArray messageBody = m_buffer.mid(0, m_message_len);
           qDebug() << "receive body msg is " << messageBody ;

           m_buffer = m_buffer.mid(m_message_len);
           // 处理注册的函数
           handleMsg(ReqId(m_message_id), m_message_len, messageBody);
       }

});
sage_len);
           qDebug() << "receive body msg is " << messageBody ;

           m_buffer = m_buffer.mid(m_message_len);
           // 处理注册的函数
           handleMsg(ReqId(m_message_id), m_message_len, messageBody);
       }

});
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值