Masimaro's blog - windows
type: channel
link: https://www.masimaro.top/tag/windows/
items: xampp 中 mysql的相关配置
items: Windows下的代码注入
items: VC++ 崩溃处理以及打印调用堆栈
items: WinSock Socket 池
items: WinSock2 API
items: WinSock 完成端口模型
items: WinSock 重叠IO模型
items: WinSock WSAEventSelect 模型
items: WSAAsyncSelect 消息模型
items: ATL模板库中的OLEDB与ADO

xampp 中 mysql的相关配置
type: item
date: 2018-10-14T15:13:00+08:00
description: 最近开始接触PHP，而一般搭建PHP环境使用的都是xampp 这个集成环境，由于之前我的系统中已经安装了mysql服务，所以在启动mysql的时候出现一些列错误，我通过查询各种资料解决了这个问题，现在记录一下，方便日后遇到同样的问题时能够快速解决，也为遇到同样问题的朋友提供一种解决的思路。启动刚开始时我在点击启动mysql的时候发现它一直卡在尝试启动mysql这个位置，xampp提示内容如下:Attempting to start MySQL service...它启动不成功但是也不提示出错，而且查询日志发现没有错误的日志，这个时候我想到应该是我本地之前安装了mysql，导致失败。而且我还将mysql安装成为了服务，后来查询相关资料，有网友说需要将mysql服务的地址改为xampp下mysql所在地址，具体怎么改我就不写了，一般都可以找到，但是我想说的是，这个方式好像在我这边不起作用。那么就干脆一点直接删除服务就好了。sc delete mysql上述命令直接删除mysql这个服务。然后重启xampp，再次启动mysql，它终于报错了。只要报错就好说了，现在来查询日志，发现日志如下:2018-10-13 22:52:19 37d0 InnoDB: Warning: Using innodb_additional_mem_pool_size is DEPRECATED. This option may be removed in future releases, together with the option innodb_use_sys_malloc and with the InnoDB's internal memory allocator. 2018-10-13 22:52:19 14288 [Note] InnoDB: innodb_empty_free_list_algorithm has been changed to legacy because of small buffer pool size. In order to use backoff, increase buffer pool at least up to 20MB. 2018-10-13 22:52:19 14288 [Note] InnoDB: Using mutexes to ref count buffer pool pages 2018-10-13 22:52:19 14288 [Note] InnoDB: The InnoDB memory heap is disabled 2018-10-13 22:52:19 14288 [Note] InnoDB: Mutexes and rw_locks use Windows interlocked functions 2018-10-13 22:52:19 14288 [Note] InnoDB: _mm_lfence() and _mm_sfence() are used for memory barrier 2018-10-13 22:52:19 14288 [Note] InnoDB: Compressed tables use zlib 1.2.3 2018-10-13 22:52:19 14288 [Note] InnoDB: Using generic crc32 instructions 2018-10-13 22:52:19 14288 [Note] InnoDB: Initializing buffer pool, size = 16.0M 2018-10-13 22:52:19 14288 [Note] InnoDB: Completed initialization of buffer pool 2018-10-13 22:52:19 14288 [Note] InnoDB: Highest supported file format is Barracuda. 2018-10-13 22:52:19 14288 [Warning] InnoDB: Resizing redo log from 2*3072 to 2*320 pages, LSN=1600607 2018-10-13 22:52:19 14288 [Warning] InnoDB: Starting to delete and rewrite log files. 2018-10-13 22:52:19 14288 [Note] InnoDB: Setting log file C:\xampp\mysql\data\ib_logfile101 size to 5 MB 2018-10-13 22:52…

