【正点原子Linux连载】第十一章 网络编程 摘自【正点原子】I.MX6U嵌入式Qt开发指南V1.0.2

第十一章 网络编程

Qt网络模块为我们提供了编写TCP / IP客户端和服务器的类。它提供了较低级别的类,例如代表低级网络概念的QTcpSocket,QTcpServer和QUdpSocket,以及诸如QNetworkRequest,QNetworkReply和QNetworkAccessManager之类的高级类来执行使用通用协议的网络操作。 它还提供了诸如QNetworkConfiguration,QNetworkConfigurationManager和QNetworkSession等类,实现承载管理。
想要在程序中使用Qt网络模块,我们需要在pro项目配置文件里增加下面的一条语句。
QT += network

11.1 获取本机的网络信息
为什么先写获取本机网络信息的内容呢?在建立网络通信之前我们至少得获取对方的IP地址。在网络应用中,经常需要用到本机的主机名、IP地址、MAC地址等网络信息,通常通在Windows通过调出命令行cmd窗口输入ipconfig或者在Linux系统中使用ifconfig命令就可以查看相关信息了,在这里我们利用Qt做出一个可以查询的界面和功能出来,为了后面的网络编程打下一个简单的基础。
Qt提供了QHostInfo和QNetworkInterface类可以用于此类信息查询。更多关于QHostInfo和QNetworkInterface的相关函数可以在Qt的帮助文档中找到。下面我们写代码时会使用到相关的函数,有清楚的注释。
11.1.1 应用实例
本例目的:了解如何通过QHostInfo和QNetworkInterface类获取本地网络所有接口的信息。
例07_networkhostinfo,获取本机网络接口信息(难度:一般)。项目路径为Qt/2/07_networkhostinfo。本例获取本机的网络接口信息,打印在文本浏览框上,点击按钮可直接获取,为了清楚看见是重新获取的过程,本例点击获取本机信息按钮后延时1s去刷新获取的信息。点击另一个清空文本信息按钮可以清空文本浏览框上的文本内容。
项目文件07_networkhostinfo.pro文件第一行添加的代码部分如下。
07_ networkhostinfo.pro编程后的代码

1   QT       += core gui network
2 
3   greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
4 
5   CONFIG += c++11
6 
7   # The following define makes your compiler emit warnings if you use
8   # any Qt feature that has been marked deprecated (the exact warnings
9   # depend on your compiler). Please consult the documentation of the
10  # deprecated API in order to know how to port your code away from it.
11  DEFINES += QT_DEPRECATED_WARNINGS
12
13  # You can also make your code fail to compile if it uses deprecated APIs.
14  # In order to do so, uncomment the following line.
15  # You can also select to disable deprecated APIs only up to a certain version of Qt.
16  #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0
17
18  SOURCES += \
19      main.cpp \
20      mainwindow.cpp
21
22  HEADERS += \
23      mainwindow.h
24
25  # Default rules for deployment.
26  qnx: target.path = /tmp/$${
    
    TARGET}/bin
27  else: unix:!android: target.path = /opt/$${
    
    TARGET}/bin
28  !isEmpty(target.path): INSTALLS += target

在头文件“mainwindow.h”具体代码如下。
mainwindow.h编程后的代码

    /******************************************************************
    Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
    * @projectName   07_networkhostinfo
    * @brief         mainwindow.h
    * @author        Deng Zhimao
    * @email         [email protected]
    * @net            www.openedv.com
    * @date           2021-04-10
    *******************************************************************/
1   #ifndef MAINWINDOW_H
2   #define MAINWINDOW_H
3 
4   #include <QMainWindow>
5   #include <QPushButton>
6   #include <QTextBrowser>
7   #include <QVBoxLayout>
8   #include <QHBoxLayout>
9   #include <QTimer>
10
11  class MainWindow : public QMainWindow
12  {
    
    
13      Q_OBJECT
14
15  public:
16      MainWindow(QWidget *parent = nullptr);
17      ~MainWindow();
18
19  private:
20      /* 点击获取和清空文本按钮 */
21      QPushButton *pushButton[2];
22
23      /* 文本浏览框用于显示本机的信息 */
24      QTextBrowser *textBrowser;
25
26      /* 水平Widget容器和垂直Widget容器*/
27      QWidget *hWidget;
28      QWidget *vWidget;
29
30      /* 水平布局和垂直布局 */
31      QHBoxLayout *hBoxLayout;
32      QVBoxLayout *vBoxLayout;
33
34      /* 定时器 */
35      QTimer *timer;
36
37      /* 获取本机的网络的信息,返回类型是QString */
38      QString getHostInfo();
39
40  private slots:
41      /* 定时器槽函数,点击按钮后定时触发 */
42      void timerTimeOut();
43
44      /* 显示本机信息 */
45      void showHostInfo();
46
47      /* 启动定时器 */
48      void timerStart();
49
50      /* 清空textBrowser的信息 */
51      void clearHostInfo();
52  };
53  #endif // MAINWINDOW_H
54

头文件里主要是声明两个按钮和一个文本浏览框。另外还有一个定时器,声明一些槽函数,比较简单。
在源文件“mainwindow.cpp”具体代码如下。
mainwindow.cpp编程后的代码

    /******************************************************************
    Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
    * @projectName   07_networkhostinfo
    * @brief         mainwindow.cpp
    * @author        Deng Zhimao
    * @email         [email protected]
    * @net            www.openedv.com
    * @date           2021-04-10
    *******************************************************************/
1   #include "mainwindow.h"
2   #include <QNetworkInterface>
3   #include <QHostInfo>
4   #include <QThread>
5   #include <QDebug>
6  
7   MainWindow::MainWindow(QWidget *parent)
8       : QMainWindow(parent)
9   {
    
    
10      /* 设置位置与大小 */
11      this->setGeometry(0, 0, 800, 480);
12 
13      /* 点击获取本地信息按钮和清空文本按钮 */
14      pushButton[0] = new QPushButton();
15      pushButton[1] = new QPushButton();
16 
17      pushButton[0]->setText("获取本机信息");
18      pushButton[1]->setText("清空文本信息");
19 
20      /* 按钮的大小根据文本自适应,
21       * 注意setSizePolicy需要在布局中使用 */
22      pushButton[0]->setSizePolicy(QSizePolicy::Fixed,
23                                   QSizePolicy::Fixed);
24      pushButton[1]->setSizePolicy(QSizePolicy::Fixed,
25                                   QSizePolicy::Fixed);
26 
27      /* 水平Widget和垂直Widget用于添加布局 */
28      hWidget = new QWidget();
29      vWidget = new QWidget();
30 
31      /* 水平布局和垂直布局 */
32      hBoxLayout = new QHBoxLayout();
33      vBoxLayout = new QVBoxLayout();
34 
35      /* 文本浏览框 */
36      textBrowser = new QTextBrowser();
37 
38      /* 添加到水平布局 */
39      hBoxLayout->addWidget(pushButton[0]);
40      hBoxLayout->addWidget(pushButton[1]);
41 
42      /* 将水平布局设置为hWidget的布局 */
43      hWidget->setLayout(hBoxLayout);
44 
45      /* 将文本浏览框和hWidget添加到垂直布局 */
46      vBoxLayout->addWidget(textBrowser);
47      vBoxLayout->addWidget(hWidget);
48 
49      /* 将垂直布局设置为vWidget的布局 */
50      vWidget->setLayout(vBoxLayout);
51 
52      /* 设置vWidget为中心部件 */
53      setCentralWidget(vWidget);
54 
55      /* 定时器初始化 */
56      timer = new QTimer();
57 
58      /* 信号槽连接 */
59      connect(pushButton[0], SIGNAL(clicked()),
60              this, SLOT(timerStart()));
61      connect(pushButton[1], SIGNAL(clicked()),
62              this, SLOT(clearHostInfo()));
63      connect(timer, SIGNAL(timeout()),
64              this, SLOT(timerTimeOut()));
65  }
66 
67  MainWindow::~MainWindow()
68  {
    
    
69  }
70 
71 
72  void MainWindow::timerStart()
73  {
    
    
74      /* 清空文本 */
75      textBrowser->clear();
76 
77      /* 定时1s */
78      timer->start(1000);
79  }
80 
81  void MainWindow::timerTimeOut()
82  {
    
    
83      /* 显示本机信息 */
84      showHostInfo();
85 
86      /* 停止定时器 */
87      timer->stop();
88  }
89 
90  QString MainWindow::getHostInfo()
91  {
    
    
92      /* 通过QHostInfo的localHostName函数获取主机名称 */
93      QString str = "主机名称:" + QHostInfo::localHostName() + "\n";
94 
95      /* 获取所有的网络接口,
96       * QNetworkInterface类提供主机的IP地址和网络接口的列表 */
97      QList<QNetworkInterface> list
98              = QNetworkInterface::allInterfaces();
99 
100     /* 遍历list */
101     foreach (QNetworkInterface interface, list) {
    
    
102         str+= "网卡设备:" + interface.name() + "\n";
103         str+= "MAC地址:" + interface.hardwareAddress() + "\n";
104
105         /* QNetworkAddressEntry类存储IP地址子网掩码和广播地址 */
106         QList<QNetworkAddressEntry> entryList
107                 = interface.addressEntries();
108
109         /* 遍历entryList */
110         foreach (QNetworkAddressEntry entry, entryList) {
    
    
111             /* 过滤IPv6地址,只留下IPv4 */
112             if (entry.ip().protocol() ==
113                     QAbstractSocket::IPv4Protocol) {
    
    
114                 str+= "IP 地址:" + entry.ip().toString() + "\n";
115                 str+= "子网掩码:" + entry.netmask().toString() + "\n";
116                 str+= "广播地址:" + entry.broadcast().toString() + "\n\n";
117             }
118         }
119     }
120
121     /* 返回网络信息 */
122     return str;
123 }
124
125 void MainWindow::showHostInfo()
126 {
    
    
127     /* 获取本机信息后显示到textBrowser */
128     textBrowser->insertPlainText(getHostInfo());
129 }
130
131 void MainWindow::clearHostInfo()
132 {
    
    
133     /* 判断textBrowser是否为空,如果不为空则清空文本 */
134     if (!textBrowser->toPlainText().isEmpty())
135
136         /* 清空文本 */
137         textBrowser->clear();
138 }
第90~123行,是本例最重要的代码。
第93行,通过QHostInfo的localHostName函数获取主机名称。
第97~98行,通过QNetworkInterface::allInterfaces()获取网络接口列表list类存储IP地址子网掩码和广播地址。如果我们用qDebug()函数打印出list,可以发现获取了所有的网络信息。而我们要提取网络里面的网络信息使用QNetworkAddressEntry。
第106~107行,使用QNetworkAddressEntry从interface接口里使用函数addressEntries(),获取所有的条目。就可以使用QNetworkAddressEntry的对象entry获取IP地址子网掩码和广播地址。

第110~118行,因为获取的entries在一个QNetworkInterface下可能有两个IP,分别是ipv4和ipv6。这里使用ip().protocol()来判断协议的类型,只留下ipv4类型的信息。筛选信息在我们写程序常常需要的。
11.1.2 程序运行效果
点击获取本机信息,在文本浏览框内就打印出本机的网络信息(包括了主机名,网卡名,ip地址等)。这里因为过滤掉了IPv6的信息。通常一个网卡有两个ip地址,一个是ipv4,另一个是ipv6的地址。下面的网卡设备lo,是本地回环网卡。另一个ens33是虚拟机的网卡,由VMware虚拟出来的。点击清空文本信息会清空文本浏览框里的网络信息。
在这里插入图片描述

11.2 TCP通信
11.2.1 TCP简介
TCP协议(Transmission Control Protocol)全称是传输控制协议是一种面向连接的、可靠的、基于字节流的传输层通信协议。
TCP通信必须先建立TCP连接,通信端分为客户端和服务端。服务端通过监听某个端口来监听是否有客户端连接到来,如果有连接到来,则建立新的socket连接;客户端通过ip和port连接服务端,当成功建立连接之后,就可进行数据的收发了。需要注意的是,在Qt中,Qt把socket当成输入输出流来对待的,数据的收发是通过read()和write()来进行的,需要与我们常见的send()与recv()进行区分。
TCP客户端与服务端通信示意图如下。
在这里插入图片描述

11.2.2 TCP服务端应用实例
本例目的:了解TCP服务端的使用。
例08_tcpserver,TCP服务端(难度:一般)。项目路径为Qt/2/08_tcpserver。本例大体流程首先获取本地IP地址。创建一个tcpSocket套接字,一个tcpServer服务端。点击监听即监听本地的主机IP地址和端口,同时等待服务端的连接。此程序需要结合客户端一起使用。
项目文件08_tcpserver.pro文件第一行添加的代码部分如下。
08_tcpserver.pro编程后的代码

