Qt——网络编程

和多线程类似, Qt 为了支持跨平台, 对网络编程的 API 也进行了重新封装。

在进行网络编程之前, 需要在项目中的 .pro 文件中添加 network 模块,添加之后运行一下,使其包含的头文件能够被加载进Qt Creator。

Qt本身是一个非常庞大,包罗万象的框架,如果直接进行编程,则会导致程序的内存过大,所以其包含的各种功能被分别存储在不同的模块,按需使用。


一.UDP

1.相关API

UDP核心的两个类为QUdpSocketQNetworkDatagram

QUdpSocket 表示⼀个 UDP 的 socket 文件,其包含的API如下:

bind(const QHostAddress&, quint16) :绑定指定的端口号

receiveDatagram():返回 QNetworkDatagram ,读取 一个 UDP 数据报

writeDatagram(const QNetworkDatagram&):发送一个 UDP 数据报

readyRead:是一个信号,在收到数据并准备就绪后触发. (类似于 IO 多路复用的通知机制)

QNetworkDatagram 表示一个 UDP 数据报,其包含的API有:

QNetworkDatagram(const QByteArray&, const QHostAddress& , quint16):

通过 QByteArray , 目标 IP 地址, 目标端口号构造一个 UDP 数据报. 通常用于发送数据时.

data():

获取数据报内部持有的数据。返回 QByteArray

senderAddress():

获取数据报中包含的对端的 IP 地址。

senderPort():

获取数据报中包含的对端的端口号。


2.回显服务器

cpp 复制代码
   //创建出对象
    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;
    }

在构造函数中,需要完成套接字对象的创建,连接信号槽并绑定IP和端口号,IP设置为任意IP

随后在槽函数中执行服务器的核心逻辑:

cpp 复制代码
//服务器的核心逻辑
void Widget::processRequest()
{
    //1.读取请求并解析
    const QNetworkDatagram& requestDatagram = socket->receiveDatagram();
    QString request = requestDatagram.data();
    //2.根据请求计算响应(由于是回显服务器,所以响应就是请求本身)
    const QString response = process(request);
    //3.把响应写回给客户端
    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;
}

将收到和发送的数据通过一个列表框显示。


3.回显客户端

服务器和客户端需要分别构建成一个项目来实现

我们希望设计一个客户端界面,可以输入内容并发送,并会显示发送和收到的数据

按钮槽函数的设计如下:

cpp 复制代码
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("");
}

输入内容,将内容数据提取,构建UDP请求报文并发送给服务器,随后将内容显示到列表框中。

cpp 复制代码
//定义两个常量,分别代表服务器的地址和端口号
const QString& SERVER_IP = "127.0.0.1";
const quint16 SERVER_PORT = 9090;

void Widget::process()
{
    //1.获取服务器响应
    QNetworkDatagram responseDategram = socket->receiveDatagram();
    //2.读取数据并显示到列表框中
    QString text = responseDategram.data();
    ui->listWidget->addItem("服务器说:" + text);
}

定义常量IP和端口号 ,代表服务器来进行连接,并构建信号槽函数,获取服务器的响应数据并显示到列表框中,结果如下:


二.TCP

1.相关API

TCP两个核心类为 QTcpServerQTcpSocket

QTcpServer 用于监听端口, 和获取客户端连接,相关API如下:

listen(const QHostAddress&, quint16 port) :绑定指定的地址和端口号,并开始监听。

nextPendingConnection() :从系统中获取到⼀个已经建立好的 tcp 连接。返回⼀个 QTcpSocket , 表示这个客户端的连接。通过这个 socket 对象完成和客户端之间的通信。

newConnection :是一个信号,有新的客户端建立连接好之后触发。

QTcpSocket 用于客户端和服务器之间的数据交互,相关API如下:

readAll() :读取当前接收缓冲区中的所有数据。返回 QByteArray 对象。

write(const QByteArray&): 把数据写入 socket 中。

deleteLater() :暂时把 socket 对象标记为无效。Qt 会在下个事件循环中析构释放该对象。

readyRead :是一个信号,有数据到达并准备就绪时触发。

disconnected :是一个信号,连接断开时触发。

