1. 程式人生 > >WinSocket模型的探討——完成埠模型

WinSocket模型的探討——完成埠模型


眾所皆知,完成埠是在WINDOWS平臺下效率最高,擴充套件性最好的IO模型,特別針對於WINSOCK的海量連線時,更能顯示出其威力。其實建立一個完成埠的伺服器也很簡單,只要注意幾個函式,瞭解一下關鍵的步驟也就行了。
這是篇完成埠入門級的文章,分為以下幾步來說明完成埠: 
函式
常見問題以及解答
步驟
例程
1、函式:
我們在完成埠模型下會使用到的最重要的兩個函式是:
CreateIoCompletionPort、GetQueuedCompletionStatus


CreateIoCompletionPort  的作用是建立一個完成埠和把一個IO控制代碼和完成埠關聯起來:


// 建立完成埠
HANDLE CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);


// 把一個IO控制代碼和完成埠關聯起來,這裡的控制代碼是一個socket 控制代碼
CreateIoCompletionPort((HANDLE)sClient, CompletionPort, (DWORD)PerHandleData, 0);
其中第一個引數是控制代碼,可以是檔案控制代碼、SOCKET控制代碼。
第二個就是我們上面創建出來的完成埠,這裡就把兩個東西關聯在一起了。
第三個引數很關鍵,叫做PerHandleData,就是對應於每個控制代碼的資料塊。我們可以使用這個引數在後面取到與這個SOCKET對應的資料。
最後一個引數給0,意思就是根據CPU的個數,允許儘可能多的執行緒併發執行。
GetQueuedCompletionStatus 的作用就是取得完成埠的結果:
// 從完成埠中取得結果
GetQueuedCompletionStatus(CompletionPort, &BytesTransferred, (LPDWORD)&PerHandleData, (LPOVERLAPPED*)&PerIoData, INFINITE)
第一個引數是完成埠
第二個引數是表明這次的操作傳遞了多少個位元組的資料
第三個引數是OUT型別的引數,就是前面CreateIoCompletionPort傳進去的單控制代碼資料,這裡就是前面的SOCKET控制代碼以及與之相對應的資料,這裡作業系統給我們返回,讓我們不用自己去做列表查詢等操作了。
第四個引數就是進行IO操作的結果,是我們在投遞 WSARecv / WSASend 等操作時傳遞進去的,這裡作業系統做好準備後,給我們返回了。非常省事!!
個人感覺完成埠就是作業系統為我們包裝了很多重疊IO的不爽的地方,讓我們可以更方便的去使用.


2、常見問題和解答
a、什麼是單控制代碼資料(PerHandle)和單IO資料(PerIO)
單控制代碼資料就是和控制代碼對應的資料,像socket控制代碼,檔案控制代碼這種東西。
單IO資料,就是對應於每次的IO操作的資料。例如每次的WSARecv/WSASend等等
其實我覺得PER是每次的意思,翻譯成每個控制代碼資料和每次IO資料還比較清晰一點。


在完成埠中,單控制代碼資料直接通過GetQueuedCompletionStatus 返回,省去了我們自己做容器去管理。單IO資料也容許我們自己擴充套件OVERLAPPED結構,所以,在這裡所有與應用邏輯有關的東西都可以在此擴充套件。
b、如何判斷客戶端的斷開
我們要處理幾種情況
1) 如果客戶端呼叫了closesocket,我們就可以這樣判斷他的斷開:
if(0 == GetQueuedCompletionStatus(CompletionPort, &BytesTransferred, 。。。)


if(BytesTransferred == 0)
{
    // 客戶端斷開,釋放資源
}
2) 如果是客戶端直接退出,那就會出現64錯誤,指定的網路名不可再用。這種情況我們也要處理的:
if(0 == GetQueuedCompletionStatus(。。。))
{
   if( (GetLastError() == WAIT_TIMEOUT) || (GetLastError() == ERROR_NETNAME_DELETED) )
   {
        // 客戶端斷開,釋放資源
   }
}
3、步驟
編寫完成埠服務程式,無非就是以下幾個步驟:
  1、建立一個完成埠
  2、根據CPU個數建立工作者執行緒,把完成埠傳進去執行緒裡
  3、建立偵聽SOCKET,把SOCKET和完成埠關聯起來
  4、建立PerIOData,向連線進來的SOCKET投遞WSARecv操作
  5、執行緒裡所做的事情:
 a、GetQueuedCompletionStatus,在退出的時候就可以使用PostQueudCompletionStatus使執行緒退出
 b、取得資料並處理
