• 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 }

             

        

  • 相关阅读:
    JDBC事务
    PreparedStatement预编译对象实现
    读取properties配置文件
    eclipse 快捷键总结
    JDBC编程六部曲
    JDBC 配置环境
    基于注解的DI(DI:Dependency Injection 依赖注入)
    基于XML的DI
    汇编call jmp理解
    常用jar包下载地址
  • 原文地址:https://www.cnblogs.com/xuxu8511/p/3127059.html
Copyright © 2020-2023  润新知