完成端口学习笔记

“完成端口”模型是迄今为止最为复杂的一种I / O模型。然而,假若一个应用程序同时需要管理为数众多的套接字,那么采用这种模型,往往可以达到最佳的系统性能!但不幸的是,该模型只适用于Windows NT和Windows 2000操作系统。因其设计的复杂性,只有在你的应用程序需要同时管理数百乃至上千个套接字的时候,而且希望随着系统内安装的C P U数量的增多,应用程序的性能也可以线性提升,才应考虑采用“完成端口”模型。

从本质上说,完成端口模型要求我们创建一个Wi n 3 2完成端口对象,通过指定数量的线程,对重叠I / O请求进行管理,以便为已经完成的重叠I / O请求提供服务。要注意的是,所谓“完成端口”,实际是Wi n 3 2、Windows NT以及Windows 2000采用的一种I / O构造机制,除套接字句柄之外,实际上还可接受其他东西

 首先要创建一个I / O完成端口对象,用它面向任意数量的套接字句柄,管理多个I / O请求。要做到这一点,需要调用C r e a t e C o m p l e t i o n P o r t函数。

HANDLE CreateIoCompletionPort(

HANDLE FileHandle,

HANDLE ExistingCompletionPort,

DWORD CompletionKey,

DWORD NumberOfConcurrentThreads

);

NumberOfConcurrentThreads 并发线程数目,如果设置为0则表示有多少个CPU就建立多少个线程

成功创建一个完成端口后,便可开始将套接字句柄与对象关联到一起。但在关联套接字
之前,首先必须创建一个或多个“工作者线程”,以便在I / O请求投递给完成端口对象后,为
完成端口提供服务。建立线程的数目与CPU的数目和工作线程是否阻塞有关;

调用CreateIo CompletionPort函数,同时为前三个参数—F i l e H a n d l e,E x i s t i n g C o m p l e t i o n P o r t和C o m p l e t i o n K e y—提供套接字的信息。其中, F i l e H a n d l e参数指定一个要同完成端口关联在一起的套接字句柄。E x i s t i n g C o m p l e t i o n P o r t参数指定的是一个现有的完成端口。C o m p l e t i o n K e y(完成键)参数则指定要与某个特定套接字句柄关联在一起的“单句柄数据”;在这个参数中,应用程序可保存与一个套接字对应的任意类型的信息,工作线程最后可以得到这个参数,从而获得套节字的信息;

将套接字句柄与一个完成端口关联在一起后,便可以套接字句柄为基础,投递发送与接收请求,开始对I / O请求的处理,完成情况都可以从关联的完全端口句柄中得到,从本质上说,完成端口模型利用了Wi n 3 2重叠I / O机制。G e t Q u e u e d C o m p l e t i o n S t a t u s函数,让一个或多个工作者线程在完成端口上等待

BOOL GetQueuedCompletionStatus(

HANDLE CompletionPort,

LPDWORD lpNumberOfBytesTransferred,

LPDWORD lpCompletionKey,

LPOVERLAPPED* lpOverlapped,

DWORD dwMilliseconds

);

C o m p l e t i o n P o r t参数对应于要在上面等待的完成端口句柄。l p N u m b e r O f B y t e sTr a n s f e r r e d参数负责在完成了一次I / O操作后(如W S A S e n d或W S A R e c v),接收实际传输的字节数。l p C o m p l e t i o n K e y参数为原先传递进入C r e a t e C o m p l e t i o n P o r t函数的套接字返回“单句柄数据”l p O v e r l a p p e d参数用于接收完成的I / O操作的重叠结果d w M i l l i s e c o n d s,用于指定调用者希望等待一个完成数据包在完成端口上出现的时间。假如将其设为I N F I N I T E,调用会无休止地等待下去。其中单句柄数据是自己定义的,绑定套节字的时候投递的,可以自定义;

关闭时应注意,调用c l o s e s o c k e t函数,任何尚未进行的重叠I / O操作都会完成。一旦所有套接字句柄都已关闭,便需在完成端口上, 终止所有工作者线程的运行。需要使用P o s t Q u e u e d C o m p l e t i o n S t a t u s函数,向每个工作者线程都发送一个特殊的完成数据包。

BOOL PostQueuedCompletionStatus(

HANDLE CompletionPort,

DWORD dwNumberOfBytesTransferred,

DWORD dwCompletionKey,

LPOVERLAPPED lpOVerlapped

);

