64、完善聊天室程序(TLV拓展)---------网络编程

完善聊天室程序(TLV拓展)

服务器TLV模式

首先我们服务器收到的消息可能是不完整的,所以就要存储从每个客户端socket收到的信息。

所以将Chatserver中的客户端集合,由下方

cpp 复制代码
//客户端连接的集合
QSet<QTcpSocket*> clients;

改为下方, 我们通过map存储每个socket对应的接受缓冲区

cpp 复制代码
// 存储每个客户端的接收缓冲区
QMap<QTcpSocket*, QByteArray> _bufferMap;

我们修改ChatServer的读取功能

cpp 复制代码
//读取客户端发送的数据
void ChatServer::slot_ready_read()
{
    //将发送信号的对象转化为QTcpSocket类型
    QTcpSocket* clientSocket = qobject_cast<QTcpSocket*>(sender());
    if (!clientSocket)
        return;

    //读取信息
    QByteArray data = clientSocket->readAll();
    //存储收到的数据
    _bufferMap[clientSocket].append(data);
    //处理数据
    processData(clientSocket);
}

在读取函数中我们将读到的数据先存起来,然后调用processData进行处理

cpp 复制代码
void ChatServer::processData(QTcpSocket* client)
{
    //获取socket对应的数据
    QByteArray& buffer = _bufferMap[client];

    while (buffer.size() >= 4) { // 至少有头部长度
        // 读取头部
        QByteArray head = buffer.left(4);
        QDataStream ds(head);
        ds.setByteOrder(QDataStream::BigEndian);
        quint32 bodyLength;
        ds >> bodyLength;

        if (buffer.size() >= 4 + bodyLength) {
            // 完整的消息体已经接收
            QByteArray body = buffer.mid(4, bodyLength);
            buffer = buffer.mid(4 + bodyLength);

            QString message = QString::fromUtf8(body);
            qDebug() << "Received message:" << message;

            //通知主界面添加消息
            emit sig_append_msg(message);

            // 广播消息给所有客户端
            broadcastMessage(body, bodyLength, client);
        } else {
            // 消息体未完全接收,等待更多数据
            break;
        }
    }
}

因为修改了clients从set到map的结构,所以广播函数要做下修改

cpp 复制代码
//广播消息
void ChatServer::broadcastMessage(QByteArray body, int bodyLength, QTcpSocket *client)
{
    // 广播消息给所有客户端
    QByteArray response;
    // 写数据流绑定到字节数组中,所有向流写入的数据都会写入到response数组
    QDataStream responseStream(&response, QIODevice::WriteOnly);
    // 设置大端模式
    responseStream.setByteOrder(QDataStream::BigEndian);
    // 写入长度
    responseStream << bodyLength;
    // 追加包体内容
    response.append(body);
    // 遍历
    foreach (QTcpSocket* clientSocket, _bufferMap.keys()) {
        // 不发送给源客户端
        if (clientSocket != client) {
            clientSocket->write(response);
        }
    }
}

修改其他几处处理socket的逻辑

cpp 复制代码
//获取新来的连接
void ChatServer::incomingConnection(qintptr socketDescriptor)
{
    QTcpSocket* clientSocket = new QTcpSocket(this);
    if (!clientSocket->setSocketDescriptor(socketDescriptor)) {
        qDebug() << "Failed to set socket descriptor";
        clientSocket->deleteLater();
        return;
    }

    //将客户端加入集合
    _bufferMap.insert(clientSocket,QByteArray());

    //连接读取信息信号
    connect(clientSocket, &QTcpSocket::readyRead, this, &ChatServer::slot_ready_read);
    //连接客户端断开信号
    connect(clientSocket, &QTcpSocket::disconnected, this, &ChatServer::slot_disconnect);

    //打印对端地址
    qDebug() << "New client connected:" << clientSocket->peerAddress().toString();
}

更新断开连接

