Qt网络应用开发

Qt提供四种网络类:

1、高度封装的网络通信类:QNetworkRequest、QNetworkAccessManager和QNetworkReply。

应用方向:QNetworkRequest类似于HTTP请求,它被传递给QNetworkAccessManager,以便在连线上发送请求;这个类返回一个QNetworkReply,它支持解析HTTP响应。

    1)QNetworkAccessManager 负责发送请求和接收返回结果的管理类,QNetworkRequest表示发送的请求,QNetworkReply表示接收到的返回结果。简单例子:

有关http 请求方法、请求头、响应头的介绍

https://www.cnblogs.com/lexiaofei/p/6943690.html

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QNetworkAccessManager mgr;//网络管理对象
    //获取当前的网络状态
    if(mgr.networkAccessible() == QNetworkAccessManager::Accessible){
        qDebug()<< "当前网咯可用";
    }
    //监控当前的网络状态
    QObject::connect(&mgr,&QNetworkAccessManager::networkAccessibleChanged,
                     [=](QNetworkAccessManager::NetworkAccessibility accessible){
        switch(accessible){
        case QNetworkAccessManager::Accessible:
            qDebug() << "无法确定网络可访问性。";
            break;
        case QNetworkAccessManager::NotAccessible:
            qDebug() << "网络当前不可访问,或者是因为当前没有网络覆盖,或者由于调用setNetworkAccessible()显式地禁用了网络访问。";
            break;
        case QNetworkAccessManager::UnknownAccessibility:
            qDebug() << "网络是可访问的。";
            break;
        }
    });

    {
        //获得当前的网络配置
        QNetworkConfiguration configuration = mgr.activeConfiguration();
        qDebug() << "当前的承载类型:" << configuration.bearerType() << " " << configuration.bearerTypeName();
        qDebug() << "当前承载类型所属的族:" << configuration.bearerTypeFamily();
        //返回此网络配置的所有子配置。只有ServiceNetwork类型的网络配置才有列表其他类型都为空
        QList<QNetworkConfiguration> subConfigurationList = configuration.children();
        qDebug() << "子配置个数:" << subConfigurationList.size();
        qDebug() << "此配置的连接超时:" << configuration.connectTimeout();
        qDebug() << "唯一的与平台相关的标识符:" << configuration.identifier();
        qDebug() << "此网络是否支持漫游:" << configuration.isRoamingAvailable();
        qDebug() << "此配置是否有效:" << configuration.isValid();
        qDebug() << "该配置的名称:" << configuration.name();
        qDebug() << "此配置的用途:" << configuration.purpose();
        qDebug() << "此配置的当前状态:" << configuration.state();
        qDebug() << "此配置的类型:" << configuration.type();
    }

    //默认情况下,QNetworkAccessManager没有设置缓存。
    mgr.cache();
    qDebug() << "将用于创建在处理网络请求时使用的网络会话的网络配置:" << mgr.configuration().name();//基本上和当前活动的配置一样

    //连接到主机
//    mgr.connectToHost("");//http
    //使用ssl配置连接到主机
//    mgr.connectToHostEncrypted("");//https

    {
        /*
         * 获取从网络获得的cookie以及即将发送的cookie。远程服务器在响应请求时设置cookie,并期望在发送其他请求时返回相同的cookie。
         * cookie jar是保存以前请求中设置的所有cookie的对象。Web浏览器将cookie jar保存到磁盘,
         * 以便在应用程序的各个调用之间保存永久cookie。
         * QNetworkCookieJar不实现永久存储:它只将cookie保存在内存中。一旦删除QNetworkCookieJar对象,
         * 它持有的所有cookie也将被丢弃。如果您想保存cookie,您应该从这个类派生并将保存到磁盘的操作实现为您自己的存储格式。
         * 返回的cookjar是系统自动处理的,如果我们需要手动处理 则需要重新继承QNetworkCookieJar然后实现虚函数
         */
        QNetworkCookieJar *jar = mgr.cookieJar();
    }

    //发送请求以删除由请求URL标识的资源。
    //    mgr.deleteResource();
    qDebug() << "支持的url模式:" << mgr.supportedSchemes();

    //构造一http个请求
    QNetworkRequest request;
    qDebug() << "此请求允许遵循的最大重定向数:" << request.maximumRedirectsAllowed();
    QObject *object = request.originatingObject();
    if(object == 0)
        qDebug() << "对发起此网络请求的对象的引用:空";
    qDebug() << "该请求的默认优先级:" << request.priority();
    //获得默认的请求头及内容--默认是空的
    foreach (QByteArray header, request.rawHeaderList()) {
        qDebug() << request.rawHeader(header);
    }

    //设置请求头
    //此属性一般是服务器发送给web浏览器的
    //        request.setHeader(QNetworkRequest::ContentDispositionHeader,"attachment;filename:aaa.txt");//表示作为附件下载
    //        request.setHeader(QNetworkRequest::ContentDispositionHeader,"inline ;filename:aaa.txt");//表示在线打开这个文件

    /*
         * Http Header里的Content-Type一般有这四三种:
         * application/x-www-form-urlencoded:数据被编码为名称/值对。这是标准的编码格式。也是默认的
         * multipart/form-data: 数据被编码为一条消息,页上的每个控件对应消息中的一个部分。
         * text/plain: 数据以纯文本形式(text/json/xml/html)进行编码,其中不含任何控件或格式字符
         * application/json:数据以json格式编码
         */