4、例程
下面是服務端的例程,可以使用《WinSocket模型的探討——Overlapped模型(一)》中的客戶端程式來測試次服務端。稍微研究一下,也就會對完成埠模型有個大概的瞭解了。
/*
   完成埠伺服器
   接收到客戶端的資訊,直接顯示出來
*/
#include "winerror.h"
#include "Winsock2.h"
#pragma comment(lib, "ws2_32")
#include "windows.h"


#include <iostream>
using namespace std;


/// 巨集定義
#define PORT 5050
#define DATA_BUFSIZE 8192
#define OutErr(a) cout << (a) << endl 
      << "出錯程式碼:" << WSAGetLastError() << endl 
      << "出錯檔案:" << __FILE__ << endl  
      << "出錯行數:" << __LINE__ << endl
#define OutMsg(a) cout << (a) << endl;


/// 全域性函式定義


///////////////////////////////////////////////////////////////////////
//
// 函式名       : InitWinsock
// 功能描述     : 初始化WINSOCK
// 返回值       : void 
//
///////////////////////////////////////////////////////////////////////
void InitWinsock()
{
 // 初始化WINSOCK
 WSADATA wsd;
 if( WSAStartup(MAKEWORD(2, 2), &wsd) != 0)
 
}
///////////////////////////////////////////////////////////////////////
//
// 函式名       : BindServerOverlapped
// 功能描述     : 繫結埠,並返回一個 Overlapped 的Listen Socket
// 引數         : int nPort
// 返回值       : SOCKET 
//
///////////////////////////////////////////////////////////////////////
SOCKET BindServerOverlapped(int nPort)
{
 // 建立socket 
 SOCKET sServer = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
 // 繫結埠
 struct sockaddr_in servAddr;
 servAddr.sin_family = AF_INET;
 servAddr.sin_port = htons(nPort);
 servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
 if(bind(sServer, (struct sockaddr *)&servAddr, sizeof(servAddr)) < 0)
 {
  OutErr("bind Failed!");
  return NULL;
 }
 // 設定監聽佇列為200
 if(listen(sServer, 200) != 0)
 {
  OutErr("listen Failed!");
  return NULL;
 }
 return sServer;
}


/// 結構體定義
typedef struct
{
   OVERLAPPED Overlapped;
   WSABUF DataBuf;
   CHAR Buffer[DATA_BUFSIZE];
} PER_IO_OPERATION_DATA, * LPPER_IO_OPERATION_DATA;


typedef struct 
{
   SOCKET Socket;
} PER_HANDLE_DATA, * LPPER_HANDLE_DATA;


