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模式生效了

相关推荐
郝学胜-神的一滴2 小时前
完全二叉树与堆底层原理深度剖析 | 手写C++大顶堆实现
java·开发语言·数据结构·c++·python·算法
专业机床数据采集2 小时前
基于 Wireshark 抓包逆向设备通信协议,并用 C# UDP协议跨平台 实现宝元数控程序列表读取、上传、下载和删除
网络·测试工具·wireshark·程序传输·宝元数控·dnc·数控程序传输
信也科技布道师2 小时前
从Istio 503 NC 错误深入理解 Mesh 路由全链路原理
java·服务器·网络
大白话_NOI2 小时前
【洛谷 P2678】 [NOIP2015 提高组] 跳石头 超详细题解
c++·算法
chase_my_dream3 小时前
LeGO-LOAM 详细源码流程解读
c++·计算机视觉·自动驾驶
A.零点3 小时前
【2个月 C 语言从入门到精通:零基础系统教程】第十二讲:深入了解指针(五)
c语言·开发语言·网络·笔记·visual studio
志栋智能3 小时前
从固定周期到动态触发:超自动化巡检的智能调度
运维·网络·自动化
插件开发4 小时前
vs2015 cuda c++ 线程号的计算详解
开发语言·c++·算法