Windows网络编程笔记6 --- WinSock I/O 控制方法
Windows提供了两种方式“套接字模式”和“套接字I/O模型”,可对一个套接字上的I/O行为加以控制。套接字模式用于决定在随一个套接字调用时,那些 Winsock函数的行为。其中的模型包括括select(选择)、WSAAsyncSelect(异步选择)、WSAEventSelect(事件选择)、OverlappedI/O(重叠式I/O)以及Completionport(完成端口)等等。
所有Windows平台都支持套接字以锁定或非锁定方式工作。在锁定模式下,在I/O操作完成前,执行操作的Winsock函数(比如send和recv)会一直等候下去,不会立即返回程序(将控制权交还给程序)。而在非锁定模式下,Winsock函数无论如何都会立即返回。
锁定模式使用线程同步机制完成数据的I/O,扩展性不好,尤其是有多个套接字时。
非锁定模式的使用需要函数 ioctlsocket(),使用如下:
SOCKET s; unsigned long ub =1; int nRet; s = socketAF_INET,SOCK_STREAM,0); nRet = ioctlsocket(s,FIOBIO,(unsigned long *)&ub);//设置非锁定模式 if(nRet == SOCKET_ERROR) { //进入非锁定模式失败 }
套接字I/O模型
1、select模型
//函数原型 int select( int nfds, //与早期程序兼容,可忽略 fd_set FAR * readfds,// 可读性 fd_set FAR * writefds,// 可写性 fd_set FAR * exceptfds,// 例外数据 const struct timeval FAR * timeout//超时时间 );
参数readfds 表示以下几种情况
有数据可读入、连接已经关闭(重设或者终止)、如果已经listen,并且正在建立连接,那么accept函数会返回成功。
参数writefds表示以下几种情况
有数据可发出、如果已完成对一个非锁定连接调用的处理,连接就会成功。
参数exceptfds表示如下
如果已完成对一个非锁定连接调用的处理,连接尝试就会失败。有带外(Out-of-band,OOB)数据可供读取。
其中fd_set结构如下:
typedef struct fd_set { u_int fd_count; /* how many are SET? */ SOCKET fd_array[FD_SETSIZE]; /* an array of SOCKETs */ } fd_set;
超时时间
struct timeval { long tv_sec; /* seconds */秒 long tv_usec; /* and microseconds */毫秒 };
用select对套接字进行监视之前,在自己的应用程序中,必须将套接字句柄分配给一个集合,设置好一个或全部读、写以及例外fd_set结构。将一个套接字分配给任何一个集合后,再来调用select,便可知道一个套接字上是否正在发生上述的I/O活动。
下面是对fd_set进行操作的一些宏
FD_CLR(s,*set):从set中删除套接字s。
FD_ISSET(s,*set):检查s是否set集合的一名成员;如答案是肯定的是,则返回TRUE。
FD_SET(s,*set):将套接字s加入集合set。
FD_ZERO(*set):将set初始化成空集合。
用select操作一个或多个套接字句柄的全过程:
1)使用FD_ZERO宏,初始化自己感兴趣的每一个fd_set。
2)使用FD_SET宏,将套接字句柄分配给自己感兴趣的每个fd_set。
3)调用select函数,然后等待在指定的fd_set集合中,I/O活动设置好一个或多个套接字句柄。select完成后,会返回在所有fd_set集合中设置的套接字句柄总数,并对每个集合进行相应的更新。
4)根据select的返回值,我们的应用程序便可判断出哪些套接字存在着尚未完成(待决)的I/O操作—具体的方法是使用FD_ISSET宏,对每个fd_set集合进行检查。
5)知道了每个集合中“待决”的I/O操作之后,对I/O进行处理,然后返回步骤1),继续进行select处理。
简单过程如下
SOCKET s; fd_set fdread; int ret; //创建 //bind() //accept() //开始 while (1) { FD_ZERO(&fdread);//初始化 FD_SET(s,&fdread);//添加 if ((ret = select(0,&fdread,NULL,NULL,NULL)) == SOCKET_ERROR) { //添加失败 } if (ret > 0) { if (FD_ISSET(s,&fdread)) { //已经是集合的一部分,正在读取数据 } } //其他操作 }
2、WSAAsyncSelect模型
WSAAsyncSelect 的使用必须要在窗口应用程序中使用,在回调函数中实现处理。
int WSAAsyncSelect( _In_ SOCKET s,//关心的socket _In_ HWND hWnd,//窗口句柄 _In_ unsigned int wMsg,//消息 _In_ long lEvent//事件类型 );
事件类型很多,如图
WSAAsyncSelect模式实现,这个实现起来挺简单的
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 // WSAAsyncSelect模式实现,这个实现起来挺简单的。 2 #include <winsock2.h> 3 #include <tchar.h> 4 #define PORT 7890 5 #define MSGSIZE 1024 6 #define WM_SOCKET WM_USER+1 7 #pragma comment(lib, "ws2_32.lib") 8 9 10 LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM); 11 int WINAPI WinMain( HINSTANCE hInstance, 12 HINSTANCE hPrevInstance, 13 LPSTR lpCmdLine, 14 int nShowCmd 15 ) 16 { 17 18 19 static TCHAR szAppName[] = TEXT ("WSAAsyncSelect Test") ; 20 HWND hwnd ; 21 MSG msg ; 22 WNDCLASS wndclass ; 23 24 wndclass.style = CS_HREDRAW | CS_VREDRAW ; 25 wndclass.lpfnWndProc = WndProc; 26 wndclass.cbClsExtra = 0 ; 27 wndclass.cbWndExtra = 0 ; 28 wndclass.hInstance = hInstance ; 29 wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; 30 wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; 31 wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; 32 wndclass.lpszMenuName = NULL ; 33 wndclass.lpszClassName = szAppName ; 34 35 if (!RegisterClass (&wndclass)) 36 { 37 MessageBox (NULL, TEXT ("Program requires Windows NT!"), 38 szAppName, MB_ICONERROR) ; 39 return 0 ; 40 } 41 42 hwnd = CreateWindow (szAppName, TEXT ("WSAAsyncSelect"), 43 WS_OVERLAPPEDWINDOW, 44 CW_USEDEFAULT, CW_USEDEFAULT, 45 CW_USEDEFAULT, CW_USEDEFAULT, 46 NULL, NULL, hInstance, NULL) ; 47 48 ShowWindow (hwnd, nShowCmd) ; 49 UpdateWindow (hwnd) ; 50 51 while (GetMessage (&msg, NULL, 0, 0)) 52 { 53 TranslateMessage (&msg) ; 54 DispatchMessage (&msg) ; 55 } 56 return msg.wParam ; 57 } 58 59 60 61 LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) 62 { 63 WSADATA wsd; 64 static SOCKET sListen; 65 SOCKET sClient; 66 SOCKADDR_IN local, client; 67 int ret, iAddrSize = sizeof(client); 68 char szMessage[MSGSIZE] ; 69 switch (message) 70 { 71 case WM_CREATE: 72 // 初始化 73 WSAStartup(0x0202, &wsd); 74 75 // 创建socket 76 sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 77 78 // 绑定 79 local.sin_addr.S_un.S_addr = inet_addr("192.168.0.87"); 80 local.sin_family = AF_INET; 81 local.sin_port = htons(PORT); 82 bind(sListen, (struct sockaddr *)&local, sizeof(local)); 83 84 // 监听 85 listen(sListen, 3); 86 // 设置WSAAsyncSelect模式 87 WSAAsyncSelect(sListen, hwnd, WM_SOCKET, FD_ACCEPT); 88 return 0; 89 case WM_DESTROY: 90 closesocket(sListen); 91 WSACleanup(); 92 PostQuitMessage(0); 93 return 0; 94 95 case WM_SOCKET: 96 if (WSAGETSELECTERROR(lParam))//使用宏 WSAGETSELECTERROR 判断lParam高字节是否有错误 97 { 98 closesocket(wParam); 99 break; 100 } 101 102 switch (WSAGETSELECTEVENT(lParam))//使用宏WSAGETSELECTEVENT判断lParam低字节是什么操作 103 { 104 case FD_ACCEPT: 105 // 从客户端接收连接 106 sClient = accept(wParam, (struct sockaddr *)&client, &iAddrSize); 107 108 // 设置事件监听 109 WSAAsyncSelect(sClient, hwnd, WM_SOCKET, FD_READ | FD_WRITE | FD_CLOSE); 110 break; 111 case FD_READ: //读 112 ret = recv(wParam, szMessage, MSGSIZE, 0); 113 if (ret == 0 || ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET) 114 { 115 closesocket(wParam); 116 } 117 else 118 { 119 szMessage[ret] = '\0'; 120 MessageBox(hwnd,"接收成功",NULL,MB_OK); 121 send(wParam, szMessage, strlen(szMessage), 0); 122 } 123 break; 124 case FD_WRITE://写 125 send(wParam, szMessage, strlen(szMessage), 0); 126 break; 127 case FD_CLOSE: //关闭 128 closesocket(wParam); 129 break; 130 } 131 return 0; 132 } 133 134 return DefWindowProc(hwnd, message, wParam, lParam); 135 }
3、WSAEventSelect 模型
这个模型和WSAAsyncSelect的区别是,它能将网络事件投递给一个事件对象句柄,而不是投递到一个窗口程序。
因此使用的时候首先就要生成一个事件对象,函数如下
WSAEVENT WSACreateEvent(void);//创建一个事件对象句柄
//创建WSAEventSelect模型 int WSAEventSelect( _In_ SOCKET s,//关心的套接字 _In_ WSAEVENT hEventObject,//事件对象 _In_ long lNetworkEvents//感兴趣的网络事件类型,FD_READ,FD_WRITE一类的 );
为WSAEventSelect创建的事件拥有两种工作状态,以及两种工作模式。其中,两种工作状态分别是“已传信”(signaled)和“未传信”(nonsignaled)。工作模式则包括“人工重设”(manual reset)和“自动重设”(auto reset)。WSACreateEvent最开始在一种未传信的工作状态中,并用一种人工重设模式,来创建事件句柄。随着网络事件触发了与一个套接字关联在一起的事件对象,工作状态便会从“未传信”转变成“已传信”。由于事件对象是在一种人工重设模式中创建的,所以在完成了一个I/O请求的处理之后,我们的应用程序需要负责将工作状态从已传信更改为未传信。
几个函数
//重置使用WSAResetEvent函数。 BOOL WSAResetEvent( _In_ WSAEVENT hEvent ); //如果不再使用,就是放资源 BOOL WSACloseEvent( _In_ WSAEVENT hEvent ); //创建过之后要监听事件,这时使用WSAWaitForMultipleEvents函数等待 DWORD WSAWaitForMultipleEvents( DWORD cEvents,//事件对象数量 const WSAEVENT *lphEvents,//所有的套接字事件,最多64个 BOOL fWaitAll,//是否等待全部完成才返回,还是大于一个时就返回 DWORD dwTimeout,//超时返回 BOOL fAlertable//在这个模型中忽略,为false,在重叠I/O时使用 );
知道网络事件引起的套接字后,可以查询发生了什么类型的网络事件
//知道网络事件引起的套接字后,可以查询发生了什么类型的网络事件 int WSAEnumNetworkEvents( _In_ SOCKET s, _In_ WSAEVENT hEventObject,//需要重设的事件对象, _Out_ LPWSANETWORKEVENTS lpNetworkEvents//网络事件 ); //参数三结构 typedef struct _WSANETWORKEVENTS { long lNetworkEvents;//网络事件类型 int iErrorCode[FD_MAX_EVENTS];//错误代码 } WSANETWORKEVENTS, FAR * LPWSANETWORKEVENTS;
WSAWaitForMultipleEvents 使用举例
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 //WSAWaitForMultipleEvents 使用举例 2 #include <winsock2.h> 3 #include <Ws2tcpip.h> 4 #include <stdio.h> 5 6 #pragma comment(lib, "Ws2_32.lib") 7 #define DATA_BUFSIZE 4096 8 9 int main() 10 { 11 //----------------------------------------- 12 WSADATA wsaData = { 0 }; 13 int iResult = 0; 14 BOOL bResult = TRUE; 15 16 WSABUF DataBuf; 17 char buffer[DATA_BUFSIZE]; 18 19 DWORD EventTotal = 0; 20 DWORD RecvBytes = 0; 21 DWORD Flags = 0; 22 DWORD BytesTransferred = 0; 23 24 WSAEVENT EventArray[WSA_MAXIMUM_WAIT_EVENTS]; 25 WSAOVERLAPPED AcceptOverlapped; 26 SOCKET ListenSocket = INVALID_SOCKET; 27 SOCKET AcceptSocket = INVALID_SOCKET; 28 29 DWORD Index; 30 31 //----------------------------------------- 32 // 初始化 33 iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); 34 if (iResult != 0) { 35 wprintf(L"WSAStartup failed: %d\n", iResult); 36 return 1; 37 } 38 //----------------------------------------- 39 //创建监听套接字 40 ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 41 if (ListenSocket == INVALID_SOCKET) { 42 wprintf(L"socket failed with error = %d\n", WSAGetLastError()); 43 WSACleanup(); 44 return 1; 45 } 46 47 u_short port = 7890; 48 char *ip; 49 sockaddr_in service; 50 service.sin_family = AF_INET; 51 service.sin_port = htons(port); 52 hostent *thisHost; 53 54 thisHost = gethostbyname(""); 55 if (thisHost == NULL) { 56 wprintf(L"gethostbyname failed with error = %d\n", WSAGetLastError()); 57 closesocket(ListenSocket); 58 WSACleanup(); 59 return 1; 60 } 61 62 ip = inet_ntoa(*(struct in_addr *) *thisHost->h_addr_list); 63 64 service.sin_addr.s_addr = inet_addr(ip); 65 66 //----------------------------------------- 67 // 绑定 68 iResult = bind(ListenSocket, (SOCKADDR *) & service, sizeof (SOCKADDR)); 69 if (iResult != 0) { 70 wprintf(L"bind failed with error = %d\n", WSAGetLastError()); 71 closesocket(ListenSocket); 72 WSACleanup(); 73 return 1; 74 } 75 //----------------------------------------- 76 // 监听 77 iResult = listen(ListenSocket, 1); 78 if (iResult != 0) { 79 wprintf(L"listen failed with error = %d\n", WSAGetLastError()); 80 closesocket(ListenSocket); 81 WSACleanup(); 82 return 1; 83 } 84 wprintf(L"Listening...\n"); 85 86 //----------------------------------------- 87 // 接受连接 88 AcceptSocket = accept(ListenSocket, NULL, NULL); 89 if (AcceptSocket == INVALID_SOCKET) { 90 wprintf(L"accept failed with error = %d\n", WSAGetLastError()); 91 closesocket(ListenSocket); 92 WSACleanup(); 93 return 1; 94 } 95 wprintf(L"Client Accepted...\n"); 96 97 //----------------------------------------- 98 // 创建事件,并初始化Overlapped结构. 99 EventArray[EventTotal] = WSACreateEvent(); 100 if (EventArray[EventTotal] == WSA_INVALID_EVENT) { 101 wprintf(L"WSACreateEvent failed with error = %d\n", WSAGetLastError()); 102 closesocket(AcceptSocket); 103 closesocket(ListenSocket); 104 WSACleanup(); 105 return 1; 106 } 107 108 ZeroMemory(&AcceptOverlapped, sizeof (WSAOVERLAPPED)); 109 AcceptOverlapped.hEvent = EventArray[EventTotal]; 110 111 DataBuf.len = DATA_BUFSIZE; 112 DataBuf.buf = buffer; 113 114 EventTotal++; 115 116 //----------------------------------------- 117 // 接受数据 118 if (WSARecv(AcceptSocket, &DataBuf, 1, &RecvBytes, &Flags, &AcceptOverlapped, NULL) == 119 SOCKET_ERROR) { 120 iResult = WSAGetLastError(); 121 if (iResult != WSA_IO_PENDING) 122 wprintf(L"WSARecv failed with error = %d\n", iResult); 123 } 124 //----------------------------------------- 125 // 开始在Socket上操作 126 while (1) { 127 128 //----------------------------------------- 129 // 等待重叠I/O完成 130 Index = WSAWaitForMultipleEvents(EventTotal, EventArray, FALSE, WSA_INFINITE, FALSE); 131 132 //----------------------------------------- 133 // 重置事件 134 bResult = WSAResetEvent(EventArray[Index - WSA_WAIT_EVENT_0]); 135 if (bResult == FALSE) { 136 wprintf(L"WSAResetEvent failed with error = %d\n", WSAGetLastError()); 137 } 138 //----------------------------------------- 139 // 确定重叠I/O事件标志 140 bResult = 141 WSAGetOverlappedResult(AcceptSocket, &AcceptOverlapped, &BytesTransferred, FALSE, 142 &Flags); 143 if (bResult == FALSE) { 144 wprintf(L"WSAGetOverlappedResult failed with error = %d\n", WSAGetLastError()); 145 } 146 //----------------------------------------- 147 // 如果连接关闭,就关闭Socket 148 if (BytesTransferred == 0) { 149 wprintf(L"Closing accept Socket %d\n", AcceptSocket); 150 closesocket(ListenSocket); 151 closesocket(AcceptSocket); 152 WSACloseEvent(EventArray[Index - WSA_WAIT_EVENT_0]); 153 WSACleanup(); 154 return 1; 155 } 156 //----------------------------------------- 157 // 如果接收到数据就在数据发送到客户端 158 iResult = 159 WSASend(AcceptSocket, &DataBuf, 1, &RecvBytes, Flags, &AcceptOverlapped, NULL); 160 if (iResult != 0) { 161 wprintf(L"WSASend failed with error = %d\n", WSAGetLastError()); 162 } 163 //----------------------------------------- 164 //重置Overlapped结构 165 Flags = 0; 166 ZeroMemory(&AcceptOverlapped, sizeof (WSAOVERLAPPED)); 167 168 AcceptOverlapped.hEvent = EventArray[Index - WSA_WAIT_EVENT_0]; 169 170 //----------------------------------------- 171 // 重置缓冲区 172 DataBuf.len = DATA_BUFSIZE; 173 DataBuf.buf = buffer; 174 } 175 176 closesocket(ListenSocket); 177 closesocket(AcceptSocket); 178 WSACleanup(); 179 return 0; 180 }
WSAEnumNetworkEvent 使用举例
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 //WSAEnumNetworkEvent 使用举例 2 #include <windows.h> 3 #include <winsock2.h> 4 #include <Ws2tcpip.h> 5 #include <stdio.h> 6 #pragma comment(lib, "Ws2_32.lib") 7 int main() 8 { 9 //------------------------- 10 WSADATA wsaData; 11 int iResult; 12 13 SOCKET SocketArray[WSA_MAXIMUM_WAIT_EVENTS], ListenSocket; 14 WSAEVENT EventArray[WSA_MAXIMUM_WAIT_EVENTS]; 15 WSANETWORKEVENTS NetworkEvents; 16 sockaddr_in InetAddr; 17 DWORD EventTotal = 0; 18 DWORD Index; 19 DWORD i; 20 21 HANDLE NewEvent = NULL; 22 23 // 初始化 24 iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); 25 if (iResult != 0) 26 { 27 wprintf(L"WSAStartup failed with error: %d\n", iResult); 28 return 1; 29 } 30 31 //创建 32 ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 33 if (ListenSocket == INVALID_SOCKET) 34 { 35 wprintf(L"socket function failed with error: %d\n", WSAGetLastError() ); 36 return 1; 37 } 38 39 InetAddr.sin_family = AF_INET; 40 InetAddr.sin_addr.s_addr = htonl(INADDR_ANY); 41 InetAddr.sin_port = htons(7890); 42 43 //绑定 44 iResult = bind(ListenSocket, (SOCKADDR *) & InetAddr, sizeof (InetAddr)); 45 if (iResult != 0) 46 { 47 wprintf(L"bind failed with error: %d\n", WSAGetLastError() ); 48 return 1; 49 } 50 51 // 创建事件 52 NewEvent = WSACreateEvent(); 53 if (NewEvent == NULL) 54 { 55 wprintf(L"WSACreateEvent failed with error: %d\n", GetLastError() ); 56 return 1; 57 } 58 // 将事件类型绑定到套接字上 59 iResult = WSAEventSelect(ListenSocket, NewEvent, FD_ACCEPT | FD_CLOSE); 60 if (iResult != 0) 61 { 62 wprintf(L"WSAEventSelect failed with error: %d\n", WSAGetLastError() ); 63 return 1; 64 } 65 66 // 监听 67 iResult = listen(ListenSocket, 10); 68 if (iResult != 0) 69 { 70 wprintf(L"listen failed with error: %d\n", WSAGetLastError() ); 71 return 1; 72 } 73 // 添加事件 74 SocketArray[EventTotal] = ListenSocket; 75 EventArray[EventTotal] = NewEvent; 76 EventTotal++; 77 78 // 等待所有套接字上的事件发生 79 Index = WSAWaitForMultipleEvents(EventTotal, EventArray, FALSE, WSA_INFINITE, FALSE); 80 Index = Index - WSA_WAIT_EVENT_0; 81 82 //枚举事件类型,直到失败 83 for (i = Index; i < EventTotal; i++) 84 { 85 Index = WSAWaitForMultipleEvents(1, &EventArray[i], TRUE, 1000, FALSE); 86 if ((Index != WSA_WAIT_FAILED) && (Index != WSA_WAIT_TIMEOUT)) 87 { 88 WSAEnumNetworkEvents(SocketArray[i], EventArray[i], &NetworkEvents); 89 } 90 } 91 92 //... 93 return 0; 94 }
4、重叠I/O模式
重叠I./O模式只支持Winsock2 ,可以使用WSARecv和WSASend实现
关键字 WSA_FLAG_OVERLAPPED
函数WSASocket在创建时可以手动指定模式
SOCKET s=WSASocket(AF_INET,SOCK_STREAM,0,NULL,0,WSA_FLAG_OVERLAPPED);
而函数socket默认就是重叠I/O模式。
typedef struct _WSAOVERLAPPED { ULONG_PTR Internal; ULONG_PTR InternalHigh; union { struct { DWORD Offset; DWORD OffsetHigh; }; PVOID Pointer; }; HANDLE hEvent;// } WSAOVERLAPPED, *LPWSAOVERLAPPED;
前几个参数有系统维护,只有hEvent字段有点儿特殊,它允许应用程序将一个事件对象句柄同一个套接字关联起来。
//结构Overlapped BOOL WSAAPI WSAGetOverlappedResult( _In_ SOCKET s, _In_ LPWSAOVERLAPPED lpOverlapped, _Out_ LPDWORD lpcbTransfer,//一次收发的实际字节数 _In_ BOOL fWait,//在重叠I/O模式中无效 _Out_ LPDWORD lpdwFlags//接收结果标志 );
对重叠I/O操作进行管理过程
1)创建一个套接字,开始在指定的端口上监听连接请求。
2)接受一个进入的连接请求。
3)为接受的套接字新建一个WSAOVERLAPPED结构,并为该结构分配一个事件对象句柄。也将事件对象句柄分配给一个事件数组,以便稍后由WSAWaitForMultipleEvents函数使用。
4)在套接字上投递一个异步WSARecv请求,指定参数为WSAOVERLAPPED结构。注意函数通常会以失败告终,返回SOCKET_ERROR错误状态WSA_IO_PENDING(I/O操作尚未完成)。
5)使用步骤3)的事件数组,调用WSAWaitForMultipleEvents函数,并等待与重叠调用关联在一起的事件进入“已传信”状态(换言之,等待那个事件的“触发”)。
6)WSAWaitForMultipleEvents函数完成后,针对事件数组,调用WSAResetEvent(重设事件)函数,从而重设事件对象,并对完成的重叠请求进行处理。
7)使用WSAGetOverlappedResult函数,判断重叠调用的返回状态是什么。
8)在套接字上投递另一个重叠WSARecv请求。
9)重复步骤5-8)。
5、完成端口模型
适合在管理成千上万的套接字时使用。
使用这种模型之前,首先要创建一个I/O完成端口对象,用它面向任意数量的套接字句柄,管理多个I/O请求。要做到这一点,需要调用CreateCompletionPort函数。函数有两种功能:
1. 用于创建一个完成端口对象。
2. 将一个句柄同完成端口关联到一起。
//创建一个完成端口对象 HANDLE CreateIoCompletionPort( HANDLE FileHandle, //关联的文件句柄,可能为套接字句柄 HANDLE ExistingCompletionPort,//已经存在的完成端口 LONG_PTR CompletionKey,// 传送给处理函数的参数 DWORD NumberOfConcurrentThreads//有多少个线程在访问这个消息队列 );
参数ExistingCompletionPort表示已经存在的完成端口。如果为NULL,则为新建一个IOCP(I/O完成端口)。
创建过程
HANDLE completionPort=CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,0,0);
//获取排队完成的状态 BOOL GetQueuedCompletionStatus( __in HANDLE CompletionPort,//完成端口 __out LPDWORD lpNumberOfBytesTransferred,//实际接受的数据 __out PULONG_PTR lpCompletionKey,//“单句柄数据” __out LPOVERLAPPED *lpOverlapped,//重叠I/O结构 __in DWORD dwMilliseconds//超时时间 );
一旦所有套接字句柄都已关闭,便需在完成端口上,终止所有工作者线程的运行,此时调用函数PostQueuedCompletionStatus向所有线程发送终止命令。
BOOL PostQueuedCompletionStatus( __in HANDLE CompletionPort,//完成端口句柄 __in DWORD dwNumberOfBytesTransferred,//实际接受数据 __in ULONG_PTR dwCompletionKey,//完成端口关键字“单句柄数据” __in_opt LPOVERLAPPED lpOverlapped//重叠I/O结构 );
使用过程
1、创建一个完成端口,第四个参数保持为0,指定在完成端口上,每个处理器一次只允许执行一个工作者线程
2、判断系统内到底安装了几个处理器
3、创建工作者线程,根据步骤2得到的处理器信息,在完成端口上,为已完成的I/O请求提供服务
4、准备好一个监听套接字
5、使用accept函数,接受进入的请求
6、创建一个数据结构,用于容纳“单句柄数据”,同时在结构中存入接受的套接字句柄。
7、调用CreateCompletionPort,将之accept返回的新套接字同完成端口关联到一起
8、开始在已接受的连接上进行I/O操作
9、重复5到8,直到服务器终止。
这些操作对套接字I/O至关重要,需要理解
本文来自博客园,作者:struggle_time,转载请注明原文链接:https://www.cnblogs.com/songliquan/p/3445767.html