C o m p l e t i o n P o r t参数指定想向其发送一个完成数据包的完成端口对象。而就d w N u m b e r O f B y t e s Tr a n s f e r r e d、d w C o m p l e t i o n K e y和l p O v e r l a p p e d这三个参数来说,每一个都允许我们指定任意一个自定义值,直接传递给G e t Q u e u e d C o m p l e t i o n S t a t u s函数中对应的参数。例如,可用d w C o m p l e t i o n P o r t参数传递0值,而一个工作者线程会将其解释成中止指令。一旦所有工作者线程都已关闭,便可使用
C l o s e H a n d l e函数,关闭完成端口,最终安全退出程序。

测试用:

// newDlg.cpp : implementation file
//

#include "stdafx.h"
#include "winsock2.h"
#include "new.h"
#include "newDlg.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif


/
// CAboutDlg dialog used for App About

struct _OVERLAPPELUS
{
    SOCKET      socket;                   
    char        InBuffer[DATA_BUFSIZE];   // 输入缓冲
    OVERLAPPED  ovIn;        
    BOOL         flags;     //收发标志
}OVERLAPPELUS, *LPOVERLAPPELUS;

void WINAPI workthread(PVOID lparam);
void WINAPI lsthread(PVOID lparam);

class CAboutDlg : public CDialog
{
public:
 CAboutDlg();

// Dialog Data
 //{{AFX_DATA(CAboutDlg)
 enum { IDD = IDD_ABOUTBOX };
 //}}AFX_DATA

 // ClassWizard generated virtual function overrides
 //{{AFX_VIRTUAL(CAboutDlg)
 protected:
 virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
 //}}AFX_VIRTUAL

// Implementation
protected:
 //{{AFX_MSG(CAboutDlg)
 //}}AFX_MSG
 DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
 //{{AFX_DATA_INIT(CAboutDlg)
 //}}AFX_DATA_INIT
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
 CDialog::DoDataExchange(pDX);
 //{{AFX_DATA_MAP(CAboutDlg)
 //}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
 //{{AFX_MSG_MAP(CAboutDlg)
  // No message handlers
 //}}AFX_MSG_MAP
END_MESSAGE_MAP()

/
// CNewDlg dialog

CNewDlg::CNewDlg(CWnd* pParent /*=NULL*/)
 : CDialog(CNewDlg::IDD, pParent)
{
 //{{AFX_DATA_INIT(CNewDlg)
  // NOTE: the ClassWizard will add member initialization here
 //}}AFX_DATA_INIT
 // Note that LoadIcon does not require a subsequent DestroyIcon in Win32
 m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CNewDlg::DoDataExchange(CDataExchange* pDX)
{
 CDialog::DoDataExchange(pDX);
 //{{AFX_DATA_MAP(CNewDlg)
  // NOTE: the ClassWizard will add DDX and DDV calls here
 //}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CNewDlg, CDialog)
 //{{AFX_MSG_MAP(CNewDlg)
 ON_WM_SYSCOMMAND()
 ON_WM_PAINT()
 ON_WM_QUERYDRAGICON()
 //}}AFX_MSG_MAP
END_MESSAGE_MAP()

/
// CNewDlg message handlers

BOOL CNewDlg::OnInitDialog()
{
 CDialog::OnInitDialog();

 // Add "About..." menu item to system menu.

 // IDM_ABOUTBOX must be in the system command range.
 ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
 ASSERT(IDM_ABOUTBOX < 0xF000);

 CMenu* pSysMenu = GetSystemMenu(FALSE);
 if (pSysMenu != NULL)
 {
  CString strAboutMenu;
  strAboutMenu.LoadString(IDS_ABOUTBOX);
  if (!strAboutMenu.IsEmpty())
  {
   pSysMenu->AppendMenu(MF_SEPARATOR);
   pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
  }
 }
 DWORD threadid=0;
 WSADATA wsadata;
 WSAStartup(MAKEWORD(2,2),&wsadata); //初始化SOCKET环境
 // Set the icon for this dialog.  The framework does this automatically
 //  when the application's main window is not a dialog
 SetIcon(m_hIcon, TRUE);   // Set big icon
 SetIcon(m_hIcon, FALSE);  // Set small icon
 // TODO: Add extra initialization here
 listener=WSASocket(AF_INET,SOCK_STREAM,0,NULL,0,WSA_FLAG_OVERLAPPED);
 SOCKADDR_IN socketaddr;
 memset(&socketaddr,0,sizeof(socketaddr));
 socketaddr.sin_addr.s_addr=htonl(ADDR_ANY);
 socketaddr.sin_port=htons(8888);
 socketaddr.sin_family=AF_INET;
 bind(listener,(PSOCKADDR)&socketaddr,sizeof(socketaddr));
 listen(listener,5);
 //开始监听端口,很标准哦,用的是异步方式
 CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)lsthread,NULL,0,&threadid); //接受连接的线程,因为会
 return TRUE;  // return TRUE  unless you set the focus to a control
}

void CNewDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
 if ((nID & 0xFFF0) == IDM_ABOUTBOX)
 {
  CAboutDlg dlgAbout;
  dlgAbout.DoModal();
 }
 else
 {
  CDialog::OnSysCommand(nID, lParam);
 }
}