//    request.setHeader(QNetworkRequest::ContentTypeHeader,"application/json");
    //传输数据的字节长度
    //        request.setHeader(QNetworkRequest::ContentLengthHeader,0);
    //服务器发送给web浏览器的重定向的URL
    //        request.setHeader(QNetworkRequest::LocationHeader,"");
    //服务器发送的。 WEB 服务器认为对象的最后修改时间,比如文件的最后修改时间,动态页面的最后产生时间等等。
    //        request.setHeader(QNetworkRequest::LastModifiedHeader,"Fri , 12 May 2006 18:53:33 GMT");
    //从客户端发送cookie给服务器的时候,是不发送cookie的各个属性的,而只是发送对应的名称和值。
    //        request.setHeader(QNetworkRequest::CookieHeader,"name=value;name2=value2");
    //从服务器端,发送cookie给客户端,是对应的Set-Cookie。包括了对应的cookie的名称,值,以及各个属性。
    //        request.setHeader(QNetworkRequest::SetCookieHeader,"lu=Rg3vHJZnehYLjVg7qi3bZjzg; Expires=Tue, 15 Jan 2013 21:47:38 GMT; Path=/; Domain=.169it.com; HttpOnly");
    /*
         * 第一部分:Mozilla/5.0 平台这部分可由多个字符串组成,用英文半角分号分开
         * 第二部分:平台这部分可由多个字符串组成,用英文半角分号分开
         * 第三部分:引擎版本
         * 第四部分:浏览器版本
         */
//            request.setHeader(QNetworkRequest::UserAgentHeader,"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3100.0 Safari/537.36");
    //指明HTTP服务器的软件信息
    //        request.setHeader(QNetworkRequest::ServerHeader,"Microsoft-IIS/7.5 ");

    //Qt没有包含的
    //浏览器端可以接受的媒体类型
    request.setRawHeader("Accept","text/html");
    //浏览器申明自己接收的编码方法,通常指定压缩方法,是否支持压缩,支持什么压缩方法
//    request.setRawHeader("Accept-Encoding","gzip, deflate");
    //浏览器申明自己接收的语言
//    request.setRawHeader("Accept-Language","zh-CN");
    //浏览器申明自己接收的字符集
//    request.setRawHeader("Accept-Charset","utf-8");

    //设置支持的最大的重定向数量
//    request.setMaximumRedirectsAllowed(0);
    //设置ssl配置
    //        request.setSslConfiguration();
    //设置要访问的网址
    //在请求前 先握个手
    request.setUrl(QUrl::fromUserInput("https://www.jianshu.com/"));

    //使用简单的get方法
    QNetworkReply *reply_ = mgr.get(request);
    qint64 readBytes = 0;

    QObject::connect(&mgr,&QNetworkAccessManager::finished,[=](QNetworkReply *reply){
        if(reply_ == reply){
            QJsonArray array1;
//            QJsonArray array2;
            foreach (QNetworkReply::RawHeaderPair pair, reply_->rawHeaderPairs()) {
                QJsonObject obj;
                obj.insert(QString(pair.first),QString(pair.second));
                array1.append(obj);
            }
//            foreach (const QByteArray byte , reply_->request().rawHeaderList()) {
//                QJsonObject obj;
//                obj.insert(QString(byte),QString(reply_->request().rawHeader(byte)));
//                array2.append(obj);
//            }
//            qDebug() << "array1=" << array1;
//            qDebug() << "array2=" << array2;
        }
        reply_->deleteLater();
    });

    QObject::connect(reply_,&QNetworkReply::readyRead,[=,&readBytes]{
        if(reply_->bytesAvailable() > 0){
            readBytes += reply_->bytesAvailable();
            //数据量不大的话直接读取,否则要按固定字节读取
            if(reply_->hasRawHeader("Content-Length")){
                int length = reply_->rawHeader("Content-Length").toInt();
                qDebug() << "Content-Length:"<<length;
                if(length == 0)
                    return;
            }
            QByteArray bytes = reply_->readAll();
//            QFile file("F:/aaa.html");
//            file.open(QIODevice::WriteOnly);
//            file.write(bytes);
//            qDebug() << "已读字节数:" << readBytes << "内容:" << bytes;
        }
    });

    QObject::connect(reply_,QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error),[=](QNetworkReply::NetworkError error){
        qDebug() << "error:" << reply_->errorString();
    });

    return a.exec();
}

很多文件支持通过http请求来下载:

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QNetworkAccessManager mgr;
    QNetworkReply *reply = nullptr;
    QNetworkRequest request(QUrl("http://ftp.sjtu.edu.cn/ubuntu-cd/18.04.1/ubuntu-18.04.1-desktop-amd64.iso"));
    QFile file("F:/ubuntu-18.04.1-desktop-amd64.iso");
    file.open(QIODevice::WriteOnly|QIODevice::Append);

    reply = mgr.get(request);

    //处理结束
    QObject::connect(reply,&QNetworkReply::finished,[=,&file]{
        //写入不足1M的
        file.write(reply->readAll());
        file.close();
        qDebug() << "下载完成";
        reply->deleteLater();
    });

    //下载进度
    QObject::connect(reply,&QNetworkReply::downloadProgress,
                     [=](qint64 bytesReceived, qint64 bytesTotal){
        qDebug() << "已下载:" << bytesReceived << "字节" << " 总大小:" << bytesTotal;
    });

    //错误信息
    QObject::connect(reply,QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error),
                     [=](QNetworkReply::NetworkError){
        qDebug() << reply->errorString();

    });

    QObject::connect(reply,&QNetworkReply::readyRead,[=,&file]{
        if(reply->bytesAvailable() > 10000000){
            qDebug() << "当前写入:" << reply->bytesAvailable() << "字节";
            file.write(reply->readAll());
        }
    });

    //检测reply是否被删除了
    QObject::connect(reply,&QNetworkReply::destroyed,[]{
        qDebug() << "删除了 reply";
    });

    return a.exec();
}

