依旧先从头文件看整体
cpp
#ifndef GROUPCHAT_H
#define GROUPCHAT_H
#include <QMainWindow>
#include<QTcpSocket>
QT_BEGIN_NAMESPACE
namespace Ui {
class GroupChat;
}
QT_END_NAMESPACE
class GroupChat : public QMainWindow
{
Q_OBJECT
public:
GroupChat(QWidget *parent = nullptr);
~GroupChat();
void start(const QString& host,quint16 port);
private slots:
void on_pushButton_clicked();
void slot_connected();
void slot_disconnected();
void slot_errorOccurred(QAbstractSocket::SocketError socketError);
void slot_readyRead();
private:
Ui::GroupChat *ui;
QTcpSocket*m_client;
};
#endif // GROUPCHAT_H
1. 类继承与 UI 结构
QMainWindow:表明这是一个主窗口程序。客户端需要显示聊天记录列表、输入框、发送按钮等,这些都属于 UI 部分。Ui::GroupChat *ui:这是 Qt 机制自动生成的界面类指针。通过ui->pushButton或ui->textEdit等方式,你可以在代码中操作在.ui设计文件里画好的界面元素。QTcpSocket *m_client:- 核心区别 :服务器端维护了一个
QList<QTcpSocket*>来管理很多用户,而客户端通常只需要一个QTcpSocket。 - 这个
m_client负责与服务器建立唯一的 TCP 连接,所有的消息收发都通过它完成。
- 核心区别 :服务器端维护了一个
2. 公共接口
void start(const QString& host, quint16 port);
- 启动连接 :类似于服务器的
start,这个函数用于让客户端尝试连接到指定的服务器 IP 和端口。
3. 私有槽函数
这里的槽函数分为两类:界面交互 和 网络状态。
A. 界面交互
void on_pushButton_clicked(); // <--- 自动关联的槽
- 命名规则 :在 Qt 中,如果你在 UI 设计器里给按钮起名叫
pushButton,Qt 会自动将它的点击信号连接到on_pushButton_clicked()这个函数上(前提是遵循特定的命名规则)。 - 逻辑推断 :这通常是"发送"按钮。用户点击后,代码会从输入框获取文本,并通过
m_client->write()发送给服务器。
B. 网络状态与数据处理
这四个槽与服务器端的逻辑非常相似,只是视角从"管理者"变成了"参与者":
-
void slot_connected():触发时机 :当
m_client成功连接到服务器时。 -
void slot_disconnected():触发时机:与服务器的连接断开时。
-
void slot_errorOccurred(...):触发时机:连接出错(如服务器没开、网络不通)。
-
void slot_readyRead():触发时机:收到服务器发来的数据时。
与服务器的总结对比
| 特性 | 服务器 (GroupChatServer) | 客户端 |
|---|---|---|
| 基类 | QObject (后台逻辑) |
QMainWindow (有界面) |
| 网络对象 | QTcpServer + 多个 QTcpSocket |
单个 QTcpSocket |
| 主要任务 | 监听、管理连接、转发消息 | 连接服务器、显示消息、发送用户输入 |
| readyRead 逻辑 | 读数据 -> 遍历列表转发给其他人 | 读数据 -> 追加显示到聊天窗口 |
下面是核心函数的具体实现:
1. 初始化与自动连接
cpp
GroupChat::GroupChat(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::GroupChat)
,m_client(new QTcpSocket(this))
{
ui->setupUi(this);
start("127.0.0.1",6666);
}
GroupChat::~GroupChat()
{
delete ui;
}
void GroupChat::start(const QString &host, quint16 port)
{
m_client->connectToHost(QHostAddress(host),port);
connect(m_client,&QTcpSocket::connected,this,&GroupChat::slot_connected);
connect(m_client,&QTcpSocket::disconnected,this,&GroupChat::slot_disconnected);
connect(m_client,&QTcpSocket::errorOccurred,this,&GroupChat::slot_errorOccurred);
connect(m_client,&QTcpSocket::readyRead,this,&GroupChat::slot_readyRead);
}
- 自动连接 :只要你启动客户端程序,它就会立刻去连接
127.0.0.1(本机)的 6666 端口。 - 信号绑定 :绑定了
readyRead等信号,意味着一旦服务器发来数据,或者连接状态改变,客户端就会自动响应。
2. 发送消息:UI + 网络 (on_pushButton_clicked)
这是用户点击"发送"按钮后的逻辑,这里有一个非常有意思的设计细节:
cpp
void GroupChat::on_pushButton_clicked()
{
auto msg = ui->sendedit->toPlainText(); // 1. 获取输入框内容
auto dt = QDateTime::currentDateTime().toString("yyyy-mm-dd hh:mm:ss");
// 2. 【关键步骤】立刻显示在自己的屏幕上
ui->showedit->insertPlainText(QString("[%1]:%2").arg(dt).arg(msg+"\n"));
// 3. 发送给服务器
m_client->write(msg.toUtf8());
}
为什么要自己在界面上显示一遍?
因为服务器收到消息后,只会转发给其他人 ,而不会把消息发回给发送者。所以,客户端必须在点击发送的瞬间,把自己说的话直接"贴"到聊天记录显示框(showedit)里。如果不这样做,你自己说的话是永远看不到的。
3. 接收消息:显示他人发言 (slot_readyRead)
cpp
void GroupChat::slot_readyRead()
{
auto msg = m_client->readAll(); // 读取服务器转发来的数据
auto dt = QDateTime::currentDateTime().toString("yyyy-mm-dd hh:mm:ss");
// 将别人的消息显示在屏幕上
ui->showedit->insertPlainText(QString("[%1]:%2").arg(dt).arg(msg+"\n"));
}
- 逻辑 :当别人在群里说话时,服务器会把消息转发给这个客户端。
readyRead触发,读取数据并追加显示到界面。