[Qt]多线程和套接字通信


1. 多线程的使用

在进行桌面应用程序开发的时候, 假设应用程序在某些情况下需要处理比较复杂的逻辑, 如果只有一个线程去处理,就会导致窗口卡顿,无法处理用户的相关操作。这种情况下就需要使用多线程,其中一个线程处理窗口事件,其他线程进行逻辑运算,多个线程各司其职,不仅可以提高用户体验还可以提升程序的执行效率。

在qt中使用了多线程,有些事项是需要额外注意的:

  1. 默认的线程在Qt中称之为窗口线程,也叫主线程,负责窗口事件处理或者窗口控件数据的更新
  2. 子线程负责后台的业务逻辑处理,子线程中不能对窗口对象做任何操作,这些事情需要交给窗口线程处理
  3. 主线程和子线程之间如果要进行数据的传递,需要使用Qt中的信号槽机制

1.1 线程类 QThread

Qt中提供了一个线程类,通过这个类就可以创建子线程了,Qt中一共提供了两种创建子线程的方式,后边会依次介绍其使用方式。看一下这个类中提供的一些常用API函数:

1.1.1 常用共用成员函数

// QThread 类常用 API
// 构造函数
QThread::QThread(QObject *parent = Q_NULLPTR);
// 判断线程中的任务是不是处理完毕了
bool QThread::isFinished() const;
// 判断子线程是不是在执行任务
bool QThread::isRunning() const;

// Qt中的线程可以设置优先级
// 得到当前线程的优先级
Priority QThread::priority() const;
void QThread::setPriority(Priority priority);
优先级:
    QThread::IdlePriority         --> 最低的优先级
    QThread::LowestPriority
    QThread::LowPriority
    QThread::NormalPriority
    QThread::HighPriority
    QThread::HighestPriority
    QThread::TimeCriticalPriority --> 最高的优先级
    QThread::InheritPriority      --> 子线程和其父线程的优先级相同, 默认是这个
// 退出线程, 停止底层的事件循环
// 退出线程的工作函数
void QThread::exit(int returnCode = 0);
// 调用线程退出函数之后, 线程不会马上退出因为当前任务有可能还没有完成, 调回用这个函数是
// 等待任务完成, 然后退出线程, 一般情况下会在 exit() 后边调用这个函数
bool QThread::wait(unsigned long time = ULONG_MAX);

1.1.2 信号槽

// 和调用 exit() 效果是一样的
// 代用这个函数之后, 再调用 wait() 函数
[slot] void QThread::quit();
// 启动子线程
[slot] void QThread::start(Priority priority = InheritPriority);
// 线程退出, 可能是会马上终止线程, 一般情况下不使用这个函数
[slot] void QThread::terminate();

// 线程中执行的任务完成了, 发出该信号
// 任务函数中的处理逻辑执行完毕了
[signal] void QThread::finished();
// 开始工作之前发出这个信号, 一般不使用
[signal] void QThread::started();

1.1.3 静态函数

// 返回一个指向管理当前执行线程的QThread的指针
[static] QThread *QThread::currentThread();
// 返回可以在系统上运行的理想线程数 == 和当前电脑的 CPU 核心数相同
[static] int QThread::idealThreadCount();
// 线程休眠函数
[static] void QThread::msleep(unsigned long msecs);	// 单位: 毫秒
[static] void QThread::sleep(unsigned long secs);	// 单位: 秒
[static] void QThread::usleep(unsigned long usecs);	// 单位: 微秒

1.1.4 任务处理函数

// 子线程要处理什么任务, 需要写到 run() 中
[virtual protected] void QThread::run();

这个run()是一个虚函数,如果想让创建的子线程执行某个任务,需要写一个子类让其继承QThread,并且在子类中重写父类的run()方法,函数体就是对应的任务处理流程。另外,这个函数是一个受保护的成员函数,不能够在类的外部调用,如果想要让线程执行这个函数中的业务流程,需要通过当前线程对象调用槽函数start()启动子线程,当子线程被启动,这个run()函数也就在线程内部被调用了。


1.2 使用方式1

1.2.1 操作步骤

Qt中提供的多线程的第一种使用方式的特点是: 简单。操作步骤如下:

  1. 需要创建一个线程类的子类,让其继承QT中的线程类 QThread,比如:
class MyThread:public QThread
{
    ......
}
  1. 重写父类的 run() 方法,在该函数内部编写子线程要处理的具体的业务流程
class MyThread:public QThread
{
    ......
 protected:
    void run()
    {
        ........
    }
}
  1. 在主线程中创建子线程对象,new 一个就可以了
MyThread * subThread = new MyThread;
  1. 启动子线程, 调用 start() 方法
subThread->start();

不能在类的外部调用run() 方法启动子线程,在外部调用start()相当于让run()开始运行

