一、Qt中多线程的使用

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

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

  • 默认的线程在Qt中称之为窗口线程,也叫主线程,负责窗口事件处理或者窗口控件数据的更新
  • 子线程负责后台的业务逻辑处理,子线程中不能对窗口对象做任何操作,这些事情需要交给窗口线程处理
  • 主线程和子线程之间如果要进行数据的传递,需要使用Qt中的信号槽机制(即如果子线程需要对窗口的数据进行修改,只能先通过connect发送给主线程,由主线程对窗口进行修改,而子线程不能直接对窗口进行修改)。

使用方式1

  • 创建一个线程类的子类,让其继承QT中的线程类 QThread
  • 重写父类的 run() 方法,在该函数内部编写子线程要处理的具体的业务流程
  • 在主线程中创建子线程对象,new 一个就可以了
  • 启动子线程, 调用 start() 方法

案例:创建一个子线程文件,里面定义三个子线程类,使其继承QThread,分别重写虚函数run,子线程需要执行的任务都写在该函数里面。而在主线程中,先创建该类的一个对象,调用start函数,则表示启动子线程(run()函数)。

下面创建了一个MyThread.h文件,在该文件里面创建了三个子线程类,分别是

  • Generate类:生成随机数
  • BubbleSort类:对生成的随机数进行冒泡排序
  • QuickSort类:对生成的随机数进行快速排序

子线程头文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
//生成随机数
class Generate : public QThread
{
Q_OBJECT
public:
explicit Generate(QObject *parent = nullptr);
void recvNum(int num); //槽函数,复制要生成的随机数
protected:
void run() override; //从父类继承的虚函数
signals:
void sendArray(QVector<int>); //向主线程发送存储随机生成的数的容器
private:
int m_num;
};

//冒泡排序
class BubbleSort : public QThread
{
Q_OBJECT
public:
explicit BubbleSort(QObject *parent = nullptr);
void recvArray(QVector<int>list); //槽函数接收要排序的数组
protected:
void run() override; //从父类继承的虚函数
signals:
void finish(QVector<int> num); //向主线程发送排序好的容器
private:
QVector<int> m_list;
};

//快速排序
class QuickSort : public QThread
{
Q_OBJECT
public:
explicit QuickSort(QObject *parent = nullptr);
void recvArray(QVector<int>list); //槽函数接收要排序的数组
protected:
void run() override; //从父类继承的虚函数
private:
void quick(QVector<int>&list, int l, int r);
signals:
void finish(QVector<int> num); //向主线程发送排序好的容器
private:
QVector<int> m_list;
};

主线程的头文件:

1
2
3
4
5
6
7
8
9
10
11
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
signals:
void starting(int num); //向子线程发送的信号,参数是要生成的随机数个数
private:
Ui::MainWindow *ui;
};

子线程的执行文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
Generate::Generate(QObject *parent) : QThread(parent)
{
}

void Generate::recvNum(int num)
{
m_num = num;
}

void Generate::run()
{
qDebug()<<"生成随机数的线程的线程地址:"<<QThread::currentThread();
QVector<int> list;
QElapsedTimer time;
time.start(); //开始计时
for(int i=0; i<m_num; i++){
list.push_back(qrand()%100000); //生成随机数放到容器里面
}
int milsec = time.elapsed();
qDebug()<<"生成"<<m_num<<"个随机数总共用时:"<<milsec<<"毫秒";
emit sendArray(list); //发送容器给主线程
}

//冒泡排序操作
BubbleSort::BubbleSort(QObject *parent):QThread(parent)
{
}

void BubbleSort::recvArray(QVector<int> list)
{
m_list = list;
}

void BubbleSort::run()
{
qDebug()<<"冒泡排序的线程的线程地址:"<<QThread::currentThread();
QElapsedTimer time;
time.start(); //开始计时
int temp;
for(int i=0; i<m_list.size()-1; i++){
for(int j=0; j<m_list.size()-i-1; j++){
if(m_list[j]>m_list[j+1]){
temp = m_list[j];
m_list[j] = m_list[j+1];
m_list[j+1] = temp;
}
}
}
int milsec = time.elapsed();
qDebug()<<"冒泡排序花费的时间是"<<milsec<<"毫秒";
emit finish(m_list); //发送排序好的给主线程
}

