Qt——系统相关

目录

事件

事件处理

鼠标事件

键盘事件

定时器

窗口事件

文件

输入输出设备文件

文件操作

QFileInfo

线程

条件变量

信号量

网络

UDP

TCP

HTTP

声音


事件

虽然 Qt 是跨平台的 C++ 开发框架,但它的很多能力都是操作系统给予的,也就是说 Qt 实际上对不同的操作系统调用接口进行了封装,用户只需要学校同一套 Qt 相关操作,拿着同一份代码就可以在不同系统上运行

事件机制在操作系统是存在的,但由于实现起来过于复杂,Qt 帮我们进行了封装,这样做的好处是让我们专注于上层业务代码的开发;用户在界面上进行了很多操作,也就产生了各自事件,通过某种方式来对各种进行处理(如信号使用 connect 进行关联槽函数),实现人机交互

Qt 中的各种时间都是 QEvent 的子类

事件处理

这里处理事件的方法与信号那里有点不一样(虽然二者是类似的),则是通过创建子类继承父类(触发事件的控件),重新相关 Event 函数来实现

案例:添加 QLabel 控件的进入事件,离开事件

cpp 复制代码
//label.h
// 继承谁要写正确
class Label : public QLabel
{
    Q_OBJECT
public:
    explicit Label(QWidget *parent = nullptr);
    // 由于 Qt 没有提示,所以要把重新的函数名写正确
    void enterEvent(QEvent* event);
    void leaveEvent(QEvent *event);
signals:

};

#endif // LABEL_H

//label.cpp
Label::Label(QWidget *parent) : QLabel(parent)
{
}

void Label::enterEvent(QEvent *event)
{
    (void) event;
    qDebug()<<"mouse enter";
}

void Label::leaveEvent(QEvent *event)
{
    (void) event;
    qDebug()<<"mouse leave";
}

此外还要将 ui 添加的控件 QLable 类型转换为子类 Label ,事件才能生效

我们也可以把前面学习 QGeometry 是的案例:运气选择进行以上类似思路的调整

cpp 复制代码
class PushButton:public QPushButton
{
    Q_OBJECT
public:
    PushButton(QWidget* parent = nullptr);
    void enterEvent(QEvent* event);
};

#endif // PUSHBUTTON_H

PushButton::PushButton(QWidget* parent)
    :QPushButton(parent)
{}

void PushButton::enterEvent(QEvent *event)
{
    (void) event;
    // 获取到父窗口,也就是QWidget
    QWidget* widget = this->parentWidget();
    int x = rand()%widget->x();
    int y = rand()%widget->y();
    this->setGeometry(x,y,this->width(),this->height());
}

鼠标事件

  • 鼠标按下
cpp 复制代码
class Label : public QLabel
{
    Q_OBJECT
public:
    Label(QWidget* parent = nullptr);
    void mousePressEvent(QMouseEvent *event);
};

#endif // LABEL_H

void Label::mousePressEvent(QMouseEvent *event)
{
    if(event->button() == Qt::LeftButton)
    {
        qDebug()<<"鼠标左键按下";
    }
    else if(event->button() == Qt::RightButton)
    {
        qDebug()<<"鼠标右键按下";
    }
    qDebug()<<"按下的坐标(label左侧为原点) x:"<<event->x()<<" y:"<<event->y();
    qDebug()<<"按下的坐标(电脑屏幕为原点) x:"<<event->globalX()<<" y:"<<event->globalY();
}
  • 鼠标释放事件
cpp 复制代码
void Label::mouseReleaseEvent(QMouseEvent *event)
{
    if(event->button() == Qt::LeftButton)
    {
        qDebug()<<"鼠标左键释放";
    }
    else if(event->button() == Qt::RightButton)
    {
        qDebug()<<"鼠标右键释放";
    }
}
  • 鼠标双击事件