当子线程别创建出来之后,父子线程之间的通信可以通过信号槽的方式,注意:

  • 在Qt中在子线程中不要操作程序中的窗口类型对象, 不允许, 如果操作了程序就挂了
  • 只有主线程才能操作程序中的窗口对象, 默认的线程就是主线程, 自己创建的就是子线程

1.2.2 示例代码

举一个简单的数数的例子,假如只有一个线程,让其一直数数,会发现数字并不会在窗口中时时更新,并且这时候如果用户使用鼠标拖动窗口,就会出现无响应的情况,使用多线程就不会出现这样的现象了。

点击按钮开始在子线程中数数,让后通过信号槽机制将数据传递给UI线程,通过UI线程将数据更新到窗口中。

在这里插入图片描述

mythread.h

#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QThread>

class MyThread : public QThread
{
    Q_OBJECT
public:
    explicit MyThread(QObject *parent = nullptr);

protected:
    void run();

signals:
    // 自定义信号, 传递数据
    void curNumber(int num);

public slots:
};

#endif // MYTHREAD_H

mythread.cpp

#include "mythread.h"
#include <QDebug>

MyThread::MyThread(QObject *parent) : QThread(parent)
{

}

void MyThread::run()
{
    qDebug() << "当前线程对象的地址: " << QThread::currentThread();

    int num = 0;
    while(1)
    {
        emit curNumber(num++);
        if(num == 10000000)
        {
            break;
        }
        QThread::usleep(1);
    }
    qDebug() << "run() 执行完毕, 子线程退出...";
}

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "mythread.h"
#include <QDebug>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    qDebug() << "主线程对象地址:  " << QThread::currentThread();
    // 创建子线程
    MyThread* subThread = new MyThread;

    connect(subThread, &MyThread::curNumber, this, [=](int num)
    {
        ui->label->setNum(num);
    });

    connect(ui->startBtn, &QPushButton::clicked, this, [=]()
    {
        // 启动子线程
        subThread->start();
    });
}

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

这种在程序中添加子线程的方式是非常简单的,但是也有弊端
假设要在一个子线程中处理多个任务,所有的处理逻辑都需要写到run()函数中,这样该函数中的处理逻辑就会变得非常混乱,不太容易维护。


1.3 使用方式2

1.3.1 操作步骤

Qt提供的第二种线程的创建方式弥补了第一种方式的缺点,用起来更加灵活,但是这种方式写起来会相对复杂一些,其具体操作步骤如下:

  1. 创建一个新的类,让这个类从QObject派生
class MyWork:public QObject
{
    .......
}
  1. 在这个类中添加一个公共的成员函数,函数体就是我们要子线程中执行的业务逻辑
class MyWork:public QObject
{
public:
    .......
    // 函数名自己指定, 叫什么都可以, 参数可以根据实际需求添加
    void working();
}
  1. 在主线程中创建一个QThread对象, 这就是子线程的对象
QThread* sub = new QThread;
  1. 在主线程中创建工作的类对象(千万不要指定给创建的对象指定父对象)
MyWork* work = new MyWork(this);    // error
MyWork* work = new MyWork;          // ok
  1. 将MyWork对象移动到创建的子线程对象中, 需要调用QObject类提供的moveToThread()方法
// void QObject::moveToThread(QThread *targetThread);
// 如果给work指定了父对象, 这个函数调用就失败了
// 提示: QObject::moveToThread: Cannot move objects with a parent
work->moveToThread(sub);	// 移动到子线程中工作
  1. 启动子线程,调用 start(), 这时候线程启动了, 但是移动到线程中的对象并没有工作

  2. 调用MyWork类对象的工作函数,让这个函数开始执行,这时候是在移动到的那个子线程中运行的


1.3.2 示例代码

假设函数处理上面在程序中数数的这个需求,具体的处理代码如下:

mywork.h

#ifndef MYWORK_H
#define MYWORK_H

#include <QObject>

class MyWork : public QObject
{
    Q_OBJECT
public:
    explicit MyWork(QObject *parent = nullptr);

    // 工作函数
    void working();

signals:
    void curNumber(int num);

public slots:
};

#endif // MYWORK_H

mywork.cpp

#include "mywork.h"
#include <QDebug>
#include <QThread>

MyWork::MyWork(QObject *parent) : QObject(parent)
{

}