//快速排序操作
QuickSort::QuickSort(QObject *parent):QThread(parent)
{
}

void QuickSort::recvArray(QVector<int> list)
{
m_list = list;
}

void QuickSort::run()
{
qDebug()<<"快速排序的线程的线程地址:"<<QThread::currentThread();
QElapsedTimer time;
time.start(); //开始计时
quick(m_list,0,m_list.size()-1); //执行快速排序
int milsec = time.elapsed();
qDebug()<<"快速排序花费的时间是"<<milsec<<"毫秒";
emit finish(m_list); //发送排序好的给主线程
}

void QuickSort::quick(QVector<int>&list, int l, int r)
{
if(l<r){
int i=l, j=r;
int x = list[i];
while(i<j){
while(i<j && list[j]>=x) j--;
list[i]=list[j];
while(i<j && list[i]<=x) i++;
list[j]=list[i];
}
list[i] = x;
quick(list,l,i-1);
quick(list,i+1,r);
}
}

主线程的执行文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);

//1.创建子线程对象
Generate* gen = new Generate;
BubbleSort* bubble = new BubbleSort;
QuickSort* quick = new QuickSort;

connect(this, &MainWindow::starting, gen, &Generate::recvNum); //连接操作,主线程发送生成随机数个数的信号,子线程接收

//2.启动子线程
//生成随机数
connect(ui->start, &QPushButton::clicked, this, [=](){
emit starting(10000); //发送信号,参数是生成随机数的个数
gen->start(); //启动子线程,即执行子线程的run方法(生成随机数)
});
//接收子线程发送的数据
connect(gen, &Generate::sendArray, bubble, &BubbleSort::recvArray); //冒泡子线程接收信号,并执行存储容器操作
connect(gen, &Generate::sendArray, quick, &QuickSort::recvArray); //快速子线程接收信号,并执行存储容器操作
connect(gen, &Generate::sendArray, this, [=](QVector<int> list){ //主线程接收信号,将生成的随机数放到窗口
bubble->start(); //启动冒牌排序子线程
quick->start(); //启动快速排序子线程
for(int i=0; i<list.size(); i++){
ui->randList->addItem(QString::number(list.at(i)));
}
});
connect(bubble, &BubbleSort::finish, this, [=](QVector<int> list){ //冒泡排序子线程排序完,将容器发送给主线程
for(int i=0; i<list.size(); i++){
ui->bubbleList->addItem(QString::number(list.at(i)));
}
});
connect(quick, &QuickSort::finish, this, [=](QVector<int> list){ //快速排序子线程排序完,将容器发送给主线程
for(int i=0; i<list.size(); i++){
ui->quickList->addItem(QString::number(list.at(i)));
}
});
}

执行结果:

使用方式2

Qt提供的第二种线程的创建方式弥补了第一种方式的缺点,用起来更加灵活,但是这种方式写起来会相对复杂一些。

  • 创建一个新的类(如MyWork),让这个类从QObject派生
  • 在这个类中添加一个公共的成员函数,如working(),函数体就是我们要子线程中执行的业务逻辑
  • 在主线程中创建一个QThread对象, 这就是子线程的对象
  • 在主线程中创建工作的类对象(千万不要指定给创建的对象指定父对象)
  • 将MyWork对象移动到创建的子线程对象中, 需要调用QObject类提供的moveToThread()方法
  • 启动子线程,调用 start(), 这时候线程启动了, 但是移动到线程中的对象(任务)并没有工作
  • 调用MyWork类对象的工作函数,让这个函数开始执行,这时候是在移动到的那个子线程中运行的

主窗口的头文件:

