最近一直在学习SSL相关知识,也明白了理论相关知识,主要SSL基本概念和连接建立。主要依据PolarSSL开源库学习。学习完了之后就希望能给有所运用,就想用Qt写一个简单的程序,添加对SSL相关概念的把握和对PolarSSL库的运用。当然,终于希望是能够使用Qt做一个比較完好的工具,帮助大家更好的理解和学习SSL相关知识。这都是后话,在第一篇里面,我们就简单用样例展示怎样在Qt里面调用PolarSSL库。

这篇博客主要是解说Qt里面调用PolarSSL库,至于SSL相关概念在后面的博客再具体介绍。

SSL握手须要客户端和服务器端交互。这里我们分别介绍。

1、编译PolarSSL库

我们准备使用的方式就是编译PolarSSL为.a静态库,然后在Qt中连接,使用的PolarSSL的版本号是0.10.1。

下载相应的软件版本号。解压缩后在library文件夹下执行make就可以生成libpolarssl.a库文件,例如以下图:

2、服务器端

使用Qt设计一个简单的界面。在button的槽函数中进行相关的操作。也就是调用PolarSSL库函数进行编程。初始化ssl相关结构体,监听port。等等。

SSL中最重要的就是执行握手操作。这里须要注意一点,因为涉及到socket编程。像accept函数都是堵塞的。假设在gui主线程中调用会造成界面冻结,也就是我们常说的ANR。解决方法就是将这些操作放在一个线程中,Qt中创建一个线程比較easy。创建一个类,继承自QThread,实现run函数。就可以。最后启动线程也比較简单,调用该类的start()

函数就可以。

好了,不多说了。上代码。首先看看服务器端的代码结构:workThread即是线程。实现SSL相关的功能,监听套接字,实现SSL握手,读取客户端发来的消息,向客户端发送消息。

mianwindow即是主窗体界面,有个button,在button的槽函数中启动线程

代码:

project文件:

#-------------------------------------------------
#
# Project created by QtCreator 2014-05-11T22:28:07
#
#------------------------------------------------- QT += core gui greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = MyPolarSSLToolSrv
TEMPLATE = app INCLUDEPATH += /home/chenlong12580/develop/polarTool/polarssl/include
LIBS += -L "/home/chenlong12580/develop/polarTool/polarssl/lib/" -lpolarssl SOURCES += main.cpp\
widget.cpp \
workthread.cpp HEADERS += widget.h \
workthread.h FORMS += widget.ui

线程类:

void WorkThread::run()
{
qDebug() << "I am a thread!"; int listen_fd = 0;
int client_fd =0;
int ret= 0;
havege_state hs;
ssl_context ssl;
ssl_session ssn;
x509_cert srvcert;
rsa_context rsa;
unsigned char buf[1024];
int len = 0; memset( &srvcert, 0, sizeof( x509_cert ) ); ret = x509parse_crt( &srvcert, (unsigned char *) test_srv_crt,
strlen( test_srv_crt ) );
if( ret != 0 )
{
printf( " failed\n ! x509parse_crt returned %d\n\n", ret );
return;
} ret = x509parse_crt( &srvcert, (unsigned char *) test_ca_crt,
strlen( test_ca_crt ) );
if( ret != 0 )
{
printf( " failed\n ! x509parse_crt returned %d\n\n", ret );
return;
} ret = x509parse_key( &rsa, (unsigned char *) test_srv_key,
strlen( test_srv_key ), NULL, 0 );
if( ret != 0 )
{
printf( " failed\n ! x509parse_key returned %d\n\n", ret );
return;
} ret = net_bind( &listen_fd, NULL, 8443 );
if (0 != ret)
{
qDebug() << ret;
return;
} qDebug() << "bind ok"; /* socket is block */
ret = net_accept( listen_fd, &client_fd, NULL );
if (0 != ret)
{
return;
} qDebug() << "accept ok"; havege_init( &hs ); ret = ssl_init( &ssl );
if (0 != ret)
{
return;
} ssl_set_endpoint( &ssl, SSL_IS_SERVER );
ssl_set_authmode( &ssl, SSL_VERIFY_NONE ); ssl_set_rng( &ssl, havege_rand, &hs );
ssl_set_dbg( &ssl, my_debug, stdout );
ssl_set_bio( &ssl, net_recv, &client_fd,
net_send, &client_fd );
ssl_set_scb( &ssl, my_get_session,
my_set_session ); ssl_set_ciphers( &ssl, my_ciphers );
ssl_set_session( &ssl, 1, 0, &ssn ); memset( &ssn, 0, sizeof( ssl_session ) ); ssl_set_ca_chain( &ssl, srvcert.next, NULL );
ssl_set_own_cert( &ssl, &srvcert, &rsa );
ssl_set_dh_param( &ssl, my_dhm_P, my_dhm_G ); qDebug() << "before ssl_handshake ok"; while( ( ret = ssl_handshake( &ssl ) ) != 0 )
{
;
} qDebug() << "ssl_handshake ok"; do
{
len = sizeof( buf ) - 1;
memset( buf, 0, sizeof( buf ) );
ret = ssl_read( &ssl, buf, len ); if( ret == POLARSSL_ERR_NET_TRY_AGAIN )
continue; if( ret <= 0 )
{
switch( ret )
{
case POLARSSL_ERR_SSL_PEER_CLOSE_NOTIFY:
printf( " connection was closed gracefully\n" );
break; case POLARSSL_ERR_NET_CONN_RESET:
printf( " connection was reset by peer\n" );
break; default:
printf( " ssl_read returned %d\n", ret );
break;
} break;
} len = ret;
printf( " %d bytes read\n\n%s", len, (char *) buf );
}while( 0 ); char *cc = (char *)buf;
QString ss(cc); qDebug() << ss; (void)sprintf( (char *) buf, HTTP_RESPONSE,
ssl_get_cipher( &ssl ) ); while( ( ret = ssl_write( &ssl, buf, len ) ) <= 0 )
{
if( ret == POLARSSL_ERR_NET_CONN_RESET )
{
printf( " failed\n ! peer closed the connection\n\n" );
return;
} if( ret != POLARSSL_ERR_NET_TRY_AGAIN )
{
printf( " failed\n ! ssl_write returned %d\n\n", ret );
return;
}
} ssl_close_notify( &ssl ); net_close( client_fd );
x509_free( &srvcert );
rsa_free( &rsa );
ssl_free( &ssl ); cur = s_list_1st;
while( cur != NULL )
{
prv = cur;
cur = cur->next;
memset( prv, 0, sizeof( ssl_session ) );
free( prv );
} memset( &ssl, 0, sizeof( ssl_context ) );
}

主窗体类:

#include "widget.h"
#include "ui_widget.h"
#include <QFileDialog>
#include <QDebug>
#include <QMessageBox>
#include "workthread.h"
#include "polarssl/havege.h"
#include "polarssl/certs.h"
#include "polarssl/x509.h"
#include "polarssl/ssl.h"
#include "polarssl/net.h" Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this); qDebug() << "server"; connect(this, SIGNAL(emit_parse_cer()), this, SLOT(slot_parse_cer()));
} Widget::~Widget()
{
delete ui;
} void Widget::on_BrowseBtn_clicked()
{
QString pathStr = QFileDialog::getOpenFileName(this, QString("选择证书文件"), QString("C:\\Users\\Administrator\\Desktop"), QString("*.*"));
if (pathStr.length() == 0)
{
qDebug() << "please select a cer file!";
return;
} ui->PathEdit->setText(pathStr); emit emit_parse_cer();
} void Widget::slot_parse_cer()
{
x509_cert crt;
memset(&crt, 0, sizeof(crt)); int res = x509parse_crtfile( &crt, ui->PathEdit->text().toLatin1().data());
if (0 != res)
{
QMessageBox::warning(this, "警告", "解析证书失败,请选择正确的证书文件", QMessageBox::Ok);
return;
} ui->CrtInfo->setText(QString("是否为根证书:") + QString::number(crt.ca_istrue));
ui->CrtInfo->append(QString("证书版本号号:") + QString::number(crt.version));
ui->CrtInfo->append(QString("有效期:") + QString::number(crt.valid_from.year) + "-" + QString::number(crt.valid_from.mon)
+ QString(" 到:") + QString::number(crt.valid_to.year) + "-" + QString::number(crt.valid_to.mon)); qDebug() << crt.ca_istrue;
qDebug() << crt.valid_from.year;
} void Widget::on_pushButton_clicked()
{
WorkThread *workThread = new WorkThread;
workThread->start();
}