void MyWork::working()
{
    qDebug() << "当前线程对象的地址: " << QThread::currentThread();

    int num = 0;
    while(1)
    {
        emit curNumber(num++);
        if(num == 10000000)
        {
            break;
        }
        QThread::usleep(1);
    }
    qDebug() << "run() 执行完毕, 子线程退出...";
}

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QThread>
#include "mywork.h"
#include <QDebug>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    qDebug() << "主线程对象的地址: " << QThread::currentThread();

    // 创建线程对象
    QThread* sub = new QThread;
    // 创建工作的类对象
    // 千万不要指定给创建的对象指定父对象
    // 如果指定了: QObject::moveToThread: Cannot move objects with a parent
    MyWork* work = new MyWork;
    // 将工作的类对象移动到创建的子线程对象中
    work->moveToThread(sub);
    // 启动线程
    sub->start();
    // 让工作的对象开始工作, 点击开始按钮, 开始工作
    connect(ui->startBtn, &QPushButton::clicked, work, &MyWork::working);
    // 显示数据
    connect(work, &MyWork::curNumber, this, [=](int num)
    {
        ui->label->setNum(num);
    });
}

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

使用这种多线程方式,假设有多个不相关的业务流程需要被处理,那么就可以创建多个类似于MyWork的类,将业务流程放多类的公共成员函数中,然后将这个业务类的实例对象移动到对应的子线程中moveToThread()就可以了,这样可让编写的程序更灵活,可读性强,易于维护。


2. 线程池的使用

2.1 QRunnable

在Qt中使用线程池需要先创建任务,添加到线程池中的每一个任务都需要是一个QRunnable类型,因此在程序中需要创建子类继承QRunnable这个类,然后重写 run() 方法,在这个函数中编写要在线程池中执行的任务,并将这个子类对象传递给线程池,这样任务就可以被线程池中的某个工作的线程处理掉了。

QRunnable类 常用函数不多,主要是设置任务对象传给线程池后,是否需要自动析构。

// 在子类中必须要重写的函数, 里边是任务的处理流程
[pure virtual] void QRunnable::run();

// 参数设置为 true: 这个任务对象在线程池中的线程中处理完毕, 这个任务对象就会自动销毁
// 参数设置为 false: 这个任务对象在线程池中的线程中处理完毕, 对象需要程序猿手动销毁
void QRunnable::setAutoDelete(bool autoDelete);
// 获取当前任务对象的析构方式,返回true->自动析构, 返回false->手动析构
bool QRunnable::autoDelete() const;

创建一个要添加到线程池中的任务类,处理方式如下:

class MyWork : public QObject, public QRunnable
{
    Q_OBJECT
public:
    explicit MyWork(QObject *parent = nullptr)
    {
        // 任务执行完毕,该对象自动销毁
        setAutoDelete(true);
    }
    ~MyWork();

    void run() override{}
}

在上面的示例中MyWork类是一个多重继承,如果需要在这个任务中使用Qt的信号槽机制进行数据的传递就必须继承QObject这个类,如果不使用信号槽传递数据就可以不继承了,只继承QRunnable即可。

class MyWork :public QRunnable
{
    Q_OBJECT
public:
    explicit MyWork()
    {
        // 任务执行完毕,该对象自动销毁
        setAutoDelete(true);
    }
    ~MyWork();

    void run() override{}
}

2.2 QThreadPool

Qt中的 QThreadPool 类管理了一组 QThreads, 里边还维护了一个任务队列。QThreadPool 管理和回收各个 QThread 对象,以帮助减少使用线程的程序中的线程创建成本。每个Qt应用程序都有一个全局 QThreadPool 对象,可以通过调用 globalInstance() 来访问它。也可以单独创建一个 QThreadPool 对象使用。

线程池常用的API函数如下:

// 获取和设置线程中的最大线程个数
int maxThreadCount() const;
void setMaxThreadCount(int maxThreadCount);

// 给线程池添加任务, 任务是一个 QRunnable 类型的对象
// 如果线程池中没有空闲的线程了, 任务会放到任务队列中, 等待线程处理
void QThreadPool::start(QRunnable * runnable, int priority = 0);
// 如果线程池中没有空闲的线程了, 直接返回值, 任务添加失败, 任务不会添加到任务队列中
bool QThreadPool::tryStart(QRunnable * runnable);

// 线程池中被激活的线程的个数(正在工作的线程个数)
int QThreadPool::activeThreadCount() const;

// 尝试性的将某一个任务从线程池的任务队列中删除, 如果任务已经开始执行就无法删除了
bool QThreadPool::tryTake(QRunnable *runnable);
// 将线程池中的任务队列里边没有开始处理的所有任务删除, 如果已经开始处理了就无法通过该函数删除了
void QThreadPool::clear();

// 在每个Qt应用程序中都有一个全局的线程池对象, 通过这个函数直接访问这个对象
static QThreadPool * QThreadPool::globalInstance();

一般情况下,我们不需要在Qt程序中创建线程池对象,直接使用Qt为每个应用程序提供的线程池全局对象即可。得到线程池对象之后,调用start()方法就可以将一个任务添加到线程池中,这个任务就可以被线程池内部的线程池处理掉了,使用线程池比自己创建线程的这种多种多线程方式更加简单和易于维护。

具体的使用方式如下:

