WSAEventSelect模型編程 詳解

轉自:http://blog.csdn.net/wangjieest/article/details/7042108

WSAEventSelect模型編程     

WSAEventSelect模型編程
這個模型是一個簡單的異步事件模型,使用起來比較方便,如今說一下其的具體的用法和須要注意的地方。
一,模型的例程(服務端):
先舉一個王豔平網絡通訊上的例子:ios

[cpp]  view plain copy print ?
 
  1. //////////////////////////////////////////////////  
  2. // WSAEventSelect文件  
  3.   
  4. #include "initsock.h"  
  5. #include <stdio.h>  
  6. #include <iostream.h>  
  7. #include <windows.h>  
  8.   
  9. // 初始化Winsock庫  
  10. CInitSock theSock;  
  11.   
  12. int main()  
  13. {  
  14.  // 事件句柄和套節字句柄表  
  15.  WSAEVENT eventArray[WSA_MAXIMUM_WAIT_EVENTS];  
  16.  SOCKET  sockArray[WSA_MAXIMUM_WAIT_EVENTS];  
  17.  int nEventTotal = 0;  
  18.   
  19.  USHORT nPort = 4567; // 此服務器監聽的端口號  
  20.   
  21.  // 建立監聽套節字  
  22.  SOCKET sListen = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);   
  23.  sockaddr_in sin;  
  24.  sin.sin_family = AF_INET;  
  25.  sin.sin_port = htons(nPort);  
  26.  sin.sin_addr.S_un.S_addr = INADDR_ANY;  
  27.  if(::bind(sListen, (sockaddr*)&sin, sizeof(sin)) == SOCKET_ERROR)  
  28.  {  
  29.   printf(" Failed bind() \n");  
  30.   return -1;  
  31.  }  
  32.  ::listen(sListen, 5);  
  33.   
  34.  // 建立事件對象,並關聯到新的套節字  
  35.  WSAEVENT event = ::WSACreateEvent();  
  36.  ::WSAEventSelect(sListen, event, FD_ACCEPT|FD_CLOSE);  
  37.  // 添加到表中  
  38.  eventArray[nEventTotal] = event;  
  39.  sockArray[nEventTotal] = sListen;   
  40.  nEventTotal++;  
  41.   
  42.  // 處理網絡事件  
  43.  while(TRUE)  
  44.  {  
  45.   // 在全部事件對象上等待  
  46.   int nIndex = ::WSAWaitForMultipleEvents(nEventTotal, eventArray, FALSE, WSA_INFINITE, FALSE);  
  47.   // 對每一個事件調用WSAWaitForMultipleEvents函數,以便肯定它的狀態  
  48.   nIndex = nIndex - WSA_WAIT_EVENT_0;  
  49.   for(int i=nIndex; i<nEventTotal; i++)  
  50.   {  
  51.    nIndex = ::WSAWaitForMultipleEvents(1, &eventArray[i], TRUE, 1000, FALSE);  
  52.    if(nIndex == WSA_WAIT_FAILED || nIndex == WSA_WAIT_TIMEOUT)  
  53.    {  
  54.     continue;  
  55.    }  
  56.    else  
  57.    {  
  58.     // 獲取到來的通知消息,WSAEnumNetworkEvents函數會自動重置受信事件  
  59.     WSANETWORKEVENTS event;  
  60.     ::WSAEnumNetworkEvents(sockArray[i], eventArray[i], &event);  
  61.     if(event.lNetworkEvents & FD_ACCEPT)    // 處理FD_ACCEPT通知消息  
  62.     {  
  63.      if(event.iErrorCode[FD_ACCEPT_BIT] == 0)  
  64.      {  
  65.       if(nEventTotal > WSA_MAXIMUM_WAIT_EVENTS)  
  66.       {  
  67.        printf(" Too many connections! \n");  
  68.        continue;  
  69.       }  
  70.       SOCKET sNew = ::accept(sockArray[i], NULL, NULL);  
  71.       WSAEVENT event = ::WSACreateEvent();  
  72.       ::WSAEventSelect(sNew, event, FD_READ|FD_CLOSE|FD_WRITE);  
  73.       // 添加到表中  
  74.       eventArray[nEventTotal] = event;  
  75.       sockArray[nEventTotal] = sNew;   
  76.       nEventTotal++;  
  77.      }  
  78.     }  
  79.     else if(event.lNetworkEvents & FD_READ)   // 處理FD_READ通知消息  
  80.     {  
  81.      if(event.iErrorCode[FD_READ_BIT] == 0)  
  82.      {  
  83.       char szText[256];  
  84.       int nRecv = ::recv(sockArray[i], szText, strlen(szText), 0);  
  85.       if(nRecv > 0)      
  86.       {  
  87.        szText[nRecv] = '\0';  
  88.        printf("接收到數據:%s \n", szText);  
  89.       }  
  90.      }  
  91.     }  
  92.     else if(event.lNetworkEvents & FD_CLOSE)  // 處理FD_CLOSE通知消息  
  93.     {  
  94.      if(event.iErrorCode[FD_CLOSE_BIT] == 0)  
  95.      {  
  96.       ::closesocket(sockArray[i]);  
  97.       for(int j=i; j<nEventTotal-1; j++)  
  98.       {  
  99.        sockArray[j] = sockArray[j+1];  
  100.        sockArray[j] = sockArray[j+1]; //這個是個BUG,應爲:   eventArray[j] = eventArray[j+1];還真沒注意,直到同事提      起才注意到。  
  101.       }  
  102.       nEventTotal--;  
  103.      }  
  104.     }  
  105.     else if(event.lNetworkEvents & FD_WRITE)  // 處理FD_WRITE通知消息  
  106.     {  
  107.     }  
  108.    }  
  109.   }  
  110.  }  
  111.  return 0;  
  112. }  