Http Post 请求:

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    setWindowTitle("测试免费的http接口");
    resize(400,300);
    QTextBrowser *borwser = new QTextBrowser;
    setCentralWidget(borwser);

    QMenu *menu = menuBar()->addMenu("http/https接口测试");
    QPixmap pixmap(menu->size().height(),menu->size().height());
    pixmap.fill(Qt::red);
    QAction *taobaoTel = menu->addAction(QIcon(pixmap), "淘宝查询电话号码归属地");
    pixmap.fill(Qt::yellow);
    QAction *expressNumber = menu->addAction(QIcon(pixmap),"快递单号查询");
    pixmap.fill(Qt::blue);
    QAction *locationInfo = menu->addAction(QIcon(pixmap),"输入IP地址查询国家、城市、所有者等信息");

    //网络对象
    QNetworkAccessManager *mgr = new QNetworkAccessManager(this);

    //设置各个action的响应
    connect(taobaoTel,&QAction::triggered,[=]{
        QNetworkRequest request;
        QUrl url("https://tcc.taobao.com/cc/json/mobile_tel_segment.htm?");
        QUrlQuery query;
        query.addQueryItem("tel","15850890324");
        url.setQuery(query);
        request.setUrl(url);
        //设置请求头
        request.setHeader(QNetworkRequest::ContentTypeHeader,"application/javascript;charset=GBK");
        QNetworkReply *reply = mgr->get(request);

        connect(reply,&QNetworkReply::finished,[=]{
            //因为返回的是gbk编码的文字 所以需要转码
            //构造一个转码对象
            QTextCodec *codec = QTextCodec::codecForName("GBK");
            borwser->setPlainText(codec->toUnicode(reply->readAll()));
        });
    });

    connect(expressNumber,&QAction::triggered,[=]{
        /*
         * 快递公司编码:
         * 申通=”shentong” EMS=”ems” 顺丰=”shunfeng” 圆通=”yuantong”
         * 中通=”zhongtong” 韵达=”yunda” 天天=”tiantian” 汇通=”huitongkuaidi”
         * 全峰=”quanfengkuaidi” 德邦=”debangwuliu” 宅急送=”zhaijisong”
         */
        QNetworkRequest request;
        QUrl url("http://www.kuaidi100.com/query?");
        QUrlQuery query;
        query.addQueryItem("type","zhongtong");
        query.addQueryItem("postid","73107853468716");
        url.setQuery(query);
        request.setUrl(url);
        request.setRawHeader("Content-Type","text/html;charset=UTF-8");
        QNetworkReply *reply = mgr->get(request);
        connect(reply,&QNetworkReply::finished,[=]{
            borwser->setHtml(reply->readAll());
        });
    });

    connect(locationInfo,&QAction::triggered,[=]{
        QNetworkRequest request(QUrl("http://www.webxml.com.cn/WebServices/IpAddressSearchWebService.asmx/getCountryCityByIp"));
        request.setHeader(QNetworkRequest::ContentLengthHeader,28);
        request.setHeader(QNetworkRequest::ContentTypeHeader,"application/x-www-form-urlencoded");
        QByteArray postData("theIpAddress=180.201.120.247");
        QNetworkReply *reply = mgr->post(request, postData);
        connect(reply,&QNetworkReply::finished,[=]{
            borwser->setPlainText(reply->readAll());
        });

        connect(reply,QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error),[=](QNetworkReply::NetworkError){
            reply->errorString();
        });
    });
}

2、简单封装的通信类:QTcpSocket、QUdpSocket和QSslSocket、QTcpServer。

应用方向:TCP/IP层通信。这些类通过waitFor*方法提供同步API和异步API;如果可能(例如,如果事件循环正在运行),应该始终首选异步API。

网络编程API:混个眼熟先

类名 功能

QUrl

一个使用url的方便接口。

QUrlQuery

一种在URL查询中操作键-值对的方法

QSocketNotifier

支持监视文件描述符上的活动

QHstsPolicy

主机支持HTTP严格传输安全策略(HSTS)

QHttpMultiPart

类似于通过HTTP发送的MIME多部分消息

QHttpPart

保存要在HTTP多部分MIME消息中使用的主体部分

QNetworkAccessManager

允许应用程序发送网络请求和接收回复

QNetworkReply

包含使用QNetworkAccessManager发送的请求的数据和头部

QNetworkRequest

保存使用QNetworkAccessManager发送的请求

QNetworkConfigurationManager

管理系统提供的网络配置

QNetworkConfiguration

提供一个或多个接入点配置的抽象

QNetworkSession

提供对系统访问点的控制,并支持在多个客户机访问同一访问点时进行会话管理

QAuthenticator

提供一个身份验证对象

QDnsDomainNameRecord

存储有关域名记录的信息

QDnsHostAddressRecord

存储有关主机地址记录的信息

QDnsLookup

表示DNS查询

QDnsMailExchangeRecord

存储关于DNS MX记录的信息

QDnsServiceRecord

存储关于DNS SRV记录的信息

QDnsTextRecord

存储关于DNS TXT记录的信息

QHostAddress

提供IP地址

QHostInfo

为主机名查找提供静态函数

QNetworkDatagram

提供UDP数据报的数据和元数据

QNetworkAddressEntry

存储网络接口支持的一个IP地址及其关联的网络掩码和广播地址

QNetworkInterface

提供主机的IP地址和网络接口的列表

