乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      非阻塞模式WinSock編程入門

       orion360doc 2010-12-29
      非阻塞模式WinSock編程入門

       

      介紹

      WinSock是Windows提供的包含了一系列網(wǎng)絡(luò)編程接口的套接字程序庫。在這篇文章中,我們將介紹如何把它的非阻塞模式引入到應(yīng)用程序中。文章中所討論的通信均為面向連接的通信(TCP),為清晰起見,文章對代碼中的一些細枝末節(jié)進行了刪減,大家可以依照文末的鏈接下載完整的工程源碼來獲取這部分內(nèi)容。

       

      阻塞模式WinSock

               下述偽代碼給出了阻塞模式下WinSock的使用方式。

      view plaincopy to clipboardprint?
      //---------------------------------------   
      // 服務(wù)器   
      //----------------------------------------   
      // WinSock初始化   
      WSAStartup();   
        
      // 創(chuàng)建服務(wù)器套接字   
      SOCKET server = socket();   
        
      // 綁定到本機端口   
      bind(server);    
        
      // 開始監(jiān)聽   
      listen(server);    
        
      // 接收到客戶端連接,分配一個客戶端套接字   
      SOCKET client = accept(server);    
        
      // 使用新分配的客戶端套接字進行消息收發(fā)   
      send(client);    
      recv(client);   
        
      // 關(guān)閉客戶端套接字   
      closesocket(client);    
        
      // 關(guān)閉服務(wù)器套接字   
      closesocket(server);   
        
      // 卸載WinSock   
      WSACleanup();  
      //---------------------------------------
      // 服務(wù)器
      //----------------------------------------
      // WinSock初始化
      WSAStartup();

      // 創(chuàng)建服務(wù)器套接字
      SOCKET server = socket();

      // 綁定到本機端口
      bind(server); 

      // 開始監(jiān)聽
      listen(server); 

      // 接收到客戶端連接,分配一個客戶端套接字
      SOCKET client = accept(server); 

      // 使用新分配的客戶端套接字進行消息收發(fā)
      send(client); 
      recv(client);

      // 關(guān)閉客戶端套接字
      closesocket(client); 

      // 關(guān)閉服務(wù)器套接字
      closesocket(server);

      // 卸載WinSock
      WSACleanup();  

      view plaincopy to clipboardprint?
      //---------------------------------------   
      // 客戶端   
      //---------------------------------------   
      WSAStartup();   
        
      // 創(chuàng)建客戶端套接字   
      SOCKET client = socket();   
        
      // 綁定本機端口   
      bind(client);   
        
      // 連接到服務(wù)器   
      ServerAddress server;   
      connect(client, server);   
        
      // 確立連接后收發(fā)消息   
      recv(client);   
      send(client);   
        
      // 關(guān)閉客戶端套接字   
      closesocket(client);   
        
      WSACleanup();  
      //---------------------------------------
      // 客戶端
      //---------------------------------------
      WSAStartup();

      // 創(chuàng)建客戶端套接字
      SOCKET client = socket();

      // 綁定本機端口
      bind(client);

      // 連接到服務(wù)器
      ServerAddress server;
      connect(client, server);

      // 確立連接后收發(fā)消息
      recv(client);
      send(client);

      // 關(guān)閉客戶端套接字
      closesocket(client);

      WSACleanup();  

               代碼中,服務(wù)器端的accept(),客戶端的connect(),以及服務(wù)器和客戶端中共同的recv()、send()函數(shù)均會產(chǎn)生阻塞。

      服務(wù)器在調(diào)用accept()后不會返回,直到接收到客戶端的連接請求;

      客戶端在調(diào)用connect()后不會返回,直到對服務(wù)器連接成功或者失??;

      服務(wù)器和客戶端在調(diào)用recv()后不會返回,直到接收到并讀取完一條消息;

      服務(wù)器和客戶端在調(diào)用send()后不會返回,直到發(fā)送完待發(fā)送的消息。

      如果這兩段代碼被放在Windows程序的主線程中,你會發(fā)現(xiàn)消息循環(huán)被阻塞,程序不再響應(yīng)用戶輸入及重繪請求。為了解決這個問題,你可能會想到開辟另外一個線程來運行這些代碼。這是可行的,但是考慮到每個SOCKET都不應(yīng)該被其他SOCKET的操作所阻塞,是不是需要為每個SOCKET開辟一個線程?再考慮到同一SOCKET的一個讀寫操作也不應(yīng)該被另外一個讀寫操作所阻塞,是不是應(yīng)該再為每個SOCKET的讀和寫分別開辟一個線程?一般來說,這種自實現(xiàn)的多線程解決方案帶來的諸多線程管理方面的問題,是你絕對不會想要遇到的。

       

      非阻塞模式WinSock

               所幸的是,WinSock同時提供了非阻塞模式,并提出了幾種I/O模型。最常見的I/O模型有select模型、WSAAsyncSelect模型及WSAEventSelect模型,下面選擇其中的WSAAsyncSelect模型進行介紹。

               使用WSAAsyncSelect模型將非阻塞模式引入到應(yīng)用程序中的過程看起來很簡單,事實上你只需要多添加一個函數(shù)就夠了。

      int WSAAsyncSelect(SOCKET s, HWND hWnd, unsigned int wMsg, long lEvent);

      該函數(shù)會自動將套接字設(shè)置為非阻塞模式,并且把發(fā)生在該套接字上且是你所感興趣的事件,以Windows消息的形式發(fā)送到指定的窗口,你需要做的就是在傳統(tǒng)的消息處理函數(shù)中處理這些事件。參數(shù)hWnd表示指定接受消息的窗口句柄;參數(shù)wMsg表示消息碼值(這意味著你需要自定義一個Windows消息碼);參數(shù)IEvent表示你希望接受的網(wǎng)絡(luò)事件的集合,它可以是如下值的任意組合:

      FD_READ, FD_WRITE, FD_OOB, FD_ACCEPT, FD_CONNECT, FD_CLOSE

               之后,就可以在我們熟知的Windows消息處理函數(shù)中處理這些事件。如果在某一套接字s上發(fā)生了一個已命名的網(wǎng)絡(luò)事件,應(yīng)用程序窗口hWnd會接收到消息wMsg。參數(shù)wParam即為該事件相關(guān)的套接字s;參數(shù)lParam的低字段指明了發(fā)生的網(wǎng)絡(luò)事件,lParam的高字段則含有一個錯誤碼,事件和錯誤碼可以通過下面的宏從lParam中取出:

      #define WSAGETSELECTEVENT(lParam) LOWORD(lParam)

      #define WSAGETSELECTERROR(lParam) HIWORD(lParam)

       

      下面繼續(xù)使用偽代碼來幫助闡述如何將上一節(jié)的阻塞模式WinSock應(yīng)用升級到非阻塞模式。

      首先自定義一個Windows消息碼,用于標(biāo)識我們的網(wǎng)絡(luò)消息。

      view plaincopy to clipboardprint?
      #define WM_CUSTOM_NETWORK_MSG (WM_USER + 100)  
      #define WM_CUSTOM_NETWORK_MSG (WM_USER + 100) 

       

      服務(wù)器端,在監(jiān)聽之前,將監(jiān)聽套接字置為非阻塞模式,并且標(biāo)明其感興趣的事件為FD_ACCEPT。

      view plaincopy to clipboardprint?
      …   
      WSAAsyncSelect(server, wnd, WM_CUSTOM_NETWORK_MSG, FD_ACCEPT);   
        
      // 開始監(jiān)聽   
      listen(server);  
      WSAAsyncSelect(server, wnd, WM_CUSTOM_NETWORK_MSG, FD_ACCEPT);

      // 開始監(jiān)聽
      listen(server);  

      客戶端,在連接之前,將套接字置為非阻塞模式,并標(biāo)明其感興趣的事件為FD_CONNECT。

      view plaincopy to clipboardprint?
      …   
      WSAAsyncSelect(client, wnd, WM_CUSTOM_NETWORK_MSG, FD_CONNECT);   
        
      // 連接到服務(wù)器   
      ServerAddress server;   
      connect(client, server);  
      WSAAsyncSelect(client, wnd, WM_CUSTOM_NETWORK_MSG, FD_CONNECT);

      // 連接到服務(wù)器
      ServerAddress server;
      connect(client, server);  

      接著,在Windows消息處理函數(shù)中,我們將處理監(jiān)聽事件、連接事件、及讀寫事件,方便起見,這里將服務(wù)器和客戶端的處理代碼放在了一起。

      view plaincopy to clipboardprint?
      LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)   
      {   
          switch (message)   
          {   
          …   
          case WM_CUSTOM_NETWORK_MSG: // 自定義的網(wǎng)絡(luò)消息碼   
              {   
                  SOCKET socket = (SOCKET)wParam; // 發(fā)生網(wǎng)絡(luò)事件的套接字   
                  long event = WSAGETSELECTEVENT(lParam); // 事件   
                  int error = WSAGETSELECTERROR(lParam); // 錯誤碼   
        
                  switch (event)   
                  {   
                  case FD_ACCEPT: // 服務(wù)器收到新客戶端的連接請求   
                      {   
                          // 接收到客戶端連接,分配一個客戶端套接字   
                          SOCKET client = accept(socket);    
                          // 將新分配的客戶端套接字置為非阻塞模式,并標(biāo)明其感興趣的事件為讀、寫及關(guān)閉   
                          WSAAsyncSelect(client, hWnd, message, FD_READ | FD_WRITE | FD_CLOSE);   
                      }   
                      break;   
                  case FD_CONNECT: // 客戶端連接到服務(wù)器的操作返回結(jié)果   
                      {   
                          // 成功連接到服務(wù)器,將客戶端套接字置為非阻塞模式,并標(biāo)明其感興趣的事件為讀、寫及關(guān)閉   
                          WSAAsyncSelect(socket, hWnd, message, FD_READ | FD_WRITE | FD_CLOSE);   
                      }   
                      break;   
                  case FD_READ: // 收到網(wǎng)絡(luò)包,需要讀取   
                      {   
                          // 使用套接字讀取網(wǎng)絡(luò)包   
                          recv(socket);   
                      }   
                      break;   
                  case FD_WRITE:   
                      {   
                          // FD_WRITE的處理后面會具體討論   
                      }   
                      break;   
                  case FD_CLOSE: // 套接字的連接方(而非本地socket)關(guān)閉消息   
                      {   
                      }   
                      break;   
                  default:   
                      break;   
                  }   
              }   
              break;   
          …   
          }   
          …   
      }  
      LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
      {
      switch (message)
      {
      case WM_CUSTOM_NETWORK_MSG: // 自定義的網(wǎng)絡(luò)消息碼
      {
      SOCKET socket = (SOCKET)wParam; // 發(fā)生網(wǎng)絡(luò)事件的套接字
      long event = WSAGETSELECTEVENT(lParam); // 事件
      int error = WSAGETSELECTERROR(lParam); // 錯誤碼

      switch (event)
      {
      case FD_ACCEPT: // 服務(wù)器收到新客戶端的連接請求
      {
      // 接收到客戶端連接,分配一個客戶端套接字
      SOCKET client = accept(socket); 
      // 將新分配的客戶端套接字置為非阻塞模式,并標(biāo)明其感興趣的事件為讀、寫及關(guān)閉
      WSAAsyncSelect(client, hWnd, message, FD_READ | FD_WRITE | FD_CLOSE);
      }
      break;
      case FD_CONNECT: // 客戶端連接到服務(wù)器的操作返回結(jié)果
      {
      // 成功連接到服務(wù)器,將客戶端套接字置為非阻塞模式,并標(biāo)明其感興趣的事件為讀、寫及關(guān)閉
      WSAAsyncSelect(socket, hWnd, message, FD_READ | FD_WRITE | FD_CLOSE);
      }
      break;
      case FD_READ: // 收到網(wǎng)絡(luò)包,需要讀取
      {
      // 使用套接字讀取網(wǎng)絡(luò)包
      recv(socket);
      }
      break;
      case FD_WRITE:
      {
      // FD_WRITE的處理后面會具體討論
      }
      break;
      case FD_CLOSE: // 套接字的連接方(而非本地socket)關(guān)閉消息
      {
      }
      break;
      default:
      break;
      }
      }
      break;
      }
      }  

      以上就是非阻塞模式WinSock的應(yīng)用框架,WSAAsyncSelect模型將套接字和Windows消息機制很好地粘合在一起,為用戶異步SOCKET應(yīng)用提供了一種較優(yōu)雅的解決方案。

       

      擴展討論

               WinSock在系統(tǒng)底層為套接字收發(fā)網(wǎng)絡(luò)數(shù)據(jù)各提供一個緩沖區(qū),接收到的網(wǎng)絡(luò)數(shù)據(jù)會緩存在這里等待應(yīng)用程序讀取,待發(fā)送的網(wǎng)絡(luò)數(shù)據(jù)也會先寫進這里之后通過網(wǎng)絡(luò)發(fā)送。

      相關(guān)的,針對FD_READ和FD_WRITE事件的讀寫處理,因涉及的內(nèi)容稍微復(fù)雜而容易使人困惑,這里需要特別進行討論。

               在FD_READ事件中,使用recv()函數(shù)讀取網(wǎng)絡(luò)包數(shù)據(jù)時,由于事先并不知道完整網(wǎng)絡(luò)包的大小,所以需要多次讀取直到讀完整個緩沖區(qū)。這就需要類似如下代碼的調(diào)用:

      view plaincopy to clipboardprint?
      void* buf = 0;   
      int size = 0;   
      while (true)   
      {   
          char tmp[128];   
          int bytes = recv(socket, tmp, 128, 0);   
          if (bytes <= 0)   
              break;   
          else  
          {   
              int new_size = size + bytes;   
              buf = realloc(buf, new_size);   
              memcpy((void*)(((char*)buf) + size), tmp, bytes);   
              size = new_size;   
          }   
      }   
      // 此時數(shù)據(jù)已經(jīng)從緩沖區(qū)全部拷貝到buf中,你可以在這里對buf做一些操作   
      …   
      free(buf);  
      void* buf = 0;
      int size = 0;
      while (true)
      {
      char tmp[128];
      int bytes = recv(socket, tmp, 128, 0);
      if (bytes <= 0)
      break;
      else
      {
      int new_size = size + bytes;
      buf = realloc(buf, new_size);
      memcpy((void*)(((char*)buf) + size), tmp, bytes);
      size = new_size;
      }
      }
      // 此時數(shù)據(jù)已經(jīng)從緩沖區(qū)全部拷貝到buf中,你可以在這里對buf做一些操作
      free(buf);
        

               這一切看起來都沒有什么問題,但是如果程序運行起來,你會收到比預(yù)期多出許多的FD_READ事件。如MSDN所述,正常的情況下,應(yīng)用程序應(yīng)當(dāng)為每一個FD_READ消息僅調(diào)用一次recv()函數(shù)。如果一個應(yīng)用程序需要在一個FD_READ事件處理中調(diào)用多次recv(),那么它將會收到多個FD_READ消息,因為每次未讀完緩沖區(qū)的recv()調(diào)用,都會重新觸發(fā)一個FD_READ消息。針對這種情況,我們需要在讀取網(wǎng)絡(luò)包前關(guān)閉掉FD_READ消息通知,讀取完這后再進行恢復(fù),關(guān)閉FD_READ消息的方法很簡單,只需要調(diào)用WSAAsyncSelect時參數(shù)lEvent中FD_READ字段不予設(shè)置即可。

      view plaincopy to clipboardprint?
      // 關(guān)閉FD_READ事件通知   
      WSAAsyncSelect(socket, hWnd, message, FD_WRITE | FD_CLOSE);   
      // 讀取網(wǎng)絡(luò)包   
      …   
      // 再次打開FD_READ事件通知   
      WSAAsyncSelect(socket, hWnd, message, FD_WRITE | FD_CLOSE | FD_READ);  
      // 關(guān)閉FD_READ事件通知
      WSAAsyncSelect(socket, hWnd, message, FD_WRITE | FD_CLOSE);
      // 讀取網(wǎng)絡(luò)包
      // 再次打開FD_READ事件通知
      WSAAsyncSelect(socket, hWnd, message, FD_WRITE | FD_CLOSE | FD_READ);  

               第二個需要討論的是FD_WRITE事件。這個事件指明緩沖區(qū)已經(jīng)準(zhǔn)備就緒,有了多出的空位可以讓應(yīng)用程序?qū)懭霐?shù)據(jù)以供發(fā)送。該事件僅在兩種情況下被觸發(fā):

      1. 套接字剛建立連接時,表明準(zhǔn)備就緒可以立即發(fā)送數(shù)據(jù)。

      2. 一次失敗的send()調(diào)用后緩沖區(qū)再次可用時。如果系統(tǒng)緩沖區(qū)已經(jīng)被填滿,那么此時調(diào)用send()發(fā)送數(shù)據(jù),將返回SOCKET_ERROR,使用WSAGetLastError()會得到錯誤碼WSAEWOULDBLOCK表明被阻塞。這種情況下當(dāng)緩沖區(qū)重新整理出可用空間后,會向應(yīng)用程序發(fā)送FD_WRITE消息,示意其可以繼續(xù)發(fā)送數(shù)據(jù)了。

      所以說收到FD_WRITE消息并不單純地等同于這是使用send()的唯一時機。一般來說,如果需要發(fā)送消息,直接調(diào)用send()發(fā)送即可。如果該次調(diào)用返回值為SOCKET_ERROR且WSAGetLastError()得到錯誤碼WSAEWOULDBLOCK,這意味著緩沖區(qū)已滿暫時無法發(fā)送,此刻我們需要將待發(fā)數(shù)據(jù)保存起來,等到系統(tǒng)發(fā)出FD_WRITE消息后嘗試重新發(fā)送。也就是說,你需要針對FD_WRITE構(gòu)建一套數(shù)據(jù)重發(fā)的機制,文末的工程源碼里包含有這套機制以供大家參考,這里不再贅述。

       

      結(jié)語

               至此,如何在非阻塞模式下使用WinSock進行編程介紹完畢,這個框架可以滿足大多數(shù)網(wǎng)絡(luò)游戲客戶端及部分服務(wù)器的通信需求。更多應(yīng)用層面上的問題(如TCP粘包等)這里沒有討論,或許會在以后的文章中給出。

               文章相關(guān)工程源碼請移步此處下載http://download.csdn.net/source/2852485。該源碼展示了采用非阻塞模式編程的服務(wù)器和客戶端,建立連接后,在服務(wù)器窗口輸入空格會向所有客戶端發(fā)送一條字符串消息。源碼中對網(wǎng)絡(luò)通信部分做了簡單封裝,所以代碼結(jié)構(gòu)會和文中的偽代碼稍有不同。

      謝謝您的閱讀!

       



      本文來自CSDN博客,轉(zhuǎn)載請標(biāo)明出處:http://blog.csdn.net/trcj1/archive/2010/11/23/6029163.aspx

        本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
        轉(zhuǎn)藏 分享 獻花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多