Qt#第十一章:网络编程

Qt直接提供网络编程模块,基于TCP/IP客户端和服务器端相关各种类。TCP通信(QTcpSocket/QTcpServer).UDP通信(QUdpSocket)。还有部分实现HTTP、FTP等网络相关协议的高级类。如QNetworkRequest/QNetworkAccessManager(HTTP)等;

它的网络编程模块提供网络承载管理类,提供基于安全套接字协议(SSL)的安全网络通信类。

我们开发过程中在项目配置文件引入:QT += network

QHostInfo 类为主机查找提供静态函数,主要用来查询主机信息、包含主机名、ip地址、DNS域名等信息;

QNetworkInterface类,主要获取主机所有ip地址和网络接口列表信息

demo历程 

.h

#ifndef GETHOSTNAMEIPINFO_H
#define GETHOSTNAMEIPINFO_H

#include <QDialog>
#include <QHostInfo>
#include <QNetworkInterface>
#include <QMessageBox>

QT_BEGIN_NAMESPACE
namespace Ui { class GetHostNameIPInfo; }
QT_END_NAMESPACE

class GetHostNameIPInfo : public QDialog
{
    Q_OBJECT
public:
    GetHostNameIPInfo(QWidget *parent = nullptr);
    ~GetHostNameIPInfo();

    void GetHostNameAndIpAddress(); // 获取主机名称和IP地址
private slots:
    void on_pushButton_GetHostNameIP_clicked();

    void on_pushButton_GetHostInfo_clicked();

private:
    Ui::GetHostNameIPInfo *ui;
};
#endif // GETHOSTNAMEIPINFO_H

.c

#include "gethostnameipinfo.h"
#include "ui_gethostnameipinfo.h"

GetHostNameIPInfo::GetHostNameIPInfo(QWidget *parent)
    : QDialog(parent)
    , ui(new Ui::GetHostNameIPInfo)
{
    ui->setupUi(this);
}

GetHostNameIPInfo::~GetHostNameIPInfo()
{
    delete ui;
}

void GetHostNameIPInfo::GetHostNameAndIpAddress() // 获取主机名称和IP地址
{
    // 获取主机名称
    QString StrLocalHostName=QHostInfo::localHostName();
    ui->lineEdit_hostname->setText(StrLocalHostName);

    // 根据主机名称获取对应的IP地址
    QString StrLocalIpAddress="";
    QHostInfo hostinfo=QHostInfo::fromName(StrLocalHostName);
    QList<QHostAddress> ipaddresslist=hostinfo.addresses();

    if(!ipaddresslist.isEmpty())
    {
        for (int i=0;i<ipaddresslist.size();i++)
        {
            QHostAddress addresshost=ipaddresslist.at(i);
            if(QAbstractSocket::IPv4Protocol==addresshost.protocol())
            {
                StrLocalIpAddress=addresshost.toString();
                break;
            }
        }
    }
    ui->lineEdit_hostip->setText(StrLocalIpAddress);
}

void GetHostNameIPInfo::on_pushButton_GetHostNameIP_clicked()
{
    GetHostNameAndIpAddress();
}

void GetHostNameIPInfo::on_pushButton_GetHostInfo_clicked()
{
    QString strTemp="";

    // 返回主机所找到的所有网络接口的列表
    QList<QNetworkInterface> netlist=QNetworkInterface::allInterfaces();
    for(int i=0;i<netlist.size();i++)
    {
        QNetworkInterface interfaces=netlist.at(i);
        strTemp=strTemp+"设备名称:"+interfaces.name()+"\n"; // 获取设备名称
        strTemp=strTemp+"硬件地址:"+interfaces.hardwareAddress()+"\n"; // 获取硬件地址

        QList<QNetworkAddressEntry> entrylist=interfaces.addressEntries(); // 遍历每一个IP地址对应信息
        for (int k=0;k<entrylist.count();k++)
        {
            QNetworkAddressEntry etry=entrylist.at(k);
            strTemp=strTemp+"IP地址:"+etry.ip().toString()+"\n";
            strTemp=strTemp+"子网掩码:"+etry.netmask().toString()+"\n";
            strTemp=strTemp+"广播地址:"+etry.broadcast().toString()+"\n";
        }

    }
    QMessageBox::information(this,"主机所有信息",strTemp,QMessageBox::Yes);
}
一、TCP协议

1.传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议。

2.TCP拥塞控制算法(也称AIMD)算法。该算法主要包括四个主部分:慢启动、拥塞避免、快速重传和快速恢复。