QNetworkProxy

提供网络层代理

QNetworkProxyFactory

提供细粒度代理选择

QAbstractSocket

提供所有套接字类型通用的基本功能

QSctpServer

提供基于sctp的服务器

QSctpSocket

提供一个SCTP套接字

QTcpServer

提供基于tcp的服务器

QTcpSocket

提供TCP套接字

QUdpSocket

提供UDP套接字

QSsl

QSsl命名空间声明了Qt网络中所有SSL类的公共枚举

QSslCertificate

为X509证书提供方便的API

QSslCertificateExtension

提供了一个API来访问X509证书的扩展。

QSslCipher

表示SSL加密密码

QSslConfiguration

保存SSL连接的配置和状态

QSslDiffieHellmanParameters

为服务器的Diffie-Hellman参数提供接口

QSslEllipticCurve

表示椭圆曲线密码算法使用的椭圆曲线

QSslError

提供SSL错误

QSslKey

为私钥和公钥提供接口

QSslPreSharedKeyAuthenticator

为预共享密钥(PSK)密码套件提供身份验证数据

QSslSocket

为客户端和服务器提供SSL加密套接字

1)QHostInfo、QHostAddress类

void printAddress(QString hostName, QHostInfo hostInfo)
{
    QList<QHostAddress> addressList = hostInfo.addresses();
    foreach (QHostAddress address, addressList) {
        if(address.protocol() == QAbstractSocket::IPv4Protocol){
            qDebug() << hostName << "-IPv4:" << address.toString();
        }else if(address.protocol() == QAbstractSocket::IPv6Protocol){
            qDebug()<< hostName << "-IPv6:" << address.toString();
        }else if(address.protocol() == QAbstractSocket::AnyIPProtocol){
            qDebug()<< hostName << "-IPv4/IPv6:" << address.toString();
        }else if(address.protocol() == QAbstractSocket::UnknownNetworkLayerProtocol){
            qDebug()<< hostName << "-other:" << address.toString();
        }
    }
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    qDebug() << "本机DNS域=" << QHostInfo::localDomainName();
    qDebug() << "主机名=" << QHostInfo::localHostName();
    QHostInfo hostInfo = QHostInfo::fromName(QHostInfo::localHostName());
    printAddress(QHostInfo::localHostName(),hostInfo);
    QHostInfo::lookupHost("www.baidu.com",0,[=](QHostInfo hostInfo){
        printAddress("www.baidu.com",hostInfo);
    });

    QHostInfo::lookupHost("www.12306.com",0,[=](QHostInfo hostInfo){
        printAddress("www.12306.com",hostInfo);
    });

    QHostInfo::lookupHost("www.jd.com",0,[=](QHostInfo hostInfo){
        printAddress("www.jd.com",hostInfo);
    });

    return a.exec();
}

2)QNetworkInterface类

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QList<QHostAddress> addressList = QNetworkInterface::allAddresses();
    foreach (QHostAddress address, addressList) {
        if(address.protocol() == QAbstractSocket::IPv4Protocol){
            qDebug() << "IPv4:" << address.toString();
        }else if(address.protocol() == QAbstractSocket::IPv6Protocol){
            qDebug()<< "IPv6:" << address.toString();
        }else if(address.protocol() == QAbstractSocket::AnyIPProtocol){
            qDebug()<< "IPv4/IPv6:" << address.toString();
        }else if(address.protocol() == QAbstractSocket::UnknownNetworkLayerProtocol){
            qDebug()<< "other:" << address.toString();
        }
    }

    QList<QNetworkInterface> interfaceList = QNetworkInterface::allInterfaces();
    foreach (QNetworkInterface interface, interfaceList) {
        qDebug() << "硬件地址:" << interface.hardwareAddress();
        qDebug() << "Windows上此网络接口的可读名称:" << interface.humanReadableName();
        qDebug() << "此网络接口的名称:" << interface.name();
        qDebug() << "";
    }

    return a.exec();
}

3)QSctpServer编写服务端、QTcpSocket客户端。简单说下这个例子的逻辑:

客户端向服务器端上传文件。服务器端支持多线程处理客户端的连接请求,即支持多个客户端的上传文件。

ClientThread :为服务器端处理文件上传的类

ClientWidget : 为客户端界面,向服务器上传文件

MainWindow :  为服务器端界面,显示各个客户端连接断开的信息

class ClientThread : public QObject
{
    Q_OBJECT
public:
    explicit ClientThread(QObject *parent = nullptr);
    ClientThread(QTcpSocket *socket, MainWindow *mainWindow);
    ~ClientThread();

signals:
    void showMessage(QString msg);
public slots:

private:
    MainWindow *m_mainWindow;
    QTcpSocket *m_client;

