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请求,发送之后通过信号槽获取响应并显示在文本框中。

结果如下:


相关推荐
用户805533698034 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner4 天前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
Quz9 天前
QML Hello World 入门示例
qt
xcyxiner12 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner13 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner13 天前
DicomViewer (添加模型类)3
qt
xcyxiner14 天前
DicomViewer (目录调整) 2
qt
xcyxiner14 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
LDR00616 天前
Type-C 快充全面升级!LDR6601 赋能个人护理便携电机,重塑剃须刀 / 理发器新体验
c语言·开发语言
雪碧聊技术16 天前
Tree.js是什么?一文讲透
开发语言·javascript·ecmascript