mywork.h

class MyWork :public QRunnable
{
    Q_OBJECT
public:
    explicit MyWork();
    ~MyWork();

    void run() override;
}

mywork.cpp

MyWork::MyWork() : QRunnable()
{
    // 任务执行完毕,该对象自动销毁
    setAutoDelete(true);
}
void MyWork::run()
{
    // 业务处理代码
    ......
}

mainwindow.cpp

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    // 线程池初始化,设置最大线程池数
    QThreadPool::globalInstance()->setMaxThreadCount(4);
    // 添加任务
    MyWork* task = new MyWork;
    QThreadPool::globalInstance()->start(task);    
}

3. 套接字通信

在标准C++没有提供专门用于套接字通信的类,所以只能使用操作系统提供的基于C的API函数,基于这些C的API函数我们也可以封装自己的C++类 。
但是Qt不一样,它是C++的一个框架并且里边提供了用于套接字通信的类(TCP、UDP)这样就使得我们的操作变得更加简单了(当然,在Qt中使用标准C的API进行套接字通信也是完全没有问题的)。

使用Qt提供的类进行基于TCP的套接字通信需要用到两个类:

  • QTcpServer:服务器类,用于监听客户端连接以及和客户端建立连接。
  • QTcpSocket:通信的套接字类,客户端、服务器端都需要使用。

这两个套接字通信类都属于网络模块network。

3.1 QTcpServer

QTcpServer类用于监听客户端连接以及和客户端建立连接,在使用之前先介绍一下这个类提供的一些常用API函数:

3.1.1 公共成员函数

构造函数

QTcpServer::QTcpServer(QObject *parent = Q_NULLPTR);

给监听的套接字设置监听

bool QTcpServer::listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0);
// 判断当前对象是否在监听, 是返回true,没有监听返回false
bool QTcpServer::isListening() const;
// 如果当前对象正在监听返回监听的服务器地址信息, 否则返回 QHostAddress::Null
QHostAddress QTcpServer::serverAddress() const;
// 如果服务器正在侦听连接,则返回服务器的端口; 否则返回0
quint16 QTcpServer::serverPort() const
  • 参数:
    • address:通过类QHostAddress可以封装IPv4、IPv6格式的IP地址,QHostAddress::Any表示自动绑定
    • port:如果指定为0表示随机绑定一个可用端口。
  • 返回值:绑定成功返回true,失败返回false

得到和客户端建立连接之后用于通信的QTcpSocket套接字对象,它是QTcpServer的一个子对象,当QTcpServer对象析构的时候会自动析构这个子对象,当然也可自己手动析构,建议用完之后自己手动析构这个通信的QTcpSocket对象。

QTcpSocket *QTcpServer::nextPendingConnection();

阻塞等待客户端发起的连接请求,不推荐在单线程程序中使用,建议使用非阻塞方式处理新连接,即使用信号 newConnection() 。

bool QTcpServer::waitForNewConnection(int msec = 0, bool *timedOut = Q_NULLPTR);
  • 参数:
    • msec:指定阻塞的最大时长,单位为毫秒(ms)
    • timeout:传出参数,如果操作超时timeout为true,没有超时timeout为false

3.1.2 信号

当接受新连接导致错误时,将发射如下信号。socketError参数描述了发生的错误相关的信息。

[signal] void QTcpServer::acceptError(QAbstractSocket::SocketError socketError);

每次有新连接可用时都会发出 newConnection() 信号。

[signal] void QTcpServer::newConnection();

3.2 QTcpSocket

QTcpSocket是一个套接字通信类,不管是客户端还是服务器端都需要使用。在Qt中发送和接收数据也属于IO操作(网络IO),先看这个类的继承关系:

在这里插入图片描述

3.2.1 公共成员函数

构造函数

QTcpSocket::QTcpSocket(QObject *parent = Q_NULLPTR);

连接服务器,需要指定服务器端绑定的IP和端口信息

[virtual] void QAbstractSocket::connectToHost(const QString &hostName, quint16 port, OpenMode openMode = ReadWrite, NetworkLayerProtocol protocol = AnyIPProtocol);

[virtual] void QAbstractSocket::connectToHost(const QHostAddress &address, quint16 port, OpenMode openMode = ReadWrite);

在Qt中不管调用读操作函数接收数据,还是调用写函数发送数据,操作的对象都是本地的由Qt框架维护的一块内存。因此,调用了发送函数数据不一定会马上被发送到网络中,调用了接收函数也不是直接从网络中接收数据,关于底层的相关操作是不需要使用者来维护的。

接收数据

// 指定可接收的最大字节数 maxSize 的数据到指针 data 指向的内存中
qint64 QIODevice::read(char *data, qint64 maxSize);
// 指定可接收的最大字节数 maxSize,返回接收的字符串
QByteArray QIODevice::read(qint64 maxSize);
// 将当前可用操作数据全部读出,通过返回值返回读出的字符串
QByteArray QIODevice::readAll();

