Qt环境及其常见函数

一 Qt安装

  windows:http://c.biancheng.net/view/3858.html

二 Qt工具集

  http://c.biancheng.net/view/3868.html
  原本 GNU 工具只在 Linux/Unix 系统里才有,随着 Windows 系统的广泛使用, 为了在 Windows 系统里可以使用 GNU 工具,诞生了 MinGW(Minimalist GNU for Windows) 项目,利用 MinGW 就可以生成 Windows 里面的 exe 程序和 dll 链接库。

三 C++11标准

  Qt 官方在编译 Qt5 库的时候都是开启 C++11 特性的,如果我们要在自己项目代码启用新标准,需要在 .pro 文件里面添加一行:

CONFIG += c++11

四 Qt Creator 的设置

  http://c.biancheng.net/view/1804.html
  安装vs调试工具。

五 hello world

在这里插入图片描述
  Qt Creator 可以创建多种项目,在最左侧的列表框中单击“Application”,中间的列表框中列出了可以创建的应用程序的模板,各类应用程序如下:

  1. Qt Widgets Application,支持桌面平台的有图形用户界面(Graphic User Interface,GUI) 界面的应用程序。GUI 的设计完全基于 C++ 语言,采用 Qt 提供的一套 C++ 类库。
  2. Qt Console Application,控制台应用程序,无 GUI 界面,一般用于学习 C/C++ 语言,只需要简单的输入输出操作时可创建此类项目。
  3. Qt Quick Application,创建可部署的 Qt Quick 2 应用程序。Qt Quick 是 Qt 支持的一套 GUI 开发架构,其界面设计采用 QML 语言,程序架构采用 C++ 语言。利用 Qt Quick 可以设计非常炫的用户界面,一般用于移动设备或嵌入式设备上无边框的应用程序的设计。
  4. Qt Quick Controls 2 Application,创建基于 Qt Quick Controls 2 组件的可部署的 Qt Quick 2 应用程序。Qt Quick Controls 2 组件只有 Qt 5.7 及以后版本才有。
  5. Qt Canvas 3D Application,创建 Qt Canvas 3D QML 项目,也是基于 QML 语言的界面设计,支持 3D 画布。

在这里插入图片描述
  在图 3 显示的界面中单击“Next”按钮,出现如图 4 所示的界面。在此界面中选择需要创建界面的基类(base class)。有 3 种基类可以选择:

  1. QMainWindow 是主窗口类,主窗口具有主菜单栏、工具栏和状态栏,类似于一般的应用程序的主窗口;
  2. QWidget 是所有具有可视界面类的基类,选择 QWidget 创建的界面对各种界面组件都可以支持;
  3. QDialog 是对话框类,可建立一个基于对话框的界面;
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
    QApplication a(argc, argv); //定义并创建应用程序
    Widget w; //定义并创建窗口
    w.show(); //显示窗口
    return a.exec(); //应用程序运行
}

  main的主要功能是创建应用程序,创建窗口,显示窗口,并运行应用程序,开始应用程序的消息循环和事件处理。
  QApplication 是 Qt 的标准应用程序类,第 1 行代码定义了一个 QApplication 类的实例 a,就是应用程序对象。
  然后定义了一个 Widget 类的变量 w,Widget 是本实例设计的窗口的类名,定义此窗口后再用 w.show() 显示此窗口。
  最后一行用 a.exec() 启动应用程序的执行,开始应用程序的消息循环和事件处理。
  参考:
  Qt窗口之QMainWindow、QDialog、QWidget:https://blog.csdn.net/rl529014/article/details/51419126

六 UI界面

  • Signals 和 Slots 编辑器与 Action 编辑器是位于待设计窗体下方的两个编辑器。Signals 和Slots 编辑器用于可视化地进行信号与槽的关联,Action 编辑器用于可视化设计 Action。
  • 对象浏览器(Object Inspector):窗口右上方是 Object Inspector,用树状视图显示窗体上各组件之间的布局包含关系,视图有两列,显示每个组件的对象名称(ObjectName)和类名称。
  • 属性编辑器(Property Editor):窗口右下方是属性编辑器,是界面设计时最常用到的编辑器。属性编辑器显示某个选中的组件或窗体的各种属性及其取值,可以在属性编辑器里修改这些属性的值。
    在这里插入图片描述

  属性编辑器的内容分为两列,分别为属性的名称和属性的值。属性又分为多个组,实际上表示了类的继承关系,可以看出 QLabel 的继承关系是QObject→QWidget→QFrame→QLabel。

