完成端口模型代码
最近要做一个网络方面的小东东,基于C/S模式的。都说IOCP可以使系统达到最佳的性能,因此我就比划了两下,献丑了。抄书开始。
从本质上说,完成端口模型要求创建一个windows完成端口对象,该对象通过指定数量的线程,对重叠I/O请求进行管理,以便为已经完成的重叠I/O请求提供服务。
首先要创建一个I/O完成端口对象,用它面向任意数量的套接字句柄,管理多个I/O请求。调用以下函数创建完成端口对象:
HANDLE CreateIoCompletionPort(
H ANDLE FileHandle,// 同IOCP关联在一起的套接字句柄
H ANDLE ExistingCompletionPort,// IOCP句柄
U LONG_PTR CompletionKey, // 完成健
D WORD NumberOfConcurrentThreads // 在IOCP上,同时允许执行的线程数量
);
该函数有两个作用:
(1)创建一个完成端口对象
(2)将一个句柄同完成端口关联到一起
然后就要创建一定数量的工作者线程,以便在套接字的I/O请求投递给完成端口后,为完成端口提供服务。写文字描述很烦,还是看代码吧:
//
NetServer3.cpp : Defines the entry point for the console application.
//
#include " stdafx.h "
#include " NetServer3.h "
#include < winsock2.h >
#pragma comment(lib, " ws2_32.lib " )
#include < iostream >
using namespace std;
/**/ //////////////////////////////////////////////////////////////////////////
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/**/ //////////////////////////////////////////////////////////////////////////
// 单句柄数据
typedef struct tagPER_HANDLE_DATA
{
SOCKET Socket;
SOCKADDR_STORAGE ClientAddr;
// 将和这个句柄关联的其他有用信息,尽管放在这里面吧
} PER_HANDLE_DATA, * LPPER_HANDLE_DATA;
// 但I/O操作数据
typedef struct tagPER_IO_DATA
{
OVERLAPPED Overlapped;
WSABUF DataBuf;
char buffer[1024];
int BufferLen;
int OperationType; // 可以作为读写的标志,为简单,我忽略了
} PER_IO_DATA, * LPPER_IO_DATA;
DWORD WINAPI ServerWorkerThread(LPVOID lpParam);
/**/ /////////////////////////////////////////////////////////////////////////////
// The one and only application object
CWinApp theApp;
using namespace std;
int _tmain( int argc, TCHAR * argv[], TCHAR * envp[])
{
int nRetCode = 0;
// initialize MFC and print and error on failure
if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
{
// TODO: change error code to suit your needs
cerr << _T("Fatal Error: MFC initialization failed") << endl;
nRetCode = 1;
}
else
{
// TODO: code your application's behavior here.
CString strHello;
strHello.LoadString(IDS_HELLO);
cout << (LPCTSTR)strHello << endl;
}
/**///////////////////////////////////////////////////////////////////////////
HANDLE CompletionPort;
WSADATA wsd;
SYSTEM_INFO SystemInfo;
SOCKADDR_IN InternetAddr;
SOCKET Listen;
// 加载WinSock2.2
WSAStartup(MAKEWORD(2, 2), &wsd);
// 1.创建一个I/O完成端口
CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE,
NULL,
0,
0);
// 2.确定系统中有多少个处理器
GetSystemInfo(&SystemInfo);
// 3.基于系统中可用的处理器数量创建工作器线程
for (int i = 0; i < SystemInfo.dwNumberOfProcessors; ++i)
{
HANDLE ThreadHandle;
// 创建一个服务器的工作器线程,并将完成端口传递到该线程
ThreadHandle = CreateThread(NULL,
0,
ServerWorkerThread,
CompletionPort,
0,
NULL);
CloseHandle(ThreadHandle);
}
// 4.创建一个监听套接字,以下的套路都是固定的。
Listen = WSASocket(AF_INET,
SOCK_STREAM,
0,
NULL,
0,
WSA_FLAG_OVERLAPPED);
InternetAddr.sin_family = PF_INET;
InternetAddr.sin_port = htons(5000);
InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(Listen, (SOCKADDR*)&InternetAddr, sizeof(InternetAddr));
listen(Listen, 5);
BOOL b = TRUE;
while (b)
{
PER_HANDLE_DATA * PerHandleData = NULL;
SOCKADDR_IN saRemote;
SOCKET Accept;
int RemoteLen;
// 5.接收连接,并分配完成端口,这儿可以用AcceptEx来代替,以创
// 建可伸缩的Winsock应用程序。
RemoteLen = sizeof(saRemote);
Accept = accept(Listen, (SOCKADDR*)&saRemote, &RemoteLen);
// 6.创建用来和套接字关联的单句柄数据信息结构
PerHandleData = (LPPER_HANDLE_DATA)GlobalAlloc(GPTR,
sizeof(PER_HANDLE_DATA));
cout << "Socket number " << Accept << " connected" << endl;
PerHandleData->Socket = Accept;
memcpy(&PerHandleData->ClientAddr, &saRemote, RemoteLen);
// 7.将接受套接字和完成端口关联起来
CreateIoCompletionPort((HANDLE)Accept,
CompletionPort,
(DWORD)PerHandleData,
0);
// 开始在接受套接字上处理I/O
// 使用重叠I/O机制,在新建的套接字上投递一个或多个异步
// WSARecv 或 WSASend请求。这些I/O请求完成后,工作者线程
// 会为I/O请求提供服务,之后就可以坐享其成了
static int const DATA_BUFSIZE = 4096; //
DWORD RecvBytes = 0;
DWORD Flags = 0;
// 单I/O操作数据
LPPER_IO_DATA PerIoData = NULL;
PerIoData = (LPPER_IO_DATA)GlobalAlloc(GPTR, sizeof(PER_IO_DATA));
ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED));
PerIoData->DataBuf.len = 1024;
PerIoData->DataBuf.buf = PerIoData->buffer;
PerIoData->OperationType = 0; // read
WSARecv(PerHandleData->Socket,
&(PerIoData->DataBuf),
1,
&RecvBytes,
&Flags,
&(PerIoData->Overlapped),
NULL);
}
/**///////////////////////////////////////////////////////////////////////////
return nRetCode;
}
/**/ //////////////////////////////////////////////////////////////////////////
DWORD WINAPI ServerWorkerThread(LPVOID lpParam)
{
HANDLE CompletionPort = (HANDLE)lpParam;
DWORD BytesTransferred;
LPOVERLAPPED lpOverlapped;
LPPER_HANDLE_DATA PerHandleData = NULL;
LPPER_IO_DATA PerIoData = NULL;
DWORD SendBytes;
DWORD RecvBytes;
DWORD Flags;
BOOL bRet = FALSE;
while (TRUE)
{
bRet = GetQueuedCompletionStatus(CompletionPort,
&BytesTransferred,
(PULONG_PTR)
&PerHandleData,
(LPOVERLAPPED*)
&lpOverlapped,
INFINITE);
// 检查成功的返回,这儿要注意使用这个宏CONTAINING_RECORD
PerIoData = (LPPER_IO_DATA)CONTAINING_RECORD(lpOverlapped,
PER_IO_DATA,
Overlapped);
// 先检查一下,看看是否在套接字上已有错误发生
if (0 == BytesTransferred)
{
closesocket(PerHandleData->Socket);
GlobalFree(PerHandleData);
GlobalFree(PerIoData);
continue;
}
// 数据处理
// 成功了!!!这儿就收到了来自客户端的数据
cout << PerIoData->DataBuf.buf << endl;
Flags = 0;
// 为下一个重叠调用建立单I/O操作数据
ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED));
PerIoData->DataBuf.len = 1024;
PerIoData->DataBuf.buf = PerIoData->buffer;
PerIoData->OperationType = 0; // read
WSARecv(PerHandleData->Socket,
&(PerIoData->DataBuf),
1,
&RecvBytes,
&Flags,
&(PerIoData->Overlapped),
NULL);
}
return 0;
}
/**/ //////////////////////////////////////////////////////////////////////////
//
#include " stdafx.h "
#include " NetServer3.h "
#include < winsock2.h >
#pragma comment(lib, " ws2_32.lib " )
#include < iostream >
using namespace std;
/**/ //////////////////////////////////////////////////////////////////////////
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/**/ //////////////////////////////////////////////////////////////////////////
// 单句柄数据
typedef struct tagPER_HANDLE_DATA
{
SOCKET Socket;
SOCKADDR_STORAGE ClientAddr;
// 将和这个句柄关联的其他有用信息,尽管放在这里面吧
} PER_HANDLE_DATA, * LPPER_HANDLE_DATA;
// 但I/O操作数据
typedef struct tagPER_IO_DATA
{
OVERLAPPED Overlapped;
WSABUF DataBuf;
char buffer[1024];
int BufferLen;
int OperationType; // 可以作为读写的标志,为简单,我忽略了
} PER_IO_DATA, * LPPER_IO_DATA;
DWORD WINAPI ServerWorkerThread(LPVOID lpParam);
/**/ /////////////////////////////////////////////////////////////////////////////
// The one and only application object
CWinApp theApp;
using namespace std;
int _tmain( int argc, TCHAR * argv[], TCHAR * envp[])
{
int nRetCode = 0;
// initialize MFC and print and error on failure
if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
{
// TODO: change error code to suit your needs
cerr << _T("Fatal Error: MFC initialization failed") << endl;
nRetCode = 1;
}
else
{
// TODO: code your application's behavior here.
CString strHello;
strHello.LoadString(IDS_HELLO);
cout << (LPCTSTR)strHello << endl;
}
/**///////////////////////////////////////////////////////////////////////////
HANDLE CompletionPort;
WSADATA wsd;
SYSTEM_INFO SystemInfo;
SOCKADDR_IN InternetAddr;
SOCKET Listen;
// 加载WinSock2.2
WSAStartup(MAKEWORD(2, 2), &wsd);
// 1.创建一个I/O完成端口
CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE,
NULL,
0,
0);
// 2.确定系统中有多少个处理器
GetSystemInfo(&SystemInfo);
// 3.基于系统中可用的处理器数量创建工作器线程
for (int i = 0; i < SystemInfo.dwNumberOfProcessors; ++i)
{
HANDLE ThreadHandle;
// 创建一个服务器的工作器线程,并将完成端口传递到该线程
ThreadHandle = CreateThread(NULL,
0,
ServerWorkerThread,
CompletionPort,
0,
NULL);
CloseHandle(ThreadHandle);
}
// 4.创建一个监听套接字,以下的套路都是固定的。
Listen = WSASocket(AF_INET,
SOCK_STREAM,
0,
NULL,
0,
WSA_FLAG_OVERLAPPED);
InternetAddr.sin_family = PF_INET;
InternetAddr.sin_port = htons(5000);
InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(Listen, (SOCKADDR*)&InternetAddr, sizeof(InternetAddr));
listen(Listen, 5);
BOOL b = TRUE;
while (b)
{
PER_HANDLE_DATA * PerHandleData = NULL;
SOCKADDR_IN saRemote;
SOCKET Accept;
int RemoteLen;
// 5.接收连接,并分配完成端口,这儿可以用AcceptEx来代替,以创
// 建可伸缩的Winsock应用程序。
RemoteLen = sizeof(saRemote);
Accept = accept(Listen, (SOCKADDR*)&saRemote, &RemoteLen);
// 6.创建用来和套接字关联的单句柄数据信息结构
PerHandleData = (LPPER_HANDLE_DATA)GlobalAlloc(GPTR,
sizeof(PER_HANDLE_DATA));
cout << "Socket number " << Accept << " connected" << endl;
PerHandleData->Socket = Accept;
memcpy(&PerHandleData->ClientAddr, &saRemote, RemoteLen);
// 7.将接受套接字和完成端口关联起来
CreateIoCompletionPort((HANDLE)Accept,
CompletionPort,
(DWORD)PerHandleData,
0);
// 开始在接受套接字上处理I/O
// 使用重叠I/O机制,在新建的套接字上投递一个或多个异步
// WSARecv 或 WSASend请求。这些I/O请求完成后,工作者线程
// 会为I/O请求提供服务,之后就可以坐享其成了
static int const DATA_BUFSIZE = 4096; //
DWORD RecvBytes = 0;
DWORD Flags = 0;
// 单I/O操作数据
LPPER_IO_DATA PerIoData = NULL;
PerIoData = (LPPER_IO_DATA)GlobalAlloc(GPTR, sizeof(PER_IO_DATA));
ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED));
PerIoData->DataBuf.len = 1024;
PerIoData->DataBuf.buf = PerIoData->buffer;
PerIoData->OperationType = 0; // read
WSARecv(PerHandleData->Socket,
&(PerIoData->DataBuf),
1,
&RecvBytes,
&Flags,
&(PerIoData->Overlapped),
NULL);
}
/**///////////////////////////////////////////////////////////////////////////
return nRetCode;
}
/**/ //////////////////////////////////////////////////////////////////////////
DWORD WINAPI ServerWorkerThread(LPVOID lpParam)
{
HANDLE CompletionPort = (HANDLE)lpParam;
DWORD BytesTransferred;
LPOVERLAPPED lpOverlapped;
LPPER_HANDLE_DATA PerHandleData = NULL;
LPPER_IO_DATA PerIoData = NULL;
DWORD SendBytes;
DWORD RecvBytes;
DWORD Flags;
BOOL bRet = FALSE;
while (TRUE)
{
bRet = GetQueuedCompletionStatus(CompletionPort,
&BytesTransferred,
(PULONG_PTR)
&PerHandleData,
(LPOVERLAPPED*)
&lpOverlapped,
INFINITE);
// 检查成功的返回,这儿要注意使用这个宏CONTAINING_RECORD
PerIoData = (LPPER_IO_DATA)CONTAINING_RECORD(lpOverlapped,
PER_IO_DATA,
Overlapped);
// 先检查一下,看看是否在套接字上已有错误发生
if (0 == BytesTransferred)
{
closesocket(PerHandleData->Socket);
GlobalFree(PerHandleData);
GlobalFree(PerIoData);
continue;
}
// 数据处理
// 成功了!!!这儿就收到了来自客户端的数据
cout << PerIoData->DataBuf.buf << endl;
Flags = 0;
// 为下一个重叠调用建立单I/O操作数据
ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED));
PerIoData->DataBuf.len = 1024;
PerIoData->DataBuf.buf = PerIoData->buffer;
PerIoData->OperationType = 0; // read
WSARecv(PerHandleData->Socket,
&(PerIoData->DataBuf),
1,
&RecvBytes,
&Flags,
&(PerIoData->Overlapped),
NULL);
}
return 0;
}
/**/ //////////////////////////////////////////////////////////////////////////
当然为了测试,各种异常处理都没有写,大家不要学我哦。