2、例程的分析
一、事件的建立和綁定
前面的一些設置咱們略過,從WSAEVENT 開始提及,跟蹤發如今winsock2.h中有以下定義:
#define WSAEVENT                HANDLE
這個事件說明是一個句柄,咱們知道在事件中有兩種狀態,一種是手動處理事件,一種是自動的,這裏使用WSACreateEvent()這個函數建立返回的事件句柄,正常的返回的狀況下,其建立的是一個手工處理的句柄,不然,其返回WSA_INVALID_EVENT,代表建立未成功,若是須要知道更多的信息WSAGetLastError()這個函數來獲得具體的信息出錯代碼。這裏埋伏下了一個雷,爲何建立的是手工處理的事件(manually reset ),那後面爲何沒有WSAResetEvent()這個函數來處理事件,先記下。
而後接着講,編程

[cpp]  view plain copy print ?
 
  1. ::WSAEventSelect(sListen, event, FD_ACCEPT|FD_CLOSE);  
  2. // 添加到表中  
  3. eventArray[nEventTotal] = event;  
  4. sockArray[nEventTotal] = sListen;   
  5. nEventTotal++;  

將事件綁定到監聽的套接字上,這裏咱們只對這個套接字的接收和關閉兩個消息有興趣,因此只監聽這兩個消息,那別的讀寫啥的呢,不要急,慢慢向下看。eventArray和sockArray,定義的是WSA_MAXIMUM_WAIT_EVENTS大小,而在頭文件中#define WSA_MAXIMUM_WAIT_EVENTS (MAXIMUM_WAIT_OBJECTS), 後者被定義成64,這也是須要注意的一點,這個模型單線程只能處理最多64個事件,再多就只能用多線程了,不過,這裏重點說明一下,這個模型即便你使用多線程, 最多也只能處理1200個左右的處理量(正常狀況),不然,會形成整個程序的性能降低,至於怎麼降低,還真沒有真正的測試,只是從書上和資料上看是這麼講的。
接着原來,程序而後進入了死循環,在這個循環裏,由於是簡單的使用嘛,因此不少的異常並無進行控制,可是爲了說明用法,就得簡單一些不是麼?
二、事件的監聽和控制處理
2.1 事件的監聽
[cpp]  view plain copy print ?
 
  1. int nIndex = ::WSAWaitForMultipleEvents(nEventTotal, eventArray, FALSE, WSA_INFINITE, FALSE);  
  2. nIndex = nIndex - WSA_WAIT_EVENT_0;  

先說這個索引爲何要減去WSA_WAIT_EVENT_0這個值,由於事件的起始值在內核中是進行定義了的,不過,在這裏這個東西最終定義仍然是0。而後咱們看這個函數
::WSAWaitForMultipleEvents(nEventTotal, eventArray, FALSE, WSA_INFINITE, FALSE),