3.TCP通信必须建立TCP连接(客户端和服务器),Qt提供QTcpSocket类和QTcpServer类专门用于建立TCP通信程序。服务器端用QTcpServer监听端口及建立服务器;QTcpSocket用于建立连接后使用套接字(socket)进行通信。

QIODevice 父类 派生出QUdpSocket和QTcpSOcket;QTcpSocket派生出QStcpSocket和QSslSocket ;而QTcpServer是从QObject继承的类用于服务器网络监听,创建socket连接。

server服务器

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QTcpServer> // 专门用于建立TCP连接并传输数据信息
#include <QtNetwork> // 此模块提供开发TCP/IP客户端和服务器的类

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
private:
    Ui::MainWindow *ui
    // 自定义如下
private:
    QTcpServer *tcpserver; //TCP服务器
    QTcpSocket *tcpsocket;// TCP通讯socket
    QString GetLocalIpAddress(); // 获取本机的IP地址
private slots:
    void clientconnect();
    void clientdisconnect();
    void socketreaddata();
    void newconnection();
    void on_pushButton_Start_clicked();
    void on_pushButton_Stop_clicked();
    void on_pushButton_Send_clicked();
protected:
    void closeEvent(QCloseEvent *event);
};
#endif // MAINWINDOW_H
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <stdexcept>
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    QString localip = GetLocalIpAddress();
    ui->comboBoxip->addItem(localip);

    ui->comboBoxip->addItem("192.168.66.158");

    tcpServer = new QTcpServer(this);
    connect(tcpServer,SIGNAL(newConnection()),this,SLOT(new_connect()));

}

MainWindow::~MainWindow()
{
    delete ui;
}

//获取本机IP地址
QString MainWindow::GetLocalIpAddress()
{
    //查询主机名称
    QString hostName = QHostInfo::localHostName();
    //根据主机名称查找它本身的IP
    QHostInfo hostinfp = QHostInfo::fromName(hostName);
    QString localip = "";
    //返回主机ip地址
    QList<QHostAddress> addressList = hostinfp.addresses();
    if(!addressList.isEmpty())
    {
        for(QHostAddress list:addressList)
        {
            if(list.protocol() == QAbstractSocket::IPv4Protocol)
            {
                localip = list.toString();
                break;
            }
        }
    }
    return localip;
}

void MainWindow::client_connect()
{
    //客户端连接
    ui->plainTextEdit->appendPlainText("************客户端socket连接************");
    ui->plainTextEdit->appendPlainText("************peer address:"+tcpSocket->peerAddress().toString());
    ui->plainTextEdit->appendPlainText("************peer port   :"+QString::number(tcpSocket->peerPort()));

}

void MainWindow::client_dis_connect()
{
    //客户端断开连接
    ui->plainTextEdit->appendPlainText("************客户端socket断开连接************");
    tcpSocket->deleteLater();
}

void MainWindow::socket_read_data()
{
    //读取数据
    while(tcpSocket->canReadLine())
    {
        ui->plainTextEdit->appendPlainText("[in]"+tcpSocket->readLine());
    }
}

void MainWindow::new_connect()
{
    //将下一个挂起的连接作为已连接的QTcpSocket对象返回。
    //套接字是作为服务器的子节点创建的,这意味着当QTcpServer对象被销毁时,它将被自动删除。在使用完对象后显式删除它仍然是一个好主意,以避免浪费内存。
    //如果在没有挂起连接的情况下调用此函数,则返回0。
    //注意:返回的QTcpSocket对象不能在其他线程中使用。如果您想使用来自另一个线程的传入连接,则需要重写incomingConnection()。
    tcpSocket = tcpServer->nextPendingConnection();
    //qDebug()<<tcpSocket<<"hahahhaha";
    connect(tcpSocket,SIGNAL(connected()),this,SLOT(client_connect()));
    client_connect();

    connect(tcpSocket,SIGNAL(disconnected()),this,SLOT(client_dis_connect()));

    connect(tcpSocket,SIGNAL(readyRead()),this,SLOT(socket_read_data()));

    connect(tcpSocket,SIGNAL(stateChanged(QAbstractSocket::SocketState)),
            this,SLOT(OnSocketStateChanged(QAbstractSocket::SocketState)));
}