3、客户端

客户端比較简单,直接在界面类进行的SSL功能相关的实现,就是创建套接字,链接服务器,进行SSL握手,向服务器发消息,读取服务器发来的消息。

project文件:

#-------------------------------------------------
#
# Project created by QtCreator 2014-05-11T22:28:07
#
#------------------------------------------------- QT += core gui greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = MyPolarSSLToolCli
TEMPLATE = app INCLUDEPATH += /home/chenlong12580/develop/polarTool/polarssl/include
LIBS += -L "/home/chenlong12580/develop/polarTool/polarssl/lib/" -lpolarssl SOURCES += main.cpp\
widget.cpp HEADERS += widget.h FORMS += widget.ui

主窗体类:

#include "widget.h"
#include "ui_widget.h"
#include <QFileDialog>
#include <QDebug>
#include <QMessageBox>
#include "polarssl/havege.h"
#include "polarssl/certs.h"
#include "polarssl/x509.h"
#include "polarssl/ssl.h"
#include "polarssl/net.h" #define SERVER_PORT 8443
/*
#define SERVER_NAME "localhost"
#define GET_REQUEST "GET / HTTP/1.0\r\n\r\n"
*/
#define SERVER_NAME "polarssl.org"
#define GET_REQUEST \
"GET /hello/ HTTP/1.1\r\n" \
"Host: polarssl.org\r\n\r\n" #define DEBUG_LEVEL 0 void my_debug( void *ctx, int level, char *str )
{
if( level < DEBUG_LEVEL )
{
fprintf( (FILE *) ctx, "%s", str );
fflush( (FILE *) ctx );
}
} Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this); qDebug() << "client"; connect(this, SIGNAL(emit_parse_cer()), this, SLOT(slot_parse_cer()));
} Widget::~Widget()
{
delete ui;
} void Widget::on_BrowseBtn_clicked()
{
QString pathStr = QFileDialog::getOpenFileName(this, QString("选择证书文件"), QString("C:\\Users\\Administrator\\Desktop"), QString("*.*"));
if (pathStr.length() == 0)
{
qDebug() << "please select a cer file!";
return;
} ui->PathEdit->setText(pathStr); emit emit_parse_cer();
} void Widget::slot_parse_cer()
{
x509_cert crt;
memset(&crt, 0, sizeof(crt)); int res = x509parse_crtfile( &crt, ui->PathEdit->text().toLatin1().data());
if (0 != res)
{
QMessageBox::warning(this, "警告", "解析证书失败,请选择正确的证书文件", QMessageBox::Ok);
return;
} ui->CrtInfo->setText(QString("是否为根证书:") + QString::number(crt.ca_istrue));
ui->CrtInfo->append(QString("证书版本号号:") + QString::number(crt.version));
ui->CrtInfo->append(QString("有效期:") + QString::number(crt.valid_from.year) + "-" + QString::number(crt.valid_from.mon)
+ QString(" 到:") + QString::number(crt.valid_to.year) + "-" + QString::number(crt.valid_to.mon)); qDebug() << crt.ca_istrue;
qDebug() << crt.valid_from.year;
} void Widget::on_pushButton_clicked()
{
int ret, len, server_fd;
unsigned char buf[1024];
havege_state hs;
ssl_context ssl;
ssl_session ssn; /*
* 0. Initialize the RNG and the session data
*/
havege_init( &hs );
memset( &ssn, 0, sizeof( ssl_session ) ); /*
* 1. Start the connection
*/
printf( "\n . Connecting to tcp/%s/%4d...", SERVER_NAME,
SERVER_PORT );
fflush( stdout ); if( ( ret = net_connect( &server_fd, "127.0.0.1",
SERVER_PORT ) ) != 0 )
{
printf( " failed\n ! net_connect returned %d\n\n", ret );
return;
} printf( " ok\n" ); /*
* 2. Setup stuff
*/
printf( " . Setting up the SSL/TLS structure..." );
fflush( stdout ); if( ( ret = ssl_init( &ssl ) ) != 0 )
{
printf( " failed\n ! ssl_init returned %d\n\n", ret );
return;
} printf( " ok\n" ); ssl_set_endpoint( &ssl, SSL_IS_CLIENT );
ssl_set_authmode( &ssl, SSL_VERIFY_NONE ); ssl_set_rng( &ssl, havege_rand, &hs );
ssl_set_dbg( &ssl, my_debug, stdout );
ssl_set_bio( &ssl, net_recv, &server_fd,
net_send, &server_fd ); ssl_set_ciphers( &ssl, ssl_default_ciphers );
ssl_set_session( &ssl, 1, 600, &ssn ); /*
* 3. Write the GET request
*/
printf( " > Write to server:" );
fflush( stdout ); len = sprintf( (char *) buf, GET_REQUEST ); while( ( ret = ssl_write( &ssl, buf, len ) ) <= 0 )
{
if( ret != POLARSSL_ERR_NET_TRY_AGAIN )
{
printf( " failed\n ! ssl_write returned %d\n\n", ret );
return;
}
} len = ret;
printf( " %d bytes written\n\n%s", len, (char *) buf ); /*
* 7. Read the HTTP response
*/
printf( " < Read from server:" );
fflush( stdout ); do
{
len = sizeof( buf ) - 1;
memset( buf, 0, sizeof( buf ) );
ret = ssl_read( &ssl, buf, len ); if( ret == POLARSSL_ERR_NET_TRY_AGAIN )
continue; if( ret == POLARSSL_ERR_SSL_PEER_CLOSE_NOTIFY )
break; if( ret <= 0 )
{
printf( "failed\n ! ssl_read returned %d\n\n", ret );
break;
} len = ret;
printf( " %d bytes read\n\n%s", len, (char *) buf );
}
while( 0 ); char *cc= (char *)buf;
QString ss(cc); qDebug() << ss; ssl_close_notify( &ssl ); return;
}