cpp 复制代码
void Label::mouseDoubleClickEvent(QMouseEvent *event)
{
    if(event->button() == Qt::LeftButton)
    {
        qDebug()<<"鼠标左键双击";
    }
    else if(event->button() == Qt::RightButton)
    {
        qDebug()<<"鼠标右键双击";
    }
}
  • 鼠标移动事件

因为鼠标移动事件会触发若干次,Qt 默认是关闭的,即使你重写该事件函数,需要手动开启

cpp 复制代码
Label::Label(QWidget* parent)
    :QLabel(parent)
{
    this->setMouseTracking(true);
}

void Label::mouseMoveEvent(QMouseEvent *event)
{
    (void) event;
    qDebug()<<"鼠标在当前区域内移动";
}
  • 鼠标滚轮事件

通过滚轮的值来调整标签字体大小

cpp 复制代码
void Label::wheelEvent(QWheelEvent *event)
{
    int old_total = _total;
    _total+=event->delta();
    // 通过滚轮来调整 label 字体大小
    QFont font = this->font();
    int current_size = font.pointSize();
    if(_total > old_total) current_size++;
    else current_size--;
    font.setPointSize(current_size);
    this->setFont(font);
}

键盘事件

在前面信号中,可以使用 QShowcut 来获取指定的键盘按键,用户是否按下了,它的底层实际上是对 keyPressEvent 事件封装的

案例:获取用户输入的键盘数据

cpp 复制代码
void Widget::keyPressEvent(QKeyEvent *event)
{
    if(event->key() == Qt::Key_A)
    {
        qDebug()<<"用户按下了A键";
    }
    if(event->modifiers() == Qt::ControlModifier && event->key() == Qt::Key_A)
    {
        qDebug()<<"用户按下了 ctrl + A键";
    }
}

定时器

前面信号章节中,使用了 QTime 来实现定时器,而它背后则是有 QTimeEvent;在 QObject 中提供了 timeEvent 函数事件来触发,使用该函数还需要搭配 starttTimer 和 killTimer 来创建定时器和关闭定时器

案例:实现倒计时(前面使用信号的方式,这里使用事件来解决)

cpp 复制代码
void Widget::timerEvent(QTimerEvent *event)
{
    if(event->timerId() != _timeid)
    {
        // 释放定时器
        this->killTimer(_timeid);
        return;
    }
    int valute = ui->lcdNumber->intValue();
    if(valute == 0)
    {
        this->killTimer(_timeid);
        return;
    }
    valute--;
    ui->lcdNumber->display(valute);

}

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    // 创建定时器
    _timeid = this->startTimer(1000);

}

窗口事件

窗口移动事件

窗口大小变化事件

案例:定义出窗口移动和窗口大小

cpp 复制代码
void Widget::moveEvent(QMoveEvent *event)
{
    qDebug()<<event->pos();
}

void Widget::resizeEvent(QResizeEvent *event)
{
    qDebug()<<event->size();
}

文件

在C语言中对于文件操作使用 fopen,fwrite,fread等接口进行实现;在C++中使用fstream对象,>> 和 << 实现;在 Linux 中使用 open,write,read等接口实现;以上方式 Qt 其实都能使用,但 Qt 诞生比较早,但是 C++ 还没有制定出标准出来,它就自己也实现出一套文件操作出来,也就是 QFile,以 QFile 为父类实现出不同类型的子类

输入输出设备文件

  • 父类是 QIODevice
  • QIODevice 的父类是 QFIleDevice
  • QFileDevice 的父类是 QFile

文件操作

  • 文件打开:open
  • 读 read
  • 写 write
  • 关闭 close

案例:在 mainwindow 中创建文本对话框,通过设置菜单栏,设置菜单项完成文件读写操作

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

   QMenuBar* bar = ui->menubar;
   QMenu* menu = new QMenu("文件");
   bar->addMenu(menu);
   QAction* action1 = new QAction("打开");
   QAction* action2 = new QAction("保存");
   menu->addAction(action1);
   menu->addAction(action2);
   // 子窗口添加文本编辑器
   edit = new QTextEdit();
   this->setCentralWidget(edit);

   connect(action1,&QAction::triggered,this,&MainWindow::ReadFile);
   connect(action2,&QAction::triggered,this,&MainWindow::WriteFile);
}

