WINDOWS编程手册
24. 计时器
Windows高精度计时器,误差<1us
LARGE_INTEGER liTimeStart, liTimeEnd, liTimeElapse; LARGE_INTEGER liFrequency; /*置计时时间*/ QueryPerformanceFrequency(&liFrequency); QueryPerformanceCounter(&liTimeStart); for(int i = 0; i != 10; ++i) ; /*计算时间间隔*/ QueryPerformanceCounter(&liTimeEnd); liTimeElapse.QuadPart = liTimeEnd.QuadPart - liTimeStart.QuadPart; double dTimeElapse = static_cast<double>(liTimeElapse.QuadPart) / liFrequency.QuadPart;
函数:
BOOL WINAPI QueryPerformanceFrequency(LARGE_INTEGER *lpFrequency
);
功能:获取频率,即系统1秒钟滴答多少下
参数:LARGE_INTEGER型指针,出参,成员QuadPart即结果
返回值:成功返回非0,失败返回0,GetLastError()获取错误码
函数:
BOOL WINAPI QueryPerformanceCounter(
LARGE_INTEGER *lpPerformanceCount
);
功能:获取自启动以来系统一共滴答了多少下
参数:LARGE_INTEGER型指针,出参,成员QuadPart即结果
返回值:成功返回非0,失败返回0,GetLastError()获取错误码
23. 查看操作系统位数
cmd打开命令提示符窗口,键入“systeminfo”
查看“系统类型”
x86-based PC: 32位操作系统
x64-based PC: 64位操作系统
22. 编程技巧
把所有的closesocket(s)放在一个函数里面:在连接异常断开时,在该函数中添加断点,方便定位。
21. 空格和水平制表符
空白字符:空格和水平制表符,ASCII码:空格-32; \t-9
str = "空格 + \t",下面的结果是9 9, str = "\t + 空格",结果是9 32。
所以有公式:空格 + \t = \t\t; \t + 空格 = \t + 空格
总结下来就是“先水平制表符后空格”
std::string str = " "; for(int i = 0; i < str.length(); i++) printf("%d\n", str[i]);
20.共享内存
创建共享内存
函数
HANDLE WINAPI CreateFileMapping(
HANDLE hFile,
LPSECURITY_ATTRIBUTES lpAttributes,
DWORD flProtect,
DWORD dwMaximumSizeHigh,
DWORD dwMaximumSizeLow,
LPCTSTR lpName
);
调用
#define SHARED_MEMORY_SIZE 1024
HANDLE hSharedMemory = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, SHARED_MEMORY_SIZE, "NameSharedMemory");
功能
若NameSharedMemory不存在,则创建NameSharedMemory共享内存。创建后系统默认全置0。
若NameSharedMemory已存在,则打开NameSharedMemory共享内存。置GetLastError()=ERROR_ALREADY_EXISTS。
返回值
成功返回共享内存句柄,失败返回NULL。
使用方式
HANDLE hSharedMemory = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, SHARED_MEMORY_SIZE, "Name"); if(hSharedMemory == NULL) { printf("CreateFileMapping == NULL, ErrorCode=%d\n", GetLastError()); return false; } if(ERROR_ALREADY_EXISTS == GetLastError()) { printf("CreateFileMapping Ok, GetLastError == ERROR_ALREADY_EXISTS, Name Already Exist\n"); }例子
进程A创建共享内存,读写数据;进程B打开共享内存,读写数据
公有结构体定义
#define SHARED_MEMORY_SIZE 1024 typedef struct { bool bFlagLive; char cObjectName[400]; char cObjectPathName[400]; }MemBlock;
A main.cpp
/*创建共享内存*/ HANDLE hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, BUF_SIZE, "Global\\TestSharedMemory"); if (hMapFile == NULL) { std::cout<<"CreateFileMapping == NULL, ErrorCode="<<GetLastError()<<std::endl; return 0; } std::string sObjectName = "他想让你\\sObjectName"; std::string sObjectPathName = "他想让你\\sObjectPathName"; /*获取共享内存地址*/ MemBlock *pMemBlock = (MemBlock*)MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, BUF_SIZE); if (pMemBlock == NULL) { std::cout<<"MapViewOfFile == NULL, ErrorCode="<<GetLastError()<<std::endl; CloseHandle(hMapFile); return 0; } /*写共享内存*/ strcpy(pMemBlock->cObjectName, sObjectName.c_str()); strcpy(pMemBlock->cObjectPathName, sObjectPathName.c_str()); InstallService(); StartService2(); /*定期修改共享内存*/ while(1) { pMemBlock->bFlag = true; Sleep(3*1000); }B main.cpp
/*打开共享内存*/ HANDLE hMapFile = OpenFileMapping(FILE_MAP_ALL_ACCESS, false, "Global\\TestSharedMemory"); if (hMapFile == NULL) { std::cout<<"CreateFileMapping == NULL, ErrorCode="<<GetLastError()<<std::endl; return 0; } /*获取共享内存地址*/ MemBlock *pMemBlock = (MemBlock*)MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, BUF_SIZE); if (pMemBlock == NULL) { std::cout<<"MapViewOfFile == NULL, ErrorCode="<<GetLastError()<<std::endl; CloseHandle(hMapFile); return 0; } /*读共享内存*/ std::string sObjectName = pMemBlock->cObjectName; std::string sObjectPathName = pMemBlock->cObjectPathName; /*定期读共享内存*/ while(1) { bool bFlag = pMemBlock->bFlag; /*修改标志位*/ pMemBlock->bFlag = false; Sleep(3*1000); }
遇到的问题:
a.win7下用户进程创建共享内存,服务程序读共享内存失败,原因Session 0隔离
在Windows Vista之后的操作系统中,服务进程运行于Session 0之中。用户进程运行在用户登陆到系统后创建的Session 0之后的Session中,例如第一个登陆的用户在Session 1中,第二个登陆的用户在Session 2中,以此类推。
这便造成用户进程与服务进程隔离开来。
事实上运行在的不同Session中,如果没有放入全局命名空间(并且设置了相应的访问控制配置),是不能共享Kernel对象的。
综上,确保跨Session访问的Kernel对象是以Global\开头,这意味着它们是属于全局Session命名空间中的。
b.共享内存结构应使用C struct,例:char cObjectName[400]改为std::string sObjectName则出现汉字乱码。
附加知识:
a. 共享内存用于多进程数据交互,一进程调用CreateFileMapping创建共享内存;其他进程调用OpenFileMapping打开共享内存。
b. 当最后一个进程退出或CloseHandle(hSharedMemory)关闭共享内存,系统才会释放该内存。
19. 头文件引用顺序错误
#include <WinSock2.h>
#include <Windows.h>
#pragma comment(lib, "Ws2_32.lib")
Socket编程头文件和库文件标准引用格式。
若#include <Windows.h>在#include <WinSock2.h>之前则报上述错误。
18.Windows定时器
A. 创建
函数原型:
BOOL WINAPI CreateTimerQueueTimer(
PHANDLE phNewTimer,
HANDLE TimerQueue,
WAITORTIMERCALLBACK Callback,
PVOID Parameter,
DWORD DueTime,
DWORD Period,
ULONG Flags
);
功能:创建定时器队列定时器
参数:
PHANDLE phNewTimer:出参,类型HANDLE指针,储存生成的Timer句柄
HANDLE TimerQueue:TimerQueue句柄,若为NULL,则生成的Timer关联到默认TimerQueue
WAITORTIMERCALLBACK Callback:Timer回调函数,原型void CALLBACK TimerRoutine(PVOID arg, BOOLEAN TimeOrWaitFired);
PVOID Parameter:回调函数参数
DWORD DueTime:DueTime毫秒后触发
DWORD Period:之后每Period毫秒触发一次
ULONG Flags:暂默认0
返回值:失败返回0,错误码GetLastError();成功非0
例:创建Timer,句柄由hTimer返回,3秒后触发,间隔3秒,触发后执行执行TimerRoutine
HANDLE hTimer = NULL; if(!CreateTimerQueueTimer(&hTimer, NULL, TimerRoutine, NULL, 3000, 3000, 0)) { std::cout<<"CreateTimerQueueTimer == 0, ErrorCode="<<GetLastError()<<std::endl; }
B. 修改
函数原型:
BOOL WINAPI ChangeTimerQueueTimer(
HANDLE TimerQueue,
HANDLE Timer,
ULONG DueTime,
ULONG Period
);
功能:修改Timer时间
参数:
HANDLE TimerQueue:TimerQueue句柄
HANDLE Timer:Timer句柄
ULONG DueTime:DueTime毫秒后触发
ULONG Period:之后每Period毫秒触发一次
返回值:失败返回0,错误码GetLastError();成功非0
例:将hTimer定时器改为7秒后触发,间隔1秒
if(!ChangeTimerQueueTimer(NULL, hTimer, 7000, 1000)) { std::cout<<"ChangeTimerQueueTimer == 0, ErrorCode="<<GetLastError()<<std::endl; }
C. 删除
函数原型:
BOOL WINAPI DeleteTimerQueueTimer(
HANDLE TimerQueue,
HANDLE Timer,
HANDLE CompletionEvent
);
参数:
HANDLE TimerQueue:TimerQueue句柄,若Timer由默认Timer Queue创建,则此参数应为NULL
HANDLE Timer:Timer句柄
HANDLE CompletionEvent:若为INVALID_HANDLE_VALUE,函数等待Timer回调结束方才返回功能:删除Timer
返回值:成功返回非0,失败返回0并置错误码
附加:ULONG Period = 0,则Timer只触发一次
完整代码
void CALLBACK TimerRoutine(PVOID arg, BOOLEAN TimeOrWaitFired) { std::cout<<"TimerRoutine Fired"<<std::endl; } unsigned int __stdcall Func(void *arg) { HANDLE *phTimer = static_cast<HANDLE*>(arg); if(!CreateTimerQueueTimer(phTimer, NULL, TimerRoutine, NULL, 3000, 3000, 0)) { std::cout<<"CreateTimerQueueTimer == 0, ErrorCode="<<GetLastError()<<std::endl; } _endthreadex(0); return 0; } unsigned int __stdcall Func3(void *arg) { HANDLE hTimer = static_cast<HANDLE>(arg); if(!ChangeTimerQueueTimer(NULL, hTimer, 7000, 1000)) { std::cout<<"ChangeTimerQueueTimer == 0, ErrorCode="<<GetLastError()<<std::endl; } _endthreadex(0); return 0; } int main() { HANDLE hTimer = NULL; _beginthreadex(NULL, 0, Func, &hTimer, 0, NULL); std::cin.get(); _beginthreadex(NULL, 0, Func3, hTimer, 0, NULL); std::cin.get(); return 0; }
17.EOF文件结束符
Windows下CTRL+Z代表EOF
16.closesocket
a.对刚创建却未连接的SOCKET s调用closesock(s),没有任何问题。
b.对初始化过的SOCKET s重复调用closesocket(s),没有任何问题。
SOCKET s = socket(AF_INET, SOCK_STREAM, 0); closesocket(s); closesocket(s); std::cout<<"Ok"<<std::endl;
输出:Ok
15.网络抓包wireshark
15.1
15.2 本机抓包
在进行通信开发的过程中,我们往往会把本机既作为客户端又作为服务器端来调试代码,使得本机自己和自己通信。但是wireshark此时是无法抓取到数据包的,需要通过简单的设置才可以。
具体方法如下:
①:以管理员身份运行cmd
②:route add 本机ip mask 255.255.255.255 网关ip
for example route add 192.168.1.103 mask 255.255.255.255 192.168.1.1 metric 1
使用完毕后用 route delete 192.168.1.103 mask 255.255.255.255 192.168.1.1 metric 1删除,否则所有本机报文都经过网卡出去走一圈回来很耗性能
此时再利用wireshark进行抓包便可以抓到本机自己同自己的通信包,这样配置的原因是将发往本机的包发送到网关,而此时wireshark可以捕获到网卡驱动的报文实现抓包。
15.3 条件设置
ip.src==192.168.5.3
ip.dst==192.168.4.18
tcp.port==4444
ip.src==192.168.5.3 and tcp.port==4444
14.sockaddr和sockaddr_in
概要:
1.一开始设置ip和port用的是sockaddr_in
2.最后执行动作的套接字函数的参数类型均为*sockaddr
综述:
sockaddr_in addrin;
addrin.sin_family = AF_INET;
addrin.sin_addr.s_addr = inet_addr("192.168.15.14");
addrin.sin_port = htons(4444);
socket_func(...(sockaddr*)&addrin,......)
知识点:
1.sizeof(sockaddr_in) == sizeof(sockaddr)
2.相互转化方式:memcpy(&addr_in, &addr, sizeof(addr));
附加:
1.绑定本机所有地址标准形式:addrin.sin_addr.s_addr = htonl(INADDR_ANY);
2.绑定特定地址标准形式: addrin.sin_addr.s_addr = inet_addr("192.168.4.118");
13.非阻塞connect
//设置socket s非阻塞 unsigned long ul = 1; ioctlsocket(s, FIONBIO, &ul); int ret = connect(s, (struct sockaddr*)&serverAddr, sizeof(serverAddr)); //成功 if(0 == ret) { //恢复s为阻塞 ul = 0; ioctlsocket(s, FIONBIO, &ul); return true; } //出错 if(SOCKET_ERROR == ret && GetLastError() != WSAEWOULDBLOCK) { closesocket(s); s = INVALID_SOCKET; return false; } //进行中 if(SOCKET_ERROR == ret && WSAEWOULDBLOCK == GetLastError()) { timeval tm; tm.tv_sec = 0; tm.tv_usec = 300; fd_set set; FD_ZERO(&set); FD_SET(s, &set); //s可写代表connect成功 ret = select(0, NULL, &set, NULL, &tm); if(SOCKET_ERROR == ret) { std::cout<<"Select Error: "<<GetLastError()<<std::endl; closesocket(s); s = INVALID_SOCKET; return false; } if(0 == ret) { std::cout<<"Connect TimeOut"<<std::endl; closesocket(s); s = INVALID_SOCKET; return false; } ul = 0; ioctlsocket(s, FIONBIO, &ul); return true; }
12.socket设置非阻塞
设置为非阻塞:
unsigned long ul = 1; int ret = ioctlsocket(s, FIONBIO, &ul);
设置为阻塞:
unsigned long ul = 0; int ret = ioctlsocket(s, FIONBIO, &ul);
返回值:
if(SOCKET_ERROR == ret) { std::cout<<"ioctlsocket Error : "<<GetLastError()<<std::endl; closesocket(s); s = INVALID_SOCKET_VALUE; return false; }
11.semaphore信号量
创建:
HANDLE semaphore = CreateSemaphore(NULL, 0, num, NULL);
参数1:安全属性,NULL即可
参数2:初始计数值值,必须大于等于0
参数3:最大计数值
参数4:名字
失败返回NULL
使用:
WaitForSingleObject(semaphore, INFINITE);
semaphore计数值大于0,则计数值减一
semaphore计数值等于0,则线程阻塞
释放:
ReleaseSemaphore(semaphore, size, 0);
将semaphore计数值加size
10.线程
创建线程:
HANDLE h = (HANDLE)_beginthreadex(NULL, 0, F, arg, 0, NULL) 失败返回0
线程函数:
unsigned int __stdcall F(void *arg) { .............. _endthreadex(0); return(0); }
等待子线程退出:
std::vector<HANDLE> vec; for(int i = 0; i < vec.size(); ++i) WaitForSingleObject(vec[i], INFINITE);
9.引用成员变量
初始化
class A { public: A(ClassName &obj) : object(obj) { } private: ClassName &object; }
8.UTC到当前时刻经过的秒数
UTC,世界标准时间,即1970年1月1日 00:00:00
#include <time.h> time_t t = time(NULL);time_t类型long long
7.静态成员变量
class A { public: A(){std::cout<<"nihao"<<std::endl;} void insert(std::string str, time_t t); void output();//遍历输出map private: static std::map<std::string, time_t> map; };
int main() { A a1; a1.insert("123", time(NULL)); A a2; a2.insert("456", time(NULL)); A a3; a3.insert("789", time(NULL)); a3.insert("123", 12);//注意:key存在则不插入,不会进行值覆盖 A a4; a4.output(); }结果
综上:
1.类对象在创建时均调用构造函数,无论局部变量和new。
2.静态成员变量为所有类对象共享且仅此一份。
静态成员变量初始化
在cpp文件开头:类型 类名::静态成员变量 (= 初始值)
#include "devOnlineInfo.h" bool DevOnlineInfo::flag = false; HANDLE DevOnlineInfo::hthread = INVALID_HANDLE_VALUE; int DevOnlineInfo::timeout = 0; HANDLE DevOnlineInfo::mutex = CreateMutex(NULL, false, ""); std::map<std::string, bool> DevOnlineInfo::mapDevOnlineInfo; DevOnlineInfo::DevOnlineInfo() { } ..............
6. 生成GUID
#include <string> #include <objbase.h> std::string Guid() { char buf[33] = {0}; GUID guid; if (S_OK == ::CoCreateGuid(&guid)) { _snprintf(buf, sizeof(buf) , "%08X%04X%04x%02X%02X%02X%02X%02X%02X%02X%02X" , guid.Data1 , guid.Data2 , guid.Data3 , guid.Data4[0], guid.Data4[1] , guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5] , guid.Data4[6], guid.Data4[7] ); } std::string str = buf; return str; }
5. 工具函数
工程中创建utils.h文件
#ifndef UTILS_H #define UTILS_H #include <string> #include <objbase.h> namespace utils { static inline std::string Func1() { std::string str = "loser"; return str; } static inline void Func2() { // // } } #endif
4. HANDLE和SOCKET初始值
HANDLE h = INVALID_HANDLE_VALUE;
SOCKET s = INVALID_SOCKET;
CloseHandle关闭线程句柄
3. CloseHandle对参数的影响
int main() { HANDLE h = (HANDLE)_beginthreadex(NULL, 0, ThreadFunc, NULL, 0, NULL); std::cout<<h<<std::endl; std::cout<<CloseHandle(h)<<std::endl; Sleep(1000); std::cout<<h<<std::endl; std::cin.get(); return 0; }结果
返回1,关闭线程句柄成功。
综上CloseHandle调用前后参数h值并无改变,则CloseHandle后需h = INVALID_HANDLE_VALUE。
由CloseHandle参数非引用该能推出参数无变化。
2. CloseHandle对线程的影响 1
unsigned int __stdcall ThreadFunc(void *arg) { while(1) { Sleep(1000); std::cout<<"woshule"<<std::endl; } _endthreadex(0); return 0; } int main() { HANDLE h = (HANDLE)_beginthreadex(NULL, 0, ThreadFunc, NULL, 0, NULL); Sleep(1000); std::cout<<CloseHandle(h)<<std::endl; std::cin.get(); return 0; }结果
CloseHandle成功返回非0,失败返回0、GetLastError()查看错误码。
返回1,关闭线程句柄成功。
综上CloseHandle
功能:关闭句柄
实质:资源引用计数减1
影响:与线程运行无关 (即便引用计数减到0,线程都不会退出)
引深:实现线程控制还需另辟蹊径 (如用控制标志bool flag来控制线程退出)
追述:打开的句柄即资源、资源则需关闭、否则发生泄露
CloseHandle对线程的影响 2
unsigned int __stdcall ThreadFunc(void *arg) { while(true) { std::cout<<"nixingfuma"<<std::endl; Sleep(1000); } _endthreadex(0); return 0; } void CloseThreadHandle() { HANDLE h = (HANDLE)_beginthreadex(NULL, 0, ThreadFunc, NULL, 0, NULL); Sleep(2000); std::cout<<CloseHandle(h)<<std::endl; } int main() { CloseThreadHandle(); std::cin.get(); return 0; }结果
综上子函数内创建并CloseHandle线程,函数退出,线程不销毁。
1. windows查看端口占用命令
任务管理器选"进程"-"查看"-"选择列"-勾选"PID"
进入windows命令提示符窗口,输入netstat
-ano
即可看到所有连接的PID
应用实例:
查看80号端口占用
C:\>netstat -ano|findstr "80"
Proto Local Address Foreign Address State PID
TCP 127.0.0.1:80 0.0.0.0:0 LISTENING 2448
80号端口被2448号进程占用,继续
C:\>tasklist|findstr "2448"
thread.exe 2016 Console 0 16,064 K
至此完结,thread.exe占用80号端口、且在80号端口监听连接。
如果第二步查不到,就查任务管理器,找2448号进程