先从头文件中了解整体逻辑
cpp
//来自groupchaatserver.h
#ifndef GROUPCHATSERVER_H
#define GROUPCHATSERVER_H
#include <QObject>
#include <QTcpServer>
#include<QTcpSocket>
class GroupChatServer : public QObject
{
Q_OBJECT
public:
explicit GroupChatServer(QObject *parent = nullptr);
//启动服务器
bool start(const QString& host,quint16 port);
public:
//服务器
void slot_acceptError(QAbstractSocket::SocketError socketError);
void slot_newConnection();
//客户端
void slot_disconnected();
void slot_errorOccurred(QAbstractSocket::SocketError socketError);
void slot_readyRead();
signals:
private:
QTcpServer *m_server;//服务器
QList<QTcpSocket*>m_sockets;//所有客户端,可以与服务器进行连接
};
#endif // GROUPCHATSERVER_H
这段代码定义了一个基于 Qt 框架 (使用 QTcpServer 和 QTcpSocket)的 群聊服务器 类 GroupChatServer。
它的核心设计逻辑是:作为一个 TCP 服务器,监听网络连接,维护所有连接进来的客户端列表,并处理数据的收发和连接状态的变化。
cpp
private:
QTcpServer *m_server; // 服务器
QList<QTcpSocket*> m_sockets; // 所有客户端
这是整个服务器的骨架:
m_server:这是监听者。它负责绑定 IP 和端口,等待外部的连接请求。它本身不传输数据,只负责"接待"。m_sockets:这是一个容器 。- 因为是"群聊"服务器,必须同时和多个客户端保持连接。
- 每当一个客户端成功连接,服务器就会创建一个对应的
QTcpSocket对象,并存入这个列表中。 - 群发逻辑的关键:当服务器收到某个客户端的消息时,会遍历这个列表,把消息转发给列表中的其他所有 Socket。
cpp
bool start(const QString& host, quint16 port);
- 启动服务器:用户调用这个函数并传入 IP 地址和端口号。
整个服务器的运行流程:
A. 连接建立阶段
void slot_newConnection():- 触发时机 :当
m_server检测到有新的客户端尝试连接时触发。
- 触发时机 :当
B. 数据通信阶段(群聊核心)
void slot_readyRead():- 触发时机:当某个客户端发送数据到达服务器时触发。
C. 连接断开阶段
void slot_disconnected():- 触发时机:当某个客户端断开连接时触发。
D. 异常处理
void slot_acceptError(...):处理服务器自身的监听错误(例如端口被占用)。void slot_errorOccurred(...):处理具体某个客户端通信过程中的错误(例如网络中断)。
下面是源文件中各函数功能的具体实现:
我们可以按照服务器的生命周期,将这段代码的逻辑分为四个关键部分来讲解:
1. 初始化与启动
cpp
GroupChatServer::GroupChatServer(QObject *parent)
: QObject{parent},
m_server(new QTcpServer(this))
{
//连接服务器信号
connect(m_server,&QTcpServer::acceptError,this,&GroupChatServer::slot_acceptError);
connect(m_server,&QTcpServer::newConnection,this,&GroupChatServer::slot_newConnection);
}
bool GroupChatServer::start(const QString& host,quint16 port){
if(m_server->isListening()){
qDebug()<<"Server already listening";
return false;
}
if(!m_server->listen((QHostAddress)host,port)){
qDebug()<<"Server listen failed:"<<m_server->errorString();
return false;
}
return true;
}
构造函数中,首先将服务器自身的"监听错误"和"新连接到来"这两个信号连接到了对应的槽函数。
启动 :start 函数非常直观,就是让服务器开始监听网络接口。如果端口被占用或 IP 无效,会打印错误信息并返回 false。
2. 新用户处理:连接建立 (slot_newConnection)
这是服务器"纳新"的逻辑:
cpp
void GroupChatServer::slot_newConnection()
{
//有未获取的连接
while(m_server->hasPendingConnections()){
//获取下一个有效连接
auto socket=m_server->nextPendingConnection();
//添加客户端到数组中
m_sockets.push_back(socket);
//接收来自客户端的信息
connect(socket,&QTcpSocket::disconnected,this,&GroupChatServer::slot_disconnected);
connect(socket,&QTcpSocket::errorOccurred,this,&GroupChatServer::slot_errorOccurred);
connect(socket,&QTcpSocket::readyRead,this,&GroupChatServer::slot_readyRead);
}
}
- 核心点 :这里不仅获取了连接,还建立了二次连接 。服务器本身只负责"接待",一旦接待成功(
socket拿到手),就让这个具体的客户端socket连接上服务器信号,去监听客户端的具体行为(比如发消息、掉线)。
3. 消息转发:群聊的核心 (slot_readyRead)
这是实现"群聊"功能的最关键代码:
cpp
void GroupChatServer::slot_readyRead()
{
auto sock=dynamic_cast<QTcpSocket*>(sender());
//读取数据
auto msg=sock->readAll();
//转发数据
for(auto& socket:m_sockets){
//不能转发给别人
if(socket==sock){
continue;
}
//转发给别人
socket->write(msg);
socket->flush();
}
}
- 逻辑 :当服务器收到任何一方的消息时,它会遍历
m_sockets列表。 - 广播机制 :它会把这条消息发送给列表里的每一个人 ,但是通过
if(socket == sock) continue;这一行,巧妙地排除了发送者本人,实现了群聊体验。
4. 用户离开:连接断开 (slot_disconnected)
cpp
void GroupChatServer::slot_disconnected()
{
//客户端断开连接,从数组中移除
auto socket=dynamic_cast<QTcpSocket*>(sender());
m_sockets.removeOne(socket);
}
- 逻辑 :利用
sender()函数,动态判断是谁断开了连接,然后直接从m_sockets列表中把它删掉。 - 结果 :下次再有
slot_readyRead转发消息时,这个已经离开的人就不会收到消息了。