一个简单实现群聊功能的服务器端和客户端端。
客户端端代码:
widget.h
#ifndef WIDGET_H #define WIDGET_H #include <QWidget> #include <QTcpSocket> //客户端类 #include <QMessageBox> #include <QVBoxLayout> QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACE class Widget : public QWidget { Q_OBJECT public: Widget(QWidget *parent = nullptr); ~Widget(); private slots: void on_connectBtn_clicked(); void on_sendBtn_clicked(); void on_disconnectBtn_clicked(); public slots: void connected_slot();//connected信号对应的槽函数的声明 void readyRead_slot();//readyRead信号对应的槽函数的声明 void disconnected_slot();//disconnected信号对应的槽函数的声明 private: Ui::Widget *ui; //实例化一个客户端指针对象 QTcpSocket *socket; //定义一个存储用户的变量 QString userName; }; #endif // WIDGET_H
widget.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->msgEdit->setEnabled(false); ui->disconnectBtn->setEnabled(false); ui->sendBtn->setEnabled(false); //如果成功连接到服务器,那么客户端就会自动发射一个connected()信号 //我们就可以将该信号连接到自定义的槽函数中处理逻辑代码,由于只需要连接一次,所以写在构造函数中 connect(socket,&QTcpSocket::connected,this,&Widget::connected_slot); //程序运行至此,说明客户端与服务器建立连接,如果服务器发来数据,那么客户端会自动发送readyRead()信号 // 我们就可以将该信号连接到自定义的槽函数,读取数据。由于秩序要连接一次,所以连接函数写在构造函数中 connect(socket, &QTcpSocket::readyRead,this, &Widget::readyRead_slot); //如果成功断开与服务器的连接,那么客户端就会自动发送一个disconnected的信号 //我们就可以将该信号连接到自定义的槽函数,读取数据。由于秩序要连接一次,所以连接函数写在构造函数中 connect(socket, &QTcpSocket::disconnected, this, &Widget::disconnected_slot); } Widget::~Widget() { delete ui; } //连接服务器按钮对应的槽函数 void Widget::on_connectBtn_clicked() { //获取ui界面上的ip和端口号 QString ip = ui->ipEdit->text(); quint16 port = ui->portEdit->text().toUInt(); //让客户端连接服务器 //virtual void connectToHost(const QString &hostName, quint16 port, OpenMode mode = ReadWrite, NetworkLayerProtocol protocol = AnyIPProtocol); //参数1:服务器的ip地址 //参数2:端口号 socket->connectToHost(ip,port); //如果成功连接到服务器,那么客户端就会自动发射一个connected()信号 //我们就可以将该信号连接到自定义的槽函数中处理逻辑代码,由于只需要连接一次,所以写在构造函数中 } //connected信号对应的槽函数 void Widget::connected_slot() { //告诉服务器 我来了 userName = ui->userNameEdit->text(); QString msg =userName + ":进入聊天室"; //将信息发送服务器 socket->write(msg.toLocal8Bit()); //连接服务器成功 QMessageBox::information(this,"","连接服务器成功"); //组件可用的相关设置 ui->msgEdit->setEnabled(true); ui->sendBtn->setEnabled(true); ui->disconnectBtn->setEnabled(true); ui->userNameEdit->setEnabled(false); ui->ipEdit->setEnabled(false); ui->portEdit->setEnabled(false); ui->connectBtn->setEnabled(false); //程序运行至此,说明客户端与服务器建立连接,如果服务器发来数据,那么客户端会自动发送readyRead()信号 // 我们就可以将该信号连接到自定义的槽函数,读取数据。由于秩序要连接一次,所以连接函数写在构造函数中 } //readyRead信号对应的槽函数 void Widget::readyRead_slot() { static int row=0; //读取服务器发来的数据 QByteArray msg = socket->readAll(); //拆分出用户名 QStringList list = (QString::fromLocal8Bit(msg)).split(":"); //将数据放入ui界面上 ui->listWidget->addItem(QString::fromLocal8Bit(msg)); //根据用户名设置显示位置 if(list[0]==userName) { //是本人的消息 靠右显示 ui->listWidget->item(row)->setTextAlignment(Qt::AlignRight); }else { //不是本人的消息 靠左显示 ui->listWidget->item(row)->setTextAlignment(Qt::AlignLeft); } if(list[1]=="离开聊天室"||list[1]=="进入聊天室") { //居中显示 ui->listWidget->item(row)->setTextAlignment(Qt::AlignCenter); } row++; } //disconnected信号对应的槽函数 void Widget::disconnected_slot() { //组件可用的相关设置 ui->msgEdit->setEnabled(false); ui->sendBtn->setEnabled(false); ui->disconnectBtn->setEnabled(false); ui->userNameEdit->setEnabled(true); ui->ipEdit->setEnabled(true); ui->portEdit->setEnabled(true); ui->connectBtn->setEnabled(true); } //发送按钮对应的槽函数 void Widget::on_sendBtn_clicked() { //获取ui界面的信息 QString msg = ui->msgEdit->text(); msg = userName + ":" + msg; //发送给服务器 socket->write(msg.toLocal8Bit()); //清空行编辑器 ui->msgEdit->clear(); } //断开按钮对应的槽函数 void Widget::on_disconnectBtn_clicked() { //告诉服务器 我走了 QString msg = userName + ":离开聊天室"; //发送给服务器 socket->write(msg.toLocal8Bit()); //断开与服务器的连接 //virtual void disconnectFromHost(); socket->disconnectFromHost(); //如果成功断开与服务器的连接,那么客户端就会自动发送一个disconnected的信号 //我们就可以将该信号连接到自定义的槽函数,读取数据。由于秩序要连接一次,所以连接函数写在构造函数中 }
服务器端代码
widget.h
#ifndef WIDGET_H #define WIDGET_H #include <QWidget> #include <QTcpSocket> //客户端类 #include <QMessageBox> #include <QVBoxLayout> QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACE class Widget : public QWidget { Q_OBJECT public: Widget(QWidget *parent = nullptr); ~Widget(); private slots: void on_connectBtn_clicked(); void on_sendBtn_clicked(); void on_disconnectBtn_clicked(); public slots: void connected_slot();//connected信号对应的槽函数的声明 void readyRead_slot();//readyRead信号对应的槽函数的声明 void disconnected_slot();//disconnected信号对应的槽函数的声明 private: Ui::Widget *ui; //实例化一个客户端指针对象 QTcpSocket *socket; //定义一个存储用户的变量 QString userName; }; #endif // WIDGET_H
widget.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; } //启动服务器按钮对应的槽函数 void Widget::on_startBtn_clicked() { //获取ui界面上的端口号 quint16 port = ui->portEdit->text().toUInt(); //服务器设置监听 //bool listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0) //参数1:监听的主机,可以是指定主机,也可以任意 //参数2:监听的端口号,可以是指定,也可以是系统提供 //监听成功,返回true,否则false if( server->listen(QHostAddress::Any,port) ) { //监听成功 QMessageBox::information(this,"","启动服务器成功!"); }else { //监听失败 QMessageBox::critical(this,"提示","启动服务器失败!"); return; } //此时如果有客户端发来连接请求,那么服务器端就会自动发射一个newconnection信号 //我们就可以将该信号连接到自定义的槽函数中,获取客户端的套接字 connect(server, &QTcpServer::newConnection, this, &Widget::newconnection_slot); } //自定义的newconnection信号对应的槽函数定义 void Widget::newconnection_slot() { //有新的客户端连接 qDebug() << "有新的客户端连接。。"; //获取最新连接的客户端套接字 //virtual QTcpSocket *nextPendingConnection(); //返回值:客户端的套接字 (QTcpSocket:客户端) QTcpSocket *s = server->nextPendingConnection(); //将获取的客户端放入客户端容器中 scoketList.push_back(s); //程序运行至此,说明客户端和服务器成功建立了连接, //如果客户端发来数据,那么客户端就会自动发射一个readyRead信号 //我们就可以将该信号连接到自定义的槽函数中,读取发来的数据 connect(s,&QTcpSocket::readyRead,this,&Widget::readyRead_slot); } //自定义的readyRead信号对应的槽函数实现 void Widget::readyRead_slot() { //读取客户端发来的数据 //遍历客户端容器,移除无效客户端 for (int i=0;i<scoketList.count();i++) { //判断客户端和客户端的连接状态 //SocketState state() const; //客户端和服务器未连接的枚举值是0 if(scoketList.at(i)->state()==0) { //移除无效客户端 scoketList.removeAt(i); } } //遍历所有在线的客户端,找出待读数据的客户端 for (int i=0;i<scoketList.count();i++) { //判断哪个客户端有数据待读 //qint64 bytesAvailable() const override; if( scoketList.at(i)->bytesAvailable() !=0 ) //表示有数据待读 { //读取数据 QByteArray msg = scoketList.at(i)->readAll();//返回值是字节数组 //将读取到的数据放入ui界面上 ui->listWidget->addItem(QString::fromLocal8Bit(msg)); //将数据广播给所有客户端 for (int j=0; j<scoketList.count(); j++) { scoketList.at(j)->write(msg); } } } }
运行效果
思维导图