添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
Qt是一个C++应用程序框架。它拥有完备的C++图形库和集成了一系列代码模块, 支持C++,Python,QML,Javascript等多种语言,同时也拥有一套完整的设计、开发工具。 使用Qt开发的软件,相同的代码可以在大多数的平台上编译运行,而不需要修改源代码。 它会自动根据平台的不同,表现平台特有的图形界面风格。

16. 网络通信

Qt提供许多用于高级和低级网络通信的类、用于web集成的类以及用于进程间通信的类。

Qt Network模块,为所使用的操作提供了一个抽象层,只显示高级类和函数,还可以处理较低级别的协议。 如用于TCP通讯的QTcpSocket和用于UDP通讯的QUdpSocket,这些类使开发人员能够使用TCP或UDP协议发送和接收消息。

关于HTTP,主要通过QNetworkRequest,QNetworkAccessManager和QNetworkReply类。 简而言之,QNetworkRequest类似于HTTP请求,该请求被传递给QNetworkAccessManager以在线发送请求, 此类返回一个QNetworkReply,它可以解析HTTP答复。

Qt Network还有用于网络承载管理的类,以及基于SSL协议进行安全通信的类,除了QSslSocket外还提供了很多具备辅助功能的类, 例如QSslCertificate,QSslConfiguration和QSslError。Qt中唯一受支持的SSL后端是OpenSSL,需要单独安装。

本章主要讲在Qt中HTTP、TCP、UDP相关类的使用和应用编程。

16.1. TCP

TCP(传输控制协议,transmission control protocol)是大多数Internet协议(包括HTTP和FTP)用于数据传输的底层网络协议。 它是一种可靠的,面向流,面向连接的传输协议,特别适合连续数据传输。

16.1.1. TCP通讯相关的类

QTcpServer 类主要用于创建socket连接,在服务端进行网络监听,提供一个基于TCP的服务器。

QTcpServer主要的接口函数

bool listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0)

公共函数,开始监听设置的IP和端口

void close()

公共函数,关闭,停止监听

virtual QTcpSocket * nextPendingConnection()

公共函数,返回下一个等待的连接

QHostAddress serverAddress() const

公共函数,如果服务器处于监听状态,返回服务器地址

quint16 serverPort() const

公共函数,如果服务器处于监听状态,返回监听的端口

void newConnection()

信号,当有新的连接时,信号被发射

QTcpServer类允许接受传入的TCP连接,可以指定端口或让QTcpServer自动选择一个端口,监听特定地址或所有机器地址。 使用listen函数监听,当有客户端连接时发射newConnection()信号,我们可以关联相关槽函数,在槽函数中使用nextPendingConnection()函数接受客户端的连接,使用QTcpSocket与之通讯。

当客户端和服务端连接后,可以使用函数serverPort()和serverAddress()返回监听的端口和服务器地址,使用QTcpSocket对象进行通讯。

QTcpSocket 使用必须先建立与远程主机和端口的TCP连接,然后才能开始任何数据传输。 建立连接后,可通过QTcpSocket::peerAddress()和QTcpSocket::peerPort()获得连接方的IP地址和端口。

QTcpSocket是QAbstractSocket的子类,允许建立TCP连接和传输数据流,QTcpSocket间接继承于QIODevice,因此有流数据读写能力,可以将其与QTextStream和QDataStream一起使用。

QAbstractSocket相关接口函数

TCP客户端发起的TCP连接,使用QTcpSocket对象与服务端连接。QTcpSocket使用connectToHost尝试异步的方式连接到服务器, 连接成功会发射connected()信号。如果是需要阻塞的方式连接到服务端,使用waitForConnected()函数,直到发出connect()信号为止。

建立socket连接后,QTcpSocket对象可以向数据缓冲区写或者接收数据,且接收和发送是异步工作的,有各自的缓冲区。

16.2. UDP

UDP(用户数据报协议,User Datagram Protocol)是一种轻量级,不可靠,面向数据报的无连接协议,当可靠性不重要时可以使用它。 UDP是面向数据报传输,不需要建立持久的socket连接:

UDP通讯不区分客户端和服务端,都是客户端。两个UDP客户端通讯时需要指定目的地址和端口。

16.2.1. UDP通讯相关的类

QUdpSocket 类提供socket,允许您发送和接收UDP数据包。它继承了QAbstractSocket,因此它共享QTcpSocket的大部分接口。 主要区别在于QUdpSocket将数据作为数据报而不是连续的数据流进行传输。