1
2
3
4
5
6
7
8
9
10
11
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
signals:
void starting(int num); //向子线程发送的信号,参数是要生成的随机数个数
private:
Ui::MainWindow *ui;
};

子类的头文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//生成随机数
class Generate : public QObject
{
Q_OBJECT
public:
explicit Generate(QObject *parent = nullptr);
void working(int num);
signals:
void sendArray(QVector<int>); //向主线程发送存储随机生成的数的容器
};

//冒泡排序
class BubbleSort : public QObject
{
Q_OBJECT
public:
explicit BubbleSort(QObject *parent = nullptr);
void working(QVector<int>list);
signals:
void finish(QVector<int> num); //向主线程发送排序好的容器
};

//快速排序
class QuickSort : public QObject
{
Q_OBJECT
public:
explicit QuickSort(QObject *parent = nullptr);
void working(QVector<int>list);
private:
void quick(QVector<int>&list, int l, int r);
signals:
void finish(QVector<int> num); //向主线程发送排序好的容器
};

主函数执行函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);

//1.创建子线程对象
QThread* t1=new QThread;
QThread* t2=new QThread;
QThread* t3=new QThread;

//2.创建任务类对象
Generate* gen = new Generate;
BubbleSort* bubble = new BubbleSort;
QuickSort* quick = new QuickSort;

//3.将任务对象移动到某个子线程中
gen->moveToThread(t1);
bubble->moveToThread(t2);
quick->moveToThread(t3);

connect(this, &MainWindow::starting, gen, &Generate::working); //连接操作,主线程发送生成随机数个数的信号,子线程接收

//2.启动子线程
//生成随机数
connect(ui->start, &QPushButton::clicked, this, [=](){
emit starting(10000); //发送信号,参数是生成随机数的个数
t1->start(); //启动子线程,即执行子线程的working()方法(生成随机数),没有start,是不会执行working函数的
});
//接收子线程发送的数据
connect(gen, &Generate::sendArray, bubble, &BubbleSort::working); //冒泡子线程接收信号,并执行存储容器操作
connect(gen, &Generate::sendArray, quick, &QuickSort::working); //快速子线程接收信号,并执行存储容器操作
connect(gen, &Generate::sendArray, this, [=](QVector<int> list){ //主线程接收信号,将生成的随机数放到窗口
t2->start(); //启动冒牌排序子线程
t3->start(); //启动快速排序子线程
for(int i=0; i<list.size(); i++){
ui->randList->addItem(QString::number(list.at(i)));
}
});
connect(bubble, &BubbleSort::finish, this, [=](QVector<int> list){ //冒泡排序子线程排序完,将容器发送给主线程
for(int i=0; i<list.size(); i++){
ui->bubbleList->addItem(QString::number(list.at(i)));
}
});
connect(quick, &QuickSort::finish, this, [=](QVector<int> list){ //快速排序子线程排序完,将容器发送给主线程
for(int i=0; i<list.size(); i++){
ui->quickList->addItem(QString::number(list.at(i)));
}
});
}

子类的执行函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
Generate::Generate(QObject *parent) : QObject(parent)
{
}

void Generate::working(int num)
{
qDebug()<<"生成随机数的线程的线程地址:"<<QThread::currentThread();
QVector<int> list;
QElapsedTimer time;
time.start(); //开始计时
for(int i=0; i<num; i++){
list.push_back(qrand()%100000); //生成随机数放到容器里面
}
int milsec = time.elapsed();
qDebug()<<"生成"<<num<<"个随机数总共用时:"<<milsec<<"毫秒";
emit sendArray(list); //发送容器给主线程
}

//冒泡排序操作
BubbleSort::BubbleSort(QObject *parent):QObject(parent)
{
}