connectToHost(const QString& , quint16):和服务器建立连接。

waitForConnected():等待并确认连接是否出错。


2.回显服务器

在TCP服务器和客户端,我们同样设计一个跟上述UDP一致的界面。

cpp 复制代码
    ui->setupUi(this);
    //1.修改窗口标题
    this->setWindowTitle("服务器");
    //2.创建TcpServer对象
    tcpserver = new QTcpServer(this);
    //3.通过信号槽,指定如何处理连接
    connect(tcpserver,&QTcpServer::newConnection,this,&Widget::ProcessConnection);
    //4.bind IP和端口号
    bool ret = tcpserver->listen(QHostAddress::Any,9090);
    if(!ret)
    {
        QMessageBox::critical(this,"服务器启动失败!",tcpserver->errorString());
        exit(1);
    }

TCP相较于UDP则更加复杂。

在构造函数中同样需要创建对象,不同的是这里创建的不是套接字对象,而是服务器对象 ,TCP不同于UDP能够直接连接,必须通过监听来获取连接。所以需要构建信号槽来处理连接。

cpp 复制代码
void Widget::ProcessConnection()
{
    //1.通过tcpServer获取到一个Socket对象,通过这个对象和客户端进行通信
    QTcpSocket* clientSocket = tcpserver->nextPendingConnection();
    QString log = "[" + clientSocket->peerAddress().toString() + ":" +
            QString::number(clientSocket->peerPort()) + "] 客户端上线啦!";
    ui->listWidget->addItem(log);
    
    //2.通过信号槽,处理客户端发来的请求报文
    connect(clientSocket, &QTcpSocket::readyRead, this, [=](){
        //a.读取请求数据
        QString request = clientSocket->readAll();
        //b.根据请求处理响应
        const QString& response = process(request);
        //c.把响应写回到客户端
        clientSocket->write(response.toUtf8());
        //d.把上述信息记录到日志
        QString log = "[" + clientSocket->peerAddress().toString() + ":" +
                QString::number(clientSocket->peerPort()) + "] " + "req: " +
                request + "resp: " + response;
        ui->listWidget->addItem(log);
    });
    
    //3.通过信号槽,处理客户端断开连接的情况
    connect(clientSocket, &QTcpSocket::disconnected, this, [=](){
        //a.显示断开连接的日志
        QString log = "[" + clientSocket->peerAddress().toString() + ":" +
                QString::number(clientSocket->peerPort()) + "] 客户端下线啦!";
        //b.使用deleteLater释放
        clientSocket->deleteLater();
    });
}
//处理回显服务器
QString Widget::process(const QString request)
{
    return request;
}

通过创建 QTcpSocket 对象来获取客户端报文,进而通过信号槽来处理报文和响应。这里的槽函数写成了lambda表达式

最后,由于 QTcpSocket 对象没有挂在 Qt 的对象树上所以当其断开连接时,必须通过信号槽将其手动释放


3.回显客户端

cpp 复制代码
    //1.修改窗口标题
    this->setWindowTitle("客户端");
    //2.获取Socket对象
    socket = new QTcpSocket(this);
    //3.和服务器建立连接
    socket->connectToHost("127.0.0.1", 9090);
    //4.连接信号槽,处理服务器响应
    connect(socket, &QTcpSocket::readyRead, this, [=](){
        //a.读取响应数据
        QString response = socket->readAll();
        //b.将数据显示在列表框上
        ui->listWidget->addItem("服务器说:" + response);
    });
    //5.等待和确认连接是否建立成功
    if(!socket->waitForConnected())
    {
        QMessageBox::critical(this,"连接服务器出错", socket->errorString());
        exit(1);
    }

在构造函数中,和服务器建立连接,并构建信号槽处理服务器响应

cpp 复制代码
void Widget::on_pushButton_clicked()
{
    //1.获取输入框信息
    QString text = ui->lineEdit->text();
    //2.将数据发送给服务器
    socket->write(text.toUtf8());
    //3.把发送的信息显示在列表框上
    ui->listWidget->addItem("客户端说:" + text);
    //4.清空输入框内容
    ui->lineEdit->setText("");
}

