目录
1.界面预览
1.1服务端界面
1.2客户端界面
- 控件就不多介绍了
2.代码分析
2.1开发流程
2.2QTcpServer QTcpSocket 类
既然要万物互联了学习网络的知识就变的至关重要了。
QTcpServer类提供了一个基于TCP的服务器。这个类使得接受传入的TCP连接成为可能。
QTcpSocket 类提供了一个TCP套接字。
QTcpSocket 是 QAbstractSocket 的一个方便子类,允许你建立TCP连接并传输数据流
调用 listen() 来让服务器开始监听传入的连接。每当客户端连接到服务器时,就会发射 newConnection() 信号。
调用 nextPendingConnection() 来接受等待中的连接作为一个已连接的 QTcpSocket。这个函数会返回一个指向处于 QAbstractSocket::ConnectedState 状态的 QTcpSocket 指针,你可以用它与客户端进行通信。
如果发生错误,serverError() 返回错误类型,可以调用 errorString() 来获得发生错误的人类可读描述。
当监听连接时,服务器正在监听的地址和端口可以通过 serverAddress() 和 serverPort() 获得。
调用 close() 会使 QTcpServer 停止监听传入的连接。
尽管 QTcpServer 主要设计用于与事件循环一起使用,但也可以在没有事件循环的情况下使用。在这种情况下,你必须使用 waitForNewConnection(),它会阻塞直到连接可用或超时。
QTcpServer *tcpServer;
tcpServer = new QTcpServer(this);//QTcpServer 类提供基于 TCP 的服务器
//监听客户端的连接
void Widget::on_pushButtonStartList_clicked()
{
//监听指定的地址和端口
if(!tcpServer->listen(QHostAddress(ui->comboBoxaddr->currentText()),ui->lineEditport->text().toInt())){
QMessageBox msgBox;
msgBox.setWindowTitle("错误");
msgBox.setText("端口号可能被占用");
msgBox.exec();
return;
}
//当监听按键按下后按键的状态
ui->pushButtonStartList->setEnabled(false);
ui->pushButtonStopList->setEnabled(true);
ui->pushButtonDuankai->setEnabled(true);
}
connect(tcpServer, SIGNAL(newConnection()), this, SLOT(on_ClientTcpConnect_slot()));
//有客户端连接
void Widget::on_ClientTcpConnect_slot()
{
//检查是否有挂起的连接请求等待被接受
if(tcpServer->hasPendingConnections()){
QTcpSocket *tcpSocket = tcpServer->nextPendingConnection(); //将挂起的连接接受为已连接的QTcpSocket,通过该对象可以与客户端通信。
ui->textEditRev->insertPlainText("cilent addr:" + tcpSocket->peerAddress().toString() + "\n" +
"cilent port:" + QString::number(tcpSocket->peerPort()));
connect(tcpSocket, SIGNAL(readyRead()), this, SLOT(on_readCilentData_Slot()));//读取客户端数据的信号与槽
//connect(tcpSocket, SIGNAL(disconnected()), this, SLOT(on_ClientDisconnect_Slot()));//客户端断开的信号与槽1
connect(tcpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this,
SLOT(on_StateChanged_Slot(QAbstractSocket::SocketState)));//客户端断开的信号与槽2
ui->comboBoxClientPort->addItem(QString::number(tcpSocket->peerPort()));//将获取的端口号添加到QComboBox中
ui->comboBoxClientPort->setCurrentText(QString::number(tcpSocket->peerPort()));//设置为当前连接的端口号
//如果有新的客户端连接就启用发送按键
if(!ui->pushButtonSend->isEnabled()){
ui->pushButtonSend->setEnabled(true);
}
}
}
- 这样就可以连接到客户端了
2.3数据的接收和发送
qobject_cast 函数,它是 Qt 提供的一个用于执行安全的类型转换的函数。在这里,它尝试将 sender() 返回的指针转换为 QTcpSocket 类型的指针。
sender() 返回发送信号的对象的指针。
//读取客户端数据
void Widget::on_readCilentData_Slot()
{
QTcpSocket *tmpreadBuf = qobject_cast<QTcpSocket *>(sender());//获取发送信号对象的指针
QByteArray readBuf = tmpreadBuf->readAll();//接收客户端的数据
ui->textEditRev->insertPlainText("\n" + readBuf);
ui->textEditRev->moveCursor(QTextCursor::End);//跟随光标移动
ui->textEditRev->ensureCursorVisible();//确保光标是可视的
}
findChildren<QTcpSocket *>() 函数,它是 QObject 类的成员函数。这个函数会在 tcpServer 对象的子对象中查找并返回所有类型为 QTcpSocket 的子对象的指针列表。(这里我们要和所有的客户端进行通信所以要获取全部的QTcpSocket)
//发送按键
void Widget::on_pushButtonSend_clicked()
{
QList<QTcpSocket *> revClients = tcpServer->findChildren<QTcpSocket *>();//获取到客户端并添加到数组里
//检查有无客户端连接 就是判断一下QComboBox是否为空
if(revClients.isEmpty()){
QMessageBox msgBox;
msgBox.setWindowTitle("错误");
msgBox.setText("无连接");
msgBox.exec();
return;
}
if(ui->comboBoxClientPort->currentText() != "Client All"){
QString tmpSendstr = ui->comboBoxClientPort->currentText();//获取当前的客户端端口号
for(QTcpSocket *tmp : revClients){
//通过比较当前和客户端的端口号进行数据的发送
if(QString::number(tmp->peerPort()) == tmpSendstr){
tmp->write(ui->lineEditSend->text().toStdString().c_str());//向客户端发送数据
}
}
}else{
for(QTcpSocket *tmprevClients : revClients){
tmprevClients->write(ui->lineEditSend->text().toStdString().c_str());
}
}
}
2.4添加客户端的端口号
- 重写事件处理函数来处理特定的事件
void QMyComBoBox::mousePressEvent(QMouseEvent *e)
{
//检测鼠标左键按下
if(e->button() == Qt::LeftButton){
//发送自定义的信号
emit falshComboBox();
}
//让程序可以继续执行
QComboBox::mousePressEvent(e);
}
connect(ui->comboBoxClientPort, &QMyComBoBox::falshComboBox,this,
&Widget::on_ClientFalshProtComboBox_Slot);//刷新QComboBox
//鼠标左键点击后可以刷新QComboBox
void Widget::on_ClientFalshProtComboBox_Slot()
{
ui->comboBoxClientPort->clear();
QList<QTcpSocket *> getClientsPort = tcpServer->findChildren<QTcpSocket *>();//获取到客户端并添加到数组里
for(QTcpSocket *tmpgetClientsPort : getClientsPort){
if(tmpgetClientsPort != nullptr)
ui->comboBoxClientPort->addItem(QString::number(tmpgetClientsPort->peerPort()));//将端口添加到QComboBox中
}
ui->comboBoxClientPort->addItem("Client All");
}
- 剩下的断开和停止监听比较简单就不多介绍了
2.5服务端完整代码
2.5.1widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QTcpServer>
#include "qmycombobox.h"
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
//创建TCP服务器的对象
QTcpServer *tcpServer;
public slots:
//有客户端连接
void on_ClientTcpConnect_slot();
//读取客户端数据
void on_readCilentData_Slot();
//客户端断开连接的实现1
//void on_ClientDisconnect_Slot();
//客户端断开连接的实现2
void on_StateChanged_Slot(QAbstractSocket::SocketState socketState);
//鼠标左键点击后可以刷新QComboBox
void on_ClientFalshProtComboBox_Slot();
private slots:
//监听客户端的连接
void on_pushButtonStartList_clicked();
//发送按键
void on_pushButtonSend_clicked();
//停止监听新的客户端
void on_pushButtonStopList_clicked();
//与客户端断开
void on_pushButtonDuankai_clicked();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
2.5.2widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QNetworkInterface>
#include <QTcpSocket>
#include <QIODevice>
#include <QMessageBox>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
tcpServer = new QTcpServer(this);//QTcpServer 类提供基于 TCP 的服务器
//按键初始化的状态
ui->pushButtonStartList->setEnabled(true);
ui->pushButtonStopList->setEnabled(false);
ui->pushButtonDuankai->setEnabled(false);
ui->pushButtonSend->setEnabled(false);
//当有新的TCP连接,会触发newConnection()信号
connect(tcpServer, SIGNAL(newConnection()), this, SLOT(on_ClientTcpConnect_slot()));
connect(ui->comboBoxClientPort, &QMyComBoBox::falshComboBox,this,
&Widget::on_ClientFalshProtComboBox_Slot);//刷新QComboBox
//获取pc的ip地址:QList<QHostAddress> QNetworkInterface::allAddresses()
QList<QHostAddress> comboBoxaddr = QNetworkInterface::allAddresses();
for(QHostAddress tmpAddr : comboBoxaddr){
if(tmpAddr.protocol() == QAbstractSocket::IPv4Protocol){//过滤出IPV4的地址
ui->comboBoxaddr->addItem(tmpAddr.toString());
}
}
//comboBox初始化的状态
ui->comboBoxaddr->setCurrentIndex(2);
}
Widget::~Widget()
{
delete ui;
}
//有客户端连接
void Widget::on_ClientTcpConnect_slot()
{
//检查是否有挂起的连接请求等待被接受
if(tcpServer->hasPendingConnections()){
QTcpSocket *tcpSocket = tcpServer->nextPendingConnection(); //将挂起的连接接受为已连接的QTcpSocket,通过该对象可以与客户端通信。
ui->textEditRev->insertPlainText("cilent addr:" + tcpSocket->peerAddress().toString() + "\n" +
"cilent port:" + QString::number(tcpSocket->peerPort()));
connect(tcpSocket, SIGNAL(readyRead()), this, SLOT(on_readCilentData_Slot()));//读取客户端数据的信号与槽
//connect(tcpSocket, SIGNAL(disconnected()), this, SLOT(on_ClientDisconnect_Slot()));//客户端断开的信号与槽1
connect(tcpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this,
SLOT(on_StateChanged_Slot(QAbstractSocket::SocketState)));//客户端断开的信号与槽2
ui->comboBoxClientPort->addItem(QString::number(tcpSocket->peerPort()));//将获取的端口号添加到QComboBox中
ui->comboBoxClientPort->setCurrentText(QString::number(tcpSocket->peerPort()));//设置为当前连接的端口号
//如果有新的客户端连接就启用发送按键
if(!ui->pushButtonSend->isEnabled()){
ui->pushButtonSend->setEnabled(true);
}
}
}
//读取客户端数据
void Widget::on_readCilentData_Slot()
{
QTcpSocket *tmpreadBuf = qobject_cast<QTcpSocket *>(sender());//获取发送信号对象的指针
QByteArray readBuf = tmpreadBuf->readAll();//接收客户端的数据
ui->textEditRev->insertPlainText("\n" + readBuf);
ui->textEditRev->moveCursor(QTextCursor::End);//跟随光标移动
ui->textEditRev->ensureCursorVisible();//确保光标是可视的
}
//客户端断开连接的实现1
//void Widget::on_ClientDisconnect_Slot()
//{
// QTcpSocket *_tmpreadBuf = qobject_cast<QTcpSocket *>(sender());
// ui->textEditRev->insertPlainText("\n客户端断开连接\n");
// _tmpreadBuf->deleteLater();
//}
//客户端断开连接的实现2
void Widget::on_StateChanged_Slot(QAbstractSocket::SocketState socketState)
{
int tmpindex;
QTcpSocket *_tmpreadBuf = qobject_cast<QTcpSocket *>(sender());//获取发送信号对象的指针
switch(socketState){
//连接断开状态
case QAbstractSocket::UnconnectedState:
ui->textEditRev->insertPlainText("\n客户端断开连接\n");
tmpindex = ui->comboBoxClientPort->findText(QString::number(_tmpreadBuf->peerPort()));//在文本控件中搜索指定的文本,并返回匹配的位置
qDebug() << QString::number(_tmpreadBuf->peerPort());
qDebug() << tmpindex;
ui->comboBoxClientPort->removeItem(tmpindex);
_tmpreadBuf->deleteLater();//延迟删除对象
qDebug() <<ui->comboBoxClientPort->count();
if(ui->comboBoxClientPort->count() == 0)
ui->pushButtonSend->setEnabled(false);
break;
//连接状态
case QAbstractSocket::ConnectingState:
case QAbstractSocket::ConnectedState:
ui->textEditRev->insertPlainText("\n客户端连接\n");
break;
}
}
//鼠标左键点击后可以刷新QComboBox
void Widget::on_ClientFalshProtComboBox_Slot()
{
ui->comboBoxClientPort->clear();
QList<QTcpSocket *> getClientsPort = tcpServer->findChildren<QTcpSocket *>();//获取到客户端并添加到数组里
for(QTcpSocket *tmpgetClientsPort : getClientsPort){
if(tmpgetClientsPort != nullptr)
ui->comboBoxClientPort->addItem(QString::number(tmpgetClientsPort->peerPort()));//将端口添加到QComboBox中
}
ui->comboBoxClientPort->addItem("Client All");
}
//监听客户端的连接
void Widget::on_pushButtonStartList_clicked()
{
//监听指定的地址和端口
if(!tcpServer->listen(QHostAddress(ui->comboBoxaddr->currentText()),ui->lineEditport->text().toInt())){
QMessageBox msgBox;
msgBox.setWindowTitle("错误");
msgBox.setText("端口号可能被占用");
msgBox.exec();
return;
}
//当监听按键按下后按键的状态
ui->pushButtonStartList->setEnabled(false);
ui->pushButtonStopList->setEnabled(true);
ui->pushButtonDuankai->setEnabled(true);
}
//发送按键
void Widget::on_pushButtonSend_clicked()
{
QList<QTcpSocket *> revClients = tcpServer->findChildren<QTcpSocket *>();//获取到客户端并添加到数组里
//检查有无客户端连接 就是判断一下QComboBox是否为空
if(revClients.isEmpty()){
QMessageBox msgBox;
msgBox.setWindowTitle("错误");
msgBox.setText("无连接");
msgBox.exec();
return;
}
if(ui->comboBoxClientPort->currentText() != "Client All"){
QString tmpSendstr = ui->comboBoxClientPort->currentText();//获取当前的客户端端口号
for(QTcpSocket *tmp : revClients){
//通过比较当前和客户端的端口号进行数据的发送
if(QString::number(tmp->peerPort()) == tmpSendstr){
tmp->write(ui->lineEditSend->text().toStdString().c_str());//向客户端发送数据
}
}
}else{
for(QTcpSocket *tmprevClients : revClients){
tmprevClients->write(ui->lineEditSend->text().toStdString().c_str());
}
}
}
//停止监听新的客户端
void Widget::on_pushButtonStopList_clicked()
{
QList<QTcpSocket *> revClients = tcpServer->findChildren<QTcpSocket *>();//获取到客户端并添加到数组里
for(QTcpSocket *tmprevClients : revClients){
tmprevClients->close();//关闭所有的QTcpSocket 连接,
}
tcpServer->close();//停止监听新的连接请求
ui->pushButtonStartList->setEnabled(true);
ui->pushButtonStopList->setEnabled(false);
ui->pushButtonDuankai->setEnabled(false);
ui->pushButtonSend->setEnabled(false);
}
//与客户端断开
void Widget::on_pushButtonDuankai_clicked()
{
//停止监听新的客户端
on_pushButtonStopList_clicked();
//卸载掉TCP服务器
delete tcpServer;
this->close();
}
2.5.3qmycomboBox.h
#ifndef QMYCOMBOBOX_H
#define QMYCOMBOBOX_H
#include <QComboBox>
#include <QWidget>
class QMyComBoBox : public QComboBox
{
Q_OBJECT
public:
QMyComBoBox(QWidget *parent);
protected:
void mousePressEvent(QMouseEvent *e) override;
signals:
void falshComboBox();
};
#endif // QMYCOMBOBOX_H
2.5.3qmycomboBox.cpp
#include "qmycombobox.h"
#include <QMouseEvent>
QMyComBoBox::QMyComBoBox(QWidget *parent) : QComboBox(parent)
{
}
void QMyComBoBox::mousePressEvent(QMouseEvent *e)
{
//检测鼠标左键按下
if(e->button() == Qt::LeftButton){
//发送自定义的信号
emit falshComboBox();
}
//让程序可以继续执行
QComboBox::mousePressEvent(e);
}
2.6客户端完整代码
2.6.1widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QTcpSocket>
#include <QTimer>
#include <QWidget>
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_pushButtonConnect_clicked();
//接收服务端的数据
void on_readServerData_Slot();
//发送数据到服务端
void on_pushButtonSend_clicked();
//断开服务端
void on_pushButtonDisconnect_clicked();
//成功连接服务端的操作
void on_connect_Slot();
//连接服务端发生错误
void on_error_Slot(QAbstractSocket::SocketError error);
//定时时间超时
void on_timerout_Slot();
private:
Ui::Widget *ui;
QTcpSocket *client;//创建一个QTcpSocket对象
QTimer *timer;//创建一个定时器的对象
//文本字体颜色的转换
void colorConver(Qt::GlobalColor color, QString str);
};
#endif // WIDGET_H
2.6.2widget.cpp
QTextCharFormat 是 Qt 中用于描述文本字符格式的类。它包含了文本的各种格式属性,比如字体、颜色、背景色、文本样式(粗体、斜体等)等。QTextCharFormat 类通常用于富文本编辑和格式化文本显示。
可以使用 QTextCharFormat 类来设置文本的格式,例如:
设置字体:setFont()
设置字体大小:setFontSize()
设置字体颜色:setForeground()
设置背景色:setBackground()
设置文本样式:setFontWeight(), setFontItalic(), setFontUnderline(), setFontStrikeOut()
设置超链接:setAnchor(), setAnchorHref()
等等
通过创建一个 QTextCharFormat 对象,可以为文本添加不同的格式,然后应用到相应的文本段落或文本块中
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
client = new QTcpSocket(this);
ui->pushButtonDisconnect->setEnabled(false);//断开按键初始化的状态
ui->pushButtonSend->setEnabled(false);//发送按键初始化的状态
connect(client, SIGNAL(readyRead()), this, SLOT(on_readServerData_Slot()));//读取服务端数据的
}
Widget::~Widget()
{
delete ui;
}
//连接服务端
void Widget::on_pushButtonConnect_clicked()
{
timer = new QTimer;
timer->setInterval(5000);//设置定时器的时间为5秒
timer->setSingleShot(true);//设置定时器单次触发模式
client->connectToHost(ui->lineEditIP->text(), ui->lineEditProt->text().toInt());//连接主机
connect(client, SIGNAL(connected()), this, SLOT(on_connect_Slot()));//成功连接到服务端
connect(client, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(on_error_Slot(QAbstractSocket::SocketError)));//连接服务端错误
connect(timer, SIGNAL(timeout()), this, SLOT(on_timerout_Slot()));
this->setEnabled(false);//当点击连接时UI界面不可选
timer->start();//启动定时器
}
//接收服务端的数据
void Widget::on_readServerData_Slot()
{
colorConver(Qt::black, client->readAll());//读取服务端数据
}
//发送数据到服务端
void Widget::on_pushButtonSend_clicked()
{
client->write(ui->textEditSend->toPlainText().toUtf8());//发送数据到服务端
colorConver(Qt::red, ui->textEditSend->toPlainText().toUtf8());//将颜色转换为
}
//断开服务端
void Widget::on_pushButtonDisconnect_clicked()
{
client->close();//关闭QTcpSocket
//UI显示设置
ui->textEditeRev->append("已断开");
ui->pushButtonConnect->setEnabled(true);
ui->lineEditIP->setEnabled(true);
ui->lineEditProt->setEnabled(true);
ui->pushButtonDisconnect->setEnabled(false);
ui->pushButtonSend->setEnabled(false);
}
//成功连接服务端的操作
void Widget::on_connect_Slot()
{
timer->stop();
this->setEnabled(true);//连接成功时UI界面可选
//UI显示设置
ui->textEditeRev->append("已连接");
ui->pushButtonConnect->setEnabled(false);
ui->lineEditIP->setEnabled(false);
ui->lineEditProt->setEnabled(false);
ui->pushButtonDisconnect->setEnabled(true);
ui->pushButtonSend->setEnabled(true);
}
//连接服务端发生错误
void Widget::on_error_Slot(QAbstractSocket::SocketError error)
{
this->setEnabled(true);//连接错误时UI界面可选
client->abort();//立即关闭当前的TCP连接
ui->textEditeRev->append("连接错误,可能服务器断开");
on_pushButtonDisconnect_clicked();//断开服务端
}
//定时时间超时
void Widget::on_timerout_Slot()
{
ui->textEditeRev->insertPlainText("连接超时!");
this->setEnabled(true);//定时时间到时UI界面可选
}
//文本字体颜色的转换
void Widget::colorConver(Qt::GlobalColor color, QString str)
{
QTextCursor cursor = ui->textEditeRev->textCursor();//获取当前光标位置
QTextCharFormat format;
format.setForeground(QBrush(color));//设置文本前景色的函数
cursor.setCharFormat(format);//设置光标位置处字符的格式
cursor.insertText(str);//将改好的格式插入文本
}