将网关服务器发送的数据通过信号传递给
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 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*)#
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);
}
});