WindowsAPI查缺补漏-WinSock网络编程
碎碎念
Windows套接字即WinSock以UNIX的伯克利套接字规范为标准,包含同名接口函数,用法也一致。WinSock结课Windows消息机制又增加了许多扩展函数。
TCP/IP规定统一使用大端序传输数据,也称为网络字节顺序。本篇不讲计网,自己去学。
这节内容真是吔屎了...前面基础WinSock编程不支持Unicode还得转,看个乐子就行了,重点在后面I/O模型。
代码例子有ANSI和Unicode混用的情况,纯属懒得改了,用的时候小心点儿。
本篇只讲TCP和UDP连接,想学HTTP啥的可以关上了。
学习本篇需要智商在线,不在线的先去听《大香蕉》洗一下脑子。
地址表示
在sockaddr_in结构中同时指定IP地址和端口号。下文定义中出现sockaddr结构的均是为了兼容1.0旧版本,都可以当作sockaddr_in使。
1 2 3 4 5 6
| struct sockaddr_in { SHORT sin_family; USHORT sin_port; struct in_addr sin_addr; CHAR sin_zero[8]; };
|
对于sin_port要转为网络字节顺序,有以下函数可借助使用。
1 2 3 4
| u_short htons(u_short hostshort); u_long htonl(u_long hostlong); u_short ntohs(u_short netshort); u_long ntohl(u_long netlong);
|
in_addr结构用来表示IP地址:
1 2 3 4 5 6 7 8 9 10 11
| typedef struct in_addr { union { struct { u_char s_b1, s_b2, s_b3, s_b4; }S_un_b; struct { u_short s_w1, s_w2; }S_un_w; u_long S_addr; }S_un; }IN_ADDR,*PIN_ADDR,FAR *LPIN_ADDR;
|
常用函数有:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| ULONG inet_addr( _In_ CONST PCHAR cp ); PCHAR FAR inet_ntoa( _In_ struct in_addr in );
INT WSAAPI InetPton( INT Family, PCTSTR pszAddrString, PVOID pAddrBuf ); PCTSTR WSAAPI InetNtop( INT Family, CONST PVOID pAddr, PTSTR pStringBuf, SIZE_T StringBufSize );
|
常用的初始化方法为:
1 2 3 4 5 6 7 8 9 10 11
| sockaddr_in sockAddr; sockAddr.sin_family = AF_INET; sockAddr.sin_port = htons(12345); sockAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
sockaddr_in sockAddr; sockAddr.sin_family = AF_INET; sockAddr.sin_port = htons(12345); InetPton(AF_INET, "127.0.0.1", &sockAddr.sin_addr.S_un.S_addr);
|
WinSock网络编程
WSAStartup
初始化WinSock库:
1 2 3 4
| INT WSAStartup( _In_ WORD wVersionRequested, _Out_ LPWSADATA lpWSAData );
|
wVersionRequested高字节为副版本号,低字节为主版本号。目前WinSock版本为2.2,需要包含WinSock2.h、用DLL为Ws2_32.dll,但Ws2_32.lib在VS中没有得自己搞。
lpWSAData有:
1 2 3 4 5 6 7 8 9
| typedef struct WSAData { WORD wVersion; WORD wHighVersion; USHORT iMaxSockets; USHORT iMaxUdpDg; CHAR FAR* lpVendorInfo; CHAR szDescription[WSADESCRIPTION_LEN + 1]; CHAR szSystemStatus[WSASYS_STATUS_LEN + 1]; } WSADATA;
|
例如常用方法:
1 2 3 4 5 6 7 8 9 10 11
| #include <winsock2.h> #pragma comment(lib,"Ws2_32") INT main(INT argc, PTCHAR argv[]) { WSADATA wsa = { 0 }; if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) { return 0; }; return 0; };
|
WSACleanup
释放WinSock资源:
socket
创建套接字:
1 2 3 4 5
| SOCKET WSAAPI socket( _In_ INT af, _In_ INT type, _In_ INT protocol );
|
对于af有AF_INET或AF_INET6,分别为IPv4或IPv6。type常用的有:SOCK_STREAM流式套接字,只能读取TCP包;SOCK_DGRAM数据报套接字,只能读取UDP包;SOCK_RAW原始套接字,可读写内核未处理的IP包,能对网络底层传输机制进行控制。protocal需要配合type使用,有IPPROTO_TCP、IPPROTO_UDP等,为0表示t自动选择ype对应的默认协议。
Windows对原始套接字搞了一堆安全限制,这玩意儿容易被蠕虫用上。
例如常用方法:
1 2 3 4 5 6
| SOCKET socketListen = socket(AF_INET, SOCK_STREAM, 0); if (socketListen == INVALID_SOCKET) { WSACleanup(); return 0; };
|
closesocket
关闭套接字资源:
1 2 3
| INT closesocket( _In_ SOCKET s );
|
bind
将本地地址与监听套接字关联起来:
1 2 3 4 5
| INT bind( _In_ SOCKET s, _In_ CONST struct sockaddr FAR* name, _In_ INT namelen );
|
例如常用方法如下,当sockAddr.sin_addr.S_un.S_addr
为INADDR_ANY表示在本机所有IP地址上进行监听,多网卡时很好使,端口号设为0表示系统自动分配唯一端口号。
1 2 3 4 5 6
| if (bind(socketListen, (sockaddr*)&sockAddr, sizeof(sockAddr)) == SOCKET_ERROR) { closesocket(socketListen); WSACleanup(); return 0; };
|
getsockname
获取分配给监听套接字的地址:
1 2 3 4 5
| INT getsockname( _In_ SOCKET s, _Out_ struct sockaddr* name, _Inout_ PINT namelen );
|
listen
使主动连接套接字变为被动连接套接字,使一个进程可接收其他进程请求,变成一个服务器进程。
1 2 3 4
| INT listen( _In_ SOCKET s, _In_ INT backlog );
|
backlog设为SOMAXCONN表示系统将其设为最大合理值,队列满后拒绝新连接请求并出现错误WSAECONNREFUSED。例如常用方法如下:
1 2 3 4 5 6
| if (listen(socketListen, SOMAXCONN) == SOCKET_ERROR) { closesocket(socketListen); WSACleanup(); return 0; };
|
accept
从套接字等待连接队列中抽取第一个连接并接受,成功则返回一个套接字句柄用于实际通信,原监听套接字仍保持监听状态。失败返回INVALID_SOCKET,用WSAGetLastError
获取错误代码。
1 2 3 4 5
| SOCKET accept( _In_ SOCKET s, _Out_ struct sockaddr* addr, _Inout_ PINT addrlen );
|
当队列中无等待连接时该函数阻塞调用进程直至新进程出现后才返回,所以一般新建进程负责通信。但这样也不合理,最好用I/O异步模型。
1 2 3 4 5 6 7 8 9 10
| sockaddr_in sockAddrClient; INT nAddrlen = sizeof(sockAddrClient); while (TRUE) { socketAccept = accept(socektListen, (sockaddr*)&sockAddrClient, &nAddrlen); if (socketAccept == INVALID_SOCKET) { continue; }; };
|
send/recv
在已连接的套接字上发送/接收数据:
1 2 3 4 5 6 7 8 9 10 11 12
| INT send( _In_ SOCKET s, _In_ CONST PCHAR buf, _In_ INT len, _In_ INT flags ); INT recv( _In_ SOCKET s, _Out_ PCHAR buf, _In_ INT len, _In_ INT flags );
|
connect
与服务器建立连接:
1 2 3 4 5
| INT connect( _In_ SOCKET s, _In_ CONST struct sockaddr* name, _In_ INT namelen );
|
sendto
向指定目的地发送数据,适用于UDP:
1 2 3 4 5 6 7 8
| INT sendto( _In_ SOCKET s, _In_ CONST PCHAR buf, _In_ INT len, _In_ INT flags, _In_ CONST struct sockaddr* to, _In_ INT tolen );
|
recvfrom
接收数据报,返回源地址:
1 2 3 4 5 6 7 8
| INT recvfrom( _In_ SOCKET s, _Out_ PCHAR buf, _In_ INT len, _In_ INT flags, _Out_ struct sockaddr* from, _Inout_opt_ PINT fromlen );
|
例子:TCP
下面这个例子用的ANSI编码,看看就行了,重点在后面。
服务端源文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
| #include <winsock2.h> #include <ws2tcpip.h> #include "resource.h" #pragma comment(lib, "Ws2_32")
const int BUF_SIZE = 1024;
HWND g_hwnd; HWND g_hListContent; HWND g_hEditMsg; HWND g_hBtnSend; SOCKET g_socketListen = INVALID_SOCKET; SOCKET g_socketAccept = INVALID_SOCKET;
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
VOID OnInit(HWND hwndDlg);
VOID OnStart();
VOID OnSend();
DWORD WINAPI RecvProc(LPVOID lpParam); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_SERVER_DIALOG), NULL, DialogProc, NULL); return 0; }; INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_INITDIALOG: { OnInit(hwndDlg); return TRUE; }; case WM_COMMAND: { switch (LOWORD(wParam)) { case IDCANCEL: { if (g_socketAccept != INVALID_SOCKET) closesocket(g_socketAccept); if (g_socketListen != INVALID_SOCKET) closesocket(g_socketListen); WSACleanup(); EndDialog(hwndDlg, IDCANCEL); break; }; case IDC_BTN_START: { OnStart(); break; }; case IDC_BTN_SEND: { OnSend(); break; }; }; return TRUE; }; }; return FALSE; };
VOID OnInit(HWND hwndDlg) { g_hwnd = hwndDlg; g_hListContent = GetDlgItem(hwndDlg, IDC_LIST_CONTENT); g_hEditMsg = GetDlgItem(hwndDlg, IDC_EDIT_MSG); g_hBtnSend = GetDlgItem(hwndDlg, IDC_BTN_SEND); EnableWindow(g_hBtnSend, FALSE); return; }; VOID OnStart() { WSADATA wsa = { 0 }; if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) { MessageBoxA(g_hwnd, "初始化WinSock库失败!", "WSAStartup Error", MB_OK); return; }; g_socketListen = socket(AF_INET, SOCK_STREAM, 0); if (g_socketListen == INVALID_SOCKET) { MessageBoxA(g_hwnd, "创建监听套接字失败!", "socket Error", MB_OK); WSACleanup(); return; }; sockaddr_in sockAddr; sockAddr.sin_family = AF_INET; sockAddr.sin_port = htons(8000); sockAddr.sin_addr.S_un.S_addr = INADDR_ANY; if (bind(g_socketListen, (sockaddr*)&sockAddr, sizeof(sockAddr)) == SOCKET_ERROR) { MessageBoxA(g_hwnd, "将监听套接字与指定的IP地址、端口号捆绑失败!", "bind Error", MB_OK); closesocket(g_socketListen); WSACleanup(); return; }; if (listen(g_socketListen, 1) == SOCKET_ERROR) { MessageBoxA(g_hwnd, "使监听套节字进入监听(等待被连接)状态失败!", "listen Error", MB_OK); closesocket(g_socketListen); WSACleanup(); return; }; MessageBoxA(g_hwnd, "服务器监听中...", "服务启动成功", MB_OK); EnableWindow(GetDlgItem(g_hwnd, IDC_BTN_START), FALSE); sockaddr_in sockAddrClient; int nAddrlen = sizeof(sockaddr_in); g_socketAccept = accept(g_socketListen, (sockaddr*)&sockAddrClient, &nAddrlen); if (g_socketAccept == INVALID_SOCKET) { MessageBoxA(g_hwnd, "接受连接请求失败!", "accept Error", MB_OK); closesocket(g_socketListen); WSACleanup(); return; }; CHAR szBuf[BUF_SIZE] = { 0 }; CHAR szIP[24] = { 0 }; inet_ntop(AF_INET, &sockAddrClient.sin_addr.S_un.S_addr, szIP, _countof(szIP)); wsprintfA(szBuf, "客户端[%s:%d]已连接!", szIP, ntohs(sockAddrClient.sin_port)); SendMessageA(g_hListContent, LB_ADDSTRING, 0, (LPARAM)szBuf); EnableWindow(g_hBtnSend, TRUE); HANDLE hThread = NULL; if ((hThread = CreateThread(NULL, 0, RecvProc, NULL, 0, NULL)) != NULL) CloseHandle(hThread); return; }; VOID OnSend() { CHAR szBuf[BUF_SIZE] = { 0 }; CHAR szShow[BUF_SIZE] = { 0 }; GetWindowTextA(g_hEditMsg, szBuf, BUF_SIZE); wsprintfA(szShow, "服务器说:%s", szBuf); SendMessageA(g_hListContent, LB_ADDSTRING, 0, (LPARAM)szShow); send(g_socketAccept, szShow, strlen(szShow), 0); SetWindowTextA(g_hEditMsg, ""); return; }; DWORD WINAPI RecvProc(LPVOID lpParam) { CHAR szBuf[BUF_SIZE] = { 0 }; int nRet = SOCKET_ERROR; while (TRUE) { ZeroMemory(szBuf, BUF_SIZE); nRet = recv(g_socketAccept, szBuf, BUF_SIZE, 0); if (nRet > 0) SendMessageA(g_hListContent, LB_ADDSTRING, 0, (LPARAM)szBuf); }; return 0; };
|
客户端源文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
| #include <winsock2.h> #include <ws2tcpip.h> #include "resource.h" #pragma comment(lib, "Ws2_32")
const int BUF_SIZE = 1024;
HWND g_hwnd; HWND g_hListContent; HWND g_hEditMsg; HWND g_hBtnSend; SOCKET g_socketClient = INVALID_SOCKET;
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
VOID OnInit(HWND hwndDlg);
VOID OnConnect();
VOID OnSend();
DWORD WINAPI RecvProc(LPVOID lpParam); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_CLIENT_DIALOG), NULL, DialogProc, NULL); return 0; }; INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_INITDIALOG: { OnInit(hwndDlg); return TRUE; }; case WM_COMMAND: { switch (LOWORD(wParam)) { case IDCANCEL: { if (g_socketClient != INVALID_SOCKET) closesocket(g_socketClient); WSACleanup(); EndDialog(hwndDlg, IDCANCEL); break; }; case IDC_BTN_CONNECT: { OnConnect(); break; }; case IDC_BTN_SEND: { OnSend(); break; }; }; return TRUE; }; }; return FALSE; };
VOID OnInit(HWND hwndDlg) { g_hwnd = hwndDlg; g_hListContent = GetDlgItem(hwndDlg, IDC_LIST_CONTENT); g_hEditMsg = GetDlgItem(hwndDlg, IDC_EDIT_MSG); g_hBtnSend = GetDlgItem(hwndDlg, IDC_BTN_SEND); EnableWindow(g_hBtnSend, FALSE); return; };
VOID OnConnect() { WSADATA wsa = { 0 }; sockaddr_in sockAddrServer; HANDLE hThread = NULL; if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) { MessageBoxA(g_hwnd, "初始化WinSock库失败!", "WSAStartup Error", MB_OK); return; }; g_socketClient = socket(AF_INET, SOCK_STREAM, 0); if (g_socketClient == INVALID_SOCKET) { MessageBoxA(g_hwnd, "创建与服务器的通信套接字失败!", "socket Error", MB_OK); WSACleanup(); return; }; sockAddrServer.sin_family = AF_INET; sockAddrServer.sin_port = htons(8000); inet_pton(AF_INET, "127.0.0.1", &sockAddrServer.sin_addr.S_un.S_addr); if (connect(g_socketClient, (sockaddr*)&sockAddrServer, sizeof(sockAddrServer)) == SOCKET_ERROR) { MessageBoxA(g_hwnd, "与服务器建立连接失败!", "connect Error", MB_OK); closesocket(g_socketClient); WSACleanup(); return; }; SendMessageA(g_hListContent, LB_ADDSTRING, 0, (LPARAM)"已连接到服务器!"); EnableWindow(GetDlgItem(g_hwnd, IDC_BTN_CONNECT), FALSE); EnableWindow(g_hBtnSend, TRUE); if ((hThread = CreateThread(NULL, 0, RecvProc, NULL, 0, NULL)) != NULL) CloseHandle(hThread); return; };
VOID OnSend() { CHAR szBuf[BUF_SIZE] = { 0 }; CHAR szShow[BUF_SIZE] = { 0 }; GetWindowTextA(g_hEditMsg, szBuf, BUF_SIZE); wsprintfA(szShow, "客户端说:%s", szBuf); SendMessageA(g_hListContent, LB_ADDSTRING, 0, (LPARAM)szShow); send(g_socketClient, szShow, strlen(szShow), 0); SetWindowTextA(g_hEditMsg, ""); return; }; DWORD WINAPI RecvProc(LPVOID lpParam) { CHAR szBuf[BUF_SIZE] = { 0 }; int nRet = SOCKET_ERROR; while (TRUE) { ZeroMemory(szBuf, BUF_SIZE); nRet = recv(g_socketClient, szBuf, BUF_SIZE, 0); if (nRet > 0) SendMessageA(g_hListContent, LB_ADDSTRING, 0, (LPARAM)szBuf); }; return 0; };
|
例子:UDP
服务端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #include <winsock2.h> #include <ws2tcpip.h> #include <stdio.h> #pragma comment(lib, "Ws2_32")
const int BUF_SIZE = 1024; int main() { WSADATA wsa = { 0 }; SOCKET socketSendRecv = INVALID_SOCKET; sockaddr_in addrServer, addrClient; int nAddrLen = sizeof(sockaddr_in); CHAR szBuf[BUF_SIZE] = { 0 }; CHAR szIP[24] = { 0 }; WSAStartup(MAKEWORD(2, 2), &wsa); socketSendRecv = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); addrServer.sin_family = AF_INET; addrServer.sin_port = htons(8000); addrServer.sin_addr.S_un.S_addr = INADDR_ANY; bind(socketSendRecv, (SOCKADDR*)&addrServer, sizeof(addrServer)); recvfrom(socketSendRecv, szBuf, BUF_SIZE, 0, (SOCKADDR*)&addrClient, &nAddrLen); inet_ntop(AF_INET, &addrClient.sin_addr.S_un.S_addr, szIP, _countof(szIP)); printf("从客户端[%s:%d]接收到数据:%s\n", szIP, ntohs(addrClient.sin_port), szBuf); sendto(socketSendRecv, szBuf, strlen(szBuf), 0, (SOCKADDR*)&addrClient, nAddrLen); closesocket(socketSendRecv); WSACleanup(); return 0; };
|
客户端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #include <winsock2.h> #include <ws2tcpip.h> #include <stdio.h> #pragma comment(lib, "Ws2_32")
const int BUF_SIZE = 1024; int main() { WSADATA wsa = { 0 }; SOCKET socketSendRecv = INVALID_SOCKET; sockaddr_in addrServer; int nAddrLen = sizeof(sockaddr_in); CHAR szBuf[BUF_SIZE] = "你好,老王!"; CHAR szIP[24] = { 0 }; WSAStartup(MAKEWORD(2, 2), &wsa); socketSendRecv = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); addrServer.sin_family = AF_INET; addrServer.sin_port = htons(8000); inet_pton(AF_INET, "127.0.0.1", &addrServer.sin_addr.S_un.S_addr); sendto(socketSendRecv, szBuf, strlen(szBuf), 0, (SOCKADDR*)&addrServer, nAddrLen); sockaddr_in addr; ZeroMemory(szBuf, sizeof(szBuf)); recvfrom(socketSendRecv, szBuf, BUF_SIZE, 0, (sockaddr*)&addr, &nAddrLen); inet_ntop(AF_INET, &addr.sin_addr.S_un.S_addr, szIP, _countof(szIP)); printf("从服务器[%s:%d]返回数据:%s\n", szIP, ntohs(addr.sin_port), szBuf); closesocket(socketSendRecv); WSACleanup(); return 0; };
|
异步I/O模型
非阻塞模式
ioctlsocket
使套接字工作在非阻塞模式下:
1 2 3 4 5
| INT ioctlsocket( _In_ SOCKET s, _In_ LONG cmd, _Inout_ PULONG argp );
|
cmd有FIONBIO、FIONREAD、SIOCATMARK等参数,自己去学。
例如设置为非阻塞模式:
1 2
| ULONG ulArgp = 1; ioctlsocket(socketListen, FIONBIO, &ulArgp);
|
此后每次发送、接收或管理连接时大多会因操作还未完成而失败,WSAGetLastError
返回WSAEWOULDBLOCK,则需要多次调用直到返回成功。
例子
数据结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| #pragma once
typedef struct _SOCKETOBJ{ SOCKET m_socket; CHAR m_szIP[16]; USHORT m_usPort; _SOCKETOBJ *m_pNext; }SOCKETOBJ, *PSOCKETOBJ; PSOCKETOBJ g_pSocketObjHeader; int g_nTotalClient; CRITICAL_SECTION g_cs;
PSOCKETOBJ CreateSocketObj(SOCKET s) { PSOCKETOBJ pSocketObj = new SOCKETOBJ; if (pSocketObj == NULL) return NULL; EnterCriticalSection(&g_cs); pSocketObj->m_socket = s; if (g_pSocketObjHeader == NULL) { g_pSocketObjHeader = pSocketObj; g_pSocketObjHeader->m_pNext = NULL; } else { pSocketObj->m_pNext = g_pSocketObjHeader; g_pSocketObjHeader = pSocketObj; }; g_nTotalClient++; LeaveCriticalSection(&g_cs); return pSocketObj; };
VOID FreeSocketObj(PSOCKETOBJ pSocketObj) { EnterCriticalSection(&g_cs); PSOCKETOBJ p = g_pSocketObjHeader; if (p == pSocketObj) g_pSocketObjHeader = g_pSocketObjHeader->m_pNext; else while (p != NULL) { if (p->m_pNext == pSocketObj) { p->m_pNext = pSocketObj->m_pNext; break; }; p = p->m_pNext; }; if (pSocketObj->m_socket != INVALID_SOCKET) closesocket(pSocketObj->m_socket); delete pSocketObj; g_nTotalClient--; LeaveCriticalSection(&g_cs); };
PSOCKETOBJ FindSocketObj(SOCKET s) { EnterCriticalSection(&g_cs); PSOCKETOBJ pSocketObj = g_pSocketObjHeader; while (pSocketObj != NULL) { if (pSocketObj->m_socket == s) { LeaveCriticalSection(&g_cs); return pSocketObj; }; pSocketObj = pSocketObj->m_pNext; }; LeaveCriticalSection(&g_cs); return NULL; };
VOID DeleteAllSocketObj() { SOCKETOBJ socketObj; PSOCKETOBJ pSocketObj = g_pSocketObjHeader; while (pSocketObj != NULL) { socketObj = *pSocketObj; if (pSocketObj->m_socket != INVALID_SOCKET) closesocket(pSocketObj->m_socket); delete pSocketObj; pSocketObj = socketObj.m_pNext; }; return; };
|
服务端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
| #include <winsock2.h> #include <ws2tcpip.h> #include "resource.h" #include "SOCKETOBJ.h" #pragma comment(lib, "Ws2_32")
const int BUF_SIZE = 4096;
HWND g_hwnd; HWND g_hListContent; HWND g_hEditMsg; HWND g_hBtnSend; SOCKET g_socketListen = INVALID_SOCKET;
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
VOID OnInit(HWND hwndDlg);
VOID OnStart();
VOID OnSend();
DWORD WINAPI RecvProc(LPVOID lpParam);
DWORD WINAPI AcceptProc(LPVOID lpParam); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_SERVER_DIALOG), NULL, DialogProc, NULL); return 0; }; INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_INITDIALOG: { OnInit(hwndDlg); return TRUE; }; case WM_COMMAND: { switch (LOWORD(wParam)) { case IDCANCEL: { if (g_socketListen != INVALID_SOCKET) closesocket(g_socketListen); WSACleanup(); EndDialog(hwndDlg, IDCANCEL); break; }; case IDC_BTN_START: { OnStart(); break; }; case IDC_BTN_SEND: { OnSend(); break; }; }; return TRUE; }; }; return FALSE; };
VOID OnInit(HWND hwndDlg) { WSADATA wsa = { 0 }; if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) { MessageBoxA(g_hwnd, "初始化WinSock库失败!", "WSAStartup Error", MB_OK); return; }; InitializeCriticalSection(&g_cs); g_hwnd = hwndDlg; g_hListContent = GetDlgItem(hwndDlg, IDC_LIST_CONTENT); g_hEditMsg = GetDlgItem(hwndDlg, IDC_EDIT_MSG); g_hBtnSend = GetDlgItem(hwndDlg, IDC_BTN_SEND); EnableWindow(g_hBtnSend, FALSE); return; }; VOID OnStart() { g_socketListen = socket(AF_INET, SOCK_STREAM, 0); if (g_socketListen == INVALID_SOCKET) { MessageBoxA(g_hwnd, "创建监听套接字失败!", "socket Error", MB_OK); WSACleanup(); return; }; sockaddr_in sockAddr; sockAddr.sin_family = AF_INET; sockAddr.sin_port = htons(8000); sockAddr.sin_addr.S_un.S_addr = INADDR_ANY; if (bind(g_socketListen, (sockaddr*)&sockAddr, sizeof(sockAddr)) == SOCKET_ERROR) { MessageBoxA(g_hwnd, "将监听套接字与指定的IP地址、端口号捆绑失败!","bind Error", MB_OK); closesocket(g_socketListen); WSACleanup(); return; }; if (listen(g_socketListen, SOMAXCONN) == SOCKET_ERROR) { MessageBoxA(g_hwnd, "使监听套节字进入监听(等待被连接)状态失败!", "listen Error", MB_OK); closesocket(g_socketListen); WSACleanup(); return; }; MessageBoxA(g_hwnd, "服务器监听中...", "服务启动成功", MB_OK); EnableWindow(GetDlgItem(g_hwnd, IDC_BTN_START), FALSE); HANDLE hThread = NULL; if ((hThread = CreateThread(NULL, 0, AcceptProc, NULL, 0, NULL)) != NULL) CloseHandle(hThread); return; }; VOID OnSend() { CHAR szBuf[BUF_SIZE] = { 0 }; CHAR szMsg[BUF_SIZE] = { 0 }; GetWindowTextA(g_hEditMsg, szBuf, BUF_SIZE); wsprintfA(szMsg, "服务器说:%s", szBuf); SendMessageA(g_hListContent, LB_ADDSTRING, 0, (LPARAM)szMsg); SetWindowTextA(g_hEditMsg, ""); PSOCKETOBJ p = g_pSocketObjHeader; while (p != NULL) { send(p->m_socket, szMsg, strlen(szMsg), 0); p = p->m_pNext; }; return; }; DWORD WINAPI RecvProc(LPVOID lpParam) { SOCKET socketAccept = (SOCKET)lpParam; PSOCKETOBJ pSocketObj = FindSocketObj(socketAccept); CHAR szBuf[BUF_SIZE] = { 0 }; CHAR szMsg[BUF_SIZE] = { 0 }; int nRet = SOCKET_ERROR; PSOCKETOBJ p; while (TRUE) { ZeroMemory(szBuf, BUF_SIZE); nRet = recv(socketAccept, szBuf, BUF_SIZE, 0); if (nRet > 0) { ZeroMemory(szMsg, BUF_SIZE); wsprintfA(szMsg, "客户端[%s:%d]说:%s", pSocketObj->m_szIP, pSocketObj->m_usPort, szBuf); SendMessageA(g_hListContent, LB_ADDSTRING, 0, (LPARAM)szMsg); p = g_pSocketObjHeader; while (p != NULL) { if (p->m_socket != socketAccept) send(p->m_socket, szMsg, strlen(szMsg), 0); p = p->m_pNext; }; } else { ZeroMemory(szMsg, BUF_SIZE); wsprintfA(szMsg, "客户端[%s:%d] 已退出!", pSocketObj->m_szIP, pSocketObj->m_usPort); SendMessageA(g_hListContent, LB_ADDSTRING, 0, (LPARAM)szMsg); FreeSocketObj(pSocketObj); if (g_nTotalClient == 0) EnableWindow(g_hBtnSend, FALSE); return 0; }; }; return 0; }; DWORD WINAPI AcceptProc(LPVOID lpParam) { SOCKET socketAccept = INVALID_SOCKET; sockaddr_in sockAddrClient; int nAddrlen = sizeof(sockaddr_in); CHAR szBuf[BUF_SIZE] = { 0 }; HANDLE hThread = NULL; while (TRUE) { socketAccept = accept(g_socketListen, (sockaddr*)&sockAddrClient, &nAddrlen); if (socketAccept == INVALID_SOCKET) { Sleep(100); continue; }; ZeroMemory(szBuf, BUF_SIZE); PSOCKETOBJ pSocketObj = CreateSocketObj(socketAccept); inet_ntop(AF_INET, &sockAddrClient.sin_addr.S_un.S_addr, pSocketObj->m_szIP, _countof(pSocketObj->m_szIP)); pSocketObj->m_usPort = ntohs(sockAddrClient.sin_port); wsprintfA(szBuf, "客户端[%s:%d] 已连接!", pSocketObj->m_szIP, pSocketObj->m_usPort); SendMessageA(g_hListContent, LB_ADDSTRING, 0, (LPARAM)szBuf); EnableWindow(g_hBtnSend, TRUE); if ((hThread = CreateThread(NULL, 0, RecvProc, (LPVOID)socketAccept, 0, NULL)) != NULL) CloseHandle(hThread); }; return 0; };
|
客户端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
| #include <winsock2.h> #include <ws2tcpip.h> #include "resource.h" #pragma comment(lib, "Ws2_32")
const int BUF_SIZE = 4096;
HWND g_hwnd; HWND g_hListContent; HWND g_hEditMsg; HWND g_hBtnSend; HWND g_hBtnConnect; SOCKET g_socketClient = INVALID_SOCKET;
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
VOID OnInit(HWND hwndDlg);
VOID OnConnect();
VOID OnSend();
DWORD WINAPI RecvProc(LPVOID lpParam); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_CLIENT_DIALOG), NULL, DialogProc, NULL); return 0; }; INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_INITDIALOG: { OnInit(hwndDlg); return TRUE; }; case WM_COMMAND: { switch (LOWORD(wParam)) { case IDCANCEL: { if (g_socketClient != INVALID_SOCKET) closesocket(g_socketClient); WSACleanup(); EndDialog(hwndDlg, IDCANCEL); break; }; case IDC_BTN_CONNECT: { OnConnect(); break; }; case IDC_BTN_SEND: { OnSend(); break; }; }; return TRUE; }; }; return FALSE; };
VOID OnInit(HWND hwndDlg) { g_hwnd = hwndDlg; g_hListContent = GetDlgItem(hwndDlg, IDC_LIST_CONTENT); g_hEditMsg = GetDlgItem(hwndDlg, IDC_EDIT_MSG); g_hBtnSend = GetDlgItem(hwndDlg, IDC_BTN_SEND); g_hBtnConnect = GetDlgItem(hwndDlg, IDC_BTN_CONNECT); EnableWindow(g_hBtnSend, FALSE); return; };
VOID OnConnect() { WSADATA wsa = { 0 }; sockaddr_in sockAddrServer; HANDLE hThread = NULL; if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) { MessageBoxA(g_hwnd, "初始化WinSock库失败!", "WSAStartup Error", MB_OK); return; }; g_socketClient = socket(AF_INET, SOCK_STREAM, 0); if (g_socketClient == INVALID_SOCKET) { MessageBoxA(g_hwnd, "创建与服务器的通信套接字失败!", "socket Error", MB_OK); WSACleanup(); return; }; sockAddrServer.sin_family = AF_INET; sockAddrServer.sin_port = htons(8000); inet_pton(AF_INET, "127.0.0.1", &sockAddrServer.sin_addr.S_un.S_addr); if (connect(g_socketClient, (sockaddr*)&sockAddrServer, sizeof(sockAddrServer)) == SOCKET_ERROR) { MessageBoxA(g_hwnd, "与服务器建立连接失败!", "connect Error", MB_OK); closesocket(g_socketClient); WSACleanup(); return; }; SendMessageA(g_hListContent, LB_ADDSTRING, 0, (LPARAM)"已连接到服务器!"); EnableWindow(g_hBtnConnect, FALSE); EnableWindow(g_hBtnSend, TRUE); if ((hThread = CreateThread(NULL, 0, RecvProc, NULL, 0, NULL)) != NULL) CloseHandle(hThread); return; };
VOID OnSend() { CHAR szBuf[BUF_SIZE] = { 0 }; CHAR szMsg[BUF_SIZE] = { 0 }; GetWindowTextA(g_hEditMsg, szBuf, BUF_SIZE); wsprintfA(szMsg, "我说:%s", szBuf); SendMessageA(g_hListContent, LB_ADDSTRING, 0, (LPARAM)szMsg); send(g_socketClient, szBuf, strlen(szBuf), 0); SetWindowTextA(g_hEditMsg, ""); return; }; DWORD WINAPI RecvProc(LPVOID lpParam) { CHAR szBuf[BUF_SIZE] = { 0 }; int nRet = SOCKET_ERROR; while (TRUE) { ZeroMemory(szBuf, BUF_SIZE); nRet = recv(g_socketClient, szBuf, BUF_SIZE, 0); if (nRet > 0) SendMessageA(g_hListContent, LB_ADDSTRING, 0, (LPARAM)szBuf); else { SendMessageA(g_hListContent, LB_ADDSTRING, 0, (LPARAM)"与服务器连接已关闭!"); EnableWindow(g_hBtnConnect, TRUE); EnableWindow(g_hBtnSend, FALSE); closesocket(g_socketClient); WSACleanup(); return 0; }; }; return 0; };
|
select模型
同时管理多个套接字,管理多个连接,用集合表示管理的多个套接字。
select
检查若干个套接字状态:
1 2 3 4 5 6 7
| INT select( _In_ INT nIds, _Inout_ fd_set* readfds, _Inout_ fd_set* writefds, _Inout_ fd_set* exceptfds, _In_ CONST struct timeval* timeout );
|
其中fd_set结构把多个套接字连接在一起,形成套接字集合:
1 2 3 4
| typedef struct fd_set { u_int fd_count; SOCKET fd_array[FD_SETSIZE]; } fd_set;
|
还有宏用于操作和检查套接字集合:
宏 |
含义 |
FD_CLR(s,*set) |
从集合中删除套接字s |
FD_SET(s,*set) |
把套接字s添加到集合中 |
FD_ISSET(s,*set) |
当套接字s为集合成员则返回非0,否则0 |
FD_ZERO(*set) |
初始化套接字集合为空集合,使用前总应清空 |
对于readfds标识要检查可读性套接字的集合。当套接字处于监听状态,一旦接收到连接请求,将被标记为可读的,从而保证在不阻塞情况下完成accept
调用。对于其他套接字,可读性意味队列数据可读取,保证对recv
、WSARecv
、WSARecvFrom
、recvfrom
调用不阻塞。对于面向连接的套接字,可读性指示已从对方接收到关闭套接字的请求。当套接字发生以下网络事件时将更新可读性套接字集合:
listen
已被调用,accept
调用正在挂起,接下来将完成accept
调用。
- 可以接收数据。
- 连接已关闭/重置/终止。
对于writefds标识要检查可写性套接字的集合。当套接字正处理一个非阻塞connect
调用,一旦连接建立成功完成,套接字是可写的。若套接字没处理connect
调用,可写性意味可调用send
、sendto
或WSASendto
进行发送。当套接字发生以下网络事件时将更新可写性套接字集合:
- 如果处理非阻塞
connect
调用,connect
已成功。
- 可发送数据。
对于exceptfds检查OOB数据是否存在的或发生任何异常错误的套接字。当套接字发生以下网络事件时将更新异常套接字集合:
- 如果处理非阻塞
connect
调用,connect
调用失败。
- OOB数据可用于读取。
select
返回时会更新相关集合,把没有发生可读写网络事件的套接字从集合中移除,即会破坏原集合。3个参数中可任意2个为NULL,不是NULL的那个至少包含一个套接字句柄。集合中套接字数目最大为FD_SETSIZE,默认64,可在包含WinSock2.h前将该值定义为别的值,但最大不能超过WinSock下层协议限制的1024个。
对于timeval结构:
1 2 3 4
| struct timeval { LONG tv_sec; LONG tv_usec; };
|
例子
修改上面那个例子一个函数即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| DWORD WINAPI AcceptProc(LPVOID lpParam) { SOCKET socketAccept = INVALID_SOCKET; sockaddr_in sockAddrClient; int nAddrlen = sizeof(sockaddr_in); CHAR szBuf[BUF_SIZE] = { 0 }; CHAR szMsg[BUF_SIZE] = { 0 }; fd_set fd; PSOCKETOBJ pSocketObj, p; int nRet = SOCKET_ERROR; fd_set readfds; FD_ZERO(&readfds); FD_SET(g_socketListen, &readfds); while (TRUE) { fd = readfds; nRet = select(0, &fd, NULL, NULL, NULL); if (nRet <= 0) continue; for (UINT i = 0; i < readfds.fd_count; i++) { if (!FD_ISSET(readfds.fd_array[i], &fd)) continue; if (readfds.fd_array[i] == g_socketListen) { if (readfds.fd_count < FD_SETSIZE) { socketAccept = accept(g_socketListen, (sockaddr*)&sockAddrClient, &nAddrlen); if (socketAccept == INVALID_SOCKET) continue; FD_SET(socketAccept, &readfds); ZeroMemory(szBuf, BUF_SIZE); pSocketObj = CreateSocketObj(socketAccept); inet_ntop(AF_INET, &sockAddrClient.sin_addr.S_un.S_addr, pSocketObj->m_szIP, _countof(pSocketObj->m_szIP)); pSocketObj->m_usPort = ntohs(sockAddrClient.sin_port); wsprintfA(szBuf, "客户端[%s:%d] 已连接!", pSocketObj->m_szIP, pSocketObj->m_usPort); SendMessageA(g_hListContent, LB_ADDSTRING, 0, (LPARAM)szBuf); EnableWindow(g_hBtnSend, TRUE); } else { MessageBoxA(g_hwnd, "客户端连接数太多!", "accept Error", MB_OK); continue; }; } else { pSocketObj = FindSocketObj(readfds.fd_array[i]); ZeroMemory(szBuf, BUF_SIZE); nRet = SOCKET_ERROR; nRet = recv(pSocketObj->m_socket, szBuf, BUF_SIZE, 0); if (nRet > 0) { ZeroMemory(szMsg, BUF_SIZE); wsprintfA(szMsg, "客户端[%s:%d]说:%s", pSocketObj->m_szIP, pSocketObj->m_usPort, szBuf); SendMessageA(g_hListContent, LB_ADDSTRING, 0, (LPARAM)szMsg); p = g_pSocketObjHeader; while (p != NULL) { if (p->m_socket != pSocketObj->m_socket) send(p->m_socket, szMsg, strlen(szMsg), 0); p = p->m_pNext; }; } else { ZeroMemory(szMsg, BUF_SIZE); wsprintfA(szMsg, "客户端[%s:%d] 已退出!", pSocketObj->m_szIP, pSocketObj->m_usPort); SendMessageA(g_hListContent, LB_ADDSTRING, 0, (LPARAM)szMsg); FD_CLR(readfds.fd_array[i], &readfds); FreeSocketObj(pSocketObj); if (g_nTotalClient == 0) EnableWindow(g_hBtnSend, FALSE); }; }; }; }; };
|
WSAAsyncSelect模型
即异步选择模型,允许应用程序以Windows消息形式接收网络事件通知,为每个套接字绑定一个消息。当套接字上出现事先设置的事件时,系统给应用程序发送一个消息。优点是在系统开销不太大情况下同时处理多个客户端连接,缺点是不需要窗口也得搞个窗口用于处理套接字网络事件。
WSAAsyncSelect
通知指定套接字又网络事件发生:
1 2 3 4 5 6
| INT WSAAsyncSelect( _In_ SOCKET s, _In_ HWND hWnd, _In_ UINT wMsg, _In_ LONG lEvent );
|
函数在检测到由lEvent指定的任何网络事件发生时向窗口hWnd发送wMsg消息,wParam标识发生网络事件的套接字句柄,lParam低位字指定已发生的网络事件,高位字包含错误代码。本函数自动将套接字设置为非阻塞模式,要手动用ioctlsocket
或WSAIoctl
重设为阻塞模式前,应用本函数清除与套接字相关联的事件记录,即lEvent设为0。
lEvent有:
枚举值 |
希望接收的通知 |
事件发生时调用的函数 |
FD_READ |
读就绪 |
recv 、recvfrom 、WSARecv 、WSARecvFrom |
FD_WRITE |
写就绪 |
send 、sendto 、WSASend 、WSASendTo |
FD_ACCEPT |
有连接接入 |
accept 、WSAAccept |
FD_CONNECT |
连接完成 |
|
FD_CLOSE |
套接字关闭 |
|
FD_OOB |
带外数据到达 |
recv 、recvfrom 、WSARecv 、WSARecvFrom |
FD_QOS |
套接字服务质量QoS更改 |
WSAIoctl (SIO_GET_QOS) |
FD_GROUP_QOS(废弃) |
套接字组服务质量更改 |
|
FD_ROUTING_INTERFACE_CHANGE |
指定目的地路由接口更改 |
WSAIoctl (SIO_ROUTING_INTERFACE_CHANGE) |
FD_ADDRESS_LIST_CHANGE |
套接字协议族本地地址列表更改 |
WSAIoctl (SIO_ADDRESS_LIST_CHANGE) |
对于同一个套接字,只能在同一个消息中处理不同网络事件,而不能为不同网络事件指定不同消息。
可用宏WSAGETSELECTEVENT(lParam)
获取错误代码,用WSAGETSELECTERROR(lParam)
获取事件代码。
该函数已过时,被WSAEventSelect替代,使用时要用:
1
| #define _WINSOCK_DEPRECATED_NO_WARNINGS
|
例子
例子同上,仅服务端发生变化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
| #define _WINSOCK_DEPRECATED_NO_WARNINGS 1 #include <winsock2.h> #include <ws2tcpip.h> #include "resource.h" #include "SOCKETOBJ.h" #pragma comment(lib, "Ws2_32")
const int BUF_SIZE = 4096;
const int WM_SOCKET = WM_APP + 1;
HWND g_hwnd; HWND g_hListContent; HWND g_hEditMsg; HWND g_hBtnSend; SOCKET g_socketListen = INVALID_SOCKET;
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
VOID OnInit(HWND hwndDlg);
VOID OnStart();
VOID OnSend();
VOID OnRecv(SOCKET s);
VOID OnAccept();
VOID OnClose(SOCKET s); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_SERVER_DIALOG), NULL, DialogProc, NULL); return 0; }; INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { SOCKET s; switch (uMsg) { case WM_INITDIALOG: { OnInit(hwndDlg); return TRUE; }; case WM_COMMAND: { switch (LOWORD(wParam)) { case IDCANCEL: { if (g_socketListen != INVALID_SOCKET) closesocket(g_socketListen); WSACleanup(); EndDialog(hwndDlg, IDCANCEL); break; }; case IDC_BTN_START: { OnStart(); break; }; case IDC_BTN_SEND: { OnSend(); break; }; }; return TRUE; }; case WM_SOCKET: { s = wParam; switch (WSAGETSELECTEVENT(lParam)) { case FD_ACCEPT: { OnAccept(); break; }; case FD_READ: { OnRecv(s); break; }; case FD_WRITE: { break; }; case FD_CLOSE: { OnClose(s); break; }; }; return TRUE; }; }; return 0; };
VOID OnInit(HWND hwndDlg) { WSADATA wsa = { 0 }; if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) { MessageBox(g_hwnd, TEXT("初始化WinSock库失败!"), TEXT("WSAStartup Error"), MB_OK); return; }; InitializeCriticalSection(&g_cs); g_hwnd = hwndDlg; g_hListContent = GetDlgItem(hwndDlg, IDC_LIST_CONTENT); g_hEditMsg = GetDlgItem(hwndDlg, IDC_EDIT_MSG); g_hBtnSend = GetDlgItem(hwndDlg, IDC_BTN_SEND); EnableWindow(g_hBtnSend, FALSE); return; }; VOID OnStart() { g_socketListen = socket(AF_INET, SOCK_STREAM, 0); if (g_socketListen == INVALID_SOCKET) { MessageBox(g_hwnd, TEXT("创建监听套接字失败!"), TEXT("socket Error"), MB_OK); WSACleanup(); return; }; WSAAsyncSelect(g_socketListen, g_hwnd, WM_SOCKET, FD_ACCEPT); sockaddr_in sockAddr; sockAddr.sin_family = AF_INET; sockAddr.sin_port = htons(8000); sockAddr.sin_addr.S_un.S_addr = INADDR_ANY; if (bind(g_socketListen, (sockaddr*)&sockAddr, sizeof(sockAddr)) == SOCKET_ERROR) { MessageBox(g_hwnd, TEXT("将监听套接字与指定的IP地址、端口号捆绑失败!"), TEXT("bind Error"), MB_OK); closesocket(g_socketListen); WSACleanup(); return; }; if (listen(g_socketListen, SOMAXCONN) == SOCKET_ERROR) { MessageBox(g_hwnd, TEXT("使监听套节字进入监听(等待被连接)状态失败!"), TEXT("listen Error"), MB_OK); closesocket(g_socketListen); WSACleanup(); return; }; MessageBox(g_hwnd, TEXT("服务器监听中..."), TEXT("服务启动成功"), MB_OK); EnableWindow(GetDlgItem(g_hwnd, IDC_BTN_START), FALSE); return; }; VOID OnSend() { CHAR szBuf[BUF_SIZE] = { 0 }; CHAR szMsg[BUF_SIZE] = { 0 }; GetWindowTextA(g_hEditMsg, szBuf, BUF_SIZE); wsprintfA(szMsg, "服务器说:%s", szBuf); SendMessage(g_hListContent, LB_ADDSTRING, 0, (LPARAM)szMsg); SetWindowTextA(g_hEditMsg, ""); PSOCKETOBJ p = g_pSocketObjHeader; while (p != NULL) { send(p->m_socket, szMsg, strlen(szMsg), 0); p = p->m_pNext; }; return; }; VOID OnAccept() { SOCKET socketAccept = INVALID_SOCKET; sockaddr_in sockAddrClient; int nAddrlen = sizeof(sockaddr_in); socketAccept = accept(g_socketListen, (sockaddr*)&sockAddrClient, &nAddrlen); if (socketAccept == INVALID_SOCKET) return; WSAAsyncSelect(socketAccept, g_hwnd, WM_SOCKET, FD_READ | FD_WRITE | FD_CLOSE); CHAR szBuf[BUF_SIZE] = { 0 }; PSOCKETOBJ pSocketObj = CreateSocketObj(socketAccept); inet_ntop(AF_INET, &sockAddrClient.sin_addr.S_un.S_addr, pSocketObj->m_szIP, _countof(pSocketObj->m_szIP)); pSocketObj->m_usPort = ntohs(sockAddrClient.sin_port); wsprintfA(szBuf, "客户端[%s:%d] 已连接!", pSocketObj->m_szIP, pSocketObj->m_usPort); SendMessage(g_hListContent, LB_ADDSTRING, 0, (LPARAM)szBuf); EnableWindow(g_hBtnSend, TRUE); return; }; VOID OnRecv(SOCKET s) { PSOCKETOBJ pSocketObj = FindSocketObj(s); CHAR szBuf[BUF_SIZE] = { 0 }; int nRet = SOCKET_ERROR; nRet = recv(pSocketObj->m_socket, szBuf, BUF_SIZE, 0); if (nRet > 0) { CHAR szMsg[BUF_SIZE] = { 0 }; wsprintfA(szMsg, "客户端[%s:%d]说:%s", pSocketObj->m_szIP, pSocketObj->m_usPort, szBuf); SendMessage(g_hListContent, LB_ADDSTRING, 0, (LPARAM)szMsg); PSOCKETOBJ p = g_pSocketObjHeader; while (p != NULL) { if (p->m_socket != pSocketObj->m_socket) send(p->m_socket, szMsg, strlen(szMsg), 0); p = p->m_pNext; }; }; return; }; VOID OnClose(SOCKET s) { PSOCKETOBJ pSocketObj = FindSocketObj(s); CHAR szBuf[BUF_SIZE] = { 0 }; wsprintfA(szBuf, "客户端[%s:%d] 已退出!", pSocketObj->m_szIP, pSocketObj->m_usPort); SendMessage(g_hListContent, LB_ADDSTRING, 0, (LPARAM)szBuf); FreeSocketObj(pSocketObj); if (g_nTotalClient == 0) EnableWindow(g_hBtnSend, FALSE); return; };
|
WSAEventSelect模型
即事件选择模型。在多个套接字上接收网络事件通知,将事件对象与网络事件集合绑定,当发生网络事件时,应用程序以事件形式接收网络事件通知。缺点是一个线程中最多只能等待64个事件,再多了就得创建多线程。
WSAEventSelect
将一个事件对象与网络事件集合关联在一起。
1 2 3 4 5
| INT WSAEventSelect( _In_ SOCKET s, _In_ WSAEVENT hEventObject, _In_ LONG lNetworkEvents );
|
该函数自动将套接字设置为非阻塞模式,要用ioctlsocket
或WSAIoctl
手动设置为阻塞模式前,用该函数清除与套接字相关联的事件记录,其中lNetworkEvents为0、hEventObject为NULL。这里lNetworkEvents与WSAAsyncSelect
用法一样。对于同一个套接字,无法为不同网络事件指定不同事件对象,否则调用时将取消先前指定的事件对象。要取消指定套接字上网络事件的关联时,lNetworkEvents为0,hEventObject被忽略随便填。
WSACreateEvent
创建一个未命名的初始状态为未触发的手动重置事件对象,子进程不能继承返回的事件对象句柄。
1
| WSAEVENT WSACreateEvent(VOID);
|
WSAResetEvent
将事件对象从已触发状态重置为未触发状态:
1 2 3
| BOOL WSAResetEvent( _In_ WSAEVENT hEvent );
|
WSASetEvent
将指定事件对象设置为已触发状态:
1 2 3
| BOOL WSASetEvent( _In_ WSAEVENT hEvent );
|
WSACloseEvent
关闭事件对象句柄,释放事件对象占用的资源:
1 2 3
| BOOL WSACloseEvent( _In_ WSAEVENT hEvent );
|
进入等待状态,直到指定的一个或全部事件对象已触发、超时或I/O完成例程已执行时返回。
1 2 3 4 5 6 7
| DWORD WSAWaitForMultipleEvents( _In_ DWORD cEvents, _In_ CONST WSAEVENT* lphEvents, _In_ BOOL fWaitAll, _In_ DWORD dwTimeout, _In_ BOOL fAlertable );
|
lphEvents数组最大只能64个。对于fWaitAll为TRUE则当lphEvents数组中所有事件对象都触发时函数才返回,返回值减WSA_WAIT_EVENT_0为事件对象在数组中最小的一个索引;为FALSE则任一事件对象触发就返回,返回值同理。dwTimeout为0时立即返回,为WSA_INFINITE时函数永远等待。fAlertable为TRUE则线程处于可通知等待状态,该函数可在系统执行I/O完成例程时返回WSA_WAIT_IO_COMPLETION,此时等待的事件对象未触发,需要再次调用该函数;为FALSE则线程处于不通知等待状态,不执行I/O完成例程。
函数成功则返回值可以是:
返回值 |
含义 |
WSA_WAIT_EVENT_0~WSA_WAIT_EVENT_0+cEvents-1 |
|
WSA_WAIT_IO_COMPLETION |
|
WSA_WAIT_TIMEOUT |
超时且fWaitAll指定条件不满足 |
WSA_WAIT_FAILED |
失败 |
WSAEnumNetworkEvents
检查指定的套接字发生了哪些网络事件:
1 2 3 4 5
| INT WSAEnumNetworkEvents( _In_ SOCKET s, _In_opt_ WSAEVENT hEventObject, _Out_ LPWSANETWORKEVENTS lpNetworkEvents );
|
hEventObject为NULL时表示不重置事件对象,否则函数执行后把该事件对象重置为未触发状态。WSANETWORKEVENTS结构有:
1 2 3 4
| typedef struct _WSANETWORKEVENTS { LONG lNetworkEvents; INT iErrorCode[FD_MAX_EVENTS]; } WSANETWORKEVENTS, FAR* LPWSANETWORKEVENTS;
|
例子
还是上面那个例子,只需修改服务端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
| #include <winsock2.h> #include <ws2tcpip.h> #include "resource.h" #include "SOCKETOBJ.h" #pragma comment(lib, "Ws2_32")
const int BUF_SIZE = 4096;
HWND g_hwnd; HWND g_hListContent; HWND g_hEditMsg; HWND g_hBtnSend; SOCKET g_socketListen = INVALID_SOCKET; WSAEVENT g_eventArray[WSA_MAXIMUM_WAIT_EVENTS]; SOCKET g_socketArray[WSA_MAXIMUM_WAIT_EVENTS]; int g_nTotalEvent;
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
VOID OnInit(HWND hwndDlg);
VOID OnStart();
VOID OnSend();
DWORD WINAPI WaitProc(LPVOID lpParam); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_SERVER_DIALOG), NULL, DialogProc, NULL); return 0; }; INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_INITDIALOG: { OnInit(hwndDlg); return TRUE; }; case WM_COMMAND: { switch (LOWORD(wParam)) { case IDCANCEL: { if (g_socketListen != INVALID_SOCKET) closesocket(g_socketListen); WSACleanup(); EndDialog(hwndDlg, IDCANCEL); break; }; case IDC_BTN_START: { OnStart(); break; }; case IDC_BTN_SEND: { OnSend(); break; }; }; return TRUE; }; }; return FALSE; };
VOID OnInit(HWND hwndDlg) { WSADATA wsa = { 0 }; if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) { MessageBox(g_hwnd, TEXT("初始化WinSock库失败!"), TEXT("WSAStartup Error"), MB_OK); return; }; InitializeCriticalSection(&g_cs); g_hwnd = hwndDlg; g_hListContent = GetDlgItem(hwndDlg, IDC_LIST_CONTENT); g_hEditMsg = GetDlgItem(hwndDlg, IDC_EDIT_MSG); g_hBtnSend = GetDlgItem(hwndDlg, IDC_BTN_SEND); EnableWindow(g_hBtnSend, FALSE); return; }; VOID OnStart() { g_socketListen = socket(AF_INET, SOCK_STREAM, 0); if (g_socketListen == INVALID_SOCKET) { MessageBox(g_hwnd, TEXT("创建监听套接字失败!"), TEXT("socket Error"), MB_OK); WSACleanup(); return; }; WSAEVENT hEvent = WSACreateEvent(); WSAEventSelect(g_socketListen, hEvent, FD_ACCEPT); g_eventArray[g_nTotalEvent] = hEvent; g_socketArray[g_nTotalEvent] = g_socketListen; g_nTotalEvent++; sockaddr_in sockAddr; sockAddr.sin_family = AF_INET; sockAddr.sin_port = htons(8000); sockAddr.sin_addr.S_un.S_addr = INADDR_ANY; if (bind(g_socketListen, (sockaddr*)&sockAddr, sizeof(sockAddr)) == SOCKET_ERROR) { MessageBox(g_hwnd, TEXT("将监听套接字与指定的IP地址、端口号捆绑失败!"), TEXT("bind Error"), MB_OK); closesocket(g_socketListen); WSACleanup(); return; }; if (listen(g_socketListen, SOMAXCONN) == SOCKET_ERROR) { MessageBox(g_hwnd, TEXT("使监听套节字进入监听(等待被连接)状态失败!"), TEXT("listen Error"), MB_OK); closesocket(g_socketListen); WSACleanup(); return; }; MessageBox(g_hwnd, TEXT("服务器监听中..."), TEXT("服务启动成功"), MB_OK); EnableWindow(GetDlgItem(g_hwnd, IDC_BTN_START), FALSE); HANDLE hThread = NULL; if ((hThread = CreateThread(NULL, 0, WaitProc, NULL, 0, NULL)) != NULL) CloseHandle(hThread); return; }; VOID OnSend() { CHAR szBuf[BUF_SIZE] = { 0 }; CHAR szMsg[BUF_SIZE] = { 0 }; GetWindowTextA(g_hEditMsg, szBuf, BUF_SIZE); wsprintfA(szMsg, "服务器说:%s", szBuf); SendMessageA(g_hListContent, LB_ADDSTRING, 0, (LPARAM)szMsg); SetWindowTextA(g_hEditMsg, ""); PSOCKETOBJ p = g_pSocketObjHeader; while (p != NULL) { send(p->m_socket, szMsg, strlen(szMsg), 0); p = p->m_pNext; }; return; }; DWORD WINAPI WaitProc(LPVOID lpParam) { SOCKET socketAccept = INVALID_SOCKET; sockaddr_in sockAddrClient; int nAddrlen = sizeof(sockaddr_in); int nIndex; WSANETWORKEVENTS networkEvents; WSAEVENT hEvent = NULL; PSOCKETOBJ pSocketObj; int nRet = SOCKET_ERROR; CHAR szBuf[BUF_SIZE] = { 0 }; CHAR szMsg[BUF_SIZE] = { 0 }; while (TRUE) { nIndex = WSAWaitForMultipleEvents(g_nTotalEvent, g_eventArray, FALSE, WSA_INFINITE, FALSE); nIndex = nIndex - WSA_WAIT_EVENT_0; WSAEnumNetworkEvents(g_socketArray[nIndex], g_eventArray[nIndex], &networkEvents); if (networkEvents.lNetworkEvents & FD_ACCEPT) { if (g_nTotalEvent >= WSA_MAXIMUM_WAIT_EVENTS) { MessageBox(g_hwnd, TEXT("客户端连接数太多!"), TEXT("accept Error"), MB_OK); continue; }; socketAccept = accept(g_socketListen, (sockaddr*)&sockAddrClient, &nAddrlen); if (socketAccept == INVALID_SOCKET) { Sleep(100); continue; }; hEvent = WSACreateEvent(); WSAEventSelect(socketAccept, hEvent, FD_READ | FD_WRITE | FD_CLOSE); g_eventArray[g_nTotalEvent] = hEvent; g_socketArray[g_nTotalEvent] = socketAccept; g_nTotalEvent++; ZeroMemory(szBuf, BUF_SIZE); PSOCKETOBJ pSocketObj = CreateSocketObj(socketAccept); inet_ntop(AF_INET, &sockAddrClient.sin_addr.S_un.S_addr, pSocketObj->m_szIP, _countof(pSocketObj->m_szIP)); pSocketObj->m_usPort = ntohs(sockAddrClient.sin_port); wsprintfA(szBuf, "客户端[%s:%d] 已连接!", pSocketObj->m_szIP, pSocketObj->m_usPort); SendMessageA(g_hListContent, LB_ADDSTRING, 0, (LPARAM)szBuf); EnableWindow(g_hBtnSend, TRUE); } else if (networkEvents.lNetworkEvents & FD_READ) { pSocketObj = FindSocketObj(g_socketArray[nIndex]); ZeroMemory(szBuf, BUF_SIZE); nRet = SOCKET_ERROR; nRet = recv(g_socketArray[nIndex], szBuf, BUF_SIZE, 0); if (nRet > 0) { ZeroMemory(szMsg, BUF_SIZE); wsprintfA(szMsg, "客户端[%s:%d]说:%s", pSocketObj->m_szIP, pSocketObj->m_usPort, szBuf); SendMessageA(g_hListContent, LB_ADDSTRING, 0, (LPARAM)szMsg); PSOCKETOBJ p = g_pSocketObjHeader; while (p != NULL) { if (p->m_socket != g_socketArray[nIndex]) send(p->m_socket, szMsg, strlen(szMsg), 0); p = p->m_pNext; }; }; } else if (networkEvents.lNetworkEvents & FD_WRITE) {} else if (networkEvents.lNetworkEvents & FD_CLOSE) { ZeroMemory(szMsg, BUF_SIZE); pSocketObj = FindSocketObj(g_socketArray[nIndex]); wsprintfA(szMsg, "客户端[%s:%d] 已退出!", pSocketObj->m_szIP, pSocketObj->m_usPort); SendMessageA(g_hListContent, LB_ADDSTRING, 0, (LPARAM)szMsg); FreeSocketObj(pSocketObj); for (int j = nIndex; j < g_nTotalEvent - 1; j++) { g_eventArray[j] = g_eventArray[j + 1]; g_socketArray[j] = g_socketArray[j + 1]; }; g_nTotalEvent--; if (g_nTotalClient == 0) EnableWindow(g_hBtnSend, FALSE); }; }; };
|
Overlapped模型
即重叠模型,允许应用程序一次投递多个I/O请求。程序非阻塞调用I/O函数,当I/O操作完成时应用程序将接到通知。通知方式有事件通知和完成例程两种。
WSASocket
创建一个重叠套接字:
1 2 3 4 5 6 7 8
| SOCKET WSASocket( _In_ INT af, _In_ INT type, _In_ INT protocol, _In_opt_ LPWSAPROTOCOL_INFO lpProtocolInfo, _In_ GROUP g, _In_ DWORD dwFlags );
|
此模型需要dwFlags为WSA_FLAG_OVERLAPPED。当dwFlags为NULL时等于socket
。
AcceptEx
接受一个客户端连接,返回本地地址和远程地址,并接收客户端发送的第一块数据。
1 2 3 4 5 6 7 8 9 10
| BOOL AcceptEx( _In_ SOCKET sListenSocket, _In_ SOCKET sAcceptSocket, _In_ PVOID lpOutputBuffer, _In_ DWORD dwReceiveDataLength, _In_ DWORD dwLocalAddressLength, _In_ DWORD dwRemoteAddressLength, _Out_ LPDWORD lpdwBytesReceived, _In_ LPOVERLAPPED lpOverlapped );
|
错误码为ERROR_IO_PENDING则操作已启动;为WSAECONNRESET则连接请求已传入,但随后远程客户端在接收函数调用前终止。
该函数在Mswsock.h中定义,需要Mswsock.lib,但函数指针可以通过WSAIoctl
动态获得。
WSAIoctl
不知道用来干啥,但能用来获取某些函数指针。
1 2 3 4 5 6 7 8 9 10 11
| INT WSAIoctl( _In_ SOCKET s, _In_ DWORD dwIoControlCode, _In_ LPVOID lpvInBuffer, _In_ DWORD cbInBuffer, _Out_ LPVOID lpvOutBuffer, _In_ DWORD cbOutBuffer, _Out_opt_ LPDWORD lpcbBytesReturned, _In_opt_ LPWSAOVERLAPPED lpOverlapped, _In_ LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine );
|
获取AcceptEx
一般方法:
1 2 3 4 5
| #include <MSWSock.h> LPFN_ACCEPTEX lpfnAcceptEx = NULL; GUID GuidAcceptEx = WSAID_ACCEPTEX; DWORD dwBytes; WSAIoctl(socketListen, SIO_GET_EXTENSION_FUNCTION_POINTER, &GuidAcceptEx, sizeof(GuidAcceptEx), &lpfnAcceptEx, sizeof(lpfnAcceptEx), &dwBytes, NULL, NULL);
|
获取GetAcceptExSockaddrs
修改为:
1 2 3
| LPFN_GETACCEPTEXSOCKADDRS lpfnGetAcceptExSockaddrs = NULL; GUID GuidGetAcceptExSockaddrs = WSAID_GETACCEPTEXSOCKADDRS;
|
GetAcceptExSockaddrs
将AcceptEx
返回的信息缓冲区解析为3个不同的部分:第一块数据、本地套接字地址和远程套接字地址。当AcceptEx
连接成功且用setsockopt
在接受的套接字上设置了SO_UPDATE_ACCEPT_CONTEXT选项,则可用getsockname
获取与接受套接字相关联的本地地址,用getpeername
获取与所接受套接字相关联的远程地址。
1 2 3 4 5 6 7 8 9 10
| VOID GetAcceptExSockaddrs( _In_ PVOID lpOutputBuffer, _In_ DWORD dwReceiveDataLength, _In_ DWORD dwLocalAddressLength, _In_ DWORD dwRemoteAddressLength, _Out_ LPSOCKADDR* LocalSockaddr, _Out_ LPINT LocalSockaddrLength, _Out_ LPSOCKADDR* RemoteSockaddr, _Out_ LPINT RemoteSockaddrLength );
|
前4个参数与传递个AcceptEx
的参数相等。
该函数在Mswsock.dll中导出,需要Mswsock.lib,但也可以用WSAIoctl
动态获取。
WSASend/WSARecv
在指定套接字上发送/接收数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| INT WSASend( _In_ SOCKET s, _In_ LPWSABUF lpBuffers, _In_ DWORD dwBufferCount, _Out_ LPDWORD lpNumberOfBytesSent, _In_ DWORD dwFlags, _In_opt_ LPWSAOVERLAPPED lpOverlapped, _In_opt_ LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine ); INT WSARecv( _In_ SOCKET s, _Inout_ LPWSABUF lpBuffers, _In_ DWORD dwBufferCount, _Out_ LPDWORD lpNumberOfBytesRecvd, _Inout_opt_ LPDWORD lpFlags, _In_ LPWSAOVERLAPPED lpOverlapped, _In_ LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine );
|
对于lpBuffers有:
1 2 3 4
| typedef struct _WSABUF { ULONG len; _Field_size_bytes_(len) CHAR FAR* buf; } WSABUF, FAR* LPWSABUF;
|
当且仅当lpOverlapped不为NULL时,lpNumberOfBytesSent应为NULL。WSAOVERLAPPED为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| typedef struct _OVERLAPPED { ULONG_PTR Internal; ULONG_PTR InternalHigh; union { struct { DWORD Offset; DWORD OffsetHigh; } DUMMYSTRUCTNAME; PVOID Pointer; } DUMMYUNIONNAME; HANDLE hEvent; } OVERLAPPED, * LPOVERLAPPED; #define WSAOVERLAPPED OVERLAPPED typedef struct _OVERLAPPED* LPWSAOVERLAPPED
|
Internal返回I/O请求错误码,发出请求时该字段为STATUS_PENGING表示操作尚未开始。当lpCompletionRoutine为NULL则hEvent必须包含一个有效WSAEVENT事件对象句柄。
完成例程函数格式为:
1 2 3 4 5 6
| VOID CALLBACK CompletionROUTINE( _In_ DWORD dwError, _In_ DWORD cbTransferred, _In_ LPWSAOVERLAPPED lpOverlapped, _In_ DWORD dwFlags );
|
当I/O操作立即完成时lpNumberOfBytesSent参数才返回数据,函数返回0,失败返回SOCKET_ERROR,用WSAGetLastError
返回错误代码。错误代码为WSA_IO_PENDING为成功启动重叠操作并在稍后完成发送操作。任何其他错误代码表示未成功启动重叠操作,并不会出现完成通知。
为了传递更多信息,通常自定义一个OVERLAPPED结构,其中第一个字段为原OVERLAPPED结构,其后的部分称为I/O唯一数据或单I/O数据。每次用I/O操作函数都要创建一个该结构,一个I/O请求对应一个,I/O操作后立即释放。
1 2 3 4 5 6 7 8
| typedef struct _PERIODATA { OVERLAPPED m_overlapped; SOCKET m_socket; WSABUF m_wsaBuf; CHAR m_szBuffer[BUF_SIZE]; IOOPERATION m_ioOperation; _PERIODATA* m_pNext; }PERIODATA, * PPERIODATA;
|
举例用法:
1 2 3
| PERIODATA perIoData; WSARecv(socket, ..., (LPOVERLAPPED)&perIoData, NULL); WSARecv(socket, ..., &perIoData.m_overlapped, NULL);
|
WSAGetOverlappedResult
判断I/O调用结果是否成功:
1 2 3 4 5 6 7
| BOOL WSAAPI WSAGetOverlappedResult( _In_ SOCKET s, _In_ LPWSAOVERLAPPED lpOverlapped, _Out_ LPDWORD lpcbTransfer, _In_ BOOL fWait, _Out_ LPDWORD lpdwFlags );
|
当fWait为TRUE则直到I/O操作完成后才返回,只有采用基于事件完成通知时才能为TRUE;为FALSE且I/O操作仍在进行时函数返回FALSE,WSAGetLastError
返回错误码WSA_IO_INCOMPLETE。
成功则返回TRUE,并更新lpcbTransfer指向的值,表示重叠操作已完成;失败返回FALSE,lpcbTransfer无意义,表示重叠操作未完成、重叠操作已完成或若干参数存在错误。
例子
重写上面服务端。OVERLAPPED结构定义头文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
| #pragma once
const int BUF_SIZE = 4096;
enum IOOPERATION { IO_UNKNOWN, IO_ACCEPT, IO_READ, IO_WRITE };
typedef struct _PERIODATA{ OVERLAPPED m_overlapped; SOCKET m_socket; WSABUF m_wsaBuf; CHAR m_szBuffer[BUF_SIZE]; IOOPERATION m_ioOperation; _PERIODATA *m_pNext; }PERIODATA, *PPERIODATA; PPERIODATA g_pPerIODataHeader;
PPERIODATA CreatePerIOData(SOCKET s) { PPERIODATA pPerIOData = new PERIODATA; if (pPerIOData == NULL) return NULL; ZeroMemory(pPerIOData, sizeof(PERIODATA)); pPerIOData->m_socket = s; pPerIOData->m_overlapped.hEvent = WSACreateEvent(); EnterCriticalSection(&g_cs); if (g_pPerIODataHeader == NULL) { g_pPerIODataHeader = pPerIOData; g_pPerIODataHeader->m_pNext = NULL; } else { pPerIOData->m_pNext = g_pPerIODataHeader; g_pPerIODataHeader = pPerIOData; }; LeaveCriticalSection(&g_cs); return pPerIOData; };
VOID FreePerIOData(PPERIODATA pPerIOData) { EnterCriticalSection(&g_cs); PPERIODATA p = g_pPerIODataHeader; if (p == pPerIOData) g_pPerIODataHeader = g_pPerIODataHeader->m_pNext; else while (p != NULL) { if (p->m_pNext == pPerIOData) { p->m_pNext = pPerIOData->m_pNext; break; }; p = p->m_pNext; }; if (pPerIOData->m_overlapped.hEvent) WSACloseEvent(pPerIOData->m_overlapped.hEvent); delete pPerIOData; LeaveCriticalSection(&g_cs); };
PPERIODATA FindPerIOData(HANDLE hEvent) { EnterCriticalSection(&g_cs); PPERIODATA pPerIOData = g_pPerIODataHeader; while (pPerIOData != NULL) { if (pPerIOData->m_overlapped.hEvent == hEvent) { LeaveCriticalSection(&g_cs); return pPerIOData; }; pPerIOData = pPerIOData->m_pNext; }; LeaveCriticalSection(&g_cs); return NULL; };
VOID DeleteAllPerIOData() { PERIODATA perIOData; PPERIODATA pPerIOData = g_pPerIODataHeader; while (pPerIOData != NULL) { perIOData = *pPerIOData; if (pPerIOData->m_overlapped.hEvent) WSACloseEvent(pPerIOData->m_overlapped.hEvent); delete pPerIOData; pPerIOData = perIOData.m_pNext; }; return; };
|
服务端源代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304
| #define _WINSOCK_DEPRECATED_NO_WARNINGS #include <winsock2.h> #include <ws2tcpip.h> #include <Mswsock.h> #include <strsafe.h> #include "resource.h" #include "SOCKETOBJ.h" #include "PERIODATA.h" #pragma comment(lib, "Ws2_32") #pragma comment(lib, "Mswsock")
HWND g_hwnd; HWND g_hListContent; HWND g_hEditMsg; HWND g_hBtnSend; SOCKET g_socketListen = INVALID_SOCKET; WSAEVENT g_eventArray[WSA_MAXIMUM_WAIT_EVENTS]; int g_nTotalEvent;
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
VOID OnInit(HWND hwndDlg);
VOID OnStart();
DWORD WINAPI WaitProc(LPVOID lpParam);
BOOL PostAccept();
BOOL PostSend(PSOCKETOBJ pSocketObj, LPTSTR pStr, int nLen);
BOOL PostRecv(PSOCKETOBJ pSocketObj); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_SERVER_DIALOG), NULL, DialogProc, NULL); return 0; }; INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { CHAR szBuf[BUF_SIZE] = { 0 }; CHAR szMsg[BUF_SIZE] = { 0 }; PSOCKETOBJ p; switch (uMsg) { case WM_INITDIALOG: { OnInit(hwndDlg); return TRUE; }; case WM_COMMAND: { switch (LOWORD(wParam)) { case IDCANCEL: { if (g_socketListen != INVALID_SOCKET) closesocket(g_socketListen); WSACleanup(); EndDialog(hwndDlg, IDCANCEL); break; }; case IDC_BTN_START: { OnStart(); break; }; case IDC_BTN_SEND: { GetWindowText(g_hEditMsg, szBuf, BUF_SIZE); wsprintf(szMsg, "服务器说:%s", szBuf); SendMessage(g_hListContent, LB_ADDSTRING, 0, (LPARAM)szMsg); SetWindowText(g_hEditMsg, ""); p = g_pSocketObjHeader; while (p != NULL) { PostSend(p, szMsg, strlen(szMsg)); p = p->m_pNext; }; break; }; }; return TRUE; }; }; return FALSE; };
VOID OnInit(HWND hwndDlg) { WSADATA wsa = { 0 }; if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) { MessageBox(g_hwnd, TEXT("初始化WinSock库失败!"), TEXT("WSAStartup Error"), MB_OK); return; }; InitializeCriticalSection(&g_cs); g_hwnd = hwndDlg; g_hListContent = GetDlgItem(hwndDlg, IDC_LIST_CONTENT); g_hEditMsg = GetDlgItem(hwndDlg, IDC_EDIT_MSG); g_hBtnSend = GetDlgItem(hwndDlg, IDC_BTN_SEND); EnableWindow(g_hBtnSend, FALSE); return; }; VOID OnStart() { g_socketListen = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED); if (g_socketListen == INVALID_SOCKET) { MessageBox(g_hwnd, TEXT("创建监听套接字失败!"), TEXT("socket Error"), MB_OK); WSACleanup(); return; }; sockaddr_in sockAddr; sockAddr.sin_family = AF_INET; sockAddr.sin_port = htons(8000); sockAddr.sin_addr.S_un.S_addr = INADDR_ANY; if (bind(g_socketListen, (sockaddr*)&sockAddr, sizeof(sockAddr)) == SOCKET_ERROR) { MessageBox(g_hwnd, TEXT("将监听套接字与指定的IP地址、端口号捆绑失败!"), TEXT("bind Error"), MB_OK); closesocket(g_socketListen); WSACleanup(); return; }; if (listen(g_socketListen, SOMAXCONN) == SOCKET_ERROR) { MessageBox(g_hwnd, TEXT("使监听套节字进入监听(等待被连接)状态失败!"), TEXT("listen Error"), MB_OK); closesocket(g_socketListen); WSACleanup(); return; }; MessageBox(g_hwnd, TEXT("服务器监听中..."), TEXT("服务启动成功"), MB_OK); EnableWindow(GetDlgItem(g_hwnd, IDC_BTN_START), FALSE); HANDLE hThread = NULL; if ((hThread = CreateThread(NULL, 0, WaitProc, NULL, 0, NULL)) != NULL) CloseHandle(hThread); for (int i = 0; i < 2; i++) PostAccept(); return; }; DWORD WINAPI WaitProc(LPVOID lpParam){ sockaddr_in* pRemoteSockaddr; sockaddr_in* pLocalSockaddr; int nAddrlen = sizeof(sockaddr_in); int nIndex; PPERIODATA pPerIOData = NULL; PSOCKETOBJ pSocketObj = NULL; PSOCKETOBJ pSocketObjAccept = NULL; DWORD dwTransfer; DWORD dwFlags = 0; BOOL bRet = FALSE; CHAR szBuf[BUF_SIZE] = { 0 }; while (TRUE){ nIndex = WSAWaitForMultipleEvents(g_nTotalEvent, g_eventArray, FALSE, 1000, FALSE); if (nIndex == WSA_WAIT_TIMEOUT || nIndex == WSA_WAIT_FAILED) continue; nIndex = nIndex - WSA_WAIT_EVENT_0; WSAResetEvent(g_eventArray[nIndex]); pPerIOData = FindPerIOData(g_eventArray[nIndex]); pSocketObj = FindSocketObj(pPerIOData->m_socket); bRet = FALSE; bRet = WSAGetOverlappedResult(pPerIOData->m_socket, &pPerIOData->m_overlapped,&dwTransfer, TRUE, &dwFlags); if (!bRet) { if (pSocketObj != NULL) { ZeroMemory(szBuf, BUF_SIZE); wsprintf(szBuf, "客户端[%s:%d] 已退出!", pSocketObj->m_szIP, pSocketObj->m_usPort); SendMessage(g_hListContent, LB_ADDSTRING, 0, (LPARAM)szBuf); FreeSocketObj(pSocketObj); }; FreePerIOData(pPerIOData); for (int j = nIndex; j < g_nTotalEvent - 1; j++) g_eventArray[j] = g_eventArray[j + 1]; g_nTotalEvent--; if (g_nTotalClient == 0) EnableWindow(g_hBtnSend, FALSE); continue; }; switch (pPerIOData->m_ioOperation) { case IO_ACCEPT: { pSocketObjAccept = CreateSocketObj(pPerIOData->m_socket); ZeroMemory(szBuf, BUF_SIZE); GetAcceptExSockaddrs(pPerIOData->m_szBuffer, 0, sizeof(sockaddr_in) + 16, sizeof(sockaddr_in) + 16, (LPSOCKADDR*)&pLocalSockaddr, &nAddrlen, (LPSOCKADDR*)&pRemoteSockaddr, &nAddrlen); inet_ntop(AF_INET, &pRemoteSockaddr->sin_addr.S_un.S_addr, pSocketObjAccept->m_szIP, _countof(pSocketObjAccept->m_szIP)); pSocketObjAccept->m_usPort = ntohs(pRemoteSockaddr->sin_port); wsprintf(szBuf, "客户端[%s:%d] 已连接!", pSocketObjAccept->m_szIP, pSocketObjAccept->m_usPort); SendMessage(g_hListContent, LB_ADDSTRING, 0, (LPARAM)szBuf); EnableWindow(g_hBtnSend, TRUE); FreePerIOData(pPerIOData); for (int j = nIndex; j < g_nTotalEvent - 1; j++) g_eventArray[j] = g_eventArray[j + 1]; g_nTotalEvent--; PostRecv(pSocketObjAccept); PostAccept(); break; }; case IO_READ: { if (dwTransfer > 0) { ZeroMemory(szBuf, BUF_SIZE); wsprintf(szBuf, "客户端[%s:%d]说:%s", pSocketObj->m_szIP, pSocketObj->m_usPort, pPerIOData->m_szBuffer); SendMessage(g_hListContent, LB_ADDSTRING, 0, (LPARAM)szBuf); PSOCKETOBJ p = g_pSocketObjHeader; while (p != NULL) { if (p->m_socket != pPerIOData->m_socket) PostSend(p, szBuf, strlen(szBuf)); p = p->m_pNext; }; PostRecv(pSocketObj); } else { ZeroMemory(szBuf, BUF_SIZE); wsprintf(szBuf, "客户端[%s:%d] 已退出!", pSocketObj->m_szIP, pSocketObj->m_usPort); SendMessage(g_hListContent, LB_ADDSTRING, 0, (LPARAM)szBuf); FreeSocketObj(pSocketObj); if (g_nTotalClient == 0) EnableWindow(g_hBtnSend, FALSE); }; FreePerIOData(pPerIOData); for (int j = nIndex; j < g_nTotalEvent - 1; j++) g_eventArray[j] = g_eventArray[j + 1]; g_nTotalEvent--; break; }; case IO_WRITE: { if (dwTransfer <= 0) { ZeroMemory(szBuf, BUF_SIZE); wsprintf(szBuf, "客户端[%s:%d] 已退出!", pSocketObj->m_szIP, pSocketObj->m_usPort); SendMessage(g_hListContent, LB_ADDSTRING, 0, (LPARAM)szBuf); FreeSocketObj(pSocketObj); if (g_nTotalClient == 0) EnableWindow(g_hBtnSend, FALSE); }; FreePerIOData(pPerIOData); for (int j = nIndex; j < g_nTotalEvent - 1; j++) g_eventArray[j] = g_eventArray[j + 1]; g_nTotalEvent--; break; }; }; }; };
BOOL PostAccept() { SOCKET socketAccept = INVALID_SOCKET; BOOL bRet = FALSE; socketAccept = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED); if (socketAccept == INVALID_SOCKET) return FALSE; PPERIODATA pPerIOData = CreatePerIOData(socketAccept); pPerIOData->m_ioOperation = IO_ACCEPT; g_eventArray[g_nTotalEvent] = pPerIOData->m_overlapped.hEvent; g_nTotalEvent++; bRet = AcceptEx(g_socketListen, socketAccept, pPerIOData->m_szBuffer, 0, sizeof(sockaddr_in) + 16, sizeof(sockaddr_in) + 16, NULL, (LPOVERLAPPED)pPerIOData); if (!bRet) if (WSAGetLastError() != WSA_IO_PENDING) return FALSE; return TRUE; };
BOOL PostSend(PSOCKETOBJ pSocketObj, LPTSTR pStr, int nLen) { DWORD dwFlags = 0; PPERIODATA pPerIOData = CreatePerIOData(pSocketObj->m_socket); ZeroMemory(pPerIOData->m_szBuffer, BUF_SIZE); StringCchCopy(pPerIOData->m_szBuffer, BUF_SIZE, pStr); pPerIOData->m_wsaBuf.buf = pPerIOData->m_szBuffer; pPerIOData->m_wsaBuf.len = nLen; pPerIOData->m_ioOperation = IO_WRITE; g_eventArray[g_nTotalEvent] = pPerIOData->m_overlapped.hEvent; g_nTotalEvent++; int nRet = WSASend(pSocketObj->m_socket, &pPerIOData->m_wsaBuf, 1, NULL, dwFlags, (LPOVERLAPPED)pPerIOData, NULL); if (nRet == SOCKET_ERROR) if (WSAGetLastError() != WSA_IO_PENDING) return FALSE; return TRUE; };
BOOL PostRecv(PSOCKETOBJ pSocketObj) { DWORD dwFlags = 0; PPERIODATA pPerIOData = CreatePerIOData(pSocketObj->m_socket); ZeroMemory(pPerIOData->m_szBuffer, BUF_SIZE); pPerIOData->m_wsaBuf.buf = pPerIOData->m_szBuffer; pPerIOData->m_wsaBuf.len = BUF_SIZE; pPerIOData->m_ioOperation = IO_READ; g_eventArray[g_nTotalEvent] = pPerIOData->m_overlapped.hEvent; g_nTotalEvent++; int nRet = WSARecv(pSocketObj->m_socket, &pPerIOData->m_wsaBuf, 1, NULL, &dwFlags, (LPOVERLAPPED)pPerIOData, NULL); if (nRet == SOCKET_ERROR) if (WSAGetLastError() != WSA_IO_PENDING) return FALSE; return TRUE; };
|
I/O完成端口IOCP模型
该模型使用线程池处理异步I/O请求,可管理成百上千个套接字,广泛用于各类高性能服务器,如Apache等。
完成端口是一个Windows I/O结构,可看作一个队列,它接收文件、套接字、邮件槽、管道等多种对象句柄,系统把重叠I/O操作完成事件通知放入队列。它是一个内核对象,单仅与创建它的进程关联,不能进程间共享。创建一个I/O完成端口时,系统维护设备列表、I/O完成队列、等待线程队列、以释放线程列表和已暂停线程列表,没有安全属性结构。
设备列表表示与完成端口关联的若干个设备,每一项都包含设备句柄和完成键,用CreateIoCompletionPort
将I/O完成端口与指定句柄关联。
当一个异步I/O操作完成后,完成端口将该操作添加到I/O完成队列末尾,其中每一项都是一个完成包。每个完成包包含传输字节数、完成键、OVERLAPPED结构地址和状态代码。
当线程池中线程从I/O完成队列中获取I/O完成包时,调用线程ID会被添加到等待队列中,等待队列中每一项都包含一个线程ID。当I/O完成队列中出现一项时等待线程队列中一个线程将被唤醒,注意此过程以栈形式进行,即晚进入睡眠状态的线程先被唤醒。对于I/O请求少的用少量线程即可应付,剩下的一直休眠就行,系统也可将未被调度的线程内存资源交换到硬盘上并清除处理器中对应的高速缓存。
当完成端口唤醒一个线程时,该线程ID保存到已释放线程列表中。当一个以释放线程因某些情况进入等待状态时,完成端口将该线程ID从已释放线程列表移除并放进已暂停线程列表。
根据创建完成端口时指定的并发线程数,完成端口将尽可能多的线程保持在已释放线程列表中。当某已释放线程因某些原因进入等待状态时,该线程进入已暂停线程列表,同时已释放线程列表缩减一项,完成端口便可释放另一个正在等待线程。当该被等待线程再次被唤醒时,则离开已暂停线程列表重新进入已释放线程列表,意味着有时已释放线程列表中线程数量会大于允许的最大并发线程数量。但在正在运行线程数量降到允许最大并发线程数前,完成端口不会再继续唤醒其他线程。这就是为啥线程池中线程数量应大于完成端口中设置的并发线程数量。
CreateIoCompletionPort
创建一个I/O完成端口对象并将其指定句柄关联,或仅创建一个句柄关联的I/O完成端口,以后再关联。句柄与I/O完成端口关联后即可接收文件句柄I/O操作通知。
1 2 3 4 5 6
| HANDLE WINAPI CreateIoCompletionPort( _In_ HANDLE FileHandle, _In_opt_ HANDLE ExistingCompletionPort, _In_ ULONG_PTR CompletionKey, _In_ DWORD NumberOfConcurrentThreads );
|
FileHandler设为INVALID_HANDLE_VALUE则仅创建一个I/O完成端口,不与句柄关联,此时ExistingCompletionPort必须为NULL,CompletionKey将被忽略。ExistingCompletionPort为NULL时,如果FileHandle有效,则创建新I/O完成端口并关联,无效则函数返回新I/O完成端口句柄且不关联。NumberOfConcurrentThreads为0则分配处理器数量相同的线程个数。
用CloseHandle
关闭I/O完成端口句柄。
GetQueuedCompletionStatus
向完成端口关联套接字句柄后即可在套接字上投递重叠I/O请求,在以后某时间用OVERLAPPED结构接收之前I/O请求结果结果。I/O操作完成后系统向完成端口对象发送一个完成包,I/O完成端口以队列方式管理完成包。
用GetQueuedCompletionStatus
从完成队列中取出完成包:
1 2 3 4 5 6 7
| BOOL WINAPI GetQueuedCompletionStatus( _In_ HANDLE CompletionPort, _Out_ LPDWORD lpNumberOfBytes, _Out_ PULONG_PTR lpCompletionKey, _Out_ LPOVERLAPPED* lpOverlapped, _In_ DWORD dwMilliseconds );
|
等待时调用线程切换到睡眠模式超时则函数返回FALSE,lpOverlapped为NULL。dwMilliseconds为INFINITE则永不超时,为0且无I/O操作已完成并退出队列则立即返回。
用CloseHandle
关闭一个完成端口时,系统将所有等待该函数返回的线程唤醒并立即返回FALSE,GetLastError
返回ERROR_ABANDONED_WAIT_0。
PostQueuedCompletionStatus
将一个自定义I/O完成包投递到I/O完成队列,也可以执行些自定义操作,还可以与线程池中线程通信。
1 2 3 4 5 6
| BOOL WINAPI PostQueuedCompletionStatus( _In_ HANDLE CompletionPort, _In_ DWORD dwNumberOfBytes, _In_ ULONG_PTR dwCompletionKey, _In_opt_ LPOVERLAPPED lpOverlapped );
|
后仨参数为GetQueuedCompletionStatus
对应返回参数。
GetQueuedCompletionStatusEx
当预计有大量I/O操作完成包时,用该函数从I/O完成队列中一次获取多个完成包,避免多个线程同时等待完成包,从而避免因线程切换带来的系统开销。
1 2 3 4 5 6 7 8
| BOOL WINAPI GetQueuedCompletionStatusEx( _In_ HANDLE CompletionPort, _Out_ LPOVERLAPPED_ENTRY lpCompletionPortEntries, _In_ ULONG ulCount, _Out_ PULONG pulNumEntriesRemoved, _In_ DWORD dwMilliseconds, _In_ BOOL fAlertable );
|
其中OVERLAPPED_ENTRY结构有:
1 2 3 4 5 6
| typedef struct _OVERLAPPED_ENTRY { ULONG_PTR lpCompletionKey; LPOVERLAPPED lpOverlapped; ULONG_PTR Internal; DWORD dwNumberOfBytesTransferred; } OVERLAPPED_ENTRY, * LPOVERLAPPED_ENTRY;
|
当fAlertable为TRUE时,若I/O完成队列中无完成包,则调用线程进入可通知等待状态,系统将I/O完成例程或APC排队到线程并执行时函数返回;为FALSE则在获取到完成包或超时前不返回。
例子1
重写服务端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328
| #define _WINSOCK_DEPRECATED_NO_WARNINGS #include <winsock2.h> #include <ws2tcpip.h> #include <Mswsock.h> #include <strsafe.h> #include "resource.h" #include "SOCKETOBJ.h" #pragma comment(lib, "Ws2_32") #pragma comment(lib, "Mswsock")
const int BUF_SIZE = 4096;
enum IOOPERATION{ IO_ACCEPT, IO_READ, IO_WRITE };
typedef struct _PERIODATA{ OVERLAPPED m_overlapped; SOCKET m_socket; WSABUF m_wsaBuf; CHAR m_szBuffer[BUF_SIZE]; IOOPERATION m_ioOperation; }PERIODATA, * PPERIODATA;
HWND g_hwnd; HWND g_hListContent; HWND g_hEditMsg; HWND g_hBtnSend; SOCKET g_socketListen; HANDLE g_hCompletionPort;
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
VOID OnInit(HWND hwndDlg);
VOID OnStart();
VOID CALLBACK WorkerThreadProc(PTP_CALLBACK_INSTANCE Instance, PVOID Context);
BOOL PostAccept();
BOOL PostSend(SOCKET s, LPTSTR pStr, int nLen);
BOOL PostRecv(SOCKET s);
VOID OnSend();
ULARGE_INTEGER GetProcessorInformation(); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_SERVER_DIALOG), NULL, DialogProc, NULL); return 0; }; INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_INITDIALOG: { OnInit(hwndDlg); return TRUE; }; case WM_COMMAND: { switch (LOWORD(wParam)) { case IDCANCEL: { if (g_hCompletionPort) CloseHandle(g_hCompletionPort); if (g_socketListen != INVALID_SOCKET) closesocket(g_socketListen); WSACleanup(); EndDialog(hwndDlg, IDCANCEL); break; }; case IDC_BTN_START: { OnStart(); break; }; case IDC_BTN_SEND: { OnSend(); break; }; }; return TRUE; }; }; return FALSE; };
VOID OnInit(HWND hwndDlg) { WSADATA wsa = { 0 }; if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) { MessageBox(g_hwnd, TEXT("初始化WinSock库失败!"), TEXT("WSAStartup Error"), MB_OK); return; }; InitializeCriticalSection(&g_cs); g_hwnd = hwndDlg; g_hListContent = GetDlgItem(hwndDlg, IDC_LIST_CONTENT); g_hEditMsg = GetDlgItem(hwndDlg, IDC_EDIT_MSG); g_hBtnSend = GetDlgItem(hwndDlg, IDC_BTN_SEND); EnableWindow(g_hBtnSend, FALSE); return; }; VOID OnStart() { ULARGE_INTEGER uli = { 0 }; g_socketListen = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED); if (g_socketListen == INVALID_SOCKET) { MessageBox(g_hwnd, TEXT("创建监听套接字失败!"), TEXT("socket Error"), MB_OK); WSACleanup(); return; }; sockaddr_in sockAddr; sockAddr.sin_family = AF_INET; sockAddr.sin_port = htons(8000); sockAddr.sin_addr.S_un.S_addr = INADDR_ANY; if (bind(g_socketListen, (sockaddr*)&sockAddr, sizeof(sockAddr)) == SOCKET_ERROR) { MessageBox(g_hwnd, TEXT("将监听套接字与指定的IP地址、端口号捆绑失败!"), TEXT("bind Error"), MB_OK); closesocket(g_socketListen); WSACleanup(); return; }; if (listen(g_socketListen, SOMAXCONN) == SOCKET_ERROR) { MessageBox(g_hwnd, TEXT("使监听套节字进入监听(等待被连接)状态失败!"), TEXT("listen Error"), MB_OK); closesocket(g_socketListen); WSACleanup(); return; }; MessageBox(g_hwnd, TEXT("服务器监听中..."), TEXT("服务启动成功"), MB_OK); EnableWindow(GetDlgItem(g_hwnd, IDC_BTN_START), FALSE); g_hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); TrySubmitThreadpoolCallback(WorkerThreadProc, NULL, NULL); PSOCKETOBJ pSocketObj = CreateSocketObj(g_socketListen); CreateIoCompletionPort((HANDLE)g_socketListen, g_hCompletionPort, (ULONG_PTR)pSocketObj, 0); uli = GetProcessorInformation(); for (DWORD i = 0; i < uli.HighPart * 2; i++) PostAccept(); return; };
VOID CALLBACK WorkerThreadProc(PTP_CALLBACK_INSTANCE Instance, PVOID Context) { sockaddr_in* pRemoteSockaddr; sockaddr_in* pLocalSockaddr; int nAddrlen = sizeof(sockaddr_in); PSOCKETOBJ pSocketObj = NULL; PPERIODATA pPerIOData = NULL; DWORD dwTrans; PSOCKETOBJ pSocket; BOOL bRet; PSOCKETOBJ p; CHAR szBuf[BUF_SIZE] = { 0 }; while (TRUE) { bRet = GetQueuedCompletionStatus(g_hCompletionPort, &dwTrans, (PULONG_PTR)&pSocketObj, (LPOVERLAPPED*)&pPerIOData, INFINITE); if (!bRet) { if (GetLastError() == ERROR_ABANDONED_WAIT_0) break; else { ZeroMemory(szBuf, BUF_SIZE); wsprintf(szBuf, "客户端[%s:%d] 已退出!", pSocketObj->m_szIP, pSocketObj->m_usPort); SendMessage(g_hListContent, LB_ADDSTRING, 0, (LPARAM)szBuf); FreeSocketObj(pSocketObj); delete pPerIOData; if (g_nTotalClient == 1) EnableWindow(g_hBtnSend, FALSE); continue; }; }; switch (pPerIOData->m_ioOperation) { case IO_ACCEPT: { pSocket = CreateSocketObj(pPerIOData->m_socket); CreateIoCompletionPort((HANDLE)pSocket->m_socket, g_hCompletionPort, (ULONG_PTR)pSocket, 0); ZeroMemory(szBuf, BUF_SIZE); GetAcceptExSockaddrs(pPerIOData->m_szBuffer, 0, sizeof(sockaddr_in) + 16, sizeof(sockaddr_in) + 16, (LPSOCKADDR*)&pLocalSockaddr, &nAddrlen, (LPSOCKADDR*)&pRemoteSockaddr, &nAddrlen); inet_ntop(AF_INET, &pRemoteSockaddr->sin_addr.S_un.S_addr, pSocket->m_szIP, _countof(pSocket->m_szIP)); pSocket->m_usPort = ntohs(pRemoteSockaddr->sin_port); wsprintf(szBuf, "客户端[%s:%d] 已连接!", pSocket->m_szIP, pSocket->m_usPort); SendMessage(g_hListContent, LB_ADDSTRING, 0, (LPARAM)szBuf); EnableWindow(g_hBtnSend, TRUE); delete pPerIOData; PostRecv(pSocket->m_socket); PostAccept(); break; }; case IO_READ: { ZeroMemory(szBuf, BUF_SIZE); wsprintf(szBuf, "客户端[%s:%d]说:%s", pSocketObj->m_szIP, pSocketObj->m_usPort, pPerIOData->m_szBuffer); SendMessage(g_hListContent, LB_ADDSTRING, 0, (LPARAM)szBuf); delete pPerIOData; p = g_pSocketObjHeader; while (p != NULL) { if (p->m_socket != g_socketListen && p->m_socket != pSocketObj->m_socket) PostSend(p->m_socket, szBuf, strlen(szBuf)); p = p->m_pNext; }; PostRecv(pSocketObj->m_socket); break; }; case IO_WRITE: { delete pPerIOData; break; }; }; }; return; };
BOOL PostAccept() { PPERIODATA pPerIOData = new PERIODATA; ZeroMemory(&pPerIOData->m_overlapped, sizeof(OVERLAPPED)); pPerIOData->m_ioOperation = IO_ACCEPT; SOCKET socketAccept = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED); pPerIOData->m_socket = socketAccept; BOOL bRet = AcceptEx(g_socketListen, socketAccept, pPerIOData->m_szBuffer, 0, sizeof(sockaddr_in) + 16, sizeof(sockaddr_in) + 16, NULL, (LPOVERLAPPED)pPerIOData); if (!bRet) if (WSAGetLastError() != WSA_IO_PENDING) return FALSE; return TRUE; };
BOOL PostSend(SOCKET s, LPTSTR pStr, int nLen) { DWORD dwFlags = 0; PPERIODATA pPerIOData = new PERIODATA; ZeroMemory(&pPerIOData->m_overlapped, sizeof(OVERLAPPED)); pPerIOData->m_ioOperation = IO_WRITE; ZeroMemory(pPerIOData->m_szBuffer, BUF_SIZE); StringCchCopy(pPerIOData->m_szBuffer, BUF_SIZE, pStr); pPerIOData->m_wsaBuf.buf = pPerIOData->m_szBuffer; pPerIOData->m_wsaBuf.len = BUF_SIZE; int nRet = WSASend(s, &pPerIOData->m_wsaBuf, 1, NULL, dwFlags, (LPOVERLAPPED)pPerIOData, NULL); if (nRet == SOCKET_ERROR) if (WSAGetLastError() != WSA_IO_PENDING) return FALSE; return TRUE; };
BOOL PostRecv(SOCKET s) { DWORD dwFlags = 0; PPERIODATA pPerIOData = new PERIODATA; ZeroMemory(&pPerIOData->m_overlapped, sizeof(OVERLAPPED)); pPerIOData->m_ioOperation = IO_READ; ZeroMemory(pPerIOData->m_szBuffer, BUF_SIZE); pPerIOData->m_wsaBuf.buf = pPerIOData->m_szBuffer; pPerIOData->m_wsaBuf.len = BUF_SIZE; int nRet = WSARecv(s, &pPerIOData->m_wsaBuf, 1, NULL, &dwFlags, (LPOVERLAPPED)pPerIOData, NULL); if (nRet == SOCKET_ERROR) if (WSAGetLastError() != WSA_IO_PENDING) return FALSE; return TRUE; };
VOID OnSend() { CHAR szBuf[BUF_SIZE] = { 0 }; CHAR szMsg[BUF_SIZE] = { 0 }; GetWindowText(g_hEditMsg, szBuf, BUF_SIZE); wsprintf(szMsg, "服务器说:%s", szBuf); SendMessage(g_hListContent, LB_ADDSTRING, 0, (LPARAM)szMsg); SetWindowText(g_hEditMsg, ""); PSOCKETOBJ p = g_pSocketObjHeader; while (p != NULL) { if (p->m_socket != g_socketListen) PostSend(p->m_socket, szMsg, strlen(szMsg)); p = p->m_pNext; }; return; };
DWORD CountSetBits(ULONG_PTR bitMask) { DWORD LSHIFT = sizeof(ULONG_PTR) * 8 - 1; DWORD bitSetCount = 0; ULONG_PTR bitTest = (ULONG_PTR)1 << LSHIFT; for (DWORD i = 0; i <= LSHIFT; ++i) { bitSetCount += ((bitMask & bitTest) ? 1 : 0); bitTest /= 2; }; return bitSetCount; }; ULARGE_INTEGER GetProcessorInformation() { PSYSTEM_LOGICAL_PROCESSOR_INFORMATION pBuf = NULL; PSYSTEM_LOGICAL_PROCESSOR_INFORMATION pTemp = NULL; DWORD dwReturnedLength = 0; DWORD dwLogicalProcessorCount = 0; DWORD dwProcessorCoreCount = 0; DWORD dwByteOffset = 0; ULARGE_INTEGER uli = { 0 }; GetLogicalProcessorInformation(pBuf, &dwReturnedLength); pBuf = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION)new BYTE[dwReturnedLength]; GetLogicalProcessorInformation(pBuf, &dwReturnedLength); pTemp = pBuf; while (dwByteOffset + sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION) <= dwReturnedLength) { if (pTemp->Relationship == RelationProcessorCore) { dwProcessorCoreCount++; dwLogicalProcessorCount += CountSetBits(pTemp->ProcessorMask); }; dwByteOffset += sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION); pTemp++; }; delete[] pBuf; uli.LowPart = dwLogicalProcessorCount; uli.HighPart = dwProcessorCoreCount; return uli; };
|
例子2
文件复制:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
| #include <windows.h> #include <tchar.h> #include <strsafe.h> #include "resource.h" #pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
enum IOOPERATION { IO_UNKNOWN, IO_READ, IO_WRITE };
class PERIODATA : public OVERLAPPED { public: PERIODATA() { Internal = InternalHigh = 0; Offset = OffsetHigh = 0; hEvent = NULL; m_nBuffSize = 0; m_pData = NULL; }; ~PERIODATA() { if (m_pData != NULL) VirtualFree(m_pData, 0, MEM_RELEASE); }; BOOL AllocBuffer(SIZE_T nBuffSize) { m_nBuffSize = nBuffSize; m_pData = VirtualAlloc(NULL, m_nBuffSize, MEM_COMMIT, PAGE_READWRITE); return m_pData != NULL; }; BOOL Read(HANDLE hFile, PLARGE_INTEGER pliOffset = NULL) { if (pliOffset != NULL) { Offset = pliOffset->LowPart; OffsetHigh = pliOffset->HighPart; }; return ReadFile(hFile, m_pData, m_nBuffSize, NULL, this); }; BOOL Write(HANDLE hFile, PLARGE_INTEGER pliOffset = NULL) { if (pliOffset != NULL) { Offset = pliOffset->LowPart; OffsetHigh = pliOffset->HighPart; }; return WriteFile(hFile, m_pData, m_nBuffSize, NULL, this); }; private: SIZE_T m_nBuffSize; LPVOID m_pData; };
#define BUFSIZE (64 * 1024) #define MAX_PENDING_IO_REQS 4
HWND g_hwndDlg;
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); BOOL FileCopy(LPCTSTR pszFileSrc, LPCTSTR pszFileDest); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL); return 0; }; INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { TCHAR szFileSrc[MAX_PATH] = { 0 }; HANDLE hFileSrc = INVALID_HANDLE_VALUE; LARGE_INTEGER liFileSizeSrc = { 0 }; TCHAR szFileSize[64] = { 0 }; TCHAR szFileDest[MAX_PATH] = { 0 }; LPTSTR lpStr; TCHAR szBuf[64] = { 0 }; OPENFILENAME ofn = { sizeof(OPENFILENAME) }; ofn.hwndOwner = hwndDlg; ofn.lpstrFilter = TEXT("All(*.*)\0*.*\0"); ofn.lpstrFile = szFileSrc; ofn.lpstrFile[0] = NULL; ofn.nMaxFile = _countof(szFileSrc); ofn.lpstrInitialDir = TEXT("C:\\"); ofn.lpstrTitle = TEXT("请选择要打开的文件"); ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_CREATEPROMPT; switch (uMsg) { case WM_INITDIALOG: { g_hwndDlg = hwndDlg; return TRUE; }; case WM_COMMAND: { switch (LOWORD(wParam)) { case IDC_BTN_OPEN: { if (GetOpenFileName(&ofn)) { SetDlgItemText(hwndDlg, IDC_EDIT_FILEPATH, szFileSrc); hFileSrc = CreateFile(szFileSrc, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFileSrc == INVALID_HANDLE_VALUE) { MessageBox(hwndDlg, TEXT("打开文件失败"), TEXT("提示"), MB_OK); return TRUE; }; GetFileSizeEx(hFileSrc, &liFileSizeSrc); _i64tot_s(liFileSizeSrc.QuadPart, szFileSize, _countof(szFileSize), 10); SetDlgItemText(hwndDlg, IDC_EDIT_FILESIZE, szFileSize); CloseHandle(hFileSrc); }; break; }; case IDC_BTN_COPY: { GetDlgItemText(hwndDlg, IDC_EDIT_FILEPATH, szFileSrc, _countof(szFileSrc)); StringCchCopy(szFileDest, _countof(szFileDest), szFileSrc); lpStr = _tcsrchr(szFileDest, TEXT('.')); StringCchCopy(szBuf, _countof(szBuf), lpStr); StringCchCopy(lpStr, _countof(szFileDest), TEXT("_复制")); StringCchCat(szFileDest, _countof(szFileDest), szBuf); FileCopy(szFileSrc, szFileDest); MessageBox(hwndDlg, TEXT("复制工作完成"), TEXT("提示"), MB_OK); break; }; }; return TRUE; }; case WM_CLOSE: { EndDialog(hwndDlg, 0); return TRUE; }; }; return FALSE; }; BOOL FileCopy(LPCTSTR pszFileSrc, LPCTSTR pszFileDest) { HANDLE hFileSrc = INVALID_HANDLE_VALUE; HANDLE hFileDest = INVALID_HANDLE_VALUE; LARGE_INTEGER liFileSizeSrc = { 0 }; LARGE_INTEGER liFileSizeDest = { 0 }; HANDLE hCompletionPort; PERIODATA arrPerIOData[MAX_PENDING_IO_REQS]; LARGE_INTEGER liNextReadOffset = { 0 }; INT nReadsInProgress = 0; INT nWritesInProgress = 0; hFileSrc = CreateFile(pszFileSrc, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_NO_BUFFERING | FILE_FLAG_OVERLAPPED, NULL); if (hFileSrc == INVALID_HANDLE_VALUE) { MessageBox(g_hwndDlg, TEXT("打开文件失败"), TEXT("提示"), MB_OK); return FALSE; }; hFileDest = CreateFile(pszFileDest, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_NO_BUFFERING | FILE_FLAG_OVERLAPPED, hFileSrc); if (hFileDest == INVALID_HANDLE_VALUE) { MessageBox(g_hwndDlg, TEXT("创建文件失败"), TEXT("提示"), MB_OK); return FALSE; }; GetFileSizeEx(hFileSrc, &liFileSizeSrc); liFileSizeDest.QuadPart = ((liFileSizeSrc.QuadPart / BUFSIZE) * BUFSIZE) + (((liFileSizeSrc.QuadPart % BUFSIZE) > 0) ? BUFSIZE : 0); SetFilePointerEx(hFileDest, liFileSizeDest, NULL, FILE_BEGIN); SetEndOfFile(hFileDest); hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); if (hCompletionPort != NULL) { CreateIoCompletionPort(hFileSrc, hCompletionPort, IO_READ, 0); CreateIoCompletionPort(hFileDest, hCompletionPort, IO_WRITE, 0); } else return FALSE; for (int i = 0; i < MAX_PENDING_IO_REQS; i++) { arrPerIOData[i].AllocBuffer(BUFSIZE); PostQueuedCompletionStatus(hCompletionPort, 0, IO_WRITE, &arrPerIOData[i]); nWritesInProgress++; }; while ((nReadsInProgress > 0) || (nWritesInProgress > 0)) { ULONG_PTR CompletionKey; DWORD dwNumberOfBytesTransferred; PERIODATA* pPerIOData; GetQueuedCompletionStatus(hCompletionPort, &dwNumberOfBytesTransferred, &CompletionKey, (LPOVERLAPPED*)&pPerIOData, INFINITE); switch (CompletionKey) { case IO_READ: { nReadsInProgress--; pPerIOData->Write(hFileDest); nWritesInProgress++; break; }; case IO_WRITE: { nWritesInProgress--; if (liNextReadOffset.QuadPart < liFileSizeDest.QuadPart) { pPerIOData->Read(hFileSrc, &liNextReadOffset); nReadsInProgress++; liNextReadOffset.QuadPart += BUFSIZE; }; break; }; }; }; CloseHandle(hFileSrc); CloseHandle(hFileDest); if (hCompletionPort != NULL) CloseHandle(hCompletionPort); hFileDest = CreateFile(pszFileDest, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFileDest == INVALID_HANDLE_VALUE) { MessageBox(g_hwndDlg, TEXT("设置目标文件大小失败"), TEXT("提示"), MB_OK); return FALSE; } else { SetFilePointerEx(hFileDest, liFileSizeSrc, NULL, FILE_BEGIN); SetEndOfFile(hFileDest); CloseHandle(hFileDest); }; return TRUE; };
|
线程池
碎碎念
线程池是代表应用程序高效执行异步回调的工作线程的集合,用于减少应用程序线程数量并提供对工作线程的管理。主要应用于:
- 高度并行的应用程序,可异步调度大量小型工作项,如分布式索引搜索、网络I/O。
- 需要频繁创建和销毁进程且每个进程运行时间短的应用程序,线程池可降低线程管理复杂度和线程创建和销毁的系统开销。
- 在后台并行处理独立工作项的应用程序,如加载多个选项卡。
- 在内核对象上执行独占等待或在一个对象上等待某事件从而阻塞的应用程序。
- 创建一个线程以等待某个事件的应用程序。
工作对象
这小节好好学,下面几小节有些重复的东西就懒得写了。
TrySubmitThreadpoolCallback
设置线程池工作线程调用的回调函数。线程池技术这里不讲。
1 2 3 4 5
| BOOL WINAPI TrySubmitThreadpoolCallback( _In_ PTP_SIMPLE_CALLBACK pfns, _Inout_opt_ PVOID pv, _In_opt_ PTP_CALLBACK_ENVIRON pcbe );
|
其中回调函数格式为:
1 2 3 4
| VOID CALLBACK SimpleCallback( _Inout_ PTP_CALLBACK_INSTANCE Instance, _Inout_opt_ PVOID pv );
|
CreateThreadpoolWork
创建一个线程池工作对象:
1 2 3 4 5
| PTP_WORK WINAPI CreateThreadpoolWork( _In_ PTP_WORK_CALLBACK pfnwk, _Inout_opt_ PVOID pv, _In_opt_ PTP_CALLBACK_ENVIRON pcbe );
|
每次将工作对象提交到线程池中时,线程池中工作线程都会调用该回调函数。pcbe一般用InitializeThreadpoolEnvironment
,也可为NULL表示回调函数在默认回调环境中执行,即默认线程池。
回调函数定义格式为:
1 2 3 4 5
| VOID CALLBACK WorkCallback( _Inout_ PTP_CALLBACK_INSTANCE Instance, _Inout_opt_ PVOID Context, _Inout_ PTP_WORK Work );
|
SubmitThreadpoolWork
将一个工作对象提交到线程池:
1 2 3
| VOID WINAPI SubmitThreadpoolWork( _Inout_ PTP_WORK pwk );
|
有时TrySubmitThreadpoolCallback
会因内存不足或限额而失败,这时可用CreateThreadpoolWork
显式创建一个工作对象,再用该函数将工作对象提交到线程池中。该函数无返回值,因为它不会失败。
WaitForThreadpoolWorkCallbacks
等待工作对象回调函数执行完成,还可选择是否取消未开始执行的工作对象中的回调函数。
1 2 3 4
| VOID WINAPI WaitForThreadpoolWorkCallbacks( _Inout_ PTP_WORK pwk, _In_ BOOL fCancelPendingCallbacks );
|
当工作对象尚未提交时,函数立即返回。
CloseThreadpoolWork
关闭并释放指定工作对象:
1 2 3
| VOID WINAPI CloseThreadpoolWork( _Inout_ PTP_WORK pwk );
|
当工作对象中没有未完成的回调函数,则立即释放工作对象;若有尚未完成的回调函数,则再回调函数执行完后异步释放工作对象。
若存在与工作对象关联的清理组则无需用改函数,用CloseThreadpoolCleanupGroupMembers
释放与清理组关联的工作对象、计时器对象和等待对象。
计时器对象
CreateThreadpoolTimer
创建一个线程池计时器对象:
1 2 3 4 5
| PTP_TIMER WINAPI CreateThreadpoolTimer( _In_ PTP_TIMER_CALLBACK pfnti, _Inout_opt_ PVOID pv, _In_opt_ PTP_CALLBACK_ENVIRON pcbe );
|
回调函数格式为:
1 2 3 4 5
| VOID CALLBACK TimerCallback( _Inout_ PTP_CALLBACK_INSTANCE Instance, _Inout_opt_ PVOID Context, _Inout_ PTP_TIMER Timer );
|
SetThreadpoolTimer
设置线程池计时器对象:
1 2 3 4 5 6
| VOID WINAPI SetThreadpoolTimer( _Inout_ PTP_TIMER pti, _In_opt_ PFILETIME pftDueTime, _In_ DWORD msPeriod, _In_opt_ DWORD msWindowLength );
|
pftDueTime为正数时表示一个UTC绝对时间,程序可取的SYSTEMTIME时间,用SystemTimeToFileTime
转为本地文件时间,再用LocalFileTimeToFileTime
转为UTC文件时间。pftDueTime为NULL时停用计时器对象回调函数,正在执行的不受影响。pftDueTime指向全0的FILETIME结构时立即触发。pftDueTime为负数表示相对时间,单位100纳秒。
msPeriod为正数,直到CloseThreadpoolTimer
关闭计时器对象或用SetThreadpoolTimer
重新设置计时器对象。msPeriod为0表示一次性的。
回调函数会在从指定的触发时间到指定的触发时间加msWindowLength毫秒这个范围内触发,系统尽量安排若干个计时器对象同时触发,减少线程环境切换、线程唤醒及睡眠等电源和系统开销。
重新设置可再次调用该函数直接改。
WaitForThreadpoolTimerCallbacks
等待计时器对象回调函数执行完成,还可选择是否取消未开始执行的计时器对象中的回调函数。
1 2 3 4
| VOID WINAPI WaitForThreadpoolTimerCallbacks( _Inout_ PTP_TIMER pti, _In_ BOOL fCancelPendingCallbacks );
|
CloseThreadpoolTimer
关闭并释放指定计时器对象:
1 2 3
| VOID WINAPI CloseThreadpoolTimer( _Inout_ PTP_TIMER pti );
|
当计时器对象中没有未完成的回调函数,则立即释放计时器对象;若有尚未完成的回调函数,则再回调函数执行完后异步释放计时器对象。
若存在与计时器对象关联的清理组则无需用改函数,用CloseThreadpoolCleanupGroupMembers
释放与清理组关联的工作对象、计时器对象和等待对象。
但在该函数后仍可能会有计时器对象的回调函数执行,一般释放时需要:用SetThreadpoolTimer(Ex)
且pftDueTime为NULL,msPeriod和msWindowLength为0,再用WaitForThreadpoolTimerCallbacks
且fCancelPendingCallbacks为TRUE,最后用该函数。
IsThreadpoolTimerSet
判断指定计时器对象是否被SetThreadpoolTimer(Ex)
设置,且pftDueTime为非NULL。
1 2 3
| BOOL WINAPI IsThreadpoolTimerSet( _Inout_ PTP_TIMER pti );
|
内核对象
CreateThreadpoolWait
创建一个线程池等待对象:
1 2 3 4 5
| PTP_WAIT WINAPI CreateThreadpoolWait( _In_ PTP_WAIT_CALLBACK pfnwa, _Inout_opt_ PVOID pv, _In_opt_ PTP_CALLBACK_ENVIRON pcbe );
|
回调函数定义格式:
1 2 3 4 5 6
| VOID CALLBACK WaitCallback( _Inout_ PTP_CALLBACK_INSTANCE Instance, _Inout_opt_ PVOID Context, _Inout_ PTP_WAIT Wait, _In_ TP_WAIT_RESULT WaitResult );
|
WaitResult可以是WAIT_OBJECT_0和WAIT_TIMEOUT。
SetThreadpoolWait
设置等待对象:
1 2 3 4 5
| VOID WINAPI SetThreadpoolWait( _Inout_ PTP_WAIT pwa, _In_opt_ HANDLE h, _In_opt_ PFILETIME pftTimeout );
|
h为NULL表示停用等待对象回调函数,正在执行的回调函数不受影响。pftDueTime为正数时表示一个UTC绝对时间,程序可取的SYSTEMTIME时间,用SystemTimeToFileTime
转为本地文件时间,再用LocalFileTimeToFileTime
转为UTC文件时间。pftDueTime为NULL时停用等待对象回调函数,正在执行的不受影响。pftDueTime指向全0的FILETIME结构时立即触发。pftDueTime为负数表示相对时间,单位100纳秒。
一个等待对象只能等待一个内核对象句柄,重复调用本函数设置等待对象将替换之前的句柄。内核对象触发后变为不活跃状态,还使用则必须用该函数设置。
WaitForThreadpoolWaitCallbacks
等待等待对象回调函数执行完,还可选择是否取消尚未开始执行的等待对象中的回调函数。
1 2 3 4
| VOID WINAPI WaitForThreadpoolWaitCallbacks( _Inout_ PTP_WAIT pwa, _In_ BOOL fCancelPendingCallbacks );
|
CloseThreadpoolWait
关闭并释放指定等待对象:
1 2 3
| VOID WINAPI CloseThreadpoolWait( _Inout_ PTP_WAIT pwa );
|
但在该函数后仍可能会有等待对象的回调函数执行,一般释放时需要:用SetThreadpoolTimer(Ex)
且h为NULL,再用WaitForThreadpoolTimerCallbacks
且fCancelPendingCallbacks为TRUE,最后用该函数。
线程池I/O完成对象
CreateThreadpoolIo
创建一个I/O完成对象:
1 2 3 4 5 6
| PTP_IO WINAPI CreateThreadpoolIo( _In_ HANDLE fl, _In_ PTP_WIN32_IO_CALLBACK pfnio, _Inout_opt_ PVOID pv, _In_opt_ PTP_CALLBACK_ENVIRON pcbe );
|
回调函数定义:
1 2 3 4 5 6 7 8
| VOID CALLBACK IoCompletionCallback( _Inout_ PTP_CALLBACK_INSTANCE Instance, _Inout_opt_ PVOID Context, _Inout_opt_ PVOID Overlapped, _In_ ULONG IoResult, _In_ ULONG_PTR NumberOfBytes, _Inout_ PTP_IO Io );
|
StartThreadpoolIo
创建一个I/O完成对象。
1 2 3
| VOID WINAPI StartThreadpoolIo( _Inout_ PTP_IO pio );
|
启动每个异步I/O操作前都要用一次该函数,否则线程池将忽略异步I/O操作完成回调,并导致内存破坏。当异步I/O操作失败,即操作结果不是NO_ERROR时,必须用CancelThreadpoolIo
取消本次完成通知。当绑定到I/O完成对象的文件句柄有FILE_SKIP_COMPLETION_PORT_ON_SUCCESS通知模式,且异步I/O操作成功返回,则不会调用I/O完成对象回调函数,这时也必须用CancelThreadpoolIo
取消本次完成通知。
CancelThreadpoolIo
取消通过StartThreadpoolIo
得到的完成通知:
1 2 3
| VOID WINAPI CancelThreadpoolIo( _Inout_ PTP_IO pio );
|
WaitForThreadpoolIoCallbacks
等待I/O完成对象回调函数执行完,还可选择是否取消尚未开始执行的I/O完成对象中的回调函数。
1 2 3 4
| VOID WINAPI WaitForThreadpoolIoCallbacks( _Inout_ PTP_IO pio, _In_ BOOL fCancelPendingCallbacks );
|
CloseThreadpoolIo
关闭并释放指定I/O完成对象:
1 2 3
| VOID WINAPI CloseThreadpoolIo( _Inout_ PTP_IO pio );
|
若I/O完成对象中存在尚未完成的回调函数时,执行完后异步释放I/O完成对象。用该函数前,应先关闭关联的文件句柄并等待所有未完成异步I/O操作完成,用后不可再向I/O完成对象发起任何I/O请求。
I/O请求可在线程池任何进程上执行,调用取消函数和处理I/O函数可能不是同一个线程,取消线程池线程上I/O请求需要同步,否则导致未知I/O错误。方法是对异步I/O用CancelIoEx
时指定所需取消I/O请求的OVERLAPPED结构地址,或自己搞个同步机制确保目标线程用CancelIoEx
后没有再启动其他I/O请求。
回调函数返回时
*WhenCallbackReturns
线程池释放指定关键段对象、互斥量对象、信号量对象的所有权、将指定事件对象设为有信号状态、卸载指定DLL。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| VOID WINAPI LeaveCriticalSectionWhenCallbackReturns( _Inout_ PTP_CALLBACK_INSTANCE pci, _Inout_ PCRITICAL_SECITION pCS ); VOID WINAPI ReleaseMutexWhenCallbackReturns( _Inout_ PTP_CALLBACK_INSTANCE pci, _In_ HANDLE hMutex ); VOID WINAPI ReleaseSemaphoreWhenCallbackReturns( _Inout_ PTP_CALLBACK_INSTANCE pci, _In_ HANDLE hSemaphore, _In_ DWORD dwReleaseCount ); VOID WINAPI SetEventWhenCallbackReturns( _Inout_ PTP_CALLBACK_INSTANCE pci, _In_ HANDLE hEvent ); VOID WINAPI FreeLibraryWhenCallbackReturns( _Inout_ PTP_CALLBACK_INSTANCE pci, _In_ HMODULE hModule );
|
上述函数会自动调用LeaveCriticalSection
、ReleaseMutex
、ReleaseSemaphore
、SetEvent
或FreeLibrary
,且上述函数只能用其中一个,否则后一个调用覆盖前一个调用。
CallbackMayRunLong
表示回调函数操作需要较长时间:
1 2 3
| BOOL WINAPI CallbackMayRunLong( _Inout_ PTP_CALLBACK_INSTANCE pci );
|
该函数返回TRUE表示线程池中有另外的线程或能创建新线程处理其他工作项,这时当前回调函数可放心使用当前线程;返回FALSE时线程池只能在延迟一段时间后尝试创建新线程,这样将影响线程池执行效率,所以这种情况下建议将当前回调函数分块处理。
DisassociateCurrentThreadFromCallback
解除当前正在执行的回调函数与发起回调的对象之间的关联,当前线程将不再为该对象执行回调函数。若当前线程为最后一个代表该对象执行回调的线程,则任何等待对象回调完成的线程都将被释放。说白了就是宣告线程池工作完成,任何因用WaitForThreadpool*Callbacks
函数而阻塞的线程可尽快返回。
1 2 3
| VOID WINAPI DisassociateCurrentThreadFromCallback( _Inout_ PTP_CALLBACK_INSTANCE pci );
|
新线程池对象
CreateThreadpool
不适用进程默认线程池,创建一个新的线程池以执行回调。
1 2 3
| PTP_POOL WINAPI CreateThreadpool( _Reserved_ PVOID reserved );
|
SetThreadpoolThreadMinimum/SetThreadpoolThreadMaximum
设置线程池最小/最大并发数,默认最小并发数为1,最大为500。
1 2 3 4 5 6 7 8
| BOOL WINAPI SetThreadpoolThreadMinimum( _Inout_ PTP_POOL ptpp, _In_ DWORD dwMinThreadCount ); VOID WINAPI SetThreadpoolThreadMaximum( _Inout_ PTP_POOL ptpp, _In_ DWORD dwMaxThreadCount );
|
最大和最小并发线程数为相同值时,线程池创建一组在线程池生命周期内永不被销毁的线程。
InitializzeThreadpoolEnvironment
初始化一个回调环境定义TP_CALLBACK_ENVIRON结构。
1 2 3
| VOID InitializeThreadpoolEnvironment( _Out_ PTP_CALLBACK_ENVIRON pcbe );
|
SetThreadpoolCallbackPool
设置指定线程池回调环境:
1 2 3 4
| VOID SetThreadpoolCallbackPool( _Inout_ PTP_CALLBACK_ENVIRON pcbe, _In_ PTP_POOL );
|
DestroyThreadpoolEnvironment
销毁回调环境:
1 2 3
| VOID DestroyThreadpoolEnvironment( _Inout_ PTP_CALLBACK_ENVIRON pcbe );
|
CloseThreadpool
关闭并释放线程池:
1 2 3
| VOID WINAPI CloseThreadpool( _Inout_ PTP_POOL ptpp );
|
SetThreadpoolCallbackRunsLong
表示与回调环境相关联的回调函数需要较长时间完成:
1 2 3
| VOID SetThreadpoolCallbackRunsLong( _Inout_ PTP_CALLBACK_ENVIRON pcbe );
|
SetThreadpoolCallbackLibrary
通知线程池只要有未完成的回调,就要确保指定DLL保持加载状态。若回调函数可能获取加载程序锁,则用该函数防止DllMain
中一个线程正等待回调函数结束而另一个正执行回调函数的线程尝试获取加载程序锁时发生死锁。若包含回调函数的DLL可能被卸载,则DllMain
清理代码必须在释放对象前取消未完成的回调。
1 2 3 4
| VOID SetThreadpoolCallbackLibrary( _Inout_ PTP_CALLBACK_ENVIRON pcbe, _In_ PVOID pModule );
|
线程池清理组
通过回调环境将工作对象、计时器对象、等待对象或I/O完成对象与清理组关联,用释放清理组函数时自动清理上述对象。
CreateThreadpoolCleanupGroup
创建一个线程池清理组:
1
| PTP_CLEANUP_GROUP WINAPI CreateThreadpoolCleanupGroup(VOID);
|
SetThreadpoolCallbackCleanupGroup
将清理组与指定回调环境相关联:
1 2 3 4 5
| VOID SetThreadpoolCallbackCleanupGroup( _Inout_ PTP_CALLBACK_ENVIRON pcbe, _In_ PTP_CLEANUP_GROUP ptpcg, _In_opt_ PTP_CLEANUP_GROUP_CANCEL_CALLBACK pfng );
|
释放线程池关联对象前取消清理组,或用CloseThreadpoolCleanupGroupMembers
时,调用pfng回调函数。
回调函数定义:
1 2 3 4
| VOID NTAPI CleanupGroupCancelCallback( _Inout_opt_ PVOID ObjectContext, _Inout_opt_ PVOID CleanupContext );
|
用CreateThreadpool*
导致从清理组中隐式添加一个成员,用CloseThreadpool*
隐式删除一个成员。
CloseThreadpoolCleanupGroupMembers
释放指定清理组中所有成员,等待所有进行中的回调函数执行完,还可选择是否取消任何未完成的回调函数。
1 2 3 4 5
| VOID WINAPI CloseThreadpoolCleanupGroupMembers( _Inout_ PTP_CLEANUP_GROUP ptpcg, _In_ BOOL fCancelPendingCallbacks, _Inout_opt_ PVOID pvCleanupContext );
|
该函数会阻塞,直到所有正在执行的任何回调函数完成。函数返回后清理组中任何对象都被释放,不能通过CloseThreadpoolWork
等单独释放任何对象。关闭清理组不影响关联回调环境,回调环境直到DestroyThreadpoolEnvironment
前会一直存在。该函数也不关闭清理组本身。
CloseThreadpoolCleanupGroup
关闭清理组:
1 2 3
| VOID WINAPI CloseThreadpoolCleanupGroup( _Inout_ PTP_CLEANUP_GROUP ptpcg );
|
例子1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| #include <windows.h> #include <strsafe.h> #include "resource.h" #pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
HWND g_hwndDlg; PTP_WORK g_pTpWork;
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
VOID CALLBACK WorkCallback(PTP_CALLBACK_INSTANCE Instance, PVOID Context, PTP_WORK Work); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL); return 0; }; INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_INITDIALOG: { g_hwndDlg = hwndDlg; g_pTpWork = CreateThreadpoolWork(WorkCallback, NULL, NULL); if (g_pTpWork == NULL) ExitProcess(0); return TRUE; }; case WM_COMMAND: { switch (LOWORD(wParam)) { case IDC_BTN_SUBMIT: { SendMessage(GetDlgItem(g_hwndDlg, IDC_LIST_INFO), LB_RESETCONTENT, 0, 0); SubmitThreadpoolWork(g_pTpWork); SubmitThreadpoolWork(g_pTpWork); SubmitThreadpoolWork(g_pTpWork); SubmitThreadpoolWork(g_pTpWork); break; }; }; return TRUE; }; case WM_CLOSE: { if (g_pTpWork != NULL) CloseThreadpoolWork(g_pTpWork); EndDialog(hwndDlg, 0); return TRUE; }; }; return FALSE; }; VOID CALLBACK WorkCallback(PTP_CALLBACK_INSTANCE Instance, PVOID Context, PTP_WORK Work) { DWORD dwThreadId = GetCurrentThreadId(); TCHAR szBuf[64] = { 0 }; StringCchPrintf(szBuf, _countof(szBuf), TEXT("线程ID[%d]\t开始工作"), dwThreadId); SendMessage(GetDlgItem(g_hwndDlg, IDC_LIST_INFO), LB_ADDSTRING, 0, (LPARAM)szBuf); Sleep(1000); StringCchPrintf(szBuf, _countof(szBuf), TEXT("线程ID[%d]\t工作完成"), dwThreadId); SendMessage(GetDlgItem(g_hwndDlg, IDC_LIST_INFO), LB_ADDSTRING, 0, (LPARAM)szBuf); return; };
|
例子2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| #include <windows.h> #include <Commctrl.h> #include "resource.h" #pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
const int nSecond = 10000000;
PTP_TIMER g_pTpTimer;
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
VOID CALLBACK TimerCallback(PTP_CALLBACK_INSTANCE Instance, PVOID Context, PTP_TIMER Timer); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL); return 0; }; INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { SYSTEMTIME st = { 0 }; FILETIME ftLocal = { 0 }, ftUTC = { 0 }; switch (uMsg) { case WM_INITDIALOG: { g_pTpTimer = CreateThreadpoolTimer(TimerCallback, NULL, NULL); return TRUE; }; case WM_COMMAND: { switch (LOWORD(wParam)) { case IDC_BTN_SET: {}; case IDC_BTN_RESET: { SendDlgItemMessage(hwndDlg, IDC_DATETIMEPICKER, DTM_GETSYSTEMTIME, 0, (LPARAM)&st); SystemTimeToFileTime(&st, &ftLocal); LocalFileTimeToFileTime(&ftLocal, &ftUTC); SetThreadpoolTimer(g_pTpTimer, &ftUTC, 5000, 10); break; }; case IDC_BTN_STOP: { SetThreadpoolTimer(g_pTpTimer, NULL, 0, 0); break; }; case IDCANCEL: { SetThreadpoolTimer(g_pTpTimer, NULL, 0, 0); WaitForThreadpoolTimerCallbacks(g_pTpTimer, TRUE); CloseThreadpoolTimer(g_pTpTimer); EndDialog(hwndDlg, 0); break; }; }; return TRUE; }; }; return FALSE; }; VOID CALLBACK TimerCallback(PTP_CALLBACK_INSTANCE Instance, PVOID Context, PTP_TIMER Timer) { ShellExecute(NULL, TEXT("open"), TEXT("Calc.exe"), NULL, NULL, SW_SHOW); return; };
|
IPHelper
由IPHLPAPI.dll提供,头文件IPHlpApi.h,导入库为IPHlpApi.lib。
获取本地计算机网络适配器信息
GetAdaptersInfo
获取本地计算机网络适配器信息,已废弃用GetAdaptersAddress
。
1 2 3 4
| DWORD GetAdaptersInfo( _Out_ PIP_ADAPTER_INFO pAdapterInfo, _Inout_ PULONG pOutBufLen );
|
若pOutBufLen不够则该参数返回所需大小,返回错误码ERROR_BUFFER_OVERFLOW,执行成功返回ERROR_SUCCESS。IP_ADAPTER_INFO结构有:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| typedef struct _IP_ADAPTER_INFO { struct _IP_ADAPTER_INFO* Next; DWORD ComboIndex; char AdapterName[MAX_ADAPTER_NAME_LENGTH + 4]; char Description[MAX_ADAPTER_DESCRIPTION_LENGTH + 4]; UINT AddressLength; BYTE Address[MAX_ADAPTER_ADDRESS_LENGTH]; DWORD Index; UINT Type; UINT DhcpEnabled; PIP_ADDR_STRING CurrentIpAddress; IP_ADDR_STRING IpAddressList; IP_ADDR_STRING GatewayList; IP_ADDR_STRING DhcpServer; BOOL HaveWins; IP_ADDR_STRING PrimaryWinsServer; IP_ADDR_STRING SecondaryWinsServer; time_t LeaseObtained; time_t LeaseExpires; } IP_ADAPTER_INFO, * PIP_ADAPTER_INFO;
|
GetAdaptersAddress
获取与本地计算机上的网络适配器相关联的IPv4和IPv6地址信息:
1 2 3 4 5 6 7
| ULONG WINAPI GetAdaptersAddress( _In_ ULONG Family, _In_ ULONG Flags, _In_ PVOID Reserved, _Inout_ PIP_ADAPTER_ADDRESS AdapterAddresses, _Inout_ PULONG SizePointer );
|
Family可以是:
枚举值 |
返回内容 |
AF_UNSPEC |
返回与IPv4或IPv6相关适配器的IPv4和IPv6地址 |
AF_INET |
只返回与IPv4相关的适配器的IPv4地址 |
AF_INET6 |
只返回与IPv6相关的适配器的IPv6地址 |
IP_ADAPTER_ADDRESS结构太复杂了自己去学。
例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| #include <winsock2.h> #include <IPHlpApi.h> #include <stdio.h> #pragma comment(lib, "Ws2_32") #pragma comment(lib, "IPHlpApi") int main() { PIP_ADAPTER_INFO pAdapterInfo = NULL; PIP_ADAPTER_INFO pAdapter = NULL; ULONG ulOutBufLen = 0; GetAdaptersInfo(pAdapterInfo, &ulOutBufLen); pAdapterInfo = (PIP_ADAPTER_INFO)new CHAR[ulOutBufLen]; if (GetAdaptersInfo(pAdapterInfo, &ulOutBufLen) == ERROR_SUCCESS) { pAdapter = pAdapterInfo; while (pAdapter) { printf("适配器的名称:\t%s\n", pAdapter->AdapterName); printf("适配器的描述:\t%s\n", pAdapter->Description); printf("适配器Mac地址:\t"); for (UINT i = 0; i < pAdapter->AddressLength; i++) if (i == (pAdapter->AddressLength - 1)) printf("%02X\n", (int)pAdapter->Address[i]); else printf("%02X-", (int)pAdapter->Address[i]); printf("IP地址:\t%s\n", pAdapter->IpAddressList.IpAddress.String); printf("子网掩码:\t%s\n", pAdapter->IpAddressList.IpMask.String); printf("默认网关:\t%s\n", pAdapter->GatewayList.IpAddress.String); if (pAdapter->DhcpEnabled) { printf("启用DHCP:\t是\n"); printf("DHCP服务器:\t%s\n", pAdapter->DhcpServer.IpAddress.String); } else printf("启用DHCP:\t否\n"); printf("**********************************************************\n"); pAdapter = pAdapter->Next; }; } else printf("GetAdaptersInfo函数调用失败!\n"); delete[] pAdapterInfo; return 0; };
|
杂项
ConnectEx
建立到指定面向连接的套接字的连接,并可在建立连接口发送一块数据:
1 2 3 4 5 6 7 8 9 10
| BOOL PASCAL ConnectEx( _In_ SOCKET s, _In_ CONST struct sockaddr* name, _In_ INT namelen, _In_opt_ PVOID lpSendBuffer, _In_opt_ DWORD dwSendDataLength, _Out_opt_ LPDWORD lpdwBytesSend, _In_ LPOVERLAPPED lpOverlapped ); typedef VOID(*LPFN_CONNECTEX)();
|
错误代码为ERROR_IO_PENDING表示连接操作已成功启动但仍在进行。该函数可用WSAIoctl
动态获得,GUID为WSAID_CONNECTEX。
gethostname
获取本地计算机标准主机名:
1 2 3 4
| INT gethostname( _Out_ PCHAR name, _In_ INT namelen );
|
gethostbyname
返回指定主机名的主机名称和地址信息,已废弃用getaddrinfo
。
1 2 3
| struct hostent* FAR gethostbyname( _In_ CONST PCHAR name );
|
其中hostent结构:
1 2 3 4 5 6 7 8
| struct hostent { CHAR FAR* h_name; CHAR FAR* FAR* h_aliases; SHORT h_addrtype; SHORT h_length; CHAR FAR* FAR* h_addr_list; #define h_addr h_addr_list[0] };
|
例如:
1 2 3 4 5
| CHAR szBuf[64], szIP[16] = { 0 }; gethostname(szBuf, _countof(szBuf)); hostent* pHost = gethostbyname(szBuf); inet_ntop(AF_INET, pHost->h_addr_list[0], szIP, _countof(szIP)); printf("%s\n", szIP);
|
TransmitFile
在一个已连接的套接字上传输文件数据。该函数用系统缓存管理器获取文件数据,在套接字上提供高性能文件数据传输。
1 2 3 4 5 6 7 8 9 10
| BOOL PASCAL TransmitFile( SOCKET hSocket, HANDLE hFile, DWORD nNumberOfBytesToWrite, DWORD nNumberOfBytesPerRead, LPOVERLAPPED lpOverlapped, LPTRANSMIT_FILE_BUFFERS lpTransmitBuffers, DWORD dwFlags ); typedef VOID(*LPFN_TRANSMITFILE)();
|
通过设置lpOverlapped结构的Offset和OffsetHigh字段,指定文件开始数据传输的偏移量。lpOverlapped为NULL时数据传输从文件当前字节偏移量开始;不为NULL时可能该函数返回前重叠I/O请求不会完成,那么该函数返回FALSE,错误码为ERROR_IO_PENDING或WSA_IO_PENDING,文件传输完成后系统将重叠结构hEvent字段或hSocket指定的套接字指定事件设置为已触发状态。lpTransmitBuffers涉及发送文件数据前后要发送的数据,只想传输文件数据则设置为NULL。dwFlags可以是:
枚举值 |
含义 |
TF_DISCONNECT |
该函数操作进入等待队列后,发起一个传输层断开动作 |
TF_REUSE_SOCKET |
为套接字句柄重用做准备,即该函数完成后仍可被用作AcceptEx 的客户机套接字,必须同时指定TF_DISCONNECT |
TF_USE_DEFAULT_WORKER |
文件传输使用系统默认线程,用于发送大型文件 |
TF_USE_SYSTEM_THREAD |
该操作用系统默认线程执行 |
TF_USE_KERNEL_APC |
用内核异步过程调用处理该请求,而不用工作线程 |
TF_WRITE_BEHIND |
该请求应立即返回,即使远端未确认已收到数据,不能与TF_DISCONNECT或TF_REUSE_SOCKET同时使用 |
该函数可用WSAIoctl
动态获得,GUID为WSAID_TRANSMITFILE。
URLDownloadToFile
从网络上下载一个文件:
1 2 3 4 5 6 7
| HRESULT URLDownloadToFile( _In_opt_ LPUNKNOWN pCaller, _In_ LPCTSTR szURL, _In_opt_ LPCTSTR szFileName, _Reserved_ DWORD dwReserved, _In_opt_ LPBINDSTATUSCALLBACK lpfnCB );
|
例如:
1 2
| TCHAR szURL[] = TEXT("https://xxx/xxx.iso"), szFileName[] = TEXT("D:\\xxx\\xxx.iso"); URLDownloadToFile(NULL, szURL, szFileName, 0, NULL);
|
时间校对
有三种时间服务协议,自己去找国内可用的时间服务器。
时间日期协议RFC-867定义使用ASCⅡ字符串准确表示日期和时间;时间协议提供32位数组表示从UTC至今秒数;网络时间协议较复杂。这里举例用时间协议。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| #include <WinSock2.h> #include <WS2tcpip.h> #include <stdio.h> #pragma comment(lib, "ws2_32")
VOID SetTimeFromTP(ULONG ulTime); int main() { WSADATA wsa = { 0 }; SOCKET socketClient = INVALID_SOCKET; sockaddr_in addrServer; int nRet; if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) return 0; if ((socketClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) return 0; addrServer.sin_family = AF_INET; inet_pton(AF_INET, "132.163.97.1", (LPVOID)(&addrServer.sin_addr.S_un.S_addr)); addrServer.sin_port = htons(37); if (connect(socketClient, (sockaddr*)&addrServer, sizeof(addrServer)) == SOCKET_ERROR) return 0; ULONG ulTime = 0; nRet = recv(socketClient, (PCHAR)&ulTime, sizeof(ulTime), 0); if (nRet > 0) { ulTime = ntohl(ulTime); SetTimeFromTP(ulTime); printf("成功与时间服务器的时间同步!\n"); } else printf("时间服务器未能返回时间!\n"); closesocket(socketClient); WSACleanup(); return 0; };
VOID SetTimeFromTP(ULONG ulTime) { FILETIME ft; SYSTEMTIME st; ULARGE_INTEGER uli; st.wYear = 1900; st.wMonth = 1; st.wDay = 1; st.wHour = 0; st.wMinute = 0; st.wSecond = 0; st.wMilliseconds = 0; SystemTimeToFileTime(&st, &ft); uli.HighPart = ft.dwHighDateTime; uli.LowPart = ft.dwLowDateTime; uli.QuadPart += (ULONGLONG)10000000 * ulTime; ft.dwHighDateTime = uli.HighPart; ft.dwLowDateTime = uli.LowPart; FileTimeToSystemTime(&ft, &st); SetSystemTime(&st); return; };
|
系统网络连接
断开网络就是把所有网卡都禁用了,恢复连接就是启用所有网卡。设备的启用、禁用就是对该设备的卸载和重安装。
UuidFromString
把一个UUID字符串转换为UUID类型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| RPC_STATUS RPC_ENTRY UuidFromStringW( _In_opt_ RPC_WSTR StringUuid, _Out_ UUID* Uuid ); RPC_STATUS RPC_ENTRY UuidFromStringA( _In_opt_ RPC_CSTR StringUuid, _Out_ UUID* Uuid ); typedef _Null_terminated_ USHORT __RPC_FAR* RPC_WSTR; typedef _Null_terminated_ UCHAR __RPC_FAR* RPC_CSTR; typedef GUID UUID; typedef struct _GUID { ULONG Data1; USHORT Data2; USHORT Data3; UCHAR Data4[ 8 ]; } GUID;
|
例如:
1 2
| GUID guid; UuidFromString((RPC_WSTR)TEXT("4D36E972-E325-11CE-BFC1-08002BE10318"), &guid);
|
SetupDiGetClassDevs
返回一个包含本机所有被请求设备的设备信息集句柄:
1 2 3 4 5 6
| HDEVINFO SetupDiGetClassDevs( _In_opt_ CONST GUID* ClassGuid, _In_opt_ PCTSTR Enumerator, _In_opt_ HWND hwndParent, _In_ DWORD Flags );
|
Flags可以是:
枚举值 |
含义 |
DIGCF_ALLCLASSES |
返回所有设备安装/接口类已安装设备列表,ClassGuid为NULL |
DIGCF_PRESENT |
仅返回系统当前存在的设备 |
DIGCF_PROFILE |
仅返回属于当前硬件配置文件的设备 |
SetupDiEnumDeviceInfo
枚举设备信息集中的设备,返回一个设备的信息。
1 2 3 4 5
| BOOL SetupDiEnumDeviceInfo( _In_ HDEVINFO DeviceInfoSet, _In_ DWORD MemberIndex, _Out_ PSP_DEVINFO_DATA DeviceInfoData );
|
MemberIndex开始为0,依次递增,直到枚举完毕返回FALSE,错误码ERROR_NO_MORE_ITEMS。其中SP_DEVINFO_DATA结构为:
1 2 3 4 5 6
| typedef struct _SP_DEVINFO_DATA { DWORD cbSize; GUID ClassGuid; DWORD DevInst; ULONG_PTR Reserved; } SP_DEVINFO_DATA, * PSP_DEVINFO_DATA;
|
SetupDiSetClassInstallParams
设置或清除设备信息集或特定设备的类安装参数:
1 2 3 4 5 6
| BOOL SetupDiSetClassInstallParams( _In_ HDEVINFO DeviceInfoSet, _In_opt_ PSP_DEVINFO_DATA DeviceInfoData, _In_opt_ PSP_CLASSINSTALL_HEADER ClassInstallParams, _In_ DWORD ClassInstallParamsSize );
|
这里ClassInstallParams改用SP_PROPCHANGE_PARAMS结构:
1 2 3 4 5 6 7 8 9 10
| typedef struct _SP_PROPCHANGE_PARAMS { SP_CLASSINSTALL_HEADER ClassInstallHeader; DWORD StateChange; DWORD Scope; DWORD HwProfile; } SP_PROPCHANGE_PARAMS, * PSP_PROPCHANGE_PARAMS; typedef struct _SP_CLASSINSTALL_HEADER { DWORD cbSize; DI_FUNCTION InstallFunction; } SP_CLASSINSTALL_HEADER, * PSP_CLASSINSTALL_HEADER;
|
其中InstallFunction需要为DIF_PROPERTYCHANGE表示要更改设备安装属性,StateChange为DICS_ENABLE或DICS_DISABLE表示启用或禁用,Scope为DICS_FLAG_GLOBAL表示全局作用域。
SetupDiCallClassInstaller
执行设备安装:
1 2 3 4 5
| BOOL SetupDiCallClassInstaller( _In_ DI_FUNCTION InstallFunction, _In_ HDEVINFO DeviceInfoSet, _In_opt_ PSP_DEVINFO_DATA DeviceInfoData );
|
其中InstallFunction设置为DIF_PROPERTYCHANGE表示要更改设备安装属性。
SetupDiDestroyDeviceInfoList
删除设备信息集并释放相关内存:
1 2 3
| BOOL SetupDiDestroyDeviceInfoList( _In_ HDEVINFO DeviceInfoSet );
|
例子
COM组件法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| #include <windows.h> #include <NetCon.h> #include "resource.h"
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); BOOL ConnectNetwork(BOOL bConnect); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL); return 0; }; INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_COMMAND: { switch (LOWORD(wParam)) { case IDC_BTN_CONNECT: { ConnectNetwork(TRUE); break; }; case IDC_BTN_DISCONNECT: { ConnectNetwork(FALSE); break; }; case IDCANCEL: { EndDialog(hwndDlg, 0); break; }; }; return TRUE; }; }; return FALSE; }; BOOL ConnectNetwork(BOOL bConnect) { HRESULT hr; INetConnectionManager* pNetConnManager; INetConnection* pNetConn; IEnumNetConnection* pEnumNetConn; ULONG uCeltFetched; CoInitializeEx(NULL, 0); hr = CoCreateInstance(CLSID_ConnectionManager, NULL, CLSCTX_SERVER, IID_INetConnectionManager, (LPVOID*)&pNetConnManager); if (FAILED(hr)) return FALSE; pNetConnManager->EnumConnections(NCME_DEFAULT, &pEnumNetConn); pNetConnManager->Release(); if (pEnumNetConn == NULL) return FALSE; while (pEnumNetConn->Next(1, &pNetConn, &uCeltFetched) == S_OK) if (bConnect) pNetConn->Connect(); else pNetConn->Disconnect(); CoUninitialize(); return TRUE; };
|
SetupAPI法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
| #include <windows.h> #include <SetupAPI.h> #include "resource.h" #pragma comment(lib, "Rpcrt4") #pragma comment(lib, "SetupAPI")
HWND g_hwndDlg;
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); BOOL ConnectNetwork(BOOL bConnect); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL); return 0; }; INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_INITDIALOG: { g_hwndDlg = hwndDlg; return TRUE; }; case WM_COMMAND: { switch (LOWORD(wParam)) { case IDC_BTN_CONNECT: { ConnectNetwork(TRUE); break; }; case IDC_BTN_DISCONNECT: { ConnectNetwork(FALSE); break; }; case IDCANCEL: { EndDialog(hwndDlg, 0); break; }; }; return TRUE; }; }; return FALSE; }; BOOL ConnectNetwork(BOOL bConnect) { GUID guid; DWORD dwNewState; HDEVINFO hDevInfoSet; SP_DEVINFO_DATA spDevInfoData; int nDeviceIndex = 0; SP_PROPCHANGE_PARAMS spPropChangeParams; if (bConnect) dwNewState = DICS_ENABLE; else dwNewState = DICS_DISABLE; UuidFromString((RPC_WSTR)TEXT("4D36E972-E325-11CE-BFC1-08002BE10318"), &guid); hDevInfoSet = SetupDiGetClassDevs(&guid, NULL, NULL, DIGCF_PRESENT); if (hDevInfoSet == INVALID_HANDLE_VALUE) { MessageBox(g_hwndDlg, TEXT("获取设备信息集句柄出错!"), TEXT("错误提示"), MB_OK); return FALSE; }; ZeroMemory(&spDevInfoData, sizeof(SP_DEVINFO_DATA)); spDevInfoData.cbSize = sizeof(SP_DEVINFO_DATA); ZeroMemory(&spPropChangeParams, sizeof(SP_PROPCHANGE_PARAMS)); spPropChangeParams.ClassInstallHeader.cbSize = sizeof(SP_CLASSINSTALL_HEADER); spPropChangeParams.ClassInstallHeader.InstallFunction = DIF_PROPERTYCHANGE; spPropChangeParams.StateChange = dwNewState; spPropChangeParams.Scope = DICS_FLAG_GLOBAL; while (TRUE) { if (!SetupDiEnumDeviceInfo(hDevInfoSet, nDeviceIndex, &spDevInfoData)) if (GetLastError() == ERROR_NO_MORE_ITEMS) break; nDeviceIndex++; SetupDiSetClassInstallParams(hDevInfoSet, &spDevInfoData, (PSP_CLASSINSTALL_HEADER)&spPropChangeParams, sizeof(spPropChangeParams)); SetupDiCallClassInstaller(DIF_PROPERTYCHANGE, hDevInfoSet, &spDevInfoData); }; SetupDiDestroyDeviceInfoList(hDevInfoSet); return TRUE; };
|
FTP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
| #include <windows.h> #include <WinInet.h> #include <tchar.h> #include <strsafe.h> #include "resource.h" #pragma comment(lib, "Wininet.lib") #pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL); return 0; }; INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { static TCHAR szFile[MAX_PATH] = { 0 }; static TCHAR szFileTitle[MAX_PATH] = { 0 }; OPENFILENAME ofn = { sizeof(OPENFILENAME) }; ofn.hwndOwner = hwndDlg; ofn.lpstrFilter = TEXT("All(*.*)\0*.*\0"); ofn.lpstrFile = szFile; ofn.nMaxFile = _countof(szFile); ofn.lpstrFileTitle = szFileTitle; ofn.nMaxFileTitle = _countof(szFileTitle); ofn.lpstrInitialDir = TEXT("C:\\"); ofn.lpstrTitle = TEXT("请选择要打开的文件"); ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_CREATEPROMPT; static HINTERNET hInternet = NULL; static HINTERNET hConnect = NULL; TCHAR szCurrentDirectory[MAX_PATH] = { 0 }; DWORD dwCurrentDirectory = _countof(szCurrentDirectory); switch (uMsg) { case WM_INITDIALOG: { hInternet = InternetOpen(TEXT("Mozilla/5.0"), INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0); hConnect = InternetConnect(hInternet, TEXT("127.0.0.1"), INTERNET_DEFAULT_FTP_PORT, TEXT("admin"), TEXT("123456"), INTERNET_SERVICE_FTP, 0, 0); return TRUE; }; case WM_COMMAND: { switch (LOWORD(wParam)) { case IDC_BTN_BROWSE: { if (GetOpenFileName(&ofn)) SetDlgItemText(hwndDlg, IDC_EDIT_FILE, szFile); break; }; case IDC_BTN_PUTFILE: { GetDlgItemText(hwndDlg, IDC_EDIT_FILE, szFile, _countof(szFile)); FtpPutFile(hConnect, szFile, szFileTitle, FTP_TRANSFER_TYPE_BINARY, 0); break; }; case IDC_BTN_GETFILE: { GetModuleFileName(NULL, szFile, _countof(szFile)); StringCchCopy(_tcsrchr(szFile, TEXT('\\')) + 1, _tcslen(szFileTitle) + 1, szFileTitle); FtpGetFile(hConnect, szFileTitle, szFile, FALSE, FILE_ATTRIBUTE_NORMAL, FTP_TRANSFER_TYPE_BINARY | INTERNET_FLAG_RESYNCHRONIZE, 0); break; }; case IDC_BTN_CREATEDIRECTORY: { FtpCreateDirectory(hConnect, TEXT("新建文件夹")); break; }; case IDC_BTN_REMOVEDIRECTORY: { FtpSetCurrentDirectory(hConnect, TEXT("/")); FtpRemoveDirectory(hConnect, TEXT("新建文件夹")); break; }; case IDC_BTN_SETDIRECTORY: { FtpSetCurrentDirectory(hConnect, TEXT("新建文件夹")); FtpGetCurrentDirectory(hConnect, szCurrentDirectory, &dwCurrentDirectory); SetDlgItemText(hwndDlg, IDC_EDIT_DIRECTORY, szCurrentDirectory); break; }; case IDC_BTN_GETDIRECTORY: { FtpGetCurrentDirectory(hConnect, szCurrentDirectory, &dwCurrentDirectory); SetDlgItemText(hwndDlg, IDC_EDIT_DIRECTORY, szCurrentDirectory); break; }; }; return TRUE; }; case WM_CLOSE: { InternetCloseHandle(hConnect); InternetCloseHandle(hInternet); EndDialog(hwndDlg, 0); return TRUE; }; }; return FALSE; };
|
HTTP
HttpQueryInfo
用于获取与HTTP请求关联的标头信息:
1 2 3 4 5 6 7
| BOOLAPI HttpQueryInfo( _In_ HINTERNET hRequest, _In_ DWORD dwInfoLevel, _Out_ LPVOID lpBuffer, _Inout_ LPDWORD lpdwBufferLength, _Inout_opt_ LPDWORD lpdwIndex );
|
hRequest参数指定为HttpOpenRequest或InternetOpenUrl函数返回的Internet句柄。dwInfoLevel参数用于指定获取标志,可以是以下值的组合:
常量 |
含义 |
HTTP_QUERY_CACHE_CONTROL |
获取Cache-Control,缓存控制 |
HTTP_QUERY_CONNECTION |
获取Connection,为特定连接指定的任何选项,控制不再转发给代理的标头 |
HTTP_QUERY_DATE |
获取Date,创建报文的日期时间 |
HTTP_QUERY_TRANSFER_ENCODING |
获取Transfer-Encoding,报文主体的传输编码方式 |
HTTP_QUERY_UPGRADE |
获取Upgrade,服务器支持的其他通信协议 |
HTTP_QUERY_VIA |
获取Via,代理服务器的相关信息 |
HTTP_QUERY_WARNING |
获取Warning,警告信息 |
HTTP_QUERY_ACCEPT |
获取Accept,文档类型 |
HTTP_QUERY_ACCEPT_CHARSET |
获取Accept-Charset,字符集 |
HTTP_QUERY_ACCEPT_ENCODING |
获取Accept-Encoding,内容编码 |
HTTP_QUERY_ACCEPT_LANGUAGE |
获取Accept-Language,语言(自然语言) |
HTTP_QUERY_AUTHORIZATION |
获取Authorization,认证信息 |
HTTP_QUERY_EXPECT |
获取Expect,客户端是否应期待100系列响应 |
HTTP_QUERY_FROM |
获取From,电子邮件地址 |
HTTP_QUERY_HOST |
获取Host,主机名和端口号 |
HTTP_QUERY_IF_MATCH |
获取If-Match,服务器会比较If-Match和资源的ETag值,仅当两者一致时,才会执行请求 |
HTTP_QUERY_IF_NONE_MATCH |
获取If-None-Match,服务器会比较If-Match和资源的ETag值,仅当两者不一致时,才会执行请求 |
HTTP_QUERY_IF_MODIFIED_SINCE |
获取If-Modified-Since,比较资源的更新时间,在指定的日期时间之后如果资源发生了更新, 服务器会接受请求 |
HTTP_QUERY_IF_UNMODIFIED_SINCE |
获取If-Unmodified-Since,比较资源的更新时间,在指定的日期时间之后没有发生更新的情况下, 服务器才会接受请求 |
HTTP_QUERY_IF_RANGE |
获取If-Range,指定的If-Range值(ETag值或时间)和请求资源的ETag值或时间相一致时作为范围请求处理;反之,则返回全体资源 |
HTTP_QUERY_MAX_FORWARDS |
获取Max-Forwards,可以将请求转发到下一个入站服务器的代理或网关的数量 |
HTTP_QUERY_PROXY_AUTHORIZATION |
获取Proxy-Authorization,代理服务器要求客户端的认证信息 |
HTTP_QUERY_RANGE |
获取Range,实体的字节范围请求 |
HTTP_QUERY_REFERER |
获取Referer,请求来源 |
HTTP_QUERY_USER_AGENT |
获取User-Agent,HTTP客户端程序的信息 |
HTTP_QUERY_ACCEPT_RANGES |
获取响应头的Accept-Ranges,是否接受字节范围请求 |
HTTP_QUERY_AGE |
获取响应头的Age,资源创建经过的时间 |
HTTP_QUERY_ETAG |
获取响应头的ETag,资源的匹配信息 |
HTTP_QUERY_LOCATION |
获取响应头的Location,令客户端重定向至指定URL |
HTTP_QUERY_PROXY_AUTHENTICATE |
获取响应头的Proxy-Authenticate,把代理服务器所要求的认证信息发送给客户端 |
HTTP_QUERY_RETRY_AFTER |
获取响应头的Retry-After,多久之后可以再次发送请求 |
HTTP_QUERY_SERVER |
获取响应头的Server,HTTP服务器的安装信息 |
HTTP_QUERY_VARY |
获取响应头的Vary,代理服务器缓存的管理信息 |
HTTP_QUERY_WWW_AUTHENTICATE |
获取响应头的WWW-Authenticate,访问请求指定资源的认证方案 |
HTTP_QUERY_ALLOW |
获取Allow,服务器支持的HTTP请求方法 |
HTTP_QUERY_CONTENT_ENCODING |
获取Content-Encoding,实体主体的内容编码方式 |
HTTP_QUERY_CONTENT_LANGUAGE |
获取Content-Language,实体主体的语言(自然语言) |
HTTP_QUERY_CONTENT_LENGTH |
获取Content-Length,实体主体的大小(以字节为单位) |
HTTP_QUERY_CONTENT_LOCATION |
获取Content-Location,和Location不同, Content-Location表示的是实体主体返回的资源对应的URL |
HTTP_QUERY_CONTENT_MD5 |
获取Content-MD5,为实体主体提供端到端消息完整性检查(MIC) |
HTTP_QUERY_CONTENT_RANGE |
获取Content-Range,完整实体主体中应插入部分实体主体的位置以及完整实体主体的总大小 |
HTTP_QUERY_CONTENT_TYPE |
获取Content-Type,实体主体的文档类型 |
HTTP_QUERY_EXPIRES |
获取Expires,实体主体过期的日期时间 |
HTTP_QUERY_LAST_MODIFIED |
获取Last-Modified,资源的最后修改日期时间 |
HTTP_QUERY_COOKIE |
获取Cookie,与请求关联的cookie |
HTTP_QUERY_SET_COOKIE |
获取Set-Cookie,为请求设置的cookie的值 |
HTTP_QUERY_RAW_HEADERS |
获取服务器返回的所有标头,每个标头都以’\0’结尾,所有标头末尾有一个附加的’\0’ |
HTTP_QUERY_RAW_HEADERS_CRLF |
获取服务器返回的所有标头,每个标头都以回车换行分隔 |
HTTP_QUERY_REQUEST_METHOD |
获取请求方法,通常是GET或POST |
HTTP_QUERY_STATUS_CODE |
获取服务器返回的状态码 |
HTTP_QUERY_STATUS_TEXT |
获取服务器在响应行上返回的任何附加文本 |
HTTP_QUERY_CONTENT_BASE |
获取用于解析实体内的相对URL的基本URL |
HTTP_QUERY_MIME_VERSION |
获取用于构造消息的MIME协议的版本 |
HTTP_QUERY_PROXY_CONNECTION |
获取代理连接标头 |
HTTP_QUERY_PUBLIC |
获取服务器上可用的方法 |
HTTP_QUERY_UNLESS_MODIFIED_SINCE |
获取Unless-Modified-Since标头 |
HTTP_QUERY_VERSION |
获取服务器返回的最后一个响应代码 |
HTTP_QUERY_X_CONTENT_TYPE_OPTIONS |
获取X-Content-Type-Options标头值 |
HTTP_QUERY_P3P |
获取P3P标头值 |
HTTP_QUERY_X_P2P_PEERDIST |
获取X-P2P-PeerDist标头值 |
HTTP_QUERY_TRANSLATE |
获取translate标头值 |
HTTP_QUERY_X_UA_COMPATIBLE |
获取X-UA-Compatible标头值 |
HTTP_QUERY_DEFAULT_STYLE |
获取Default-Style标头值 |
HTTP_QUERY_X_FRAME_OPTIONS |
获取X-Frame-Options标头值 |
HTTP_QUERY_X_XSS_PROTECTION |
获取X-XSS-Protection标头值 |
HTTP_QUERY_FLAG_NUMBER |
对于值为数字(例如状态代码)的标头,将数据作为32位数字返回 |
HTTP_QUERY_FLAG_REQUEST_HEADERS |
仅查询请求头 |
HTTP_QUERY_FLAG_SYSTEMTIME |
将标头值作为SYSTEMTIME结构返回,用于值为日期时间字符串的标头 |
HTTP_QUERY_CONTENT_ID |
获取内容ID |
HTTP_QUERY_CONTENT_TRANSFER_ENCODING |
获取资源的附加内容编码 |
HTTP_QUERY_CUSTOM |
获取lpBuffer指定的标头名称并将标头数据存储在lpBuffer中 |
HTTP_QUERY_PRAGMA |
获取特定于实现的指令,这些指令可能应用于请求/响应链上的任何接收者 |
HTTP_QUERY_URI |
获取部分或全部统一资源标识符(URI),通过该标识符可以识别Request-URI资源 |
lpBuffer参数指定为接收所请求信息的缓冲区的指针。lpdwBufferLength参数用于指定缓冲区的长度,以字节为单位。lpdwIndex参数指定为从0开始的标头索引,用于枚举具有相同名称的多个标头时。要枚举具有相同名称的多个标头,第1次调用HttpQueryInfo函数的时候可以将lpdwIndex参数指向的DWORD型变量的值设置为0,函数返回时,该参数指向的DWORD型变量的值会被设置为下一个标头的索引,程序可以通过lpdwIndex参数的返回值循环调用HttpQueryInfo函数,每次调用HttpQueryInfo函数都应该通过调用GetLastError函数获取错误代码,直到返回错误代码ERROR_HTTP_HEADER_NOT_FOUND。
InternetSetOption
设置Internet选项:
1 2 3 4 5 6
| BOOLAPI InternetSetOption( _In_opt_ HINTERNET hInternet, _In_ DWORD dwOption, _In_opt_ LPVOID lpBuffer, _In_ DWORD dwBufferLength );
|
hInternet参数指定为要设置的Internet句柄。dwOption参数指定为要设置的Internet选项,可以是以下值之一(可以用于InternetSetOption和InternetQueryOption函数):
常量 |
含义 |
INTERNET_OPTION_BYPASS_EDITED_ENTRY |
设置或获取系统是否应该检查网络以获取较新的内容,并在发现较新的版本时覆盖已编辑的缓存条目 |
INTERNET_OPTION_CACHE_TIMESTAMPS |
从Internet缓存中获取INTERNET_CACHE_TIMESTAMPS结构,该结构包含LastModified和Expires时间 |
INTERNET_OPTION_CALLBACK |
设置或获取为Internet句柄定义的回调函数的地址 |
INTERNET_OPTION_CLIENT_CERT_CONTEXT |
lpBuffer参数必须是指向CERT_CONTEXT结构的指针,而不是指向CERT_CONTEXT指针的指针。如果应用程序收到ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED,它必须在重试请求之前调用InternetErrorDlg或使用InternetSetOption提供证书,然后调用CertDuplicateCertificateContext,这样传递的证书上下文可以由应用程序独立释放,仅用于InternetSetOption函数 |
INTERNET_OPTION_CODEPAGE |
默认情况下,Unicode URL的主机或授权部分根据IDN规范进行编码。当IDN被禁用时,在请求或连接句柄上设置该选项可以为URL的主机部分指定代码页编码方案,调用InternetSetOption时的lpBuffer参数包含所需的DBCS代码页;如果在lpBuffer参数中没有指定代码页,WinINet使用默认的系统代码页(CP_ACP)。注意:如果未禁用IDN,则忽略该选项 |
INTERNET_OPTION_CODEPAGE_PATH |
默认情况下,URL的路径部分采用UTF8编码。WinINet对高位字符执行转义(%)编码,在请求或连接句柄上设置该选项会禁用UTF8编码并设置为指定的代码页。调用InternetSetOption时的lpBuffer参数指定为URL路径所需的DBCS代码页;如果在lpBuffer参数中没有指定代码页,WinINet使用默认的CP_UTF8 |
INTERNET_OPTION_CODEPAGE_EXTRA |
默认情况下,URL的路径部分采用默认系统代码页(CP_ACP),不会对额外部分执行转义(%)转换,在请求或连接句柄上设置该选项会禁用CP_ACP编码。调用InternetSetOption时的lpBuffer参数指定为URL额外部分所需的DBCS代码页;如果在lpBuffer参数中没有指定代码页,WinINet使用默认的系统代码页(CP_ACP) |
INTERNET_OPTION_COMPRESSED_CONTENT_LENGTH |
对于WinINet解压缩服务器提供的Content-Encoding请求,将服务器报告的响应正文的Content-Length作为ULONGLONG型,适用于Windows 10 1507及更高版本 |
INTERNET_OPTION_CONNECT_RETRIES |
设置或获取WinINet尝试解析和连接到主机的次数,默认值为5 |
INTERNET_OPTION_CONNECT_TIMEOUT |
设置或获取Internet连接请求的超时值(以毫秒为单位),设置为0xFFFFFFFF表示禁用计时器 |
INTERNET_OPTION_CONNECTED_STATE |
设置或获取连接状态 |
INTERNET_OPTION_CONTEXT_VALUE |
设置或获取与Internet句柄关联的上下文值地址的DWORD_PTR |
INTERNET_OPTION_DATA_RECEIVE_TIMEOUT |
设置或获取对FTP事务数据通道请求的响应的超时值(以毫秒为单位),仅用于FTP |
INTERNET_OPTION_DATA_SEND_TIMEOUT |
设置或获取对FTP事务数据通道请求的超时值(以毫秒为单位),仅用于FTP |
INTERNET_OPTION_DATAFILE_NAME |
获取下载实体的文件名称 |
INTERNET_OPTION_DATAFILE_EXT |
设置下载实体的文件扩展名 |
INTERNET_OPTION_DIAGNOSTIC_SOCKET_INFO |
获取HTTP请求的数据的INTERNET_DIAGNOSTIC_SOCKET_INFO结构,Windows 7不再支持该选项 |
INTERNET_OPTION_DIGEST_AUTH_UNLOAD |
注销摘要式身份验证SSPI包,清除为该进程创建的所有凭据,仅用于InternetSetOption函数 |
INTERNET_OPTION_ENABLE_HTTP_PROTOCOL |
设置HTTP版本,可用的值仅有HTTP_PROTOCOL_FLAG_HTTP2(0x2),仅用于Windows10 1507及更高版本 |
INTERNET_OPTION_ENABLE_REDIRECT_CACHE_READ |
设置是否从WinINet缓存中为给定请求返回重定向,默认值为FALSE,仅用于Windows 8及更高版本 |
INTERNET_OPTION_ENCODE_EXTRA |
设置或获取查询字符串中的非ASCII字符是否应进行%编码,默认值为FALSE,仅用于Windows 8.1及更高版本 |
INTERNET_OPTION_END_BROWSER_SESSION |
从硬盘驱动器上的密码缓存中刷新未使用的条目,还重置同步模式为每个会话一次时使用的缓存时间,仅用于InternetSetOption函数 |
INTERNET_OPTION_ERROR_MASK |
设置可以由客户端应用程序处理的错误掩码,可以是以下值的组合:INTERNET_ERROR_MASK_COMBINED_SEC_CERT表示所有证书错误都将使用相同的错误代码ERROR_INTERNET_SEC_CERT_ERRORS;INTERNET_ERROR_MASK_INSERT_CDROM表示客户端应用程序可以处理错误代码ERROR_INTERNET_INSERT_CDROM;INTERNET_ERROR_MASK_LOGIN_FAILURE_DISPLAY_ENTITY_BODY表示客户端应用程序可以处理错误代码ERROR_INTERNET_LOGIN_FAILURE_DISPLAY_ENTITY_BODY |
INTERNET_OPTION_ENTERPRISE_CONTEXT |
设置用于请求的企业ID,仅用于Windows 10 1507及更高版本 |
INTERNET_OPTION_EXTENDED_ERROR |
获取Winsock错误代码,映射到线程上下文中最后返回的ERROR_INTERNET_*错误消息 |
INTERNET_OPTION_FROM_CACHE_TIMEOUT |
设置或获取系统在检查缓存中是否有资源副本之前等待网络请求响应的时间 |
INTERNET_OPTION_HANDLE_TYPE |
获取Internet句柄类型,例如INTERNET_HANDLE_TYPE_CONNECT_FTP、INTERNET_HANDLE_TYPE_CONNECT_HTTP等 |
INTERNET_OPTION_HSTS |
设置或获取WinINet是否应遵循来自服务器的HTTP严格传输安全(HSTS)指令,如果启用,对具有WinINet缓存的HSTS策略的域的https://方案请求将被重定向到匹配的https://URL。默认值为FALSE。仅用于Windows 8.1及更高版本 |
INTERNET_OPTION_HTTP_DECODING |
使WinINet能够对gzip和deflate编码方案执行解码 |
INTERNET_OPTION_HTTP_PROTOCOL_USED |
获取HTTP版本,可用的值仅有HTTP_PROTOCOL_FLAG_HTTP2(0x2),仅用于Windows 10 1507及更高版本 |
INTERNET_OPTION_HTTP_VERSION |
设置或获取HTTP版本的HTTP_VERSION_INFO结构 |
INTERNET_OPTION_IDN |
设置请求或连接句柄以启用或禁用IDN |
INTERNET_OPTION_IGNORE_OFFLINE |
设置或获取是否应忽略请求句柄的全局脱机标志 |
INTERNET_OPTION_MAX_CONNS_PER_1_0_SERVER |
设置或获取HTTP/1.0服务器允许的最大连接数 |
INTERNET_OPTION_MAX_CONNS_PER_PROXY |
设置或获取每个CERN代理允许的最大连接数 |
INTERNET_OPTION_MAX_CONNS_PER_SERVER |
设置或获取每个服务器允许的最大连接数 |
INTERNET_OPTION_OPT_IN_WEAK_SIGNATURE |
将弱签名(例如SHA-1)视为不安全签名,这将指示WinINet使用CERT_CHAIN_OPT_IN_WEAK_SIGNATURE参数调用CertGetCertificateChain |
INTERNET_OPTION_PARENT_HANDLE |
获取指定Internet句柄的父句柄 |
INTERNET_OPTION_PASSWORD |
设置或获取与InternetConnect函数返回的Internet句柄关联的密码 |
INTERNET_OPTION_PER_CONNECTION_OPTION |
设置或获取INTERNET_PER_CONN_OPTION_LIST结构,该结构指定特定连接的选项列表 |
INTERNET_OPTION_PROXY |
设置或获取INTERNET_PROXY_INFO结构,该结构包含现有InternetOpen句柄的代理数据 |
INTERNET_OPTION_PROXY_PASSWORD |
设置或获取用于访问代理的密码 |
INTERNET_OPTION_PROXY_SETTINGS_CHANGED |
通知当前WinINet实例代理设置已更改,并且必须使用新设置进行更新。要通知所有可用的WinINet实例,需要将InternetSetOption的lpBuffer参数设置为NULL并将dwBufferLength设置为0 |
INTERNET_OPTION_PROXY_USERNAME |
设置或获取用于访问代理的用户名 |
INTERNET_OPTION_READ_BUFFER_SIZE |
设置或获取读缓冲区的大小,仅用于FTP |
INTERNET_OPTION_CONTROL_RECEIVE_TIMEOUT或 INTERNET_OPTION_RECEIVE_TIMEOUT |
设置或获取对请求响应的超时值(以毫秒为单位) |
INTERNET_OPTION_REFRESH |
从注册表中重新读取代理数据 |
INTERNET_OPTION_REQUEST_FLAGS |
获取正在进行下载的状态的特殊状态标志,可以是以下值之一:INTERNET_REQFLAG_CACHE_WRITE_DISABLED表示无法缓存Internet请求(例如,HTTPS请求);INTERNET_REQFLAG_FROM_CACHE表示响应来自缓存;INTERNET_REQFLAG_NET_TIMEOUT表示请求超时;INTERNET_REQFLAG_NO_HEADERS表示原始响应不包含标头;INTERNET_REQFLAG_VIA_PROXY表示请求是通过代理发出的 |
INTERNET_OPTION_REQUEST_PRIORITY |
设置或获取竞争连接的请求的优先级 |
INTERNET_OPTION_RESET_URLCACHE_SESSION |
为进程启动一个新的缓存会话 |
INTERNET_OPTION_SECONDARY_CACHE_KEY |
设置或获取二级缓存键的字符串值,仅供内部使用 |
INTERNET_OPTION_SECURITY_CERTIFICATE |
获取格式化字符串形式的SSL/PCT服务器证书 |
INTERNET_OPTION_SECURITY_CERTIFICATE_STRUCT |
获取INTERNET_CERTIFICATE_INFO结构形式的SSL/PCT服务器证书 |
INTERNET_OPTION_SECURITY_FLAGS |
设置或获取Internet句柄的安全标志,可以是以下值的组合:SECURITY_FLAG_FORTEZZA表示Fortezza已用于为指定的连接提供保密、身份验证和/或完整性;SECURITY_FLAG_IGNORE_CERT_CN_INVALID表示忽略ERROR_INTERNET_SEC_CERT_CN_INVALID错误消息;SECURITY_FLAG_IGNORE_CERT_DATE_INVALID表示忽略ERROR_INTERNET_SEC_CERT_DATE_INVALID错误消息;SECURITY_FLAG_IGNORE_REDIRECT_TO_HTTP表示忽略ERROR_INTERNET_HTTPS_TO_HTTP_ON_REDIR错误消息;SECURITY_FLAG_IGNORE_REDIRECT_TO_HTTPS表示忽略ERROR_INTERNET_HTTP_TO_HTTPS_ON_REDIR错误消息;SECURITY_FLAG_IGNORE_REVOCATION表示忽略证书吊销问题;SECURITY_FLAG_IGNORE_UNKNOWN_CA表示忽略未知的证书颁发机构问题;SECURITY_FLAG_IGNORE_WEAK_SIGNATURE表示忽略弱证书签名问题;SECURITY_FLAG_IGNORE_WRONG_USAGE表示忽略错误的使用问题;SECURITY_FLAG_SECURE表示使用安全传输;SECURITY_FLAG_STRENGTH_MEDIUM表示使用中等(56位)加密;SECURITY_FLAG_STRENGTH_STRONG表示使用强(128位)加密;SECURITY_FLAG_NORMALBITNESS或SECURITY_FLAG_STRENGTH_WEAK表示使用弱(40位)加密;SECURITY_FLAG_UNKNOWNBIT表示用于加密的位大小是未知的 |
INTERNET_OPTION_SECURITY_KEY_BITNESS |
获取加密密钥的位大小,数值越大,使用的加密强度越大 |
INTERNET_OPTION_CONTROL_SEND_TIMEOUT或 INTERNET_OPTION_SEND_TIMEOUT |
设置或获取发送请求的超时值 |
INTERNET_OPTION_SERVER_CERT_CHAIN_CONTEXT |
以复制的PCCERT_CHAIN_CONTEXT的形式获取服务器的证书链上下文,可以将此复制的上下文传递给任何采用PCCERT_CHAIN_CONTEXT的加密API函数 |
INTERNET_OPTION_SETTINGS_CHANGED |
通知系统注册表设置已更改 |
INTERNET_OPTION_SUPPRESS_SERVER_AUTH |
设置HTTP请求对象,使其不会登录到源服务器,但会自动登录到HTTP代理服务器,该选项不同于请求标志INTERNET_FLAG_NO_AUTH,后者阻止对代理服务器和源服务器进行身份验证。设置该模式将禁止在与源服务器通信时使用任何凭据材料(先前提供的用户名/密码或客户端SSL证书);但是,如果请求必须通过身份验证代理传输,WinINet仍将根据用户的Intranet区域设置对HTTP代理执行自动身份验证,默认的Intranet区域设置是允许使用用户的默认凭据自动登录。为了确保抑制所有识别信息,调用者应该将INTERNET_OPTION_SUPPRESS_SERVER_AUTHINTERNET_FLAG_NO_COOKIES请求标志结合起来,该选项只能在请求对象被发送之前设置,在请求发送后尝试设置该选项将返回ERROR_INTERNET_INCORRECT_HANDLE_STATE |
INTERNET_OPTION_SUPPRESS_BEHAVIOR |
设置在进程范围内抑制行为的通用选项,可以是以下值的组合:INTERNET_SUPPRESS_RESET_ALL表示禁用所有抑制,重新启用默认和配置的行为,该选项相当于分别设置INTERNET_SUPPRESS_COOKIE_POLICY_RESET和INTERNET_SUPPRESS_COOKIE_PERSIST_RESET;INTERNET_SUPPRESS_COOKIE_POLICY表示忽略任何已配置的cookie策略并允许设置cookie;INTERNET_SUPPRESS_COOKIE_POLICY_RESET表示禁用INTERNET_SUPPRESS_COOKIE_POLICY抑制,允许根据配置的cookie策略评估cookie;INTERNET_SUPPRESS_COOKIE_PERSIST表示抑制cookie的持久性,即使服务器已将它们指定为持久性;INTERNET_SUPPRESS_COOKIE_PERSIST_RESET表示禁用INTERNET_SUPPRESS_COOKIE_PERSIST抑制,重新启用cookie的持久性,但任何先前被抑制的cookie都不会持久化 |
INTERNET_OPTION_URL |
获取下载资源的完整URL字符串 |
INTERNET_OPTION_USER_AGENT |
设置或获取由InternetOpen函数提供并在后续HttpSendRequest函数中使用的Internet句柄上的用户代理字符串 |
INTERNET_OPTION_USERNAME |
设置或获取与InternetConnect函数返回的Internet句柄关联的用户名字符串 |
INTERNET_OPTION_VERSION |
获取包含Wininet.dll版本号的INTERNET_VERSION_INFO结构 |
INTERNET_OPTION_WRITE_BUFFER_SIZE |
设置或获取写缓冲区大小(以字节为单位),仅用于FTP |
lpBuffer参数指定为包含Internet选项设置的缓冲区的指针。dwBufferLength参数用于指定缓冲区的大小。如果lpBuffer参数指定的是一个字符串,则大小以字符为单位;如果lpBuffer参数指定的是字符串以外的任何数据,则大小以字节为单位。
例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
| #include <windows.h> #include <WinInet.h> #include <tchar.h> #include <strsafe.h> #include "resource.h" #pragma comment(lib, "Wininet.lib") #pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc, NULL); return 0; }; INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { static HINTERNET hInternet = NULL; HINTERNET hConnect = NULL; HINTERNET hRequest = NULL; TCHAR szServerName[MAX_PATH] = { 0 }; LPCTSTR arrszAcceptTypes[] = { TEXT("text/*"), TEXT("*/*"), NULL }; DWORD dwNumberOfBytesAvailable = 0; LPVOID lpBuf = NULL; DWORD dwNumberOfBytesRead = 0; BOOL bRet = FALSE; TCHAR szFileLocal[MAX_PATH] = { 0 }; HANDLE hFileLocal = INVALID_HANDLE_VALUE; LPVOID lpBuffer = NULL; DWORD dwBufferLength = 0; LPCSTR lpStrOptional = "username=Admin&password=123456&remember=yes&login=%E7%99%BB%E5%BD%95"; HINTERNET hFile = NULL; switch (uMsg) { case WM_INITDIALOG: { SetDlgItemText(hwndDlg, IDC_EDIT_HOST, TEXT("www.httptest.com")); hInternet = InternetOpen(TEXT("Mozilla/5.0"), INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0); return TRUE; }; case WM_COMMAND: { switch (LOWORD(wParam)) { case IDC_BTN_GET: { if (GetDlgItemText(hwndDlg, IDC_EDIT_HOST, szServerName, _countof(szServerName)) > 0) hConnect = InternetConnect(hInternet, szServerName, INTERNET_DEFAULT_HTTP_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0); else { MessageBox(hwndDlg, TEXT("请输入主机名或IP地址"), TEXT("错误提示"), MB_OK); return TRUE; }; hRequest = HttpOpenRequest(hConnect, TEXT("GET"), TEXT("index.html"), NULL, TEXT("http://www.httptest.com/"), arrszAcceptTypes, INTERNET_FLAG_KEEP_CONNECTION, 0); HttpAddRequestHeaders(hRequest, TEXT("Accept-Encoding:gzip, deflate\r\nAccept-Language:zh-CN\r\n\r\n"), -1, HTTP_ADDREQ_FLAG_ADD | HTTP_ADDREQ_FLAG_REPLACE | HTTP_ADDREQ_FLAG_COALESCE); HttpSendRequest(hRequest, NULL, 0, NULL, 0); GetModuleFileName(NULL, szFileLocal, _countof(szFileLocal)); StringCchCopy(_tcsrchr(szFileLocal, TEXT('\\')) + 1, _tcslen(TEXT("index.html")) + 1, TEXT("index.html")); hFileLocal = CreateFile(szFileLocal, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); do { dwNumberOfBytesRead = 0; bRet = FALSE; InternetQueryDataAvailable(hRequest, &dwNumberOfBytesAvailable, 0, 0); if (dwNumberOfBytesAvailable > 0) { lpBuf = new BYTE[dwNumberOfBytesAvailable]; bRet = InternetReadFile(hRequest, lpBuf, dwNumberOfBytesAvailable, &dwNumberOfBytesRead); WriteFile(hFileLocal, lpBuf, dwNumberOfBytesRead, NULL, NULL); delete[]lpBuf; }; } while (bRet && dwNumberOfBytesRead > 0); MessageBox(hwndDlg, TEXT("下载index.html成功"), TEXT("操作成功"), MB_OK); CloseHandle(hFileLocal); InternetCloseHandle(hRequest); InternetCloseHandle(hConnect); break; }; case IDC_BTN_POST: { if (GetDlgItemText(hwndDlg, IDC_EDIT_HOST, szServerName, _countof(szServerName)) > 0) hConnect = InternetConnect(hInternet, szServerName, INTERNET_DEFAULT_HTTP_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0); else { MessageBox(hwndDlg, TEXT("请输入主机名或IP地址"), TEXT("错误提示"), MB_OK); return TRUE; }; hRequest = HttpOpenRequest(hConnect, TEXT("POST"), TEXT("login.php"), NULL, TEXT("http://www.httptest.com/login.html"), arrszAcceptTypes, INTERNET_FLAG_KEEP_CONNECTION, 0); HttpAddRequestHeaders(hRequest, TEXT("Accept-Encoding:gzip, deflate\r\nAccept-Language:zh-CN\r\nContent-Type:application/x-www-form-urlencoded\r\nContent-Length:68\r\n\r\n"), -1, HTTP_ADDREQ_FLAG_ADD | HTTP_ADDREQ_FLAG_REPLACE | HTTP_ADDREQ_FLAG_COALESCE); HttpSendRequest(hRequest, NULL, 0, (LPVOID)lpStrOptional, strlen(lpStrOptional)); lpBuffer = NULL; dwBufferLength = 0; HttpQueryInfo(hRequest, HTTP_QUERY_RAW_HEADERS_CRLF | HTTP_QUERY_FLAG_REQUEST_HEADERS, NULL, &dwBufferLength, NULL); lpBuffer = new BYTE[dwBufferLength + 2]; ZeroMemory(lpBuffer, dwBufferLength + 2); HttpQueryInfo(hRequest, HTTP_QUERY_RAW_HEADERS_CRLF | HTTP_QUERY_FLAG_REQUEST_HEADERS, lpBuffer, &dwBufferLength, NULL); MessageBox(hwndDlg, (LPCTSTR)lpBuffer, TEXT("请求标头"), MB_OK); delete[]lpBuffer; lpBuffer = NULL; dwBufferLength = 0; HttpQueryInfo(hRequest, HTTP_QUERY_RAW_HEADERS_CRLF, NULL, &dwBufferLength, NULL); lpBuffer = new BYTE[dwBufferLength + 2]; ZeroMemory(lpBuffer, dwBufferLength + 2); HttpQueryInfo(hRequest, HTTP_QUERY_RAW_HEADERS_CRLF, lpBuffer, &dwBufferLength, NULL); MessageBox(hwndDlg, (LPCTSTR)lpBuffer, TEXT("响应标头"), MB_OK); delete[]lpBuffer; InternetCloseHandle(hRequest); InternetCloseHandle(hConnect); hFile = InternetOpenUrl(hInternet, TEXT("http://www.httptest.com/index.php"), TEXT("Accept-Encoding:gzip, deflate\r\nAccept-Language:zh-CN\r\n\r\n"), _tcslen(TEXT("Accept-Encoding:gzip, deflate\r\nAccept-Language:zh-CN\r\n\r\n")), INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP | INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS | INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_NO_AUTH | INTERNET_FLAG_RESYNCHRONIZE, 0); GetModuleFileName(NULL, szFileLocal, _countof(szFileLocal)); StringCchCopy(_tcsrchr(szFileLocal, TEXT('\\')) + 1, _tcslen(TEXT("index.php")) + 1, TEXT("index.php")); hFileLocal = CreateFile(szFileLocal, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); do { dwNumberOfBytesRead = 0; bRet = FALSE; InternetQueryDataAvailable(hFile, &dwNumberOfBytesAvailable, 0, 0); if (dwNumberOfBytesAvailable > 0) { lpBuf = new BYTE[dwNumberOfBytesAvailable]; bRet = InternetReadFile(hFile, lpBuf, dwNumberOfBytesAvailable, &dwNumberOfBytesRead); WriteFile(hFileLocal, lpBuf, dwNumberOfBytesRead, NULL, NULL); delete[]lpBuf; }; } while (bRet && dwNumberOfBytesRead > 0); MessageBox(hwndDlg, TEXT("下载index.php成功"), TEXT("操作成功"), MB_OK); CloseHandle(hFileLocal); InternetCloseHandle(hFile); break; }; }; return TRUE; }; case WM_CLOSE: { InternetCloseHandle(hInternet); EndDialog(hwndDlg, 0); return TRUE; }; }; return FALSE; };
|