首页
归档
友情链接
关于
Search
1
在wsl2中安装archlinux
159 阅读
2
nvim番外之将配置的插件管理器更新为lazy
107 阅读
3
2018总结与2019规划
76 阅读
4
从零开始配置 vim(15)——状态栏配置
75 阅读
5
PDF标准详解(五)——图形状态
51 阅读
软件与环境配置
博客搭建
从0开始配置vim
Vim 从嫌弃到依赖
linux
Emacs
MySQL
Git与Github
AndroidStudio
cmake
读书笔记
编程
PDF 标准
从0自制解释器
qt
C/C++语言
Windows 编程
Python
Java
算法与数据结构
PE结构
Thinking
FIRE
菜谱
登录
Search
标签搜索
c++
c
学习笔记
windows
文本操作术
编辑器
NeoVim
Vim
win32
VimScript
emacs
linux
elisp
读书笔记
文本编辑器
Java
反汇编
OLEDB
数据库编程
数据结构
Masimaro
累计撰写
338
篇文章
累计收到
32
条评论
首页
栏目
软件与环境配置
博客搭建
从0开始配置vim
Vim 从嫌弃到依赖
linux
Emacs
MySQL
Git与Github
AndroidStudio
cmake
读书笔记
编程
PDF 标准
从0自制解释器
qt
C/C++语言
Windows 编程
Python
Java
算法与数据结构
PE结构
Thinking
FIRE
菜谱
页面
归档
友情链接
关于
搜索到
338
篇与
的结果
2018-01-28
SQL语句执行与结果集的获取
上次说到命令对象是用来执行SQL语句的。数据源在执行完SQL语句后会返回一个结果集对象,将SQL执行的结果返回到结果集对象中,应用程序在执行完SQL语句后,解析结果集对象中的结果,得到具体的结果,这次的主要内容是如何解析结果集对象并获取其中的值。如何执行SQL语句执行SQL语句一般的步骤如下:创建ICommandText接口.使用ICommandText接口的SetCommandText方法设置SQL命令使用ICommandText接口的Excute方法执行SQL语句并接受返回的结果集对象,这个结果集对象一般是IRowset.其实OLEDB并不一定非要传入SQL语句,他可以传入简单的命令,只要数据源能够识别,也就是说我们可以根据数据源的不同传入那些只有特定数据源才会支持的命令,已达到简化操作或者实现某些特定功能的目的.针对有的SQL语句,我们并不是那么关心它返回了那些数据,比如说Delete语句,insert语句,针对这种情况我们可以将对应返回结果集的参数设置为NULL,比如像下面这样pICommandText->Execute(NULL, IID_NULL, NULL, NULL, NULL)明确告诉数据源程序不需要返回结果集,这样数据源不会准备结果集,减少了数据源的相关操作,从某种程度上减轻了数据源的负担。设置command对象的属性与之前数据源对象和会话对象的属性不同,command对象的属性是作用在返回的数据源对象上的,比如我们没有设置对应的更新属性,那么数据源就不允许我们使用结果集进行更新数据的操作。这些属性必须在执行SQL语句得到结果集的操作之前定义好。因为在获得数据源返回的结果集的时候数据源已经设置了对应的属性。command对象的属性集ID是PROPSET_ROWSET.该属性集中有很多能够影响结果集对象的属性。下面是一个执行SQL语句的例子: LPOLESTR lpSql = OLESTR("select * from aa26"); CreateDBSession(pIOpenRowset); HRESULT hRes = pIOpenRowset->QueryInterface(IID_IDBCreateCommand, (void**)&pIDBCreateCommand); COM_SUCCESS(hRes, _T("查询接口IDBCreateCommand失败,错误码:%08x\n"), hRes); hRes = pIDBCreateCommand->CreateCommand(NULL, IID_ICommandText, (IUnknown**)&pICommandText); COM_SUCCESS(hRes, _T("创建接口IDBCreateCommand失败,错误码:%08x\n"), hRes); hRes = pICommandText->SetCommandText(DBGUID_DEFAULT, lpSql); COM_SUCCESS(hRes, _T("设置sql语句失败,错误码:%08x\n"), hRes); DBPROP dbProp[16] = {0}; DBPROPSET dbPropset[1] = {0}; //设置结果集可以进行增删改操作 dbProp[0].colid = DB_NULLID; dbProp[0].dwOptions = DBPROPOPTIONS_REQUIRED; dbProp[0].dwPropertyID = DBPROP_UPDATABILITY; dbProp[0].vValue.vt = VT_I4; dbProp[0].vValue.lVal = DBPROPVAL_UP_DELETE | DBPROPVAL_UP_CHANGE | DBPROPVAL_UP_INSERT; //申请打开书签功能 dbProp[1].colid = DB_NULLID; dbProp[1].dwOptions = DBPROPOPTIONS_OPTIONAL; dbProp[1].dwPropertyID = DBPROP_BOOKMARKS; dbProp[1].vValue.vt = VT_BOOL; dbProp[1].vValue.boolVal = VARIANT_TRUE; //申请打开行查找功能 dbProp[2].colid = DB_NULLID; dbProp[2].dwOptions = DBPROPOPTIONS_OPTIONAL; dbProp[2].dwPropertyID = DBPROP_IRowsetFind; dbProp[2].vValue.vt = VT_BOOL; dbProp[2].vValue.boolVal = VARIANT_TRUE; //申请打开行索引 dbProp[3].colid = DB_NULLID; dbProp[3].dwOptions = DBPROPOPTIONS_OPTIONAL; dbProp[3].dwPropertyID = DBPROP_IRowsetIndex; dbProp[3].vValue.vt = VT_BOOL; dbProp[3].vValue.boolVal = VARIANT_TRUE; //申请打开行定位功能 dbProp[4].colid = DB_NULLID; dbProp[4].dwOptions = DBPROPOPTIONS_OPTIONAL; dbProp[4].dwPropertyID = DBPROP_IRowsetLocate; dbProp[4].vValue.vt = VT_BOOL; dbProp[4].vValue.boolVal = VARIANT_TRUE; //申请打开行滚动功能 dbProp[5].colid = DB_NULLID; dbProp[5].dwOptions = DBPROPOPTIONS_OPTIONAL; dbProp[5].dwPropertyID = DBPROP_IRowsetScroll; dbProp[5].vValue.vt = VT_BOOL; dbProp[5].vValue.boolVal = VARIANT_TRUE; //申请打开行集视图功能 dbProp[6].colid = DB_NULLID; dbProp[6].dwOptions = DBPROPOPTIONS_OPTIONAL; dbProp[6].dwPropertyID = DBPROP_IRowsetView; dbProp[6].vValue.vt = VT_BOOL; dbProp[6].vValue.boolVal = VARIANT_TRUE; //申请打开行集刷新功能 dbProp[7].colid = DB_NULLID; dbProp[7].dwOptions = DBPROPOPTIONS_OPTIONAL; dbProp[7].dwPropertyID = DBPROP_IRowsetRefresh; dbProp[7].vValue.vt = VT_BOOL; dbProp[7].vValue.boolVal = VARIANT_TRUE; //申请打开列信息扩展接口 dbProp[8].colid = DB_NULLID; dbProp[8].dwOptions = DBPROPOPTIONS_OPTIONAL; dbProp[8].dwPropertyID = DBPROP_IColumnsInfo2; dbProp[8].vValue.vt = VT_BOOL; dbProp[8].vValue.boolVal = VARIANT_TRUE; //申请打开数据库同步状态接口 dbProp[9].colid = DB_NULLID; dbProp[9].dwOptions = DBPROPOPTIONS_OPTIONAL; dbProp[9].dwPropertyID = DBPROP_IDBAsynchStatus; dbProp[9].vValue.vt = VT_BOOL; dbProp[9].vValue.boolVal = VARIANT_TRUE; //申请打开行集分章功能 dbProp[10].colid = DB_NULLID; dbProp[10].dwOptions = DBPROPOPTIONS_OPTIONAL; dbProp[10].dwPropertyID = DBPROP_IChapteredRowset; dbProp[10].vValue.vt = VT_BOOL; dbProp[10].vValue.boolVal = VARIANT_TRUE; dbProp[11].colid = DB_NULLID; dbProp[11].dwOptions = DBPROPOPTIONS_OPTIONAL; dbProp[11].dwPropertyID = DBPROP_IRowsetCurrentIndex; dbProp[11].vValue.vt = VT_BOOL; dbProp[11].vValue.boolVal = VARIANT_TRUE; dbProp[12].colid = DB_NULLID; dbProp[12].dwOptions = DBPROPOPTIONS_OPTIONAL; dbProp[12].dwPropertyID = DBPROP_IGetRow; dbProp[12].vValue.vt = VT_BOOL; dbProp[12].vValue.boolVal = VARIANT_TRUE; //申请打开行集更新功能 dbProp[13].colid = DB_NULLID; dbProp[13].dwOptions = DBPROPOPTIONS_OPTIONAL; dbProp[13].dwPropertyID = DBPROP_IRowsetUpdate; dbProp[13].vValue.vt = VT_BOOL; dbProp[13].vValue.boolVal = VARIANT_TRUE; dbProp[14].colid = DB_NULLID; dbProp[14].dwOptions = DBPROPOPTIONS_OPTIONAL; dbProp[14].dwPropertyID = DBPROP_IConnectionPointContainer; dbProp[14].vValue.vt = VT_BOOL; dbProp[14].vValue.boolVal = VARIANT_TRUE; dbPropset[0].cProperties = 15; dbPropset[0].guidPropertySet = DBPROPSET_ROWSET; dbPropset[0].rgProperties = dbProp; hRes = pICommandText->QueryInterface(IID_ICommandProperties, (void**)&pICommandProperties); COM_SUCCESS(hRes, _T("查询接口ICommandProperties失败,错误码:%08x\n"), hRes); hRes = pICommandProperties->SetProperties(1, dbPropset); COM_SUCCESS(hRes, _T("设置属性失败,错误码:%08x\n"), hRes); hRes = pICommandText->Execute(NULL, IID_IRowset, NULL, NULL, (IUnknown**)&pIRowset); COM_SUCCESS(hRes, _T("执行sql语句失败,错误码:%08x\n"), hRes);这段代码详细的展示了如何执行SQL语句获取结果集并设置COMMANDUI对象的属性。结果集对象结果集一般是执行完SQL语句后返回的一个代表二维结构化数组的对象。这个结构化对象可以理解为一个与数据表定义相同的一个结构体。而结果集中保存了这个结构体的指针下面是结果集对象的详细定义CoType TRowset { [mandatory] interface IAccessor; [mandatory] interface IColumnsInfo; [mandatory] interface IConvertType; [mandatory] interface IRowset; [mandatory] interface IRowsetInfo; [optional] interface IChapteredRowset; [optional] interface IColumnsInfo2; [optional] interface IColumnsRowset; [optional] interface IConnectionPointContainer; [optional] interface IDBAsynchStatus; [optional] interface IGetRow; [optional] interface IRowsetChange; [optional] interface IRowsetChapterMember; [optional] interface IRowsetCurrentIndex; [optional] interface IRowsetFind; [optional] interface IRowsetIdentity; [optional] interface IRowsetIndex; [optional] interface IRowsetLocate; [optional] interface IRowsetRefresh; [optional] interface IRowsetScroll; [optional] interface IRowsetUpdate; [optional] interface IRowsetView; [optional] interface ISupportErrorInfo; [optional] interface IRowsetBookmark; }结果集对象的一般用法得到结果集后,它的使用步骤一般如下:首先Query出IColumnsInfo接口通过调用IColumnsInfo::GetColumnInfo方法得到关于结果集的列的详细信息DBCOLUMNINFO结构的数组,包括:列序号,列名,类型,字节长度,精度,比例等3.通过该结构数组,准备一个对应的DBBINDING结构数组,并计算每行数据实际需要的缓冲大小,并填充结构DBBINDING。这个过程一般叫做绑定4.利用DBBINDING数组和IAccessor::CreateAccessor方法创建一个数据访问器并得到句柄HACCESSOR调用IRowset::GetNextRow遍历行指针到下一行,第一次调用就是指向第一行,并得到行句柄HROW,这个行句柄表示我们访问的当前是结果中的第几行,一般它的值是一个依次递增的整数调用IRowset::GetData传入准备好的行缓冲内存指针,以及之前创建的访问器HACCESSOR句柄和HROW句柄。最终行数据就被放置到了指定的缓冲中。循环调用GetNextRow和GetData即可遍历整个二维结果集。列信息的获取取得结果集对象后,紧接着的操作一般就是获取结果集的结构信息,也就是获取结果集的列信息(有些材料中称为字段信息)要获取列信息,就需要QueryInterface出结果集对象的IColumnsInfo接口,并调用IColumnsInfo::GetColumnInfo方法获得一个称为DBCOLUMNINFO结构体的数组该结构体中反映了列的逻辑结构信息(抽象数据类型)和物理结构信息(内存需求大小等信息)函数GetColumnInfo定义如下:HRESULT GetColumnInfo ( DBORDINAL *pcColumns, DBCOLUMNINFO **prgInfo, OLECHAR **ppStringsBuffer);第一个参数表示总共有多少列,第二个参数是一个DBCOLUMNINFO,返回一个列信息的数组指针,第三个参数返回一个字符串指针,这个字符串中保存的是个列的名称,每个名称间以\0\0分割。但是我们一般不使用它来获取列名,我们一般使用DBCOLUMNINFO结构的pwszName成员。DBCOLUMNINFO定义如下:typedef struct tagDBCOLUMNINFO { LPOLESTR pwszName; //列名 ITypeInfo *pTypeInfo; //列的类型信息 DBORDINAL iOrdinal; //列序号 DBCOLUMNFLAGS dwFlags; //列的相关标识 DBLENGTH ulColumnSize; //列最大可能的大小,对于字符串来说,它表示的是字符个数 DBTYPE wType; //列类型 BYTE bPrecision; //精度(它表示小数点后面的位数) BYTE bScale; //表示该列的比例,目前没有用处,一般给的是0 DBID columnid; //列信息在数据字典表中存储的ID } DBCOLUMNINFO;对于columnid成员,DBMS系统一般会有多个系统表来表示众多的信息,比如用户信息,数据库信息,数据表信息等等,其中针对每个表中的列的相关信息DBMS系统使用特定的系统表来存储,而查询这个系统表来获取列信息时使用的就是这个columnid值。数据绑定一般绑定需要两步,1是获取列信息的DBCOLUMNINFO结构,接着就是根据列信息来填充DBBINDING数据结构。有的时候可能会觉得绑定好麻烦啊,还不如直接返回一个缓冲,将所有结果放入里面,应用程序根据需求自己去解析它,这样岂不是更方便。之所以需要绑定,有下面一个理由:并不是所有的数据类型都能被应用程序支持,比如说数据库中的NUMBER类型在VC++中找不到对应的数据结构来支持。有时一行数据并不能完全读取到内存中,比如说我们给的缓冲不够或者是数据库中的数据本身比较大,比如存储了一个视频文件等等。在程序中并不是所有的访问器都是为了读取数据,而且使用返回所有结果的方式太简单粗暴了,比如我只想要一列的数据那个数据可能占用内存不足1K,但是数据库表中某一列数据特别大,可能占用内存会超过一个G,如果全都返回的话太浪费内存了。所以在绑定时候可以灵活的指定返回那些数据,返回数据长度是多少,针对特别大的数据,我们可以指定它只返回部分,比如只返回前面的1K使用绑定可以灵活的安排返回数据在内存中的摆放形式。绑定结构的定义如下:typedef struct tagDBBINDING { DBORDINAL iOrdinal; //列号 DBBYTEOFFSET obValue; DBBYTEOFFSET obLength; DBBYTEOFFSET obStatus; ITypeInfo *pTypeInfo; DBOBJECT *pObject; DBBINDEXT *pBindExt; DBPART dwPart; DBMEMOWNER dwMemOwner; DBPARAMIO eParamIO; DBLENGTH cbMaxLen; //数据的最大长度,一般给我们为这个列的数据准备的缓冲的长度 DWORD dwFlags; DBTYPE wType; //将该列转化为何种数据类型展示 BYTE bPrecision; BYTE bScale; }DBBINDING;参数的详细说明:obValue、obLength、obStatus:数据源在返回结果的时候一般会返回该列信息的三中数据,数据长度、数据状态、数据值。数据状态表示数据源在提供数据的一个状态信息,比如该列信息为空时它会返回一个DBSTATUS_S_ISNULL,列数据比较长,而提供的数据可能不够,这个时候会返回一个状态表示发生了截断。而数据长度表示返回结果的长度。这个值是返回的数据对应的字节长度,注意这里需要与前面ulColumnSize区分开来。三个数据在内存中摆放的顺序如下:其中每列数据都是按照status length value结构排布,而不同的列的数据按照顺序依次向后排放,这个内存的布局有点像结构体数组在内存中的的布局方式。而绑定结构中的obValue、obLength、obStatus规定了它们三者在一块内存缓冲中的偏移,要注意后面一列的开始位置是在前面一列的结束位置而不是所有数据都是从0开始。dwPart:前面说数据源返回结果中有3个部分,但是我们可以指定数据源返回这3个部分的哪些部分,它的值是一些标志位,根据这些标志来决定需要返回哪些数据,不需要返回哪些数据.它的值主要有:DBPART_LENGTH、 DBPART_STATUS、DBPART_VALUE;dwMemOwner,这个值表示将使用何种内存缓冲来保存数据源返回的结果,我们一般使用DBMEMOWNER_CLIENTOWNED,表示使用用户自定义内存的方式,即这个缓冲需要自行准备。eParamIO:我们将返回的值做何种用途,DBPARAMIO_NOTPARAM表示不做特殊用途,DBPARAMIO_INPUT,作为输入值,一般在需要更新列数据的时候使用这个标志,DBPARAMIO_OUTPUT,作为输出值,这个输出时相对于数据源来说的,表示输出到应用程序程序缓冲,作为展示用。wType:将数据源中的原始数据做何种类型的转化,比如原来数据库中存储的是整数的123456,而这个值是DBTYPE_WSTR的话,数据源中的结果会被转化为字符串的"123456",放入到缓冲中。DBBINDING 与DBCOLUMNSINFO结构的比较它们二者中有许多数据成员是相同的,表示的含义也基本相同,但是二者也有显著的区别:DBCOLUMNINFO是数据提供者给使用者的信息,它是固定的,对相同的查询来说,列总是相同的,因此数据提供者返回的 DBCOLUMNINFO数组也是固定的.而DBBINDING是作为数据消费者创建之后给数据提供者的一个结构数组,它的内容则由调用者来完全控制,通过这个结构可以指定数据提供者最终将数据摆放成调用者指定的格式,并进行指定的数据类型转换.针对相同的查询我们可以指定不同的DBBINDINGS结构。DBCOLUMNINFO反映的是二维结果集的原始列结构信息而DBBINDING则反映的是二维结果集数据最终按要求摆放在内存中的样式下面是一个针对绑定的列子:ExecSql(pIOpenRowset, pIRowset); //创建IColumnsInfo接口 HRESULT hRes = pIRowset->QueryInterface(IID_IColumnsInfo, (void**)&pIColumnsInfo); COM_SUCCESS(hRes, _T("查询接口IComclumnsInfo,错误码:%08x\n"), hRes); //获取结果集的详细信息 hRes = pIColumnsInfo->GetColumnInfo(&cClumns, &rgColumnInfo, &lpClumnsName); COM_SUCCESS(hRes, _T("获取列信息失败,错误码:%08x\n"), hRes); //绑定 pDBBindings = (DBBINDING*)MALLOC(sizeof(DBBINDING) * cClumns); for (int iRow = 0; iRow < cClumns; iRow++) { pDBBindings[iRow].bPrecision = rgColumnInfo[iRow].bPrecision; pDBBindings[iRow].bScale = rgColumnInfo[iRow].bScale; pDBBindings[iRow].cbMaxLen = rgColumnInfo[iRow].ulColumnSize * sizeof(WCHAR); if (rgColumnInfo[iRow].wType == DBTYPE_I4) { //数据库中行政单位的长度最大为6位 pDBBindings[iRow].cbMaxLen = 7 * sizeof(WCHAR); } pDBBindings[iRow].dwMemOwner = DBMEMOWNER_CLIENTOWNED; pDBBindings[iRow].dwPart = DBPART_LENGTH | DBPART_STATUS | DBPART_VALUE; pDBBindings[iRow].eParamIO = DBPARAMIO_NOTPARAM; pDBBindings[iRow].iOrdinal = rgColumnInfo[iRow].iOrdinal; pDBBindings[iRow].obStatus = dwOffset; pDBBindings[iRow].obLength = dwOffset + sizeof(DBSTATUS); pDBBindings[iRow].obValue = dwOffset + sizeof(DBSTATUS) + sizeof(ULONG); pDBBindings[iRow].wType = DBTYPE_WSTR; dwOffset = dwOffset + sizeof(DBSTATUS) + sizeof(ULONG) + pDBBindings[iRow].cbMaxLen * sizeof(WCHAR); dwOffset = COM_ROUNDUP(dwOffset); //进行内存对齐 } //创建访问器 hRes = pIRowset->QueryInterface(IID_IAccessor, (void**)&pIAccessor); COM_SUCCESS(hRes, _T("查询IAccessor接口失败错误码:%08x\n"), hRes); hRes = pIAccessor->CreateAccessor(DBACCESSOR_ROWDATA, cClumns, pDBBindings, 0, &hAccessor, NULL); COM_SUCCESS(hRes, _T("创建访问器失败错误码:%08x\n"), hRes); //输出列名信息 DisplayColumnName(rgColumnInfo, cClumns); //分配对应的内存 pData = MALLOC(dwOffset * cRows); while (TRUE) { hRes = pIRowset->GetNextRows(DB_NULL_HCHAPTER, 0, cRows, &uRowsObtained, &phRows); if (hRes != S_OK && uRowsObtained != 0) { break; } ZeroMemory(pData, dwOffset * cRows); //显示数据 for (int i = 0; i < uRowsObtained; i++) { pCurrData = (BYTE*)pData + dwOffset * i; pIRowset->GetData(phRows[i], hAccessor, pCurrData); DisplayData(pDBBindings, cClumns, pCurrData); } //清理hRows pIRowset->ReleaseRows(uRowsObtained, phRows, NULL, NULL, NULL); CoTaskMemFree(phRows); phRows = NULL; } //显示列名称 void DisplayColumnName(DBCOLUMNINFO *pdbColumnInfo, DBCOUNTITEM iDbCount) { COM_DECLARE_BUFFER(); for (int iColumn = 0; iColumn < iDbCount; iColumn++) { COM_CLEAR_BUFFER(); TCHAR wszColumnName[MAX_DISPLAY_SIZE + 1] =_T(""); size_t dwSize = 0; StringCchLength(pdbColumnInfo[iColumn].pwszName, MAX_DISPLAY_SIZE, &dwSize); dwSize = min(dwSize, MAX_DISPLAY_SIZE); StringCchCopy(wszColumnName, MAX_DISPLAY_SIZE, pdbColumnInfo[iColumn].pwszName); COM_PRINTF(wszColumnName); COM_PRINTF(_T("\t")) } COM_PRINTF(_T("\n")); } //显示数据 void DisplayData(DBBINDING *pdbBindings, DBCOUNTITEM iDbColCnt, void *pData) { COM_DECLARE_BUFFER(); for (int i = 0; i < iDbColCnt; i++) { COM_CLEAR_BUFFER(); DBSTATUS status = *(DBSTATUS*)((PBYTE)pData + pdbBindings[i].obStatus); ULONG uSize = (*(ULONG*)((PBYTE)pData + pdbBindings[i].obLength)) / sizeof(WCHAR); PWSTR pCurr = (PWSTR)((PBYTE)pData + pdbBindings[i].obValue); switch (status) { case DBSTATUS_S_OK: case DBSTATUS_S_TRUNCATED: COM_PRINTF(_T("%s\t"), pCurr); break; case DBSTATUS_S_ISNULL: COM_PRINTF(_T("%s\t"), _T("(null)")); break; default: break; } } COM_PRINTF(_T("\n")); }在使用前一个列子中的方法设置对应的属性并执行SQL语句后,得到一个结果集,然后调用对应的Query方法,得到一个pIColumnsInfo接口,接着调用接口的GetColumnsInfo方法,获取结构的具体信息。最需要注意的是绑定部分的代码,根据返回的具体列数,我们定义了一个对应的绑定结构的数组,将每个赋值,赋值的时候定义了一个dwOffset结构来记录当前使用内存的情况,这样每次在循环执行一次后,它的位置永远在上一个列信息缓冲的尾部,这样我们可以很方便的进行偏移的计算。绑定完成后这个dwOffset的值就是所有列使用的内存的总大小,因此在后面利用这个值分配一个对应长度的内存。然后循环调用GetNextRows、GetData方法依次获取每行、每列的数据。最后调用相应的函数来进行显示,至此就完成了数据的读取操作。最后,我发现码云上的代码片段简直就是为保存平时例子代码而生的,所以后面的代码将不再在GitHub上更新了,而换到码云上面。源代码查看
2018年01月28日
32 阅读
5 评论
0 点赞
2018-01-21
事务对象和命令对象
上次说到数据源对象,这次接着说事务对象和命令对象。事务是一种对数据源的一系列更新进行分组或批处理以便当所有更新都成功时同时提交这些更新,或者如果任何一个更新失败则不提交任何更新并且回滚整个事务的方法.命令对象一般是用来执行sql语句并生成结果集的对象会话对象在OLEDB中通过以下3中方式支持事务:ITransactionLocal::StartTransactionITransaction::commitITransaction::AbortOLEDB中定义事务和回话对象的接口如下:CoType TSession { [mandatory] interface IGetDataSource; [mandatory] interface IOpenRowset; [mandatory] interface ISessionProperties; [optional] interface IAlterIndex; [optional] interface IAlterTable; [optional] interface IBindResource; [optional] interface ICreateRow; [optional] interface IDBCreateCommand; [optional] interface IDBSchemaRowset; [optional] interface IIndexDefinition; [optional] interface ISupportErrorInfo; [optional] interface ITableCreation; [optional] interface ITableDefinition; [optional] interface ITableDefinitionWithConstraints; [optional] interface ITransaction; [optional] interface ITransactionJoin; [optional] interface ITransactionLocal; [optional] interface ITransactionObject; }在创建了数据库连接之后使用QueryInterface 查询出IDBCreateSeesion对象,然后调用IDBCreateSession的CreateSession方法创建一个回话对象。需要注意的是,一个数据源连接可以创建多个回话对象,这里只能通过这种方式创建回话对象,而不能直接通过CoCreateInstance 来创建。一般来说应用中至少创建一个会话对象Command对象Command对象的定义如下:CoType TCommand { [mandatory] interface IAccessor; [mandatory] interface IColumnsInfo; [mandatory] interface ICommand; [mandatory] interface ICommandProperties; [mandatory] interface ICommandText; [mandatory] interface IConvertType; [optional] interface IColumnsRowset; [optional] interface ICommandPersist; [optional] interface ICommandPrepare; [optional] interface ICommandWithParameters; [optional] interface ISupportErrorInfo; [optional] interface ICommandStream; }一般创建Command对象是通过Session对象Query出来一个IDBCreateCommand接口让后调用CreateCommand方法创建。与会话对象相似,一个会话对象可以创建多个命令对象,但是从上面会话对象的定义可以看出IDBCreateCommand接口是一个可选接口,并不是所有的数据库都支持,因此在创建命令对象的时候一定要注意判断是否支持。对于不支持的可以采用其他办法(一般采用直接打开数据库表的方式)。下面是一个演示例子:#define CHECK_OLEDB_INTERFACE(I, iid)\ hRes = (I)->QueryInterface(IID_##iid, (void**)&(p##iid));\ if(FAILED(hRes))\ {\ OLEDB_PRINTF(_T("不支持接口:%s\n"), _T(#iid));\ }\ else\ {\ OLEDB_PRINTF(_T("支持接口:%s\n"), _T(#iid));\ } BOOL CreateDBSession(IOpenRowset* &pIOpenRowset) { DECLARE_BUFFER(); DECLARE_OLEDB_INTERFACE(IDBPromptInitialize); DECLARE_OLEDB_INTERFACE(IDBInitialize); DECLARE_OLEDB_INTERFACE(IDBCreateSession); BOOL bRet = FALSE; HWND hDesktop = GetDesktopWindow(); HRESULT hRes = CoCreateInstance(CLSID_DataLinks, NULL, CLSCTX_INPROC_SERVER, IID_IDBPromptInitialize, (void**)&pIDBPromptInitialize); OLEDB_SUCCESS(hRes, _T("创建接口IDBPromptInitialize失败,错误码:%08x\n"), hRes); hRes = pIDBPromptInitialize->PromptDataSource(NULL, hDesktop, DBPROMPTOPTIONS_WIZARDSHEET, 0, NULL, NULL, IID_IDBInitialize, (IUnknown**)&pIDBInitialize); OLEDB_SUCCESS(hRes, _T("弹出接口数据源配置对话框失败,错误码:%08x\n"), hRes); hRes = pIDBInitialize->Initialize(); OLEDB_SUCCESS(hRes, _T("链接数据库失败,错误码:%08x\n"), hRes); hRes = pIDBInitialize->QueryInterface(IID_IDBCreateSession, (void**)&pIDBCreateSession); OLEDB_SUCCESS(hRes, _T("创建接口IDBCreateSession失败,错误码:%08x\n"), hRes); hRes = pIDBCreateSession->CreateSession(NULL, IID_IOpenRowset, (IUnknown**)&pIOpenRowset); OLEDB_SUCCESS(hRes, _T("创建接口IOpenRowset失败,错误码:%08x\n"), hRes); bRet = TRUE; __CLEAR_UP: OLEDB_SAFE_RELEASE(pIDBPromptInitialize); OLEDB_SAFE_RELEASE(pIDBInitialize); OLEDB_SAFE_RELEASE(pIDBCreateSession); return bRet; } int _tmain(int argc, TCHAR *argv[]) { DECLARE_BUFFER(); DECLARE_OLEDB_INTERFACE(IOpenRowset); DECLARE_OLEDB_INTERFACE(IDBInitialize); DECLARE_OLEDB_INTERFACE(IDBCreateCommand); DECLARE_OLEDB_INTERFACE(IAccessor); DECLARE_OLEDB_INTERFACE(IColumnsInfo); DECLARE_OLEDB_INTERFACE(ICommand); DECLARE_OLEDB_INTERFACE(ICommandProperties); DECLARE_OLEDB_INTERFACE(ICommandText); DECLARE_OLEDB_INTERFACE(IConvertType); DECLARE_OLEDB_INTERFACE(IColumnsRowset); DECLARE_OLEDB_INTERFACE(ICommandPersist); DECLARE_OLEDB_INTERFACE(ICommandPrepare); DECLARE_OLEDB_INTERFACE(ICommandWithParameters); DECLARE_OLEDB_INTERFACE(ISupportErrorInfo); DECLARE_OLEDB_INTERFACE(ICommandStream); CoInitialize(NULL); if (!CreateDBSession(pIOpenRowset)) { OLEDB_PRINTF(_T("调用函数CreateDBSession失败,程序即将推出\n")); return 0; } HRESULT hRes = pIOpenRowset->QueryInterface(IID_IDBCreateCommand, (void**)&pIDBCreateCommand); OLEDB_SUCCESS(hRes, _T("创建接口IDBCreateCommand失败,错误码:%08x\n"), hRes); hRes = pIDBCreateCommand->CreateCommand(NULL, IID_IAccessor, (IUnknown**)&pIAccessor); OLEDB_SUCCESS(hRes, _T("创建接口IAccessor失败,错误码:%08x\n"), hRes); CHECK_OLEDB_INTERFACE(pIAccessor, IColumnsInfo); CHECK_OLEDB_INTERFACE(pIAccessor, ICommand); CHECK_OLEDB_INTERFACE(pIAccessor, ICommandProperties); CHECK_OLEDB_INTERFACE(pIAccessor, ICommandText); CHECK_OLEDB_INTERFACE(pIAccessor, IConvertType); CHECK_OLEDB_INTERFACE(pIAccessor, IColumnsRowset); CHECK_OLEDB_INTERFACE(pIAccessor, ICommandPersist); CHECK_OLEDB_INTERFACE(pIAccessor, ICommandPrepare); CHECK_OLEDB_INTERFACE(pIAccessor, ICommandWithParameters); CHECK_OLEDB_INTERFACE(pIAccessor, ISupportErrorInfo); CHECK_OLEDB_INTERFACE(pIAccessor, ICommandStream); __CLEAR_UP: OLEDB_SAFE_RELEASE(pIOpenRowset); OLEDB_SAFE_RELEASE(pIDBInitialize); OLEDB_SAFE_RELEASE(pIDBCreateCommand); OLEDB_SAFE_RELEASE(pIAccessor); OLEDB_SAFE_RELEASE(pIColumnsInfo); OLEDB_SAFE_RELEASE(pICommand); OLEDB_SAFE_RELEASE(pICommandProperties); OLEDB_SAFE_RELEASE(pICommandText); OLEDB_SAFE_RELEASE(pIConvertType); OLEDB_SAFE_RELEASE(pIColumnsRowset); OLEDB_SAFE_RELEASE(pICommandPersist); OLEDB_SAFE_RELEASE(pICommandPrepare); OLEDB_SAFE_RELEASE(pICommandWithParameters); OLEDB_SAFE_RELEASE(pISupportErrorInfo); OLEDB_SAFE_RELEASE(pICommandStream); CoUninitialize(); return 0; }在上述的例子中,首先定义了一个宏,用来判断是否支持对应的接口。同时定义了一个CreateDBSession方法来创建一个会话对象。在该函数中首先利用上一节的方法创建一个数据库连接,然后在数据源对象上调用QueryInterface来获取接口IDBCreateSeesion,接着利用IDBCreateSeesion接口的CreateSeesion方法创建一个会话对象,由于IDBCreateCommand接口并不是所有的数据源都支持,所以为了保证一定能创建会话对象,我们选择会话对象必须支持的接口IOpenRowset。在得到会话对象后,尝试创建IDBCreateSession对象,如果它不支持,那么程序直接退出。接着调用IDBCreateCommand接口来创建一个命令对象并尝试query命令对象的其他接口,得出数据源支持哪些接口。这个例子非常简单,只是为了演示如何创建会话对象和数据源对象罢了。本节例子代码
2018年01月21日
3 阅读
0 评论
0 点赞
2018-01-12
OLEDB数据源
数据源在oledb中指数据提供者,这里可以简单的理解为数据库程序。数据源对象代表数据库的一个连接,是需要创建的第一个对象。而数据源对象主要用于配置数据库连接的相关属性如连接数据库的用户名密码等等
2018年01月12日
14 阅读
0 评论
0 点赞
2018-01-07
《疯狂的程序员》读后感
之前我在新年计划中说,争取新加一点读书笔记的内容,为博客赚点流量,所以这是我的第一篇读书笔记,为什么选这本书呢?可以说这本书给我详细介绍了程序员的工作,世界观,使我对计算机编程有了浓厚的兴趣,同时它也给我指明了后面努力的方向。初次读这本书是在我大二那年,由一个同学推荐,后来在毕业之后我在万能的某宝上找到了它的纸质版,又读了几遍,每次读都有很大的感触,所以我决定拿他作为我的第一篇读书笔记。感悟初次读这本书是我的一个大学同学推荐的,当时我正在大二,那是个时候我感觉到有一丝迷茫,有一丝压力,压力主要在将来就业上,当时我读的不是什么名牌大学,没有什么特别突出的能力,专业课也听得似懂非懂,那个时候总感觉以现在的状态坑定要失业。虽说想要学习点东西,不知道从何学起。那个时候学习的东西太多了,C/C++、Java、HTML、ASP.NET等等,很多东西都是浅尝辄止,一直感觉很无力,。我与几个朋友一起交流的时候,他给我说:“我给你推荐一本书吧,叫《疯狂的程序员》网上有电子版的,这本书是作者根据自身经历写,讲述了主人公从刚进入大学到最后工作再到后期创业的整个故事。里面详细介绍了主角的整个学习过程,主人公从大学一直学习汇编,然后到毕业实习、工作,逐渐过渡到VC++的种种,至此我似乎有了一丝学习的脉络,当时我给自己定下的目标是利用一个寒假好好补一下之前落下的汇编,然后再利用一个学期学习C/C++,当我把汇编学习完了之后我发现C语言里面的指针,数组,结构体,共用体什么的都不再那么神秘,从汇编角度上看只不过是内存的不同摆放方式,采用不同的寻址方式罢了,函数调用,各种传参等等就没有什么难度了,学完汇编再看C语言的种种语法就觉得是那么顺理成章。后来两年中,我基本上是按照这本书中主人公的学习脉络来的,先从汇编到Win32 汇编再到VC++,MFC编程,依照这个顺序,在自学的过程中再也没有遇到过之前那种离了书本就什么都不会的经历,这也给我后面学习带来很大的动力。以前我一直把程序员当做一个理工科的内容,一直认为它只是类似于水利工程师,建筑工程师之类的职业,但是我从这本书中读到一个观点“程序员的双手是魔术师的双手,他将枯燥的代码编程优美的可执行的软件”。原来程序员可以是一个艺术家,是类似于画家的,是可以写出优美的软件,从书中的字里行间,我读到作者作为一个程序员的骄傲,作者那种学习新技术,努力写出优美软件的那种执着。这些都使我对程序员的世界产生了浓厚的兴趣,根据这本书上的相关内容,我注册了一个CSDN,一个看雪的账号,经常在各大程序员论坛潜水,努力希望融入这个圈子,学习程序员文化。可以说这本书带给我关于程序员的启蒙教育,以前一直把程序员当做一个赚钱的行业,当时从那个时候起,我是真正喜欢上了这个职业,喜欢上这种文化。名言警句书中也有许多有意思的想法,时不时爆出一些名言警句什么的,下面我列举出我比较喜欢的一些句子:要么做第一个,要么做最好的一个。什么是鸡肋课?就是每个人都在自己课表的这门课旁边标注一个“可旷”或者“选修”。什么程序优化啊,都抵不上有钱,有钱就是最好的优化。说什么“效率就是金钱”,其实“金钱就是效率”。中国人和外国人很大不同就在于外国人总想热衷于第一时间把自己的新发现公布出来以此向广大人民群众展示自己的水平。中国人往往喜欢把自己的新发现阴在心里,自己偷偷垄断享用。唯恐被第二个人知道。公布出去,相关部门肯定马上解决。所谓好的病毒就是要:“持续时间特别长,波及范围特别广,破坏力特别大。”能进别人系统偷东西,那就是小偷;能进别人系统又不偷东西,那就是黑客。当你不是黑客的时候,总说:“我是个黑客”。当你真正成为黑客的时候,你往往会说:“我不是黑客”程序员是值得尊敬的,程序员的双手是魔术师的双手,他们把枯燥无味的代码变成了丰富多彩的软件……一个人静静的坐在电脑前写大卖的感觉,那是什么感觉?那就是武林高手闭关修炼的感觉。一本好书,就像高级武功秘籍一样,哪怕只从里面领悟个一招半式,功力提升起来都是惊人的,眉超风学的那半生不熟的九阴真经就是证明。所以练武功和写程序一样,不在乎你修炼了几十年还是几百年,也不在乎你少林武当娥眉拜了多少门派,关键是你有没有把一门武艺炼到出神入化的境界。学武艺,最忌讳总想着去追求大而全,你要明白,其实只需要“打狗棍法”这么一招,就足够你掌舵丐帮,受用终生。天下武功出少林,天下语言出汇编提升功力大低有两种方法,一是自己不断写代码,不断完善,不断把自己的代码写好,二是看牛人的代码。牛人的代码实在太高深了,一句看似平凡的语句,也许背后都蕴含着惊天地泣鬼神的智慧。现在你看不出什么端倪,等十年八年之后,你猛然醒悟:啊,原来大牛的代码竟是如此博大精深!对男人来说钱什么都不是,但是你没钱,你就失去了让她了解你的机会。对女人来说漂亮什么都不是,但是你不漂亮,你就失去了让他了解你的机会。人不怕被识破也不怕丢脸,怕就怕被当场识破和当众丢脸。。力的作用是相互的,你打别人有多疼,自己的手就有多疼。与其大家都疼,还不如最开始就不要下手打。每个人都期盼着便宜能光顾到自己,就算没有正儿八经地想,多多少少也偷偷的想过,但是有天便宜真的来了,可要小心了。写程序并不是一辈子都只是写代码。IT这一行是相当广博的,不管你是男的还是女的,不管你技术是初级、中级还是高级,你都能在这行中找到你自己合适的位置。如果你真的用心了,它带给你的会是一生的回报。男人的工资,和女人的年龄差不多。没钱的时候,最忌讳别人追问自己的工资待遇。可男人的工资,又和女人的年龄不一样。女人要是年轻,别人问她年龄,她自然敢大方地回答。男人的工资呢?要是高了,还是怕别人追问。“疯狂的程序员”绝对不是靠狂妄和拼命的程序员,而是能够脚踏实地、持续努力的程序员。一个程序员真正做到了这两点,技术上去之后,唯一能够限制他的只有想像力,到那个时候,才算“疯狂的程序员”,这种程序员啊,才能令竞争对手无比恐惧。技术其实还是我们最需要的东西,以前我们没有过硬的技术,所以疯狂地追求它。现在呢?有了一点技术,便觉得技术不那么重要。如果这样放任下去,等到我们失去技术的那一天,一定会后悔莫及的!最后说点题外话从当时懵懂的菜鸟到现在入行块两年的老菜鸟,目前仍然在学习Windows编程方面的东西,很多人都说现在PC端萎缩,学习PC端的编程没有出路,但是我自己不这么想,编程这个东西与具体的平台无关,与语言无关,编程靠的永远都是基础知识,数学和算法才是王道。但是算法和数学就只看理论是没有办法入门的,必须要实践,而实践的最好方法就是深入一个平台,一门语言不停的学习某个平台某一个语言的相关内容。在学习的途中会涉及到许多算法,操作系统,网络,数据库等等基础部分的知识,有的还可能会涉及到具体的硬件知识。比如Windows平台编程中的进程、线程、内存管理,想要学通这些没有操作系统的相关知识是不可能的,学习网络编程的时候需要网络知识,而数据库则需要数据库相关的知识,而且在Windows中采用的许多框架许多机制一般在其他平台也适用,比如Windows上的回调、消息机制、映射等等相关内容在其他平台其他语言基本上都有,现代操作系统,编程语言都是想通的,基本上是一通百通。因此在学习的过程中不要太纠结于平台和语言。随便选择一个平台,努力深入进去,在编程的同时学习一下基础理论的内容。不要被目前很火的大数据、人工智能云计算,等等东西蒙蔽了头脑,它们说到底也是对数学和算法在具体环境中的应用而已,只要有扎实的基础,想要跳到对应行业,只需要了解对应语言的语法,相应的接口而已。归结起来就一句话:数学和算法是王道。以上都是一个IT菜鸟的肤浅的理解而已,同意就点个赞,不同意也不要喷!!!!
2018年01月07日
7 阅读
0 评论
0 点赞
2018-01-06
Windows数据库编程接口简介
数据库是计算机中一种专门管理数据资源的系统,目前几乎所有软件都需要与数据库打交道(包括操作系统,比如Windows上的注册表其实也是一种数据库),有些软件更是以数据库为核心因此掌握数据库系统的使用方法以及数据库系统编程接口的使用方法是程序员非常重要的基本技能之一。所以我花了一定的时间学习了在Windows平台上使用COM接口的方式操作数据库。这段时间我会将自己学习过程中掌握的知识和其中的一些坑都发布出来,供个人参考,也方便他人学习
2018年01月06日
3 阅读
0 评论
0 点赞
2018-01-06
hexo next主题为博客添加分享功能
今天心血来潮,决定给博客添加分享功能,百度上首先是找到了使用shareSDK的分享功能,最后在实践的过程中发现它添加时步骤比较多,添加完成后效果比较丑(就是一个长条的浅蓝色按钮),而且点击后想要退出分享比较麻烦(它的取消按钮实在太难找了,它在页面最下方的位置,呈现浅灰色,这个设计太反人类了,决定放弃它了)。在next主题的官方的文档中发现它自身集成了百度分享的功能,所以决定采用百度了。
2018年01月06日
13 阅读
0 评论
0 点赞
2018-01-01
2017总结与2018规划
时间如白驹过隙,回想起过去的2017年仿佛就在昨天,元旦做了几天咸鱼,没有能够及时更新自己的年终总结,在此补上回望2017回望2017,在4月份我换了一份工作,之前的工作实在太闲了,我感觉这样不利于自己的进步,于是在3月份的时候正式提出离职,在4月份进入新公司。工作感悟新公司主要是做Web安全的,而我主要负责一款扫描器插件的开发于扫描器本身的维护工作。这份工作经常给我带来惊喜,就比如说sql注入,正常人的思维是输入一个值,然后在数据库中查询,但是sql注入就不是,它会在输入的查询条件中带上sql语句,这与传统的方式不同,它给我一种耳目一新的感觉。我第一次学习到sql注入的原理时是那样的兴奋,感慨于它的不同寻常。同时也对安全从业者产生了一定的崇拜,他们一定是一些思维活跃的人,是一群打破常规的人,这些都是我决定从事安全行业,努力融入安全圈内。这一年收获很多,在这一年中学习了一些常见漏洞的原理,攻击与防御的相关知识,通过阅读公司扫描器的源代码,学习了一些阅读源代码的方法,虽说还没有总结出一整套的方法论,但是也有一些思路。并根据这些方法我找到对应的代码并进行了一些修改。另外,在这一年中学会了一些脚本语言,比如Python、JavaScript。我自己感觉虽然扫描器是使用VC++语言编写的,但是我在这一年中本身使用最多的反而是Python。在刚入职时使用Python开发了扫描器对于ajax部分的支持,使其能够正常爬取到使用JavaScript动态生成的脚本。后来又使用Python开发了大量的漏洞扫描插件。现在又在使用Python开发一款网站监控软件。可以说这一年如果不是自己一直在自学VC的内容,可能现在C的东西都忘干净了^_^由于需要与Web程序打交道,所以一些理论方面的内容必定会涉及到,这一年主要详细了解了http协议,但是主要是在数据包层面上的,比如http的协议头、协议体等等,对于网络底层的东西仍然不是太明白,这也是目前的一个短板。后来为了方便,使用Python发送网络包的时候主要使用的requests库。知道了使用requests库定制协议头、判断响应包等等操作。但是反而对原生的urllib库不太了解。总结这一年我感觉最有用处的项目就是当时我使用Python + webkit写的一个web2.0的爬虫以及现在的一个web监控系统。web监控目前没有完成,所以暂时不提它,这里主要说说web2.0爬虫项目web2.0爬虫参考书籍《白帽子将web扫描》一书,基本代码都是按照书中的思路来的。这个项目使我详细了解了http协议,正则表达式、xpath表达式等等。为了使用webkit,项目主体是一个qt的任务调度部分负责调度由扫描器传过来的需要解析的url,每当有一个url过来的时候,都会触发一个自定义的事件,然后由qt调用对应的槽函数。其实我原本打算使用Python的线程池,来进行调度,但是由于使用的是webkit的Python封装——ghost库,这个库有一个好处就是能够捕捉到所有的网络请求,这样我可以很方便的得到ajax异步加载时请求的URL,从而得到一些额外的url,当时ghost库使用的是全局的app类对象,qt又不允许跨线程访问对象,所以没办法取消了多线程。后来经过我的测试在性能上虽然有一些损失但是损失在能接受的范围内。在解析url时候主要解析下面几种:在一些常规标签中的url,对于这种采用的是常规的xpath表达式出现在文本内容中的链接,或者JavaScript脚本中的链接,这种采用的是正则表达式需要进行异步请求的链接,这种链接主要出现在一些JavaScript注册的事件中。这种链接主要使用ghost库,获取所有网络请求,然后解析其中的url还有就是一些需要进行渲染之后才能出现的链接,对于这种链接,采用的是ghost库中的webkit进行渲染然后解析生成的HTML其余事项我在国庆期间去了一趟深圳,见了一下之前的大学同学,虽然很遗憾,不少人都回去了,但是我见到了当年的室友,以及领我上安全这条路的大牛同学。在于大牛同学的一些交谈中,我发现自己进步的没有想象中那么大,这些都激励着我继续努力不足2017虽然有进步,但是也有许多的不足:生活过于懒散:最大的毛病在于生活过于懒散,没有规律。经常周五熬夜做自己感兴趣的事,一旦这件事做完了或者碰到瓶颈没法解决的时候,后面几天就成了一无是处的咸鱼。手机占据了大量的业余时间,在这一年中,公司很多时候没有什么大事,加班比较少,我虽然回来的早,但是大量的时间用在玩手机上,根据我自己的观察,差不多从7点到8点半的时间很多时候都是在玩手机。而且有时候在学习的时候集中不了精力,时不时会看看手机,这样极大的影响了注意力。缺少锻炼:要说这一年什么收获最大,那应该是我身上的30斤肥肉T_T,从年初的100多斤涨到现在快130斤,在加上自己经常久坐,导致现在有时候稍微活动一下就浑身不舒服,浑身酸痛。18年需要改变这一现状读书太少:这一条与玩手机太多有很大的关系,长时间玩手机,使读书的时间压缩了,有时候200多页的书需要看个把月,总体算下来,今年一年加上在kindle上的书共有6本,明年可能要花更多时间在读书上展望2018生活习惯针对上述出现的问题2018年决定做如下改变:多锻炼:目前计划在3月份房子到期找到新地方后就近找一个健身房办张卡,经常去锻炼身体,并且强迫自己每个星期必须出去走走按时休息:每天按时休息不再分周末和平时,到点了放下手机或者手中的事睡觉,周末白天尽量在8点前起来,保证每天早上出去吃个早餐尝试自己做饭,少点外卖学习计划详细学习网络原理系统学习渗透测试,一些常见漏洞的攻击与防御学习一些逆向的知识学习一些Windows驱动编程的东西看完之前的一套VC高级编程的视频,并针对每个知识点编写相应的代码多写博客,做到每周一篇技术博客,尝试写一些读书笔记、鸡汤、项目总结类的文章将CSDN博客中之前写的VC反汇编系列、VC高级编程系列的内容慢慢转到自己在GitHub的博客上我的博客合理利用周末,周末做到每天玩手机时间不超过2小时,平时不超过半小时2018书单2018年计划读如下书目:1.《白帽子讲Web安全》2.《TCP\IP协议》3.《数学之美》4.《人类简史》5.《未来简史》6.《腾讯传》7.《古龙全集》(利用坐地铁的时间)8.《球状闪电》 最后祝我与所有朋友2018越来越好
2018年01月01日
6 阅读
0 评论
0 点赞
2017-12-19
为 MariaDB 配置远程访问权限
最近在配置MySQL远程连接的时候发现我的MySQL数据库采用的是 MariaDB 引擎,与普通的数据库配置有点不同经过查找资料终于完成了,特此记录方便以后查询MariaDB 与普通的MySQL数据库的一个不同在于它的配置文件不止一个,它将不同的数据放入到不同的配置文件中,之前的/etc/mysql/my.cnf内容如下:从文件中的注释上来看,它主要有这么几个配置文件/etc/mysql/mariadb.cnf 默认配置文件,/etc/mysql/conf.d/*.cnf 设置全局项的文件"/etc/mysql/mariadb.conf.d/*.cnf" 设置与MariaDB相关的信息"~/.my.cnf" 设置该账户对应的信息这也就是为什么我们在my.cnf做相关设置有的时候不起作用(可能在其他配置文件中有相同的项,MySQL最终采用的是另外一个文件中的设置)。根据官方的说法, MariaDB为了提高安全性,默认只监听127.0.0.1中的3306端口并且禁止了远程的TCP链接,我们可以通过下面两步来开启MySQL的远程服务注释掉skip-networking选项来开启远程访问.注释bind-address项,该项表示运行哪些IP地址的机器连接,允许所有远程的机器连接但是配置文件这么多,这两选项究竟在哪呢?这个时候使用grep在/etc/mysql/目录中的所有文件中递归查找,看哪个文件中含有这个字符串我们输入:grep -rn "skip-networking" *,结果如下:十分幸运的是这两项都在同一文件中(我自己的是没有skip-networking项)我们打开文件/etc/mysql/mariadb.conf.d/50-server.cnf,注释掉bind-address项,如下:只有这些仍然不够,我们只是开启了MySQL监听远程连接的选项,接下来需要给对应的MySQL账户分配权限,允许使用该账户远程连接到MySQL输入select User, host from mysql.user;查看用户账号信息:root账户中的host项是localhost表示该账号只能进行本地登录,我们需要修改权限,输入命令:GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY 'password' WITH GRANT OPTION;修改权限。%表示针对所有IP,password表示将用这个密码登录root用户,如果想只让某个IP段的主机连接,可以修改为GRANT ALL PRIVILEGES ON *.* TO 'root'@'192.168.100.%' IDENTIFIED BY 'my-new-password' WITH GRANT OPTION;注意:此时远程连接的密码可能与你在本地登录时的密码不同了,主要看你在IDENTIFIED BY后面给了什么密码具体的请参考GRANT命令最后别忘了FLUSH PRIVILEGES;保存更改。再看看用户信息:这个时候发现相比之前多了一项,它的host项是%,这个时候说明配置成功了,我们可以用该账号进行远程访问了输入service mysql restart重启远程服务器,测试一下:如果这些都做完了,还是不能连接,可以看一下端口是不是被防火墙拦截了参考地址:官方文档
2017年12月19日
44 阅读
2 评论
0 点赞
2017-12-07
向上取整算法
在进行内存分配的时候一般都需要在实际使用内存大小的基础上进行内存对齐,比如一般32位平台进行4字节对齐,而64位平台使用8字节对齐等等。一般采用的算法是先利用公式$$int(\frac{a + b - 1} { b})$$(其中a是实际使用的内存, b是对齐值)然后根据这个值乘以b即可得到对应的对齐值公式推导$ 假设 A = NB +M (M \in \left[0,B-1\right])$$\because\frac{A}{B} = N + \frac{M}{B}$$\because M \in\left[0, B-1\right]$$\therefore \frac{A}{B} \leq \frac{NB +B -1}{B} \leq \frac{A + B -1}{B}$从上面可以得出$\frac{A}{B}$向上取整可能是int($\frac{A+B-1}{B}$)但是具体是否有比它小的整数,仍然不能确定.因此我们根据推导一下这个结果与$\frac{A}{B}$向上取整的结果是否相同$ 假设 A = NB +M (M \in \left[0,B-1\right])$$当M = 0时UP(\frac{A}{B}) = N$$当M \neq 0时,UP(\frac{A}{B}) = N$而针对int($\frac{A+B-1}{B}$)的结果$当M = 0时int(\frac{A+B-1}{B} )=int(\frac{NB+B -1}{B}) = int(N + 1 - \frac{1}{B}) $$\because \frac{1}{B} < 1$$\therefore int(\frac{A+B-1}{B} )$$当 M \neq 0 时int(\frac{A+B-1}{B} ) = int(\frac{NB +M + B -1}{B}) = int(N + 1 + \frac{M-1}{B})$$当M = 1时 int(\frac{A+B-1}{B} ) = int(N + 1 + \frac{M-1}{B}) = N + 1$$当1 < M \leq B-1时 \frac{M -1}{B} < 1$<br/>$\therefore int(\frac{A+B-1}{B} ) = int(N + 1 + \frac{M-1}{B}) = N$从上面的推导来看二者的值完全相同所以可以得出结论$UP(\frac{A}{B}) = int(\frac{A + B - 1}{B})$所以当我们对A字节的内存进行B字节的对齐时可以使用公式$int(\frac{A + B - 1}{B}) \times B $补充其实还有一个算法long(A + B - 1) &~ (B - 1)也可以计算,但是我没有弄清楚它的原理是什么,暂时不管先记住再说^_^
2017年12月07日
7 阅读
0 评论
0 点赞
2017-11-28
python检测404页面
某些网站为了实现友好的用户交互,提供了一种自定义的错误页面,而不是显示一个大大的404 ,比如CSDN上的404提示页面如下:这样虽然提高了用户体验,但是在编写对应POC进行检测的时候如果只根据返回的HTTP头部信息判断,则很可能造成误报,为了能准确检测到404页面,需要从状态码和页面内容两个方面来进行判断。从状态码来判断比较简单。可以直接使用requests库发送http请求,得到响应码即可。从页面内容上进行判断的话,采用的思路是访问web站点上明显不存在的页面,获取页面内容进行保存,然后访问目标页面,将二者进行比较,如果相似度达到某一阈值,则该页面为404页面,否则为正常页面。为了判断两个页面的相似度,采用Python的simhash库,这个库具体实现的算法我不太懂,但是Python的好处就是:不懂无所谓,直接拿来用就行。这里也只是简单的拿来用一下:#-*- encoding:utf-8 -*- # 404 页面识别 from hashes.simhash import simhash import requests class page_404: def __init__(self, domain): #检测站点 self._404_page = [] # 404页面 self._404_url = [] #404 url self._404_path = ["test_404.html", "404_test.html", "helloworld.html", "test.asp?action=modify&newsid=122%20and%201=2%20union%20select%201,2,admin%2bpassword,4,5,6,7%20from%20shopxp_admin"] #404页面路径,用于生成一部分404页面 self._404_code = [200, 301, 302] #当前可能是404页面的http请求的返回值 #自己构造404url,以便收集一些404页面的信息 for path in self._404_path: for path in self._404_path: if domain[-1] == "/": url = domain + path else: url = domain + "/" + path response = requests.get(url) if response.status_code in self._404_code: self.kb_appent(response.content, url) def kb_appent(self, _404_page, _404_url): if _404_page not in self._404_page: self._404_page.append(_404_page) if _404_url not in self._404_url: self._404_url.append(_404_url) def is_similar_page(self, page1, page2): hash1 = simhash(page1) hash2 = simhash(page2) similar = hash1.similarity(hash2) if similar > 0.85: #当前阈值定义为0.85 return True else: return False def is_404(self, url): if url in self._404_url: return True response = requests.get(url) if response.status_code == 404: return True if response.status_code in self._404_code: for page in self._404_page: if self.is_similar_page(response.content, page): self.kb_appent(url, response.content) #如果是404页面,则保存当前的url和页面信息 return True else: return False return False上面的代码中,检测类中主要保存了这样几个信息:_404_page:404页面,用于与其他请求的页面进行相似度判断,以便识别404页面,这里用列表主要为了防止一个站点有多种404页面,这段代码运行时间越长它的准确度越高_404_url:404 页面的url,保存之前判断出页面是404的url,已经判断出来的就不再判断,为了提升效率_404_path:构建不存在页面的url,最后一个是一个sql注入的代码,这里为了识别出那些被防火墙拦截而显示的错误页面_404_code:可能返回404页面的响应码,如果响应码为这些,则需要对页面进行判断类在初始化时需要传入一个域名,根据这个域名来拼接几个不存在的或者会被防火墙拦截的请求并提交这些请求,得到返回信息,将这些信息作为判断的信息进行保存。在判断时首先根据之前保存的404 url信息进行判断,如果当前url是404页面则直接返回,提高效率。然后提交正常的http请求并获取响应信息,如果响应码为404则返回True,否则再状态码是否在_404_code列表中,最后再与之前保存的404页面信息进行比较得到结果。这段代码的测试代码如下:from page_404 import page_404 if __name__ == '__main__': domain = "http://xzylrd.gov.cn" check_404 = page_404(domain) dest_url = "http://xzylrd.gov.cn/TEXTBOX2.ASP?action=modify&newsid=122%20and%201=2%20union%20select%201,2,admin%2bpassword,4,5,6,7%20from%20shopxp_admin" print (check_404.is_404(dest_url))
2017年11月28日
6 阅读
0 评论
0 点赞
1
...
23
24
25
...
34