void MainWindow::ReadFile()
{
    // 选择文件
    QString path = QFileDialog::getOpenFileName(this);
    // 状态栏显示文件
    QStatusBar* status = this->statusBar();
    status->showMessage("当前文件路径 "+path);
    // 打开文件
    QFile rfile(path);
    if(!rfile.open(QFile::ReadOnly))
    {
        status->showMessage("打开文件 "+path+"失败");
        return;
    }
    // 读文件 QByteArray -> QString(QString 有 operator=(QByteArray*))
    QString data = rfile.readAll();
    // 数据显示到 QTextEdit
    edit->setText(data);
    // 千万要记得关闭文件
    rfile.close();
}

void MainWindow::WriteFile()
{
    // 选择文件
    QString path = QFileDialog::getOpenFileName(this);
    // 状态栏显示文件
    QStatusBar* status = this->statusBar();
    status->showMessage("当前文件路径 "+path);
    // 打开文件
    QFile wfile(path);
    if(!wfile.open(QFile::WriteOnly))
    {
        status->showMessage("打开文件 "+path+"失败");
        return;
    }
    // 从 QTextEdit 获取数据
    QString data = edit->toPlainText();
    // 写文件 QString -> QByteArray 有对应的方法
    wfile.write(data.toUtf8());
    // 千万要记得关闭文件
    wfile.close();
}

QFileInfo

⽂件和⽬录信息类,获取⽂件名、文件大小、文件修改日期等

  • isDir() 检查该⽂件是否是⽬录
  • isExecutable() 检查该⽂件是否是可执⾏⽂件
  • fileName() 获得⽂件名
  • completeBaseName() 获取完整的⽂件名
  • suffix() 获取⽂件后缀名
  • completeSuffix() 获取完整的⽂件后缀
  • size() 获取⽂件大小
  • isFile() 判断是否为⽂件
  • fileTime() 获取⽂件创建时间、修改时间、最近访问时间等

线程

Qt 中的线程,其实就是把 pthread 库进行了封装,用起来更方便;与 C++11 的 std::pthread 本质上来说是类似的,但它使用起来比它还要来得方便些

使用时,创建 QThread 的子类,重写 run 函数方法,也就是来设计线程执行的逻辑,与前面的事件有点类似

Qt 线程相关 API

|--------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| run() | 线程的⼊⼝函数 |
| start() | 通过调⽤ run() 开始执⾏线程。操作系统将根据优先级参数调度线程。如果线程已 经在运⾏,这个函数什么也不做 |
| currentThread() | 返回⼀个指向管理当前执⾏线程的 QThread的指针 |
| isRunning() | 如果线程正在运⾏则返回 true;否则返回 false |
| sleep()/msleep()/ usleep() | 使线程休眠,单位为秒/毫秒/微秒 |
| wait() | 阻塞线程,直到满⾜以下任何⼀个条件: 与此 QThread 对象关联的线程已经完成执⾏(即当它从run()返回时)。如果线程已 经完成,这个函数将返回 true。如果线程尚未启动,它也返回 true。 已经过了⼏毫秒。如果时间是 ULONG_MAX(默认值),那么等待永远不会超时(线程 必须从run()返回)。如果等待超时,此函数将返回 false 这提供了与 POSIX pthread_join() 函数类似的功能。 |
| terminate() | 终⽌线程的执⾏。线程可以⽴即终⽌,也可以不⽴即终⽌,这取决于操作系统的调 度策略。在terminate() 之后使⽤ QThread::wait()来确保 |
| finished() | 当线程结束时会发出该信号,可以通过该信号来实现线程的清理⼯作。 |

案例:使用线程版完成倒计时

cpp 复制代码
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    // Thread t 是 Widget 成员, Thread 只要没退出,Widget 就不会退
    connect(&t,&Thread::notify,this,&Widget::handler);
    t.start();
}