void BubbleSort::working(QVector<int>list)
{
qDebug()<<"冒泡排序的线程的线程地址:"<<QThread::currentThread();
QElapsedTimer time;
time.start(); //开始计时
int temp;
for(int i=0; i<list.size()-1; i++){
for(int j=0; j<list.size()-i-1; j++){
if(list[j]>list[j+1]){
temp = list[j];
list[j] = list[j+1];
list[j+1] = temp;
}
}
}
int milsec = time.elapsed();
qDebug()<<"冒泡排序花费的时间是"<<milsec<<"毫秒";
emit finish(list); //发送排序好的给主线程
}

//快速排序操作
QuickSort::QuickSort(QObject *parent):QObject(parent)
{
}

void QuickSort::working(QVector<int>list)
{
qDebug()<<"快速排序的线程的线程地址:"<<QThread::currentThread();
QElapsedTimer time;
time.start(); //开始计时
quick(list,0,list.size()-1); //执行快速排序
int milsec = time.elapsed();
qDebug()<<"快速排序花费的时间是"<<milsec<<"毫秒";
emit finish(list); //发送排序好的给主线程
}

void QuickSort::quick(QVector<int>&list, int l, int r)
{
if(l<r){
int i=l, j=r;
int x = list[i];
while(i<j){
while(i<j && list[j]>=x) j--;
list[i]=list[j];
while(i<j && list[i]<=x) i++;
list[j]=list[i];
}
list[i] = x;
quick(list,l,i-1);
quick(list,i+1,r);
}
}

二、线程池

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

主线程的头文件:

1
2
3
4
5
6
7
8
9
10
11
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
signals:
void starting(int num); //向子线程发送的信号,参数是要生成的随机数个数
private:
Ui::MainWindow *ui;
};

子线程的头文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//生成随机数
class Generate : public QObject, public QRunnable
{
Q_OBJECT
public:
explicit Generate(QObject *parent = nullptr);
void recvNum(int num);
void run() override; //从父类继承的虚函数
signals:
void sendArray(QVector<int>); //向主线程发送存储随机生成的数的容器
private:
int m_num;
};

//冒泡排序
class BubbleSort : public QObject, public QRunnable
{
Q_OBJECT
public:
explicit BubbleSort(QObject *parent = nullptr);
void recvArray(QVector<int>list); //槽函数接收要排序的数组
void run() override; //从父类继承的虚函数
signals:
void finish(QVector<int> num); //向主线程发送排序好的容器
private:
QVector<int> m_list;
};

//快速排序
class QuickSort : public QObject, public QRunnable
{
Q_OBJECT
public:
explicit QuickSort(QObject *parent = nullptr);
void recvArray(QVector<int>list); //槽函数接收要排序的数组
void run() override; //从父类继承的虚函数
private:
void quick(QVector<int>&list, int l, int r);
signals:
void finish(QVector<int> num); //向主线程发送排序好的容器
private:
QVector<int> m_list;
};

主线程执行文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);

//1.创建任务对象
Generate* gen = new Generate;
BubbleSort* bubble = new BubbleSort;
QuickSort* quick = new QuickSort;
connect(this, &MainWindow::starting, gen, &Generate::recvNum); //连接操作,主线程发送生成随机数个数的信号,子线程接收
//2.启动子线程
//生成随机数
connect(ui->start, &QPushButton::clicked, this, [=](){
emit starting(10000); //发送信号,参数是生成随机数的个数
QThreadPool::globalInstance()->start(gen); //把任务对象添加到线程池里面去
});
//接收子线程发送的数据
connect(gen, &Generate::sendArray, bubble, &BubbleSort::recvArray);//冒泡子线程接收信号,并执行存储容器操作
connect(gen, &Generate::sendArray, quick, &QuickSort::recvArray); //快速子线程接收信号,并执行存储容器操作
connect(gen, &Generate::sendArray, this, [=](QVector<int> list){ //主线程接收信号,将生成的随机数放到窗口
QThreadPool::globalInstance()->start(bubble); //把任务对象添加到线程池里面去
QThreadPool::globalInstance()->start(quick); //把任务对象添加到线程池里面去
for(int i=0; i<list.size(); i++){
ui->randList->addItem(QString::number(list.at(i)));
}
});
connect(bubble, &BubbleSort::finish, this, [=](QVector<int> list){ //冒泡排序子线程排序完,将容器发送给主线程
for(int i=0; i<list.size(); i++){
ui->bubbleList->addItem(QString::number(list.at(i)));
}
});
connect(quick, &QuickSort::finish, this, [=](QVector<int> list){ //快速排序子线程排序完,将容器发送给主线程
for(int i=0; i<list.size(); i++){
ui->quickList->addItem(QString::number(list.at(i)));
}
});
}