1   QT       += core gui network
2 
3   greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
4 
5   CONFIG += c++11
6 
7   # The following define makes your compiler emit warnings if you use
8   # any Qt feature that has been marked deprecated (the exact warnings
9   # depend on your compiler). Please consult the documentation of the
10  # deprecated API in order to know how to port your code away from it.
11  DEFINES += QT_DEPRECATED_WARNINGS
12
13  # You can also make your code fail to compile if it uses deprecated APIs.
14  # In order to do so, uncomment the following line.
15  # You can also select to disable deprecated APIs only up to a certain version of Qt.
16  #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0
17
18  SOURCES += \
19      main.cpp \
20      mainwindow.cpp
21
22  HEADERS += \
23      mainwindow.h
24
25  # Default rules for deployment.
26  qnx: target.path = /tmp/$${
    
    TARGET}/bin
27  else: unix:!android: target.path = /opt/$${
    
    TARGET}/bin
28  !isEmpty(target.path): INSTALLS += target

在头文件“mainwindow.h”具体代码如下。
mainwindow.h编程后的代码

    /******************************************************************
    Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
    * @projectName   08_tcpserver
    * @brief         mainwindow.h
    * @author        Deng Zhimao
    * @email         [email protected]
    * @net            www.openedv.com
    * @date           2021-04-13
    *******************************************************************/
1   #ifndef MAINWINDOW_H
2   #define MAINWINDOW_H
3 
4   #include <QMainWindow>
5   #include <QTcpServer>
6   #include <QTcpSocket>
7   #include <QVBoxLayout>
8   #include <QHBoxLayout>
9   #include <QPushButton>
10  #include <QTextBrowser>
11  #include <QLabel>
12  #include <QComboBox>
13  #include <QSpinBox>
14  #include <QHostInfo>
15  #include <QLineEdit>
16  #include <QNetworkInterface>
17  #include <QDebug>
18
19  class MainWindow : public QMainWindow
20  {
    
    
21      Q_OBJECT
22
23  public:
24      MainWindow(QWidget *parent = nullptr);
25      ~MainWindow();
26
27  private:
28      /* tcp服务器 */
29      QTcpServer *tcpServer;
30
31      /* 通信套接字 */
32      QTcpSocket *tcpSocket;
33
34      /* 按钮 */
35      QPushButton *pushButton[4];
36
37      /* 标签文本 */
38      QLabel *label[2];
39
40      /* 水平容器 */
41      QWidget *hWidget[3];
42
43      /* 水平布局 */
44      QHBoxLayout *hBoxLayout[3];
45
46      /* 垂直容器 */
47      QWidget *vWidget;
48
49      /* 垂直布局 */
50      QVBoxLayout *vBoxLayout;
51
52      /* 文本浏览框 */
53      QTextBrowser *textBrowser;
54
55      /* 用于显示本地ip */
56      QComboBox *comboBox;
57
58      /* 用于选择端口 */
59      QSpinBox  *spinBox;
60
61      /* 文本输入框 */
62      QLineEdit *lineEdit;
63
64      /* 存储本地的ip列表地址 */
65      QList<QHostAddress> IPlist;
66
67      /* 获取本地的所有ip */
68      void getLocalHostIP();
69
70  private slots:
71      /* 客户端连接处理槽函数 */
72      void clientConnected();
73
74      /* 开始监听槽函数 */
75      void startListen();
76
77      /* 停止监听槽函数 */
78      void stopListen();
79
80      /* 清除文本框时的内容 */
81      void clearTextBrowser();
82
83      /* 接收到消息 */
84      void receiveMessages();
85
86      /* 发送消息 */
87      void sendMessages();
88
89      /* 连接状态改变槽函数 */
90      void socketStateChange(QAbstractSocket::SocketState);
91  };
92  #endif // MAINWINDOW_H

头文件里主要是声明界面用的元素,及一些槽函数。重点是声明tcpServer和tcpSocket。
在源文件“mainwindow.cpp”具体代码如下。
mainwindow.cpp编程后的代码

    /******************************************************************
    Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
    * @projectName   08_tcpserver
    * @brief         mainwindow.cpp
    * @author        Deng Zhimao
    * @email         [email protected]
    * @net            www.openedv.com
    * @date           2021-04-13
    *******************************************************************/
1   #include "mainwindow.h"
2  
3   MainWindow::MainWindow(QWidget *parent)
4       : QMainWindow(parent)
5   {
    
    
6       /* 设置主窗体的位置与大小 */
7       this->setGeometry(0, 0, 800, 480);
8  
9       /* 实例化tcp服务器与tcp套接字 */
10      tcpServer = new QTcpServer(this);
11      tcpSocket = new QTcpSocket(this);
12 
13      /* 开始监听按钮 */
14      pushButton[0] = new QPushButton();
15      /* 停止监听按钮 */
16      pushButton[1] = new QPushButton();
17      /* 清空聊天文本按钮 */
18      pushButton[2] = new QPushButton();
19      /* 发送消息按钮 */
20      pushButton[3] = new QPushButton();
21 
22      /* 水平布局一 */
23      hBoxLayout[0] = new QHBoxLayout();
24      /* 水平布局二 */
25      hBoxLayout[1] = new QHBoxLayout();
26      /* 水平布局三 */
27      hBoxLayout[2] = new QHBoxLayout();
28      /* 水平布局四 */
29      hBoxLayout[3] = new QHBoxLayout();
30 
31      /* 水平容器一 */
32      hWidget[0] =  new QWidget();
33      /* 水平容器二 */
34      hWidget[1] =  new QWidget();
35      /* 水平容器三 */
36      hWidget[2] =  new QWidget();
37 
38      vWidget = new QWidget();
39      vBoxLayout = new QVBoxLayout();
40 
41      /* 标签实例化 */
42      label[0] = new QLabel();
43      label[1] = new QLabel();
44 
45      lineEdit = new QLineEdit();
46      comboBox = new QComboBox();
47      spinBox = new QSpinBox();
48      textBrowser = new QTextBrowser();
49 
50      label[0]->setText("监听IP地址:");
51      label[1]->setText("监听端口:");
52 
53      /* 设置标签根据文本文字大小自适应大小  */
54      label[0]->setSizePolicy(QSizePolicy::Fixed,
55                              QSizePolicy::Fixed);
56      label[1]->setSizePolicy(QSizePolicy::Fixed,
57                              QSizePolicy::Fixed);
58 
59      /* 设置端口号的范围,注意不要与主机的已使用的端口号冲突 */
60      spinBox->setRange(10000, 99999);
61 
62      pushButton[0]->setText("开始监听");
63      pushButton[1]->setText("停止监听");
64      pushButton[2]->setText("清空文本");
65      pushButton[3]->setText("发送消息");
66 
67      /* 设置停止监听状态不可用 */
68      pushButton[1]->setEnabled(false);
69 
70      /* 设置输入框默认的文本 */
71      lineEdit->setText("www.openedv.com正点原子论坛");
72 
73      /* 水平布局一添加内容 */
74      hBoxLayout[0]->addWidget(pushButton[0]);
75      hBoxLayout[0]->addWidget(pushButton[1]);
76      hBoxLayout[0]->addWidget(pushButton[2]);
77 
78      /* 设置水平容器一的布局为水平布局一 */
79      hWidget[0]->setLayout(hBoxLayout[0]);
80 
81      /* 水平布局二添加内容 */
82      hBoxLayout[1]->addWidget(label[0]);
83      hBoxLayout[1]->addWidget(comboBox);
84      hBoxLayout[1]->addWidget(label[1]);
85      hBoxLayout[1]->addWidget(spinBox);
86 
87      /* 设置水平容器二的布局为水平布局二 */
88      hWidget[1]->setLayout(hBoxLayout[1]);
89 
90      /* 水平布局三添加内容 */
91      hBoxLayout[2]->addWidget(lineEdit);
92      hBoxLayout[2]->addWidget(pushButton[3]);
93 
94      /* 设置水平容器三的布局为水平布局一 */
95      hWidget[2]->setLayout(hBoxLayout[2]);
96 
97      /* 垂直布局添加内容 */
98      vBoxLayout->addWidget(textBrowser);
99      vBoxLayout->addWidget(hWidget[1]);
100     vBoxLayout->addWidget(hWidget[0]);
101     vBoxLayout->addWidget(hWidget[2]);
102
103     /* 设置垂直容器的布局为垂直布局 */
104     vWidget->setLayout(vBoxLayout);
105
106     /* 居中显示 */
107     setCentralWidget(vWidget);
108
109     /* 获取本地ip */
110     getLocalHostIP();
111
112     /* 信号槽连接 */
113     connect(pushButton[0], SIGNAL(clicked()),
114             this, SLOT(startListen()));
115     connect(pushButton[1], SIGNAL(clicked()),
116             this, SLOT(stopListen()));
117     connect(pushButton[2], SIGNAL(clicked()),
118             this, SLOT(clearTextBrowser()));
119     connect(pushButton[3], SIGNAL(clicked()),
120             this, SLOT(sendMessages()));
121     connect(tcpServer, SIGNAL(newConnection()),
122             this, SLOT(clientConnected()));
123 }
124
125 MainWindow::~MainWindow()
126 {
    
    
127 }
128
129 /* 新的客户端连接 */
130 void MainWindow::clientConnected()
131 {
    
    
132     /* 获取客户端的套接字 */
133     tcpSocket = tcpServer->nextPendingConnection();
134     /* 客户端的ip信息 */
135     QString ip = tcpSocket->peerAddress().toString();
136     /* 客户端的端口信息 */
137     quint16 port = tcpSocket->peerPort();
138     /* 在文本浏览框里显示出客户端的连接信息 */
139     textBrowser->append("客户端已连接");
140     textBrowser->append("客户端ip地址:"
141                         + ip);
142     textBrowser->append("客户端端口:"
143                         + QString::number(port));
144
145     connect(tcpSocket, SIGNAL(readyRead()),
146             this, SLOT(receiveMessages()));
147     connect(tcpSocket, 
148             SIGNAL(stateChanged(QAbstractSocket::SocketState)),
149             this, 
150             SLOT(socketStateChange(QAbstractSocket::SocketState)));
151 }
152
153 /* 获取本地IP */
154 void MainWindow::getLocalHostIP()
155 {
    
    
156     // /* 获取主机的名称 */
157     // QString hostName = QHostInfo::localHostName();
158
159     // /* 主机的信息 */
160     // QHostInfo hostInfo = QHostInfo::fromName(hostName);
161
162     // /* ip列表,addresses返回ip地址列表,注意主机应能从路由器获取到
163     // * IP,否则可能返回空的列表(ubuntu用此方法只能获取到环回IP) */
164     // IPlist = hostInfo.addresses();
165     // qDebug()<<IPlist<<endl;
166
167     // /* 遍历IPlist */
168     // foreach (QHostAddress ip, IPlist) {
    
    
169     //      if (ip.protocol() == QAbstractSocket::IPv4Protocol)
170     //          comboBox->addItem(ip.toString());
171     //    }
172
173     /* 获取所有的网络接口,
174      * QNetworkInterface类提供主机的IP地址和网络接口的列表 */
175     QList<QNetworkInterface> list
176             = QNetworkInterface::allInterfaces();
177
178     /* 遍历list */
179     foreach (QNetworkInterface interface, list) {
    
    
180
181         /* QNetworkAddressEntry类存储IP地址子网掩码和广播地址 */
182         QList<QNetworkAddressEntry> entryList
183                 = interface.addressEntries();
184
185         /* 遍历entryList */
186         foreach (QNetworkAddressEntry entry, entryList) {
    
    
187             /* 过滤IPv6地址,只留下IPv4 */
188             if (entry.ip().protocol() ==
189                     QAbstractSocket::IPv4Protocol) {
    
    
190                 comboBox->addItem(entry.ip().toString());
191                 /* 添加到IP列表中 */
192                 IPlist<<entry.ip();
193             }
194         }
195     }
196 }
197
198 /* 开始监听 */
199 void MainWindow::startListen()
200 {
    
    
201     /* 需要判断当前主机是否有IP项 */
202     if (comboBox->currentIndex() != -1) {
    
    
203         qDebug()<<"start listen"<<endl;
204         tcpServer->listen(IPlist[comboBox->currentIndex()],
205                 spinBox->value());
206
207         /* 设置按钮与下拉列表框的状态 */
208         pushButton[0]->setEnabled(false);
209         pushButton[1]->setEnabled(true);
210         comboBox->setEnabled(false);
211         spinBox->setEnabled(false);
212
213         /* 在文本浏览框里显示出服务端 */
214         textBrowser->append("服务器IP地址:"
215                             + comboBox->currentText());
216         textBrowser->append("正在监听端口:"
217                             + spinBox->text());
218     }
219 }
220
221 /* 停止监听 */
222 void MainWindow::stopListen()
223 {
    
    
224     qDebug()<<"stop listen"<<endl;
225     /* 停止监听 */
226     tcpServer->close();
227
228     /* 如果是连接上了也应该断开,如果不断开客户端还能继续发送信息,
229      * 因为socket未断开,还在监听上一次端口 */
230     if (tcpSocket->state() == tcpSocket->ConnectedState)
231         tcpSocket->disconnectFromHost();
232
233     /* 设置按钮与下拉列表框的状态  */
234     pushButton[1]->setEnabled(false);
235     pushButton[0]->setEnabled(true);
236     comboBox->setEnabled(true);
237     spinBox->setEnabled(true);
238
239     /* 将停止监听的信息添加到文本浏览框中 */
240     textBrowser->append("已停止监听端口:"
241                         + spinBox->text());
242 }
243
244 /* 清除文本浏览框里的内容 */
245 void MainWindow::clearTextBrowser()
246 {
    
    
247     /* 清除文本浏览器的内容 */
248     textBrowser->clear();
249 }
250
251 /* 服务端接收消息 */
252 void MainWindow::receiveMessages()
253 {
    
    
254     /* 读取接收到的消息 */
255     QString messages = "客户端:" + tcpSocket->readAll();
256     textBrowser->append(messages);
257 }
258
259 /* 服务端发送消息 */
260 void MainWindow::sendMessages()
261 {
    
    
262     if(NULL == tcpSocket)
263         return;
264
265     /* 如果已经连接 */
266     if(tcpSocket->state() == tcpSocket->ConnectedState) {
    
    
267         /* 发送消息 */
268         tcpSocket->write(lineEdit->text().toUtf8().data());
269
270         /* 在服务端插入发送的消息 */
271         textBrowser->append("服务端:" + lineEdit->text());
272     }
273 }
274
275 /* 服务端状态改变 */
276 void MainWindow::socketStateChange(QAbstractSocket::SocketState state)
277 {
    
    
278     switch (state) {
    
    
279     case QAbstractSocket::UnconnectedState:
280         textBrowser->append("scoket状态:UnconnectedState");
281         break;
282     case QAbstractSocket::ConnectedState:
283         textBrowser->append("scoket状态:ConnectedState");
284         break;
285     case QAbstractSocket::ConnectingState:
286         textBrowser->append("scoket状态:ConnectingState");
287         break;
288     case QAbstractSocket::HostLookupState:
289         textBrowser->append("scoket状态:HostLookupState");
290         break;
291     case QAbstractSocket::ClosingState:
292         textBrowser->append("scoket状态:ClosingState");
293         break;
294     case QAbstractSocket::ListeningState:
295         textBrowser->append("scoket状态:ListeningState");
296         break;
297     case QAbstractSocket::BoundState:
298         textBrowser->append("scoket状态:BoundState");
299         break;
300     default:
301         break;
302     }
303 }

