要使用 Qt 网络编程,需要在项目中的 .pro
文件中添加 network
模块
核心API
Qt 的 UDP Socket 主要的类有两个 QUdpSocket
和 QNetworkDatagram
因为是 UDP 是面向数据报的,QNetworkDatagram 就是对 数据报的封装,内容包括:对端发送的数据,对端的 IP 和 端口号
QUdpSocket
表示一个 UDP 的 socket 文件
名称 | 类型 | 说明 | 对标原生API |
---|---|---|---|
bind(const QHostAddress &address, quint16 port = 0) | 方法 | 绑定指定的 IP地址 和 端口号 | bind |
receiveDatagram() | 方法 | 返回 QNetworkDatagram 读取一个 UDP 数据报 | recvfrom |
writeDatagram(const QNetworkDatagram &datagram) | 方法 | 发送一个 UDP 数据报 | sendto |
writeDatagram(const char *data, qint64 size, const HostAddress &address, quint16 port) | 方法 | data是要发送的数据,size是数据的长度,address是目标 IP地址,port是目标端口号 | sendto |
readyRead | 信号 | 在收到数据并准备就绪后触发 | 类似于 IO 多路复用的通知机制 |
QNetworkDatagram
表示一个 UDP 数据报
名称 | 类型 | 说明 | 对标原生API |
---|---|---|---|
QNetworkDatagram(const QByteArray &data, const QHostAddress &destinationAddress = QHostAddress(), quint16 port = 0) | 构造函数 | 通过 QByteArray ,目标 IP地址,目标端口号构造一个 UDP 数据报,通常用于发送数据 | 无 |
data() | 方法 | 获取数据报内部持有的数据,返回 QByteArray | 无 |
senderAddress() | 方法 | 获取数据报中包含的对端的IP地址 | recvfrom 包含了该功能 |
senderPort() | 方法 | 获取数据报中包含的对端的端口号 | recvfrom 包含了该功能 |
代码示例
通过模拟 客户服务器的UDP通信
熟悉上述方法使用
客户端通过输入栏获取数据,点击按钮发送给服务端
服务端接受客户端数据,并返回相同数据,验证通信无误
服务器
- 创建界面,包含一个
QListWidget
用来显示消息
- 创建
QUdpSocket
成员,修改widget.h
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
//槽函数
public slots:
void handleRequest();//处理客户端请求
private:
QString process(const QString &request);//业务处理逻辑
private:
Ui::Widget *ui;
QUdpSocket *socket;//套接字
};
- 编写
widget.cpp
,实现相关方法
因为 UDP 是无连接的,绑定端口号后就可以接受数据了,所以要先对 readReady信号
连接槽函数
逻辑如下:1. 设置窗口标题 2. 创建套接字 3. 连接信号槽 4. 绑定端口号
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//1. 设置窗口标题
this->setWindowTitle("服务端");
//2. 创建套接字
socket = new QUdpSocket(this);
//UDP绑定完就开启服务了,所以需要先连接信号槽
//3. 连接信号槽
connect(socket, &QUdpSocket::readyRead, this, &Widget::handleRequest);
//4. 绑定,服务端绑定任意IP,端口号指定为9090
bool ret = socket->bind(QHostAddress::Any, 9090);
if(!ret)
{
QMessageBox::critical(this, "错误", "绑定套接字失败");
return;
}
}
编写 readReady
对应槽函数
逻辑如下:1. 获取用户数据报 2. 提取其中数据 3. 业务处理 4. 返回响应
void Widget::handleRequest()
{
//1. 获取用户数据报
const QNetworkDatagram requestDatagram = socket->receiveDatagram();
//2. 提取其中数据
QString request = requestDatagram.data();
//3. 业务处理逻辑
QString responce = process(request);
//4. 记录客户端数据
QString log = "[" + requestDatagram.senderAddress().toString() + ":" + QString::number(requestDatagram.senderPort())
+ "] request:" + request + " responce:" + responce;
ui->listWidget->addItem(log);
//5. 返回响应
QNetworkDatagram responceDatagram(responce.toUtf8(), requestDatagram.senderAddress(), requestDatagram.senderPort());
socket->writeDatagram(responceDatagram);
}
此处服务器的业务处理只是简单的回显数据
QString Widget::process(const QString &request)
{
return request;
}
客户端
- 创建界面。包含 一个
QLineEdit
,QPushButton
,QListWidget
通过布局管理器搭配,规范界面
- 声明全局常量:服务端的
IP地址
和端口号
#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 9090
- 创建
QUdpSocket
成员,修改widget.h
,定义成员
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private slots:
//按钮点击槽函数
void on_pushButton_clicked();
//客户端业务处理逻辑
void handleRequest();
private:
Ui::Widget *ui;
QUdpSocket *socket;
};
- 修改
widget.cpp
,初始化 socket
客户端不需要绑定端口号
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
ui->pushButton->setShortcut(QKeySequence(Qt::Key_Return));
//设置窗口标题
this->setWindowTitle("客户端");
//创建套接字
socket = new QUdpSocket(this);
//连接信号槽
connect(socket, &QUdpSocket::readyRead, this, &Widget::handleRequest);
}
按钮点击槽函数
逻辑如下:1. 获取输入栏文本 2. 记录发送信息 3. 将消息发送给服务端 4. 清空输入栏
void Widget::on_pushButton_clicked()
{
//1. 获取输入栏的文本
QString text = ui->lineEdit->text();
QNetworkDatagram requestDatagram(text.toUtf8(), QHostAddress(SERVER_IP), SERVER_PORT);
//2. 记录发送信息
QString message = "发送:" + text;
ui->listWidget->addItem(message);
//3. 将消息发送给服务端
socket->writeDatagram(requestDatagram);
//4. 将输入栏清空
ui->lineEdit->clear();
}
客户端业务处理函数
void Widget::handleRequest()
{
//1. 获取服务端返回的数据报
QNetworkDatagram responceDatagram = socket->receiveDatagram();
QString responce = responceDatagram.data();
//2. 显示消息
ui->listWidget->addItem("接收:" + responce);
}
运行结果如下:
结束语
感谢你的阅读
如果觉得本篇文章对你有所帮助的话,不妨点个赞支持一下博主,拜托啦,这对我真的很重要。