    bool isHead;
    QFile m_file;
    qint64 m_fileSize;
    qint64 m_receievedSize;
};
ClientThread::ClientThread(QTcpSocket *socket, MainWindow *mainWindow)
    :m_client(socket),m_mainWindow(mainWindow),isHead(true),m_fileSize(0),m_receievedSize(0)
{
    connect(m_client,&QTcpSocket::disconnected,this,[=]{
        m_mainWindow->removeClient(m_client);
        delete this;
    });

    connect(m_client,QOverload<QAbstractSocket::SocketError>::of(&QTcpSocket::error),[=](QAbstractSocket::SocketError){
        showMessage(m_client->errorString());
    });

    connect(m_client,&QTcpSocket::readyRead,[=]{
        //首次获取数据为头部信息
        if(isHead){
            isHead = false;
            //获取头部信息
            QDataStream in(m_client->readAll());
            QString fileName;
            in >> fileName >> m_fileSize;
            showMessage(QString("接收到客户端文件信息,文件名称:%1,文件大小:%2").arg(fileName).arg(m_fileSize));
            QString localFilePath = QString("F:/%1").arg(fileName);
            qDebug()<< "服务器路径:" << localFilePath;
            m_file.setFileName(localFilePath);
            m_file.open(QIODevice::WriteOnly|QIODevice::Append);
        }else{
            //正常的接收文件
            m_receievedSize += m_file.write(m_client->readAll());
            if(m_receievedSize == m_fileSize){
                m_file.close();
                showMessage("文件接收完毕");
                isHead = true;
                m_receievedSize = 0;
            }
        }
    });
}
class ClientWidget : public QWidget
{
    Q_OBJECT
public:
    explicit ClientWidget(QWidget *parent = nullptr);
protected:
    void closeEvent(QCloseEvent *event);
signals:

public slots:
private:
    QTcpSocket *m_socket;

    QFile m_file;
    qint64 m_payloadSize;//负载大小
};
ClientWidget::ClientWidget(QWidget *parent) : QWidget(parent),m_socket(new QTcpSocket)
{
    m_payloadSize = 1024*1024;//负载大小 1M
    setWindowTitle(tr("Client"));
    QVBoxLayout *mainLayout = new QVBoxLayout;
    setLayout(mainLayout);

    QTextBrowser *browser = new QTextBrowser;
    mainLayout->addWidget(browser);

    QFormLayout *formLayout = new QFormLayout;
    QLineEdit *ipEdit = new QLineEdit(tr("127.0.0.1"));
    formLayout->addRow(tr("服务器ip:"), ipEdit);
    QLineEdit *portEdit = new QLineEdit(tr("8888"));
    formLayout->addRow(tr("服务器port:"), portEdit);
    mainLayout->addLayout(formLayout);

    QHBoxLayout *buttonLayout = new QHBoxLayout;
    QPushButton *connectButton = new QPushButton(tr("连接服务器"));
    QPushButton *disconnectButton = new QPushButton(tr("与服务器断开"));
    QPushButton *uploadFileButton = new QPushButton(tr("上传文件"));
    uploadFileButton->setEnabled(false);
    buttonLayout->addWidget(connectButton);
    buttonLayout->addWidget(disconnectButton);
    buttonLayout->addWidget(uploadFileButton);
    mainLayout->addLayout(buttonLayout);

    QProgressBar *bar = new QProgressBar;
    mainLayout->addWidget(bar);

    connect(connectButton,&QPushButton::clicked,[=]{
        if(m_socket->state() == QAbstractSocket::ConnectedState)
            return;
        m_socket->connectToHost(ipEdit->text().trimmed(),portEdit->text().trimmed().toInt());
    });
    connect(disconnectButton,&QPushButton::clicked,[=]{
        m_socket->disconnectFromHost();
        m_socket->close();
    });

    connect(m_socket,&QTcpSocket::connected,[=]{
        uploadFileButton->setEnabled(true);
        browser->append(tr("连接上服务器,ip:%1 port:%2").arg(ipEdit->text().trimmed()).arg(portEdit->text().trimmed().toInt()));
    });

    connect(m_socket,QOverload<QAbstractSocket::SocketError>::of(&QTcpSocket::error),[=](QAbstractSocket::SocketError){
        browser->append(m_socket->errorString());
        m_socket->close();
    });

    connect(uploadFileButton,&QPushButton::clicked,[=]{
        //选择一个文件
        QString filePath = QFileDialog::getOpenFileName(this,"选择上传文件","F:/","Text(*.txt *.doc)");
        qDebug() << filePath;
        //文件名称 文件大小
        QFileInfo info(filePath);
        QString fileName = info.fileName();
        qint64 fileSize = info.size();
        bar->setMaximum(fileSize+fileName.toUtf8().size());
        bar->setMinimum(0);

        //写入流中
        QByteArray head;
        QDataStream sendOut(&head,QIODevice::WriteOnly);
        sendOut << fileName << fileSize;

        //打开文件
        m_file.setFileName(filePath);
        bool ok = m_file.open(QIODevice::ReadOnly);
        if(!ok){
            QMessageBox::critical(this,"打开文件","打开文件失败");
            return;
        }

        //发送到服务器端
        m_socket->write(head);
    });

    connect(m_socket,&QTcpSocket::bytesWritten,[=](qint64 bytes){
        bar->setValue(bar->maximum()-m_file.bytesAvailable());

        if(m_file.bytesAvailable() >= m_payloadSize){
            m_socket->write(m_file.read(m_payloadSize));
        }else if(m_file.bytesAvailable() > 0){
            m_socket->write(m_file.readAll());
        }else{
            m_file.close();
            browser->append("文件发送完毕");
        }
    });
}

void ClientWidget::closeEvent(QCloseEvent *event)
{
    qDebug() << __FUNCTION__;
    if(m_file.isOpen())
        m_file.close();
    m_socket->disconnectFromHost();
    m_socket->close();
    QWidget::closeEvent(event);
}
class MainWindow : public QMainWindow
{
    Q_OBJECT

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

    void removeClient(QTcpSocket *socket);

protected:
    void closeEvent(QCloseEvent *event);

private:
    QTcpServer *server;
    QList<QTcpSocket*> clients;