void Widget::handler()
{
    int value = ui->lcdNumber->intValue();
    value--;
    ui->lcdNumber->display(value);
}

多线程在服务器端用的最多,因为服务器同时处理若干个用户的请求,使用单进程处理不过来,可以使用多线程充分利用CPU资源,且让主线程压力不会很大;但在客户端这里多线程就没有用了吗? 不是的,在客户端这里也是有意义的,比如在用户交互页面上如果使用单进程,当遇到下载等任务,客户端从服务器进行IO时,此时单进程就会阻塞挂起,用户就无法进行其它操作,且这个过程可能会出现问题导致本机操作系统提出无法响应的对话框,来提示用户是否关闭当前下载的客户端程序,这样就非常影响用户"体验",所有此时使用多线程,让新线程去完成下载任务后,把结果告诉主线程,主线程则可以接着进行事件循环,让用户进行除下载外的操作

多线程访问共享资源,是会出现线程安全的问题,如:多线程累加同一个 num 值

cpp 复制代码
// thread.cpp
int Thread::num = 0;
void Thread::run()
{
    for(int i=0;i<50000;i++)
    {
        num++;
    }
}

// widget.cpp
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    Thread t1;
    Thread t2;
    t1.start();
    t2.start();
    t1.wait();
    t2.wait();
    // 预期num为100000
    qDebug()<<Thread::num;
}

这种情况就要让线程通过加锁实现串行访问(主要原因是 ++ 操作不是原子性,分为三个操作:num在内存中的变量加载到CPU寄存器中;CPU对寄存器的值进行修改;最后再拷贝回去)

cpp 复制代码
// thread.cpp
int Thread::num = 0;
QMutex Thread::mtx;
void Thread::run()
{
    for(int i=0;i<50000;i++)
    {
        mtx.lock();
        num++;
        mtx.unlock();
    }
}

但如果代码复杂了,可能会忘记解锁或者说在加锁的过程因为某些判断条件不符合就退出了,所以推荐使用锁守卫(使用 RAII 机制(C++ 智能指针)进行封装)

cpp 复制代码
QMutexLocker lock(&mtx);
num++;

条件变量

在多线程编程中,假设除了等待操作系统正在执⾏的线程之外,某个线程还必须等待某些条件满⾜才能执⾏,这时就会出现问题。这种情况下,线程会很⾃然地使⽤锁的机制来阻塞其他线程,因为这只是线程的轮流使⽤,并且该线程等待某些特定条件,⼈们会认为需要等待条件的线程,在释放互斥锁或读写锁之后进⼊了睡眠状态,这样其他线程就可以继续运⾏。当条件满⾜时,等待条件的线程将被另⼀个线程唤醒。

在 Qt 中,专门提供了 QWaitCondition类来解决像上述这样的问题

cpp 复制代码
QMutex mutex;
QWaitCondition condition;
//在等待线程中
mutex.lock();
//检查条件是否满⾜,若不满⾜则等待
while (!conditionFullfilled())//这里使用的是while
{
    condition.wait(&mutex); //等待条件满⾜,等待之前会先释放锁
}
//条件满⾜后继续执⾏
//...
mutex.unlock();
//在改变条件的线程中
mutex.lock();
//改变条件
changeCondition();
condition.wakeAll(); //唤醒等待的线程
mutex.unlock();

信号量

有时在多线程编程中,需要确保多个线程可以相应的访问⼀个数量有限的相同资源。例如,运⾏程序的设备可能是⾮常有限的内存,因此我们更希望需要⼤量内存的线程将这⼀事实考虑在内,并根据可⽤的内存数量进⾏相关操作,多线程编程中类似问题通常⽤信号量来处理。信号量类似于增强的互斥锁,不仅能完成上锁和解锁操作,⽽且可以跟踪可⽤资源的数量。

