Windows 网络编程

 

Winsock 套接字模式:锁定和非锁定
 1.锁定模式:
   在I/O操作完成前,执行操作的Winsock函数(例send和recv)会一直等待下去,不会立即返回程序
   应用程序需要读取(或写入)指定数量的字节,然后以它为基础执行一些运算,
   假如没有数据处于"待决"状态,那么函数可能永远都无法返回,只有从系统的输入缓冲区中读回点东西
   才允许返回!
 2.非锁定模式:
   函数无论如何都会立即返回,
   把套接字置为非锁定:
   int ioctlsocket(s,FIOBIO,(unsigned long*)&ul);
  
 3.锁定模式的多线程示例:
   CRITICAL_SECTION         data;
   HANDLE                   hEvent;
   TCHAR                    buff[MAX_BUFFER_SIZE];
   int                      nbytes;

   //读线程
   void  ReadThread(void)
   {
      int  nTotal =0 ;
           nRead  =0;
           nLeft  =0;
           nBytes =0;

      while(!done)
      {
          nTotal =0;
          nLeft=NUM_BYTES_REQUIRED;
          while(nTotal != NUM_BYTES_REQUIRED)
          {
              EnterCriticalSection(&data);
              nRead = recv(sock,&(buff[MAX_BUFFER_SIZE-nBytes]),nLeft);
              if(nRead==-1)
              {
                  //Error;
                  ExitThread();
              }
              nTotal+=nRead;
              nLeft-=nRead;
              nBytes+=nRead;
              LeaveCriticalSection(&data);
          }
          SetEvent(hEvent);
      }
   }
   //计算线程
   void  ProcessThread(void)
   {
      WaitForSingleObject(hEvent);

      EnterCriticalSection(&data);
      DoSomeComputionOnData(buff);
      nBytes-=NUM_BYTES_REQUIRED;

      LeaveCriticalSection(&data);
   }

 

