文章目录
- Qt网络编程
- QUdpSocket
- Udp回显服务器
- Udp客户端
Qt网络编程
网络编程本质上是写应用层代码,需要传输层提供支持。
而传输层最核心的协议就是UDP和TCP,这两个协议有较大差别,所以Qt提供了两套API。
要是有Qt网络编程的API,需要现在.pro
文件当中添加network
模块。
之前的各种控件,各种内容都是包含在
QtCore
模块当中的(默认添加)
为什么要划分模块?
Qt本身十分庞大,包含了很多框架,如果把所有的Qt功能放到一起,即是写一个简单的
hello world
,此时生成的可执行程序也会非常庞大。比如说,Qt会应用到嵌入式,嵌入式系统的配置并没有那么高,所以对空间的利用 > 对时间的利用
进行模块化处理,在默认情况下,其他额外的模块不参与编译,用的时候在
.pro
文件中引入对应的模块,才能把对应功能给编译加载进来。
网络编程是操作系统提供的API,
C++标准库里面还未提供网络编程的api封装
QUdpSocket
名称 | 类型 | 说明 | 原生API |
---|---|---|---|
bind(const QHostAddress&, quint16) | 方法 | 绑定指定端口 | bind |
receiveDatagram() | 方法 | 返回QNetWorkDatagram 读取UDP数据报 | recvfrom |
writeDatagram(const QNetworkDatagram&) | 方法 | 发送UDP数据报 | sendto |
readyRead | 信号 | 收到数据并准备就绪后触发 | 无(类似与IO多路复用机制) |
QNetWorkDatagram
表示有个UDP数据报
名称 | 类型 | 说明 | 原生API |
---|---|---|---|
QNetworkDatagram(const QByteArray&, const QHostAddress&, quint16) | 构造函数 | 通过QByteArray ,目标IP地址目标端口号,构造一个UDP数据报通常用于发送数据时 | 无 |
data() | 方法 | 获取数据报内部持有的数据 返回 QByteArray | 无 |
senderAddress() | 方法 | 获取数据报中包含的对端IP地址 | 无,recvfrom 包含此类功能 |
senderPort() | 方法 | 获取数据报中包含的对端的端口号 | 无,recvfrom 包含此类功能 |
Udp回显服务器
一般服务器都没有图形化界面,此处为了更加直观看到,采用图形化界面方式
ui界面:
.pro
引入network
模块
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:
Ui::Widget *ui;
QUdpSocket* socket;
void processRequest();
QString process(const QString& request);
};
#endif // WIDGET_H
widget.cpp
:
构造对象的时候,参数可以是parent,也就是挂到对象树上(如果不写,之后手动delete
即可)
要线连接信号槽,再进行bind
一旦进行bind,就意味着请求可以收到了。
如果在连接信号槽之前bind,可能收到请求,而信号槽还没连接,此时请求就丢失了
#include "widget.h"
#include "ui_widget.h"
#include<QMessageBox>
#include<QNetworkDatagram>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//创建实例
socket = new QUdpSocket(this); //可以挂到对象树
//设置窗口标题
this->setWindowTitle("udp服务器");
//连接信号槽
connect(socket, &QUdpSocket::readyRead, this, &Widget::processRequest);
//绑定端口号
//any表示不管几个网卡,都可以绑定上去
if(!socket->bind(QHostAddress::Any, 8080))
{
QMessageBox::critical(this, "bind error", socket->errorString());
return;
}
}
Widget::~Widget()
{
delete ui;
}
void Widget::processRequest()
{
//读取请求并解析
const QNetworkDatagram& requestDatagram = socket->receiveDatagram();
//data()返回QByteArray QBtyeArray可以赋值给QString
QString request = requestDatagram.data();
//根据请求计算(此时是回显服务器, 收到什么响应什么)
const QString& response = process(request);
//把响应写给客户端
//取出字节数组 发送到的地址 发送到的端口 都包含在requestDatagram
QNetworkDatagram responseDatagram(response.toUtf8(), requestDatagram.senderAddress(), requestDatagram.senderPort());
socket->writeDatagram(responseDatagram);
//交互的信息 显式到界面
QString log = "[" + requestDatagram.senderAddress().toString() + ":" + QString::number(requestDatagram.senderPort()) +
"] req:" + request + ", resp: " + response;
ui->listWidget->addItem(log);
}
QString Widget::process(const QString &request)
{
return request;
}
Udp客户端
Qt Creator支持同时打开多个项目,但如果姓名中存在同名文件,就非常容易混淆
客户端主动发起请求,界面设置一个输入框,一个发送按钮,一个显示服务器返回内容
widget.h
:
#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 processResponse();
private:
Ui::Widget *ui;
QUdpSocket* socket;
};
#endif // WIDGET_H
widget.cpp
:
在Linux中,需要考虑一些阻塞问题,什么时候阻塞,什么时候解除阻塞。
而这里直接用信号槽机制,很方便。
#include "widget.h"
#include "ui_widget.h"
#include<QNetworkDatagram>
const QString SERVER_IP = "127.0.0.1";
const quint16 SERVER_PORT = 8080;
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
socket = new QUdpSocket(this);
//修改窗口标题
this->setWindowTitle("客户端");
//通过信号槽处理返回的数据
connect(socket, &QUdpSocket::readyRead, this, &Widget::processResponse);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_clicked()
{
//获取输入框内容
const QString& text = ui->lineEdit->text();
//构造udp请求数据包
//需要的参数是字节数组,要转换一下 IP地址也要转换一下
QNetworkDatagram requestDatagram(text.toUtf8(), QHostAddress(SERVER_IP), SERVER_PORT);
//发送请求数据
socket->writeDatagram(requestDatagram);
//发送的请求添加到列表框
ui->listWidget->addItem("client say# " + text);
//输入框内容清空
ui->lineEdit->setText("");
}
void Widget::processResponse()
{
//读取响应数据
const QNetworkDatagram& responseDatagram = socket->receiveDatagram();
QString response = responseDatagram.data();
//响应到界面
ui->listWidget->addItem("server say# " + response);
}