Winsock I/O方法

分类:

  1、Winsock固有模型,阻塞模型和非阻塞模型;

    使用socket -> bind ->listen->accpet ->recv ->closesocket即为阻塞模型;正常情况下(函数返回正确),直到数据传输完程序才会结束。

    问题:会出现主程序假死的情况,数据未传输完,但是断网了。导致recv函数一直处于阻塞状态下。

    解决方法:

       使用开线程的方法,如:accpet得到一个新的socket链接,则新开一个读线程和计算线程。这样,既能解决假死的问题,还能满足多链接申请的需求。

    问题:开线程的方法存在缺点,即来一个链接申明要开两个线程,系统开销巨大。

    解决方法:

       使用非阻塞模型,ioctlsocket()完成由阻塞状态到非阻塞状态的转变。

  

  2、Winsock I/O模型,select模型,WSAAsyncSelect,WSAEventSelect,重叠模型,完成端口模型

    2.1 select模型

int select (
int maxfdp1,        //兼容老版本
fd_set *readset,   //读状态的集合,内容用数组实现
fd_set *writeset,   //写状态的集合,内容用数组实现
fd_set *exceptset,//其他状态的集合,内容用数组实现
const struct timeval * timeout //超时,为空时,select调用将无限期
);

    使用方法:

int sock;
int fd;
fd_set fds;  // 数组
sock=socket(...);
bind(...);
while(true)
{
FD_ZERO(&fds); //每次循环都要清空集合,否则不能检测描述符变化
FD_SET(sock,&fds); //添加描述符
switch(select(0,&fds,NULL,NULL,&timeout)) //select使用
{
  case -1: exit(-1);break; //select错误,退出程序
  case 0:break; //再次轮询
  default:
  if(FD_ISSET(sock,&fds)) //测试sock是否可读,即是否网络上有数据
  {
  recvfrom(sock,buffer,256,.....);//接受网络数据
  }// end if break;
}// end switch
}//end while

    优点:单线程完成了多个socket链接申请的即时响应需求。同时,也避免了开线程的系统系统剧增问题。

    缺点:无法做到链接申请实时通知,也就是有链接申请时,不能实时让接收端知道。只能是代码运行到FD_ISSET时才能判断知道。为此,微软引进了WSAAyncSelect模型。

  2.2 WSAAyncSelect模型

  实例:http://blog.csdn.net/jofranks/article/details/7917749

  优点:解决了实时响应链接申请的问题,但是会增加一个窗口过程。

  缺点:用一个单窗口程序来处理成千上万的套接字中的所有事件,是不现实的的。[摘自:《Windows网络编程(第二版)》]

  2.3 WSAEventSelect模型

  1 #include <iostream>
  2 #include <WinSock2.h>
  3 #pragma comment(lib,"ws2_32.lib")
  4 
  5 int main()
  6 {
  7     // 事件句柄和套接字句柄
  8     WSAEVENT eventArray[WSA_MAXIMUM_WAIT_EVENTS];
  9     SOCKET  sockArray[WSA_MAXIMUM_WAIT_EVENTS];
 10     int nEventTotal = 0;
 11 
 12     USHORT nPort = 5150; // 监听端口号
 13     WSAData wsaData;
 14     WSAStartup(MAKEWORD(2,2),&wsaData);
 15 
 16     // 创建监听套接字
 17     SOCKET sListen = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 
 18     sockaddr_in sin;
 19     sin.sin_family = AF_INET;
 20     sin.sin_port = htons(nPort);
 21     sin.sin_addr.s_addr = inet_addr("10.15.81.120");
 22     if(::bind(sListen, (sockaddr*)&sin, sizeof(sin)) == SOCKET_ERROR)
 23     {
 24         printf(" Failed bind() \n");
 25         return -1;
 26     }
 27     ::listen(sListen, 5);
 28         
 29     // 创建事件对象,并关联到新的套接字
 30     WSAEVENT event = ::WSACreateEvent();
 31     ::WSAEventSelect(sListen, event, FD_ACCEPT|FD_CLOSE);
 32     // 添加到列表中
 33     eventArray[nEventTotal] = event;
 34     sockArray[nEventTotal] = sListen; 
 35     nEventTotal++;
 36 
 37     char szText[512] = {0};
 38 
 39     // 处理网络事件
 40     while(TRUE)
 41     {
 42         std::cout << "waiting connecting..." << std::endl;
 43         // 在所有对象上等待
 44         int nIndex = ::WSAWaitForMultipleEvents(nEventTotal, eventArray, FALSE, WSA_INFINITE, FALSE);
 45         // 确定事件的状态
 46         nIndex = nIndex - WSA_WAIT_EVENT_0;
 47 
 48         // WSAWaitForMultipleEvents总是返回所有事件对象的最小值,为了确保所有的事件对象得到执行的机会,对 大于nIndex的事件对象进行轮询,使其得到执行的机会。
 49         for(int i=nIndex; i<nEventTotal; i++) 
 50         {
 51             
 52             nIndex = ::WSAWaitForMultipleEvents(1, &eventArray[i], TRUE, 0, FALSE);
 53             WSAResetEvent (eventArray[i]) ;
 54             if(nIndex == WSA_WAIT_FAILED || nIndex == WSA_WAIT_TIMEOUT)
 55             {
 56                 continue;
 57             }
 58             else
 59             {
 60                 // 获取到来的通知消息,WSAEnumNetworkEvents函数会自动重置受信事件
 61                 WSANETWORKEVENTS event;
 62                 ::WSAEnumNetworkEvents(sockArray[i], eventArray[i], &event);
 63                 if(event.lNetworkEvents & FD_ACCEPT)    // 处理FD_ACCEPT事件
 64                 {
 65                     if(event.iErrorCode[FD_ACCEPT_BIT] == 0)
 66                     {
 67                         if(nEventTotal > WSA_MAXIMUM_WAIT_EVENTS)
 68                         {
 69                            printf(" Too many connections! \n");
 70                            continue;
 71                         }
 72                         SOCKET sNew = ::accept(sockArray[i], NULL, NULL);
 73                         WSAEVENT event = ::WSACreateEvent();
 74                         ::WSAEventSelect(sNew, event, FD_READ|FD_CLOSE|FD_WRITE);
 75                         // 添加到列表中
 76                         eventArray[nEventTotal] = event;
 77                         sockArray[nEventTotal] = sNew; 
 78                         nEventTotal++;
 79                     }
 80                 }
 81                 else if(event.lNetworkEvents & FD_READ)   // 处理FD_READ事件
 82                 { 
 83                     if(event.iErrorCode[FD_READ_BIT] == 0)
 84                     {
 85                         memset(szText, 0x01, sizeof(szText));
 86                         int nRecv = ::recv(sockArray[i], szText, strlen(szText), 0);
 87                         if(nRecv > 0)    
 88                         {
 89                             szText[nRecv] = '\0';
 90                             printf("%s \n", szText);
 91                         }
 92 
 93                         char *szText = "服务器的数据" ;
 94                         int szLen = strlen(szText) ;
 95                         ::send(sockArray[i],szText,szLen,0) ;
 96                         shutdown(sockArray[i],SD_SEND);
 97                     }
 98                 }
 99                 else if(event.lNetworkEvents & FD_CLOSE)  // 处理FD_CLOSE事件
100                 {
101                     if(event.iErrorCode[FD_CLOSE_BIT] == 0)
102                     {
103                        ::closesocket(sockArray[i]);
104                        for(int j=i; j<nEventTotal-1; j++)
105                        {
106                            sockArray[j] = sockArray[j+1];
107                            sockArray[j] = sockArray[j+1]; 
108                        }
109                        nEventTotal--;
110                     }
111                }
112                else if(event.lNetworkEvents & FD_WRITE)  // FD_WRITE事件破难理解,下面将重点说明。
113                {
114                    if(event.iErrorCode[FD_READ_BIT] == 0)
115                     {
116                         char *szText = "服务器的数据" ;
117                         int szLen = strlen(szText) ;
118                         ::send(sockArray[i],szText,szLen,0) ;
119 
120                     }
121                }
122              }
123           }
124           WSAResetEvent (eventArray[nIndex]) ;
125        }
126        return 0;
127 }

  优点:使用简单,只需要使用WSACreateEvent、WSAEventSelect、WSAWaitForMultipleEvents、WSAEnumNetworkEvents、WSAResetEvent五个函数。不需要创建窗口过程。

  缺点:一次只能等待64事件,如果需处理的套接字较多时,只能使用多线程(线程池技术)来处理。为此,产生了重叠模型。

  2.4 重叠模型

   2.4.1 事件通知方式

    类似,WSAEventSelect模型,也是使用WSAWaitForMultipleEvents等待【注:一次最多只能处理64个事件】。

 1 #ifndef WIN32_LEAN_AND_MEAN
  2 #define WIN32_LEAN_AND_MEAN
  3 #endif
  4 
  5 #include <Windows.h>
  6 
  7 #include <winsock2.h>
  8 #include <ws2tcpip.h>
  9 #include <stdio.h>
 10 #include <stdlib.h>
 11 
 12 // Need to link with Ws2_32.lib
 13 #pragma comment(lib, "ws2_32.lib")
 14 
 15 #define DATA_BUFSIZE 4096
 16 #define SEND_COUNT   1
 17 
 18 int __cdecl main()
 19 {
 20     WSADATA wsd;
 21 
 22     struct addrinfo *result = NULL;
 23     struct addrinfo hints;
 24     WSAOVERLAPPED SendOverlapped;
 25 
 26     SOCKET ListenSocket = INVALID_SOCKET;
 27     SOCKET AcceptSocket = INVALID_SOCKET;
 28 
 29     WSABUF DataBuf;
 30     DWORD SendBytes;
 31     DWORD Flags;
 32     DWORD RecvBytes;
 33 
 34 
 35     char buffer[DATA_BUFSIZE] = {0};
 36 
 37     int err = 0;
 38     int rc, i;
 39 
 40     // Load Winsock
 41     rc = WSAStartup(MAKEWORD(2, 2), &wsd);
 42     if (rc != 0) {
 43         printf("Unable to load Winsock: %d\n", rc);
 44         return 1;
 45     }
 46 
 47     // Make sure the hints struct is zeroed out
 48     SecureZeroMemory((PVOID) & hints, sizeof(struct addrinfo));
 49 
 50     // Initialize the hints to obtain the 
 51     // wildcard bind address for IPv4
 52     hints.ai_family = AF_INET;
 53     hints.ai_socktype = SOCK_STREAM;
 54     hints.ai_protocol = IPPROTO_TCP;
 55     hints.ai_flags = AI_PASSIVE;
 56 
 57     rc = getaddrinfo(NULL, "5150", &hints, &result);
 58     
 59     if (rc != 0) {
 60         printf("getaddrinfo failed with error: %d\n", rc);
 61         return 1;
 62     }
 63 
 64     ListenSocket = socket(result->ai_family,
 65                           result->ai_socktype, result->ai_protocol);
 66     if (ListenSocket == INVALID_SOCKET) {
 67         printf("socket failed with error: %d\n", WSAGetLastError());
 68         freeaddrinfo(result);
 69         return 1;
 70     }
 71 
 72     rc = bind(ListenSocket, result->ai_addr, (int) result->ai_addrlen);
 73     if (rc == SOCKET_ERROR) {
 74         printf("bind failed with error: %d\n", WSAGetLastError());
 75         freeaddrinfo(result);
 76         closesocket(ListenSocket);
 77         return 1;
 78     }
 79 
 80     rc = listen(ListenSocket, 1);
 81     if (rc == SOCKET_ERROR) {
 82         printf("listen failed with error: %d\n", WSAGetLastError());
 83         freeaddrinfo(result);
 84         closesocket(ListenSocket);
 85         return 1;
 86     }
 87     // Accept an incoming connection request
 88     AcceptSocket = accept(ListenSocket, NULL, NULL);
 89     if (AcceptSocket == INVALID_SOCKET) {
 90         printf("accept failed with error: %d\n", WSAGetLastError());
 91         freeaddrinfo(result);
 92         closesocket(ListenSocket);
 93         return 1;
 94     }
 95 
 96     printf("Client Accepted...\n");
 97 
 98     // Make sure the SendOverlapped struct is zeroed out
 99     SecureZeroMemory((PVOID) & SendOverlapped, sizeof (WSAOVERLAPPED));
100 
101     // Create an event handle and setup the overlapped structure.
102     SendOverlapped.hEvent = WSACreateEvent();
103     if (SendOverlapped.hEvent == NULL) {
104         printf("WSACreateEvent failed with error: %d\n", WSAGetLastError());
105         freeaddrinfo(result);
106         closesocket(ListenSocket);
107         closesocket(AcceptSocket);
108         return 1;
109     }
110 
111     DataBuf.len = DATA_BUFSIZE;
112     DataBuf.buf = buffer;
113 
114     for (i = 0; i < SEND_COUNT; i++) {
115 
116         //接收数据,并绑定重叠结构
117         Flags = 0;
118         rc = WSARecv(AcceptSocket,&DataBuf,1,&RecvBytes,&Flags,&SendOverlapped,NULL);
119         if ((rc == SOCKET_ERROR) &&
120             (WSA_IO_PENDING != (err = WSAGetLastError()))) {
121             printf("WSARecv failed with error: %d\n", err);
122             break;
123         }
124         DataBuf.buf[RecvBytes] = '\0';
125         printf("%s\n",DataBuf.buf);
126 
127         //等待事件
128         rc = WSAWaitForMultipleEvents(1, &SendOverlapped.hEvent, TRUE, INFINITE,  
129                                       TRUE);
130         if (rc == WSA_WAIT_FAILED) {
131             printf("WSAWaitForMultipleEvents failed with error: %d\n",
132                    WSAGetLastError());
133             break;
134         }    
135 
136         //返回重叠I/O的处理结果
137         rc = WSAGetOverlappedResult(AcceptSocket, &SendOverlapped, &SendBytes,
138                                     FALSE, &Flags);
139         if (rc == FALSE) {
140             printf("WSAGetOverlappedResult failed with error: %d\n", WSAGetLastError());
141             break;
142         }
143         //重置事件为未传信状态
144         WSAResetEvent(SendOverlapped.hEvent);
145 
146         memset(buffer,0,DATA_BUFSIZE);
147         sprintf_s(buffer,DATA_BUFSIZE,"服务器端发送的数据\0");
148         int len = strlen(buffer);
149         DataBuf.len = len;
150         DataBuf.buf = buffer;
151 
152         //发送数据
153         rc = WSASend(AcceptSocket, &DataBuf, 1,
154                      &SendBytes, Flags, &SendOverlapped, NULL);
155         if ((rc == SOCKET_ERROR) &&
156             (WSA_IO_PENDING != (err = WSAGetLastError()))) {
157             printf("WSASend failed with error: %d\n", err);
158             break;
159         }
160         printf("Wrote %d bytes\n", SendBytes);
161 
162     }
163 
164     WSACloseEvent(SendOverlapped.hEvent);
165     closesocket(AcceptSocket);
166     closesocket(ListenSocket);
167     freeaddrinfo(result);
168 
169     WSACleanup();
170 
171     return 0;
172 }

  2.4.2 完成例程

   完成例程,使用回调函数实现网络事件的处理,因此是良好的替代方式。必须注意回调函数中关于等待状态、网络关闭等状况的处理。

   没有试验编写代码;

  2.5 完成端口模型

   理解:

   创建等同与操作系统CPU的线程数(避免IO时线程切换),socket对象平均分配给多个线程对象。当socket的IO完成时,触发挂起线程的状态转变为运行状态。

    1、创建线程,并挂起线程(GetQueuedCompletionStatus方法);

    2、创建完成端口;

    3、完成端口和socket关联;

    4、WSARecv、WSASend等IO方法完成时,触发挂起线程的状态转变为运行状态;

    5、线程中完成socket的处理操作;

    

  1 /////////////////////////////////////////////////
  2 // IOCPDemo.cpp文件            调试通过
  3 
  4 //#include "WSAInit.h"
  5 #include <stdio.h>
  6 #include <WinSock2.h>
  7 #include <windows.h>
  8 
  9 #pragma comment(lib,"ws2_32.lib")
 10 
 11 
 12 #define BUFFER_SIZE 1024
 13 #define OP_READ   1
 14 #define OP_WRITE  2
 15 #define OP_ACCEPT 3
 16 
 17 typedef struct _PER_HANDLE_DATA        // per-handle数据
 18 {
 19     SOCKET s;            // 对应的套节字句柄
 20     sockaddr_in addr;    // 客户方地址
 21     char buf[BUFFER_SIZE];    // 数据缓冲区
 22     int nOperationType;        // 操作类型
 23 } PER_HANDLE_DATA, *PPER_HANDLE_DATA;
 24 
 25 DWORD WINAPI ServerThread(LPVOID lpParam)
 26 {
 27     // 得到完成端口对象句柄
 28     HANDLE hCompletion = (HANDLE)lpParam;
 29 
 30     DWORD dwTrans;
 31     PPER_HANDLE_DATA pPerHandle;
 32     OVERLAPPED *pOverlapped;
 33     while(TRUE)
 34     {
 35         // 在关联到此完成端口的所有套节字上等待I/O完成
 36         // GetQueuedCompletionStatus使调用线程挂起
 37         printf("wait connecting...\n");
 38         BOOL bOK = ::GetQueuedCompletionStatus(hCompletion, 
 39             &dwTrans, (PULONG_PTR)&pPerHandle, &pOverlapped, WSA_INFINITE);
 40         if(!bOK)                        // 在此套节字上有错误发生
 41         {
 42             ::closesocket(pPerHandle->s);
 43             ::GlobalFree(pPerHandle);
 44             ::GlobalFree(pOverlapped);
 45             continue;
 46         }
 47 
 48         if(dwTrans == 0 &&                // 套节字被对方关闭
 49             (pPerHandle->nOperationType == OP_READ || pPerHandle->nOperationType == OP_WRITE))    
 50 
 51         {
 52             ::closesocket(pPerHandle->s);
 53             ::GlobalFree(pPerHandle);
 54             ::GlobalFree(pOverlapped);
 55             continue;
 56         }
 57 
 58         switch(pPerHandle->nOperationType)    // 通过per-I/O数据中的nOperationType域查看什么I/O请求完成了
 59         {
 60         case OP_READ:    // 完成一个接收请求
 61             {
 62 //                 // 继续投递接收I/O请求
 63 //                 WSABUF buf;
 64 //                 buf.buf = pPerHandle->buf ;
 65 //                 buf.len = BUFFER_SIZE;
 66 //                 pPerHandle->nOperationType = OP_READ;
 67 //     
 68 //                 DWORD nFlags = 0;
 69 //                 ::WSARecv(pPerHandle->s, &buf, 1, &dwTrans, &nFlags, pOverlapped, NULL);
 70 
 71                 pPerHandle->buf[dwTrans] = '\0';
 72                 printf("%s\n",pPerHandle-> buf);
 73 
 74                 WSABUF buf;
 75                 char *buffer = "服务器端的数据";
 76                 int len = strlen(buffer);
 77                 buf.len = 512;
 78                 buf.buf = buffer;
 79                 DWORD SendBytes;
 80                 int err = 0;
 81                 int rc = ::WSASend(pPerHandle->s,
 82                             &buf,1,&SendBytes,0,NULL,NULL);
 83                 shutdown(pPerHandle->s,SD_SEND);
 84                 
 85 
 86             }
 87             break;
 88         case OP_WRITE: // 本例中没有投递这些类型的I/O请求
 89         case OP_ACCEPT:
 90             break;
 91         }
 92     }
 93     return 0;
 94 }
 95 
 96 
 97 void main()
 98 {
 99     int nPort = 5150;
100 
101     // 创建完成端口对象,创建工作线程处理完成端口对象中事件
102     HANDLE hCompletion = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0);
103     ::CreateThread(NULL, 0, ServerThread, (LPVOID)hCompletion, 0, 0);
104 
105     // 创建监听套节字,绑定到本地地址,开始监听
106     WSADATA wsaData;
107     WSAStartup (MAKEWORD(2,2),&wsaData);
108     SOCKET sListen = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
109     SOCKADDR_IN si;
110     si.sin_family = AF_INET;
111     si.sin_port = ::ntohs(nPort);
112     si.sin_addr.S_un.S_addr = inet_addr("10.15.81.120");
113     ::bind(sListen, (sockaddr*)&si, sizeof(si));
114     ::listen(sListen, 5);
115 
116     // 循环处理到来的连接
117     while(TRUE)
118     {
119         // 等待接受未决的连接请求
120         SOCKADDR_IN saRemote;
121         int nRemoteLen = sizeof(saRemote);
122         SOCKET sNew = ::accept(sListen, (sockaddr*)&saRemote, &nRemoteLen);
123 
124         // 接受到新连接之后,为它创建一个per-handle数据,并将它们关联到完成端口对象。
125         PPER_HANDLE_DATA pPerHandle = 
126             (PPER_HANDLE_DATA)::GlobalAlloc(GPTR, sizeof(PER_HANDLE_DATA));
127         pPerHandle->s = sNew;
128         memcpy(&pPerHandle->addr, &saRemote, nRemoteLen);
129         pPerHandle->nOperationType = OP_READ;
130 
131         //把sNew套接字关联到hCompletion完成端口对象,pPerHandle是携带的参数数据;
132         ::CreateIoCompletionPort((HANDLE)pPerHandle->s, hCompletion, (ULONG_PTR)pPerHandle, 0);
133 
134         //投递一个接收请求
135         OVERLAPPED *pol = (OVERLAPPED *)::GlobalAlloc(GPTR, sizeof(OVERLAPPED));
136         WSABUF buf;
137         buf.buf = pPerHandle->buf;
138         buf.len = BUFFER_SIZE;    
139         DWORD dwRecv;
140         DWORD dwFlags = 0;
141         //IO操作(WSARecv、WSASend、WSARecvFrom、WSASendTo)完成,即sNew的IO操作完成,
142         //触发线程回到运行状态,即GetQueuedCompletionStatus返回,并得到携带参数数据.
143         ::WSARecv(pPerHandle->s, &buf, 1, &dwRecv, &dwFlags, pol, NULL);
144 
145     }
146 }

         

    

原文地址:https://www.cnblogs.com/xuxu8511/p/3127059.html