void MainWindow::on_pushButtonstart_clicked()
{
    QString ip = ui->comboBoxip->currentText(); //组合框当前的值
    quint16 port = ui->spinBoxport->value();//这个属性保存旋转框的值

    QHostAddress address(ip);
    tcpServer->listen(address,port); //Tcpserver监听

    ui->plainTextEdit->appendPlainText("$$$$$$$$$$开始监听$$$$$$$$$$");
    ui->plainTextEdit->appendPlainText("$$$$$$$$$$服务器地址$$$$$$$$$"+tcpServer->serverAddress().toString());
    ui->plainTextEdit->appendPlainText("$$$$$$$$$$服务器端口号$$$$$$$$"+QString::number(tcpServer->serverPort()));

    ui->pushButtonstart->setEnabled(false);
    ui->pushButtonstop->setEnabled(true);
}

void MainWindow::on_pushButtonstop_clicked()
{
    if(tcpServer->isListening())
    {
        tcpServer->close();
        ui->pushButtonstart->setEnabled(true);
        ui->pushButtonstop->setEnabled(true);
        ui->plainTextEdit->appendPlainText("$$$$$$$$$$服务器已关闭$$$$$$$$");
    }
    /* 如果是连接上了也应该断开,如果不断开客户端还能继续发送信息,
     * 因为 socket 未断开,还在监听上一次端口
     */
    if(tcpSocket->state() == tcpSocket->ConnectedState)
    {
        tcpSocket->disconnectFromHost();
    }

}

void MainWindow::on_pushButtonsend_clicked()
{
    if(NULL == tcpSocket)
        return;
    //tcpSocket->ConnectedState 等价于 QAbstractSocket::ConnectedState
    if(tcpSocket->state() == tcpSocket->ConnectedState)
    {
        QString strMsg = ui->lineEditinputmsg->text();
        ui->plainTextEdit->appendPlainText("[out]:"+strMsg);
        ui->lineEditinputmsg->clear(); //清除文本信息
        QByteArray str = strMsg.toUtf8();
        str.append("\n");
        tcpSocket->write(str);
    }

}

/*
void MainWindow::closeEvent(QCloseEvent *event):
这是一个事件处理函数,用于处理主窗口的关闭事件。当用户尝试关闭主窗口时,这个函数会被触发。

if (tcpServer->isListening()):
这行代码检查一个名为 tcpServer 的对象是否正在监听传入的连接。isListening 是 Qt 的 QTcpServer 类的成员函数,用于检查服务器是否正在监听。

tcpServer->close():
如果 tcpServer 正在监听传入的连接(isListening 返回 true),则通过调用 close 方法来关闭服务器。这会停止服务器接受新的连接请求。

event->accept():
最后,通过调用 accept 方法,设置事件对象的 accept 标志为 true,表示主窗口可以正常关闭。这是必要的,因为在某些情况下,你可能希望阻止窗口关闭,例如在某些数据未保存的情况下。
在这种情况下,你可以将 accept 标志设置为 false,以阻止窗口关闭。
*/
void MainWindow::closeEvent(QCloseEvent *event)
{
    //如果服务器当前正在侦听传入的连接,则返回true;否则返回false。
    if(tcpServer->isListening())
        tcpServer->close();
    //设置事件对象的accept标志,相当于调用setAccepted(true)。
    //表示接收关闭请求
    event->accept();
}


void MainWindow::on_pushButtonclear_clicked()
{
    ui->plainTextEdit->clear();
}

clicent客户端

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QtNetwork>
#include <QHostInfo>
#include <QHostAddress>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    Ui::MainWindow *ui;

    QTcpSocket  *tcpClient; //tcpClient
    QString GetLocalIp();   //获取�?机IP地址

protected:
    void closeEvent(QCloseEvent *event);

private slots:
    void connect_func();
    void dis_connect_func();
    void socket_read_data();

    void on_pushButtonstart_clicked();
    void on_pushButtonstop_clicked();
    void on_pushButtonsend_clicked();
    void on_pushButtonclear_clicked();
};
#endif // MAINWINDOW_H
#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    setCentralWidget(ui->widget);

    QString hostIp = GetLocalIp();
    ui->comboBoxip->addItem(hostIp);
    //ui->comboBoxip->addItem("192.168.66.158");

    tcpClient = new QTcpSocket(this);

    connect(tcpClient,SIGNAL(connected()),this,SLOT(connect_func()));
    connect(tcpClient,SIGNAL(disconnected()),this,SLOT(dis_connect_func()));
    connect(tcpClient,SIGNAL(readyRead()),this,SLOT(socket_read_data()));

}

MainWindow::~MainWindow()
{
    delete ui;
}