Windows下的代码注入
type: item
date: 2018-09-09T15:22:00+08:00
description: 木马和病毒的好坏很大程度上取决于它的隐蔽性，木马和病毒本质上也是在执行程序代码，如果采用独立进程的方式需要考虑隐藏进程否则很容易被发现，在编写这类程序的时候可以考虑将代码注入到其他进程中，借用其他进程的环境和资源来执行代码。远程注入技术不仅被木马和病毒广泛使用，防病毒软件和软件调试中也有很大的用途，最近也简单的研究过这些东西，在这里将它发布出来。想要将代码注入到其他进程并能成功执行需要解决两个问题：第一个问题是如何让远程进程执行注入的代码。原始进程有它自己的执行逻辑，想要破坏原来的执行流程，使EIP寄存器跳转到注入的代码位置基本是不可能的第二个问题是每个进程中地址空间是独立的，比如在调用某个句柄时，即使是同一个内核对象，在不同进程中对应的句柄也是不同的，这就需要进行地址转化。要进行远程代码注入的要点和难点主要就是这两个问题，下面给出两种不同的注入方式来说明如何解决这两个问题DLL注入DLL注入很好的解决了第二个问题，DLL被加载到目标进程之后，它里面的代码中的地址就会自动被转化为对应进程中的地址，这个特性是由于DLL加载的过程决定的，它会自己使用它所在进程中的资源和地址空间，所以只要DLL中不存在硬编码的地址，基本不用担心里面会出现函数或者句柄需要进行地址转化的问题。那么第一个问题改怎么解决呢？要执行用户代码，在Windows中最常见的就是使用回调的方式，Windows采用的是事件驱动的方式，只要发生了某些事件就会调用回调，在众多使用回调的场景中，线程的回调是最简单的，它不会干扰到目标进程的正常执行，也就不用考虑最后还原EIP的问题，因此DLL注入采用的最常见的就是创建一个远程线程，让线程加载DLL代码。DLL注入中一般的思路是：使用CreateRemoteThread来在目标进程中创建一个远程的线程，这个线程主要是加载DLL到目标进程中，由于DLL在入口函数(DLLMain)中会处理进程加载Dll的事件，所以将注入代码写到这个事件中，这样就能执行注入的代码了。那么如何在远程进程中执行DLL的加载操作呢？我们知道加载DLL主要使用的是函数LoadLibrary,仔细分析线程的回调函数和LoadLibrary函数的原型，会发现，它们同样都是传入一个参数，而CreateRemoteThread函数正好需要一个函数的地址作为回调，并且传入一个参数作为回调函数的参数。这样就有思路了，我们让LoadLibrary作为线程的回调函数，将对应dll的文件名和路径作为参数传入，这样就可以在对应进程中加载dll了，进一步也就可以执行dllmain中的对应代码了。还有一个很重要的问题，我们知道不同进程中，地址空间是隔离的，那么我在注入的进程中传入LoadLibrary函数的地址，这算是一个硬编码的地址，它在目标进程中是否是一样的呢？答案是，二者的地址是一样的，这是由于kernel32.dll在32位程序中加载的基地址是一样的，而LoadLibrary在kernel32.dll中的偏移是一定的（只要不同的进程加载的是同一份kernel32.dll）那么不同进程中的LoadLibrary函数的地址是一样的。其实不光是LoadLibrary函数，只要是kernel32.dll中导出的函数，在不同进程中的地址都是一样的。注意这里只是32位，如果想要使用32位程序往64位目标程序中注入，可能需要考虑地址转换的问题，只要知道kernel32.dll在64位中的偏移，就可以计算出对应函数的地址了。LoabLibrary函数传入的代表路径的字符串的首地址在不同进程中同样是不同的，而且也没办法利用偏移来计算，这个时候解决的办法就是在远程进程中申请一块虚拟地址空间，并将目标字符串写入对应的地址中，然后将对应的首地址作为参数传入即可。最后总结一下DLL注入的步骤：获取LoadLibrary函数的地址调用VirtualAllocEx 函数在远程进程中申请一段虚拟内存调用WriteProcessMemory 函数将参数写入对应的虚拟内存调用CreateRemoteThread 函数创建远程线程，线程的回调函数为LoadLibrary，参数为对应的字符串的地址按照这个思路可以编写如下的代码:typedef HMODULE(WINAPI *pfnLoadLibrary)(LPCWSTR); if (!DebugPrivilege()) //提权代码，在Windows Vista 及以上的版本需要将进程的权限提升，否则打开进程会失败 { return FALSE; } //打开目标进程 HANDLE hRemoteProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid); //dwPid是对应的进程ID if (NULL =…

