完善聊天室程序(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模式生效了