子线程执行文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
Generate::Generate(QObject *parent) : QObject(parent), QRunnable()
{
setAutoDelete(true); //设置当前类的对象放到线程池中后,工作完毕能自动析构
}

void Generate::recvNum(int num)
{
m_num = num;
}

void Generate::run()
{
qDebug()<<"生成随机数的线程的线程地址:"<<QThread::currentThread();
QVector<int> list;
QElapsedTimer time;
time.start(); //开始计时
for(int i=0; i<m_num; i++){
list.push_back(qrand()%100000); //生成随机数放到容器里面
}
int milsec = time.elapsed();
qDebug()<<"生成"<<m_num<<"个随机数总共用时:"<<milsec<<"毫秒";
emit sendArray(list); //发送容器给主线程
}

//冒泡排序操作
BubbleSort::BubbleSort(QObject *parent):QObject(parent), QRunnable()
{
setAutoDelete(true); //设置
}

void BubbleSort::recvArray(QVector<int> list)
{
m_list = list;
}

void BubbleSort::run()
{
qDebug()<<"冒泡排序的线程的线程地址:"<<QThread::currentThread();
QElapsedTimer time;
time.start(); //开始计时
int temp;
for(int i=0; i<m_list.size()-1; i++){
for(int j=0; j<m_list.size()-i-1; j++){
if(m_list[j]>m_list[j+1]){
temp = m_list[j];
m_list[j] = m_list[j+1];
m_list[j+1] = temp;
}
}
}
int milsec = time.elapsed();
qDebug()<<"冒泡排序花费的时间是"<<milsec<<"毫秒";
emit finish(m_list); //发送排序好的给主线程
}

//快速排序操作
QuickSort::QuickSort(QObject *parent):QObject(parent), QRunnable()
{
setAutoDelete(true); //设置
}

void QuickSort::recvArray(QVector<int> list)
{
m_list = list;
}

void QuickSort::run()
{
qDebug()<<"快速排序的线程的线程地址:"<<QThread::currentThread();
QElapsedTimer time;
time.start(); //开始计时
quick(m_list,0,m_list.size()-1); //执行快速排序
int milsec = time.elapsed();
qDebug()<<"快速排序花费的时间是"<<milsec<<"毫秒";
emit finish(m_list); //发送排序好的给主线程
}

void QuickSort::quick(QVector<int>&list, int l, int r)
{
if(l<r){
int i=l, j=r;
int x = list[i];
while(i<j){
while(i<j && list[j]>=x) j--;
list[i]=list[j];
while(i<j && list[i]<=x) i++;
list[j]=list[i];
}
list[i] = x;
quick(list,l,i-1);
quick(list,i+1,r);
}
}

三、基于TCP的Qt网络通信

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

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

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

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

服务器端

通信流程:

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

客户端

  • 创建通信的套接字类QTcpSocket对象
  • 使用服务器端绑定的IP和端口连接服务器QAbstractSocket::connectToHost()
  • 使用QTcpSocket对象和服务器进行通信

案例1:通过服务端/客户端实现数据发送

服务端头文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class MainWindow : public QMainWindow
{
Q_OBJECT

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

private slots:
void on_setListen_clicked(); //点击 启动监听服务 按钮触发的槽函数
void on_sendMsg_clicked(); //点击 发送信息 按钮触发的槽函数

private:
Ui::MainWindow *ui;
QTcpServer* m_s; //监听的服务器对象(监听描述符)
QTcpSocket* m_tcp; //通信的套接字对象(通信描述符)
QLabel* m_status; //状态栏里面的label对象
};