上面的代码主要是服务端开启监听,如果有客户端连到服务端,就会发射newConnection()信号,同时也连接到接收消息的信号与槽函数。点击发送消息按钮就可以使用tcpSocket发送消息。注意发送消息和接收消息都是通过tcpSocket的read()和write()进行。
11.2.3 TCP客户端应用实例
本例目的:了解TCP客户的使用。
例09_tcpclient,TCP客户端(难度:一般)。项目路径为Qt/2/09_ tcpclient。本例大体流程:首先获取本地IP地址。创建一个tcpSocket套接字,然后用tcpSocket套接字使用connectToHost函数连接服务端的主机IP地址和端口,即可相互通信。
项目文件08_tcpserver.pro文件第一行添加的代码部分如下。
09_tcpclient.pro编程后的代码

1   QT       += core gui network
2 
3   greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
4 
5   CONFIG += c++11
6 
7   # The following define makes your compiler emit warnings if you use
8   # any Qt feature that has been marked deprecated (the exact warnings
9   # depend on your compiler). Please consult the documentation of the
10  # deprecated API in order to know how to port your code away from it.
11  DEFINES += QT_DEPRECATED_WARNINGS
12
13  # You can also make your code fail to compile if it uses deprecated APIs.
14  # In order to do so, uncomment the following line.
15  # You can also select to disable deprecated APIs only up to a certain version of Qt.
16  #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0
17
18  SOURCES += \
19      main.cpp \
20      mainwindow.cpp
21
22  HEADERS += \
23      mainwindow.h
24
25  # Default rules for deployment.
26  qnx: target.path = /tmp/$${
    
    TARGET}/bin
27  else: unix:!android: target.path = /opt/$${
    
    TARGET}/bin
28  !isEmpty(target.path): INSTALLS += target

在头文件“mainwindow.h”具体代码如下。
mainwindow.h编程后的代码

    /******************************************************************
    Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
    * @projectName   09_tcpclient
    * @brief         mainwindow.h
    * @author        Deng Zhimao
    * @email         [email protected]
    * @net            www.openedv.com
    * @date           2021-04-13
    *******************************************************************/
1   #ifndef MAINWINDOW_H
2   #define MAINWINDOW_H
3 
4   #include <QMainWindow>
5   #include <QTcpServer>
6   #include <QTcpSocket>
7   #include <QVBoxLayout>
8   #include <QHBoxLayout>
9   #include <QPushButton>
10  #include <QTextBrowser>
11  #include <QLabel>
12  #include <QComboBox>
13  #include <QSpinBox>
14  #include <QHostInfo>
15  #include <QLineEdit>
16  #include <QNetworkInterface>
17  #include <QDebug>
18
19  class MainWindow : public QMainWindow
20  {
    
    
21      Q_OBJECT
22
23  public:
24      MainWindow(QWidget *parent = nullptr);
25      ~MainWindow();
26
27  private:
28      /* 通信套接字 */
29      QTcpSocket *tcpSocket;
30
31      /* 按钮 */
32      QPushButton *pushButton[4];
33
34      /* 标签文本 */
35      QLabel *label[2];
36
37      /* 水平容器 */
38      QWidget *hWidget[3];
39
40      /* 水平布局 */
41      QHBoxLayout *hBoxLayout[3];
42
43      /* 垂直容器 */
44      QWidget *vWidget;
45
46      /* 垂直布局 */
47      QVBoxLayout *vBoxLayout;
48
49      /* 文本浏览框 */
50      QTextBrowser *textBrowser;
51
52      /* 用于显示本地ip */
53      QComboBox *comboBox;
54
55      /* 用于选择端口 */
56      QSpinBox  *spinBox;
57
58      /* 文本输入框 */
59      QLineEdit *lineEdit;
60
61      /* 存储本地的ip列表地址 */
62      QList<QHostAddress> IPlist;
63
64      /* 获取本地的所有ip */
65      void getLocalHostIP();
66
67  private slots:
68      /* 连接 */
69      void toConnect();
70
71      /* 断开连接 */
72      void toDisConnect();
73
74      /* 已连接 */
75      void connected();
76
77      /* 已断开连接 */
78      void disconnected();
79
80      /* 清除文本框时的内容 */
81      void clearTextBrowser();
82
83      /* 接收到消息 */
84      void receiveMessages();
85
86      /* 发送消息 */
87      void sendMessages();
88
89      /* 连接状态改变槽函数 */
90      void socketStateChange(QAbstractSocket::SocketState);
91  };
92  #endif // MAINWINDOW_H

头文件里主要是声明界面用的元素,及一些槽函数。重点是声明tcpSocket。
在源文件“mainwindow.cpp”具体代码如下。
mainwindow.cpp编程后的代码

    /******************************************************************
    Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
    * @projectName   09_tcpclient
    * @brief         mainwindow.cpp
    * @author        Deng Zhimao
    * @email         [email protected]
    * @net            www.openedv.com
    * @date           2021-04-13
    *******************************************************************/
1   #include "mainwindow.h"
2  
3   MainWindow::MainWindow(QWidget *parent)
4       : QMainWindow(parent)
5   {
    
    
6       /* 设置主窗体的位置与大小 */
7       this->setGeometry(0, 0, 800, 480);
8  
9       /* tcp套接字 */
10      tcpSocket = new QTcpSocket(this);
11 
12      /* 开始监听按钮 */
13      pushButton[0] = new QPushButton();
14      /* 停止监听按钮 */
15      pushButton[1] = new QPushButton();
16      /* 清空聊天文本按钮 */
17      pushButton[2] = new QPushButton();
18      /* 发送消息按钮 */
19      pushButton[3] = new QPushButton();
20 
21      /* 水平布局一 */
22      hBoxLayout[0] = new QHBoxLayout();
23      /* 水平布局二 */
24      hBoxLayout[1] = new QHBoxLayout();
25      /* 水平布局三 */
26      hBoxLayout[2] = new QHBoxLayout();
27      /* 水平布局四 */
28      hBoxLayout[3] = new QHBoxLayout();
29 
30      /* 水平容器一 */
31      hWidget[0] =  new QWidget();
32      /* 水平容器二 */
33      hWidget[1] =  new QWidget();
34      /* 水平容器三 */
35      hWidget[2] =  new QWidget();
36 
37 
38      vWidget = new QWidget();
39      vBoxLayout = new QVBoxLayout();
40 
41      /* 标签实例化 */
42      label[0] = new QLabel();
43      label[1] = new QLabel();
44 
45      lineEdit = new QLineEdit();
46      comboBox = new QComboBox();
47      spinBox = new QSpinBox();
48      textBrowser = new QTextBrowser();
49 
50      label[0]->setText("服务器地址:");
51      label[1]->setText("服务器端口:");
52 
53      /* 设置标签根据文本文字大小自适应大小  */
54      label[0]->setSizePolicy(QSizePolicy::Fixed,
55                              QSizePolicy::Fixed);
56      label[1]->setSizePolicy(QSizePolicy::Fixed,
57                              QSizePolicy::Fixed);
58 
59      /* 设置端口号的范围,注意不要与主机的已使用的端口号冲突 */
60      spinBox->setRange(10000, 99999);
61 
62      pushButton[0]->setText("连接服务器");
63      pushButton[1]->setText("断开连接");
64      pushButton[2]->setText("清空文本");
65      pushButton[3]->setText("发送消息");
66 
67      /* 设置停止监听状态不可用 */
68      pushButton[1]->setEnabled(false);
69 
70      /* 设置输入框默认的文本 */
71      lineEdit->setText("广州星翼电子科技有限公司");
72 
73      /* 水平布局一添加内容 */
74      hBoxLayout[0]->addWidget(pushButton[0]);
75      hBoxLayout[0]->addWidget(pushButton[1]);
76      hBoxLayout[0]->addWidget(pushButton[2]);
77 
78      /* 设置水平容器的布局为水平布局一 */
79      hWidget[0]->setLayout(hBoxLayout[0]);
80 
81      hBoxLayout[1]->addWidget(label[0]);
82      hBoxLayout[1]->addWidget(comboBox);
83      hBoxLayout[1]->addWidget(label[1]);
84      hBoxLayout[1]->addWidget(spinBox);
85 
86      /* 设置水平容器的布局为水平布局二 */
87      hWidget[1]->setLayout(hBoxLayout[1]);
88 
89      /* 水平布局三添加内容 */
90      hBoxLayout[2]->addWidget(lineEdit);
91      hBoxLayout[2]->addWidget(pushButton[3]);
92 
93      /* 设置水平容器三的布局为水平布局一 */
94      hWidget[2]->setLayout(hBoxLayout[2]);
95 
96      /* 垂直布局添加内容 */
97      vBoxLayout->addWidget(textBrowser);
98      vBoxLayout->addWidget(hWidget[1]);
99      vBoxLayout->addWidget(hWidget[0]);
100     vBoxLayout->addWidget(hWidget[2]);
101
102     /* 设置垂直容器的布局为垂直布局 */
103     vWidget->setLayout(vBoxLayout);
104
105     /* 居中显示 */
106     setCentralWidget(vWidget);
107
108     /* 获取本地ip */
109     getLocalHostIP();
110
111     /* 信号槽连接 */
112     connect(pushButton[0], SIGNAL(clicked()),
113             this, SLOT(toConnect()));
114     connect(pushButton[1], SIGNAL(clicked()),
115             this, SLOT(toDisConnect()));
116     connect(pushButton[2], SIGNAL(clicked()),
117             this, SLOT(clearTextBrowser()));
118     connect(pushButton[3], SIGNAL(clicked()),
119             this, SLOT(sendMessages()));
120     connect(tcpSocket, SIGNAL(connected()),
121             this, SLOT(connected()));
122     connect(tcpSocket, SIGNAL(disconnected()),
123             this, SLOT(disconnected()));
124     connect(tcpSocket, SIGNAL(readyRead()),
125             this, SLOT(receiveMessages()));
126     connect(tcpSocket,
127             SIGNAL(stateChanged(QAbstractSocket::SocketState)),
128             this,
129             SLOT(socketStateChange(QAbstractSocket::SocketState)));
130 }
131
132 MainWindow::~MainWindow()
133 {
    
    
134 }
135
136 void MainWindow::toConnect()
137 {
    
    
138     /* 如果连接状态还没有连接 */
139     if (tcpSocket->state() != tcpSocket->ConnectedState) {
    
    
140         /* 指定IP地址和端口连接 */
141         tcpSocket->connectToHost(IPlist[comboBox->currentIndex()],
142                 spinBox->value());
143     }
144 }
145
146 void MainWindow::toDisConnect()
147 {
    
    
148     /* 断开连接 */
149     tcpSocket->disconnectFromHost();
150
151     /* 关闭socket*/
152     tcpSocket->close();
153 }
154
155 void MainWindow::connected()
156 {
    
    
157     /* 显示已经连接 */
158     textBrowser->append("已经连上服务端");
159
160     /* 设置按钮与下拉列表框的状态 */
161     pushButton[0]->setEnabled(false);
162     pushButton[1]->setEnabled(true);
163     comboBox->setEnabled(false);
164     spinBox->setEnabled(false);
165 }
166
167 void MainWindow::disconnected()
168 {
    
    
169     /* 显示已经断开连接 */
170     textBrowser->append("已经断开服务端");
171
172     /* 设置按钮与下拉列表框的状态  */
173     pushButton[1]->setEnabled(false);
174     pushButton[0]->setEnabled(true);
175     comboBox->setEnabled(true);
176     spinBox->setEnabled(true);
177 }
178
179 /* 获取本地IP */
180 void MainWindow::getLocalHostIP()
181 {
    
    
182     // /* 获取主机的名称 */
183     // QString hostName = QHostInfo::localHostName();
184
185     // /* 主机的信息 */
186     // QHostInfo hostInfo = QHostInfo::fromName(hostName);
187
188     // /* ip列表,addresses返回ip地址列表,注意主机应能从路由器获取到
189     // * IP,否则可能返回空的列表(ubuntu用此方法只能获取到环回IP) */
190     // IPlist = hostInfo.addresses();
191     // qDebug()<<IPlist<<endl;
192
193     // /* 遍历IPlist */
194     // foreach (QHostAddress ip, IPlist) {
    
    
195     //      if (ip.protocol() == QAbstractSocket::IPv4Protocol)
196     //          comboBox->addItem(ip.toString());
197     //    }
198
199     /* 获取所有的网络接口,
200      * QNetworkInterface类提供主机的IP地址和网络接口的列表 */
201     QList<QNetworkInterface> list
202             = QNetworkInterface::allInterfaces();
203
204     /* 遍历list */
205     foreach (QNetworkInterface interface, list) {
    
    
206
207         /* QNetworkAddressEntry类存储IP地址子网掩码和广播地址 */
208         QList<QNetworkAddressEntry> entryList
209                 = interface.addressEntries();
210
211         /* 遍历entryList */
212         foreach (QNetworkAddressEntry entry, entryList) {
    
    
213             /* 过滤IPv6地址,只留下IPv4 */
214             if (entry.ip().protocol() ==
215                     QAbstractSocket::IPv4Protocol) {
    
    
216                 comboBox->addItem(entry.ip().toString());
217                 /* 添加到IP列表中 */
218                 IPlist<<entry.ip();
219             }
220         }
221     }
222 }
223
224 /* 清除文本浏览框里的内容 */
225 void MainWindow::clearTextBrowser()
226 {
    
    
227     /* 清除文本浏览器的内容 */
228     textBrowser->clear();
229 }
230
231 /* 客户端接收消息 */
232 void MainWindow::receiveMessages()
233 {
    
    
234     /* 读取接收到的消息 */
235     QString messages = tcpSocket->readAll();
236     textBrowser->append("服务端:" + messages);
237 }
238
239 /* 客户端发送消息 */
240 void MainWindow::sendMessages()
241 {
    
    
242     if(NULL == tcpSocket)
243         return;
244
245     if(tcpSocket->state() == tcpSocket->ConnectedState) {
    
    
246         /* 客户端显示发送的消息 */
247         textBrowser->append("客户端:" + lineEdit->text());
248
249         /* 发送消息 */
250         tcpSocket->write(lineEdit->text().toUtf8().data());
251     }
252 }
253
254 /* 客户端状态改变 */
255 void MainWindow::socketStateChange(QAbstractSocket::SocketState state)
256 {
    
    
257     switch (state) {
    
    
258     case QAbstractSocket::UnconnectedState:
259         textBrowser->append("scoket状态:UnconnectedState");
260         break;
261     case QAbstractSocket::ConnectedState:
262         textBrowser->append("scoket状态:ConnectedState");
263         break;
264     case QAbstractSocket::ConnectingState:
265         textBrowser->append("scoket状态:ConnectingState");
266         break;
267     case QAbstractSocket::HostLookupState:
268         textBrowser->append("scoket状态:HostLookupState");
269         break;
270     case QAbstractSocket::ClosingState:
271         textBrowser->append("scoket状态:ClosingState");
272         break;
273     case QAbstractSocket::ListeningState:
274         textBrowser->append("scoket状态:ListeningState");
275         break;
276     case QAbstractSocket::BoundState:
277         textBrowser->append("scoket状态:BoundState");
278         break;
279     default:
280         break;
281     }
282 }