4.1 select 模型
       a>利于select函数,判断套接字上是否可以读取或写入数据,
          int select ( int nfds,               //忽略
                       fd_set  FAR* readfds,   //检察可读性
                       fd_set  FAR* writefds,  //检察可写性
                       fd_set  FAR* exceptfds, //异常数据
                       const struct timeval FAR*  timeout
                     );
          说明:fd_set类型,代表着特定套接字的集合,
               readfds包括任何一个下述的套接字:
                 i>有数据可以读入
                 ii>连接己经关闭,重设或中止
                 iii>假如己经调用了listen,而且一个连接正在建立,那么accept函数调用会成功
               writefds包括任何一个下述的套接字
                 i>有数据可以发出
                 ii>如果己完成了对一个非锁定连接调用的处理,连接就会成功
               exceptfds包括任何一个下述的套接字
                 i>假如己完成了对一个非锁定连接调用的处理,连接尝试就会失败
                 ii>有带外(out-of-band,OOB)数据可供读取
               select 函数完成后,可读取或可写入数据的套接字,保留在fds_set集合中,否则会被
               删除掉,
               三个fds_set集合参数,至少有一个不能为空值,
               将一个套接字分配给任何一个集合后,再调用select,便可知一个套接字是否发生I/O活动
         b>FD_CLR(s,*set):从set中删除套接字
         c>FD_ISSET(s,*set):检查s是否set集合的一名成员
         d>FD_SET(s,*set):将套接字s加入集合set
         e>FD_ZERO(*set):将set初始化成空集合
         f>下述步骤可以完成select操作一个或多个套接字句柄的全过程:
            1)使用FD_ZERO宏,初始化自己感兴趣的每一个fd_set
            2)使用F_SET宏,将套接字句柄分配给自己感兴趣的每个fd_set
            3)调用select函数,然后等待在指定的fd_set集合中,I/O活动设置好一个或多个套接字句柄.
              select完成后,会返回在所有fd_set集合中设置的套接字句柄总数,并对每个集合进行相
              应的更新
            4)根据select的返回值,应用程序便可判断出哪些套接字可以读取或写入(使用FD_ISSET),
              对每个fd_set集合进行检查
            5)知道了每个集合中"待决"的I/O操作后,对I/O进行处理,然后返回步骤1),继续进行select
              处理.
            select返回后,会修改每个fd_set结构,删除不存在待决I/O操作的套接字句柄
    g>例子为一个(只有一个)套接字设置select模型所需的一系列基本步骤.
      (若想在这个应用程序中添加更多的套接字,只需为额外的套接字维护它们的一个列表或数组)
      SOCKET     s;
      fd_set     fdread;
      int        ret;

      //Creat a socket and accept a connection
      while(True)
      {
          // Always clear the read set before calling select()
          FD_ZERO(&fdread);

          //Add socket s to the read set
          FD_SET(s,&fdread);

          if (ret=select(0,&fdread,NULL,NULL,NULL)==SOCKET_ERROR)
          {
              //Error
          }
          if (ret>0)
          {
              //For this simple case,select() should return the value 1,
              //An application dealing with more than one socket could get a value
              //greater than 1,At this point,your application should check to see whether
              //the socket is part of a set
              if (FI_ISSET(s,&fdread))
              {  //A read event has occurred on socket s}
          }
      }

 

4.2 WSAAsyncSelect (异步I/O模型)
        应用程序可在一个套接字上,接收以Windows消息为基础的网络事件通知.
        在应用程序中,必须用CreatWindow函数,创建一个窗口(若在MFC的VC中,指定窗口就行了),
        再为该窗口提供一个窗口例程函数(即回调函数)WinProc.
       int WSAAyncSelect( SOCKET       s,
                          HWND         hWnd,   //窗口句柄,要接收网络事件消息的窗口
                          unsigned int wMsg,   //准备接收的消息
                          long         lEvent  //位掩码,网络事件的组合
                        )
        说明:网络事件:FD_READ,FD_WRITE,FD_ACCEPT,FD_CONNECT,FD_CLOSE.
             对套接字s调用此函数,则此套接字自动从"锁定" 自动变成"非锁定"
       窗口例程:
       LRESULT  CALLBACK  WindowProc(HWND     hWnd,      //指向窗口的句柄,调用此例程的窗口
                                     UINT     uMsg,   //需果处理的消息
                                     WPARAM   wParam, //指向一个或多个发生网络事件的套接字
                                     LPARAM   lParam  //低位字己经发生的网络事件,高位字错误码
                                    )
       网络事件消息到达窗口例程后,程序首选检察lParam的高字位,断定套接字上是否发生了错误
       宏WSAGETSELECTERROR返回高字位包含的错误消息,若无错,确定网络事件类型,
       宏WSAGETSELECTEVENT返回低字位包含的网络事件类型
    例.WSAAsyncSelect服务器示范代码

       #define WM_SOCKET  WM_USER+1
       #include <windows.h>

       int WINAPI  WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,
                           int nCmdShow)
       {
           SOCKET    Listen;
           HWND      Window;
          
           //Create a window and assign the ServerWinpro below to it
           Window = CreateWindow();
           //Start Winsock and create a socket
           WSAStartup(…);
           Listen = Socket();
           //Bind the socket to port 5150 and begin listening for connections
          
           InternetAddr.sin_family      = AF_INET;
           InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);
           InternetAddr.sin_port        = htons(5150);
           bind(Listen,(PSOCKADDR)&InternetAddr,sizeof(InternetAddr));
          
           //Set up window message notiffication on the new socket using the WM_SOCKET define
           // above
           WSAAsyncSelect(Listen,Window,WM_SOCKET,FD_ACCEPT|FD_CLOSE);

           listen(Listen,5);
           //Translate and dispatch window messages until the application terminates
       }

       //窗口函数(例程,回调函数)
       BOOL  CALLBACK  ServerWinproc(HWND hDlg,WORD wMsg,WORD wParam,DWORD lParam)
       {
           SOCKET    Accept;
          
           switch(wMsg)
           {
               case WM_PAINT:
                    // Process window paint message
                    break;
               case WM_SOCKET:
                    //Determine whether an error occurred on the socket by using the
                    //WSAGETSELECTERROR() macro
                    if(WSAGETSELECTERROR(lParam))
                    {
                        //Errror
                        closesocket(wParam);
                        break;
                    }
                    //Determine what event occurred on the socket
                    switch(WSAGETSELECTEVENT(lParam))
                    {
                        case FD_ACCEPT:
                             //Accept an incoming connection
                             Accept = accept(wParam,NULL,NULL);

                             //Prepare accepted socket for read,
                             //Write and close notification
                             WSAAsyncSelect(Accept,hwnd,WM_SOCKET,FD_READ|FD_WRITE|FD_CLOse);
                             break;
                        case FD_READ:
                             //Receive data from the socket in wParam
                             break;
                        case FD_WRITE:
                             //the socket in wParam is ready for sending data
                             break;
                        case FD_CLOSE:
                             //the connection is now closed
                             closesocket(wParam);
                             break;
                    }
                    break;
           }
           return TRUE;
       }
     只有在三种条件下,才会发出FD_WRITE通知,
     i>使用connect或WSAConnect,一个套接字首次建立了连接
     ii>使用accept或WSAAccept,套接字被接受以后
     iii>若send,WSASend,sendto或WSASendTO操作失败,返回了WSAEWOULDBLOCK错误,而且缓冲区空间
         变得可用
     因此,应用程序自收到首条FD_WRITE消息开始,便应认为自己必然能在一个套接字上发出数据,直至一
     个send ,WSASend,sendto或WSASendTo返回套接字错误WSAEWOULDBLOCK.经过了这样的失败后,
     要再用另一条FD_WRITE通知程序再次发送数据.

 