這個函數用來監聽多個事件(就是上面咱們綁定的事件)的狀態,有狀態或者是事件被觸發,就會返回,不然會按照你設置的參數進行操做。
前面兩個參數,第一個是監聽的數量,最小是一,MSDN上有,第二是一個事件的數組,第三個是精彩的去處,若是設置成TRUE,那麼只有這第二個事件數組中的全部的事件都受信或者說觸發,纔會動做,若是是FALSE呢,則只要有一個就能夠動做。第五個是超時設置,能夠是0,是WSA_INFINITE,也能夠是其它的數值,這裏有一個問題,若是設置爲0會形成程序的CPU佔用率太高,WSA_INFINITE則可能會出如今等待數量爲一個字時,且第三個參數設置爲TRUE,產生死套接字的長期阻塞。因此仍是設置成一個經驗值爲好,至於這個經驗值是多少,看你的程序的具體的應用了
其實這個函數本質仍是調用WaitForMulipleObjectsEx這個函數,MSDN上講WSAEventSelect模型在等待時不佔用CPU時間,就是這個緣由,因此其比阻塞的SOCKET通訊要效率高不少,其實那個消息的模型WSAAsycSelect和這個事件的模型也差很少,殊途同歸之妙吧。不過適用範圍是有區別的,這個能夠用在WINCE上。消息則不行。
這裏就又引出一個注意點,在這個模型裏,若是同時有幾個事件受信,或者說觸發,那麼nIndex = ::WSAWaitForMultipleEvents()只返回最前面的一個事件,那麼怎麼解決其後面的呢,書上有曰:屢次循環調用這個就能夠了,因此纔會引出下面的再次在for循環裏調用
nIndex = ::WSAWaitForMultipleEvents(1, &eventArray[i], TRUE, 1000, FALSE);
注意這裏參數的變化,數量爲1,事件爲[i],但事件會不斷的增加,全面受信改爲了TRUE,超時爲1000,最後的這個參數在這裏只能設置成FALSE,具體爲何查MSDN去。
若是這裏咱們處理的很差,若是把1000改爲無限等待的話,就能夠出現上面說的死套接字的無限阻塞,也就是說若是一個套接字死掉了,你沒有在事件隊伍裏刪除他,那麼他就會一直在這兒阻塞,即便後面有事件也沒法獲得響應,可是,若是你的套接字只有一個鏈接的話,就沒有什麼了,能夠改爲無限等待。不過,最好仍是別這樣,由於若是你處理一個失誤,就會產生死的套接字(好比重連,但你沒有刪除先前無用的套接字)。
用兩個::WSAWaitForMultipleEvents函數,windows

一個用來處理監聽多個事件數組,一個用來遍歷每一個數組事件,數組

防止出現丟失響應的現象,因此其參數的設置是不一樣的,必定要引發注意。服務器

2.2事件的處理網絡

而後戲又來了,上面說的讀寫監聽呢,就在這裏出現了,包括上面埋伏下的一個雷,也在這裏處理了:多線程

首先調用::WSAEnumNetworkEvents(sockArray[i], eventArray[i], &event),把上面的雷給拆了,併發

::WSAEnumNetworkEvents會自動重置事件app

而後獲得事件的索引或者說ID,框架

[cpp]  view plain copy print ?
 
  1. if(event.lNetworkEvents & FD_ACCEPT)    // 處理FD_ACCEPT通知消息  
  2. {  
  3.  if(event.iErrorCode[FD_ACCEPT_BIT] == 0)  
  4.  {  
  5.   if(nEventTotal > WSA_MAXIMUM_WAIT_EVENTS)  
  6.   {  
  7.    printf(" Too many connections! \n");  
  8.    continue;  
  9.   }  
  10.   SOCKET sNew = ::accept(sockArray[i], NULL, NULL);  
  11.   WSAEVENT event = ::WSACreateEvent();  
  12.   ::WSAEventSelect(sNew, event, FD_READ|FD_CLOSE|FD_WRITE);  
  13.   // 添加到表中  
  14.   eventArray[nEventTotal] = event;  
  15.   sockArray[nEventTotal] = sNew;   
  16.   nEventTotal++;  
  17.  }  
  18. }  


代碼裏從新調用了事件建立和事件綁定函數,而且將兩個數組自動增大,最最重要的是咱們終於看到了,FD_READ|FD_CLOSE|FD_WRITE