上面的代码主要是客户端开使用connectToHost通过IP地址和端口与服务端连接,如果连接成功,就会发射connected ()信号,同时也连接到接收消息的信号与槽函数。点击发送消息按钮就可以使用tcpSocket发送消息。注意发送消息和接收消息都是通过tcpSocket的read()和write()进行。
11.2.4 程序运行效果
开启服务端后,需要选择本地监听的IP地址和监听的端口(特别需要注意,不要选择监听的端口与本地主机的已经使用的端口,所以编者把端口号设置的特别大,查看本地已经使用的端口号可以使用netstat指令。)
启动客户端后,选择需要连接的服务器IP地址和服务器监听的端口。点击连接后就可以相互发送消息了。
注意服务端和客户端都本例都是选择了本地环回IP 127.0.0.1测试。也可以选择本地的其他IP地址进行测试。
TCP服务端:
在这里插入图片描述

TCP客户端:

在这里插入图片描述

11.3 UDP通信
11.3.1 UDP简介
UDP(User Datagram Protocol即用户数据报协议)是一个轻量级的,不可靠的,面向数据报的无连接协议。我们日常生活中使用的QQ,其聊天时的文字内容是使用UDP协议进行消息发送的。因为QQ有很多用户,发送的大部分都是短消息,要求能及时响应,并且对安全性要求不是很高的情况下使用UDP协议。但是QQ也并不是完全使用UDP协议,比如我们在传输文件时就会选择TCP协议,保证文件正确传输。像QQ语音和QQ视频通话,UDP的优势就很突出了。在选择使用协议的时候,选择UDP必须要谨慎。在网络质量令人十分不满意的环境下,UDP协议数据包丢失会比较严重。但是由于UDP的特性:它不属于连接型协议,因而具有资源消耗小,处理速度快的优点,所以通常音频、视频和普通数据在传送时使用UDP较多,因为它们即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。
QUdpSocket类提供了一个UDP套接字。QUdpSocket是QAbstractSocket的子类,允许发送和接收UDP数据报。使用该类最常见的方法是使用bind()绑定到一个地址和端口,然后调用writeDatagram()和readDatagram() / receiveDatagram()来传输数据。注意发送数据一般少于512字节。如果发送多于512字节的数据,即使我们发送成功了,也会在IP层被分片(分成小片段)。
如果您想使用标准的QIODevice函数read()、readLine()、write()等,您必须首先通过调用connectToHost()将套接字直接连接到对等体。每次将数据报写入网络时,套接字都会发出bytesWritten()信号。
如果您只是想发送数据报,您不需要调用bind()。readyRead()信号在数据报到达时发出。在这种情况下,hasPendingDatagrams()返回true。调用pendingDatagramSize()来获取第一个待处理数据报的大小,并调用readDatagram()或receiveDatagram()来读取它。注意:当您接收到readyRead()信号时,一个传入的数据报应该被读取,否则这个信号将不会被发送到下一个数据报。
UDP通信示意图如下。重点是QUdpSocket类,已经为我们提供了UDP通信的基础。

在这里插入图片描述

UDP消息传送有三种模式,分别是单播、广播和组播三种模式。
在这里插入图片描述

单播(unicast):单播用于两个主机之间的端对端通信,需要知道对方的IP地址与端口。
广播(broadcast):广播UDP与单播UDP的区别就是IP地址不同,广播一般使用广播地址255.255.255.255,将消息发送到在同一广播(也就是局域网内同一网段)网络上的每个主机。值得强调的是:本地广播信息是不会被路由器转发。当然这是十分容易理解的,因为如果路由器转发了广播信息,那么势必会引起网络瘫痪。这也是为什么IP协议的设计者故意没有定义互联网范围的广播机制。广播地址通常用于在网络游戏中处于同一本地网络的玩家之间交流状态信息等。其实广播顾名思义,就是想局域网内所有的人说话,但是广播还是要指明接收者的端口号的,因为不可能接受者的所有端口都来收听广播。
组播(multicast):组播(多点广播),也称为“多播”,将网络中同一业务类型主机进行了逻辑上的分组,进行数据收发的时候其数据仅仅在同一分组中进行,其他的主机没有加入此分组不能收发对应的数据。在广域网上广播的时候,其中的交换机和路由器只向需要获取数据的主机复制并转发数据。主机可以向路由器请求加入或退出某个组,网络中的路由器和交换机有选择地复制并传输数据,将数据仅仅传输给组内的主机。多播的这种功能,可以一次将数据发送到多个主机,又能保证不影响其他不需要(未加入组)的主机的其他通信。
注意:单播一样和多播是允许在广域网即Internet上进行传输的,而广播仅仅在同一局域网上才能进行。
11.3.2 UDP单播与广播
广播UDP与单播UDP的区别就是IP地址不同,所以我们的实例可以写成一个。我们可以这么理解,单播实际上是通信上对应一对一,广播则是一对多(多,这里指广播地址内的所有主机)。
11.3.2.1 应用实例
本例目的:了解QUdpSocket单播和广播使用。
例10_udp_unicast_broadcast,UDP单播与广播应用(难度:一般)。项目路径为Qt/2/10_udp_unicast_broadcast。本例大体流程首先获取本地IP地址。创建一个udpSocket套接字,然后绑定本地主机的端口(也就是监听端口)。我们可以使用QUdpSocket类提供的读写函数readDatagram和writeDatagram,知道目标IP地址和端口,即可完成消息的接收与发送。
项目文件10_udp_unicast_broadcast.pro文件第一行添加的代码部分如下。
10_udp_unicast_broadcast.pro编程后的代码

1   QT       += core gui network
2 
3   greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
4 
5   CONFIG += c++11
6 
7   # The following define makes your compiler emit warnings if you use
8   # any Qt feature that has been marked deprecated (the exact warnings
9   # depend on your compiler). Please consult the documentation of the
10  # deprecated API in order to know how to port your code away from it.
11  DEFINES += QT_DEPRECATED_WARNINGS
12
13  # You can also make your code fail to compile if it uses deprecated APIs.
14  # In order to do so, uncomment the following line.
15  # You can also select to disable deprecated APIs only up to a certain version of Qt.
16  #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0
17
18  SOURCES += \
19      main.cpp \
20      mainwindow.cpp
21
22  HEADERS += \
23      mainwindow.h
24
25  # Default rules for deployment.
26  qnx: target.path = /tmp/$${
    
    TARGET}/bin
27  else: unix:!android: target.path = /opt/$${
    
    TARGET}/bin
28  !isEmpty(target.path): INSTALLS += target

在头文件“mainwindow.h”具体代码如下。
mainwindow.h编程后的代码

    /******************************************************************
    Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
    * @projectName   10_udp_unicast_broadcast
    * @brief         mainwindow.h
    * @author        Deng Zhimao
    * @email         [email protected]
    * @net            www.openedv.com
    * @date           2021-04-14
    *******************************************************************/
1   #ifndef MAINWINDOW_H
2   #define MAINWINDOW_H
3 
4   #include <QMainWindow>
5   #include <QUdpSocket>
6   #include <QVBoxLayout>
7   #include <QHBoxLayout>
8   #include <QPushButton>
9   #include <QTextBrowser>
10  #include <QLabel>
11  #include <QComboBox>
12  #include <QSpinBox>
13  #include <QHostInfo>
14  #include <QLineEdit>
15  #include <QNetworkInterface>
16  #include <QDebug>
17
18  class MainWindow : public QMainWindow
19  {
    
    
20      Q_OBJECT
21
22  public:
23      MainWindow(QWidget *parent = nullptr);
24      ~MainWindow();
25
26  private:
27      /* Udp通信套接字 */
28      QUdpSocket *udpSocket;
29
30      /* 按钮 */
31      QPushButton *pushButton[5];
32
33      /* 标签文本 */
34      QLabel *label[3];
35
36      /* 水平容器 */
37      QWidget *hWidget[3];
38
39      /* 水平布局 */
40      QHBoxLayout *hBoxLayout[3];
41
42      /* 垂直容器 */
43      QWidget *vWidget;
44
45      /* 垂直布局 */
46      QVBoxLayout *vBoxLayout;
47
48      /* 文本浏览框 */
49      QTextBrowser *textBrowser;
50
51      /* 用于显示本地ip */
52      QComboBox *comboBox;
53
54      /* 用于选择端口 */
55      QSpinBox  *spinBox[2];
56
57      /* 文本输入框 */
58      QLineEdit *lineEdit;
59
60      /* 存储本地的ip列表地址 */
61      QList<QHostAddress> IPlist;
62
63      /* 获取本地的所有ip */
64      void getLocalHostIP();
65
66  private slots:
67      /* 绑定端口 */
68      void bindPort();
69
70      /* 解绑端口 */
71      void unbindPort();
72
73      /* 清除文本框时的内容 */
74      void clearTextBrowser();
75
76      /* 接收到消息 */
77      void receiveMessages();
78
79      /* 发送消息 */
80      void sendMessages();
81
82      /* 广播消息 */
83      void sendBroadcastMessages();
84
85      /* 连接状态改变槽函数 */
86      void socketStateChange(QAbstractSocket::SocketState);
87  };
88  #endif // MAINWINDOW_H