VC++ 崩溃处理以及打印调用堆栈
type: item
date: 2018-08-28T15:56:00+08:00
description: 我们在程序发布后总会面临崩溃的情况，这个时候一般很难重现或者很难定位到程序崩溃的位置，之前有方法在程序崩溃的时候记录dump文件然后通过windbg来分析。那种方法对开发人员的要求较高，它需要程序员理解内存、寄存器等等一系列概念还需要手动加载对应的符号表。Java、Python等等语言在崩溃的时候都会打印一条异常的堆栈信息并告诉用户那块出错了，根据这个信息程序员可以很容易找到对应的代码位置并进行处理，而C/C++则会弹出一个框告诉用户程序崩溃了，二者对比来看，C++似乎对用户太不友好了，而且根据它的弹框很难找到对应的问题，那么有没有可能使c++像Java那样打印异常的堆栈呢？这个自然是可能的，本文就是要讨论如何在Windows上实现类似的功能异常处理一般当程序发生异常时，用户代码停止执行，并将CPU的控制权转交给操作系统，操作系统接到控制权后，将当前线程的环境保存到结构体CONTEXT中，然后查找针对此异常的处理函数。系统利用结构EXCEPTION_RECORD保存了异常描述信息，它与CONTEXT一同构成了结构体EXCEPTION_POINTERS，一般在异常处理中经常使用这个结构体。异常信息EXCEPTION_RECORD的定义如下：typedef struct _EXCEPTION_RECORD { DWORD ExceptionCode; //异常码 DWORD ExceptionFlags; //标志异常是否继续，标志异常处理完成后是否接着之前有问题的代码 struct _EXCEPTION_RECORD* ExceptionRecord; //指向下一个异常节点的指针，这是一个链表结构 PVOID ExceptionAddress; //异常发生的地址 DWORD NumberParameters; //异常附加信息 ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS]; //异常的字符串 } EXCEPTION_RECORD, *PEXCEPTION_RECORD;Windows平台提供的这一套异常处理的机制，我们叫它结构化异常处理(SEH)，它的处理过程一般如下:如果程序是被调试运行的（比如我们在VS编译器中调试运行程序），当异常发生时，系统首先将异常信息交给调试程序，如果调试程序处理了那么程序继续运行，否则系统便在发生异常的线程栈中查找可能的处理代码。若找到则处理异常，并继续运行程序如果在线程栈中没有找到，则再次通知调试程序，如果这个时候仍然不能处理这个异常，那么操作系统会对异常进程默认处理，这个时候一般都是直接弹出一个错误的对话框然后终止程序。系统在每个线程的堆栈环境中都维护了一个SEH表，表中是用户注册的异常类型以及它对应的处理函数，每当用户在函数中注册新的异常处理函数，那么这个信息会被保存在链表的头部，也就是说它是采用头插法来插入新的处理函数，从这个角度上来说，我们可以很容易理解为什么在一般的高级语言中一般会先找与try块最近的catch块，然后在找它的上层catch，由里到外依次查找。与try块最近的catch是最后注册的，由于采用的是头插法，自然它会被首先处理。在Windows中针对异常处理，扩展了__try 和 __except 两个操作符，这两个操作符与c++中的try和catch非常相似,作用也基本类似，它的一般的语法结构如下:__try { //do something } __except(filter) { //handle }使用 __try 和 __except 的时候它主要分为3个部分，分别为:保护代码体、过滤表达式、异常处理块保护代码体一般是try中的语句，它值被保护的代码，也就是说我们希望处理那个代码块产生的异常过滤表达式是 except后面扩号中的值，它只能是3个值中的一个，EXCEPTION_CONTINUE_SEARCH继续向下查找异常处理，也就是说这里的异常处理块不处理这种异常，EXCEPTION_CONTINUE_EXECUTION表示异常已被处理，这个时候可以继续执行直线产生异常的代码，EXCEPTION_EXECUTE_HANDLER表示异常已被处理，此时直接跳转到except里面的代码块中，这种方式下它的执行流程与一般的异常处理的流程类似.异常处理块，指的是except下面的扩号中的代码块.注意：我们说过滤表达式只能是这三个值中的一个，但是没有说这里一定得填这三个值，它还支持函数或者其他的表达式类型，只要函数或者表达式的返回值是这三个值中的一个即可。上述的方式也有他的局限性，也就是说它只能保护我们指定的代码，如果是在 __try 块之外的代码发生了崩溃，可能还是会造成程序被kill掉，而且每个位…

WinSock Socket 池
type: item
date: 2018-08-12T16:23:00+08:00
description: 之前在WinSock2.0 API 中说到，像DisConnectEx 函数这样，它具有回收SOCKET的功能，而像AcceptEx这样的函数，它不会自己在内部创建新的SOCKET，需要外部传入SOCKET作为传输数据用的SOCEKT，使用这两个函数，我们可以做到，事先创建大量的SOCKET，然后使用AcceptEx函数从创建的SOCKET中选择一个作为连接用的SOCKET，在不用这个SOCKET的时候使用DisConnectEx回收。这样的功能就是一个SOCKET池的功能。SOCKET池WinSock 函数就是为了提升程序的性能而产生的，这些函数主要使用与TCP协议，我们可以在程序启动的时候创建大量的SOCKET句柄，在必要的时候直接使用AcceptEx这样的函数来使用已有的SOCKET作为与客户端的连接，在适当的时候使用WSARecv、WSASend等函数金星秀数据收发操作。而在不用SOCKET的时候使用DisConnectEx 回收，这样在响应用户请求的时候就省去了大量SOCKET创建和销毁的时间，从而提高的响应速度。IOCP本身也是一个线程池，如果用它结合WinSock 的线程池将会是Windows系统上最佳的性能组合，当然在此基础上可以考虑加入线程池、内存池的相关技术来进一步提高程序的性能。这里我想顺便扯点关于程序优化的理解。程序优化主要考虑对函数进行优化，毕竟在C/C++中函数是最常用，最基本的语法块，它的使用十分常见。函数的优化一般有下面几个需要考虑的部分是否需要大量调用这个函数。针对需要大量调用某个函数的情况，可以考虑对算法进行优化，减少函数的调用函数中是否有耗时的操作，如果有可以考虑使用异步的方式，或者将函数中的任务放到另外的线程（只有当我们确实不关心这个耗时操作的结果的时候，也就是说不涉及到同步的时候）函数中是否有大量的资源调用，如果有，可以考虑使用资源池的方式避免大量资源的申请与释放操作下面是一个使用SOCKET池的客户端的实例#include <stdio.h> #include <process.h> #include "MSScokFunc.h" #define SERVICE_IP "119.75.213.61" #define MAX_CONNECT_SOCKET 500 unsigned int WINAPI IOCPThreadProc(LPVOID lpParam); LONG g_nPorts = 0; CMSScokFunc g_MsSockFunc; typedef struct _tag_CLEINT_OVERLAPPED { OVERLAPPED overlapped; ULONG ulNetworkEvents; DWORD dwFlags; DWORD wConnectPort; DWORD dwTransBytes; char *pBuf; DWORD dwBufSize; SOCKET sConnect; }CLIENT_OVERLAPPED, *LPCLIENT_OVERLAPPED; SOCKADDR_IN g_LocalSockAddr = {AF_INET}; int main() { WSADATA wd = {0}; WSAStartup(MAKEWORD(2, 2), &wd); SYSTEM_INFO si = {0}; const int on = 1; GetSystemInfo(&si); HANDLE hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, NULL, si.dwNumberOfProcessors); HANDLE *pThreadArray = (HANDLE *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(HANDLE) * 2 * si.dwNumberOfProcessors); for (int i = 0; i < 2 * si.dwNumberOfProcessors; i++) { HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, IOCPThreadProc, &hIOCP, 0, NULL); pThreadArray[i] = hThread; } g_MsSockFunc.LoadAllFunc(AF_INET, SOCK_STREAM, IPPROTO_IP); LPCLIENT_OVERLAPPED *pOverlappedArray = (LPCLIENT_OVERLAPPED *)HeapAlloc(GetProcessHeap(…

WinSock2 API
type: item
date: 2018-07-21T16:44:00+08:00
description: WinSock中提供的5种网络模型已经可以做到很高效了，特别是完成端口，它的高效的原因在于它不仅另外开启了线程来处理完成通知而不是占用主程序的时间，同时也在于我们在完成端口中运用了大量异步IO处理函数。比如WSASend、WSARecv等等。为了高效的处理网络IO，WinSock提供了大量这样的异步函数。这篇博文主要探讨这些函数的用法和他们与传统的巴克利套接字相比更加高效的秘密AcceptEx其实在使用TCP协议编程时，接受连接的过程也是需要进行收发包操作的，具体的过程请参考TCP的三次握手。针对这种特性WinSock提供了对应的异步操作函数AcceptEx。函数原型如下:BOOL AcceptEx( SOCKET sListenSocket, SOCKET sAcceptSocket, PVOID lpOutputBuffer, DWORD dwReceiveDataLength, DWORD dwLocalAddressLength, DWORD dwRemoteAddressLength, LPDWORD lpdwBytesReceived, LPOVERLAPPED lpOverlapped );sListenSocket: 监听套接字sAcceptSocket：该参数是一个SOCKET的句柄，一旦连接成功建立，那么会使用该SOCKET作为通信的SOCKETlpOutputBuffer：是三个数据一体化的缓冲区的指针，这三个数据分别是接收连接时顺带接收客户端发过来的数据的缓冲，之后是本地地址结构的缓冲，最后是远程客户端地址结构的指针dwReceiveDataLength：是lpOutputBuffer的缓冲长度dwLocalAddressLength：是本地地址结构长度,其值等于sizeof(SOCKADDR)+16dwRemoteAddressLength：是远程客户端地址结构长度,其值也等于sizeof(SOCKADDR)+16lpdwBytesReceived：该参数用于返回接受连接请求时接收的数据的长度lpOverlapped：就是重叠I/O需要的结构第一个参数是一个十分重要的参数，这个参数是AcceptEx比较高效的一个重要的原因。从功能上来看它与传统的accept函数并没有什么区别，都是接受客户端连接的。它与accpet相比比较高效的原因如下：从内部机理来说accpet在内部其实有一个创建SOCKET的操作，当函数成功后会返回这个SOCKET，所以AcceptEx与accept相比少了一个创建SOCKET的操作，它的功能更加纯粹，这就给了我们一个启示：我们可以在初始化的时候创建大量的SOCKET，并投递到AcceptEx中，这样在接受连接时省去了创建SOCKET的时间，能够更快速的响应客户端的连接。由于AcceptEx不用创建SOCKET，所以它也将accept内部对socket设置的操作给省去了，也少了一些其他的附带操作，比如地址的解析，其实这里我们可以简单的理解为lpOutputBuffer中保存的信息就是TCP三次握手中的SYN包和ACK包，这些包的信息需要在函数返回后由用户通过其他方法来解析，而accpet帮我们解析了，所以AcceptEx比accept更加高效因为AcceptEx的设计目标纯粹就是为了性能,所以监听套接字的属性不会被代表客户端通讯的套接字自动继承。要继承属性(包括socket内部接受/发送缓存大小等等)就必须调用setsockopt使用选项SO_UPDATE_ACCEPT_CONTEXT,如下:int nRet = ::setsockopt(skClient, SOL_SOCKET ,SO_UPDATE_ACCEPT_CONTEXT ,(char *)&skListen, sizeof(skListen));AcceptEx函数除了能够接受客户端的连接之外，它也可以在接受连接的同时接收客户端随着连接请求一块发过来的数据，只要我们设置dwReceiveDataLength 参数大于0，并在lpOutputBuffer中分配相应的缓冲即可，但是这里会存在一个安全问题，当我们设置了这些之后，如果客户端只发送连接请求，但是不发送数据，AcceptEx会一直等待，如果有大量这样的客户端，那么可能会给服务器造成大量的资源浪费从而不能及时的服务其他正常客户端。要防止这样的情况，可以采用下列措施：设置dwReceiveDataLength为0，并且不分配对应的缓冲，也就是关闭这个接收数据的功能。启动一个监视线程对用于连接的SOCKET轮询调用：int iSecs; int iBytes = sizeof( int ); getsockopt( hAcceptSocket, SOL_SOCKET, …

WinSock 完成端口模型
type: item
date: 2018-07-06T16:50:00+08:00
description: title: WinSock 完成端口模型tags: [WinSock 模型, 网络编程, 完成端口]date: 2018-07-06 20:44:39categories: Windows 网络编程keywords: WinSock 模型, 网络编程, 完成端口之前写了关于Winsock的重叠IO模型，按理来说重叠IO模型与之前的模型相比，它的socket即是非阻塞的，也是异步的，它基本上性能非常高，但是它主要的缺点在于，即使我们使用历程来处理完成通知，但是我们知道历程它本身是在对应线程暂停，它借用当前线程的线程环境来执行完成通知，也就是说要执行完成通知就必须暂停当前线程的工作。这对工作线程来说也是一个不必要的性能浪费，这样我们自然就会想到，另外开辟一个线程来执行完成通知，而本来的线程就不需要暂停，而是一直执行它自身的任务。处于这个思想，WinSock提供了一个新的模型——完成端口模型。完成端口简介完成端口本质上是一个线程池的模型，它需要我们创建对应的线程放在那，当完成通知到来时，他会直接执行线程。在这5中模型中它的性能是最高的。在文件中我们也提到过完成端口，其实我们利用Linux上一切皆文件的思想来考虑这个问题就可以很方便的理解，既然我们需要异步的方式来读写网卡的信息，这与读写文件的方式类似，既然文件中存在完成端口模型，网络上存在也就不足为奇了。对于完成端口Windows没有引入新的API函数，而是仍然采用文件中一堆相关的函数。可以使用CreateIoCompletionPort来创建完成端口的句柄，该函数原型如下:HANDLE WINAPI CreateIoCompletionPort( __in HANDLE FileHandle, __in_opt HANDLE ExistingCompletionPort, __in ULONG_PTR CompletionKey, __in DWORD NumberOfConcurrentThreads );第一个参数是与完成端口绑定的文件句柄，如果我们要创建完成端口句柄，这个值必须传入INVALID_HANDLE_VALUE。如果是要将文件句柄与完成端口绑定，这个参数必须穿入一个支持完成端口的文件句柄。在Winsock中如果要绑定SOCKET到完成端口只需要将SOCKET强转为HANDLE。第二个参数是一个已知的完成端口句柄，如果是创建完成端口，这个参数填入NULL。第三个参数是一个LONG型的指针，它作为一个标志，由完成通知传入完成线程中，用来标识不同的完成通知。一般我们会定义一个扩展来OVERLAPPED结构来标识不同的完成通知，所以这个参数一般不用传入NULL。第四个参数是同时执行的线程数，如果是绑定文件句柄到完成端口，则这个参数填入0我们可以在对应的完成线程中调用GetQueuedCompletionStatus函数来获取完成通知，这个函数只有当有IO操作完成时才会返回，函数原型如下:BOOL WINAPI GetQueuedCompletionStatus( __in HANDLE CompletionPort, __out LPDWORD lpNumberOfBytes, __out PULONG_PTR lpCompletionKey, __out LPOVERLAPPED* lpOverlapped, __in DWORD dwMilliseconds ); 它的第一个参数是一个完成端口的句柄。第二个参数表示当前有多少字节的数据完成IO操作。第三个参数是一个标记值，用来标识不同文件句柄对应的完成通知，它是通过 CreateIoCompletionPort 函数设置的那个标识。第四个参数是OVERLAPPED结构。第五个参数表示等待的时间，如果填入INFINITE则会一直等到有IO操作完成。完成端口的示例：下面是一个完成端口的示例typedef struct _tag_MY_OVERLAPPED { OVERLAPPED m_overlapped; SOCKET m_sClient; long m_lEvent; DWORD m_dwNumberOfBytesRecv; DWORD m_dwFlags; char *m_pszBuf; LONG m_dwBufSize; }MY_OVERLAPPED, *LPMY_OVERLAPPED; unsigned int __stdcall IOCPThread(LPVOID lpParameter); #define BUFFER_SIZE 1024 #define SERVER_PORT 6000 int _tmain(int argc, TCHAR *argv) { WSADATA wd = {0}; WSAStar…

WinSock 重叠IO模型
type: item
date: 2018-06-29T17:47:00+08:00
description: title: WinSock 重叠IO模型tags: [WinSock 模型, 网络编程, 重叠IO模型]date: 2018-06-29 20:26:13categories: Windows 网络编程keywords: WinSock 模型, 网络编程, 重叠IO模型之前介绍的WSAAsyncSelect和WSAEvent模型解决了收发数据的时机问题，但是网卡这种设备相比于CPU和内存来说仍然是慢速设备，而调用send和recv进行数据收发操作仍然是同步的操作，即使我们能够在恰当的时机调用对应的函数进行收发操作，但是仍然需要快速的CPU等待慢速的网卡。这样仍然存在等待的问题，这篇博文介绍的重叠IO模型将解决这个等待的问题之前介绍的WSAAsyncSelect和WSAEvent模型解决了收发数据的时机问题，但是网卡这种设备相比于CPU和内存来说仍然是慢速设备，而调用send和recv进行数据收发操作仍然是同步的操作，即使我们能够在恰当的时机调用对应的函数进行收发操作，但是仍然需要快速的CPU等待慢速的网卡。这样仍然存在等待的问题，这篇博文介绍的重叠IO模型将解决这个等待的问题重叠IO简介一般接触重叠IO最早是在读写磁盘时提出的一种异步操作模型，它主要思想是CPU只管发送读写的命令，而不用等待读写完成，CPU发送命令后接着去执行自己后面的命令，至于具体的读写操作由硬件的DMA来控制，当读写完成时会向CPU发送一个终端信号，此时CPU中断当前的工作转而去进行IO完成的处理。这是在磁盘操作中的一种高效工作的方式，为什么在网络中又拿出来说呢？仔细想想，前面的模型解决了接收数据的时机问题，现在摆在面前的就是如何高效的读写数据，与磁盘操作做类比，当接收到WSAAsyncSelect对应的消息或者WSAEvent返回时就是执行读写操作的时机，下面紧接着就是调用对应的读写函数来进行读写数据了，而联想到linux中的一切皆文件的思想，我们是不是可以认为操作网卡也是在操作文件？这也是在WinSock1中，使用WriteFile和ReadFile来进行网络数据读写的原因。既然它本质上也是CPU需要等待慢速的设备，那么为了效率它必定可以支持异步操作，也就可以使用重叠IO。创建重叠IO的socket要想使用重叠IO，就不能在像之前那样使用socket函数来创建SOCKET, 这函数最多只能创建一个普通SOCKET然后设置它为非阻塞(请注意非阻塞与异步的区别)。要创建异步的SOCKET需要使用WinSock2.0函数 WSASocketSOCKET WSASocket( int af, int type, int protocol, LPWSAPROTOCOL_INFO lpProtocolInfo, GROUP g, DWORD dwFlags );该函数的前3个参数与socket的参数含义相同,第4个参数是一个协议的具体信息，配合WSAEnumProtocols 使用可以将枚举出来的网络协议信息传入，这样不通过前三个参数就可以创建一个针对具体协议的SOCKET。第5个参数目前不受支持简单的传入0即可。第6个参数是一个标志，如果要创建重叠IO的SOCKET，需要将这个参数设置为WSA_FLAG_OVERLAPPED。否则普通的SOCKET直接传入0即可使用重叠IO除了要将SOCKET设置为支持重叠IO外，还需要使用对应的支持重叠IO的函数，之前了解的巴克利套接字函数最多只能算是支持非阻塞而不支持异步。在WinSock1.0 中可以使用ReadFile和WriteFile来支持重叠IO，但是WinSock2.0 中重新设计的一套函数来支持重叠IOWSASend (send的等价函数)WSASendTo (sendto的等价函数)WSARecv (recv的等价函数)WSARecvFrom (recvfrom的等价函数)WSAIoctl (ioctlsocket的等价函数)WSARecvMsg (recv OOB版的等价函数)AcceptEx (accept 等价函数)ConnectEx (connect 等价函数)TransmitFile (专门用于高效发送文件的扩展API)TransmitPackets (专门用于高效发送大规模数据包的扩展API)DisconnectEx (扩展的断开连接的Winsock API)WSANSPIoctl (用于操作名字空间的重叠I/O版扩展控制API)那么如果使用上述函数但是传入一个非阻塞的SOCKET会怎么样呢，这些函数只看是否传入OVERLAPPED结构而不管SOCKET是否是阻塞的，一律按重叠IO的方式来运行。这也就是说,要使用重叠I/O方式来操作SOCKET,那么不一定非要一开初就创建一个重叠I/O方式的SO…

WinSock WSAEventSelect 模型
type: item
date: 2018-06-23T18:13:00+08:00
description: 在前面我们说了WSAAsyncSelect 模型，它相比于select模型来说提供了这样一种机制：当发生对应的IO通知时会立即通知操作系统，并调用对应的处理函数，它解决了调用send和 recv的时机问题，但是它有一个明显的缺点，就是它必须依赖窗口。对此WinSock 提供了另一种模型 WSAEventSelect模型简介该模型主要特色在于它使用事件句柄来完成SOCKET事件的通知。与WSAAsyncSelect 模型类似，它也允许使用事件对象来完成多个socket的完成通知。该模型首先在每个socket句柄上调用WSACreateEvent来创建一个WSAEvent对象句柄（早期的WSAEvent与传统的Event句柄有一定的区别，但是从WinSock2.0 以后二者是同一个东西）。接着调用WSAEventSelect将SOCKET句柄和WSAEvent对象绑定，最终通过WSAWaitForMultiEvents来等待WSAEvent变为有信号，然后再来处理对应的socketWSAEvent有两种工作模式和工作状态工作状态有有信号和无信号两种工作模式有手工重置和人工重置，手工重置指的是每当WSAWaitForMultiEvents或者WSAWaitForSingleEvents 返回之后，WSAEvent不会自动变为无信号，需要手工调用WSAResetEvent来将WSAEvent对象设置为无信号，而自动重置表示每次等待函数返回后会自动重置为无信号；调用WSACreateEvent创建的WSAEvent对象是需要手工重置的，如果想创建自动重置的WSAEvent对象可以调用CreateEvent函数来创建（由于WinSock2.0 之后二者没有任何区别，所以只需要调用CreateEvent并将返回值强转为WSAEvent即可）WSAEventSelect函数的原型如下:int WSAEventSelect( SOCKET s, WSAEVENT hEventObject, long lNetworkEvents);其中s表示对应的SOCKET，hEventObject表示对应的WSAEvent对象，lNetworkEvents 表示我们需要处理哪些事件，它有一些对应的宏定义网络事件对应的含义FD_READ当前可以进行数据接收操作，此时可以调用像 recv, recvfrom, WSARecv, 或者 WSARecvFrom 这样的函数FD_WRITE此时可以发送数据，可以调用 send, sendto, WSASend, or WSASendToFD_ACCEPT可以调用accept (Windows Sockets) 或者 WSAAccept 除非返回的错误代码是WSATRY_AGAIN.FD_CONNECT表示当前可以连接远程服务器FD_CLOSE当前收到关闭的消息当WSAWaitForMultipleEvents返回时同时会返回一个序号，用于标识是数组中的哪个WSAEvent有信号，我们使用 index - WSA_WAIT_EVENT_0 来获取对应WSAEvent在数组中的下标，然后根据这个事件对象找到对应的SOCKET即可获得了对应的SOCKET以后，还需要获取到当前是哪个事件发生导致它变为有信号，我们可以调用WSAEnumNetworkEvents函数来获取对应发生的网络事件int WSAEnumNetworkEvents( SOCKET s, WSAEVENT hEventObject, LPWSANETWORKEVENTS lpNetworkEvents );s就是要获取其具体事件通知的SOCKET句柄hEventObject就是对应的WSAEvent句柄,可以不传入,因为SOCKET句柄已经说明了要获取那个句柄上的通知,当然如果传入了,那么这个函数会对这个WSAEvent做一次重置,置为无信号的状态,相当于WSAResetEvent调用。此时我们就不需要调用WSAResetEvent函数了最后一个参数是一个结构，结构的定义如下:typedef struct _WSANETWORKEVENTS { long lNetworkEvents; int iErrorCode[FD_MAX_EVENTS]; } WSANETWORKEVENTS, *LPWSANETWORKEVENTS;第一个数据是当前产生的网络事件。iErrorCode数组是对应每个网络事件可能发生的错误代码,对于每个事件错误代码其具体数组下标是预定义的一组FD_开头的串再加上一个_BIT结尾的宏,比如FD_READ事件对应的错误码下标是FD_READ_BIT下面的代码演示了处理接收(读取)数据的事件错误的例子代码if (NetworkEvents…

WSAAsyncSelect 消息模型
type: item
date: 2018-06-03T09:42:00+08:00
description: select 模型虽然可以管理多个socket，但是它涉及到一个时机的问题，select模型会针对所管理的数组中的每一个socket循环检测它管理是否在对应的数组中，从时间复杂度上来说它是O(n^2)的,而且还有可能发生数组中没有socket处于待决状态而导致本轮循环做无用功的情况，针对这些问题，winsock中有了新的模型——WSAAsyncSelect 消息模型消息模型的核心是基于Windows窗口消息获得网络事件的通知，Windows窗口是用来与用户交互的，而它并不知道用户什么时候会操作窗口，所以Windows窗口本身就是基于消息的异步通知，网络事件本身也是一个通知消息，将二者结合起来可以很好的使socket通知像消息那样当触发通知时调用窗口过程。这样就解决了select中的时机问题和里面两层循环的问题WSAAsyncSelect函数原型如下:int WSAAsyncSelect( __in SOCKET s, __in HWND hWnd, __in unsigned int wMsg, __in long lEvent );第一个参数是绑定的socket，第二个参数是消息所对应的窗口句柄，第三个参数是对应的消息，这个消息需要自己定义，第4个参数是我们所关心的事件，当在s这个socket发生lEvent这个事件发生时会向hWnd对应的窗口发送wMsg消息。在消息附带的两个参数wParam和lParam中，lParam的高位16位表示当前的错误码，低16位表示当前socket上发生的事件。其中事件的取值如下:FD_WRITE : 当socket上可写时触发该事件，FD_WRITE的触发与调用send没有必然的联系，FD_WRITE只是表示socket已经为发送准备好了必要的条件，其实调用时可以不必理会这个事件，只需要在想发送数据的场合调用send，一般来说FD_WRITE只在这些条件下触发：a) 调用connect函数成功连接到服务器 b) 调用accept接受连接成功后（该条件是绑定在accept返回的那个与客户端通讯的socket上) c）调用send，sendto 失败并返回WSAWOULDBLOCK(由于是异步操作，可能同时客户端也在发数据, 此时可能导致send失败)为了方便我们处理这些参数，WinSock 提供了两个宏来解析它的高16位和低16位，分别是WSAGETSELECTERROR和WSAGETSELECTEVENT而lParam则保存了当前触发事件的socket句柄如果对一个句柄调用了WSAAsyncSelect 并成功后，对应的socket会自动编程非阻塞模式。它就不像前面的select模型那样需要显示调用ioctrlsocket将socekt设置为非阻塞。另外不需要每个socket都定义一个消息ID，通常一个ID已经足够处理所有的socket事件。下面是一个具体的例子int _tmain(int argc, TCHAR *argv[]) { WSADATA wd = {0}; WSAStartup(MAKEWORD(2, 2), &wd); SOCKADDR_IN SrvAddr = {AF_INET}; SrvAddr.sin_addr.s_addr = htonl(INADDR_ANY); SrvAddr.sin_port = htons(SERVER_PORT); SOCKET skServer = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); if (INVALID_SOCKET == skServer) { printf("初始化socket失败，错误码为:%08x\n", WSAGetLastError()); goto __CLEAR_UP; } if (0 != bind(skServer, (SOCKADDR*)&SrvAddr, sizeof(SOCKADDR))) { printf("绑定失败，错误码为:%08x\n", WSAGetLastError()); goto __CLEAR_UP; } if (0 != listen(skServer, 5)) { printf("监听失败，错误码为:%08x\n", WSAGetLastError()); goto __CLEAR_UP; } RegisterWindow(); CreateAndShowWnd(); g_uSockMsgID = RegisterWindowMessage(SOCKNOTIFY_MESSAGE); WSAAsyncSelect(skServer, g_hMainWnd, g_uSockMsgID, FD_ACCEPT | FD_CLOSE); Message…