DWORD WINAPI ProcessIO(LPVOID lpParam)
{
 HANDLE CompletionPort = (HANDLE)lpParam;
    DWORD BytesTransferred;
    LPPER_HANDLE_DATA PerHandleData;
    LPPER_IO_OPERATION_DATA PerIoData;
 while(true)
 {
 
  if(0 == GetQueuedCompletionStatus(CompletionPort, &BytesTransferred, (LPDWORD)&PerHandleData, (LPOVERLAPPED*)&PerIoData, INFINITE))
  {
   if( (GetLastError() == WAIT_TIMEOUT) || (GetLastError() == ERROR_NETNAME_DELETED) )
   {
    cout << "closing socket" << PerHandleData->Socket << endl;
    
    closesocket(PerHandleData->Socket);
    
    delete PerIoData;
    delete PerHandleData;
    continue;
   }
   else
   {
    OutErr("GetQueuedCompletionStatus failed!");
   }
   return 0;
  }
  
  // 說明客戶端已經退出
  if(BytesTransferred == 0)
  {
   cout << "closing socket" << PerHandleData->Socket << endl;
   closesocket(PerHandleData->Socket);
   delete PerIoData;
   delete PerHandleData;
   continue;
  }
  // 取得資料並處理
  cout << PerHandleData->Socket << "傳送過來的訊息:" << PerIoData->Buffer << endl;
  // 繼續向 socket 投遞WSARecv操作
  DWORD Flags = 0;
  DWORD dwRecv = 0;
  ZeroMemory(PerIoData, sizeof(PER_IO_OPERATION_DATA));
  PerIoData->DataBuf.buf = PerIoData->Buffer;
  PerIoData->DataBuf.len = DATA_BUFSIZE;
  WSARecv(PerHandleData->Socket, &PerIoData->DataBuf, 1, &dwRecv, &Flags, &PerIoData->Overlapped, NULL); 
 }
 return 0;
}
void main()
{
 InitWinsock();
 HANDLE CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
 // 根據系統的CPU來建立工作者執行緒
 SYSTEM_INFO SystemInfo;
 GetSystemInfo(&SystemInfo);
 for(int i = 0; i < SystemInfo.dwNumberOfProcessors * 2; i++)
 {
  HANDLE hProcessIO = CreateThread(NULL, 0, ProcessIO, CompletionPort, 0, NULL);
  if(hProcessIO)
  
 }
 // 建立偵聽SOCKET
 SOCKET sListen = BindServerOverlapped(PORT);


 SOCKET sClient;
 LPPER_HANDLE_DATA PerHandleData;
    LPPER_IO_OPERATION_DATA PerIoData;
 while(true)
 {
  // 等待客戶端接入
  //sClient = WSAAccept(sListen, NULL, NULL, NULL, 0);
  sClient = accept(sListen, 0, 0);
  
  cout << "Socket " << sClient << "連線進來" << endl;
  
  PerHandleData = new PER_HANDLE_DATA();
  PerHandleData->Socket = sClient;
  // 將接入的客戶端和完成埠聯絡起來
  CreateIoCompletionPort((HANDLE)sClient, CompletionPort, (DWORD)PerHandleData, 0);
  // 建立一個Overlapped,並使用這個Overlapped結構對socket投遞操作
  PerIoData = new PER_IO_OPERATION_DATA();
  
  ZeroMemory(PerIoData, sizeof(PER_IO_OPERATION_DATA));
  PerIoData->DataBuf.buf = PerIoData->Buffer;
  PerIoData->DataBuf.len = DATA_BUFSIZE;
  // 投遞一個WSARecv操作
  DWORD Flags = 0;
  DWORD dwRecv = 0;
  WSARecv(sClient, &PerIoData->DataBuf, 1, &dwRecv, &Flags, &PerIoData->Overlapped, NULL);
 }
 DWORD dwByteTrans;
 PostQueuedCompletionStatus(CompletionPort, dwByteTrans, 0, 0);
 closesocket(sListen);
}

相關推薦

WinSocket模型探討——完成模型

眾所皆知,完成埠是在WINDOWS平臺下效率最高,擴充套件性最好的IO模型,特別針對於WINSOCK的海量連線時,更能顯示出其威力。其實建立一個完成埠的伺服器也很簡單,只要注意幾個函式,瞭解一下關鍵的步驟也就行了。 這是篇完成埠入門級的文章,分為以下幾步來說明完成埠: 

 Windows socket之IO完成(IOCP)模型開發

       Windows socket之IO完成埠(IOCP)模型開發      IO完成埠是一種核心物件。利用完成埠,套接字應用程式能夠管理數百上千個套接字。應用程式建立完成埠物件後,通過指定一定數量的服務執行緒,為已經完成的重疊IO操作提供服務。該模型可以達到最後的系統性能。       完成埠是一種

YII2中自定義使用者認證模型完成登陸和註冊

有些時候我們需要自已定義使用者類,操作自已建的使用者表,來完成登陸和註冊功能。 使用者表結構如下,當然可以根據自已的需要新增或刪除: CREATE TABLE `tb_user` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '使

聚類系數可變無標度網絡模型Holme-Kim HK模型

turn eat ase -c pickle gles __name__ ets ood # -*- coding: cp936 -*- import random import networkx as nx from networkx.generators.classi

盒子模型與布局模型

add content 相對 html float border 偏移 mar tom 外邊距:margin 邊框:border 內填充:padding 內容:content 三種基本布局模型 流動模型:Flow html默認的就是流動模型 浮動模型:Float

IE盒模型和標準盒模型

模式 mes html註釋 註意 eset 最有 color ie版本 包括 標準盒模型和ie盒模型(怪異盒模型) w3c標準盒模型 width和height不包括padding和border ie盒模型 width和height

星型模型和雪花型模型比較

業務層 事務所 討論 解決方案 數據優化 特性 個數 數據庫結構 完整 每個數據倉庫都包含一個或者多個事實數據表。事實數據表可能包含業務銷售數據,如現金登記事務所產生的數據,事實數據表通常包含大量的行。事實數據表的主要特點是包含數字數據(事實),並且這些數字信息可以匯總,以

一.Windows I/O模型之選擇(select)模型

寫入 lap 調用 pla lose 結構 fd_set 關閉 監視 1.選擇(select)模型:選擇模型:通過一個fd_set集合管理套接字,在滿足套接字需求後,通知套接字。讓套接字進行工作。避免套接字進入阻塞模式,進行無謂的等待。選擇模型的核心的FD_SET集合和se