4、执行效果

以下说说执行的效果,首先启动服务器端。服务器端启动线程,监听套接字:

接着启动客户端。客户端链接服务器端,写入消息,读取服务器端的响应:

最后依据打印能够看出服务器端和客户端握手成功。写入读取消息成功:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvY2hlbmxvbmcxMjU4MA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" />

我们能够依据抓包来看看SSL握手的过程。例如以下:

通过抓包能够看到服务器端和客户端的握手过程,以及在不同的握手阶段中做得事情:

服务器端:

/*
* SSL handshake -- server side
*/
int ssl_handshake_server( ssl_context *ssl )
{
int ret = 0; SSL_DEBUG_MSG( 2, ( "=> handshake server" ) ); while( ssl->state != SSL_HANDSHAKE_OVER )
{
SSL_DEBUG_MSG( 2, ( "server state: %d", ssl->state ) ); if( ( ret = ssl_flush_output( ssl ) ) != 0 )
break; switch( ssl->state )
{
case SSL_HELLO_REQUEST:
ssl->state = SSL_CLIENT_HELLO;
break; /*
* <== ClientHello
*/
case SSL_CLIENT_HELLO:
ret = ssl_parse_client_hello( ssl );
break; /*
* ==> ServerHello
* Certificate
* ( ServerKeyExchange )
* ( CertificateRequest )
* ServerHelloDone
*/
case SSL_SERVER_HELLO:
ret = ssl_write_server_hello( ssl );
break; case SSL_SERVER_CERTIFICATE:
ret = ssl_write_certificate( ssl );
break; case SSL_SERVER_KEY_EXCHANGE:
ret = ssl_write_server_key_exchange( ssl );
break; case SSL_CERTIFICATE_REQUEST:
ret = ssl_write_certificate_request( ssl );
break; case SSL_SERVER_HELLO_DONE:
ret = ssl_write_server_hello_done( ssl );
break; /*
* <== ( Certificate/Alert )
* ClientKeyExchange
* ( CertificateVerify )
* ChangeCipherSpec
* Finished
*/
case SSL_CLIENT_CERTIFICATE:
ret = ssl_parse_certificate( ssl );
break; case SSL_CLIENT_KEY_EXCHANGE:
ret = ssl_parse_client_key_exchange( ssl );
break; case SSL_CERTIFICATE_VERIFY:
ret = ssl_parse_certificate_verify( ssl );
break; case SSL_CLIENT_CHANGE_CIPHER_SPEC:
ret = ssl_parse_change_cipher_spec( ssl );
break; case SSL_CLIENT_FINISHED:
ret = ssl_parse_finished( ssl );
break; /*
* ==> ChangeCipherSpec
* Finished
*/
case SSL_SERVER_CHANGE_CIPHER_SPEC:
ret = ssl_write_change_cipher_spec( ssl );
break; case SSL_SERVER_FINISHED:
ret = ssl_write_finished( ssl );
break; case SSL_FLUSH_BUFFERS:
SSL_DEBUG_MSG( 2, ( "handshake: done" ) );
ssl->state = SSL_HANDSHAKE_OVER;
break; default:
SSL_DEBUG_MSG( 1, ( "invalid state %d", ssl->state ) );
return( POLARSSL_ERR_SSL_BAD_INPUT_DATA );
} if( ret != 0 )
break;
} SSL_DEBUG_MSG( 2, ( "<= handshake server" ) ); return( ret );
}