ATL模板库中的OLEDB与ADO
type: item
date: 2018-05-27T09:58:00+08:00
description: 上次将OLEDB的所有内容基本上都说完了，从之前的示例上来看OLEDB中有许多变量的定义，什么结果集对象、session对象、命令对象，还有各种缓冲等等，总体上来说直接使用OLEDB写程序很麻烦，用很大的代码量带来的仅仅只是简单的功能。还要考虑各种缓冲的释放，各种对象的关闭，程序员的大量精力都浪费在无用的事情上，针对这些情况微软在OLEDB上提供了两种封装方式，一种是将其封装在ATL模板库中，一种是使用ActiveX控件来进行封装称之为ADO，这次主要写的是这两种方式ATL 模板中的OLEDB由于ATL模板是开源的，这种方式封装简洁，调试简易（毕竟源代码都给你了），各个模块相对独立，但是它的缺点很明显就是使用门槛相对较高，只有对C++中的模板十分熟悉的开发人员才能使用的得心应手。ATL中的OLEDB主要有两大模块，提供者模块和消费者模块，顾名思义，提供者模块是数据库的开发人员使用的，它主要使用这个模块实现OLEDB中的接口，对外提供相应的数据库服务；消费者模块就是使用OLEDB在程序中操作数据库。这里主要说的是消费者模块ATL主要封装的类ATL针对OLEDB封装的主要有这么几个重要的类：数据库对象CDataConnection 数据源连接类主要实现的是数据库的连接相关的功能，根据这个可以猜测出来它实际上封装的是OLEDB中的数据源对象和会话对象CDataSource：数据源对象CEnumerator: 架构结果集对象，主要用来查询数据库的相关信息，比如数据库中的表结构等信息CSession: 会话对象访问器对象：CAccessor: 常规的访问器对象CAccessorBase: 访问器对象的基类CDynamicAccessor：动态绑定的访问器CDynamicParamterAccessor:参数绑定的访问器,从之前博文的内容来看它应该是进行参数化查询等操作时使用的对象CDynamicStringAccessor:这个一般是要将查询结果显示为字符串时使用，它负责将数据库中的数据转化为字符串ALT中针对OLEDB的封装在头文件atldbcli.h中，在项目中只要包含它就行了模板的使用静态绑定针对静态绑定，VS提供了很好的向导程序帮助我们生成对应的类，方便了开发，使用的基本步骤如下:在项目上右键，选择添加类在类选择框中点击ATL并选择其中的ATL OLEDB使用者选择对应的数据源、数据库表和需要对数据库进行的操作注意如果要对数据库表进行增删改查等操作，一定要选这里的表选项点击数据源配置数据源连接的相关属性，最后点击完成。最终会在项目中生成对应的头文件这是最终生成的完整代码class Caa26Accessor { public: //value LONG m_aac031; TCHAR m_aaa146[51]; LONG m_aaa147; LONG m_aaa148; //status DBSTATUS m_dwaac031Status; DBSTATUS m_dwaaa146Status; DBSTATUS m_dwaaa147Status; DBSTATUS m_dwaaa148Status; //lenth DBLENGTH m_dwaac031Length; DBLENGTH m_dwaaa146Length; DBLENGTH m_dwaaa147Length; DBLENGTH m_dwaaa148Length; void GetRowsetProperties(CDBPropSet* pPropSet) { pPropSet->AddProperty(DBPROP_CANFETCHBACKWARDS, true, DBPROPOPTIONS_OPTIONAL); pPropSet->AddProperty(DBPROP_CANSCROLLBACKWARDS, true, DBPROPOPTIONS_OPTIONAL); pPropSet->AddProperty(DBPROP_IRowsetChange, true, DBPROPOPTIONS_OPTIONAL); pPropSet->AddProperty(DBPROP_UPDATABILITY, DBPROPVAL_UP_CHANGE | DBPROPVAL_UP_INSERT | DBPROPVAL_UP_DELETE); } HRESULT OpenDataSource() { CDataSource _db; HRESULT hr; hr = _db.OpenFromInitializationString(L"Provider=SQLOLEDB.1;Persist Security Info=False;User ID=sa; Password=XX…