目录
核心API
示例:回显服务器
服务器端编写:
第一步:创建出socket对象
第二步: 连接信号槽
第三步:绑定端口号
第四步:编写信号槽所绑定方法
第五步:编写第四步中处理请求的方法
客户端编写:
界面设计
代码编写
发送按钮槽函数
处理响应函数
完整代码:
测试结果:
注意:
使用Qt网络编程的API,需要先在 .pro文件中添加 network模块! !
QT += core gui network
核心API
QUdpSocket 表示⼀个 UDP 的 socket ⽂件.
名称 | 类型 | 说明 | 对应原生API |
---|---|---|---|
bind(const QHostAddress&, quint16)
|
方法
|
绑定指定的端⼝号.
|
bind
|
receiveDatagram()
| 方法 |
返回QNetworkDatagram读取⼀个 UDP 数据报
|
recvfrom
|
writeDatagram(const QNetworkDatagram&)
| 方法 |
发送⼀个 UDP 数据报
|
sendto
|
readyRead
| 信号 |
在收到数据并准备就绪后触发
|
⽆ (类似于 IO 多路复⽤的通 知机制)
|
名称 | 类型 | 说明 | 对应原生API |
---|---|---|---|
QNetworkDatagram(const QByteArray&, const QHostAddress& , quint16 )
|
构造函
数
|
通过
QByteArray
, ⽬标 IP 地址, ⽬标端⼝号 构造⼀个 UDP 数据报. 通常⽤于发送数据时.
| 无 |
data()
| 方法 |
获取数据报内部持有的数据. 返回
QByteArray
| 无 |
senderAddress()
| 方法 |
获取数据报中包含的对端的 IP 地
址.
| 无 |
senderPort()
| 方法 |
获取数据报中包含的对端的端⼝号.
| ;无 |
在编写udp相关代码时,注意事项:
- 一定要先连接信号槽,再绑定端口号,一旦绑定端口了,意味着请求就可以被收到了,如果在完成绑定之后,在连接信号槽之前,有客户端把请求发过来了,此时就可能读不到这样的请求。
- 一个端口号只能被一个socket绑定。
- socket->errorString() 本质上也是对系统的errno机制进行封装
示例:回显服务器
服务器端编写:
第一步:创建出socket对象
socket = new QUdpSocket(this);
第二步: 连接信号槽
connect(socket , &QUdpSocket::readyRead , this , &Widget::processRequest);
第三步:绑定端口号
这里记得判断是否绑定成功,如果端口号被其他socket所绑定,我们就不能在绑定该端口号!
bool ret = socket->bind(QHostAddress::Any,9090);
if(!ret)
{
//绑定失败
QMessageBox::critical(this,"服务器启动出错",socket->errorString());
return;
}
第四步:编写信号槽所绑定方法
void Widget::processRequest()
{
//1.读取请求并解析
const QNetworkDatagram& requestDatagram = socket->receiveDatagram();
QString request = requestDatagram.data(); //这里data()返回的是QByte数据
//2.根据请求计算响应(由于这里仅仅是回显服务器,响应不需要计算,就是请求本身)
const QString& response = process(request);
//3.把响应写回客户端,响应数据包(数据是啥,客户端ip地址,客户端端口)
QNetworkDatagram responseDatagram(response.toUtf8(),requestDatagram.senderAddress(),requestDatagram.senderPort());
//response.toUtf8() 取出QString内部的字节数组
socket->writeDatagram(responseDatagram);
//4.把这次交互的信息,显示到界面上
QString log = "[" + requestDatagram.senderAddress().toString() + ":" + QString::number(requestDatagram.senderPort())+
"] req:" + request + ", resp:" + response;
ui->listWidget->addItem(log);
}
第五步:编写第四步中处理请求的方法
QString Widget::process(const QString &request)
{
//请求处理过程,由于当前是回显服务i,响应和请求完全一样,不需要进行处理
return request;
}
完整代码如下:
#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("服务器");
//连接信号槽
connect(socket,&QUdpSocket::readyRead,this,&Widget::processRequest);
//绑定端口号
bool ret = socket->bind(QHostAddress::Any,9090);
if(!ret)
{
//绑定失败
QMessageBox::critical(this,"服务器启动出错",socket->errorString());
return;
}
}
Widget::~Widget()
{
delete ui;
}
void Widget::processRequest()
{
//1.读取请求并解析
const QNetworkDatagram& requestDatagram = socket->receiveDatagram();
QString request = requestDatagram.data();
//2.根据请求计算响应(由于这里仅仅是回显服务器,响应不需要计算,就是请求本身)
const QString& response = process(request);
//3.把响应写回客户端,响应数据包(数据是啥,客户端ip地址,客户端端口)
QNetworkDatagram responseDatagram(response.toUtf8(),requestDatagram.senderAddress(),requestDatagram.senderPort());
//response.toUtf8() 取出QString内部的字节数组
socket->writeDatagram(responseDatagram);
//4.把这次交互的信息,显示到界面上
QString log = "[" + requestDatagram.senderAddress().toString() + ":" + QString::number(requestDatagram.senderPort())+
"] req:" + request + ", resp:" + response;
ui->listWidget->addItem(log);
}
QString Widget::process(const QString &request)
{
//请求处理过程,由于当前是回显服务i,响应和请求完全一样,不需要进行处理
return request;
}
客户端编写:
界面设计
我们客户端简单设计成如下:
效果如下:
端口号本质上是一个2字节的无符号整数 。
代码编写
首先我们写定义两个常量,描述服务器的地址和端口:
const QString& SERVER_IP = "127.0.0.1";
const quint16 SERVER_PORT = 9090;
发送按钮槽函数
void Widget::on_pushButton_clicked()
{
//1.获取输入框的内容
const QString& text = ui->lineEdit->text();
//2. 构造UDP请求数据,这里ip我们是QString,参数识别不出来,需要我们进行转换
QNetworkDatagram requestDatagram(text.toUtf8(),QHostAddress(SERVER_IP),SERVER_PORT);
//3. 发送请求数据
socket->writeDatagram(requestDatagram);
//4. 把发送的请求数据也添加到列表框中
ui->listWidget->addItem("客户端说:" + text);
//5. 把输入框的内容也清空一下,方便下次输入
ui->lineEdit->setText("");
}
处理响应函数
void Widget::processHandle()
{
//通过这个函数来处理收到的响应
//1.读取到响应的数据
const QNetworkDatagram& responseDatagram = socket->receiveDatagram();
//这里不用换引用是因为.data()返回的是QByte类型,涉及类型转换
QString response =responseDatagram.data();
//2. 把响应数据显示到界面上
ui->listWidget->addItem("服务器说:"+response);
}
完整代码:
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();
void processHandle();
private slots:
void on_pushButton_clicked();
private:
Ui::Widget *ui;
QUdpSocket* socket;
};
#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QNetworkDatagram>
const QString& SERVER_IP = "127.0.0.1";
const quint16 SERVER_PORT = 9090;
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::processHandle);
}
Widget::~Widget()
{
delete ui;
}
void Widget::processHandle()
{
//通过这个函数来处理收到的响应
//1.读取到响应的数据
const QNetworkDatagram& responseDatagram = socket->receiveDatagram();
QString response =responseDatagram.data();
//2. 把响应数据显示到界面上
ui->listWidget->addItem("服务器说:"+response);
}
void Widget::on_pushButton_clicked()
{
//1.获取输入框的内容
const QString& text = ui->lineEdit->text();
//2. 构造UDP请求数据
QNetworkDatagram requestDatagram(text.toUtf8(),QHostAddress(SERVER_IP),SERVER_PORT);
//3. 发送请求数据
socket->writeDatagram(requestDatagram);
//4. 把发送的请求数据也添加到列表框中
ui->listWidget->addItem("客户端说:" + text);
//5. 把输入框的内容也清空一下,方便下次输入
ui->lineEdit->setText("");
}
测试结果:
要想开启多个客户端,我们只需要找到项目对应的文件中,运行.exe文件即可