Qt——网络编程

Qt封装了各个系统的网络API,以供我们进行网络编程。要想使用网络API,先要引入网络模块(其实就是在.pro文件中设置编译选项以链接库罢了):

不过值得一提的是,Qt利用了信号槽机制实现了事件驱动(即不会盲目阻塞,而是收到数据准备好了的通知后再去读取数据或者获取新链接)。


QUdpSocket

相关接口,方法,信号

使用QUdpsocket编写服务器和客户端

服务器:

cpp 复制代码
#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) {
        // 绑定失败!
        //errorString相当于perror()
        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. 把响应写回给客户端.
    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;
}

客户端:

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::processResponse);
}

Widget::~Widget()
{
    delete ui;
}


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("");
}

void Widget::processResponse()
{
    // 通过这个函数来处理收到的响应.
    // 1. 读取到响应数据
    const QNetworkDatagram& responseDatagram = socket->receiveDatagram();
    QString response = responseDatagram.data();
    // 2. 把响应数据显示到界面上.
    ui->listWidget->addItem("服务器说: " + response);
}

QTcpSocket和QTcpServer

相关接口,方法,信号

使用QTcpsocket和QTcpServer编写服务器和客户端

服务器:

cpp 复制代码
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    //设置窗口标题
    this->setWindowTitle("服务器");

    //创建监听套接字
    listen_tcp = new QTcpServer();


    //给链接建立信号关联槽
    connect(listen_tcp,&QTcpServer::newConnection,this,&MainWindow::accept_link);

    //绑定地址端口号,开始接收链接
    bool ret = listen_tcp->listen(QHostAddress::Any,8080);
    if(!ret)
    {
        //errorString相当于perror()
        QMessageBox::critical(this,"服务器启动失败",listen_tcp->errorString());
        return;
    }
}

void MainWindow::accept_link()
{
    //获取新链接
    QTcpSocket* newlink = listen_tcp->nextPendingConnection();

    //在服务器界面显示链接成功的信息
    const QHostAddress& addr = newlink->peerAddress();
    ui->listWidget->addItem("["+addr.toString()+":"+QString::number(newlink->peerPort())+"]"+"有客户端链接成功了");

    //给新链接的readReady信号关联消息处理的槽
    connect(newlink,&QTcpSocket::readyRead,this,[=]()->void
    {
        //获取客户端发来的信息
        QString mesg = newlink->readAll();
        ui->listWidget->addItem("[客户端说]"+mesg);

        //给客户端回复消息
        QString result = mesg;
        newlink->write(result.toUtf8());
    });

    //给新链接的disconnected信号关联槽
    connect(newlink,&QTcpSocket::disconnected,this,[=]()->void
    {
       //删除链接对象
       newlink->deleteLater();
       ui->listWidget->addItem("["+addr.toString()+"]"+"客户端断开链接");
    });
}

客户端:

cpp 复制代码
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    //设置窗口标题
    this->setWindowTitle("客户端");

    //创建套接字对象
    socket = new QTcpSocket();

    //请求连接
    socket->connectToHost("127.0.0.1",8080);

    //链接的readReady信号关联消息处理的槽
    connect(socket,&QTcpSocket::readyRead,this,[=]()->void
    {
           QString x = "[服务器回复说]"+socket->readAll();
           ui->listWidget->addItem(x);
    });

    bool ret = socket->waitForConnected();
     if (!ret) {
         //errorString相当于perror()
         QMessageBox::critical(this, "连接服务器出错", socket->errorString());
         return;
     }
}

void MainWindow::on_pushButton_clicked()
{
    //获取输入框的数据
    QString x = ui->lineEdit->text();
    //将数据发送给服务器
    socket->write(x.toUtf8());
    //将数据回显到客户端界面
    ui->listWidget->addItem("你发送了"+x);
    //清除输入框的数据
    ui->lineEdit->setText("");
}

QTcpSocket的销毁

对于服务器来说,链接会有很多,所以QTcpSocket不能依靠对象树释放,要在断开连接的时候就及时释放,防止内存,fd不够用。QTcpSever和QUdpSocket则没有这样的问题,因为他们只有一份。

销毁有两种方式,第一种就是直接delete。第二种是Qt提供的方法:deleteLater,调用了这个方法的QTcpSocket会在下一个事件循环中****销毁。

客户端的链接

linux中的原生接口connect是阻塞式的,而Qt中的connectToHost是非阻塞式的,效率会好一点,使用这个函数还要****配合 waitForConnected函数,他能判断链接是否成功**。**


QNetworkAccessManager

QNetworkAccessManager是基于Http协议的通信接口,专用于Http通信。

相关接口,方法,信号


使用QNetworkAccessManager编写一个访问网页的客户端

cpp 复制代码
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    //设置窗口标题
    this->setWindowTitle("客户端");

    //创建QNetworkAccessManager对象
    httper = new QNetworkAccessManager();
}

void MainWindow::on_pushButton_clicked()
{
    //从输入框获取用户输入的url并创建url对象
    QString myurl = ui->lineEdit->text();
    QUrl url(myurl);
    //构建一个http请求
    QNetworkRequest request(url);

    //发送一个get类型的http请求,非阻塞式
    QNetworkReply* response =  httper->get(request);

    //response收到完整的应答后会触发信号finished,这里给它关联一个处理槽函数
    connect(response,&QNetworkReply::finished,this,[=]()->void
    {
        //首先判断获取应答成功没有
        if(response->error() == QNetworkReply::NoError)
        {
            //读取响应报文的body并展示在文本框
            QString str = response->readAll();
            ui->plainTextEdit->setPlainText(str);
        }
        else
        {
            //把出错原因显示在文本框
            ui->plainTextEdit->setPlainText(response->errorString());
        }

        //这里的意思是下个事件循环再删除response对象
        response->deleteLater();
    });

    //清空输入框
    ui->lineEdit->setText("");
}

非阻塞获取应答

同connectToHost一样,QNetworkAccessManager也是非阻塞的,即并不一定在获取一个完整响应报文后才返回一个QNetworkReply对象,而是立即返回。等到QNetworkReply对象真的有了完整响应报文或者发生错误就会发送finished信号通知上层可以取用。

相关推荐
wazmlp0018873699 小时前
第五次python作业
服务器·开发语言·python
云深处@9 小时前
【C++11】部分特性
开发语言·c++
尘缘浮梦9 小时前
websockets简单例子1
开发语言·python
jxy99989 小时前
mac mini 安装java JDK 17
java·开发语言·macos
独望漫天星辰9 小时前
C++ 树结构进阶:从工程化实现到 STL 底层与性能优化
开发语言·c++
HellowAmy9 小时前
我的C++规范 - 鸡蛋工厂
开发语言·c++·代码规范
叫我一声阿雷吧9 小时前
深入理解JavaScript作用域和闭包,解决变量访问问题
开发语言·javascript·ecmascript
froginwe119 小时前
Vue.js 事件处理器
开发语言