客户端:

/*
* SSL handshake -- client side
*/
int ssl_handshake_client( ssl_context *ssl )
{
int ret = 0; SSL_DEBUG_MSG( 2, ( "=> handshake client" ) ); while( ssl->state != SSL_HANDSHAKE_OVER )
{
SSL_DEBUG_MSG( 2, ( "client state: %d", ssl->state ) ); if( ( ret = ssl_flush_output( ssl ) ) != 0 )
break; switch( ssl->state )
{
case SSL_HELLO_REQUEST:
ssl->state = SSL_CLIENT_HELLO;
break; /*
* ==> ClientHello
*/
case SSL_CLIENT_HELLO:
ret = ssl_write_client_hello( ssl );
break; /*
* <== ServerHello
* Certificate
* ( ServerKeyExchange )
* ( CertificateRequest )
* ServerHelloDone
*/
case SSL_SERVER_HELLO:
ret = ssl_parse_server_hello( ssl );
break; case SSL_SERVER_CERTIFICATE:
ret = ssl_parse_certificate( ssl );
break; case SSL_SERVER_KEY_EXCHANGE:
ret = ssl_parse_server_key_exchange( ssl );
break; case SSL_CERTIFICATE_REQUEST:
ret = ssl_parse_certificate_request( ssl );
break; case SSL_SERVER_HELLO_DONE:
ret = ssl_parse_server_hello_done( ssl );
break; /*
* ==> ( Certificate/Alert )
* ClientKeyExchange
* ( CertificateVerify )
* ChangeCipherSpec
* Finished
*/
case SSL_CLIENT_CERTIFICATE:
ret = ssl_write_certificate( ssl );
break; case SSL_CLIENT_KEY_EXCHANGE:
ret = ssl_write_client_key_exchange( ssl );
break; case SSL_CERTIFICATE_VERIFY:
ret = ssl_write_certificate_verify( ssl );
break; case SSL_CLIENT_CHANGE_CIPHER_SPEC:
ret = ssl_write_change_cipher_spec( ssl );
break; case SSL_CLIENT_FINISHED:
ret = ssl_write_finished( ssl );
break; /*
* <== ChangeCipherSpec
* Finished
*/
case SSL_SERVER_CHANGE_CIPHER_SPEC:
ret = ssl_parse_change_cipher_spec( ssl );
break; case SSL_SERVER_FINISHED:
ret = ssl_parse_finished( ssl );
break; case SSL_FLUSH_BUFFERS:
SSL_DEBUG_MSG( 2, ( "handshake: done" ) );
ssl->state = SSL_HANDSHAKE_OVER;
break; default:
SSL_DEBUG_MSG( 1, ( "invalid state %d", ssl->state ) );
return( POLARSSL_ERR_SSL_BAD_INPUT_DATA );
} if( ret != 0 )
break;
} SSL_DEBUG_MSG( 2, ( "<= handshake client" ) ); return( ret );
}

版权声明:本文博客原创文章。博客,未经同意,不得转载。