通常使用UDP接收信息,需要bind()绑定到地址和端口,用于传入的数据报。当有数据报传入时,会发射readyRead()信号,然后使用 readDatagram()/rereceiveDatagram()读取接收的数据报。 需要注意的是,当收到readyRead()信号时,应该读取传入的数据报,否则该信号下一个数据报接收到时,将不会发出信号。

使用UDP发送信息,不需要绑定地址和端口,调用writeDatagram()写数据包, 数据报通常小于512字节,除了要传输的数据外,还包含数据报的发送方和接收方的IP地址和端口。

使用QUdpSocket,还可以使用connectToHost()建立与UDP服务器的虚拟连接,然后使用标准QIODevices的read()和write()函数交换数据报,而无需为每个数据报指定接收器。

使用示例:

24
// 初始化一个QUdpSocket,绑定地址和端口
void Server::initSocket()
    // 创建一个UDP套接字对象
    udpSocket = new QUdpSocket(this);
    // 绑定套接字到本地主机的IP地址和端口号
    udpSocket->bind(QHostAddress::LocalHost, 7755);
    // 将UDP套接字的readyRead信号连接到readPendingDatagrams槽函数
    connect(udpSocket, &QUdpSocket::readyRead, this, &Server::readPendingDatagrams);
void Server::readPendingDatagrams()
    // 循环读取待处理的广播数据报
    while (udpSocket->hasPendingDatagrams()) {
        // 接收一个广播数据报
        QNetworkDatagram datagram = udpSocket->receiveDatagram();
        // 处理接收到的广播数据报
        processTheDatagram(datagram);

前面将的都是一对一的UDP通讯,叫做单播(unicast), UDP通讯还有广播(boardcast)和多播/组播(multicast),广播通常用于实现网络发现协议,例如查找网络上的哪个主机具有最大的可用硬盘空间。 要广播数据报,只需将其发送到特殊地址QHostAddress::Broadcast(255.255.255.255),或发送到本地网络的广播地址。

QUdpSocket还支持多播/组播(multicast)。UDP客户端加入一个有组播IP地址的多播组,向组播地址发送数据报,组内成员将都收到数据报。 使用joinMulticastGroup()和leaveMulticastGroup()函数加入或者离开一个多播组。

16.3. 高层网络协议(HTTP,FTP等)

Qt 网络模块还提供了一些类,用于高层的网络协议(例如HTTP,FTP),主要是QNetworkRequest、QNetworkAccessManage、QNetworkReply。

网络请求由 QNetworkRequest 类表示,该类也充当与请求相关联的信息(例如,任何标头信息和所使用的加密)的常规容器。 构造请求对象时指定的URL确定用于请求的协议,支持HTTP,FTP和本地文件URL进行上载和下载。

网络操作的协调由 QNetworkAccessManager 类执行。使用QNetworkRequest类创建请求后, 将使用QNetworkAccessManager类来分派请求并发出信号以报告其进度。QNetworkAccessManager还协调使用Cookie来存储客户端上的数据,身份验证请求以及代理的使用。

对网络请求的响应由 QNetworkReply 类表示。由QNetworkAccessManager在发送网络请求后创建网络响应(QNetworkReply), QNetworkReply提供的信号可用于单独监视每个答复,或者开发人员可以选择为此目的使用管理器的信号,而放弃对答复的引用。 由于QNetworkReply是QIODevice的子类,因此可以同步或异步地处理答复,即作为阻塞或非阻塞操作。

16.4. 例程说明

例程是一个简单的TCP socket通讯。

16.4.1. 代码简单讲解

在Qt Creator创建一个工程,包含UI界面,界面布局参考下配套例程。

在主函数中,创建创建一个QActionGroup,用于选择当作TCP客户端还是TCP服务端, 然后为TCP客户端创建一个socket(tcpClient),为TCP服务端创建一个QTcpServer,监听接受客户端的请求。

lubancat_qt_tutorial_code/QtNetwork/TcpCommunication/mainwindow.cpp
36
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
    ui->setupUi(this);   // 初始化UI
    QActionGroup *tcpGroupAction = new QActionGroup(this);   // 创建一个QActionGroup
    tcpGroupAction->setExclusive(true);                      // 设置QActionGroup中Qaction互斥选中
    tcpGroupAction->addAction(ui->actionClient);   // 将客户端Qaction添加到动作组中
    tcpGroupAction->addAction(ui->actionServer);   // 将服务器Qaction添加到动作组中
    // 连接QActionGroup的triggered信号到updateTcpGroup槽函数
    connect(tcpGroupAction, &QActionGroup::triggered, this, &MainWindow::updateTcpGroup);
    ui->btn_connect->setEnabled(true);       // 启用连接按钮
    ui->btn_disconnect->setEnabled(false);   // 禁用断开连接按钮
    this->setWindowTitle("TCP客户端");        // 设置主窗口标题为"TCP客户端"
    QString localIP=getLocalIP();        // 获取本机IP地址
    ui->comboServer->addItem(localIP);   // 将本机IP地址添加到服务器下拉框中
    ui->comboClient->addItem(localIP);   // 将本机IP地址添加到客户端下拉框中
    // 创建TCP socket
    tcpClient=new QTcpSocket(this);   // 创建一个QTcpSocket对象,用于TCP客户端的连接和数据传输
    // 关联tcp客户端socket相关槽函数
    connect(tcpClient,SIGNAL(connected()),   this,SLOT(do_connected()));
    connect(tcpClient,SIGNAL(disconnected()),this,SLOT(do_disconnected()));
    connect(tcpClient,SIGNAL(readyRead()),   this,SLOT(do_tcpClient_ReadyRead()));
    connect(tcpClient,&QTcpSocket::stateChanged,this,&MainWindow::do_socketStateChange);
    // 创建TCP server
    tcpServer=new QTcpServer(this);   // 创建一个QTcpServer对象,用于监听和接受TCP客户端的连接请求
    // 当有新的连接请求时,执行do_newConnection()槽函数
    connect(tcpServer,SIGNAL(newConnection()),this,SLOT(do_newConnection()));

用做TCP服务端时,设置服务端的IP的端口,点击监听按钮,就会 当接受到一个连接,执行do_newConnection()槽函数,在该函数中创建一个Socket用于通信。 当该Socket接受到数据时,执行do_tcpSocket_ReadyRead函数,读取缓冲区数据,并显示在plainTextEdit。

lubancat_qt_tutorial_code/QtNetwork/TcpCommunication/mainwindow.cpp
20
// QTcpServer  newConnection的槽函数
void MainWindow::do_newConnection()
    ui->plainTextEdit->appendPlainText("一个客户端连接");
    tcpSocket = tcpServer->nextPendingConnection();   // 创建TCP服务端通信socket
    // 关联tcpSocket相关槽函数
    connect(tcpSocket, SIGNAL(connected()),this, SLOT(do_clientConnected()));
    connect(tcpSocket, SIGNAL(disconnected()),this, SLOT(do_clientDisconnected()));
    connect(tcpSocket,SIGNAL(readyRead()),  this,SLOT(do_tcpSocket_ReadyRead()));
    connect(tcpSocket,&QTcpSocket::stateChanged,this,&MainWindow::do_socketStateChange);
// 读取缓冲区
void MainWindow::do_tcpSocket_ReadyRead()
    // tcp 服务端socket读取
    while(tcpSocket->canReadLine())
        ui->plainTextEdit->appendPlainText("接收:"+tcpSocket->readLine());

用做TCP客户端时,设置服务端的监听地址和端口,点击连接按钮,就会执行on_btn_connect_clicked槽函数, 使用connectToHost(ipaddr,port)函数连接服务端,连接成功会显示socket的状态:

lubancat_qt_tutorial_code/QtNetwork/TcpCommunication/mainwindow.cpp
35
// TCP客户端连接服务器
void MainWindow::on_btn_connect_clicked()
    QString ipaddr=ui->comboServer->currentText();
    quint16 port=ui->spinPort->value();
    tcpClient->connectToHost(ipaddr,port);      // 连接到服务端
// socket状态变化
void MainWindow::do_socketStateChange(QAbstractSocket::SocketState socketState)
    switch(socketState)
    case QAbstractSocket::UnconnectedState:
        ui->statusbar->showMessage("tcpsocket状态:UnconnectedState");
        break;
    case QAbstractSocket::HostLookupState:
        ui->statusbar->showMessage("tcpsocket状态:HostLookupState");
        break;
    case QAbstractSocket::ConnectingState:
        ui->statusbar->showMessage("tcpsocket状态:ConnectingState");
        break;
    case QAbstractSocket::ConnectedState:
        ui->statusbar->showMessage("tcpsocket状态:ConnectedState");
        break;
    case QAbstractSocket::BoundState:
        ui->statusbar->showMessage("tcpsocket状态:BoundState");
        break;
    case QAbstractSocket::ClosingState:
        ui->statusbar->showMessage("tcpsocket状态:ClosingState");
        break;
    case QAbstractSocket::ListeningState:
        ui->statusbar->showMessage("tcpsocket状态:ListeningState");