头文件里主要是声明界面用的元素,及一些槽函数。重点是声明udpSocket。
在源文件“mainwindow.cpp”具体代码如下。
mainwindow.cpp编程后的代码

    /******************************************************************
    Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
    * @projectName   10_udp_unicast_broadcast
    * @brief         mainwindow.cpp
    * @author        Deng Zhimao
    * @email         [email protected]
    * @net            www.openedv.com
    * @date           2021-04-14
    *******************************************************************/
1   #include "mainwindow.h"
2  
3   MainWindow::MainWindow(QWidget *parent)
4       : QMainWindow(parent)
5   {
    
    
6       /* 设置主窗体的位置与大小 */
7       this->setGeometry(0, 0, 800, 480);
8  
9       /* udp套接字 */
10      udpSocket = new QUdpSocket(this);
11 
12      /* 绑定端口按钮 */
13      pushButton[0] = new QPushButton();
14      /* 解绑端口按钮 */
15      pushButton[1] = new QPushButton();
16      /* 清空聊天文本按钮 */
17      pushButton[2] = new QPushButton();
18      /* 发送消息按钮 */
19      pushButton[3] = new QPushButton();
20      /* 广播消息按钮 */
21      pushButton[4] = new QPushButton();
22 
23      /* 水平布局一 */
24      hBoxLayout[0] = new QHBoxLayout();
25      /* 水平布局二 */
26      hBoxLayout[1] = new QHBoxLayout();
27      /* 水平布局三 */
28      hBoxLayout[2] = new QHBoxLayout();
29      /* 水平布局四 */
30      hBoxLayout[3] = new QHBoxLayout();
31 
32      /* 水平容器一 */
33      hWidget[0] =  new QWidget();
34      /* 水平容器二 */
35      hWidget[1] =  new QWidget();
36      /* 水平容器三 */
37      hWidget[2] =  new QWidget();
38 
39 
40      vWidget = new QWidget();
41      vBoxLayout = new QVBoxLayout();
42 
43      /* 标签实例化 */
44      label[0] = new QLabel();
45      label[1] = new QLabel();
46      label[2] = new QLabel();
47 
48      lineEdit = new QLineEdit();
49      comboBox = new QComboBox();
50      spinBox[0] = new QSpinBox();
51      spinBox[1] = new QSpinBox();
52      textBrowser = new QTextBrowser();
53 
54      label[0]->setText("目标IP地址:");
55      label[1]->setText("绑定端口:");
56      label[2]->setText("目标端口:");
57 
58      /* 设置标签根据文本文字大小自适应大小  */
59      label[0]->setSizePolicy(QSizePolicy::Fixed,
60                              QSizePolicy::Fixed);
61      label[1]->setSizePolicy(QSizePolicy::Fixed,
62                              QSizePolicy::Fixed);
63      label[2]->setSizePolicy(QSizePolicy::Fixed,
64                              QSizePolicy::Fixed);
65 
66      /* 设置端口号的范围,注意不要与主机的已使用的端口号冲突 */
67      spinBox[0]->setRange(10000, 99999);
68      spinBox[1]->setRange(10000, 99999);
69 
70      pushButton[0]->setText("绑定端口");
71      pushButton[1]->setText("解除绑定");
72      pushButton[2]->setText("清空文本");
73      pushButton[3]->setText("发送消息");
74      pushButton[4]->setText("广播消息");
75 
76      /* 设置停止监听状态不可用 */
77      pushButton[1]->setEnabled(false);
78 
79      /* 设置输入框默认的文本 */
80      lineEdit->setText("您好!");
81 
82      /* 水平布局一添加内容 */
83      hBoxLayout[0]->addWidget(pushButton[0]);
84      hBoxLayout[0]->addWidget(pushButton[1]);
85      hBoxLayout[0]->addWidget(pushButton[2]);
86 
87      /* 设置水平容器的布局为水平布局一 */
88      hWidget[0]->setLayout(hBoxLayout[0]);
89 
90      hBoxLayout[1]->addWidget(label[0]);
91      hBoxLayout[1]->addWidget(comboBox);
92      hBoxLayout[1]->addWidget(label[1]);
93      hBoxLayout[1]->addWidget(spinBox[0]);
94      hBoxLayout[1]->addWidget(label[2]);
95      hBoxLayout[1]->addWidget(spinBox[1]);
96 
97      /* 设置水平容器的布局为水平布局二 */
98      hWidget[1]->setLayout(hBoxLayout[1]);
99 
100     /* 水平布局三添加内容 */
101     hBoxLayout[2]->addWidget(lineEdit);
102     hBoxLayout[2]->addWidget(pushButton[3]);
103     hBoxLayout[2]->addWidget(pushButton[4]);
104
105     /* 设置水平容器三的布局为水平布局一 */
106     hWidget[2]->setLayout(hBoxLayout[2]);
107
108     /* 垂直布局添加内容 */
109     vBoxLayout->addWidget(textBrowser);
110     vBoxLayout->addWidget(hWidget[1]);
111     vBoxLayout->addWidget(hWidget[0]);
112     vBoxLayout->addWidget(hWidget[2]);
113
114     /* 设置垂直容器的布局为垂直布局 */
115     vWidget->setLayout(vBoxLayout);
116
117     /* 居中显示 */
118     setCentralWidget(vWidget);
119
120     /* 获取本地ip */
121     getLocalHostIP();
122
123     /* 信号槽连接 */
124     connect(pushButton[0], SIGNAL(clicked()),
125             this, SLOT(bindPort()));
126     connect(pushButton[1], SIGNAL(clicked()),
127             this, SLOT(unbindPort()));
128     connect(pushButton[2], SIGNAL(clicked()),
129             this, SLOT(clearTextBrowser()));
130     connect(pushButton[3], SIGNAL(clicked()),
131             this, SLOT(sendMessages()));
132     connect(pushButton[4], SIGNAL(clicked()),
133             this, SLOT(sendBroadcastMessages()));
134     connect(udpSocket, SIGNAL(readyRead()),
135             this, SLOT(receiveMessages()));
136     connect(udpSocket,
137             SIGNAL(stateChanged(QAbstractSocket::SocketState)),
138             this,
139             SLOT(socketStateChange(QAbstractSocket::SocketState)));
140 }
141
142 MainWindow::~MainWindow()
143 {
    
    
144 }
145
146 void MainWindow::bindPort()
147 {
    
    
148     quint16 port = spinBox[0]->value();
149
150     /* 绑定端口需要在socket的状态为UnconnectedState */
151     if (udpSocket->state() != QAbstractSocket::UnconnectedState)
152         udpSocket->close();
153
154     if (udpSocket->bind(port)) {
    
    
155         textBrowser->append("已经成功绑定端口:"
156                             + QString::number(port));
157
158         /* 设置界面中的元素的可用状态 */
159         pushButton[0]->setEnabled(false);
160         pushButton[1]->setEnabled(true);
161         spinBox[1]->setEnabled(false);
162     }
163 }
164
165 void MainWindow::unbindPort()
166 {
    
    
167     /* 解绑,不再监听 */
168     udpSocket->abort();
169
170     /* 设置界面中的元素的可用状态 */
171     pushButton[0]->setEnabled(true);
172     pushButton[1]->setEnabled(false);
173     spinBox[1]->setEnabled(true);
174 }
175
176 /* 获取本地IP */
177 void MainWindow::getLocalHostIP()
178 {
    
    
179     // /* 获取主机的名称 */
180     // QString hostName = QHostInfo::localHostName();
181
182     // /* 主机的信息 */
183     // QHostInfo hostInfo = QHostInfo::fromName(hostName);
184
185     // /* ip列表,addresses返回ip地址列表,注意主机应能从路由器获取到
186     // * IP,否则可能返回空的列表(ubuntu用此方法只能获取到环回IP) */
187     // IPlist = hostInfo.addresses();
188     // qDebug()<<IPlist<<endl;
189
190     // /* 遍历IPlist */
191     // foreach (QHostAddress ip, IPlist) {
    
    
192     //      if (ip.protocol() == QAbstractSocket::IPv4Protocol)
193     //          comboBox->addItem(ip.toString());
194     //    }
195
196     /* 获取所有的网络接口,
197      * QNetworkInterface类提供主机的IP地址和网络接口的列表 */
198     QList<QNetworkInterface> list
199             = QNetworkInterface::allInterfaces();
200
201     /* 遍历list */
202     foreach (QNetworkInterface interface, list) {
    
    
203
204         /* QNetworkAddressEntry类存储IP地址子网掩码和广播地址 */
205         QList<QNetworkAddressEntry> entryList
206                 = interface.addressEntries();
207
208         /* 遍历entryList */
209         foreach (QNetworkAddressEntry entry, entryList) {
    
    
210             /* 过滤IPv6地址,只留下IPv4 */
211             if (entry.ip().protocol() ==
212                     QAbstractSocket::IPv4Protocol) {
    
    
213                 comboBox->addItem(entry.ip().toString());
214                 /* 添加到IP列表中 */
215                 IPlist<<entry.ip();
216             }
217         }
218     }
219 }
220
221 /* 清除文本浏览框里的内容 */
222 void MainWindow::clearTextBrowser()
223 {
    
    
224     /* 清除文本浏览器的内容 */
225     textBrowser->clear();
226 }
227
228 /* 客户端接收消息 */
229 void MainWindow::receiveMessages()
230 {
    
    
231     /* 局部变量,用于获取发送者的IP和端口 */
232     QHostAddress peerAddr;
233     quint16 peerPort;
234
235     /* 如果有数据已经准备好 */
236     while (udpSocket->hasPendingDatagrams()) {
    
    
237         /* udpSocket发送的数据报是QByteArray类型的字节数组 */
238         QByteArray datagram;
239
240         /* 重新定义数组的大小 */
241         datagram.resize(udpSocket->pendingDatagramSize());
242
243         /* 读取数据,并获取发送方的IP地址和端口 */
244         udpSocket->readDatagram(datagram.data(),
245                                 datagram.size(),
246                                 &peerAddr,
247                                 &peerPort);
248         /* 转为字符串 */
249         QString str = datagram.data();
250
251         /* 显示信息到文本浏览框窗口 */
252         textBrowser->append("接收来自"
253                             + peerAddr.toString()
254                             + ":"
255                             + QString::number(peerPort)
256                             + str);
257     }
258 }
259
260 /* 客户端发送消息 */
261 void MainWindow::sendMessages()
262 {
    
    
263     /* 文本浏览框显示发送的信息 */
264     textBrowser->append("发送:" + lineEdit->text());
265
266     /* 要发送的信息,转为QByteArray类型字节数组,数据一般少于512个字节 */
267     QByteArray data = lineEdit->text().toUtf8();
268
269     /* 要发送的目标Ip地址 */
270     QHostAddress peerAddr = IPlist[comboBox->currentIndex()];
271
272     /* 要发送的目标端口号 */
273     quint16 peerPort = spinBox[1]->value();
274
275     /* 发送消息 */
276     udpSocket->writeDatagram(data, peerAddr, peerPort);
277 }
278
279 void MainWindow::sendBroadcastMessages()
280 {
    
    
281     /* 文本浏览框显示发送的信息 */
282     textBrowser->append("发送:" + lineEdit->text());
283
284     /* 要发送的信息,转为QByteArray类型字节数组,数据一般少于512个字节 */
285     QByteArray data = lineEdit->text().toUtf8();
286
287     /* 广播地址,一般为255.255.255.255,
288      * 同一网段内监听目标端口的程序都会接收到消息 */
289     QHostAddress peerAddr = QHostAddress::Broadcast;
290
291     /* 要发送的目标端口号 */
292     quint16 peerPort = spinBox[1]->text().toInt();
293
294     /* 发送消息 */
295     udpSocket->writeDatagram(data, peerAddr, peerPort);
296 }
297 /* socket状态改变 */
298 void MainWindow::socketStateChange(QAbstractSocket::SocketState state)
299 {
    
    
300     switch (state) {
    
    
301     case QAbstractSocket::UnconnectedState:
302         textBrowser->append("scoket状态:UnconnectedState");
303         break;
304     case QAbstractSocket::ConnectedState:
305         textBrowser->append("scoket状态:ConnectedState");
306         break;
307     case QAbstractSocket::ConnectingState:
308         textBrowser->append("scoket状态:ConnectingState");
309         break;
310     case QAbstractSocket::HostLookupState:
311         textBrowser->append("scoket状态:HostLookupState");
312         break;
313     case QAbstractSocket::ClosingState:
314         textBrowser->append("scoket状态:ClosingState");
315         break;
316     case QAbstractSocket::ListeningState:
317         textBrowser->append("scoket状态:ListeningState");
318         break;
319     case QAbstractSocket::BoundState:
320         textBrowser->append("scoket状态:BoundState");
321         break;
322     default:
323         break;
324     }
325 }