特点:QSemaphore 是 Qt框架提供的计数信号量类,⽤于控制同时访问共享资源的线程数量。⽤途:限制并发线程数量,⽤于解决⼀些资源有限的问题

cpp 复制代码
QSemaphore semaphore(2); //同时允许两个线程访问共享资源
//在需要访问共享资源的线程中
semaphore.acquire(); //尝试获取信号量,若已满则阻塞
//访问共享资源
//...
semaphore.release(); //释放信号量
//在另⼀个线程中进⾏类似操作

网络

C++ 的最新标准还未提供相关网络库,而在 Qt 这里则提供了网络库,只不过要在 .pro 文件中引入 network 模块进行参与编译,而前面使用的控件都包含在 QtCore 中(默认就添加了);Qt 区别模板化的好处在于:使用 Qt 编写一些简单的程序,编译出来的程序不会太大(比如在一些嵌入式中引用 Qt 区别模块化就比较适合)

我们知道,应用层编写主要还是要靠实际选择传输层 UDP 还是 TCP来编写代码,而这两者编写还不太一样,Qt 也就给我们提供了这二者的相关接口操作

UDP

QUdpSocket 表⽰⼀个 UDP 的 socket ⽂件,它的核心接口

|-----------------------------------------|----|-----------------------------------|----------------------|
| 名称 | 类型 | 说明 | 对标原⽣ API |
| bind(const QHostAddress& ,quint16) | ⽅法 | 绑定指定的端⼝号 | bind |
| receiveDatagram() | ⽅法 | 返回 QNetworkDatagram,读取 ⼀个 UDP 数据报 | recvfrom |
| writeDatagram(const QNetworkDatagram&) | ⽅法 | 发送⼀个 UDP 数据报 | sendto |
| readyRead | 信号 | 在收到数据并准备就绪后触发 | ⽆(类似于 IO 多路复⽤的通 知机制) |

QNetworkDatagram 表⽰⼀个 UDP 数据报,它的核心接口

|-------------------------------------------------------------------|-------|----------------------------------------------------------|----------|
| 名称 | 类型 | 说明 | 对标原⽣ API |
| QNetworkDatagram(const QByteArray&,const QHostAddress&,quint16) | 构造函 数 | 通过 QByteArray ,⽬标 IP 地址,⽬标端⼝号来构造⼀个 UDP 数据报,通常⽤于发送数据时进行使用 | ⽆ |
| data() | ⽅法 | 获取数据报内部持有的数据,返回 QByteArray | ⽆ |
| senderAddress() | ⽅法 | 获取数据报中包含的对端的 IP 地址 | recvfrom |
| senderPort() | ⽅法 | 获取数据报中包含的对端的端⼝号 | recvfrom |

案例:回显服务器

cpp 复制代码
// server
const qint16 g_port = 8888;
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    this->setWindowTitle("服务器");
    // 服务器绑定 ip 和 port
    if(!server.bind(QHostAddress::Any,g_port))
    {
        QString err = "bind errno:";
        err+=server.errorString();
        ui->listWidget->addItem(err);
    }
    else
    {
        // 关联读事件信号
        connect(&server,&QUdpSocket::readyRead,this,&Widget::process);
    }
}

void Widget::process()
{
    // 读事件一定就绪,不会阻塞
    const QNetworkDatagram& RequestData = server.receiveDatagram();
    QString ClientStr = RequestData.data();
    // 把读取到的客户端数据显示到 LineEdit 上
    char buff[1024] = {0};
    QString ClientIP = RequestData.senderAddress().toString();
    qint16 ClientPort = RequestData.senderPort();
    sprintf(buff,"[%s:%d]: %s",ClientIP.toUtf8().constData(),ClientPort,ClientStr.toUtf8().constData());
    ui->listWidget->addItem(buff);
    // 处理业务逻辑,可以进行回调,这里就只是简单返回客户端的数据
    const QString& result = ClientStr;
    // 发送数据之前要先根据响应请求
    QNetworkDatagram ResponseData(result.toUtf8(),RequestData.senderAddress(),RequestData.senderPort());
    server.writeDatagram(ResponseData);
}
cpp 复制代码
//client
const QString ServerIp = "127.0.0.1";
const quint16 ServerProt = 8888;
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    this->setWindowTitle("客户端");
    // OS自动绑定
    connect(&client,&QUdpSocket::readyRead,this,&Widget::process);
}

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

