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模式实现,这个实现起来挺简单的

  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] = '';  
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 }  
View Code

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 使用举例

  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
", 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
", 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
", 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
", 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
", WSAGetLastError());
 80         closesocket(ListenSocket);
 81         WSACleanup();
 82         return 1;
 83     }
 84     wprintf(L"Listening...
");
 85 
 86     //-----------------------------------------
 87     // 接受连接
 88     AcceptSocket = accept(ListenSocket, NULL, NULL);
 89     if (AcceptSocket == INVALID_SOCKET) {
 90         wprintf(L"accept failed with error = %d
", WSAGetLastError());
 91         closesocket(ListenSocket);
 92         WSACleanup();
 93         return 1;
 94     }
 95     wprintf(L"Client Accepted...
");
 96 
 97     //-----------------------------------------
 98     // 创建事件,并初始化Overlapped结构.
 99     EventArray[EventTotal] = WSACreateEvent();
100     if (EventArray[EventTotal] == WSA_INVALID_EVENT) {
101         wprintf(L"WSACreateEvent failed with error = %d
", 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
", 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
", 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
", WSAGetLastError());
145         }
146         //-----------------------------------------
147         // 如果连接关闭,就关闭Socket
148         if (BytesTransferred == 0) {
149             wprintf(L"Closing accept Socket %d
", 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
", 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 }
View Code

WSAEnumNetworkEvent 使用举例

 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
", 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
", 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
", WSAGetLastError() );
48         return 1;
49     }
50 
51     // 创建事件
52     NewEvent = WSACreateEvent();
53     if (NewEvent == NULL)
54     {
55         wprintf(L"WSACreateEvent failed with error: %d
", 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
", 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
", 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 }
View Code

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至关重要,需要理解

原文地址:https://www.cnblogs.com/songliquan/p/3445767.html