第146~163行,绑定端口。使用bind方法,即可绑定一个端口。注意我们绑定的端口不能和主机已经使用的端口冲突!
第165~174行,解绑端口。使用abort方法即可解绑。
第229~258行,接收消息,注意接收消息是QByteArray字节数组。读数组使用的是readDatagram方法,在readDatagram方法里可以获取对方的套接字IP地址与端口号。
第261~277行,单播消息,需要知道目标IP与目标端口号。即可用writeDatagram方法发送消息。
第279~296行,广播消息与单播消息不同的是将目标IP地址换成了广播地址,一般广播地址为255.255.255.255。
11.3.2.2 程序运行效果
本实例可以做即是发送者,也是接收者。如果在同一台主机同一个系统里运行两个本例程序。不能绑定同一个端口!否则会冲突!当您想测试在同一局域网内不同主机上运行此程序,那么绑定的端口号可以相同。
本例设置目标IP地址为127.0.0.1,此IP地址是Ubuntu/Windows上的环回IP地址,可以用于无网络时测试。绑定端口号与目标端口号相同,也就是说,此程序正在监听端口号为10000的数据,此程序也向目标IP地址127.0.0.1的10000端口号发送数据,实际上此程序就完成了自发自收。
当我们点击发送消息按钮时,文本消息窗口显示发送的数据“您好!”,同时接收到由本地IP 127.0.0.1发出的数据“您好!”。其中ffff:是通信套接字的标识。呵呵!您可能会问为什么不是本主机的其它地址如(192.168.1.x)发出的呢?因为我们选择了目标的IP地址为127.0.0.1,那么要与此目标地址通信,必须使用相同网段的IP设备与之通信。注意不能用本地环回发送消息到其他主机上。因为本地环回IP只适用于本地主机上的IP通信。
当我们点击广播消息按钮时,广播发送的目标IP地址变成了广播地址255.255.255.255。那么我们将收到从本地IP地址192.168.x.x的数据。如下图,收到了从192.168.1.129发送过来的数据。因为环回IP 127.0.0.1的广播地址为255.0.0.0,所以要与255.255.255.255的网段里的IP通信数据必须是由192.168.x.x上发出的。如果其他同一网段上的其他主机正在监听目标端口,那么它们将同时收到消息。这也验证了上一小节为什么会从127.0.0.1发送数据。
本例不难,可能有点绕,大家多参考资料理解理解,知识点有点多,如果没有些通信基础的话,我们需要慢慢吃透。
在这里插入图片描述

11.3.3 UDP组播
通常,在传统的网络通讯中,有两种方式,一种是源主机和目标主机两台主机之间进行的“一对一”的通讯方式,即单播,第二种是一台源主机与网络中所有其他主机之间进行的通讯,即广播。那么,如果需要将信息从源主机发送到网络中的多个目标主机,要么采用广播方式,这样网络中所有主机都会收到信息,要么,采用单播方式,由源主机分别向各个不同目标主机发送信息。可以看出来,在广播方式下,信息会发送到不需要该信息的主机从而浪费带宽资源,甚至引起广播风暴:而单播方式下,会因为数据包的多次重复而浪费带宽资源,同时,源主机的负荷会因为多次的数据复制而加大,所以,单播与广播对于多点发送问题有缺陷。在此情况下,组播技术就应用而生了。
组播类似于QQ群,如果把腾讯向QQ每个用户发送推送消息比作广播,那么组播就像是QQ群一样,只有群内的用户才能收到消息。想要收到消息,我们得先加群。
一个D类IP地址的第一个字节必须以“1110”开始,D类IP地址不分网络地址和主机地址,是一个专门保留的地址,其地址范围为224.0.0.0~239.255.255.255。D类IP地址主要用于多点广播(Multicast,也称为多播(组播))之中作为多播组IP地址。其中,多播组IP地址让源主机能够将分组发送给网络中的一组主机,属于多播组的主机将被分配一个多播组lP地址。由于多播组lP地址标识了一组主机(也称为主机组),因此多播组IP地址只能作为目标地址,源地址总是为单播地址。
224.0.0.0~224.0.0.255为预留的组播地址(永久组地址),地址224.0.0.0保留不做分配,其它地址供路由协议使用。
224.0.1.0~238.255.255.255为用户可用的组播地址(临时组地址),全网范围内有效。
239.0.0.0~239.255.255.255为本地管理组播地址,仅在特定的本地范围内有效。
通过以上的信息,我们只需要关注,哪些组播地址可以被我们在本地主机使用即可。在家庭网络和办公网络局域网内使用UDP组播功能,那么可用的组播地址范围是239.0.0.0~239.255.255.255。
QUdpSocket类支持UDP组播,提供了joinMulticastGroup方法使本地主机加入多播组,leaveMulticastGroup离开多播组。其他绑定端口,发送接收功能与UDP单播和广播完全一样。实际上我们在上一个实例学会使用joinMulticastGroup和leaveMulticastGroup的应用即可!
11.3.3.1 应用实例
本例目的:了解QUdpSocket组播使用。
例11_udp_multicast,UDP单播与广播应用(难度:一般)。项目路径为Qt/2/11_udp_multicast。本例大体流程首先获取本地IP地址。创建一个udpSocket套接字,加入组播前必须绑定本机主机的端口。加入组播使用joinMulticastGroup,退出组播使用leaveMulticastGroup。其他收发消息的功能与上一节单播和广播一样。
项目文件10_udp_unicast_broadcast.pro文件第一行添加的代码部分如下。
11_udp_multicast.pro编程后的代码

1   QT       += core gui network
2 
3   greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
4 
5   CONFIG += c++11
6 
7   # The following define makes your compiler emit warnings if you use
8   # any Qt feature that has been marked deprecated (the exact warnings
9   # depend on your compiler). Please consult the documentation of the
10  # deprecated API in order to know how to port your code away from it.
11  DEFINES += QT_DEPRECATED_WARNINGS
12
13  # You can also make your code fail to compile if it uses deprecated APIs.
14  # In order to do so, uncomment the following line.
15  # You can also select to disable deprecated APIs only up to a certain version of Qt.
16  #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0
17
18  SOURCES += \
19      main.cpp \
20      mainwindow.cpp
21
22  HEADERS += \
23      mainwindow.h
24
25  # Default rules for deployment.
26  qnx: target.path = /tmp/$${
    
    TARGET}/bin
27  else: unix:!android: target.path = /opt/$${
    
    TARGET}/bin
28  !isEmpty(target.path): INSTALLS += target

在头文件“mainwindow.h”具体代码如下。
mainwindow.h编程后的代码

    /******************************************************************
    Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
    * @projectName   10_udp_unicast_broadcast
    * @brief         mainwindow.h
    * @author        Deng Zhimao
    * @email         [email protected]
    * @net            www.openedv.com
    * @date           2021-04-14
    *******************************************************************/
1   #ifndef MAINWINDOW_H
2   #define MAINWINDOW_H
3 
4   #include <QMainWindow>
5   #include <QUdpSocket>
6   #include <QVBoxLayout>
7   #include <QHBoxLayout>
8   #include <QPushButton>
9   #include <QTextBrowser>
10  #include <QLabel>
11  #include <QComboBox>
12  #include <QSpinBox>
13  #include <QHostInfo>
14  #include <QLineEdit>
15  #include <QNetworkInterface>
16  #include <QDebug>
17
18  class MainWindow : public QMainWindow
19  {
    
    
20      Q_OBJECT
21
22  public:
23      MainWindow(QWidget *parent = nullptr);
24      ~MainWindow();
25
26  private:
27      /* Udp通信套接字 */
28      QUdpSocket *udpSocket;
29
30      /* 按钮 */
31      QPushButton *pushButton[4];
32
33      /* 标签文本 */
34      QLabel *label[3];
35
36      /* 水平容器 */
37      QWidget *hWidget[3];
38
39      /* 水平布局 */
40      QHBoxLayout *hBoxLayout[3];
41
42      /* 垂直容器 */
43      QWidget *vWidget;
44
45      /* 垂直布局 */
46      QVBoxLayout *vBoxLayout;
47
48      /* 文本浏览框 */
49      QTextBrowser *textBrowser;
50
51      /* 用于显示本地ip */
52      QComboBox *comboBox[2];
53
54      /* 用于选择端口 */
55      QSpinBox  *spinBox;
56
57      /* 文本输入框 */
58      QLineEdit *lineEdit;
59
60      /* 存储本地的ip列表地址 */
61      QList<QHostAddress> IPlist;
62
63      /* 获取本地的所有ip */
64      void getLocalHostIP();
65
66  private slots:
67      /* 加入组播 */
68      void joinGroup();
69
70      /* 退出组播 */
71      void leaveGroup();
72
73      /* 清除文本框时的内容 */
74      void clearTextBrowser();
75
76      /* 接收到消息 */
77      void receiveMessages();
78
79      /* 组播消息 */
80      void sendMessages();
81
82      /* 连接状态改变槽函数 */
83      void socketStateChange(QAbstractSocket::SocketState);
84  };
85  #endif // MAINWINDOW_H

头文件里主要是声明界面用的元素,及一些槽函数。重点是声明udpSocket。
在源文件“mainwindow.cpp”具体代码如下。
mainwindow.cpp编程后的代码

    /******************************************************************
    Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
    * @projectName   10_udp_unicast_broadcast
    * @brief         mainwindow.cpp
    * @author        Deng Zhimao
    * @email         [email protected]
    * @net            www.openedv.com
    * @date           2021-04-14
    *******************************************************************/