QString MainWindow::GetLocalIp()
{
    QString hostName = QHostInfo::localHostName();
    QHostInfo hostInfo = QHostInfo::fromName(hostName);

    QString localIp = "";
    QList<QHostAddress> addressList = hostInfo.addresses();
    if(!addressList.isEmpty())
    {
        for(QHostAddress list:addressList)
        {
            if(list.protocol() == QAbstractSocket::IPv4Protocol)
            {
                localIp = list.toString();
                break;
            }
        }
    }
    return localIp;
}

void MainWindow::on_pushButtonstart_clicked()
{
    /* 如果连接状态还没有连接 */
    if(tcpClient->state() != QAbstractSocket::ConnectedState)
    {
        QString addr = ui->comboBoxip->currentText();
        quint16 port = ui->spinBoxport->value();
        tcpClient->connectToHost(addr,port);
    }
}

void MainWindow::on_pushButtonstop_clicked()
{
    if(tcpClient->state() == QAbstractSocket::ConnectedState)
    {
        //关闭套接字的I/O设�?�,并调用disconnectFromHost()来关�?套接字的连接�?
        tcpClient->disconnectFromHost(); //关闭套接字的连接
        tcpClient->close();//�?己加�?
    }
}

void MainWindow::on_pushButtonsend_clicked()
{
    if(NULL == tcpClient)
        return;
    if(tcpClient->state() == tcpClient->ConnectedState)
    {
        QString strMsg = ui->lineEditinputmsg->text();
        ui->plainTextEdit->appendPlainText("[out]:"+strMsg);
        ui->lineEditinputmsg->clear();
        //发送字节数�?
        tcpClient->write(strMsg.toUtf8().append("\n"));
    }
}

void MainWindow::on_pushButtonclear_clicked()
{
    ui->plainTextEdit->clear();
}

void MainWindow::closeEvent(QCloseEvent *event)
{
    if(tcpClient->state() == QAbstractSocket::ConnectedState)
    {
        tcpClient->disconnectFromHost(); //关闭套接�?
    }
    event->accept();
}

void MainWindow::connect_func()
{
    ui->plainTextEdit->appendPlainText("**********已经连接到服务器�?**********");
    ui->plainTextEdit->appendPlainText("**********peer address:"+tcpClient->peerAddress().toString());
    ui->plainTextEdit->appendPlainText("**********peer port:"+QString::number(tcpClient->peerPort()));

    ui->pushButtonstart->setEnabled(false);
    ui->pushButtonstop->setEnabled(true);
}
void MainWindow::dis_connect_func()
{
    ui->plainTextEdit->appendPlainText("**********�?开服务器连�?**********");
    ui->pushButtonstart->setEnabled(true);
    ui->pushButtonstop->setEnabled(true);
}
void MainWindow::socket_read_data()
{
    //如果�?以从套接字�?�取一行数�?,则返回true;否则返回false�?
    while(tcpClient->canReadLine())
    {
        ui->plainTextEdit->appendPlainText("[in]:"+tcpClient->readLine());
    }
}
二、UDP协议基础知识

1.UDP(用户数据报协议)是轻量的、不可靠的、面向数据报、无连接的协议,用于可靠性要求不高的场合。两个应用程序之间进行UDP通信不需要先建立持久的socket连接,UDP每次发送数据报都需要指定目标地址和端口。

2. UDP报文没有可靠性保证、顺序保证和流量控制字段等,可靠性较差。但是正因为UDP协议的控制选项较少,在数据传输过程中延迟较小、数据传输效率高,适合可靠性要求不高的应用程序,或者可以保障可靠性的应用程序,如DNS、TFTP、SNMP等。

3.UDP 报头由4 个域组成,其中每个域各占用2 个字节,具体包括源端口号、目标端口号、数据包长度、校验值。端口号有效范围0--65535,假设端口号大于49151 的端口都代表动态端口。

4、QUdpSocket 类从QAbstractSocket 类继承,基本跟QTcpSocket共用大部分的接口函数,主要区别在于QUdpSocket 以数据报传输数据, 不是以连续的数据流, 发送方发送数据报使用函数
QUdpSocket::writeDataGram(),数据报长度一般不超过512 个字节,每个数据报包含发送方和接收方的IP 地址和端口等数据信息。

5、UDP 数据接收使用QUdpSocket::bind()函数绑定端口,用于接收传入的数据报,当有数据报传入发射readyRead()信号,使用ReadDatagram()函数来读取接收数据报。UDP 消息传送有单播、广播和组播三种模式。
单播:一个UDP 客户端发出数据报只发送到另一个指定地址和端口的UDP 客户端(一对一的数据传输)。
广播:一个UDP 客户端发出的数据,在同一个网络范围内其它所有UDP 客户端都可以收到。