4.3  WSAEventSelect
      该模型把网络事件投递至一个事件对象句柄,而不是一个窗口函数
      4.3.1事件通知:
        应用程序针对打算使用的套接字,首先创建一个事件对象
        WSAEVENT    WSACreateEvent( void );
        将返回事件句柄对象与套接字关联在一起,同时注册感兴趣的网络事件
        int  WSAEventSelect( SOCKET  s,
                             WSAEVENT hEventObject,     //事件句柄对象
                             long     lNetworkEvents
                           );
          
         为WSAEventSelect创建的事件有两种状态以及两种工作模式:
         工作状态:己传信和未传信
         工作模式:人工重设和自动重设
         WSACreateEvent最开始用未传信的工作状态和人工重设模式创建事件句柄,随着网络事件触发
         事件对象,工作状态便会从"未传信"转变成"己传信",由于事件对象是人工模式创建的,所在
         程序在完成了一个I/O处理后,应把事件对象从"己传信"更改为"未传信",用WSAResetEvent函数
         BOOL   WSAResetEvent( WSAEVENT  hEvent);
         程序完成对一个事件对象的处理后,应调用WSACloseEvent函数,释放资源
         BOOL   WSACloseEvent (WSAEVENT  hEvent); 
       4.3.2
         套接字与事件对象句柄关联后,程序就应该I/O处理:方法是等待网络事件触发事件对象句柄的
         工作状态,WSAWaitForMulipleEvents函数,便是用来等待一个或多个事件对象句柄,并在事先指
         定的一个或所有句柄进入"己传信"状态后或超时后,立即返回.
         DWORD    WSAWaitForMultipleEvents
          (DWORD    cEvents,      //WSAEVENT对象构成的数组里的事件对象数量
           const WSAEVENT FAR * lphEvents,   //指向事件对象数组
           BOOL  fWaitAll,        //True,所有事件对象变成"己传信"时,返回,否则有一个变就返回
           DWORD  dwTimeout,     //最多一个网络事件发生时,函数的等待时间
           BOOL   fAlertable    //FALSE
          );返回造成函数返回的事件对象的索引,从而知道发生在哪个套接字上
       例:
          Index = WSAWaitForMultipleEvents(....);
          MyEvent = EvnetArray[Index-WSA_WAIT_EVENT_0];
         接下来,调查发生网络事件的类型(WSAEnumNetworkEvents):
         int WSAEnumNetworkEvent(
                  SOCKET     s,         //对应产生网络事件的套接字
                  WSAEVENT   hEventObject,   //准备置成"未传信"状态的事件句柄
                  LPWSANETWORKEVENTS  lpNetworkEvents  //指向 WSANETWORKEVENTS结构,如下:
         );
         typedef  struct _WSANETWORKEVENTS
         {
             long  lNetworkEvents;  //对应套接字上发生的所有网络事件类型(可能多个)
             int   iErrorCode[FD_MAX_EVENTS];   //错误代码数组,同lNetworkEVents关联
                                                //每个错误索引是在事件类型后加'_BIT'
         } WSANETWORKEVENTS,FAR* LPWSANETWORKEVENTS;
        例:
          // Process FD_READ notification
          if (NetworkEvents.lNetworkEvents & FD_READ)
          {
              if(NetworkEvents.iErrorCode[FD_READ_BIT] !=0)
              {
                 //Error
              }
          }
    例:采用WSAEventSelect I/O模型的示范服务器源代码
    SOCKET    Socket[WSA_MAXIMUM_WAIT_EVENTS];
    WSAEVENT   Event[WSA_MAXINUM_WAIT_EVENTS];
    SOCKET    Accept,
              Listen;
    DWORD     EventTotal = 0;
    DWORD     Index;

    //Set up a TCP socket for listening on port 5150
    Listen = socket(PF_INET,SOCK_STREAM,0);
   
    InternetAddr.sin_family      = AF_INET;
    InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    InternetAddr.sin_port        = htons(5150);

    bind(Listen,(PSOCKADDR) &InternetAddr,sizeof(InternetAddr));

    NewEvent = WSACreateEvent();

    WSAEventSelect(Listen,NewEvnet,FD_ACCEPT|FD_CLOSE);

    listen(Listen,5);

    Socket[EventTotal] = Listen;
    Event[EventTotal] = NewEvent;
    EventTotal++;

    while (TRUE)
    {
        //Wait for network events on all sockets
        Index = WSAWaitForMultipleEvents(EventTotal,EventArray,FALSE,WSA_INFINITE,FALSE);

        WSAEnumNewWorkEvents(SocketArray[Index-WSA_WAIT_EVENT_0],
                              EventArray[Index-WSA_WAIT_EVENT_0],
                              &NetworkEvents);
        //Check for FD_ACCEPT messages
        if (NetworkEvents.lNetworkEvents & FD_ACCEPT)
        {
           if (NetworkEvents.iErrorCode[FD_ACCEPT_BIT] !=0)
           {
              //Error
              break;
           }
           //Accept a new connection and add it to the socket and event lists
           Accept = accept(SocketArray[Index-WSA_WAIT_EVENT_0],NULL,NULL);

           //We cannot process more than WSA_MAXIMUM_WAIT_EVENTS sockets ,
           //so close the accepted socket
           if (EventTotal > WSA_MAXIMUM_WAIT_EVENTS)
           {
               printf("........");
               closesocket (Accept);
               break;
           }
           NewEvent = WSACreateEvent();
      
           WSAEventSelect(Accept,NewEvent,FD_READ|FD_WRITE|FD_CLOSE);

           Event[EventTotal] = NewEvent;
           Socket[EventTotal]= Accept;
           EventTotal++;
           prinrt("Socket %d connect/n",Accept);
        }
        //Process FD_READ notification
        if (NetworkEvents.lNetworkEvents & FD_READ)
        {
            if (NetworkEvents.iErrorCode[FD_READ_BIT !=0])
            {
               //Error
               break;
            }

            //Read data from the socket
            recv(Socket[Index-WSA_WAIT_EVENT_0],buffer,sizeof(buffer),0);
        }
        //process FD_WRITE notitication
        if (NetworkEvents.lNetworkEvents & FD_WRITE)
        {
            if (NetworkEvents.iErrorCode[FD_WRITE_BIT] !=0)
            {
               //Error
               break;
            }
            send(Socket[Index-WSA_WAIT_EVENT_0],buffer,sizeof(buffer),0);
        }
        if (NetworkEvents.lNetworkEvents & FD_CLOSE)
        {
            if(NetworkEvents.iErrorCode[FD_CLOSE_BIT] !=0)
            {
                //Error
                break;
            }
            closesocket (Socket[Index-WSA_WAIT_EVENT_0]);
            //Remove socket and associated event from the Socket and Event arrays and
            //decrement eventTotal
            CompressArrays(Event,Socket,& EventTotal);
        }
    }
 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值