服务端执行文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->port->setText("8899"); //端口号默认设置为8899
setWindowTitle("服务器"); //给窗口添加标题

//创建监听的服务器对象
m_s = new QTcpServer(this); //指定父对象,窗口关闭自动析构服务器对象

//服务端监听的时候,当有客户端连接到达时,就会发出信号newConnection
connect(m_s, &QTcpServer::newConnection, this, [=](){
m_tcp = m_s->nextPendingConnection(); //得到一个用于通信的套接字对象(实例化)
m_status->setPixmap(QPixmap(":/connect.png").scaled(20,20)); //改为连接状态的图片,固定高度和宽度都是20
//检测客户点是否发来数据(当接收到readyRead信号的时候,就可以接收数据了)
connect(m_tcp, &QTcpSocket::readyRead, this, [=](){
QByteArray data = m_tcp->readAll(); //接收所有数据
ui->record->append("客户端say:" + data); //将接收的数据显示在record框
});
//当客户端断开连接,m_tcp会发出信号disconnected
connect(m_tcp, &QTcpSocket::disconnected, this, [=](){
m_tcp->close(); //关闭通信描述符
m_tcp->deleteLater(); //进行析构
m_status->setPixmap(QPixmap(":/disconnect.png").scaled(20,20)); //状态栏的连接状态重新设置为为未连接状态
});
});

//状态栏
m_status = new QLabel; //创建一个label
m_status->setPixmap(QPixmap(":/disconnect.png").scaled(20,20)); //默认情况下是未连接状态的图片,固定高度和宽度都是20
//往状态栏里面添加label
ui->statusbar->addWidget(new QLabel("连接状态:"));
ui->statusbar->addWidget(m_status);
}

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

//点击窗口的 启动监听服务 按钮触发的槽函数
void MainWindow::on_setListen_clicked()
{
unsigned short port = ui->port->text().toUShort(); //获取端口号(字符串类型),转为无符号短整型
m_s->listen(QHostAddress::Any, port); //进行监听(绑定了本地的任意一个ip地址,指定端口)
ui->setListen->setDisabled(true); //将该按钮设置为不可用状态
}

//点击 发送信息 按钮触发的槽函数
void MainWindow::on_sendMsg_clicked()
{
QString msg = ui->msg->toPlainText(); //以纯文本的方式读取要发送的信息
m_tcp->write(msg.toUtf8()); //将string类型转换为QByteArray类型进行写入发送
ui->record->append("服务器say:" + msg); //将要发送的msg写入到record框
ui->msg->clear(); //发送完毕后,清空输入框
}

客户端头文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_sendMsg_clicked(); //点击 发送信息 按钮触发的槽函数
void on_connect_clicked(); //点击 连接服务器 按钮触发的槽函数
void on_disconnect_clicked(); //点击 断开连接 按钮触发的槽函数
private:
Ui::MainWindow *ui;

QTcpSocket* m_tcp; //通信的套接字对象(通信描述符)
QLabel* m_status; //状态栏里面的label对象
};

客户端执行文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->port->setText("8899"); //端口号默认设置为8899
ui->ip->setText("127.0.0.1");
setWindowTitle("客户端"); //给窗口添加标题
ui->disconnect->setDisabled(true); //刚开始时,断开连接 按钮不能使用

//创建通信的服务器对象
m_tcp = new QTcpSocket(this); //实例化通信套接字对象,如果指定父对象this,窗口关闭自动析构服务器对象

//检测客户点是否发来数据(当接收到readyRead信号的时候,就可以接收数据了)
connect(m_tcp, &QTcpSocket::readyRead, this, [=](){
QByteArray data = m_tcp->readAll(); //接收所有数据
ui->record->append("服务端say:" + data); //将接收的数据显示在record框
});

