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");