“完成端口”模型是迄今为止最为复杂的一种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); //抛出事件
}