组播(多播):UDP 客户端加入到另一个组播IP 地址指定的多播组,成员向组播地址发送的数据报组内成员都可以接收到。

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QUdpSocket>   // 用于发送和接收UDP数据报
#include <QtNetwork>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    void on_pushButtonStart_clicked();

    void on_pushButtonStop_clicked();

    void on_pushButtonSend_clicked();

    void on_pushButtonBroadcast_clicked();

    void on_pushButtonClear_clicked();

private:
    Ui::MainWindow *ui;
    QUdpSocket      *udpSocket;

    QString getLocalIpAddress();//获取本机的IP地址

    //自定义槽函数
private slots:
    void socketReadyReadData();  //读取socket传输数据信息
    void socketStateChange(QAbstractSocket::SocketState);
};
#endif // MAINWINDOW_H
#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    setCentralWidget(ui->centralwidget);

    QString strIp = getLocalIpAddress();
    ui->comboBoxTargetIp->addItem(strIp);

    udpSocket = new QUdpSocket(this); //专门用于连接客户端的UDP套接字

    connect(udpSocket,SIGNAL(readyRead()),this,SLOT(socketReadyReadData()));
    connect(udpSocket,SIGNAL(stateChanged(QAbstractSocket::SocketState)),this,SLOT(socketStateChange(QAbstractSocket::SocketState)));
}

MainWindow::~MainWindow()
{
    delete ui;
}

//启动服务
void MainWindow::on_pushButtonStart_clicked()
{
    quint16 port = ui->spinBoxBind->value(); //本机udp端口
    /* 绑定端口需要在 socket 的状态为 UnconnectedState */
    if (udpSocket->state() != QAbstractSocket::UnconnectedState)
        udpSocket->close();

    if(udpSocket->bind(port))
    {
        ui->plainTextEditMsg->appendPlainText("**********服务启动成功**********");
        ui->plainTextEditMsg->appendPlainText("**********绑定端口号:"+QString::number(udpSocket->localPort()));
        ui->pushButtonStart->setEnabled(false);
        ui->pushButtonStop->setEnabled(true);
    }
    else
    {
        ui->plainTextEditMsg->appendPlainText("**********服务启动失败**********");
    }
}

void MainWindow::on_pushButtonStop_clicked()
{

    udpSocket->abort(); //解绑 不需要监听了

    ui->pushButtonStart->setEnabled(true);
    ui->pushButtonStop->setEnabled(true);
    ui->plainTextEditMsg->appendPlainText("**********停止服务**********");
}

void MainWindow::on_pushButtonSend_clicked()
{
    //获取 目标的IP地址
    QString targetIpAddress = ui->comboBoxTargetIp->currentText();

    QHostAddress targetAddress(targetIpAddress);
    //获取 目标的端口号
    quint16 targetPort = ui->spinBoxTarget->value();
    QString targetMsg = ui->lineEditMsg->text();

    QByteArray str = targetMsg.toUtf8();
    //发送数据
    udpSocket->writeDatagram(str,targetAddress,targetPort);

    ui->plainTextEditMsg->appendPlainText("[out]:"+targetMsg);
    ui->lineEditMsg->clear();
    ui->lineEditMsg->setFocus(); //将光标焦点定位到编辑框控件
}

void MainWindow::on_pushButtonBroadcast_clicked()
{
    //获取 目标的端口号
    quint16 targetPort = ui->spinBoxTarget->value();
    QString targetMsg = ui->lineEditMsg->text();

    QByteArray str = targetMsg.toUtf8();
    //发送数据
    udpSocket->writeDatagram(str,QHostAddress::Broadcast,targetPort);

    ui->plainTextEditMsg->appendPlainText("[broadcast]:"+targetMsg);
    ui->lineEditMsg->clear();
    ui->lineEditMsg->setFocus(); //将光标焦点定位到编辑框控件}
}
void MainWindow::on_pushButtonClear_clicked()
{
    ui->plainTextEditMsg->clear();
}

QString MainWindow::getLocalIpAddress()
{
    QString hostName = QHostInfo::localHostName();
    QHostInfo hostInfo = QHostInfo::fromName(hostName);
    QString strIp = "";
    QList<QHostAddress> addressList = hostInfo.addresses();
    if(!addressList.isEmpty())
    {
        for(QHostAddress list:addressList)
        {
            if(list.protocol() == QAbstractSocket::IPv4Protocol)
            {
                strIp = list.toString();
                break;
            }
        }
    }
    return strIp;
}

