客户端登录逻辑
将网关服务器发送的数据通过信号传递给
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传入获取的host和port连接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);
});
将网关服务器传来的用户
uid和token写入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 serveremit 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*)#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);}});