明白了吧,這個簡單的程序的本質實際上是將 讀 寫 和 接收關閉 的套接字混合到了一塊兒

而在後面的服務器例程裏,咱們發現,這個已經拆開,而且從新手動設置受信的事件,調用了::ResetEvent(event)。這樣不就完美的拆除了上面的雷麼。


2.3 其它處理方法
當程序繼續循環到最外層時,::WSAWaitForMultipleEvents無限等待全部的事件,只要有一個事件響應,就會進入到下一層循環,若是是接收就重複上述的動做,若是是讀寫就進入:

[cpp]  view plain copy print ?
 
  1. else if(event.lNetworkEvents & FD_READ)   // 處理FD_READ通知消息  
  2. {  
  3.  if(event.iErrorCode[FD_READ_BIT] == 0)  
  4.  {  
  5.   char szText[256];  
  6.   int nRecv = ::recv(sockArray[i], szText, strlen(szText), 0);  
  7.   if(nRecv > 0)      
  8.   {  
  9.    szText[nRecv] = '\0';  
  10.    printf("接收到數據:%s \n", szText);  
  11.   }  
  12.  }  
  13. }  
  14. else if(event.lNetworkEvents & FD_CLOSE)  // 處理FD_CLOSE通知消息  
  15. {  
  16.  if(event.iErrorCode[FD_CLOSE_BIT] == 0)  
  17.  {  
  18.   ::closesocket(sockArray[i]);  
  19.   for(int j=i; j<nEventTotal-1; j++)  
  20.   {  
  21.    sockArray[j] = sockArray[j+1];  
  22.    sockArray[j] = sockArray[j+1];   
  23.   }  
  24.   nEventTotal--;  
  25.  }  
  26. }  
  27. else if(event.lNetworkEvents & FD_WRITE)  // 處理FD_WRITE通知消息  
  28. {  
  29. }  

如此往復,不就達到了不斷接收鏈接和處理數據的問題麼。
這裏還重複一下,網上不少程序都沒有處理多個事件同時受信的狀況,在網上和各類資料中,也有的只使用一個::WSAWaitForMultipleEvents函數,但參數的設置得從新來過,並且得當心的處理各類的事件和異常的發生。可能在小併發量和小數據量時沒有問題,但併發一多數據一大,可能會出現丟數據的問題,沒有作過測試,但多是很大的。不然不會說遍歷調用這個函數了。

2.4 FD_WRITE 事件的觸發

這裏得羅嗦兩句FD_WRITE 事件的觸發,前面的都好理解,主要是啥時候兒會觸發這個事件呢,咱們在一開始只對接收和關閉進行了監聽,爲何沒有這個FD_WRITE事件的

監聽呢,

這就引出了下面的東東:(從一個網友那轉來)

下面是MSDN中對FD_WRITE觸發機制的解釋:

The FD_WRITE network event is handled slightly differently. An FD_WRITE network event is recorded when a socket is first connected with connect/WSAConnect or

accepted with accept/WSAAccept, and then after a send fails with WSAEWOULDBLOCK and buffer space becomes available. Therefore, an application can assume that

sends are possible starting from the first FD_WRITE network event setting and lasting until a send returns WSAEWOULDBLOCK. After such a failure the

application will find out that sends are again possible when an FD_WRITE network event is recorded and the associated event object is set

FD_WRITE事件只有在如下三種狀況下才會觸發

①client 經過connect(WSAConnect)首次和server創建鏈接時,在client端會觸發FD_WRITE事件

②server經過accept(WSAAccept)接受client鏈接請求時,在server端會觸發FD_WRITE事件

③send(WSASend)/sendto(WSASendTo)發送失敗返回WSAEWOULDBLOCK,而且當緩衝區有可用空間時,則會觸發FD_WRITE事件

①②實際上是同一種狀況,在第一次創建鏈接時,C/S端都會觸發一個FD_WRITE事件。

主要是③這種狀況:send出去的數據其實都先存在winsock的發送緩衝區中,而後才發送出去,若是緩衝區滿了,那麼再調用send(WSASend,sendto,WSASendTo)的話,就會返回一個 WSAEWOULDBLOCK的錯誤碼,接下來隨着發送緩衝區中的數據被髮送出去,緩衝區中出現可用空間時,一個 FD_WRITE 事件纔會被觸發,這裏比較容易混淆的是 FD_WRITE 觸發的前提是 緩衝區要先被充滿而後隨着數據的發送又出現可用空間,而不是緩衝區中有可用空間,也就是說像以下的調用方式可能出現問題