void MainWindow::socketReadyReadData()
{
    //读取接受到的数据报信息
    while(udpSocket->hasPendingDatagrams())//如果至少有一个数据报等待读取,则返回true;否则返回false
    {
        QByteArray datagrams;
        //将字节数组的大小设置为size bytes。
        //pendingDatagramSize():返回第一个挂起的UDP数据报的大小。如果没有可用的数据报,这个函数返回-1。
        datagrams.resize(udpSocket->pendingDatagramSize());
        QHostAddress pAddress;
        quint16  pPort;
        //接收不大于maxSize字节的数据报,并将其存储在data中。发送方的主机地址和端口存储在*address和*port中(除非指针为0)。
        //成功时返回数据报的大小;否则返回-1。
        udpSocket->readDatagram(datagrams.data(),datagrams.size(),&pAddress,&pPort);

        QString strs = datagrams.data();
        QString data = "[From to:"+pAddress.toString()+":"+QString::number(pPort)+"]"+strs+"\n";
        ui->plainTextEditMsg->appendPlainText(data);
    }
}

/* socket 状态改变 */
void MainWindow::socketStateChange(QAbstractSocket::SocketState state)
{
    switch (state) {
    case QAbstractSocket::UnconnectedState:
        ui->plainTextEditMsg->appendPlainText("scoket 状态:UnconnectedState");
        break;
    case QAbstractSocket::ConnectedState:
        ui->plainTextEditMsg->appendPlainText("scoket 状态:ConnectedState");
        break;
    case QAbstractSocket::ConnectingState:
        ui->plainTextEditMsg->appendPlainText("scoket 状态:ConnectingState");
        break;

    case QAbstractSocket::HostLookupState:
        ui->plainTextEditMsg->appendPlainText("scoket 状态:HostLookupState");
        break;
    case QAbstractSocket::ClosingState:
        ui->plainTextEditMsg->appendPlainText("scoket 状态:ClosingState");
        break;
    case QAbstractSocket::ListeningState:
        ui->plainTextEditMsg->appendPlainText("scoket 状态:ListeningState");
        break;
    case QAbstractSocket::BoundState:
        ui->plainTextEditMsg->appendPlainText("scoket 状态:BoundState");
        break;
    default:
        break;
    }
}
三、Http协议基本知识

1.http:超文本传输协议,是一个简单的请求-响应协议,它通常运行在TCP之上。规定WWW服务器与浏览器之间信息传递规范,是二者共同遵守的协议。

2.http工作原理:HTTP是基于客户/服务器模式,且面向连接。

头文件(QNetworkRequest(请求对象)/QNetworkAccessManager(发送请求))

HTTP事务处理流程:

        ①:客户端与服务器端建立连接

        ②:客户端向服务器端请求

        ③:服务器端接受请求,并根据请求返回相应的文件作为应答

        ④:客户与服务器关闭连接

这段代码是通过HTTP请求获取百度网页,显示在页面上

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

#include <QtNetwork> //提供编写TCP、IP客户端和服务器的类
#include <QUrl>     //提供接口使用URLS

//它允许您创建和发送网络请求(例如HTTP请求)以访问远程资源,如Web页面、API端点或下载文件。
class QNetworkAccessManager;
/*
    当您使用 QNetworkAccessManager 发送请求后,它将返回一个 QNetworkReply 对象,该对象包含了从服务器接收到的数据和其他与响应相关的信息。
    您可以使用 QNetworkReply 读取响应的数据,例如文本、二进制数据或JSON,也可以获取响应的状态码、头信息等。
    QNetworkReply 也支持异步操作,通常通过信号和槽机制来处理响应数据的读取和处理。
*/
class QNetworkReply; //此类是QIODevice的子类

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    Ui::MainWindow *ui;

    QNetworkAccessManager *mgr; //
public slots:
    void replayFinishedFunc(QNetworkReply*);

private slots:
    void on_pushButtonGetData_clicked();
};
#endif // MAINWINDOW_H
#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    setWindowTitle("http应用测试");

    mgr = new QNetworkAccessManager(this);

    connect(mgr,SIGNAL(finished(QNetworkReply*)),this,SLOT(replayFinishedFunc(QNetworkReply*)));
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::replayFinishedFunc(QNetworkReply *reply) //响应
{
    QString strall = reply->readAll(); // 读取数据
    ui->textBrowser->setText(strall); //    显示数据
    ui->label_disp->setText("数据下载成功!");
    reply->deleteLater(); //删除对象
}