void Widget::process()
{
    // 获取服务器发来的数据
    QNetworkDatagram RequestData = client.receiveDatagram();
    QString ServerData = RequestData.data();
    // 显示到 listWidget 上
    ui->listWidget->addItem("服务器说: " + ServerData);
}

void Widget::on_pushButton_clicked()
{
    // 从 lineEdit 获取数据
    QString data = ui->lineEdit->text();
    ui->lineEdit->setText("");
    // 发送给服务器
    QNetworkDatagram ResponseData(data.toUtf8(),QHostAddress(ServerIp),ServerProt);
    client.writeDatagram(ResponseData);
}

可以将以上UDP服务器的代码部署到云服务器上吗?

可以,但很大概率会失败,因为以上代码需要云服务器去下载 GUI 图形化界面的软件,但默认不是自带的,前面没下载过就需要进行相关的配置后才能成功启动(但服务器一般都是使用图形化界面的形式来编写的)

使用以上的UDP服务器可以与LInux上写的UDP客户端进行通信吗?

这是可以的,因为它们底层都是对 socket 原生 API 的封装,本质都是一样的

TCP

QTcpServer ⽤于监听端⼝,和获取客户端连接

名称 类型 说明 对标原⽣API
listen(QHostAddress&,quint16) ⽅法 绑定指定的地址和端⼝号,并开始监听 bind 和 listen
nextPendingConnection() ⽅法 从系统中获取到⼀个已经建⽴好的 tcp 连接 返回⼀个 QTcpSocket,表⽰这个 客⼾端的连接,通过这个 socket 对象完成和客⼾端之间的通信 accept
newConnection 信号 有新的客⼾端建⽴连接好之后触发 类似于 IO 多路复⽤中的通知机制

QTcpSocket 客户端端和服务器之间的数据交互

名称 类型 说明 对标原⽣API
readAll() ⽅法 读取当前接收缓冲区中的所有数据, 返回 QByteArray 对象 read
write(QByteArray&) ⽅法 把数据写⼊ socket 中 write
deleteLater ⽅法 暂时把 socket 对象标记为⽆效,Qt 会在下个事件循环中析构释放该对 象 类似于 "半⾃动化的垃圾回收"
readyRead 信号 有数据到达并准备就绪时触发 类似于 IO 多路复⽤中的通知机制
disconnected 信号 连接断开时触发 类似于 IO 多路复⽤中的通知机制

案例:回显服务器

Tcp 服务器代码

cpp 复制代码
// Widget.cpp
const static QString ServerIp = "127.0.0.1";
const static quint16 ServerPort = 8888;
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    socket.connectToHost(ServerIp,ServerPort);
    // 设置数据到来的槽函数
    connect(&socket,&QTcpSocket::readyRead,this,[&](){
       // 这里同样是不严谨的
       QString RequestData = socket.readAll();
       // 显示到 ListEdit 上
       QString log = "服务器说:";
       log+=RequestData;
       ui->listWidget->addItem(log);
    });
}

void Widget::on_pushButton_clicked()
{
    // 从 listEdit 获取数据
    QString ResponseData = ui->lineEdit->text();
    ui->lineEdit->setText("");
    // 进行发送
    socket.write(ResponseData.toUtf8());
}

Tcp客户端代码

cpp 复制代码
//Widget.cpp
const quint16 g_port = 8888;
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    // 先设置新连接的槽函数
    connect(&TcpServer,&QTcpServer::newConnection,this,&Widget::NewConnection);
    // 再进行绑定和监听
    if(!TcpServer.listen(QHostAddress::Any,g_port))
    {
        QMessageBox::critical(this,"服务器 listen 失败",TcpServer.errorString());
        exit(1);
    }
}

