client
cpp
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
, socket(new QTcpSocket(this))
{
ui->setupUi(this); // 设置 UI 界面
// 控件初始状态设置为禁用,防止未连接时误操作
ui->sendBtn->setEnabled(false);
ui->sendEdit->setEnabled(false);
ui->brokenBtn->setEnabled(false);
// 信号与槽的连接
connect(socket, &QTcpSocket::connected, this, &Widget::connected_slot);
connect(socket, &QTcpSocket::readyRead, this, &Widget::readyRead_slot);
//如果成功断开与服务器的连接,那么客户端就会自动发射一个disconnected的信号
//我们就可以将该信号连接到自定义的槽函数,。由于只需连接一次,所以连接函数写在构造函数中。
connect(socket, &QTcpSocket::disconnected, this, &Widget::disconnected_slot);
}
Widget::~Widget()
{
delete ui;
delete socket; // 释放socket的内存
}
// 当连接成功时的处理函数
void Widget::connected_slot()
{
userName = ui->userEdit->text(); // 读取用户输入的用户名
QString msg = userName + ":进入聊天室"; // 构建进入聊天室的欢迎消息
// 将消息写入 socket,并发送到服务器
socket->write(msg.toLocal8Bit());
socket->flush(); // 确保所有数据都被发送
// 显示连接成功的信息
QMessageBox::information(this,"提示","已连接到服务器");
// 启用发送控件,禁用连接控件
ui->sendEdit->setEnabled(true);
ui->sendBtn->setEnabled(true);
ui->brokenBtn->setEnabled(true);
ui->userEdit->setEnabled(false);
ui->ipEdit->setEnabled(false);
ui->portEdit->setEnabled(false);
ui->connectBtn->setEnabled(false);
}
// 当 socket 准备读数据时的处理函数
void Widget::readyRead_slot()
{
// 读取 socket 中的所有数据
QByteArray msg = socket->readAll();
qDebug() << msg;
//将数据放入ui界面上
ui->listWidget->addItem(QString::fromLocal8Bit(msg));
}
//disconnected()信号对应的槽函数
void Widget::disconnected_slot()
{
//组件可用的相关设置
ui->sendEdit->setEnabled(false);
ui->sendBtn->setEnabled(false);
ui->brokenBtn->setEnabled(false);
ui->userEdit->setEnabled(true);
ui->ipEdit->setEnabled(true);
ui->portEdit->setEnabled(true);
ui->connectBtn->setEnabled(true);
}
//连接服务器按钮对应的槽函数
void Widget::on_connectBtn_clicked()
{
//获取ui界面上的ip和端口号
QString ip = ui->ipEdit->text();
quint16 port = ui->portEdit->text().toUShort(); // 转换为 quint16 //字符串转换成整型
socket->connectToHost(ip,port);
}
//发送按钮对应的槽函数
void Widget::on_sendBtn_clicked()
{
//获取ui界面的信息
QString msg = ui->sendEdit->text();
msg = userName + ":" + msg;
//发送给服务器
socket->write(msg.toLocal8Bit());
socket->flush();
//清空行编辑器
ui->sendEdit->clear();
}
//断开连接按钮对应的槽函数
void Widget::on_brokenBtn_clicked()
{
//告诉服务器 我走了
QString msg = userName + ":离开聊天室";
//发送给服务器
socket->write(msg.toLocal8Bit());
socket->flush();
//断开与服务器的连接
socket->disconnectFromHost(); // 断开连接
//如果成功断开与服务器的连接,那么客户端就会自动发射一个disconnected的信号
//我们就可以将该信号连接到自定义的槽函数,。由于只需连接一次,所以连接函数写在构造函数中。
}
h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QTcpServer> // 注意:QTcpServer 通常用于服务器端,这里应使用 QTcpSocket
#include <QTcpSocket> // 客户端类
#include <QMessageBox> // 消息对话框
#include <QDebug>
#include <QByteArray>
#include <QIODevice>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
signals:
void disconnected();
// 私有槽函数,由信号触发执行
private slots:
// 当 QTcpSocket 连接成功时调用
void connected_slot();
// 当 QTcpSocket 准备读取数据时调用
void readyRead_slot();
void disconnected_slot();
// 公共槽函数,由 UI 控件触发执行
public slots:
void on_connectBtn_clicked();
void on_sendBtn_clicked();
void on_brokenBtn_clicked();
private:
Ui::Widget *ui;
QTcpSocket *socket; // QTcpSocket 对象,用于客户端连接
QString userName; // 用户名
};
#endif // WIDGET_H
服务器
.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QTcpServer>
#include <QMessageBox>
#include <QDebug>
#include <QTcpSocket>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
public slots:
void newConnection_slot();
void readyRead_slot();
private slots:
void on_startBtn_clicked();
private:
Ui::Widget *ui;
QTcpServer *server;
QList<QTcpSocket *> socketList;
};
#endif // WIDGET_H
.cpp
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
, server(new QTcpServer(this))
{
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
// newConnection信号对应槽函数
void Widget::newConnection_slot()
{
qDebug() << "有新的客户端连接····";
// 获取最新客户端套接字
// virtual QTcpSocket *nextPendingConnection();
// 返回值:客户端套接字————QTcpSocket类
QTcpSocket *s = server->nextPendingConnection();
// 将获取的客户端套接字放入容器
socketList.push_back(s);
//
connect(s,&QTcpSocket::readyRead,this,&Widget::readyRead_slot);
}
void Widget::readyRead_slot()
{
for (int i = 0; i < socketList.count(); i++) {
if (socketList.at(i)->state() == 0)
{
socketList.removeAt(i);
}
}
for (int i = 0; i < socketList.count(); i++) {
// 判断哪个客户端有数据待读
if (socketList.at(i)->bytesAvailable() != 0)
{
// 读取数据
QByteArray msg = socketList.at(i)->readAll();
//放入ui界面
ui->listWidget->addItem(QString::fromLocal8Bit(msg));
// 广播所有客户端
for (int j = 0; j < socketList.count(); j++) {
socketList.at(j)->write(msg);
}
}
}
}
//启动服务器按钮的槽函数
void Widget::on_startBtn_clicked()
{
// ui界面的端口号
quint16 port = ui->portEdit->text().toUInt();
// 服务器监听连接
// bool listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0);
// const QHostAddress &address = QHostAddress::Any,
// quint16 port = 0
if( server->listen(QHostAddress::Any,port) )
{
QMessageBox::information(this,"","启动服务器成功!");
}
else
{
QMessageBox::information(this,"","启动服务器失败!");
return;
}
connect(server,&QTcpServer::newConnection,this,&Widget::newConnection_slot);
}