目录
1、Qt的UDP Socket
1.1 用Udp实现服务器
1.2 用Udp实现客户端
2、Qt的TCP Socket
2.1 用Tcp实现服务器
2.2 用Tcp实现客户端
3、Qt的HTTP
3.1使用Qt的HTTP
结语
前言:
网络协议是每个平台都必须遵守的,只是不同的平台所提供的网络API不相同,而Qt具有跨平台性,因此Qt对网络编程也封装了一套自己的API。值得注意的是,在使用Qt进行网络编程之前, 需要在项目中的.pro文件中添加network模块。
1、Qt的UDP Socket
Qt使用UDP通信,需要用到两个类,分别是:1、QUdpSocket,2、QNetworkDatagram。其中在网络通信中关于socket的相关工作都被集成在QUdpSocket类中,而数据传输用到的数据报则用QNetworkDatagram类表示(数据报包括数据内容,对方的端口号、ip地址)。
QUdpSocket提供的接口介绍如下:
bind(const QHostAddress&, quint16)
| 绑定端口号、ip地址 |
receiveDatagram()
|
返回
QNetworkDatagram,即
对方发送过来的数据报
|
writeDatagram(const QNetworkDatagram&)
| 向对方发送一个QNetworkDatagram |
readyRead(是一个信号)
|
在收到数据并准备就绪后触发
|
QNetworkDatagram提供的接口介绍如下:
QNetworkDatagram(const QByteArray&, const QHostAddress& , quint16 )
|
通过
QByteArray,
目标IP地址,目标端⼝号构造⼀个 UDP数据报,通常用于发送数据时
|
data()
|
返回QByteArray,表示数据报内部持有的文本
|
senderAddress()
|
获取数据报中对方的IP地址
|
senderPort()
|
获取数据报中对方的端⼝号
|
QByteArray是⼀个字节数组,可以和QString进行相互转换。例如: 使⽤QString的构造函数即可把QByteArray转成QString,使用QString的toUtf8函数即可把QString转成QByteArray。
1.1 用Udp实现服务器
服务器的界面设计比较简单,因为服务器只需要显示消息即可,服务器回馈给客户端的信息也是程序自动触发的,所以只需要一个QListWidget控件即可,界面设计如下:
1、首先在widget.h文件中创建一个QUdpSocket对象,代码如下:
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QUdpSocket>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
void request();//槽函数
private:
Ui::Widget *ui;
QUdpSocket* ser;
};
#endif // WIDGET_H
2、在widget.cpp文件中new出一个QUdpSocket对象给到udp指针,并完成信号readyRead与槽函数的连接,而所有的发送、接收逻辑都在该槽函数中实现。于此同时还要完成绑定,目的是让服务器能够接收到客户端的消息。代码如下:
#include "widget.h"
#include "ui_widget.h"
#include <QMessageBox>
#include <QNetworkDatagram>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
ser = new QUdpSocket(this);
this->setWindowTitle("服务器");//设置窗口标题
connect(ser,&QUdpSocket::readyRead,this,&Widget::request);//绑定操作
bool ret = ser->bind(QHostAddress::Any,8080);//连接信号与槽
if(!ret)
{
QMessageBox::critical(nullptr, "服务器启动出错", ser->errorString());
return;
}
}
Widget::~Widget()
{
delete ui;
}
QString ser_response(QString request)
{
return "服务器说:"+request;
}
void Widget::request()
{
QNetworkDatagram request = ser->receiveDatagram();//获取一个请求的数据报
QString request_text = request.data();//拿到数据报中的文本内容
QString response_text = ser_response(request_text);//模拟服务器处理请求的动作,生成一个答复
//将答复发送回去
QNetworkDatagram response(request_text.toUtf8(),request.senderAddress(),request.senderPort());
ser->writeDatagram(response);
//在界面的listwidget中打印出以上信息
QString log = "["+request.senderAddress().toString()+" "+
QString::number(request.senderPort())+"]"+"客户端说:"+request_text;
log+=" "+response_text;
ui->listWidget->addItem(log);
}
运行结果:
此时的运行结果什么也观察不到,原因就是服务器的接收和反馈功能都是在触发readyRead信号时才会执行的,而只有当客户端发送数据才会触发readyRead信号,所以还需要写一个客户端才能看到具体的效果。
1.2 用Udp实现客户端
设计一个界面,该界面包含⼀个QLineEdit , QPushButton , QListWidget。其中将要发送的文本写进QLineEdit中,点击QPushButton按钮就进行发送操作,并且发送的信息会显示在QListWidget,方便后续的查看。界面如下:
1、和服务器一样,先在widget.h文件中创建一个QUdpSocket对象,代码如下:
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QUdpSocket>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private slots:
void on_pushButton_clicked();//按钮的槽函数
void response();//接收的数据时,会执行该槽函数
private:
Ui::Widget *ui;
QUdpSocket* cli;
};
#endif // WIDGET_H
2、首先实现发送消息的逻辑:QPushButton的槽函数,点击QPushButton时,则客户端向服务器发送信息,其次实现接收逻辑,因为客户端要拿到服务器的反馈,因此可以连接readyRead信号与槽,在槽函数中实现接收逻辑。代码如下:
#include "widget.h"
#include "ui_widget.h"
#include <QNetworkDatagram>
const QString& ser_ip = "127.0.0.1";//服务器ip
const int ser_port = 8080;//服务器端口号
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
cli = new QUdpSocket(this);
//设置窗口标题
this->setWindowTitle("客户端");
//连接信号与槽,目的是处理服务器反馈的消息
connect(cli,&QUdpSocket::readyRead,this,&Widget::response);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_clicked()//按钮的槽函数
{
QString text = ui->lineEdit->text();//获取要发送给服务器的内容
//构造数据报
QNetworkDatagram resquest(text.toUtf8(),QHostAddress(ser_ip),ser_port);
cli->writeDatagram(resquest);//发送给客户端
ui->listWidget->addItem("客户端说:"+text);//将发送的内容显示在界面上
ui->lineEdit->clear();//发送过后情况输入框中的内容
}
void Widget::response()
{
QString response = cli->receiveDatagram().data();
ui->listWidget->addItem("服务器说:"+response);
}
运行结果:
2、Qt的TCP Socket
Qt使用TCP 通信,需要用到两个类:1、QTcpServer,2、QTcpSocket。其中QTcpServer是专门给服务器提供的类,客户端用不到该类,服务器用该类进行绑定、监听以及建立连接,连接建立好后,使用QTcpSocket类进行数据的传输和接收。
QTcpServer提供的函数介绍如下:
listen(const QHostAddress&, quint16 port)
|
绑定指定的地址和端口号, 并开始监听
|
nextPendingConnection()
|
用于建立连接,返回⼀个QTcpSocket类型的指针,表示与客户端建立好了连接,通过这个指针完成与客户端的通信
|
newConnection (是一个信号)
|
与新的客户端建立好连接后触发
|
QTcpSocket提供的函数介绍如下:
readAll()
|
读取当前接收缓冲区中的所有数据存放到QByteArray对象并返回
|
write(const QByteArray& )
| 将数据发送给对方 |
deleteLater()
|
暂时把socket对象标记为⽆效,在下个事件循环中析构释放当前对象
|
readyRead
| 在收到数据并准备就绪后触发 |
disconnected
|
连接断开时触发
|
connectconst QHostAddress&, quint16 | 用于给客户端连接上服务器 |
2.1 用Tcp实现服务器
Tcp实现服务器的界面逻辑和Udp相似,只需要一个QListWidget即可,界面设计如下:
1、在widget.h文件中创建一个QUdpSocket对象,代码如下:
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QTcpServer>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
void new_cli();//建立连接时调用的槽函数
private:
Ui::Widget *ui;
QTcpServer* ser;
};
#endif // WIDGET_H
2、当有客户端进行连接时,服务器的QTcpServer会产生newConnection信号,说明有客户端连接服务器了,这时候可以在对应的槽函数中实现通信逻辑,即调用nextPendingConnection函数获取到QTcpSocket指针,通过该指针与新连接的客户端进行数据传输。此时,当客户端发送信息时,QTcpSocket也会产生readyRead信号,在该信号的槽函数中实现具体接收和发送逻辑,最后可以在客户端断开连接的时候,依靠信号disconnected来做一个提示。服务器的widget.cpp代码如下:
#include "widget.h"
#include "ui_widget.h"
#include <QTcpSocket>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
ser = new QTcpServer(this);
//设置窗口标题
this->setWindowTitle("服务器");
//当有新客户端来连接时,执行new_cli函数
connect(ser,&QTcpServer::newConnection,this,&Widget::new_cli);
//绑定、监听
ser->listen(QHostAddress::Any,8080);
}
Widget::~Widget()
{
delete ui;
}
QString ser_response(QString request)
{
return request;
}
void Widget::new_cli()
{
QTcpSocket* client = ser->nextPendingConnection();//拿到客户端的socket
QString text = "["+client->peerAddress().toString()+" "
+QString::number(client->peerPort())+"]"+"客户端上线";
ui->listWidget->addItem(text);
//lambda处理数据传输
connect(client,&QTcpSocket::readyRead,this,[=](){
QString request = client->readAll();//拿到客户端的请求
QString response = ser_response(request);//模拟服务器处理请求,得到答复
client->write(response.toUtf8());//反馈给客户端
QString log = "["+client->peerAddress().toString()+" "
+QString::number(client->peerPort())+"]"+"客户端说:"+request
+" "+"服务器说:"+response;
ui->listWidget->addItem(log);
});
//lambda处理断开连接
connect(client,&QTcpSocket::disconnected,this,[=](){
QString log = "["+client->peerAddress().toString()+" "
+ QString::number(client->peerPort()) + "]客⼾端下线!";
ui->listWidget->addItem(log);
// 删除 clientSocket
client->deleteLater();
});
}
仅仅有一个服务器的代码是无法测试结果的,因此还需要些一个客户端代码,如下文。
2.2 用Tcp实现客户端
Tcp实现的客户端界面和Udp客户端界面是一样的,因此这里不再展示。不同的是,Tcp的客户端无需使用QTcpServer,直接使用QTcpSocket类进行通信即可,客户端的widget.cpp代码如下:
#include "widget.h"
#include "ui_widget.h"
const QString& ser_ip = "127.0.0.1";//服务器ip
const int ser_port = 8080;//服务器端口号
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
cli = new QTcpSocket(this);
//设置标题
this->setWindowTitle("客户端");
//连接服务器
cli->connectToHost(ser_ip,ser_port);
//实现客户端接收服务器反馈的逻辑
connect(cli,&QTcpSocket::readyRead,this,[=]()
{
QString ser_text = cli->readAll();
ui->listWidget->addItem("服务器说:"+ser_text);
});
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_clicked()//按钮的槽函数
{
QString text = ui->lineEdit->text();//获取输入框的文本
cli->write(text.toUtf8());//发送该文本
ui->listWidget->addItem("客户端说:"+text);//显示在listwidget上
ui->lineEdit->clear();//发送完成后清空输入框
}
运行结果:
客户端关闭后:
3、Qt的HTTP
Qt对HTTP协议做了封装,以供开发者方便使用HTTP协议进行与服务器的交互。Qt使用HTTP协议的核心三个类分别是:1、QNetworkRequest,2、QNetworkAccessManager,3、QNetworkReply。
1、其中QNetworkRequest提供的核心API如下:
QNetworkRequest(const QUrl& )
|
通过URL构造⼀个 HTTP 请求
|
2、QNetworkAccessManager提供的核心API如下:
get(const QNetworkRequest& )
|
以QNetworkRequest为参数,
发起⼀个 HTTP GET 请求,返回 QNetworkReply对象
|
3、QNetworkReply提供的核心API如下:
readAll()
|
读取响应body
|
error()
|
获取出错状态
|
errorString()
|
获取出错原因的文本
|
finished(是一个信号) | 在客户端收到完整的响应数据之后触发,使用逻辑和上文的readyRead一样,即该信号触发后就可以从网络中读取内容了 |
3.1使用Qt的HTTP
界面设计和上述的客户端例子一样,通过发送输入框中的文本内容给服务器,从而从服务器获取到答复,再将该答复显示在QListWidget。
widget.h代码如下:
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QNetworkAccessManager>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private slots:
void on_pushButton_clicked();
private:
Ui::Widget *ui;
QNetworkAccessManager* manager;
};
#endif // WIDGET_H
widget.cpp代码如下:
#include "widget.h"
#include "ui_widget.h"
#include <QNetworkReply>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
manager = new QNetworkAccessManager(this);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_clicked()//实现按钮的槽函数
{
// 1. 获取到输⼊框中的 URL, 构造 QUrl 对象
QUrl url(ui->lineEdit->text());
// 2. 构造 HTTP 请求对象
QNetworkRequest request(url);
// 3. 发送 GET 请求
QNetworkReply* response = manager->get(request);
// 4. 通过信号槽来处理响应
connect(response, &QNetworkReply::finished, this, [=]() {
if (response->error() == QNetworkReply::NoError) {
// 响应正确
QString html(response->readAll());
ui->listWidget->addItem(html);
// qDebug() << html;
} else {
// 响应出错
ui->listWidget->addItem(response->errorString());
}
response->deleteLater();
});
}
运行结果:
结语
以上就是关于Qt网络编程的讲解,网络编程在任何平台下的编写逻辑都大同小异,不同的只是细节上的问题,Qt中对网络API进行了封装,这不仅让Qt可以在不同的平台下进行网络编程,还方便了开发者的使用。
最后如果本文有遗漏或者有误的地方欢迎大家在评论区补充,谢谢大家!!