void Widget::NewConnection()
{
    QTcpSocket* socket = TcpServer.nextPendingConnection();
    char log[1024] = {0};
    QString ClientIp = socket->peerAddress().toString();
    quint16 ClientPort = socket->peerPort();
    sprintf(log,"[%s:%d] 客户端连接",ClientIp.toUtf8().data(),ClientPort);
    ui->listWidget->addItem(log);
    // 设置数据到来的槽函数
    // 使用值=捕捉,如果使用引用&捕捉 socket 失效
    connect(socket,&QTcpSocket::readyRead,this,[=](){
        const QString& ResquestData = socket->readAll();
        // 这里是业务的处理,要处理Tcp的粘包问题,也就是使用应用层协议进行处理,但这里只是进行回显
        const QString& ResponseData = ResquestData;
        // 添加到 ListEdit 中
        char log[1024] = {0};
        QString ClientIp = socket->peerAddress().toString();
        quint16 ClientPort = socket->peerPort();
        sprintf(log,"[%s:%d]:req %s ; resp %s",ClientIp.toUtf8().data(),ClientPort,ResquestData.toUtf8().data(),ResponseData.toUtf8().data());
        ui->listWidget->addItem(log);
        socket->write(ResponseData.toUtf8());
    });
    // 设置断开连接的槽函数
    connect(socket,&QTcpSocket::disconnected,this,[=](){
        char log[1024] = {0};
        QString ClientIp = socket->peerAddress().toString();
        quint16 ClientPort = socket->peerPort();
        sprintf(log,"[%s:%d] 客户端断连",ClientIp.toUtf8().data(),ClientPort);
        ui->listWidget->addItem(log);
        // 标记为无效,不推荐使用delete
        socket->deleteLater();
    });
}

HTTP

QNetworkAccessManager 提供了 HTTP 的核心操作

|--------------------------------------------------|---------------------------------------|
| get(const QNetworkRequest&) | 发起⼀个 HTTP GET 请求,返回 QNetworkReply 对象 |
| post(const QNetworkRequest&,const QByteArray&) | 发起⼀个 HTTP POST 请求,返回 QNetworkReply 对象 |

QNetworkRequest 表⽰⼀个 HTTP 请求(不含 body)

|------------------------------------------------------------------|---------------------|
| QNetworkRequest(const QUrl&) | 通过 URL 构造⼀个 HTTP 请求 |
| setHeader(QNetworkRequest::KnownHeaders, 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 的值 |

案例:访问 http://baidu.com 后获取百度页面的 HTML 数据

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

    manager = new QNetworkAccessManager(this);
}

void Widget::on_pushButton_clicked()
{
    QString url = ui->lineEdit->text();
    ui->lineEdit->setText("");
    QNetworkRequest req(url);
    // QNetworkReply 不阻塞
    QNetworkReply* resp = manager->get(req);
    connect(resp,&QNetworkReply::finished,this,[=](){
        if(resp->error() == resp->NoError)
        {
            // 正常响应数据
            QString body = resp->readAll();
            ui->plainTextEdit->setPlainText(body);
        }
        else
        {
            QString err = "响应失败:";
            err += resp->errorString();
            ui->plainTextEdit->setPlainText(err);
        }
        resp->deleteLater();
    });
}

声音

Qt 提供了 QSound 进行声音播放的各种效果

案例:点击按钮进行音频视频(.wav格式)的播放

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

    sound = new QSound(":/sample-3s.wav",this);

}

void Widget::on_pushButton_clicked()
{
    sound->play();
}

以上便是全部内容,有问题欢迎在评论区指正,感谢观看!

相关推荐
用户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
桥田智能16 天前
桥田智能 QT-650S:面向白车身焊装的 800kg 重载快换解决方案
开发语言·qt·系统架构
森G16 天前
75、服务器源码解析---------云视频服务项目
linux·服务器·网络·c++·qt