1   #include "mainwindow.h"
2  
3   MainWindow::MainWindow(QWidget *parent)
4       : QMainWindow(parent)
5   {
    
    
6       /* 设置主窗体的位置与大小 */
7       this->setGeometry(0, 0, 800, 480);
8  
9       /* udp套接字 */
10      udpSocket = new QUdpSocket(this);
11 
12      /* 参数1是设置IP_MULTICAST_TTL套接字选项允许应用程序主要限制数据包在Internet中的生存时间,
13       * 并防止其无限期地循环,数据报跨一个路由会减一,默认值为1,表示多播仅适用于本地子网。*/
14      udpSocket->setSocketOption(QAbstractSocket::MulticastTtlOption, 1);
15 
16      /* 加入组播按钮 */
17      pushButton[0] = new QPushButton();
18      /* 退出组播按钮 */
19      pushButton[1] = new QPushButton();
20      /* 清空聊天文本按钮 */
21      pushButton[2] = new QPushButton();
22      /* 组播消息按钮 */
23      pushButton[3] = new QPushButton();
24 
25      /* 水平布局一 */
26      hBoxLayout[0] = new QHBoxLayout();
27      /* 水平布局二 */
28      hBoxLayout[1] = new QHBoxLayout();
29      /* 水平布局三 */
30      hBoxLayout[2] = new QHBoxLayout();
31      /* 水平布局四 */
32      hBoxLayout[3] = new QHBoxLayout();
33 
34      /* 水平容器一 */
35      hWidget[0] =  new QWidget();
36      /* 水平容器二 */
37      hWidget[1] =  new QWidget();
38      /* 水平容器三 */
39      hWidget[2] =  new QWidget();
40 
41 
42      vWidget = new QWidget();
43      vBoxLayout = new QVBoxLayout();
44 
45      /* 标签实例化 */
46      label[0] = new QLabel();
47      label[1] = new QLabel();
48      label[2] = new QLabel();
49 
50      lineEdit = new QLineEdit();
51      comboBox[0] = new QComboBox();
52      comboBox[1] = new QComboBox();
53      spinBox = new QSpinBox();
54      textBrowser = new QTextBrowser();
55 
56      label[0]->setText("本地IP地址:");
57      label[1]->setText("组播地址:");
58      label[2]->setText("组播端口:");
59 
60      /* 设置标签根据文本文字大小自适应大小  */
61      label[0]->setSizePolicy(QSizePolicy::Fixed,
62                              QSizePolicy::Fixed);
63      label[1]->setSizePolicy(QSizePolicy::Fixed,
64                              QSizePolicy::Fixed);
65      label[2]->setSizePolicy(QSizePolicy::Fixed,
66                              QSizePolicy::Fixed);
67 
68      /* 设置端口号的范围,注意不要与主机的已使用的端口号冲突 */
69      spinBox->setRange(10000, 99999);
70 
71      pushButton[0]->setText("加入组播");
72      pushButton[1]->setText("退出组播");
73      pushButton[2]->setText("清空文本");
74      pushButton[3]->setText("组播消息");
75 
76      /* 设置停止监听状态不可用 */
77      pushButton[1]->setEnabled(false);
78 
79      /* 设置输入框默认的文本 */
80      lineEdit->setText("您好!");
81 
82      /* 默认添加范围内的一个组播地址 */
83      comboBox[1]->addItem("239.255.255.1");
84 
85      /* 设置可编辑,用户可自行修改此地址 */
86      comboBox[1]->setEditable(true);
87 
88      /* 水平布局一添加内容 */
89      hBoxLayout[0]->addWidget(pushButton[0]);
90      hBoxLayout[0]->addWidget(pushButton[1]);
91      hBoxLayout[0]->addWidget(pushButton[2]);
92 
93      /* 设置水平容器的布局为水平布局一 */
94      hWidget[0]->setLayout(hBoxLayout[0]);
95 
96      hBoxLayout[1]->addWidget(label[0]);
97      hBoxLayout[1]->addWidget(comboBox[0]);
98      hBoxLayout[1]->addWidget(label[1]);
99      hBoxLayout[1]->addWidget(comboBox[1]);
100     hBoxLayout[1]->addWidget(label[2]);
101     hBoxLayout[1]->addWidget(spinBox);
102
103     /* 设置水平容器的布局为水平布局二 */
104     hWidget[1]->setLayout(hBoxLayout[1]);
105
106     /* 水平布局三添加内容 */
107     hBoxLayout[2]->addWidget(lineEdit);
108     hBoxLayout[2]->addWidget(pushButton[3]);
109
110     /* 设置水平容器三的布局为水平布局一 */
111     hWidget[2]->setLayout(hBoxLayout[2]);
112
113     /* 垂直布局添加内容 */
114     vBoxLayout->addWidget(textBrowser);
115     vBoxLayout->addWidget(hWidget[1]);
116     vBoxLayout->addWidget(hWidget[0]);
117     vBoxLayout->addWidget(hWidget[2]);
118
119     /* 设置垂直容器的布局为垂直布局 */
120     vWidget->setLayout(vBoxLayout);
121
122     /* 居中显示 */
123     setCentralWidget(vWidget);
124
125     /* 获取本地ip */
126     getLocalHostIP();
127
128     /* 信号槽连接 */
129     connect(pushButton[0], SIGNAL(clicked()),
130             this, SLOT(joinGroup()));
131     connect(pushButton[1], SIGNAL(clicked()),
132             this, SLOT(leaveGroup()));
133     connect(pushButton[2], SIGNAL(clicked()),
134             this, SLOT(clearTextBrowser()));
135     connect(pushButton[3], SIGNAL(clicked()),
136             this, SLOT(sendMessages()));
137     connect(udpSocket, SIGNAL(readyRead()),
138             this, SLOT(receiveMessages()));
139     connect(udpSocket,
140             SIGNAL(stateChanged(QAbstractSocket::SocketState)),
141             this,
142             SLOT(socketStateChange(QAbstractSocket::SocketState)));
143 }
144
145 MainWindow::~MainWindow()
146 {
    
    
147 }
148
149 void MainWindow::joinGroup()
150 {
    
    
151     /* 获取端口 */
152     quint16 port = spinBox->value();
153     /* 获取组播地址 */
154     QHostAddress groupAddr = QHostAddress(comboBox[1]->currentText());
155
156     /* 绑定端口需要在socket的状态为UnconnectedState */
157     if (udpSocket->state() != QAbstractSocket::UnconnectedState)
158         udpSocket->close();
159
160     /* 加入组播前必须先绑定端口 */
161     if (udpSocket->bind(QHostAddress::AnyIPv4,
162                         port, QUdpSocket::ShareAddress)) {
    
    
163
164         /* 加入组播组,返回结果给ok变量 */
165         bool ok = udpSocket->joinMulticastGroup(groupAddr);
166
167         textBrowser->append(ok ? "加入组播成功" : "加入组播失败");
168
169         textBrowser->append("组播地址IP:" 
170                             + comboBox[1]->currentText());
171
172         textBrowser->append("绑定端口:"
173                             + QString::number(port));
174
175         /* 设置界面中的元素的可用状态 */
176         pushButton[0]->setEnabled(false);
177         pushButton[1]->setEnabled(true);
178         comboBox[1]->setEnabled(false);
179         spinBox->setEnabled(false);
180     }
181 }
182
183 void MainWindow::leaveGroup()
184 {
    
    
185     /* 获取组播地址 */
186     QHostAddress groupAddr = QHostAddress(comboBox[1]->currentText());
187
188     /* 退出组播 */
189     udpSocket->leaveMulticastGroup(groupAddr);
190
191     /* 解绑,不再监听 */
192     udpSocket->abort();
193
194     /* 设置界面中的元素的可用状态 */
195     pushButton[0]->setEnabled(true);
196     pushButton[1]->setEnabled(false);
197     comboBox[1]->setEnabled(true);
198     spinBox->setEnabled(true);
199 }
200
201 /* 获取本地IP */
202 void MainWindow::getLocalHostIP()
203 {
    
    
204     // /* 获取主机的名称 */
205     // QString hostName = QHostInfo::localHostName();
206
207     // /* 主机的信息 */
208     // QHostInfo hostInfo = QHostInfo::fromName(hostName);
209
210     // /* ip列表,addresses返回ip地址列表,注意主机应能从路由器获取到
211     // * IP,否则可能返回空的列表(ubuntu用此方法只能获取到环回IP) */
212     // IPlist = hostInfo.addresses();
213     // qDebug()<<IPlist<<endl;
214
215     // /* 遍历IPlist */
216     // foreach (QHostAddress ip, IPlist) {
    
    
217     //      if (ip.protocol() == QAbstractSocket::IPv4Protocol)
218     //          comboBox->addItem(ip.toString());
219     //    }
220
221     /* 获取所有的网络接口,
222      * QNetworkInterface类提供主机的IP地址和网络接口的列表 */
223     QList<QNetworkInterface> list
224             = QNetworkInterface::allInterfaces();
225
226     /* 遍历list */
227     foreach (QNetworkInterface interface, list) {
    
    
228
229         /* QNetworkAddressEntry类存储IP地址子网掩码和广播地址 */
230         QList<QNetworkAddressEntry> entryList
231                 = interface.addressEntries();
232
233         /* 遍历entryList */
234         foreach (QNetworkAddressEntry entry, entryList) {
    
    
235             /* 过滤IPv6地址,只留下IPv4,并且不需要环回IP */
236             if (entry.ip().protocol() ==
237                     QAbstractSocket::IPv4Protocol &&
238                     ! entry.ip().isLoopback()) {
    
    
239                 /* 添加本地IP地址到comboBox[0] */
240                 comboBox[0]->addItem(entry.ip().toString());
241                 /* 添加到IP列表中 */
242                 IPlist<<entry.ip();
243             }
244         }
245     }
246 }
247
248 /* 清除文本浏览框里的内容 */
249 void MainWindow::clearTextBrowser()
250 {
    
    
251     /* 清除文本浏览器的内容 */
252     textBrowser->clear();
253 }
254
255 /* 客户端接收消息 */
256 void MainWindow::receiveMessages()
257 {
    
    
258     /* 局部变量,用于获取发送者的IP和端口 */
259     QHostAddress peerAddr;
260     quint16 peerPort;
261
262     /* 如果有数据已经准备好 */
263     while (udpSocket->hasPendingDatagrams()) {
    
    
264         /* udpSocket发送的数据报是QByteArray类型的字节数组 */
265         QByteArray datagram;
266
267         /* 重新定义数组的大小 */
268         datagram.resize(udpSocket->pendingDatagramSize());
269
270         /* 读取数据,并获取发送方的IP地址和端口 */
271         udpSocket->readDatagram(datagram.data(),
272                                 datagram.size(),
273                                 &peerAddr,
274                                 &peerPort);
275         /* 转为字符串 */
276         QString str = datagram.data();
277
278         /* 显示信息到文本浏览框窗口 */
279         textBrowser->append("接收来自"
280                             + peerAddr.toString()
281                             + ":"
282                             + QString::number(peerPort)
283                             + str);
284     }
285 }
286
287 /* 客户端发送消息 */
288 void MainWindow::sendMessages()
289 {
    
    
290     /* 文本浏览框显示发送的信息 */
291     textBrowser->append("发送:" + lineEdit->text());
292
293     /* 要发送的信息,转为QByteArray类型字节数组,数据一般少于512个字节 */
294     QByteArray data = lineEdit->text().toUtf8();
295
296     /* 要发送的目标Ip地址 */
297     QHostAddress groupAddr = QHostAddress(comboBox[1]->currentText());
298
299     /* 要发送的目标端口号 */
300     quint16 groupPort = spinBox->value();
301
302     /* 发送消息 */
303     udpSocket->writeDatagram(data, groupAddr, groupPort);
304 }
305
306 /* socket状态改变 */
307 void MainWindow::socketStateChange(QAbstractSocket::SocketState state)
308 {
    
    
309     switch (state) {
    
    
310     case QAbstractSocket::UnconnectedState:
311         textBrowser->append("scoket状态:UnconnectedState");
312         break;
313     case QAbstractSocket::ConnectedState:
314         textBrowser->append("scoket状态:ConnectedState");
315         break;
316     case QAbstractSocket::ConnectingState:
317         textBrowser->append("scoket状态:ConnectingState");
318         break;
319     case QAbstractSocket::HostLookupState:
320         textBrowser->append("scoket状态:HostLookupState");
321         break;
322     case QAbstractSocket::ClosingState:
323         textBrowser->append("scoket状态:ClosingState");
324         break;
325     case QAbstractSocket::ListeningState:
326         textBrowser->append("scoket状态:ListeningState");
327         break;
328     case QAbstractSocket::BoundState:
329         textBrowser->append("scoket状态:BoundState");
330         break;
331     default:
332         break;
333     }
334 }

第161~162行,绑定端口。使用bind方法,即可绑定一个端口。注意我们绑定的端口不能和主机已经使用的端口冲突!
第165行,使用joinMulticastGroup加入组播,QHostAddress::AnyIPv4,是加入Ipv4组播的一个接口,所有操作系统都不支持不带接口选择的加入IPv6组播组。加入的结果返回给变量ok。组播地址可由用户点击comboBox[1]控件输入(默认编者已经输入一个地址为239.255.255.1),注意组播地址的范围必须是239.0.0.0~239.255.255.255中的一个数。
第189行,使用leaveMulticastGroup退出组播。
第192行,解绑端口。使用abort方法即可解绑。
第256~285行,接收消息,注意接收消息是QByteArray字节数组。读数组使用的是readDatagram方法,在readDatagram方法里可以获取对方的套接字IP地址与端口号。
第288~304行,发送消息,组播与广播消息或单播消息不同的是将目标IP地址换成了组播地址239.255.255.1。
11.3.3.2 程序运行效果
运行程序后,点击加入组播,然后点击组播消息,本实例可以做即是发送者,也是接收者。如果在同一台主机同一个系统里运行两个本例程序。不能绑定同一个端口!否则会冲突!当您想测试在同一局域网内不同主机上运行此程序,那么绑定的端口号可以相同。
因为是组播消息,所以自己也会收到消息,如果在局域网内其他主机运行此程序,当点击加入组播后,就可以收发消息了。
在这里插入图片描述

11.4 网络下载实例
Qt网络模块还提供了直接访问如HTTP,FTP等网络协议的类,这些类是QNetworkAccessManager、QNetworkRequest和QNetworkReply。
通常需要这三个类协作才能完成一个网络操作。可以用于从网络获取时间,天气和图片等等数据。比如本例需要下载一张图片,大概流程如下。
由QNetworkRequest类设置一个URL地址发起网络协议请求,QNetworkRequest类保存要用QNetworkAccessManager发送的请求。QNetworkRequest是网络访问API的一部分,是一个持有通过网络发送请求所需信息的类。它包含一个URL和一些可用于修改请求的辅助信息。
QNetworkAccessManager类允许应用程序发送网络请求并接收响应。在QNetworkRequest发起网络请求后,QNetworkAccessManager负责发送网络请求,创建网络响应。
QNetworkReply类就用于QNetworkAccessManager创建的网络响应。最终由QNetworkReply处理网络响应。它提供了finished()、readyRead()和downloadProgress()等信号,可以监测网络响应的执行情况。并且QNetworkReply继承于QIODevice,所以QNetworkReply支持流读写,可以直接用read()和write等功能。
11.4.1 应用实例
本例目的:了解QNetworkAccessManager、QNetworkRequest和QNetworkReply类的使用。
例12_imagedownload,下载小图片(难度:一般)。项目路径为Qt/2/12_imagedownload。本例大体流程,设置一个下载图片的URL,通过networkReply处理响应后,从流中读取图片的数据,然后保存到本地。
项目文件12_imagedownload.pro文件第一行添加的代码部分如下。
12_imagedownload.pro编程后的代码