发送数据

// 发送指针 data 指向的内存中的 maxSize 个字节的数据
qint64 QIODevice::write(const char *data, qint64 maxSize);
// 发送指针 data 指向的内存中的数据,字符串以 \0 作为结束标记
qint64 QIODevice::write(const char *data);
// 发送参数指定的字符串
qint64 QIODevice::write(const QByteArray &byteArray);

3.2.2 信号

在使用QTcpSocket进行套接字通信的过程中,如果该类对象发射出readyRead()信号,说明对端发送的数据达到了,之后就可以调用read 函数接收数据了。

[signal] void QIODevice::readyRead();

调用connectToHost()函数并成功建立连接之后发出connected()信号。

[signal] void QAbstractSocket::connected();

在套接字断开连接时发出disconnected()信号。

[signal] void QAbstractSocket::disconnected();

3.3 通信流程

使用Qt提供的类进行套接字通信比使用标准C API进行网络通信要简单(因为在内部进行了封装) Qt中的套接字通信流程如下:

3.3.1 服务器端

3.3.1.1 通信流程
  1. 创建套接字服务器QTcpServer对象
  2. 通过QTcpServer对象设置监听,即:QTcpServer::listen()
  3. 基于QTcpServer::newConnection()信号检测是否有新的客户端连接
  4. 如果有新的客户端连接调用QTcpSocket *QTcpServer::nextPendingConnection()得到通信的套接字对象
  5. 使用通信的套接字对象QTcpSocket和客户端进行通信

3.3.1.2 代码片段

服务器端的窗口界面如下图所示:

在这里插入图片描述

头文件

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QTcpServer>
#include <QTcpSocket>
#include <QLabel>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:

    void on_startBtn_clicked();

    void on_sendBtn_clicked();

private:
    Ui::MainWindow *ui;
    QTcpServer* m_server;
    QTcpSocket* m_tcp;
    QLabel* m_status;
};
#endif // MAINWINDOW_H

源文件

#include "mainwindow.h"
#include "ui_mainwindow.h"


MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    setWindowTitle("Tcp - Server");

    //创建QServer对象
    m_server = new QTcpServer(this);

    //检测是否有新的客户端连接
    connect(m_server,&QTcpServer::newConnection,this,[=](){
        m_tcp = m_server->nextPendingConnection();
        //连接成功后更新历史记录和底部状态栏
        ui->record->append("connect sucessfully");
        m_status->setPixmap(QPixmap(":/bingo").scaled(20,20));


        //检测客户端是否发送数据
        connect(m_tcp,&QTcpSocket::readyRead,this,[=](){
            QString recvmsg = m_tcp->readAll();
            ui->record->append("client say : " + recvmsg);
        });

        connect(m_tcp,&QTcpSocket::disconnected,this,[=](){
            ui->record->append("disconnect...");
            m_tcp->close();
            m_tcp->deleteLater();
            m_status->setPixmap(QPixmap(":/wrong").scaled(20,20));
        });
    });
    m_status = new QLabel;
    m_status->setPixmap(QPixmap(":/wrong").scaled(20,20));
    ui->statusbar->addWidget(new QLabel("connect status : "));
    ui->statusbar->addWidget(m_status);
}

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


void MainWindow::on_startBtn_clicked()
{
    unsigned short port = ui->port->text().toInt();

    //设置服务器监听
    m_server->listen(QHostAddress::Any,port);
    ui->startBtn->setEnabled(false);
}


void MainWindow::on_sendBtn_clicked()
{
    QString sendmsg = ui->msg->toPlainText();
    m_tcp->write(sendmsg.toUtf8());
    ui->record->append("server say : " +sendmsg);
    ui->msg->clear();
}



3.3.2 客户端

3.3.2.1 通信流程
  1. 创建通信的套接字类QTcpSocket对象
  2. 使用服务器端绑定的IP和端口连接服务器QAbstractSocket::connectToHost()
  3. 使用QTcpSocket对象和服务器进行通信

3.3.2.2 代码片段

客户端的窗口界面如下图所示:

在这里插入图片描述

头文件

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QTcpSocket>
#include <QLabel>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    void on_conBtn_clicked();

    void on_disBtn_clicked();

    void on_sendBtn_clicked();

private:
    Ui::MainWindow *ui;
    QTcpSocket* m_tcp;
    QLabel* m_status;
};
#endif // MAINWINDOW_H