七 Qt 之模式、非模式、半模式对话框

  本节内容来自这里

1 简述

  关于“模式”和“非模式”对话框,相信大家都比较熟悉,但其中有一个可能很多人都比较陌生,介于两者之间的状态,我们称之为“半模式“。

2 模式对话框

2.1 描述

  阻塞同一应用程序中其它可视窗口输入的对话框。模式对话框有自己的事件循环,用户必须完成这个对话框中的交互操作,并且关闭了它之后才能访问应用程序中的其它任何窗口。模式对话框仅阻止访问与对话相关联的窗口,允许用户继续使用其它窗口中的应用程序。
  显示模态对话框最常见的方法是调用其exec()函数,当用户关闭对话框,exec()将提供一个有用的返回值,并且这时流程控制继续从调用exec()的地方进行。通常情况下,要获得对话框关闭并返回相应的值,我们连接默认按钮,例如:”确定”按钮连接到accept()槽,”取消”按钮连接到reject()槽。另外我们也可以连接done()槽,传递给它Accepted或Rejected。

2.2 效果

在这里插入图片描述

2.3 源码
MainWindow *pMainWindow = new MainWindow();
pMainWindow->setWindowTitle(QStringLiteral("主界面"));
pMainWindow->show();

CustomWindow *pDialog = new CustomWindow(pMainWindow);
pDialog->setWindowTitle(QStringLiteral("模式对话框"));

// 关键代码
pDialog->exec();

// 关闭模态对话框以后才会执行下面的代码
pMainWindow->setWindowTitle(QStringLiteral("主界面-模式对话框"));
qDebug() << QStringLiteral("关闭模态对话框以后,可以继续向下执行");

  1、主界面被阻塞,不能进行点击、拖动等任何操作。
  2、exec()之后的代码不会执行,直到关闭模态对话框。

3 非模式对话框

3.1 描述

  和同一个程序中其它窗口操作无关的对话框。在文字处理中的查找和替换对话框通常是非模式的,允许用户同时与应用程序的主窗口和对话框进行交互。调用show()来显示非模式对话框,并立即将控制返回给调用者。
  如果隐藏对话框后调用show()函数,对话框将显示在其原始位置,这是因为窗口管理器决定的窗户位置没有明确由程序员指定,为了保持被用户移动的对话框位置,在closeEvent()中进行处理,然后在显示之前,将对话框移动到该位置。

3.2 效果

在这里插入图片描述

3.3 源码
MainWindow *pMainWindow = new MainWindow();
pMainWindow->setWindowTitle(QStringLiteral("主界面"));
pMainWindow->show();

CustomWindow *pDialog = new CustomWindow(pMainWindow);
pDialog->setWindowTitle(QStringLiteral("非模式对话框"));

// 关键代码
pDialog->show();

// 下面的代码会立即运行
pMainWindow->setWindowTitle(QStringLiteral("主界面-非模式对话框"));
qDebug() << QStringLiteral("立即运行");

  1、主界面不会被阻塞,可以进行点击、拖动等任何操作。
  2、show()之后的代码会立即执行。

4 半模式对话框

4.1 描述

  调用setModal(true)或者setWindowModality(),然后show()。有别于exec(),show() 立即返回给控制调用者。
  对于进度对话框来说,调用setModal(true)是非常有用的,用户必须拥有与其交互的能力,例如:取消长时间运行的操作。如果使用show()和setModal(true)共同执行一个长时间操作,则必须定期在执行过程中调用QApplication ::processEvents(),以使用户能够与对话框交互(可以参考QProgressDialog)。

4.2 效果