    QMutex m_mutex;
};
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent),server(new QTcpServer)
{
    setWindowTitle(tr("Server"));
    QWidget *widget = new QWidget;
    QVBoxLayout *mainLayout = new QVBoxLayout;
    widget->setLayout(mainLayout);
    setCentralWidget(widget);

    QGroupBox *groupBox = new QGroupBox(tr("客户端连接信息展示"));
    QVBoxLayout *groupLayout = new QVBoxLayout;
    groupBox->setLayout(groupLayout);
    QTextBrowser *browser = new QTextBrowser;
    groupLayout->addWidget(browser);
    groupBox->setLayout(groupLayout);
    mainLayout->addWidget(groupBox);

    QHBoxLayout *buttonLayout = new QHBoxLayout;
    QPushButton *startButton = new QPushButton(tr("启动"));
    startButton->setSizePolicy(QSizePolicy::Fixed,QSizePolicy::Fixed);
    buttonLayout->addWidget(startButton,0,Qt::AlignLeft);
    QPushButton *closeButton = new QPushButton(tr("断开"));
    closeButton->setSizePolicy(QSizePolicy::Fixed,QSizePolicy::Fixed);
    buttonLayout->addWidget(closeButton,0,Qt::AlignRight);
    mainLayout->addLayout(buttonLayout);

    connect(startButton,&QPushButton::clicked,[=]{
        if(server->isListening())
            return;
        bool ok= server->listen(QHostAddress::LocalHost,8888);
        if(ok){
            browser->append(tr("服务器端开始监听..."));
        }else{
            browser->append(tr("服务器端在此端口监听失败!"));
        }
    });

    connect(closeButton,&QPushButton::clicked,[=]{
        foreach (QTcpSocket * socket, clients) {
            socket->disconnectFromHost();
            socket->close();
        }
    });

    connect(server,&QTcpServer::newConnection,[=]{
        QTcpSocket *socket = server->nextPendingConnection();
        clients << socket;
        browser->append(QString("已连接客户端name:%1 ip:%2 port:%3").arg(socket->peerName()).arg(socket->peerAddress().toString()).arg(socket->peerPort()));
        QThread *thread = new QThread;
        ClientThread *client = new ClientThread(socket,this);
        connect(client,&ClientThread::destroyed,thread,[=]{
            thread->quit();
            thread->wait();
            delete thread;
            browser->append(tr("线程删除"));
        });
        connect(client,&ClientThread::showMessage,[=](QString msg){
            browser->append(msg);
        });
    });
}

MainWindow::~MainWindow()
{

}

void MainWindow::removeClient(QTcpSocket *socket)
{
    if(nullptr == socket)
        return;
    QMutexLocker locker(&m_mutex);
    clients.removeOne(socket);
}

void MainWindow::closeEvent(QCloseEvent *event)
{
    qDebug() << __FUNCTION__;
    foreach (QTcpSocket * socket, clients) {
        socket->disconnectFromHost();
        socket->close();
    }
}
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();

    ClientWidget client;
    client.show();

//    ClientWidget client1;
//    client1.show();

//    ClientWidget client2;
//    client2.show();

    return a.exec();
}

3、和web端通信的专用类:QWebSocket、QWebSocketServer。

应用方向:直接TCP或HTTP通信的另一种选择是使用WebSocket协议(RFC 6455)。它是TCP协议之上的双向通信协议,可以利用现有的web基础设施,而不必创建额外的基于客户机-服务器的通信。

WebSocket是一种基于web的协议,旨在支持客户端应用程序和远程主机之间的双向通信。它允许两个实体在初始握手成功时来回发送数据。WebSocket是一种解决方案,适用于那些需要通过更少的网络延迟和最小的数据交换来获取实时数据的应用程序。

这里是对websocket的详细介绍:

https://www.cnblogs.com/fuqiang88/p/5956363.html

简单的例子:

webServer端代码:

WebServer::WebServer(QWidget *parent) : QMainWindow(parent)
{
    QTextBrowser *browser = new QTextBrowser;
    setCentralWidget(browser);

    QWebSocketServer *server = new QWebSocketServer("Echo Server",QWebSocketServer::NonSecureMode,this);
    server->listen(QHostAddress::LocalHost,8888);
    connect(server,&QWebSocketServer::newConnection,[=]{
        QWebSocket *socket = server->nextPendingConnection();
        connect(socket,&QWebSocket::textMessageReceived,[=](const QString &message){
            browser->append(message);
            socket->sendTextMessage(message);
        });
    });
}

webClient端代码:

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    QVBoxLayout *mainLayout = new QVBoxLayout;
    setLayout(mainLayout);

    QFormLayout *formLayout = new QFormLayout;
    QLineEdit *urlEdit = new QLineEdit("ws://localhost:8888");
    formLayout->addRow("Url:",urlEdit);
    mainLayout->addLayout(formLayout);

    QTextBrowser *browser = new QTextBrowser;
    mainLayout->addWidget(browser);

    QPushButton *button = new QPushButton("连接");
    button->setSizePolicy(QSizePolicy::Fixed,QSizePolicy::Fixed);
    mainLayout->addWidget(button,0,Qt::AlignHCenter);

    QLineEdit *textEdit = new QLineEdit;
    mainLayout->addWidget(textEdit);

    QPushButton *sendBtn = new QPushButton("发送数据");
    mainLayout->addWidget(sendBtn);

    QTimer *timer = new QTimer(this);
    socket = new QWebSocket;

    connect(socket,&QWebSocket::textMessageReceived,[=](const QString &message){
        browser->append("textReceiveString" + message);
    });

    connect(socket,&QWebSocket::disconnected,[=]{
        timer->start(3000);
        browser->append("websocket is disconnected");
    });

    connect(socket,&QWebSocket::connected,[=]{
        timer->stop();
        browser->append("connect successful");
    });

    connect(button,&QPushButton::clicked,[=]{
        socket->open(QUrl(urlEdit->text().trimmed()));
    });

    connect(timer,&QTimer::timeout,[=]{
        browser->append("websocket reconnected");
        socket->abort();
        socket->open(QUrl(urlEdit->text().trimmed()));
    });

    connect(sendBtn,&QPushButton::clicked,[=]{
        socket->sendTextMessage(textEdit->text().trimmed());
    });
}