源文件

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    setWindowTitle("Tcp - client");
    //创建套接字对象
    m_tcp = new QTcpSocket(this);


    //接受服务器的消息
    connect(m_tcp,&QTcpSocket::readyRead,this,[=](){
        QString recvmsg = m_tcp->readAll();
        ui->record->append("server say : " + recvmsg);
    });

    //是否和服务器连接成功
    connect(m_tcp,&QTcpSocket::connected,this,[=](){
        ui->record->append("connect sucessfully");
        m_status->setPixmap(QPixmap(":/bingo").scaled(20,20));
    });

    //检测是否断开连接
    connect(m_tcp,&QTcpSocket::disconnected,this,[=](){
        ui->record->append("disconnect......");
        ui->disBtn->setEnabled(false);
        ui->conBtn->setEnabled(true);
        m_status->setPixmap(QPixmap(":/wrong").scaled(20,20));
    });

    m_status = new QLabel;
    m_status->setPixmap(QPixmap(":/wrong").scaled(20,20));
    ui->statusbar->addWidget(new QLabel("connect status : "));
    ui->statusbar->addWidget(m_status);
}

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


void MainWindow::on_conBtn_clicked()
{
    QString ip = ui->Ip->text();
    unsigned short port_tmp = ui->port->text().toInt();

    //连接服务器
    m_tcp->connectToHost(QHostAddress(ip),port_tmp);
    ui->conBtn->setEnabled(false);
    ui->disBtn->setEnabled(true);
}


void MainWindow::on_disBtn_clicked()
{
    m_tcp->close();
    ui->disBtn->setEnabled(false);
    ui->conBtn->setEnabled(true);
}


void MainWindow::on_sendBtn_clicked()
{
    QString sendmsg = ui->msg->toPlainText();
    m_tcp->write(sendmsg.toUtf8());
    ui->record->append("client say : " + sendmsg);
    ui->msg->clear();
}



  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Qt技术是一套强大的跨平台应用程序开发框架,专注于使用C++语言来构建高性能、高可维护性的软件解决方案。自1991年首次推出以来,Qt因其卓越的跨平台能力、丰富的功能集、高效的开发工具和良好的社区支持而广受开发者欢迎。以下是对Qt技术的详细介绍: 核心特性 跨平台性: Qt设计的核心理念是“一次编写,到处运行”。它支持多种操作系统,包括但不限于Windows、macOS、Linux、Android和iOS。通过Qt开发者可以使用同一套源代码,在不同平台上编译并生成原生外观与体验的应用程序,极大地提高了开发效率和产品一致性。 图形用户界面(GUI)开发: 提供了一个完整的GUI工具箱,包含各种控件(如按钮、文本框、滑块、列表视图等)、布局管理器、样式表支持、动画效果等,帮助开发者快速构建美观、功能丰富的桌面和移动应用界面。Qt Designer是一个可视化界面设计工具,允许通过拖放操作构建UI,所见即所得。 非GUI应用开发: 除了GUI程序,Qt同样适用于开发命令行工具、后台服务、嵌入式系统等非图形化应用。其底层API涵盖了文件处理、线程、网络通信、数据库访问等广泛的功能。 面向对象设计: Qt采用面向对象的设计原则,提供了高度模块化的类库,使得代码组织清晰、易于扩展和重用。它遵循MVC(模型-视图-控制器)模式,支持数据驱动的界面设计。 元对象系统与信号槽机制: Qt Meta-Object System(元对象系统)是Qt框架的一个重要特性,它引入了元对象编译器(moc),用于在编译时生成额外的代码以支持对象间通信、反射、动态属性绑定等高级功能。 Signal & Slot机制是Qt中实现对象间事件驱动通信的核心方式。信号代表对象状态变化或事件发生,槽则是响应这些信号的可调用实体。这种松耦合的通信方式简化了异步编程和事件处理。 QML与Qt Quick: QML是一种声明性语言,结合JavaScript,用于快速创建流畅、动态的用户界面。它与Qt Quick框架紧密集成,特别适合开发现代化、触屏友好的应用。 Qt Quick Controls和Qt Quick Dialogs提供了一系列预定义的QML组件,用于构建具有传统桌面风格或现代移动风格的界面元素。同时,Qt Quick也支持自定义控件开发。 多媒体支持: Qt Multimedia模块提供对音频、视频播放、录音、摄像头访问等功能的支持,使得开发多媒体应用程序变得简便。 网络功能: Qt Network模块封装了网络通信相关的API,包括HTTP、FTP、TCP/UDP套接字、SSL加密、DNS查询等,便于开发网络应用和服务。 国际化与本地化: Qt Linguist工具支持应用程序的多语言翻译和本地化工作,确保软件能适应全球市场。 开发工具与生态系统: Qt Creator是一款集成开发环境(IDE),集成了项目管理、代码编辑、调试、版本控制、QML预览等多种功能,为Qt开发提供了无缝的工作流程。 Qt拥有活跃的开发者社区、丰富的文档资源、示例代码库以及商业支持选项,为开发者的学习、问题解决和项目实施提供了坚实后盾。