[cpp]  view plain copy print ?
 
  1. else if(event.lNetworkEvents & FD_WRITE)  
  2. {  
  3.     if(event.iErrorCode[FD_WRITE_BIT] == 0)  
  4.      {  
  5.          send(g_sockArray[nIndex], buffer, buffersize);  
  6.          ....  
  7.      }  
  8.      else  
  9.      {  
  10.      }  
  11. }  

問題在於創建鏈接後 FD_WRITE 第一次被觸發, 若是send發送的數據不足以充滿緩衝區,雖然緩衝區中仍有空閒空間,可是 FD_WRITE 不會再被觸發,程序永遠也等不到能夠發送的網絡事件。

基於以上緣由,在收到FD_WRITE事件時,程序就用循環或線程不停的send數據,直至send返回WSAEWOULDBLOCK,代表緩衝區已滿,再退出循環或線程。

當緩衝區中又有新的空閒空間時,FD_WRITE 事件又被觸發,程序被通知後又可發送數據了。

上面代碼片斷中省略的對 FD_WRITE 事件處理

[cpp]  view plain copy print ?
 
  1. else if(event.lNetworkEvents & FD_WRITE)  
  2. {  
  3.     if(event.iErrorCode[FD_WRITE_BIT] == 0)  
  4.      {  
  5.         while(TRUE)  
  6.          {  
  7.             // 獲得要發送的buffer,能夠是用戶的輸入,從文件中讀取等  
  8.              GetBuffer....  
  9.             if(send(g_sockArray[nIndex], buffer, buffersize, 0) == SOCKET_ERROR)  
  10.              {  
  11.                 // 發送緩衝區已滿  
  12.                 if(WSAGetLastError() == WSAEWOULDBLOCK)  
  13.                     break;  
  14.                  else  
  15.                      ErrorHandle...  
  16.              }  
  17.          }  
  18.      }  
  19.      else  
  20.      {  
  21.          ErrorHandle..  
  22.         break;  
  23.      }  
  24. }  

若是你不是大數據量的不斷的發送數據,建議你忽略這個事件,畢竟緩衝區不是很容易被弄滿的,結果就是你的發送事件沒法完成。

2.5異常的處理

主要是0個鏈接時,處理CPU的佔用率的問題,以及在多於64個事件時的監聽處理問題。並且包括上面講的,沒有雙循環時的多事件同時受信的問題。
 
2.6 多線程服務端

這個你們能夠看王豔平的書,說得很清楚,須要注意的是在他的主服務程序裏,使用的是int nRet = ::WaitForSingleObject(event, 5*1000);
因此下面要手動的從新對事件進行設置,不然這個事件就再沒法監聽獲得了。

其它的難度主要是面向對象的設計封裝要弄明白,若是這個弄明白知道封裝SOCKET和THREAD結構體的目的是什麼,再照着書上看就不會有錯了,
但提醒一點, 線程結構體中的第一個事件是重建事件,不要和其它的監聽事件弄混了。

若是作一個介於書上兩種代碼間的小框架,能夠用一個線程來監聽ACCEPT和CLOSE事件,另外的線程監聽小於64個的讀寫等事件,通常的小的SOCKET通訊應該就沒有什麼問題了。重要的是你要把這個服務端封裝好,有時間作一下。

3、例程(客戶端)