void MainWindow::on_pushButtonGetData_clicked()
{
    ui->label_disp->setText("数据正在下载中,请耐心等待....");
    mgr->get(QNetworkRequest(QUrl("http://www.baidu.com")));

}
四、WebSocket基础知识

WebSocket是一种通过单个TCP连接提供的全双工通信信道网络技术。它可用于客户端应用程序和服务端应用程序;记得加这个 QT+= websocket

服务器端

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QWebSocketServer>
#include <QWebSocket>
#include <QAbstractSocket>
#include <QJsonDocument> //提供读取和写入JSON文档的相关方法
#include <QJsonObject>  //JSON对象

class QWebSocket;

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private:
    Ui::Widget *ui;

    //自定义
    class QWebSocketServer *websocketserver;
    QList<QWebSocket*> websocketlist;   //存储客户端

    void getNewConnect();//获取新的客户端连接
    void receiveMsg(const QString &msg);

public slots:
    void onErrorFunc(QAbstractSocket::SocketError);

private slots:
    void on_pushButton_senddata_clicked();
};
#endif // WIDGET_H
#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    setContentsMargins(0,5,5,5);
    //WS (服务器非安全模式运行)
    //SSL (安全模式运行)
    websocketserver = new QWebSocketServer(QStringLiteral("testServer"),QWebSocketServer::NonSecureMode,this);

    connect(websocketserver,&QWebSocketServer::newConnection,this,&Widget::getNewConnect);

    websocketserver->listen(QHostAddress::Any,8899);

}

Widget::~Widget()
{
    delete ui;
    for (auto socket:websocketlist)
    {
        socket->close();
    }
    websocketserver->close();
}

void Widget::getNewConnect()
{
    //hasPendingConnections():如果服务器有挂起的连接返回true;否则返回false。
    if(websocketserver->hasPendingConnections())
    {
        QWebSocket *websocket = websocketserver->nextPendingConnection();
        ui->textEdit_msgList->append(websocket->origin()+"客户端已连接上服务器");
        websocketlist<<websocket;
        QListWidgetItem *item = new QListWidgetItem;
        item->setText(websocket->origin()); //origin():返回当前连接源
        ui->listWidget_client->addItem(item); //将连接的客户端添加到客户端列表控件

        connect(websocket,&QWebSocket::disconnected,this,[websocket,this]
        {
            ui->textEdit_msgList->append(websocket->origin()+"客户端已退出");
            websocketlist.removeOne(websocket);
            for (int i =0;i<ui->listWidget_client->count();i++)
            {
                QListWidgetItem *item = ui->listWidget_client->item(i);
                if(item->text()==websocket->origin())
                {
                    ui->listWidget_client->removeItemWidget(item);
                    delete item;
                    break;
                }
            }
            websocket->deleteLater();

        });

        connect(websocket,&QWebSocket::textMessageReceived,this,&Widget::receiveMsg);
        connect(websocket,SIGNAL(error(QAbstractSocket::SocketError)),this,SLOT(QAbstractSocket::SocketError));
    }
}

void Widget::receiveMsg(const QString &msg)
{
    QJsonDocument jd = QJsonDocument::fromJson(msg.toLatin1().data());

    if(jd.isEmpty())
    {
        //sender() 函数用于获取当前信号的发送者对象,通常在槽函数内部使用
        QWebSocket *websocket = qobject_cast<QWebSocket*>(sender());
        ui->textEdit_msgList->append("收到客户端消息["+websocket->origin()+"]---->"+msg);
    }
    else
    {
        QJsonObject jdo = jd.object();
        qDebug()<<jdo;
        QString dst = jdo["dst:"].toString();
        for (auto socket:websocketlist)
        {
            if(dst == socket->origin())
                socket->sendTextMessage(msg);
        }
    }
}

void Widget::onErrorFunc(QAbstractSocket::SocketError error)
{
    QWebSocket *websocket = qobject_cast<QWebSocket*>(sender());
    //origin():返回当前原点。errorString():返回最近发生的错误的人类可读的描述
    ui->textEdit_msgList->append(websocket->origin()+"出错"+websocket->errorString());
}