Qt技术是一套强大的跨平台应用程序开发框架,专注于使用C++语言来构建高性能、高可维护性的软件解决方案。自1991年首次推出以来,Qt因其卓越的跨平台能力、丰富的功能集、高效的开发工具和良好的社区支持而广受开发者欢迎。以下是对Qt技术的详细介绍: 核心特性 跨平台性: Qt设计的核心理念是“一次编写,到处运行”。它支持多种操作系统,包括但不限于Windows、macOS、Linux、Android和iOS。通过Qt开发者可以使用同一套源代码,在不同平台上编译并生成原生外观与体验的应用程序,极大地提高了开发效率和产品一致性。 图形用户界面(GUI)开发: 提供了一个完整的GUI工具箱,包含各种控件(如按钮、文本框、滑块、列表视图等)、布局管理器、样式表支持、动画效果等,帮助开发者快速构建美观、功能丰富的桌面和移动应用界面。Qt Designer是一个可视化界面设计工具,允许通过拖放操作构建UI,所见即所得。 非GUI应用开发: 除了GUI程序,Qt同样适用于开发命令行工具、后台服务、嵌入式系统等非图形化应用。其底层API涵盖了文件处理、线程、网络通信、数据库访问等广泛的功能。 面向对象设计: Qt采用面向对象的设计原则,提供了高度模块化的类库,使得代码组织清晰、易于扩展和重用。它遵循MVC(模型-视图-控制器)模式,支持数据驱动的界面设计。 元对象系统与信号槽机制: Qt Meta-Object System(元对象系统)是Qt框架的一个重要特性,它引入了元对象编译器(moc),用于在编译时生成额外的代码以支持对象间通信、反射、动态属性绑定等高级功能。 Signal & Slot机制是Qt中实现对象间事件驱动通信的核心方式。信号代表对象状态变化或事件发生,槽则是响应这些信号的可调用实体。这种松耦合的通信方式简化了异步编程和事件处理。 QML与Qt Quick: QML是一种声明性语言,结合JavaScript,用于快速创建流畅、动态的用户界面。它与Qt Quick框架紧密集成,特别适合开发现代化、触屏友好的应用。 Qt Quick Controls和Qt Quick Dialogs提供了一系列预定义的QML组件,用于构建具有传统桌面风格或现代移动风格的界面元素。同时,Qt Quick也支持自定义控件开发。 多媒体支持: Qt Multimedia模块提供对音频、视频播放、录音、摄像头访问等功能的支持,使得开发多媒体应用程序变得简便。 网络功能: Qt Network模块封装了网络通信相关的API,包括HTTP、FTP、TCP/UDP套接字、SSL加密、DNS查询等,便于开发网络应用和服务。 国际化与本地化: Qt Linguist工具支持应用程序的多语言翻译和本地化工作,确保软件能适应全球市场。 开发工具与生态系统: Qt Creator是一款集成开发环境(IDE),集成了项目管理、代码编辑、调试、版本控制、QML预览等多种功能,为Qt开发提供了无缝的工作流程。 Qt拥有活跃的开发者社区、丰富的文档资源、示例代码库以及商业支持选项,为开发者的学习、问题解决和项目实施提供了坚实后盾。
Qt技术是一套强大的跨平台应用程序开发框架,专注于使用C++语言来构建高性能、高可维护性的软件解决方案。自1991年首次推出以来,Qt因其卓越的跨平台能力、丰富的功能集、高效的开发工具和良好的社区支持而广受开发者欢迎。以下是对Qt技术的详细介绍: 核心特性 跨平台性: Qt设计的核心理念是“一次编写,到处运行”。它支持多种操作系统,包括但不限于Windows、macOS、Linux、Android和iOS。通过Qt开发者可以使用同一套源代码,在不同平台上编译并生成原生外观与体验的应用程序,极大地提高了开发效率和产品一致性。 图形用户界面(GUI)开发: 提供了一个完整的GUI工具箱,包含各种控件(如按钮、文本框、滑块、列表视图等)、布局管理器、样式表支持、动画效果等,帮助开发者快速构建美观、功能丰富的桌面和移动应用界面。Qt Designer是一个可视化界面设计工具,允许通过拖放操作构建UI,所见即所得。 非GUI应用开发: 除了GUI程序,Qt同样适用于开发命令行工具、后台服务、嵌入式系统等非图形化应用。其底层API涵盖了文件处理、线程、网络通信、数据库访问等广泛的功能。 面向对象设计: Qt采用面向对象的设计原则,提供了高度模块化的类库,使得代码组织清晰、易于扩展和重用。它遵循MVC(模型-视图-控制器)模式,支持数据驱动的界面设计。 元对象系统与信号槽机制: Qt Meta-Object System(元对象系统)是Qt框架的一个重要特性,它引入了元对象编译器(moc),用于在编译时生成额外的代码以支持对象间通信、反射、动态属性绑定等高级功能。 Signal & Slot机制是Qt中实现对象间事件驱动通信的核心方式。信号代表对象状态变化或事件发生,槽则是响应这些信号的可调用实体。这种松耦合的通信方式简化了异步编程和事件处理。 QML与Qt Quick: QML是一种声明性语言,结合JavaScript,用于快速创建流畅、动态的用户界面。它与Qt Quick框架紧密集成,特别适合开发现代化、触屏友好的应用。 Qt Quick Controls和Qt Quick Dialogs提供了一系列预定义的QML组件,用于构建具有传统桌面风格或现代移动风格的界面元素。同时,Qt Quick也支持自定义控件开发。 多媒体支持: Qt Multimedia模块提供对音频、视频播放、录音、摄像头访问等功能的支持,使得开发多媒体应用程序变得简便。 网络功能: Qt Network模块封装了网络通信相关的API,包括HTTP、FTP、TCP/UDP套接字、SSL加密、DNS查询等,便于开发网络应用和服务。 国际化与本地化: Qt Linguist工具支持应用程序的多语言翻译和本地化工作,确保软件能适应全球市场。 开发工具与生态系统: Qt Creator是一款集成开发环境(IDE),集成了项目管理、代码编辑、调试、版本控制、QML预览等多种功能,为Qt开发提供了无缝的工作流程。 Qt拥有活跃的开发者社区、丰富的文档资源、示例代码库以及商业支持选项,为开发者的学习、问题解决和项目实施提供了坚实后盾。
Qt技术是一套强大的跨平台应用程序开发框架,专注于使用C++语言来构建高性能、高可维护性的软件解决方案。自1991年首次推出以来,Qt因其卓越的跨平台能力、丰富的功能集、高效的开发工具和良好的社区支持而广受开发者欢迎。以下是对Qt技术的详细介绍: 核心特性 跨平台性: Qt设计的核心理念是“一次编写,到处运行”。它支持多种操作系统,包括但不限于Windows、macOS、Linux、Android和iOS。通过Qt开发者可以使用同一套源代码,在不同平台上编译并生成原生外观与体验的应用程序,极大地提高了开发效率和产品一致性。 图形用户界面(GUI)开发: 提供了一个完整的GUI工具箱,包含各种控件(如按钮、文本框、滑块、列表视图等)、布局管理器、样式表支持、动画效果等,帮助开发者快速构建美观、功能丰富的桌面和移动应用界面。Qt Designer是一个可视化界面设计工具,允许通过拖放操作构建UI,所见即所得。 非GUI应用开发: 除了GUI程序,Qt同样适用于开发命令行工具、后台服务、嵌入式系统等非图形化应用。其底层API涵盖了文件处理、线程、网络通信、数据库访问等广泛的功能。 面向对象设计: Qt采用面向对象的设计原则,提供了高度模块化的类库,使得代码组织清晰、易于扩展和重用。它遵循MVC(模型-视图-控制器)模式,支持数据驱动的界面设计。 元对象系统与信号槽机制: Qt Meta-Object System(元对象系统)是Qt框架的一个重要特性,它引入了元对象编译器(moc),用于在编译时生成额外的代码以支持对象间通信、反射、动态属性绑定等高级功能。 Signal & Slot机制是Qt中实现对象间事件驱动通信的核心方式。信号代表对象状态变化或事件发生,槽则是响应这些信号的可调用实体。这种松耦合的通信方式简化了异步编程和事件处理。 QML与Qt Quick: QML是一种声明性语言,结合JavaScript,用于快速创建流畅、动态的用户界面。它与Qt Quick框架紧密集成,特别适合开发现代化、触屏友好的应用。 Qt Quick Controls和Qt Quick Dialogs提供了一系列预定义的QML组件,用于构建具有传统桌面风格或现代移动风格的界面元素。同时,Qt Quick也支持自定义控件开发。 多媒体支持: Qt Multimedia模块提供对音频、视频播放、录音、摄像头访问等功能的支持,使得开发多媒体应用程序变得简便。 网络功能: Qt Network模块封装了网络通信相关的API,包括HTTP、FTP、TCP/UDP套接字、SSL加密、DNS查询等,便于开发网络应用和服务。 国际化与本地化: Qt Linguist工具支持应用程序的多语言翻译和本地化工作,确保软件能适应全球市场。 开发工具与生态系统: Qt Creator是一款集成开发环境(IDE),集成了项目管理、代码编辑、调试、版本控制、QML预览等多种功能,为Qt开发提供了无缝的工作流程。 Qt拥有活跃的开发者社区、丰富的文档资源、示例代码库以及商业支持选项,为开发者的学习、问题解决和项目实施提供了坚实后盾。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值