先上一段代碼:
[cpp]  view plain copy print ?
 
  1. DWORD WINAPI Connect(LPVOID lpParam)  
  2. {  
  3.  //////////////////第1步:初始化,建立,鏈接套接字//////////////////  
  4.  WSADATA WsaData;int err;  
  5.  err = WSAStartup (0x0002, &WsaData);if(err!=0) return 1;    //0x0002表明版本2.0  
  6.  socket_client=socket(AF_INET,SOCK_STREAM,0);  
  7.  if(socket_client==INVALID_SOCKET){AfxMessageBox("建立套接字錯誤!\n");return 1;}  
  8.   
  9.  SOCKADDR_IN sconnect_pass;  
  10.  sconnect_pass.sin_family=AF_INET;  
  11.  sconnect_pass.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");  
  12.  sconnect_pass.sin_port=htons(55551);  
  13.   
  14.  if (SOCKET_ERROR==connect(socket_client,(SOCKADDR*)&sconnect_pass,sizeof(SOCKADDR)))  
  15.  {  
  16.   AfxMessageBox("鏈接服務端錯誤\n");  
  17.   return 1;  
  18.  }  
  19.  else  
  20.  {  
  21.   //將套接口s置於」非阻塞模式「  
  22.   u_long u1=1;//0爲保持默認的阻塞,非0表示改成非阻塞  
  23.   ioctlsocket(socket_client,FIONBIO,(u_long*)&u1);  
  24.   //--------------①建立事件對象-----------------  
  25.   WSAEVENT ClientEvent=WSACreateEvent();  
  26.   if (ClientEvent==WSA_INVALID_EVENT)  
  27.   {  
  28. #ifdef _DEBUG    
  29.    ::OutputDebugString("建立事件錯誤!\n");  
  30. #endif // _DEBUG  
  31.    AfxMessageBox("WSACreateEvent() Failed,Error=【%d】\n");  
  32.    return 1;  
  33.   }  
  34.   //--------------②網絡事件註冊------------  
  35.   int WESerror=WSAEventSelect(socket_client,ClientEvent,FD_READ|FD_CLOSE);  
  36.   if (WESerror==INVALID_SOCKET)  
  37.   {  
  38. #ifdef _DEBUG    
  39.    ::OutputDebugString("網絡事件註冊錯誤!\n");  
  40. #endif // _DEBUG  
  41.    AfxMessageBox("WSAEventSelect() Failed,Error=【%d】\n");  
  42.    return -1;  
  43.   }  
  44.   //-----------準備工做---------------  
  45.   //WSAWaitForMultipleEvents只能等待64個事件,若想更多,則建立額外的工做線程  
  46.   SOCKET sockArray[WSA_MAXIMUM_WAIT_EVENTS]; WSAEVENT eventArray[WSA_MAXIMUM_WAIT_EVENTS];  
  47.   int nEventCount = 0;  
  48.   sockArray[0]=socket_client; eventArray[nEventCount]=ClientEvent;   
  49.   nEventCount++;//事件個數+1,第1次等待1個事件,注意WSAWaitForMultipleEvents的參數1是動態  
  50.   int t=1;//超時次數  
  51.   //------------循環處理-------------  
  52.   while (1)  
  53.   {  
  54.    //---------------⑦等待事件對象--------------  
  55.    int nIndex=WSAWaitForMultipleEvents(nEventCount,eventArray,FALSE,40000,FALSE);//參數1:注意是動態增減的,不能固定死 .注:參數1與2本質同樣,但數值不同.若是參  
  56.   
  57. 數1爲1個,那麼數組括號內[]爲0  
  58.    //參數3:參數1中的任何一個有消息進來,都馬上中止阻塞,運行下一步操做  
  59.    AfxMessageBox("響應事件,進入下一步\n");//進來時爲0,響應時爲對應的數組標籤號  
  60.    if (nIndex==WSA_WAIT_FAILED)//------7.1調用失敗---------  
  61.    {  
  62.     AfxMessageBox("WSAEventSelect調用失敗\n");  
  63.     break;//退出while(1)循環  
  64.    }  
  65.    else if (nIndex==WSA_WAIT_TIMEOUT)//-------7.2超時---------  
  66.    {  
  67.     if (t<3)  
  68.     {  
  69.      AfxMessageBox("第【%d】次超時\n");  
  70.      t++;  
  71.      continue;  
  72.     }  
  73.     else  
  74.     {  
  75.      AfxMessageBox("第【%d】次超時,退出\n");  
  76.      break;  
  77.     }  
  78.    }  
  79.    //---------------7.3網絡事件觸發事件對象句柄的工做狀態--------  
  80.    else  
  81.    {  
  82.     WSANETWORKEVENTS event;//該結構記錄網絡事件和對應出錯代碼  
  83.     //---------⑧網絡事件查詢-----------  
  84.     WSAEnumNetworkEvents(sockArray[nIndex-WSA_WAIT_EVENT_0],NULL,&event);  
  85.     WSAResetEvent(eventArray[nIndex-WSA_WAIT_EVENT_0]);  
  86.     if (event.lNetworkEvents&FD_READ)    //-------8.2處理FD_READ通知消息  
  87.     {  
  88.      if (event.iErrorCode[FD_READ_BIT]==0)  
  89.      {  
  90.       char m_RecvBuffer[4096];  
  91.       PCMD_HEADER pcm = (PCMD_HEADER)m_RecvBuffer;  
  92.       if(recv(sockArray[nIndex-WSA_WAIT_EVENT_0],(char*)&m_RecvBuffer,sizeof(m_RecvBuffer),0)==SOCKET_ERROR)  
  93.       {  
  94.        AfxMessageBox("接收失敗,退出重recv接收!");     
  95.        break;  
  96.       }  
  97.       else   
  98.       {      
  99.        switch ( pcm->ncmd )  
  100.        {  
  101.        case CMD_AS_REP_C_MACHINE_LOGIN://很明顯這個pcm->ncmd,是登陸包中ncmd標識符  
  102.         {           
  103.          PAREP_C_MACHINE_LOGIN cmd = (PAREP_C_MACHINE_LOGIN)pcm;  
  104.          if (cmd->nStatus==1)  
  105.          {  
  106.           AfxMessageBox("收到登陸回覆包(Client->Server)狀態:成功!");           
  107.          }  
  108.          else  
  109.          {  
  110.           AfxMessageBox("收到登陸回覆包(Client->Server)狀態:失敗!");   
  111.          }  
  112.         }  
  113.         break;  
  114.        }  
  115.       }  
  116.      }  
  117.     }  
  118.     else if (event.lNetworkEvents&FD_CLOSE)  //---------8.3處理FD_CLOSE通知消息  
  119.     {  
  120.      if (event.iErrorCode[FD_CLOSE_BIT]==0)                                       //客戶端正常關閉  
  121.      {  
  122.       closesocket(sockArray[nIndex-WSA_WAIT_EVENT_0]);  
  123.       WSACloseEvent(eventArray[nIndex-WSA_WAIT_EVENT_0]);  
  124.       AfxMessageBox("套接字已關閉鏈接\n");//注:會觸發7.1調用失敗  
  125.      }  
  126.      else                                                                         //客戶端異常已關閉  
  127.      {  
  128.       if (event.iErrorCode[FD_CLOSE_BIT]==10053)//右鍵->轉到定義,能夠查看到不少錯誤標識.按需設置(此處僅設置了客戶端沒有通知服務端,就非法關閉了)  
  129.       {  
  130.        closesocket(sockArray[nIndex-WSA_WAIT_EVENT_0]);  
  131.        WSACloseEvent(eventArray[nIndex-WSA_WAIT_EVENT_0]);  
  132.        AfxMessageBox("服務端非法關閉鏈接\n");//注:會觸發7.1調用失敗  
  133.       }  
  134.      }  
  135.      for (int j=nIndex-WSA_WAIT_EVENT_0;j<nEventCount-1;j++)  
  136.      {  
  137.       sockArray[j]=sockArray[j+1];  
  138.       eventArray[j]=eventArray[j+1];  
  139.      }  
  140.      nEventCount--;   
  141.     }  
  142.    }// end 網絡事件觸發  
  143.   }//end while  
  144.   //////////////////////////////////////////////////////////////////////////  
  145.  }  
  146.  AfxMessageBox("服務端已退出.客戶端退出中\n");  
  147.  closesocket(socket_client);  
  148.  WSACleanup();  
  149.  return 0;  
  150. }  
  151. void CMyDlg::OnBnClickedButtonRun()  
  152. {  
  153.  //發包  
  154.  C_MACHINE_LOGIN_SYSTEM cmd;  
  155.  strcpy(cmd.sMachineCode,"20100904164702750199");//機器碼  
  156.  CString str;  
  157.  str.Format("%d",cmd.nVersion);  
  158.  if(send(socket_client,(char*)&cmd,sizeof(cmd),0)==SOCKET_ERROR)  
  159.  {  
  160. #ifdef _DEBUG    
  161.   ::OutputDebugString("發送失敗:發送機器碼!\n");  
  162. #endif // _DEBUG  
  163.  }  
  164. }  

這裏就再也不進行詳細的分析,比照服務端,這裏會更簡單,須要說明的是,在這裏可使用WSAConnect這個函數來達到鏈接的目的,不用使用這個東西,固然,若是這樣的話,你的發送和接收都要使用WSARecv和 WSASend函數。主要是使用overloapped重疊IO,使用起來更簡單明瞭。

相關文章
相關標籤/搜索