按钮槽函数的设计与UDP基本一致。

结果如下:


三.HTTP

在Qt中,只提供了HTTP的客户端库,未提供服务器库,因此,只能在Qt上开发HTTP客户端


1.相关API

进行 Qt 开发时,和服务器之间的通信很多时候也会用到 HTTP 协议,其核心类包括:QNetworkAccessManager 、 QNetworkRequest 、 QNetworkReply

QNetworkAccessManager 提供了 HTTP 的核心操作

get(const QNetworkRequest&)

发起⼀个 HTTP GET 请求。 返回 QNetworkReply 对象

post(const QNetworkRequest& , const QByteArray&)

发起⼀个 HTTP POST 请求。返回 QNetworkReply 对象

QNetworkRequest 表示一个 HTTP 请求(不含 body)。如果需要发送⼀个带有 body 的请求(比如 post),会在 QNetworkAccessManager 的 post 方法中通过单独的参数来传入 body.

QNetworkRequest(const QUrl& ):

通过 URL 构造⼀个 HTTP 请求

setHeader(QNetworkRequest::KnownHeaders header, const QVariant &value):

设置请求头

其中的 QNetworkRequest::KnownHeaders 是⼀个枚举类型, 常⽤取值:

ContentTypeHeader :描述 body 的类型.

ContentLengthHeader:描述 body 的长度.

LocationHeader :用于重定向报文中指定重定向地址. (响应中使用, 请求用不到)

CookieHeader :设置 cookie

UserAgentHeader :设置 User-Agent

QNetworkReply 表示⼀个 HTTP 响应. 这个类同时也是 QIODevice 的子类.

error(): 获取出错状态.

errorString() :获取出错原因的文本.

readAll(): 读取响应 body.

header(QNetworkRequest::KnownHeaders header): 读取响应指定 header 的值.

此外, QNetworkReply 还有⼀个重要的信号 finished 会在客户端收到完整的响应数据之后触发.


2.客户端实现

设计HTTP客户端时,需要在构造函数中创建 QNetworkAccessManager 对象 ,随后在按钮槽函数中实现请求和响应处理

cpp 复制代码
void Widget::on_pushButton_clicked()
{
    //1.从输入框获取hul
    QUrl url(ui->lineEdit->text());
    //2.构建一个Http请求对象
    QNetworkRequest request(url);
    //3.发送请求
    QNetworkReply* response = manager->get(request);
    //4.构建信号槽,处理响应
    connect(response, &QNetworkReply::finished, this, [=](){
       if(response->error() == QNetworkReply::NoError)
       {
           //获取到正确的HTTP响应
           QString html = response->readAll();
           ui->plainTextEdit->setPlainText(html);
       }
       else
       {
           //响应出错了
           ui->plainTextEdit->setPlainText(response->errorString());
       }
       //还需要对response释放
       response->deleteLater();
    });
}

这里设计了一个简单的不带body的HTTP请求,发送之后通过信号槽获取响应并显示在文本框中。

结果如下:


相关推荐
鲤籽鲲43 分钟前
C# _ 数字分隔符的使用
开发语言·c#
fillwang2 小时前
Python实现Excel行列转换
开发语言·python·excel
北极糊的狐3 小时前
SQL中,# 和 $ 用于不同的占位符语法
java·开发语言
漫漫不慢.4 小时前
九进制转10进制
java·开发语言
西猫雷婶4 小时前
python学opencv|读取图像(二十五)使用cv2.putText()绘制文字进阶-垂直镜像文字
开发语言·python·opencv
大小科圣4 小时前
windows配置jdk
java·开发语言
西猫雷婶4 小时前
python学opencv|读取图像(二十四)使用cv2.putText()绘制文字进阶-倾斜文字
开发语言·python·opencv
2401_858286115 小时前
L27.【LeetCode笔记】2 的幂(五种解法)
c语言·开发语言·笔记·算法·leetcode
代码对我眨眼睛5 小时前
vite+vue3动态引入资源文件(问题已解决但离了个大谱)
开发语言·javascript·vue.js
疯狂的沙粒5 小时前
如何在 JavaScript 中实现日期格式化?
开发语言·前端·css·node.js