main.cpp:

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();

    WebServer server;
    server.show();

    return a.exec();
}

4、进程间通信类:QLocalSocket、QLocalServer、QSharedMemory、QProcess。

1)跨平台共享内存类QSharedMemory提供了对操作系统共享内存实现的访问。它允许多个线程和进程安全访问共享内存段。此外,QSystemSemaphore可用于控制对系统共享的资源的访问,以及进程之间的通信

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    resize(300,200);
    QVBoxLayout *mainLayout = new QVBoxLayout;
    setLayout(mainLayout);

    QDialogButtonBox *buttonBox = new QDialogButtonBox(Qt::Horizontal,this);
    QPushButton *writeBtn = buttonBox->addButton(tr("共享内存写入"),QDialogButtonBox::NoRole);
    QPushButton *readBtn = buttonBox->addButton(tr("共享内存读取"),QDialogButtonBox::NoRole);
    mainLayout->addWidget(buttonBox,0,Qt::AlignHCenter);

    QLabel *label = new QLabel;
    QPixmap pixmap(size().width(),size().height()-buttonBox->height());
    pixmap.fill(Qt::yellow);
    label->setPixmap(pixmap);
    mainLayout->insertWidget(0,label);

    //构造共享内存对象
    QSharedMemory *smem = new QSharedMemory;
    //设置一个key
    smem->setKey("MySharedMemoryKey");

    //写入
    connect(writeBtn,&QPushButton::clicked,[=]{
        if(smem->isAttached()){
            qDebug() << "当前进程关联了该共享内存";
            if(smem->detach()){
                qDebug() << "释放了关联:成功";
            }else{
                qDebug() << "释放关联:失败";
                return;
            }
        }

        QString imagePath = QFileDialog::getOpenFileName(this,"选择一张图片","","Image (*.png, *.jpg)");
        QImage image;
        if(!image.load(imagePath)){
            qDebug() << "加载图片失败,请重新选择图片";
            return;
        }
        label->setPixmap(QPixmap::fromImage(image));

        QByteArray bytes;
        QDataStream ds(&bytes,QIODevice::WriteOnly);
        ds << image;

        //为共享内存创建图片大小的空间
        if(!smem->create(image.byteCount())){
            qDebug() << "共享内存创建空间大小失败";
            return;
        }
        qDebug() << "共享内存大小:" << smem->size();

        //对共享内存进行写操作
        if(!smem->lock()){
            qDebug() << "锁失败";
            return;
        }

        memcpy((char*)smem->data(),bytes.constData(),bytes.size());

        //解锁
        if(!smem->unlock()){
            qDebug() << "解锁失败";
            return;
        }
        label->setText("共享内存写入成功");
    });

    //读取
    connect(readBtn,&QPushButton::clicked,[=]{
        if(!smem->isAttached()){
            if(!smem->attach()){
                qDebug() << "关联共享内存失败:" << smem->errorString() << smem->error();
                return;
            }
        }
        //对共享内存进行读操作
        if(!smem->lock()){
            qDebug() << "锁失败";
            return;
        }

        qDebug() << "共享内存大小:" << smem->size();
        QByteArray bytes((char*)smem->data(),smem->size());
        QDataStream ds(&bytes,QIODevice::ReadOnly);
        QImage image;
        ds >> image;

        //解锁
        if(!smem->unlock()){
            qDebug() << "解锁失败";
            return;
        }

        label->setPixmap(QPixmap::fromImage(image));
    });
}

2)跨平台类QProcess可用于作为子进程启动外部程序,并与之通信。它提供了用于监视和控制子进程状态的API。QProcess通过继承QIODevice来访问子进程的输入/输出通道。

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    resize(300,200);
    QVBoxLayout *mainLayout = new QVBoxLayout;
    setLayout(mainLayout);

    QPushButton *btn = new QPushButton("选择");
    btn->setSizePolicy(QSizePolicy::Fixed,QSizePolicy::Fixed);
    mainLayout->addStretch();
    mainLayout->addWidget(btn,0,Qt::AlignHCenter);
    mainLayout->addStretch();
    QTextBrowser *log = new QTextBrowser;
    log->append(tr("Log:"));
    mainLayout->addWidget(log,0,Qt::AlignHCenter);
    mainLayout->addStretch();

    QPushButton *exitBtn = new QPushButton("结束进程");
    exitBtn->setSizePolicy(QSizePolicy::Fixed,QSizePolicy::Fixed);
    mainLayout->addWidget(exitBtn,0,Qt::AlignHCenter);

    QProcess *process = new QProcess(this);

    connect(btn,&QPushButton::clicked,[=]{
        if(process->state() == QProcess::Running){
            log->append((tr("Log: The process is running")));
            return;
        }
        QString path = QFileDialog::getOpenFileName(this,"选择程序","D:/","EXE (*.exe)");
        qDebug() << path;
        process->start(path);

        //阻塞等待程序启动  也可以使用信号 如果在主线程 会锁死界面 最好放进线程中去
        process->waitForStarted();
        log->append("waitForStarted");

        //阻塞等待程序结束 也可以使用信号 如果在主线程 会锁死界面 最好放进线程中去
        process->waitForFinished();
        log->append("waitForFinished");
    });

    //进程错误信号
    connect(process,QOverload<QProcess::ProcessError>::of(&QProcess::errorOccurred),[=](QProcess::ProcessError error){
        log->append(process->errorString());
    });

    //进程启动信号
    connect(process,&QProcess::started,[=]{
        log->append(tr("The process started"));
    });

    //进程结束信号
    connect(process,QOverload<int,QProcess::ExitStatus>::of(&QProcess::finished),[=](int exitCode, QProcess::ExitStatus exitStatus){
        log->append(tr("The process finished.exitCode:%1").arg(exitCode));
    });

    //杀死进程
    connect(exitBtn,&QPushButton::clicked,[=]{
        if(process->state() == QProcess::Running){
//            process->kill();//立即结束进程,错误信号会返回crashed
            process->terminate();//尝试结束进程,因为进程中的程序可能占用了其他资源,不会立即结束进程
            log->append(tr("terminate the process!"));
        }
    });
    
    //还可以与外部程序进行通信,需要外部程序有相关的代码配合 这里暂不学习了.
}