// If you add a minimize button to your dialog, you will need the code below
//  to draw the icon.  For MFC applications using the document/view model,
//  this is automatically done for you by the framework.

void CNewDlg::OnPaint()
{
 if (IsIconic())
 {
  CPaintDC dc(this); // device context for painting

  SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);

  // Center icon in client rectangle
  int cxIcon = GetSystemMetrics(SM_CXICON);
  int cyIcon = GetSystemMetrics(SM_CYICON);
  CRect rect;
  GetClientRect(&rect);
  int x = (rect.Width() - cxIcon + 1) / 2;
  int y = (rect.Height() - cyIcon + 1) / 2;

  // Draw the icon
  dc.DrawIcon(x, y, m_hIcon);
 }
 else
 {
  CDialog::OnPaint();
 }
}

// The system calls this to obtain the cursor to display while the user drags
//  the minimized window.
HCURSOR CNewDlg::OnQueryDragIcon()
{
 return (HCURSOR) m_hIcon;
}

void WINAPI lsthread(PVOID lparam)
{
 while (1)
 {
  SOCKET newsock;
  HANDLE compleport;
  newsock=WSASocket(AF_INET,SOCK_STREAM,0,NULL,0,WSA_FLAG_OVERLAPPED);
  DWORD threadid=0;
  _OVERLAPPELUS *pkey=new _OVERLAPPELUS; //单句柄数据结构
  CNewDlg *app=(CNewDlg*)AfxGetApp()->GetMainWnd();
  newsock=WSAAccept(app->listener,NULL,NULL,NULL,0);
  compleport=CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,0,0); //建立完成端口句柄
  CreateIoCompletionPort((HANDLE)newsock,compleport,(DWORD&)pkey,0);
  //将新建的完成句柄与套节字绑定,发生在这个套节字上的时间都会通过这个完成句柄捕获
  app->RetWSArecv(newsock,(PVOID)pkey,TRUE);
  CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)workthread,(LPVOID)compleport,0,&threadid);
  //为每个完成端口建立工作线程,捕获时间,处理数据
 }
 
}

//工作线程用postqueuedcompletionstatus()退出
void WINAPI workthread(LPVOID lparam)
{
 long i=0;
 HANDLE compleport=(HANDLE)lparam;
 BOOL bresult=FALSE;
 DWORD dwnums=0;
 _OVERLAPPELUS *pkey=NULL;
 LPOVERLAPPED lpoverlapped=NULL;
 CNewDlg *app=(CNewDlg*)AfxGetApp()->GetMainWnd();
 while (1)
 {
  bresult=GetQueuedCompletionStatus(compleport,&dwnums,(LPDWORD)&pkey,&lpoverlapped,INFINITE);
  //完成端口上的事件捕获,每个完成端口对应一个处理的线程
  if (dwnums==0)
  {
   AfxMessageBox("用户退出");
   closesocket(pkey->socket);
   if (pkey!=NULL)
   {
    delete pkey;
   }
  }
  else if(bresult==TRUE&&dwnums!=0) //处理接收好的数据
  {
   app->RetWSArecv(pkey->socket,(PVOID)pkey,TRUE); //重新投递消息
   //这时接收来的数据就在pkey->InBuffer中,数据大小是dwnums
  }
  else
  {
   AfxMessageBox("未知错误");//-b-
  }
 }
 
}

void CNewDlg::RetWSArecv(SOCKET s,PVOID key,BOOL Bflags)
{
 _OVERLAPPELUS *pkey=(_OVERLAPPELUS*)key;
 WSABUF wsabuf;
 DWORD recvbytes=0;
 DWORD flags=0;
 pkey->socket=s;
 pkey->ovIn.hEvent=CreateEvent(NULL,NULL,FALSE,NULL);
 memset(pkey->InBuffer,0,sizeof(pkey->InBuffer));
 ZeroMemory(&pkey->ovIn,sizeof(OVERLAPPED));
 wsabuf.buf=pkey->InBuffer;
 wsabuf.len=DATA_BUFSIZE;
 pkey->flags=Bflags;
 //这个东东我还没想好干吗,瞎写的测试,一会可以被接收区分不同的东西用
 WSARecv(pkey->socket,&wsabuf,1,&recvbytes,&flags,&pkey->ovIn,NULL); //抛出事件
}

你可能感兴趣的:(编程相关,null,winapi,dialog,initialization,工作,socket)