1   QT       += core gui network
2 
3   greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
4 
5   CONFIG += c++11
6 
7   # The following define makes your compiler emit warnings if you use
8   # any Qt feature that has been marked deprecated (the exact warnings
9   # depend on your compiler). Please consult the documentation of the
10  # deprecated API in order to know how to port your code away from it.
11  DEFINES += QT_DEPRECATED_WARNINGS
12
13  # You can also make your code fail to compile if it uses deprecated APIs.
14  # In order to do so, uncomment the following line.
15  # You can also select to disable deprecated APIs only up to a certain version of Qt.
16  #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0
17
18  SOURCES += \
19      main.cpp \
20      mainwindow.cpp
21
22  HEADERS += \
23      mainwindow.h
24
25  # Default rules for deployment.
26  qnx: target.path = /tmp/$${
    
    TARGET}/bin
27  else: unix:!android: target.path = /opt/$${
    
    TARGET}/bin
28  !isEmpty(target.path): INSTALLS += target

在头文件“mainwindow.h”具体代码如下。
mainwindow.h编程后的代码

    /******************************************************************
    Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
    * @projectName   12_imagedownload
    * @brief         mainwindow.h
    * @author        Deng Zhimao
    * @email         [email protected]
    * @net            www.openedv.com
    * @date           2021-04-16
    *******************************************************************/
1   #ifndef MAINWINDOW_H
2   #define MAINWINDOW_H
3 
4   #include <QMainWindow>
5   #include <QNetworkAccessManager>
6   #include <QNetworkReply>
7   #include <QFile>
8   #include <QLabel>
9   #include <QPushButton>
10  #include <QProgressBar>
11  #include <QHBoxLayout>
12  #include <QVBoxLayout>
13  #include <QLineEdit>
14
15  class MainWindow : public QMainWindow
16  {
    
    
17      Q_OBJECT
18
19  public:
20      MainWindow(QWidget *parent = nullptr);
21      ~MainWindow();
22  private:
23      /* 网络管理 */
24      QNetworkAccessManager *networkAccessManager;
25
26      /* 标签 */
27      QLabel *label[3];
28
29      /* 按钮 */
30      QPushButton *pushButton;
31
32      /* 下载进度条 */
33      QProgressBar *progressBar;
34
35      /* 水平布局 */
36      QHBoxLayout *hBoxLayout[2];
37
38      /* 垂直布局 */
39      QVBoxLayout *vBoxLayout;
40
41      /* 水平容器 */
42      QWidget *hWidget[2];
43
44      /* 垂直容器 */
45      QWidget *vWidget;
46
47      /* 链接输入框 */
48      QLineEdit *lineEdit;
49
50  private slots:
51      /* 读取数据 */
52      void readyReadData();
53
54      /* 响应完成处理 */
55      void replyFinished();
56
57      /* 下载进度管理 */
58      void imageDownloadProgress(qint64, qint64);
59
60      /* 点击开始下载 */
61      void startDownload();
62
63      /* 响应错误处理函数 */
64      void networkReplyError(QNetworkReply::NetworkError);
65  };

66 #endif // MAINWINDOW_H
头文件里主要是声明界面用的元素,及一些槽函数。重点是声明networkAccessManager。
在源文件“mainwindow.cpp”具体代码如下。
mainwindow.cpp编程后的代码

    /******************************************************************
    Copyright © Deng Zhimao Co., Ltd. 1990-2021. All rights reserved.
    * @projectName   12_imagedownload
    * @brief         mainwindow.cpp
    * @author        Deng Zhimao
    * @email         [email protected]
    * @net            www.openedv.com
    * @date           2021-04-16
    *******************************************************************/
1   #include "mainwindow.h"
2   #include <QMessageBox>
3   #include <QCoreApplication>
4  
5   MainWindow::MainWindow(QWidget *parent)
6       : QMainWindow(parent)
7   {
    
    
8       /* 设置主窗体的位置与大小 */
9       this->setGeometry(0, 0, 800, 480);
10 
11      /* 标签0, 显示下载的图像 */
12      label[0] = new QLabel();
13      /* 标签1, 显示URL标签 */
14      label[1] = new QLabel();
15      /* 下载进度标签 */
16      label[2] = new QLabel();
17 
18      /* 下载图片链接输入框 */
19      lineEdit = new QLineEdit();
20 
21      /* 下载按钮 */
22      pushButton = new QPushButton();
23 
24      /* 下载进度条 */
25      progressBar = new QProgressBar();
26 
27      /* 水平布局 */
28      hBoxLayout[0] = new QHBoxLayout();
29      hBoxLayout[1] = new QHBoxLayout();
30 
31      /* 垂直布局 */
32      vBoxLayout = new QVBoxLayout();
33 
34      /* 水平容器 */
35      hWidget[0] = new QWidget();
36      hWidget[1] = new QWidget();
37 
38      /* 垂直容器 */
39      vWidget = new QWidget();
40 
41      label[1]->setText("URL链接:");
42      label[2]->setText("文件下载进度:");
43 
44      pushButton->setText("下载");
45 
46      /* 设置下载链接地址 */
47      lineEdit->setText("https://ss0.bdstatic.com/70cFuH"
48                        "Sh_Q1YnxGkpoWK1HF6hhy/it/u=42710"
49                        "87328,1384669424&fm=11&gp=0.jpg");
50      /* 设置标签的最小显示大小 */
51      label[0]->setMinimumSize(this->width(),
52                               this->height() * 0.75);
53 
54      /* 根据文本文字大小自适应大小 */
55      label[1]->setSizePolicy(QSizePolicy::Fixed,
56                              QSizePolicy::Fixed);
57      label[2]->setSizePolicy(QSizePolicy::Fixed,
58                              QSizePolicy::Fixed);
59      pushButton->setSizePolicy(QSizePolicy::Fixed,
60                                QSizePolicy::Fixed);
61 
62      /* 水平布局0添加元素 */
63      hBoxLayout[0]->addWidget(label[1]);
64      hBoxLayout[0]->addWidget(lineEdit);
65      hBoxLayout[0]->addWidget(pushButton);
66 
67      /* 设置水平布局0为水平容器的布局0 */
68      hWidget[0]->setLayout(hBoxLayout[0]);
69 
70      /* 水平布局1添加元素 */
71      hBoxLayout[1]->addWidget(label[2]);
72      hBoxLayout[1]->addWidget(progressBar);
73 
74      /* 设置水平布局1为水平容器的布局1 */
75      hWidget[1]->setLayout(hBoxLayout[1]);
76 
77      /* 垂直布局添加元素 */
78      vBoxLayout->addWidget(label[0]);
79      vBoxLayout->addWidget(hWidget[0]);
80      vBoxLayout->addWidget(hWidget[1]);
81 
82      /* 设置垂直布局为垂直容器的布局 */
83      vWidget->setLayout(vBoxLayout);
84 
85      /* 设置居中 */
86      setCentralWidget(vWidget);
87 
88      /* 网络管理 */
89      networkAccessManager = new QNetworkAccessManager(this);
90 
91      /* 信号槽连接 */
92      connect(pushButton, SIGNAL(clicked()),
93              this, SLOT(startDownload()));
94 
95  }
96 
97  MainWindow::~MainWindow()
98  {
    
    
99  }
100
101 void MainWindow::startDownload()
102 {
    
    
103     /* 获取URL链接 */
104     QUrl newUrl(QUrl(lineEdit->text()));
105
106     /* 如果下载链接无效,则直接返回 */
107     if (!newUrl.isValid()) {
    
    
108         QMessageBox::information(this, "error", "invalid url");
109         return;
110     }
111
112     /* 网络请求 */
113     QNetworkRequest  networkRequest;
114
115     /* 设置下载的地址 */
116     networkRequest.setUrl(newUrl);
117
118     /* 网络响应 */
119     QNetworkReply *newReply =
120             networkAccessManager->get(networkRequest);
121
122     /* 信号槽连接 */
123     connect(newReply, SIGNAL(finished()), 
124             this, SLOT(replyFinished()));
125     connect(newReply, SIGNAL(readyRead()), 
126             this, SLOT(readyReadData()));
127     connect(newReply, SIGNAL(downloadProgress(qint64, qint64)),
128             this, SLOT(imageDownloadProgress(qint64, qint64)));
129     connect(newReply, 
130             SIGNAL(error(QNetworkReply::NetworkError)),
131             this, 
132             SLOT(networkReplyError(QNetworkReply::NetworkError )));
133 }
134
135 void MainWindow::readyReadData()
136 {
    
    
137     /* 设置按钮不可用,防止未完成,再次点击 */
138     pushButton->setEnabled(false);
139
140     /* 获取信号发送者 */
141     QNetworkReply *reply = (QNetworkReply *)sender();
142
143     QFile imageFile;
144     /* 保存到当前路径,名称为"下载的.jpg" */
145     imageFile.setFileName(QCoreApplication::applicationDirPath()
146                           + "/下载的.jpg");
147
148     /* 如果此图片已经存在,则删除 */
149     if (imageFile.exists())
150         imageFile.remove();
151
152     /* 读取数据 */
153     QByteArray data =  reply->readAll();
154     /* 如果数据为空,返回 */
155     if (data.isEmpty()) {
    
    
156         qDebug()<<"data is null, please try it again!"<<endl;
157         return;
158     }
159
160     /* 判断是不是JPG格式的图片,如果不是则返回 */
161     if (! (data[0] == (char)0xff
162            && data[1] == (char)0xd8
163            && data[data.size() - 2] == (char)0xff
164            && data[data.size() - 1] == (char)0xd9)) {
    
    
165         qDebug()<<"not JPG data, please try it again!"<<endl;
166         return;
167     }
168
169     /* 转为QPixmap */
170     QPixmap pixmap;
171     pixmap.loadFromData(data);
172     pixmap.save(imageFile.fileName());
173 }
174
175 void MainWindow::replyFinished()
176 {
    
    
177     /* 获取信号发送者 */
178     QNetworkReply *reply = (QNetworkReply *)sender();
179
180     /* 防止内存泄漏 */
181     reply->deleteLater();
182
183     /* 判断当前执行程序下的图像是否下载完成 */
184     QFile imageFile(QCoreApplication::applicationDirPath()
185                     + "/下载的.jpg");
186     if (imageFile.exists()) {
    
    
187         /* 显示下载的图像 */
188         label[0]->setPixmap(QPixmap(imageFile.fileName()));
189         qDebug() <<"已经成功下载,文件路径为:"
190                 <<imageFile.fileName()<<endl;
191     } else
192         /* 清空显示 */
193         label[0]->clear();
194
195     /* 设置按钮可用 */
196     pushButton->setEnabled(true);
197 }
198
199 void MainWindow::imageDownloadProgress(qint64 bytes,
200                                        qint64 totalBytes)
201 {
    
    
202     /* 设置进度条的最大值 */
203     progressBar->setMaximum(totalBytes);
204     /* 设置当前值 */
205     progressBar->setValue(bytes);
206 }
207
208 /* 网络响应处理函数 */
209 void MainWindow::networkReplyError(QNetworkReply::NetworkError
210                                    error)
211 {
    
    
212     switch (error) {
    
    
213     case QNetworkReply::ConnectionRefusedError:
214         qDebug()<<"远程服务器拒绝连接"<<endl;
215         break;
216     case QNetworkReply::HostNotFoundError:
217         qDebug()<<"找不到远程主机名"<<endl;
218         break;
219     case QNetworkReply::TimeoutError:
220         qDebug()<<"与远程服务器连接超时"<<endl;
221         break;
222     default:
223         break;
224     }
225 }

第89行,全局变量networkAccessManager实例化。
第101行~133行,首先从单行输入框里获取URL链接为newUrl,判断链接的有效性。然后创建局部变量networkRequest,设置networkRequest请求URL为newUrl。QNetworkReply *newReply = networkAccessManager->get(networkRequest);为最重要的代码,所有响应本次的操作都交给了newReply。通过信号槽处理对应的操作。
第135~173行,这部分代码就是从newReply流里读取网络下载的数据。
第160~167行,这里编者做了一些处理,从网络下载的数据可能遇到数据丢失或者下载错误的情况。本例是从百度里下载一张JPG格式的图片,因为JPG图片的判断依据是第一个字节和第二个字节的数据是0xff和0xd8,倒数第一个和倒数第二个字节数据分别是0xd9和0xff。如果都对,那么判断此数据为JPG图片数据。然后进行保存,否则则不保存图片。处理数据往往是需要的,我们经常要对下载的数据进行处理。
第174~197行,网络响应完成。记得要删除reply,防止内存泄漏。如果下载成功图片,则显示图片到label[0]上。
第209~225行,网络响应错误处理函数。

11.4.2 程序运行效果
点击下载按钮后,可以看到本次下载的图片已经保存到当前编译输出的路径下,名字叫“下载的.jpg”,并显示到界面上。由于文件下载的速度非常快,所以下载的进度条一下子就变成了100%。若想看见下载进度条下载进度缓慢一些,可以修改本例去下载其他文件,注意不要保存为jpg图片了。注意:此程序里的下载链接可能失效,请替换自己的图片链接。

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_55796564/article/details/123864477
今日推荐