在这里插入图片描述

4.3 源码
MainWindow *pMainWindow = new MainWindow();
pMainWindow->setWindowTitle(QStringLiteral("主界面"));
pMainWindow->show();

CustomWindow *pDialog = new CustomWindow(pMainWindow);
pDialog->setWindowTitle(QStringLiteral("半模式对话框"));

// 关键代码
pDialog->setModal(true);
pDialog->show();

// 下面的代码会立即运行
pMainWindow->setWindowTitle(QStringLiteral("主界面-半模式对话框"));
qDebug() << QStringLiteral("立即运行");

  1、主界面被阻塞,不能进行点击、拖动等任何操作。
  2、show()之后的代码会立即执行。

5 python实现

  **需求:**在PYQT5中,点击主窗口中的按钮,弹出子窗口。
  测试代码:
  例1:
  在主窗口添加按钮,并把按钮信号关联槽,在槽函数中创建子窗口对象赋值到普通变量,并调用其 show 方法。

from PyQt5.QtWidgets import *
import sys
 
class Main(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("主窗口")
        button = QPushButton("弹出子窗", self)
        button.clicked.connect(self.show_child)
 
    def show_child(self):
        child_window = Child()
        child_window.show()
 
class Child(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("我是子窗口啊")
 
# 运行主窗口
if __name__ == "__main__":
    app = QApplication(sys.argv)
 
    window = Main()
    window.show()
 
    sys.exit(app.exec_())

  运行结果: 该段代码运行后,点击主窗口中的按钮,子窗口一闪而过。
  例2:
  在主窗口添加按钮,并把按钮信号关联槽,在槽函数中创建子窗口对象并赋值为对象属性,并调用其 show 方法。

from PyQt5.QtWidgets import *
import sys
 
class Main(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("主窗口")
        button = QPushButton("弹出子窗", self)
        button.clicked.connect(self.show_child)
 
    def show_child(self):
        self.child_window = Child()
        self.child_window.show()
 
class Child(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("我是子窗口啊")
 
# 运行主窗口
if __name__ == "__main__":
    app = QApplication(sys.argv)
 
    window = Main()
    window.show()
 
    sys.exit(app.exec_())

  运行结果: 该段代码运行后,点击主窗口中的按钮,子窗口正常打开,重复点击按钮,子窗口重复弹出。
  例3:
  在主窗口__init__方法中创建子窗口对象并赋值为对象属性,添加按钮,并把按钮信号关联槽,在槽函数中调用子窗口对象的 show 方法。

from PyQt5.QtWidgets import *
import sys
 
class Main(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("主窗口")
        button = QPushButton("弹出子窗", self)
        button.clicked.connect(self.show_child)
        self.child_window = Child()
 
    def show_child(self):
        self.child_window.show()
 
class Child(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("我是子窗口啊")
 
# 运行主窗口
if __name__ == "__main__":
    app = QApplication(sys.argv)
 
    window = Main()
    window.show()
 
    sys.exit(app.exec_())

  运行结果: 重复点击按钮,子窗口不重复弹出。
  例4:
  QWidget不像QDialog,直接有exec_()方法来设置模态对话框,但是可以通过如下方法设置:setWindowModality(Qt.ApplicationModal)

from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import *
import sys

class Main(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("主窗口")
        button = QPushButton("弹出子窗", self)
        button.clicked.connect(self.show_child)
        self.child_window = Child()

    def show_child(self):
        self.child_window.setWindowModality(Qt.ApplicationModal)
        self.child_window.show()

class Child(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("我是子窗口啊")

# 运行主窗口
if __name__ == "__main__":
    app = QApplication(sys.argv)

    window = Main()
    window.show()

    sys.exit(app.exec_())

  运行结果:子窗口顺利弹出,且不能重新选择父窗口
  结论:
  例1中 子窗口 通过 show() 方法显示,为非模态窗口,它的实例为父窗口show_child()方法中的局部变量,当窗口显示后,父窗口的show_child()方法继续执行,当方法运行完后,python的回收机制就把局部变量销毁了,相当于子窗口实例被销毁,故子窗口一闪而过;
  例2中 子窗口实例为 主窗口类的变量,当show_child()方法运行完后,主窗口对象依旧存在,子窗口实例也存在,故子窗口正常显示,但是每一次运行槽函数都会重新创建子窗口对象;
  例3中 子窗口实例为 主窗口类的变量,当show_child()方法运行完后,主窗口对象依旧存在,子窗口实例也存在,故子窗口正常显示,每一次show_child()函数,重新调用子窗口对象show_child()方法,不会创建新窗口,且可随意在父,子窗口间切换;
  例4中 子窗口通过 exec() 方法显示,为模态窗口,虽然他为父窗口show_child()方法中的局部变量,由于阻塞的机制,父窗口show_child()并没有继续执行,故其不会像 例1 中 一闪而过,且不能在父,子窗口间切换;

6 更多参考

QWidget、QDialog、QMainWindow的异同点

七 程序打包

  http://enigmaprotector.com/en/downloads.html
  https://blog.csdn.net/windsnow1/article/details/78004265

八 Qt静态编译

https://www.cnblogs.com/ike_li/p/6860089.html

九 Qt 独立运行时伴随CMD命令窗口

  https://www.cnblogs.com/DreamDog/p/9160159.html

一 QPushButton中setEnabled()和setClickable()的区别

  当setEnabled()setClickable()设置成false,按钮就不可点击,设置成true,按钮就可以点击。
  它们的区别在于:
  setClickable():设置成true时,按钮为可点击,设置为false时,按钮不可点击,不能响应点击事件,但此时如果setEnabled()为true,那么按钮即使不可点击,也会产生变化(一闪一闪)。
  setEnabled():设置成true时,相当于激活了按钮,按钮的状态不再是死的,而是会对触摸或者点击产生反应,并且可以响应一些触发事件。而设置成false时,按钮是灰色的,无论是否可点击(即使将setClickable()设置成true),都无法响应任何触发事件。
  总的来看,setEnabled()相当于总开关,控制着按钮的状态,而setClickable()相当于具体的某个开关,控制这个开关是否可以点击。

二 信号与槽

  1、signals前面不可加public、private和protected进行修饰;slots前面可以加,因为Qt说槽函数可以当普通函数使用。
  2、信号的定义过程是在类的定义过程即头文件中实现的。为了中间编译工具moc的正常运行,不要在源文件(.cpp)中定义信号,signals区域的函数必须是void类型,而且这些信号函数没有函数体,也就是说不可以自己定义这些信号函数,你只要声明它就够了,其它不用管,Qt内部自己弄。
  3、宏定义和函数指针不能用于信号和槽的参数,信号和槽也不能有缺省参数。

1 概述

  在Qt中,对于发出信号的对象来说,它并不知道是谁接收了这个信号。这样的设计可能在某些地方会有些不便,但却杜绝了紧耦合,于总体设计有利。反应槽是用来接收信号的,但它实际上也是普通的函数,程序员可以象调用普通函数一样来调用反应槽。与信号类似的是,反应槽的拥有者也不知道是谁向它发出了信号。在程序设计过程中,多个信号可以连接至一个反应槽,类似的,一个信号也可以连接至多个反应槽,甚至一个信号可以连接至另一个信号。
  信号(Signal)就是在特定情况下被发射的事件;
  槽(Slot)就是对信号响应的函数。槽就是一个函数,与一般的C++函数是一样的,可以定义在类的任何部分(public、private或protected),可以具有任何参数,也可以被直接调用。槽函数与一般的函数不同的是:槽函数可以与一个信号关联,当信号被发射时,关联的槽函数被自动执行。
  信号与槽关联是用QObject::connect()函数实现的,其基本格式是:QObject::connect(sender, SIGNAL(signal()), receiver, SLOT(slot()));
  connect()QObject类的一个静态函数,而QObject是所有Qt类的基类,在实际调用时可以忽略前面的限定符,所以可以直接写为:connect(sender, SIGNAL(signal()), receiver, SLOT(slot()));
  其中,sender是发射信号的对象的名称,signal()是信号名称。信号可以看做是特殊的函数,需要带括号,有参数时还需要指明参数。receiver是接收信号的对象名称,slot()是槽函数的名称,需要带括号,有参数时还需要指明参数。

2 应用

class Demo : public QObject
{
    Q_OBJECT
public:
    Demo();
    int value() const { return val; };
public slots:
    void setValue( int );
signals:
    void valueChanged( int );
private:
    int val;
};

  由样例可看到,类的定义中有两个关键字slots和signals,还有一个宏Q_OBJECT。在Qt的程序中如果使用了信号与反应槽就必须在类的定义中声明这个宏,不过如果你声明了该宏但在程序中并没有信号与反应槽,对程序也不会有任何影响,所以建议大家在用Qt写程序时不妨都把这个宏加上。使用slots定义的就是信号的功能实现,即反应槽,例如:

void Demo::setValue( int v )
{
    if ( v != val )
    {
        val = v;
        emit valueChanged(v);
    }
}

  这段程序表明当setValue执行时它将释放出valueChanged这个信号。以下程序示范了不同对象间信号与反应槽的连接。

Demo a, b;
connect(&a, SIGNAL(valueChanged(int)), &b, SLOT(setValue(int)));
b.setValue(11);
a.setValue(79);
b.value(); // b的值将是79而不是原先设的11

  在以上程序中,一旦信号与反应槽连接,当执行a.setValue(79)时就会释放出一个valueChanged(int)的信号,对象b将会收到这个信号并触发setValue(int)这个函数。当b在执行setValue(int)这个函数时,它也将释放valueChanged(int)这个信号,当然b的信号无人接收,因此就什么也没干。请注意,在样例中我们仅当输入变量v不等于val时才释放信号,因此就算对a与b进行了交叉连接也不会导致死循环的发生。
  Qt本身并不包括C++的编译器,必须用Qt的中间编译工具moc.exe把该段代码转换为无专用关键字和宏的C++代码才能为这些编译程序所解析、编译与链接。
  一个对象的不同信号可以连接至不同的对象。当一个信号被释放时,与之连接的反应槽将被立刻执行,就像是在程序中直接调用该函数一样。信号的释放过程是阻塞的,这意味着只有当反应槽执行完毕后该信号释放过程才返回。如果一个信号与多个反应槽连接,则这些反应槽将被顺序执行,排序过程则是任意的。因此如果程序中对这些反应槽的先后执行次序有严格要求的,应特别注意。
  如果你要设计一个通用的类或控件,则在信号或反应槽的参数中应尽可能使用常规数据以增加通用性。如上例代码中valueChanged的参数为int型,如果它使用了特殊类型如QRangeControl::Range,那么这种信号只能与RangeControl中的反应槽连接。如前所述,反应槽也是常规函数,与未定义slots的用户函数在执行上没有任何区别。
  但在程序中不可把信号与常规函数连接在一起,否则信号的释放不会引起对应函数的执行。要命的是中间编译程序moc并不会对此种情况报错,C++编译程序更不会报错。初学者比较容易忽略这一点,往往是程序编好了没有错误,逻辑上也正确,但运行时就是不按自己的意愿出现结果,这时候应检查一下是不是这方面的疏忽。
  Qt的设计者之所以要这样做估计是为了信号与反应槽之间匹配的严格性。既然反应槽与常规函数在执行时没有什么区别,因此它也可以定义成公共反应槽(public slots)、保护反应槽(protected slots)和私有反应槽(private slots)。如果需要,我们也可以把反应槽定义成虚函数以便子类进行不同的实现,这一点是非常有用的。

3 emit、signal、slot

  Qt为此而新增的语法:三个关键字:slots、signals和emit,三个宏:SLOT()、SIGNAL()和Q_OBJECT。在头文件qobjectdefs.h中,我们可以看到这些新增语法的定义如下:

#define slots // slots: in class
#define signals protected // signals: in class
#define emit // emit signal
# define SLOT(a)     "1"#a
# define SIGNAL(a)   "2"#a

  由此可知其实三个关键字没有做什么事情,而SLOT()和SIGNAL()宏也只是在字符串前面简单地加上单个字符,以便程序仅从名称就可以分辨谁是信号、谁是反应槽。中间编译程序moc.exe则可以根据这些关键字和宏对相应的函数进行“翻译”,以便在C++编译器中编译。剩下一个宏Q_OBJECT比较复杂,它的定义如下:

#define Q_OBJECT \
public: \
    QT_WARNING_PUSH \
    Q_OBJECT_NO_OVERRIDE_WARNING \
    static const QMetaObject staticMetaObject; \
    virtual const QMetaObject *metaObject() const; \
    virtual void *qt_metacast(const char *); \
    virtual int qt_metacall(QMetaObject::Call, int, void **); \
    QT_TR_FUNCTIONS \
private: \
    Q_OBJECT_NO_ATTRIBUTES_WARNING \
    Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); \
    QT_WARNING_POP \
    struct QPrivateSignal {}; \
    QT_ANNOTATE_CLASS(qt_qobject, "")

  从定义中可以看出该宏的作用有两个:一是对与自己相关的QMetaObject中间类操作进行声明,另一个是对信号的释放操作和反应槽的激活操作进行声明。当moc.exe对头文件进行预编译之后,将会产生一个可供C++编译器编译的源文件。以上述的Demo类为例,假设它的代码文件分别为demo.h和demo.cpp,预编译后将产生moc_demo.cpp,其主要内容如下:

QMetaObject *Demo::metaObj = 0;
void Demo::initMetaObject()
{
    if ( metaObj )
        return;
    if ( strcmp(QObject::className(), "QObject") != 0 )
        badSuperclassWarning("Demo","QObject");
    (void) staticMetaObject();
}

QMetaObject* Demo::staticMetaObject()
{
    if ( metaObj )
        return metaObj;
    (void) QObject::staticMetaObject();
    typedef void(Demo::*m1_t0)(int);
    m1_t0 v1_0 = Q_AMPERSAND Demo::setValue;
    QMetaData *slot_tbl = QMetaObject::new_metadata(1);
   
    QMetaData::Access *slot_tbl_access = QMetaObject::new_metaaccess(1);
    slot_tbl[0].name = "setValue(int)";
    slot_tbl[0].ptr = *((QMember*)&v1_0);
   
    slot_tbl_access[0] = QMetaData::Public;
    typedef void(Demo::*m2_t0)(int);
    m2_t0 v2_0 = Q_AMPERSAND Demo::valueChanged;
    QMetaData *signal_tbl = QMetaObject::new_metadata(1);
    signal_tbl[0].name = "valueChanged(int)";
    signal_tbl[0].ptr = *((QMember*)&v2_0);
   
    metaObj = QMetaObject::new_metaobject(
   
    "Demo", "QObject",
    slot_tbl, 1,
    signal_tbl, 1,
    0, 0 );
    metaObj->set_slot_access( slot_tbl_access );
    return metaObj;
}
// 有信号时即激活对应的反应槽或另一个信号
void Demo::valueChanged( int t0 )
{
    activate_signal( "valueChanged(int)", t0 );
}

4 disconnect()

  disconnect()有3种用法,其原型如下:bool QObject::disconnect(const QObject * sender, const char * signal, const QObject * receiver, const char * method)
  1、断开与myObject对象的信号与其他对象间的连接,使用后myObject发出的信号没有对应的槽函数进行响应。

disconnect(myObject, 0, 0, 0);
// or
myObject->disconnect();

  2、断开与myObject对象的mySignal()信号与其他对象间的连接,使用后myObject发出mySignal()信号没有对应的槽函数进行响应。

disconnect(myObject, SIGNAL(mySignal()), 0, 0);
// or
myObject->disconnect(SIGNAL(mySignal()));

  3、断开与myObject对象与myReceiver对象间的连接,使用后myObject发出mySignal()信号myReceiver对应的槽函数进行响应。

disconnect(myObject, 0, myReceiver, 0);
// or
myObject->disconnect(myReceiver);

  注意:0表示任意的信号或者接收者对象;const QObject * sender不能是0。

三 使用多线程的两种方法(子类化QThread+子类化QObject)

  本节转载于这儿
  Qt有两种多线程的方法,其中一种是继承QThread的run函数,另外一种是把一个继承于QObject的类转移到一个Thread里。
  Qt4.8之前都是使用继承QThread的run这种方法,但是Qt4.8之后,Qt官方建议使用第二种方法。两种方法区别不大,用起来都比较方便,但继承QObject的方法更加灵活。这里要记录的是如何正确的创建一个线程,特别是如何正确的退出一个线程。

四 QTcpSocket对连接服务器中断的不同情况进行判定

本节内容来自这里

1 简述

  对于一个C/S结构的程序,客户端有些时候需要实时得知与服务器的连接状态。而对于客户端与服务器断开连接的因素很多,现在就目前遇到的情况进行一下总结。

2 分为下面六种不同情况

  • 客户端网线断开
  • 客户端网络断开
  • 客户端通过HTTP代理连接服务器,代理机器断开代理
  • 客户端通过HTTP代理连接服务器,代理机器的网络断开
  • 客户端通过HTTP代理连接服务器,代理机器的网线断开
  • 服务器断开

  同时对于以上六种情况又分为连接服务器之前和连接上服务器之后,下面就分别对不同的情况进行分析。

3 开始连接服务器之前

  1、 客户端网线断开
  此时用socket调用connectToHost方法连接服务器会立即触发QTcpSocket的error信号,我们可以绑定相应的槽去处理连接失败的结果。
  2、 客户端网络断开
  3、 客户端通过HTTP代理连接服务器,代理机器断开代理
  4、 客户端通过HTTP代理连接服务器,代理机器的网络断开
  5、客户端通过HTTP代理连接服务器,代理机器的网线断开
  6、 服务器断开
  此时用socket调用connectToHost方法连接服务器并不会立即触发QTcpSocket的error信号,而是经过40s+的连接等待超时发出error信号,见下图。
在这里插入图片描述

4 已经连接上服务器

  1、 客户端网线断开
  此时socket不会发送error信号,也不会发送disconnect信号,查询资料是因为网线断开是属于物理链路层,tcp无法察觉到,socket仍处于连接状态。
  2、 客户端网络断开
  3、 客户端通过HTTP代理连接服务器,代理机器断开代理
  4、 客户端通过HTTP代理连接服务器,代理机器的网络断开
  5、客户端通过HTTP代理连接服务器,代理机器的网线断开
  第二和第三种情况下会立即触发error信号,而第四和第五种情况下会等待30s左右会发送error信号。
  6、 服务器断开
  此时socket会发送disconnect信号,可以绑定相应的槽去处理服务器断开的情况。

5 检测与服务器断开的另外方法

  对于有些程序(客户端)需要立即知道与服务端连接状态,而不是等待几十秒之后才有信号通知到或者根本就检测不出与服务器断开,除了利用QTcpSocket提供的信号(有几种情况不会发出信号或发出信号延迟),这里列出另外几种处理方法。
  1、发送心跳包,即客户端每隔一段时间发送一条报文,报文不需附带具体内容,只需要让服务端知道这是一条心跳报文,并回发一条消息,客户端收到这条消息后就得知与服务器保持连接的状态。
  2、检测本地网络,定义一个时钟,每次timeout去检测本地的网络,关于怎么判断本地网络是否通畅呢?可以用windows提供的IsNetworkAlive方法,返回为false为网络异常。加上头文件为#include “Sensapi.h”。同时需要包含Sensapi.lib。(通过IsNetworkAlive方法判断本地网络,在客户端已经连接上服务器,并且禁用网络时会立即发送error信号,在error信号绑定的槽中去调用这个方法发现返回值为true,因为这种情况下禁用网络后会立即发送error信号,调用IsNetworkAlive方法时可能立即检测不到网络异常。如果通过断点的方式,在调用IsNetworkAlive时就会返回false)

DWORD dwFlag;
if (FALSE == IsNetworkAlive(&dwFlag))
{
    qDebug() << "NetWorkError";
}

  注意:
  但是这种方法,在本地存在虚拟机并且虚拟机开启时会失效,因为IsNetworkAlive会检测本地所有的网络,在网线断开后,可能检测到虚拟机网络正常,导致返回ture。
  3、如果有自己的服务器就ping服务器(前提服务器不会挂),否则就ping一个相对可靠的IP (比如百度),通过看他ping的结果怎么样。
  同时在C++ 实现 ping 功能&& 域名(URL)解析实际 IP地址这篇博客中用C++实现了 ping的 功能,有兴趣的小伙伴可以看一看,了解一下。

QProcess *cmd = new QProcess;
cmd->start("ping www.baidu.com");
// 等待ping 的结果
while (cmd->waitForFinished())
{
    QString result = QString::fromLocal8Bit(cmd->readAll());
    qDebug() << result;
}

  或

QHostInfo::lookupHost("www.baidu.com", this, SLOT(lookedUp(QHostInfo)));

void lookedUp(QHostInfo &host)
{
     qDebug() << host.addresses().first().toString();
}
//得到IP 地址 就是在互联网上 如果不能得到 就不行

  4、QNetworkConfigurationManager::isOnline()。
当然这个只能检查你是否有网络链接,而不能检测你是否连接到互联网。
  
  在qt中,当连接异常断开时,会触发相应的信号,我们只要在这个信号对应的槽函数中做重连处理就可以了,不需要另开线程也不需要心跳包。
  对于需要自动重连的客户端可以通过以上方法,在判断出与服务器断开后可以重新连接,或者通过超时定时器进行重连,方法很多,在于尝试。
  
  在用QT写服务端时想要知道客户端是否断开连接,百度一下没有找到方法,看了下帮助文档,得到以下方法,实测可用,如有更好的还请告知

enum SocketError {  
        ConnectionRefusedError,  
        RemoteHostClosedError,//客户端断开时出现这个错误  
        HostNotFoundError,  
        SocketAccessError,  
        SocketResourceError,  
        SocketTimeoutError,                     /* 5 */  
        DatagramTooLargeError,  
        NetworkError,  
        AddressInUseError,  
        SocketAddressNotAvailableError,  
        UnsupportedSocketOperationError,        /* 10 */  
        UnfinishedSocketOperationError,  
        ProxyAuthenticationRequiredError,  
        SslHandshakeFailedError,  
        ProxyConnectionRefusedError,  
        ProxyConnectionClosedError,             /* 15 */  
        ProxyConnectionTimeoutError,  
        ProxyNotFoundError,  
        ProxyProtocolError,  
        OperationError,  
        SslInternalError,                       /* 20 */  
        SslInvalidUserDataError,  
        TemporaryError,  
        UnknownSocketError = -1  
    };  

QAbstractSocket::RemoteHostClosedError 1 The remote host closed the connection. Note that the client socket (i.e., this socket) will be closed after the remote close notification has been sent
远程主机关闭连接,注解:client socket 将送出通知后关闭

QAbstractSocket::error() const
Returns the type of error that last occurred.
返回最后出现的错误类型

下面为实现代码

connect(serverConnect, SIGNAL(error(QAbstractSocket::SocketError)),  
                        this, SLOT(MSGError(QAbstractSocket::SocketError)));//建立槽函数,获取错误信息  
  
void DataDisplay::MSGError(QAbstractSocket::SocketError)  
{  
    error = serverConnect->error();  
    switch(error)  
    {  
    case QAbstractSocket::RemoteHostClosedError://客户端断开  
    {  
        QString hostAddress=serverConnect->QAbstractSocket::peerAddress().toString();  
        ui->m_display->insertPlainText(tr("客户端[%1]断开连接\r\n").arg(hostAddress));  
        break;  
    }  
    default:  
    {  
        error = -1;  
        QMessageBox::information(this, "show", serverConnect->errorString());  
        break;  
    }  
    }  
}
  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值