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 }