void Widget::on_pushButton_senddata_clicked()
{
    //trimmed() 返回从开始和结束处删除空白的字符串。toPlainText()获取纯文本
    QString strtext = ui->textEdit->toPlainText().trimmed();
    if(strtext.isEmpty())
        return;
    if(ui->radioButton_sendall->isChecked()) //群发
    {
        if(websocketlist.size()==0)
          return;
        for (auto sock:websocketlist)
        {
            sock->sendTextMessage(strtext);
        }
        ui->textEdit_msgList->append("服务器给所有连接者发送:"+strtext);
    }
    else
    {
        if(!ui->listWidget_client->currentItem())
          return;
        QString strcurrent = ui->listWidget_client->currentItem()->text();

        QWebSocket *websocket = nullptr;
        for(auto socket:websocketlist)
        {
            if(socket->origin()==strcurrent)
            {
                websocket = socket;
            }
        }
        if(websocket)
        {
            websocket->sendTextMessage(strtext);
            ui->textEdit_msgList->append("服务器给["+websocket->origin()+"]发送--->"+strtext);
        }
        ui->textEdit->clear();

    }
}

客户端

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

#include<QAbstractSocket>
#include<QWebSocket>
#include<QJsonDocument>
#include<QJsonObject>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private:
    Ui::Widget *ui;

    class QWebSocket *websocket; //套接字
    void receivedMsgFunc(const QString &msg); //接收消息
    bool bConnect = false;

private slots:
    void onerrorFunc(QAbstractSocket::SocketError error);
    void on_pushButton_start_clicked();
    void on_pushButton_sendmsg_clicked();
};
#endif // WIDGET_H
#include "widget.h"
#include "ui_widget.h"
#include <QMessageBox>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    websocket = nullptr;
}

Widget::~Widget()
{
    delete ui;
    if(websocket)
        websocket->close();
}

void Widget::receivedMsgFunc(const QString &msg)
{
    QJsonParseError error;
    QJsonDocument jsd = QJsonDocument::fromJson(msg.toUtf8().data(),&error);

    if(jsd.isNull()) //如果解析失败 则直接显示
    {
        ui->textEdit_msglist->append(msg);
    }
    else
    {
        QJsonObject jsobj = jsd.object();
        ui->textEdit_msglist->append("收到来自"+jsobj["SRC"].toString()+"的消息"+jsobj["msg"].toString());
    }
}

void Widget::onerrorFunc(QAbstractSocket::SocketError error)
{
    ui->textEdit_msglist->append(websocket->origin()+"出错"+websocket->errorString());
}

void Widget::on_pushButton_start_clicked()
{
    if(!websocket) //实现连接与断开服务器
    {
        //判断服务器名称是否为空
        if(ui->lineEdit_name->text().trimmed().isEmpty())
        {
            QMessageBox::critical(this,"错误","服务器名称为空",QMessageBox::Yes);
            return;
        }
        websocket = new QWebSocket(ui->lineEdit_name->text().trimmed(),QWebSocketProtocol::VersionLatest,this);
        //连接服务器
        connect(websocket,&QWebSocket::connected,this,[this]
        {
            ui->textEdit_msglist->append("已经连接上"+websocket->peerAddress().toString());
            bConnect = true;
            ui->pushButton_start->setText("断开服务器");
        });
        //断开服务器
        connect(websocket,&QWebSocket::disconnected,this,[this]
        {
            ui->textEdit_msglist->append("已"+websocket->peerAddress().toString()+"断开连接");
            bConnect = false;
            ui->pushButton_start->setText("连接服务器");
        });
        //连接发生的错误
        connect(websocket,SIGNAL(error(QAbstractSocket::SocketError)),this,SLOT(onerrorFunc(QAbstractSocket::SocketError)));
        //接收消息
        connect(websocket,&QWebSocket::textMessageReceived,this,&Widget::receivedMsgFunc);

    }
    if(!bConnect)
        websocket->open(QUrl(ui->lineEdit_serveraddress->text().trimmed()));
    else
    {
        websocket->close();
        websocket->deleteLater();
        websocket=nullptr;
    }
}

void Widget::on_pushButton_sendmsg_clicked()
{
    if(!websocket)
        return;
    //如果套接字已准备好读写,则返回true;否则返回false。
    if(!websocket->isValid())
        return;
    //获取发送数据的信息
    QString str = ui->textEdit_senddata->toPlainText().trimmed();
    if(str.isEmpty())
        return;
    //获取客户端名称
    QString strclient = ui->lineEdit_sendmsg->text().trimmed();
    if(strclient.isEmpty())
    {
        websocket->sendTextMessage(str);
        ui->textEdit_msglist->append("发送消息"+str);
    }
    else
    {
        QJsonObject json;
        json["src"] = websocket->origin();
        json["dst"] = strclient;
        json["msg"] = str;
        websocket->sendTextMessage(QString(QJsonDocument(json).toJson()));

        ui->textEdit_msglist->append("给客户端"+strclient+"发送消息:"+str);
    }
    ui->textEdit_senddata->clear();
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值