//当客户端断开连接,m_tcp会发出信号disconnected
connect(m_tcp, &QTcpSocket::disconnected, this, [=](){
//m_tcp->deleteLater(); //进行析构(m_tcp创建时没有指定父对象,就在这里手动析构)-->这里如果手动析构,断开后再连接就闪退
m_status->setPixmap(QPixmap(":/disconnect.png").scaled(20,20));//状态栏的连接状态重新设置为为未连接状态
ui->record->append("服务端与客户端断开了连接.....");
ui->connect->setDisabled(false); //断开连接后,连接按钮能使用
ui->disconnect->setEnabled(false); //断开按钮不可以使用
});

//当客户端连接成功,就发出connected信号
connect(m_tcp, &QTcpSocket::connected, this, [=](){
m_status->setPixmap(QPixmap(":/connect.png").scaled(20,20));
ui->record->append("已经成功连接到服务端.....");
ui->connect->setDisabled(true); //成功连接到服务端后,连接按钮不能使用
ui->disconnect->setEnabled(true); //断开按钮可以使用
});

//状态栏
m_status = new QLabel; //创建一个label
m_status->setPixmap(QPixmap(":/disconnect.png").scaled(20,20)); //默认情况下是未连接状态的图片,固定高度和宽度都是20
//往状态栏里面添加label
ui->statusbar->addWidget(new QLabel("连接状态:"));
ui->statusbar->addWidget(m_status);
}

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

//点击 发送信息 按钮触发的槽函数
void MainWindow::on_sendMsg_clicked()
{
QString msg = ui->msg->toPlainText(); //以纯文本的方式读取要发送的信息
m_tcp->write(msg.toUtf8()); //将string类型转换为QByteArray类型进行写入发送
ui->record->append("客户端say:" + msg); //将要发送的msg写入到record框
ui->msg->clear(); //发送完毕后,清空输入框
}

//点击 连接服务器 按钮触发的槽函数
void MainWindow::on_connect_clicked()
{
QString ip = ui->ip->text(); //获取ip
unsigned short port = ui->port->text().toUShort(); //获取端口
m_tcp->connectToHost(QHostAddress(ip),port); //通过ip和端口进行连接服务端
}

//点击 断开连接按钮,触发的槽函数
void MainWindow::on_disconnect_clicked()
{
m_tcp->close(); //关闭通信套接字连接,后续会发出信号disconnected
ui->connect->setDisabled(false); //断开连接后,连接按钮能使用
ui->disconnect->setEnabled(false); //断开按钮不可以使用
}

案例2:通过服务端/客户端实现文件发送(C:\Qt\study\socket_Qt)

四、Json在Qt中使用

JSON(JavaScrip Object Notation)是一种轻量级的数据交换格式,它基于ECMAScript(欧洲计算机协会制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得JSON成为理想的数据交换语言,易于阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。

可以理解为Json是一种数据格式,和语言无关,在什么语言中都可以使用Json,基于这种通用的数据格式,一般处理两方面任务:

  • 组织数据(数据序列化),用于数据的网络传输
  • 组数数据(数据序列化),写磁盘文件实现数据的持久化存储(一般以.json作为文件后缀)

Json中主要有两种数据格式:Json数组和Json对象,并且这两种格式可以交叉嵌套使用。

Json数组

Json数组使用[]表示,[]里面是元素,元素和元素之间使用逗号间隔,最后一个因素后边没有逗号,一个Json数组中支持同时存在多种不同类型的成员,包括:整型浮点字符串布尔类型json数组json对象空值(null)。可见Json数组比起c/c++数组要灵活很多

Json对象

Json对象使用{}来描述,每个Json对象中可以存储若干个元素,每一个元素对应一个键值对(key:value结构),元素和元素之间使用逗号间隔,最后一个元素后边没有逗号,对于每个元素中的键值对有以下需要注意:

  • 键值(key)必须是字符串,位于同一层级的键值不要重复(因为是通过键值取出对应的value值)
  • value值的类型是可选的,,可根据实际需求指定,可用类型包括:整型浮点字符串布尔类型json数组json对象空值(null)