套接字的I/O模型(一)

套接字的I/O模型(一)

共有6种类型的套接字I/O模型,它们包括:blocking(阻塞)、select(选择)、WSAAsyncSelect(异步选择)、WSAEventSelect(事件选择)、overlapped(重叠)以及completionport(完成端口).

  1. 阻塞模型

    • 通常采用这个模型的应用程序,在处理I/O时,每个套接字连接通常会使用一个或两个线程。之后每个线程都将发出阻塞操作,如send和recv。
    • 阻塞模型的优点是其简洁性。
    • 缺点是创建线程会消耗系统资源,很难将它扩展到有很多连接的情况。

  2. select模型

    • select模型的工作原理是利用select函数,实现对I/O的管理,所以称其为“select模型”。

    • select函数可用于判断套接字上是否存在数据,或者能否向一个套接字写入数据。

    • 设计select函数的目的是为了防止应用程序在套接字处于阻塞模式时,在I/O绑定调用(如send或recv)过程中进入阻塞状态;同时也防止在套接字处于非阻塞模式中,产生WSAEWOULDBLOCK错误。除非满足事先用参数规定的条件,否则select函数在进行I/O操作时会阻塞。

      WSAEWOULDBLOCK错误意味着请求的操作在调用期间没有时间完成。

    int select(
     _In_    int                  nfds,     //忽略,仅为和早起套接字程序兼容 置为0
     _Inout_ fd_set               *readfds, // checked for readability.
     _Inout_ fd_set               *writefds,    // checked for writability.
     _Inout_ fd_set               *exceptfds,   //checked for errors
     _In_    const struct timeval *timeout
    );
    1. 在三个参数readfds,writefds,exceptfds,至少有一个不能为空值(NULL);
    2. timeout,用来决定select等待I/O操作完成时,最多等待多长的时间,若超过timeval设定时间,便会返回0。如果timeout是一个空指针,select会无限期的处于阻塞状态,直到有一个描述符于指定条件相符后才结束
    • readfds集合包含符合下述一个条件的套接字:
      1. 有数据可读
      2. 连接已经被关闭、重启或终止
      3. 假如已调用了listen,而且有一个连接正处于搁置状态,那么accept函数调用会成功
    • writefds集合包括符合下述一个条件的套接字:
      1. 有数据可以发出
      2. 如果正对一个非阻塞连接调用进行处理,则连接成功
    • exceptfds集合包括符合下述一个条件的套接字:
      1. 假如正对一个非阻塞连接调用进行处理,连接尝试就会失败
      2. 有OOB(Out-of-band,带外)数据可供读取
    typedef struct timeval {
     long tv_sec;
     long tv_usec;
    } timeval;
    //tv_sec为 timeval时的秒数,tv_usec为微秒数,即秒后面的零头。
    • 用select对套接字进行监听之前,应用程序必须将套接字句柄分配给一个集合,设置好一个或所有的读、写以及例外的fd_set。 讲一个套接字分配给任何一个集合后,再来调用select,便可知道某个套接字上是否在发生I/O活动。可以使用下列宏对fd_set集合进行处理与检查:
      1. FD_ZERO(* set) : 将set初始化成空集合。集合在使用前都要清空
      2. FD_CLR(s, * set): 从set中删除套接字s
      3. FD_ISSET(s, * set): 检查s是否为set集合的一名成员:若是返回TRUE
      4. FD_SET(s, * set): 将套接字s加入集合set中
    • 假设我们想知道是否可以从一个套接字中安全地读取数据,同时不会陷入阻塞状态,便可使用FD_SET宏,将这个套接字分配给fd_read集合,在调用select。要检测这个套接字是否仍属于fd_read集合的一部分,可使用FD_ISSET宏。
    • 采用下述步骤,便可完成用selectc操作一个或多个套接字句柄的全过程:
      1. 使用FD_ZERO宏,初始化自己感兴趣的每一个fd_set。
      2. 使用FD_SET宏,将套接字句柄分配给自己感兴趣的每个fd_set。
      3. 调用select函数,然后等待直到I/O活动在指定的fd_set集合中设置好了一个或多个套接字句柄。select完成后,会返回在所有fd_set集合中设置的套接字句柄总数,并对每个集合进行相应的更新。
      4. 根据select的返回值,应用程序便可判断出哪些套接字存在着被搁置的I/O操作(具体的方法是使用FD_ISSET宏,对每个fd_set集合进行检查)。
      5. 知道了每个集合中被挂起的I/O操作之后,对I/O进行处理,然后返回步骤1,继续处理select。
    • select返回后,它会修改每个fd_set结构,删除那些不存在被挂起的I/O操作的套接字句柄。这正是上述步骤4中,为何用使用FD_ISSET宏来判断某个特定的套接字是否仍在集合中的原因。
    • 示例代码:(仅流程)
    SOCKET s;
    fd_set fdread;
    int ret;
    
    //创建套接字,接受连接
    
    //在套接字上管理I/O
    while (TRUE)
    {
        //在调用select()之前始终清除读出集
        FD_ZERO(&fdread);
        if ((ret == select(0, &fdread, NULL, NULL, NULL)) == SOCKET_ERROR)
        {
            //条件有错
        }
    
        if (ret > 0)
        {
            //在这个简单的例子中,select() 返回值为1.处理多个套接字的应用程序可返回比1大的值。
            //在这里,应用程序应该检查一下,看看该套接字是否属于集合的一部分。
            if (FD_ISSET(s, &fdread))
            {
                //套接字上发生了一个事件
            }
        }
    }
    • 使用select的优势是,能够从单个线程的套接字上进行多重连接及I/O。这就避免了伴随阻塞套接字和多重连接的线程剧增。但可以加到fd_set结构中的最大套接字数量是一个不好的地方,默认状态下,最大数据由FD_SETSIZE定义(默认为64)。用fd_set设置最大值为1024。

  3. WSAAsyncSelect模型

    • WSAAsyncSelect是一个异步I/O模型,应用程序可在一个套接字上,接收以windows消息为基础的网络事件通知。 具体做法是在建好一个套接字后,调用WSAAsyncSelect函数。

      WSAAsyncSelect和WSAEventSelect模型提供了读写数据能力的异步通知,但是它们不提供异步数据传送,而重叠及完成端口模型却提供异步数据传送。

    • 【消息通知】想要使用WSAAsyncSelect模型,在应用程序中,首先必须使用createWindow函数创建一个窗口,再为该窗口提供一个窗口过程支持函数(Winproc)。亦可使用一个对话框,为其提供一个对话过程来代替窗口过程,这是因为对话框本质也是窗口。

    int WSAAsyncSelect(
     _In_ SOCKET       s,       //我们感兴趣的套接字
     _In_ HWND         hWnd,    //指定窗口句柄,网络事件发生后,想要接收到通知消息的窗口或对话框
     _In_ unsigned int wMsg,    //指定网络事件发生时,打算接收消息。投递到hWnd所标示的窗口或对话框
     _In_ long         lEvent   //应用程序感兴趣的一系列事件。
    );
    WSAAsyncSelect(s, hWnd, wMsg, FD_READ|FD_CONNECT|FD_CLOSE);//可参考MSDN
    • 多个事件务必在套接字上一次完成注册。另外还要注意的是,一旦在某个套接字上启用了事件通知,那么以后除非明确调用closesocket命令,或者由应用程序针对这个套接字调用了WSAAsyncSelect,从而更改注册的网络事件类型,否则事件通知总是有效的。若将lEvent设置为0,则相当于停止在套接字上进行所有的网络事件通知。

    • 应用程序在一个套接字上成功调用WSAAsyncSelect之后,它会在hWnd窗口句柄参数相关联的窗口过程中,以Windows消息的形式,接收网络事件通知。

      LRESULT CALLBACK WindowProc(
      _In_ HWND   hwnd,        //窗口句柄
      _In_ UINT   uMsg,        //指定需要对哪些消息进行处理
      _In_ WPARAM wParam,  //指定的是一个套接字,该套接字上发生了一个网络事件
      _In_ LPARAM lParam   //低位节指定了已经发生的网络事件,高位字包含了可能出现的错误代码
      );

      网络事件抵达窗口过程后,应用程序首先检查lParam高字位判断是否发生了网络错误(WSAGETSELECTERROR)。若无错误,则调查是哪个网络事件类型造成了这条Windows消息的触发(lParam低字位内容)。WSAGETSELECTEVENT返回lParam低字部分。

    • 示例代码:(仅流程)

    
    #define WM_SOCKET WM_USER+11
    
    
    
    #include <WinSock2.h>
    
    
    #include <windows.h>
    
    
    #include<Ws2tcpip.h>
    
    
    int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
    {
    WSADATA wsd;
    SOCKET Listen;
    SOCKADDR_IN InternetAddr;
    HWND Window;
    
    //创建窗口,并将下面的ServerWinProc分配给它
    Window = CreateWindow();
    
    //启动并创建套接字
    WSAStartup(MAKEWORD(2, 2),&wsd);
    Listen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    
    //将套接字绑定到5150端口上并开始监听连接
    InternetAddr.sin_family = AF_INET;
    inet_pton(AF_INET, INADDR_ANY, (void*)InternetAddr.sin_addr.S_un.S_addr);
    InternetAddr.sin_port = htons(5150);
    
    bind(Listen, (PSOCKADDR)&InternetAddr,sizeof(InternetAddr));
    
    //使用上面定义的WM_SOCKET在新套接字上设置窗口消息通知
    WSAAsyncSelect(Listen, Window, WM_SOCKET, FD_ACCEPT | FD_CLOSE);
    listen(Listen,5);
    
    //转换并分派窗口消息,知道应用程序终止
    while (TRUE)
    {
        //.............
    }
    }
    
    BOOL CALLBACK ServerWinProc(HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lParam)
    {
    SOCKET Accept;
    switch (wMsg)
    {
    case WM_PAINT:
        //处理窗口画图消息
        break;
    case WM_SOCKET:
        //使用WSAGETSELECTERROR宏来判断套接字上是否发生了错误
        if (WSAGETSELECTERROR(lParam))
        {
            //显示错误,关闭套接字
            closesocket((SOCKET)wParam);
            break;
        }
        //确定在套接字上发生了什么事件
        switch (WSAGETSELECTEVENT(lParam))
        {
        case FD_ACCEPT:
            //接受一个传入的连接
            Accept = accept(wParam, NULL, NULL);
    
            //让结束套接字为读、写及关闭通知做好准备
            WSAAsyncSelect(Accept, hDlg, WM_SOCKET);
            break;
        case FD_READ:
            //从wParam中的套接字中检索数据
            break;
        case FD_WRITE:
            从wParam中的套接字已准备好发送数据
            break;
        case FD_CLOSE:
            closesocket((SOCKET)wParam);
            break;
        }
        break;
    }
    return TRUE;
    }
    • 应用程序如何对FD_WRITE事件通知进行处理。在下列三种条件下,FD_WRITE通知才会发出:

      1. 使用connect或WSAConnect,一个套接字首次建立连接
      2. 使用accept或WSAAccept,套接字被接受以后
      3. 若send、WSASend、sendto或WSASendTo操作失败,返回了WSAEWOULDBLOCK错误,而且缓冲区的空间变得可用时

      因此,作为一个应用程序,自收到首条FD_WRITE消息开始,便应认为必然能在一个套接字上发出数据,直至send、WSASend、sendto或WSASendTo返回套接字错误WSAEWOULDBLOCK。经过了这样的失败以后,要再用另一条FD_WRITE通知应用程序可以再次发送数据。

    • WSAAsyncSelect优点:它可以在系统开销不大的情况下同时处理许多连接,而select模型需要接力fd_set结构。

    • WSAAsyncSelect缺点:应用程序不需要窗口(例如服务或控制台应用程序),它也不得不额外使用一个窗口。同时,用一个单窗口程序来处理成千上万的套接字中的所有事件,很有可能成为性能瓶颈(意味着这个模型的伸缩性不太好)。

  4. WSAEventSelect模型

    • 类似于WSAAsyncSelect,它也允许应用程序在一个或多个套接字上,接收以事件为基础的网络事件通知。于它的主要差别在于网络事件通知是由对象句柄完成的,而不是通过窗口例程完成。

    • 事件通知模型要求应用程序针对打算使用的每一个套接字,首先创建一个事件对象。

      WSAEVENT WSACreateEvent(void);//if error return WSA_INVALID_EVENT
    • WSACreateEvent返回值是一个人工重设的对象句柄,一旦得到了事件对象句柄之后,必须将它与某个套接字关联在一起,同时注册感兴趣的网络事件类型。使用WSAEventSelect。

      int WSAEventSelect(
      _In_ SOCKET   s,             //感兴趣的套接字
      _In_ WSAEVENT hEventObject,  //要与套接字关联在一起的事件对象,用WSACreateEvent获得。
      _In_ long     lNetworkEvents //网络事件 FD_XXX 
      );
    • 为WSACreateEvent创建的事件有两种工作状态和两种工作模式。

      1. 工作状态:已传信(signaled)和未传信(non-signaled)
      2. 工作模式:人工重设(manualreset)和自动重设(autoreset)。
    • WSACreateEvent最开始在一种未传信的工作状态中,并用一种人工重设模式,来创建事件句柄。若网络事件触发了一个与套接字关联在一起的事件对象,工作状态便会从未传信转变为已传信。由于事件对象是在一种人工重设模式中创建的,所以在完成了一个I/O请求的处理之后,应用程序需要负责将工作状态从已传信更改为未传信。需要调用WSAResetEvent。

      BOOL WSAResetEvent(_In_ WSAEVENT hEvent    //事件句柄
                     );//if succeeds return TRUE

      当应用程序完成对某个事件对象的处理后,便应调用WSACloseEvent释放由事件句柄使用的系统资源。

      BOOL WSACloseEvent( _In_ WSAEVENT hEvent);////if succeeds return TRUE
    • 套接字同一个事件对象句柄关联在一起后,应用程序便可开始开始I/O处理。这就需要应用程序等待网络事件触发事件对象的工作状态。WSAWaitForMultipleEvents函数便是一个用来等待一个或多个事件对象句柄,并在事先指定的一个或所有句柄进入已传信状态后,或在超过一个规定的时间周期后,立即返回。

      DWORD WSAWaitForMultipleEvents(
      _In_       DWORD    cEvents,     //事件对象的数量 最大值:WSA_MAXIMUM_WAIT_EVENTS (64)
      _In_ const WSAEVENT *lphEvents,  //引用该事件对象的数组
      _In_       BOOL     fWaitAll,        //TRUE:lphEvents所有对象进入已传信状态才会返回
      _In_       DWORD    dwTimeout,   //等待最长时间(毫秒),WSA_INFINITE等到一个事件为止
      _In_       BOOL     fAlertable   //在WSACreateEvent里可被忽略且应设置为FALSE
      );

      如果一次只服务一个已传信事件(fWaitAll设为FALSE),就可能让套接字一直处于“挨饿”,且可能持续到事件数组末尾。例如下面代码

      WSAEVENT HandleArray[WSA_MAXIMUM_WAIT_EVENTS];
      int Waitcount = 0, ret, index;
      
      //将事件句柄分配到HandleArray
      while (TRUE)
      {
      ret = WSAWaitForMultipleEvents(Waitcount, HandleArray, FALSE, WSA_INFINITE, FALSE);
      if ((ret != WSA_WAIT_FAILED) && (ret != WSA_WAIT_TIMEOUT))
      {
          index = ret - WSA_WAIT_EVENT_0;
          //服务事件在HandleArray[index]上被传信
          WSAResetEvent(HandleArray[index]);
      }
      }

      如果和事件数组中索引0相关的套接字连续地接收数据, 以至于事件被重置之后,又有额外数据到达,并导致事件再次被传信,则数组中其余的时间就会被闲置。这当然不是理想的状态。只要回路中有一个事件被传信并得到处理,就应该检查数组中所有的事件,看看它们是否也被传信。在有事件被传信之后,对每个事件的句柄使用WSAWaitForMultipleEvents,并将dwTimeOut指定为0,既可达到上述目的。

    • 若WSAWaitForMultipleEvents收到一个事件对象的网络事件通知,便会返回一个值,指出造成函数返回的事件对象。这样,应用程序便可引用事件数组中已传信的事件,并检索与那个事件对应的套接字,判断到底是哪个套接字上,发生了什么样的网络事件类型。对事件数组中的事件进行引用时应该用WSAWaitForMultipleEvents的返回值减去预定于值WSA_WAIT_EVENT_0,从而得到具体索引值。

      index = WSAWaitForMultipleEvents(......);
      MyEven t= EvenArray[index - WSA_WAIT_EVENT_0];
    • 知道了造成网络事件的套接字之后,接下来可调用WSAEnumNetworkEvents函数,查看网络事件。

      int WSAEnumNetworkEvents(
      _In_  SOCKET             s,              //造成网络事件的套接字
      _In_  WSAEVENT           hEventObject,   //事件句柄
      _Out_ LPWSANETWORKEVENTS lpNetworkEvents //检索套接字上发生的网络事件类型等....
      );
      //hEventObject参数是可选的,指定了一个事件句柄,对应打算重设的那个事件对象。由于我们对象处于一种已传信状态,所以可将它传入,令其自动成为未传信状态。如果不想用hEventObject参数来重设事件,那么可使用WSAResetEvent函数对事件进行人工重设。
    • 示例代码:(仅流程)

      
      #include<winsock2.h>
      
      
      #include<Ws2tcpip.h>
      
      
      #include<stdio.h>
      
      
      
      #pragma comment(lib,"WS2_32")
      
      
      
      #define BUFFER_SIZE 1024  
      
      void CompressArrays(WSAEVENT events[], SOCKET sockets[], DWORD *total, int index)
      {
      for (size_t i = index + 1; i < *total; i++)
      {
          events[i - 1] = events[i];
      }
      *total--;
      }
      int main(int argc, char **argv)
      {
      WSADATA wsaData;
      char buffer[BUFFER_SIZE];
      sockaddr_in InternetAddr;
      SOCKET SocketArray[WSA_MAXIMUM_WAIT_EVENTS];
      
      WSANETWORKEVENTS NetworkEvents;
      WSAEVENT NewEvent;
      WSAEVENT EventArray[WSA_MAXIMUM_WAIT_EVENTS];
      
      SOCKET Accept, Listen;
      
      DWORD EventTotal = 0;
      DWORD Index;
      if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
      {
          printf("WSAStartup()/n");
          return 0;
      }
      // 创建一个流式套接口  
      Listen = socket(AF_INET, SOCK_STREAM, 0);
      InternetAddr.sin_family = AF_INET;
      InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);
      InternetAddr.sin_port = htons(5050);
      if (bind(Listen, (PSOCKADDR)&InternetAddr, sizeof(InternetAddr)) == SOCKET_ERROR)
      {
          printf("bind()/n");
          return 0;
      }
      // 创建一个事件对象  
      NewEvent = WSACreateEvent();
      // 在Listen套接口上注册套接口连接和关闭的网络事件  
      WSAEventSelect(Listen, NewEvent, FD_ACCEPT | FD_CLOSE);
      
      if (listen(Listen, 5) == SOCKET_ERROR)
      {
          printf("listen()/n");
          return 0;
      }
      SocketArray[EventTotal] = Listen;
      EventArray[EventTotal] = NewEvent;
      EventTotal++;
      while (true)
      {
          // 在所有套接口上等待网络事件的发生  
          Index = WSAWaitForMultipleEvents(EventTotal, EventArray, FALSE, WSA_INFINITE, FALSE);
          if (WSAEnumNetworkEvents(SocketArray[Index - WSA_WAIT_EVENT_0],
              EventArray[Index - WSA_WAIT_EVENT_0],
              &NetworkEvents) == SOCKET_ERROR)
          {
              printf("%d/n", WSAGetLastError());
              printf("WSAEnumNetworkEvents()/n");
              return 0;
          }
      
          // 检查FD_ACCEPT  
          if (NetworkEvents.lNetworkEvents & FD_ACCEPT)
          {
              if (NetworkEvents.iErrorCode[FD_ACCEPT_BIT] != 0)
              {
                  WSACloseEvent(EventArray[Index - WSA_WAIT_EVENT_0]);
                  printf("FD_ACCEPT failed with error %d/n", NetworkEvents.iErrorCode[FD_ACCEPT_BIT]);
                  break;
              }
              // 接收新的连接,并将其存入套接口数组  
              Accept = accept(SocketArray[Index - WSA_WAIT_EVENT_0], NULL, NULL);
              // 当套接口的数量超界时,关闭该套接口  
              if (EventTotal > WSA_MAXIMUM_WAIT_EVENTS)
              {
                  printf("Too many connections");
                  closesocket(Accept);
                  break;
              }
              NewEvent = WSACreateEvent();
              if (NewEvent == WSA_INVALID_EVENT)
              {
                  printf("WSACreateEvent()/n");
                  break;
              }
              WSAEventSelect(Accept, NewEvent, FD_READ | FD_WRITE | FD_CLOSE);
              EventArray[EventTotal] = NewEvent;
              SocketArray[EventTotal] = Accept;
              EventTotal++;
              printf("Socket %d connected/n", Accept);
          }
      
          // 一下处理FD_READ通知  
          if (NetworkEvents.lNetworkEvents & FD_READ)
          {
              if (NetworkEvents.iErrorCode[FD_READ_BIT] != 0)
              {
                  WSACloseEvent(EventArray[Index - WSA_WAIT_EVENT_0]);
                  printf("FD_READ failed with error %d/n", NetworkEvents.iErrorCode[FD_READ_BIT]);
                  break;
              }
      
              // 从套接口读入数据  
              int iRecv = recv(SocketArray[Index - WSA_WAIT_EVENT_0], buffer, sizeof(buffer), 0);
              if (iRecv == 0)
              {
                  break;
              }
              else if (iRecv == SOCKET_ERROR)
              {
                  printf("recv()/n");
                  break;
              }
              else
              {
                  printf("recv data: %s", buffer);
              }
          }
      
          // 以下处理FD_WRITE通知  
          if (NetworkEvents.lNetworkEvents & FD_WRITE)
          {
              if (NetworkEvents.iErrorCode[FD_WRITE_BIT] != 0)
              {
                  WSACloseEvent(EventArray[Index - WSA_WAIT_EVENT_0]);
                  printf("FD_WRITE failed with error %d/n", NetworkEvents.iErrorCode[FD_WRITE_BIT]);
                  break;
              }
              send(SocketArray[Index - WSA_WAIT_EVENT_0], buffer, sizeof(buffer), 0);
          }
      
          // 以下处理FD_CLOSE通知  
          if (NetworkEvents.lNetworkEvents & FD_CLOSE)
          {
              if (NetworkEvents.iErrorCode[FD_WRITE_BIT] != 0)
              {
                  WSACloseEvent(EventArray[Index - WSA_WAIT_EVENT_0]);
                  printf("FD_WRITE faield with error %d/n", NetworkEvents.iErrorCode[FD_WRITE_BIT]);
                  break;
              }
      
              // 关闭套接口  
              closesocket(SocketArray[Index - WSA_WAIT_EVENT_0]);
              // 从套接口事件和事件数组中删除关闭的套接口的有关信息  
              CompressArrays(EventArray, SocketArray, &EventTotal, Index - WSA_WAIT_EVENT_0);
          }
      }
      WSACleanup();
      return 0;
      }
      //client
      
      
      #include<winsock2.h>
      
      
      #include<Ws2tcpip.h>
      
      
      #include<stdio.h>
      
      
      
      #pragma comment(lib,"WS2_32")
      
      
      
      #define BUFFER_SIZE 1024  
      
      
      
      int main(int argc, char **argv)
      {
      WSADATA wsaData;
      sockaddr_in ser;
      
      SOCKET sClient;
      
      char send_buf[] = "hello, I am a client";
      
      if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
      {
          printf("WSAStartup()/n");
          return 0;
      }
      
      ser.sin_family = AF_INET;
      ser.sin_port = htons(5050);
      
      InetPtonA(AF_INET, "127.0.0.1", (void*)&ser.sin_addr);
      
      
      sClient = socket(AF_INET, SOCK_STREAM, 0);
      if (sClient == INVALID_SOCKET)
      {
          printf("socket()/n");
          return 0;
      }
      if (connect(sClient, (sockaddr*)&ser, sizeof(ser)) == INVALID_SOCKET)
      {
          printf("socket()/n");
          return 0;
      }
      else
      {
          for (int i = 0; i < 10; i++)
          {
              int iLen = send(sClient, send_buf, sizeof(send_buf), 0);
              if (iLen == 0)
              {
                  return 0;
              }
              else if (iLen == SOCKET_ERROR)
              {
                  printf("send()/n");
                  return 0;
              }
          }
      }
      closesocket(sClient);
      WSACleanup();
      return 0;
      }
  5. overlapped模型

  6. completionport模型

声明:以上整理于Windows网络编程(第二版)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值