模型——標準盒模型與怪異盒模型

inter 所有 ges 讓我 圖片 生活 -s idt div2 盒模型是CSS中一種重要的思維模型,理解了盒模型才能進行更好的頁面布局。顧名思義,我們把頁面上所有的元素都看做是一個生活中常見的盒子,它具備內容(content),內邊距(padding),邊框(borde

Socket編程模型之完畢port模型

value result received 在那 void 系統性能 dcom 查詢 tails 轉載請註明來源:viewmode=contents">http://blog.csdn.net/caoshiying?vi

JavaScript的事件、DOM模型、事件流模型以及內置對象詳解(三)

dde function n) 事件冒泡 字符 nds rep == 防止 JS中的事件 JS中的事件分類   1.鼠標事件:     click/dbclick/mouseover/mouseout   2.HTML事件:     onload/onunload

# 運維小白的成長日記第三天-基礎網絡構建OSI七層模型與TCP/IP模型

網絡運維運維小白的成長日記第三天- 基礎網絡構建OSI七層模型與TCP/IP模型 網絡運維的小白和想要加入網絡運維的小夥伴們值得一看哦~今天是初識網絡運維的第三天。希望能有誌同道合的小夥伴一起討論和學習,也希望有網絡運維的大神能夠幫忙在網絡運維這條路上幫忙指點,能夠多提意見使我進步。 今天和大家分享一下基礎網

模型特征選擇:用簡單模型為復雜模型篩選特征

子集 最好 這樣的 需要 使用 意義 實體 簡單 任務 問題是這樣的: 好凡需要做一個命名實體識別(序列標註)的任務,按照他以往的經驗,用條件隨機場就可以達到預期的指標,眼下他精心設計了10個特征。 問題一:由於實驗室設備老舊,降低任務復雜度的工作非常有意義,那麽他該如何選

c理解提高(3)程式的記憶體四區模型和函式呼叫模型

程式的記憶體四區模型 記憶體四區的建立流程 流程說明 1、作業系統把物理硬碟程式碼load到記憶體 2、作業系統把c程式碼分成四個區 3、作業系統找到main函式入口執行   各區元素分析 函式呼叫模型 基本原理

JVM記憶體結構、Java記憶體模型以及Java物件模型之間的區別

Java作為一種面向物件的,跨平臺語言,其物件、記憶體等一直是比較難的知識點。而且很多概念的名稱看起來又那麼相似,很多人會傻傻分不清楚。比如本文我們要討論的JVM記憶體結構、Java記憶體模型和Java物件模型,這就是三個截然不同的概念,但是很多人容易弄混。 可以這樣說,很多高階開發甚至都搞

Linux環境下實現LVS-NAT模型和LVS-DR模型

一、實現LVS-NAT模型 1、LVS主機yum -y install ipvsadm 防火牆關閉,RS1、RS2安裝httpd,新增index.html,路由器新增到達172.20.0.123的路由表 2、各主機配置 RS1 並存在 RS2 並同時有 router,開

基於JVM原理、JMM模型和CPU快取模型深入理解Java併發程式設計

許多以Java多執行緒開發為主題的技術書籍,都會把對Java虛擬機器和Java記憶體模型的講解,作為講授Java併發程式設計開發的主要內容,有的還深入到計算機系統的記憶體、CPU、快取等予以說明。實際上,在實際的Java開發工作中,僅僅瞭解併發程式設計的建立、啟動、管理和通訊等基本知識還是不夠的。一

css的標準盒模型和怪異盒模型

大多數瀏覽器採用W3C標準模型,而IE中則採用Microsoft自己的標準。 在標準盒模型:一個div的內容區 = width + border +padding + margin 怪異盒模型:div的內容區 = width + margin   width = paddin

機器學習之判別式模型和生成式模型

https://www.cnblogs.com/nolonely/p/6435213.html   判別式模型(Discriminative Model)是直接對條件概率p(y|x;θ)建模。常見的判別式模型有線性迴歸模型、線性判別分析、支援向量機SVM、神經網路、boosting

使用mmdnn將caffe模型轉換為tensorflow模型

mmdnn是微軟推出的用於各個模型互轉的工具,支援主流框架,同類產品有onnx. 這兩個存在的共同問題是文件太舊,更新後很多命令不再適用 這裡給出mmdnn能用的命令 @echo off set PYTHONPATH=D:/CNN/caffe/python mmconvert -sf c