Qt调用PolarSSL库(一个)的更多相关文章

  1. Qt中调用PolarSSL库(一)

    最近一直在学习SSL相关的知识,也是先了解理论相关的知识,主要是SSL相关的基本概念和连接建立过程,主要是基于PolarSSL开源库进行学习.学习完了之后就希望能给有所运用,就想用Qt写一个简单的程序 ...

  2. Linux下Qt调用共享库文件.so

    修改已有的pro文件,添加如下几句: INCLUDEPATH += /home/ubuntu/camera/camera/LIBS += -L/home/ubuntu/camera/camera -l ...

  3. qt调用仪器驱动库dll实现程控

    在<使用qt+visa实现程控>中实现了qt调用visa库的简单Demo本文将尝试使用qt调用仪器驱动库来实现对仪器仪表的程控 开发环境 系统: windows 10 环境: qt 5.8 ...

  4. QT 调用VS2015编写的Dll

    最近在用QT调用VC生成的库,QT使用的是MinGW调试器,出现与动态库不兼容的问题,最后发现QT只能识别VC生成的C格式下的动态库 也就是在导入导出设置时加入extern "C" ...

  5. Qt打开外部程序和文件夹需要注意的细节(Qt调用VC写的动态库,VC需要用C的方式输出函数,否则MinGW32编译过程会报错)

    下午写程序中遇到几个小细节,需要在这里记录一下. ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 QProcess *process = new QProcess(this ...

  6. Qt生成和调用动态库dll,和静态库.a(windows和linux通用)

    系统1:ThinkPad T570.Windows10.QT5.12.2(Qt Creater 4.8.2)一.动态库.dll的创建和调用1.在qtcreater中按如下步骤创建动态库,动态库名为my ...

  7. windows下Qt Creator5.1.0编写程序以及调用OpenCV库

    系统说明 最近使用opencv编写程序,程序编的差不多就学习使用QT加个界面,首先声明下本人的系统和使用的软件版本, 系统: windows xp QT IDE:QT Creator5.1.0 Ope ...

  8. 【Qt官方MQTT库的使用,附一个MqttClient例子】

    Qt官方MQTT库的使用,附一个MqttClient例子 开发环境:win7 64 + Qt5.9 记录时间:2018年3月11日 00:48:42 联系邮箱: yexiaopeng1992@126. ...

  9. 一个简单的C共享库的创建及Python调用此库的方法

    /********************************************************************* * Author  : Samson * Date    ...

随机推荐

  1. rabbitMQ服务安装

    1.rabbitMQ简介 1.1.rabbitMQ的优点(适用范围)1. 基于erlang语言开发具有高可用高并发的优点,适合集群服务器.2. 健壮.稳定.易用.跨平台.支持多种语言.文档齐全.3. ...

  2. [React] Use Jest's Snapshot Testing Feature

    Often when testing, you use the actual result to create your assertion and have to manually update i ...

  3. Python爬虫突破封禁的6种常见方法

    转 Python爬虫突破封禁的6种常见方法 2016年08月17日 22:36:59 阅读数:37936 在互联网上进行自动数据采集(抓取)这件事和互联网存在的时间差不多一样长.今天大众好像更倾向于用 ...

  4. 使用Delegate在两个ViewController之间传值

    以下就实现了使用Delegate在两个ViewController之间传值,这种场景一般应用在进入子界面输入信息,完后要把输入的信息回传给前一个界面的情况,比如修改用户个人信息,点击修改进入修改界面, ...

  5. java phoenix 连接hbase

    <dependency> <groupId>org.apache.phoenix</groupId> <artifactId>phoenix-core& ...

  6. erlang连接mysql

    http://blog.csdn.net/flyinmind/article/details/7740540 项目中用到erlang,同时也用到mysql.惯例,google. 但是,按照网上说的做, ...

  7. Cocos2d-x 3.1.1 Lua演示样例 ActionManagerTest(动作管理)

    Cocos2d-x 3.1.1 Lua演示样例 ActionManagerTest(动作管理) 本篇博客介绍Cocos2d-x的动作管理样例,这个样例展示了Cocos2d-x的几个动作: MoveTo ...

  8. Qt 元对象系统(Meta-Object System)(不管是否使用信号槽,都推荐使用)

    Qt 元对象系统(Meta-Object System) Qt的元对象系统基于如下三件事情: 类:QObject,为所有需要利用原对象系统的对象提供了一个基类. 宏:Q_OBJECT,通常可以声明在类 ...

  9. React事件处理函数传参问题

    React事件处理函数参数 HTML标签与React 组件是不同的,事件对象e是HTML标签元素的,组件没有的.

  10. docker-windows随笔资料整理

    背景 业务需求:优化机器学习,IOT边缘计算性能,替换现有的虚拟机部署方案. 技术背景: .netcore python Docker 学习资料 Docker中文社区: http://www.dock ...