3)QLocalSocket类提供了一个本地套接字。在Windows上这是一个命名管道,在Unix上这是一个本地域套接字。QLocalServer类提供了一个基于本地套接字的服务器。该类使接受传入的本地套接字连接成为可能。

尽管QLocalSocket是为使用事件循环而设计的,但是不使用事件循环也是可以的。在这种情况下,您必须使用waitForConnected()、waitForReadyRead()、waitForBytesWritten()和waitForDisconnected(),它们会阻塞,直到操作完成或超时过期。

尽管QLocalServer是为使用事件循环而设计的,但也可以不使用事件循环而使用它。在这种情况下,您必须使用waitForNewConnection(),它将阻塞,直到连接可用或超时过期。

本地客户端:

Client::Client(QWidget *parent) : QWidget(parent)
{
    //去掉窗口右上角的X
    setWindowFlags(windowFlags()^Qt::WindowCloseButtonHint);
    resize(300,200);
    QVBoxLayout *mainLayout = new QVBoxLayout;
    setLayout(mainLayout);

    QTextBrowser *browser = new QTextBrowser;
    mainLayout->addWidget(browser);

    QDialogButtonBox *buttonBox = new QDialogButtonBox;
    QPushButton *connectBtn = buttonBox->addButton("连接服务器",QDialogButtonBox::NoRole);
    QPushButton *disconnectBtn = buttonBox->addButton("断开连接",QDialogButtonBox::NoRole);
    QPushButton *closeBtn = buttonBox->addButton("退出",QDialogButtonBox::NoRole);
    mainLayout->addWidget(buttonBox);

    //客户端套接字
    QLocalSocket *client = new QLocalSocket(this);

    connect(connectBtn,&QPushButton::clicked,[=]{
        client->connectToServer("myLocalServer");
    });

    connect(disconnectBtn,&QPushButton::clicked,[=]{
        browser->append("主动断开与服务器的连接...");
        client->disconnectFromServer();
    });

    connect(client,&QLocalSocket::connected,[=]{
        browser->append("连接myLocalServer成功");
        QString str = "hello localServer";
        browser->append(tr("向服务器发送:%1").arg(str));
        client->write(str.toUtf8());
    });
    connect(client,&QLocalSocket::disconnected,[=]{
        browser->append("与myLocalServer断开了连接");
    });

    //错误
    connect(client,QOverload<QLocalSocket::LocalSocketError>::of(&QLocalSocket::error),
            [=](QLocalSocket::LocalSocketError){
        browser->append(client->errorString());
        qDebug() << client->error();
    });

    connect(closeBtn,&QPushButton::clicked,[=]{
        client->abort();
        close();
    });
}

本地服务器端:

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    resize(300,200);
    QVBoxLayout *mainLayout = new QVBoxLayout;
    setLayout(mainLayout);

    QTextBrowser *browser = new QTextBrowser;
    mainLayout->addWidget(browser);

    QDialogButtonBox *buttonBox = new QDialogButtonBox;
    QPushButton *listenBtn = buttonBox->addButton((tr("开始监听...")),QDialogButtonBox::NoRole);
    QPushButton *closeBtn = buttonBox->addButton("停止监听,不再接入新的连接",QDialogButtonBox::NoRole);
    mainLayout->addWidget(buttonBox);

    //创建本地服务
    QLocalServer *server = new QLocalServer(this);
    //获得默认的支持的最大连接数
    browser->append(QString("默认最大连接数:%1").arg(server->maxPendingConnections()));
    //获取默认的套接字选项
    qDebug() << server->socketOptions();

    //启动监听
    connect(listenBtn,&QPushButton::clicked,[=]{
        if(server->isListening()){
            browser->append(tr("当前服务正在监听..."));
            return;
        }
        browser->append(QString("移除之前的服务:%1").arg(QLocalServer::removeServer("myLocalServer")));
        if(server->listen("myLocalServer")){
            browser->append("监听成功");
        }else{
            browser->append(tr("监听失败:%1").arg(server->errorString()));
        }
    });

    connect(closeBtn,&QPushButton::clicked,[=]{
        browser->append("停止监听,不再接入新的连接");
        server->close();
    });

    //监听新的连接
    connect(server,&QLocalServer::newConnection,[=]{
        QLocalSocket *socket = server->nextPendingConnection();
        connect(socket,&QLocalSocket::readyRead,[=]{
            browser->append(QString("客户端说:%1").arg(QString(socket->readAll())));
        });
    });

}

猜你喜欢

转载自blog.csdn.net/wei375653972/article/details/86154187