cpp 复制代码
//获取客户端断开连接槽函数
void ChatServer::slot_disconnect()
{
    //获取发送者转为QTcpSocket类型
    QTcpSocket* clientSocket = qobject_cast<QTcpSocket*>(sender());
    if (!clientSocket)
        return;

    //获取对端地址
    auto address = clientSocket->peerAddress().toString();
    //从客户端移除
    _bufferMap.remove(clientSocket);
    //删除socket
    clientSocket->deleteLater();
    //推广离开信息
    QString leftMessage = QString("%1 left the chat.").arg(address);
    auto byteArray = leftMessage.toUtf8();
    broadcastMessage(byteArray, byteArray.size(), clientSocket);
    //发送消息通知主界面显示离开信息
    emit sig_append_msg(leftMessage);
    qDebug() << "Client disconnected:" << address;
}

到此为止,服务器改造完毕。

客户端TLV模式

客户端TcpClient新增成员_buffer用来缓冲收到的数据

cpp 复制代码
//缓存收到的数据
QByteArray _buffer;

修改发送数据的函数

cpp 复制代码
//发送数据槽函数
void TcpClient::slot_send_msg(const QString &msg)
{
    //如果连接异常则直接返回
    if(_socket->state() != QAbstractSocket::ConnectedState){
        QMessageBox::information(nullptr,"提示消息","断开连接无法发送");
        return;
    }

    //将msg转化为字节数组
    QByteArray body = msg.toUtf8();
    //获取body的长度
    quint32 bodyLength = body.size();

    //创建字节数组
    QByteArray data;
    //绑定字节数组
    QDataStream stream(&data, QIODevice::WriteOnly);
    //设置大端模式
    stream.setByteOrder(QDataStream::BigEndian);
    //写入长度
    stream << bodyLength;
    //写入包体
    data.append(body);

    //发送消息
     _socket->write(data);
}

收到数据后先缓存起来,因为数据可能没收完整,或者多个包粘连在一起

cpp 复制代码
//接受服务器发送的信息
void TcpClient::slot_ready_read()
{
    //读取所有数据
    QByteArray data = _socket->readAll();

    //将数据缓存起来
    _buffer.append(data);

    //处理收到的数据
    processData();
}

实现数据处理

cpp 复制代码
void TcpClient::processData()
{
    while(_buffer.size() >= 4){
        //先取出四字节头部
        auto head_byte = _buffer.left(4);
        QDataStream stream(head_byte);
        //设置为大端模式
        stream.setByteOrder(QDataStream::BigEndian);
        //读取长度
        quint32 body_length;
        stream >> body_length;
        if(_buffer.size() >= 4+body_length){
            //完整的消息体已经接受
            QByteArray body = _buffer.mid(4,body_length);
            //去掉完整的消息包
            _buffer = _buffer.mid(4+body_length);
            //发送消息给MainWindow显示
            emit sig_append_msg(QString::fromUtf8(body));
        }else{
            //消息未完全接受,所以中断
            break;
        }
    }
}

再次运行客户端和服务器,可以看到TLV模式生效了

相关推荐
I'm a winner5 小时前
【IP核】 Xilinx FPGA LVDS 高速接口,含验证工程与板级测试用例
tcp/ip·fpga开发·测试用例
王老师青少年编程6 小时前
2026年6月GESP真题及题解(C++一级):交税
c++·题解·真题·gesp·一级·2026年6月·交税
Piko6148 小时前
H3C IRF2 堆叠实战:打造高可靠核心交换网络
运维·网络·笔记
茯苓gao10 小时前
嵌入式开发笔记:CANopen相关移位运算与通信协议术语详解
网络·嵌入式硬件·学习·信息与通信
万联WANFLOW10 小时前
SD-WAN 控制平面高可用怎么做?SDWAN 控制器挂了,全网会发生什么
运维·网络·分布式·架构
疋瓞10 小时前
python和C++对比(1)_数据类型和数据结构
数据结构·c++·python
C语言小火车11 小时前
C++ 堆排序深度精讲:基于完全二叉树的选择排序进化,最坏情况 O(n log n) 的稳定王者
开发语言·c++·算法·排序算法·堆排序
酱学编程11 小时前
【从零到一实现一个 AI Agent 框架 · 第四篇】04. 任务规划:拆解复杂目标 -
服务器·网络·数据库·人工智能
ALex_zry12 小时前
C++26 std::complex 结构化绑定详解:auto [re, im] = c
c语言·开发语言·c++
风行南方12 小时前
密码学之分组密码
网络·密码学