最近在業余時間折騰了一下IOCP,IOCP模型在windows平台上網絡通信這塊效率還是蠻高的,所以特別是對游戲服務端開發來說,至少要對IOCP有一定的了解吧!!
發下代碼,希望看到的大鳥們,幫忙指正下
IocpModel.h
#ifndef __IOCPMODEL_H
#define __IOCPMODEL_H
#include <iostream>
#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib")
#define OP_READ 1
#define OP_WRITE 2
#define OP_ACCEPT 3
#define OP_END -1
#define BUFFER_SIZE 1024
using namespace std;
// 自定義結構,即“完成鍵”(單句柄數據)
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; // IO操作類型
}PER_IO_DATA, *LPPER_IO_DATA;
// 線程方法
DWORD WINAPI ServerWorkerThread(LPVOID lpParam);
#endif
#define __IOCPMODEL_H
#include <iostream>
#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib")
#define OP_READ 1
#define OP_WRITE 2
#define OP_ACCEPT 3
#define OP_END -1
#define BUFFER_SIZE 1024
using namespace std;
// 自定義結構,即“完成鍵”(單句柄數據)
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; // IO操作類型
}PER_IO_DATA, *LPPER_IO_DATA;
// 線程方法
DWORD WINAPI ServerWorkerThread(LPVOID lpParam);
#endif
IocpModel.cpp
//
IocpModel.cpp : 定義控制台應用程序的入口點。
//
#include " stdafx.h "
#include " IocpModel.h "
#define Op_READ 1
#define Op_WRITE 2
WSADATA wsa;
HANDLE CompletionPort;
SYSTEM_INFO Systeminfo;
SOCKADDR_IN InternetAddr;
HANDLE ThreadHanle;
void ChangeHandleData(LPPER_IO_DATA lpPerIoContext, int state)
{
/* *
Fucntion Description:
根據傳進來的state,來進行改變下一步的IO操作
Parameter:
lpPerIoContext:上一個IO操作的結果
state 上一個IO操作的狀態
Return Value:
返回值為空
* */
if(OP_READ == state)
{
// 完成了讀取客戶端數據,並且返回數據
lpPerIoContext->OperationType = OP_WRITE;
ZeroMemory(&(lpPerIoContext->Overlapped), sizeof(OVERLAPPED));
// 返回數據給客戶端
char *sendClientData = " sendClientData ";
lpPerIoContext->DataBuf.buf = sendClientData;
lpPerIoContext->DataBuf.len = BUFFER_SIZE;
}
if(OP_WRITE == state)
{
// 上一個IO數據發送客戶端完成,結束就用 OP_END
// 繼續接受,叫類型設置為OP_READ,注意清空下 buff
// 繼續發送,將數據設置為OP_WRITE
lpPerIoContext->OperationType = OP_READ;
lpPerIoContext->DataBuf.buf = lpPerIoContext->buffer;
lpPerIoContext->DataBuf.len = BUFFER_SIZE;
}
if(OP_ACCEPT == state)
{
// 立起來的連接,把句柄設為讀類型
lpPerIoContext->OperationType = OP_READ;
ZeroMemory(&(lpPerIoContext->Overlapped), sizeof(OVERLAPPED));
ZeroMemory(&(lpPerIoContext->DataBuf), sizeof(lpPerIoContext->DataBuf));
lpPerIoContext->DataBuf.buf = lpPerIoContext->buffer;
lpPerIoContext->DataBuf.len = BUFFER_SIZE;
cout << " 數據包操作類型被改為 : OP_READ "<<endl;
}
}
void SendHandleData(LPPER_IO_DATA lpPerIoContext,LPPER_HANDLE_DATA perHandData)
{
/* *
根據lpPerIoContext對象OperationType來進行下一步的操作
* */
DWORD dwIosize = 0;
int nResult = 0;
DWORD RecvBytes = 0;
DWORD nFlag = 0;
if(lpPerIoContext->OperationType == OP_WRITE)
{
/* 發送數據到客戶端 */
nResult = WSASend(perHandData->Socket,&(lpPerIoContext->DataBuf), 1,&dwIosize, 0,&(lpPerIoContext->Overlapped),NULL);
if((nResult == SOCKET_ERROR) && WSAGetLastError() != ERROR_IO_PENDING )
{
cout<< " WSASend error : "<<WSAGetLastError()<<endl;
::closesocket(perHandData->Socket);
return;
}
}
else if(lpPerIoContext->OperationType == OP_READ)
{
// 清空,准備下個I/O數據
ZeroMemory(&(lpPerIoContext->Overlapped), sizeof(OVERLAPPED));
// 注意:這里清空下buffer,要不然數據會跟上一條粘在一起
ZeroMemory(&(lpPerIoContext->buffer), sizeof(lpPerIoContext->buffer));
lpPerIoContext->DataBuf.len = BUFFER_SIZE;
if(SOCKET_ERROR == WSARecv(perHandData->Socket, &(lpPerIoContext->DataBuf), 1,&RecvBytes,&nFlag,&(lpPerIoContext->Overlapped),NULL)){
cout << " WSARecv() failed: " << WSAGetLastError() << endl;
return;
}
}
else if(lpPerIoContext->OperationType == OP_END)
{
::closesocket(perHandData->Socket);
}
}
int main()
{
SOCKET ListenSock;
SOCKET accpSocket;
PER_HANDLE_DATA *PerHandleData = NULL;
SOCKADDR_IN saRemote;
int RemoteLen;
cout<< " Server start...... "<<endl;
WSAStartup(MAKEWORD( 2, 2),&wsa);
GetSystemInfo(&Systeminfo);
// 創建一個IO完成端口
CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL, 0, 0);
if ( CompletionPort == INVALID_HANDLE_VALUE )
{
cout<< " 完成端口創建失敗!!!! "<<endl;
return 0;
}
// 創建IOCP工作線程(根據CPU個數 * 2)
for( int i = 0;i< Systeminfo.dwNumberOfProcessors * 2 ; ++ i)
{
// 創建線程
ThreadHanle = CreateThread(NULL, 0,ServerWorkerThread,CompletionPort, 0,NULL);
CloseHandle(ThreadHanle);
}
// 4. 創建一個監聽套接字,
ListenSock = WSASocket(AF_INET,SOCK_STREAM, 0,NULL, 0,WSA_FLAG_OVERLAPPED);
if(ListenSock == INVALID_SOCKET)
{
cout<< " 套接字創建 失敗!! "<<endl;
return 0;
}
InternetAddr.sin_family = AF_INET;
InternetAddr.sin_port = htons( 5000);
InternetAddr.sin_addr.s_addr = inet_addr( " 127.0.0.1 ");
if(SOCKET_ERROR == bind(ListenSock,(SOCKADDR*)&InternetAddr, sizeof(InternetAddr)))
{
cout<< " 服務器綁定地址信息 失敗!! errid: "<<GetLastError()<<endl;
closesocket(ListenSock);
return 0;
}
int ret = listen(ListenSock, 5);
if (ret == SOCKET_ERROR)
{
cout<< " 監聽套接字失敗!! errid: "<<GetLastError()<<endl;
closesocket(ListenSock);
return 0;
}
while(TRUE)
{
// 接受連接
RemoteLen = sizeof(saRemote);
// accpSocket = WSAAccept(ListenSock,NULL,NULL,NULL,0);
accpSocket = accept(ListenSock, (SOCKADDR*)&saRemote, &RemoteLen);
if(SOCKET_ERROR == accpSocket)
{
cout<< " 接受套接字錯誤!! errid: "<<GetLastError()<<endl;
closesocket(accpSocket);
return 0;
}
// 創建用來和套接字關聯句柄信息結構
PerHandleData =(LPPER_HANDLE_DATA)GlobalAlloc(GPTR, sizeof(PER_HANDLE_DATA));
if(PerHandleData == NULL)
{
cout<< " PerHandleData = null!! errid: "<<GetLastError()<<endl;
closesocket(accpSocket);
return 0;
}
cout<< " Socker number: "<< accpSocket << " connected " << endl;
PerHandleData->Socket = accpSocket;
memcpy(&PerHandleData->ClientAddr,&saRemote,RemoteLen);
// 將接受的套接字關聯到 完成端口
if(CreateIoCompletionPort((HANDLE)accpSocket,CompletionPort,(DWORD)PerHandleData, 0) == NULL)
{
cout<< " create complertion port error! errid: "<<GetLastError()<<endl;
return 0;
}
LPPER_IO_DATA PerIoData = (LPPER_IO_DATA)GlobalAlloc(GPTR, sizeof(PER_IO_DATA));;
ChangeHandleData(PerIoData,OP_ACCEPT);
SendHandleData(PerIoData,PerHandleData);
}
return 0;
}
// 創建線程函數
DWORD WINAPI ServerWorkerThread(LPVOID lpParam)
{
HANDLE CompletionPort = (HANDLE)lpParam;
DWORD ByresTransferred;
LPOVERLAPPED lpOverlapped = NULL;
LPPER_HANDLE_DATA PerHandleData = NULL;
// LPPER_IO_DATA PerHandleData = NULL;
LPPER_IO_DATA PerIoData = NULL;
DWORD SendBytes;
DWORD RecvBytes = 0;
DWORD Flags = 0;
BOOL bRet = FALSE;
while (TRUE)
{
bRet = GetQueuedCompletionStatus(CompletionPort,
&ByresTransferred, // 的I/O操作所傳送數據的字節數
(PULONG_PTR)&PerHandleData, // 用於存放與之關聯的Completion鍵
(LPOVERLAPPED*)&lpOverlapped,
INFINITE);
if(!bRet)
{
closesocket(PerHandleData->Socket);
::GlobalFree(PerHandleData);
::GlobalFree(PerIoData);
::GlobalFree(lpOverlapped);
cout<< " 失去客戶端連接!!!! " << bRet <<endl;
return 0;
}
PerIoData = (LPPER_IO_DATA)lpOverlapped;
// 先檢查一下,看看是否套接字有錯誤發生
if( 0 == ByresTransferred)
{
if(SOCKET_ERROR == closesocket(PerHandleData->Socket)){
cout<< " ByresTransferred SOCKET_ERROR err: " << GetLastError() << endl;
}
::GlobalFree(PerHandleData);
::GlobalFree(PerIoData);
cout<< " 發生錯誤! error " <<endl;
continue;
}
// 處理操作
switch(PerIoData->OperationType)
{
case OP_ACCEPT:
if(ByresTransferred)
{
// 第一次連接接受到的數據
ChangeHandleData(PerIoData,OP_ACCEPT);
SendHandleData(PerIoData,PerHandleData);
}
else
{
// 連接成功,數據沒接受到,繼續接受
ChangeHandleData(PerIoData,OP_ACCEPT);
SendHandleData(PerIoData,PerHandleData);
}
break;
case OP_READ:
cout<< " -----------------reader---------------------- "<< endl;
cout<< " 傳送數據的字節數: "<< ByresTransferred <<endl;
cout<< " 接受數據為: "<<PerIoData->DataBuf.buf<< endl;
cout<< " --------------------------------------------- "<< endl;
ChangeHandleData(PerIoData,OP_READ);
SendHandleData(PerIoData,PerHandleData);
break;
case OP_WRITE:
ChangeHandleData(PerIoData,OP_WRITE);
SendHandleData(PerIoData,PerHandleData);
break;
default:
// 其他操作
break;
}
}
return 0 ;
}
//
#include " stdafx.h "
#include " IocpModel.h "
#define Op_READ 1
#define Op_WRITE 2
WSADATA wsa;
HANDLE CompletionPort;
SYSTEM_INFO Systeminfo;
SOCKADDR_IN InternetAddr;
HANDLE ThreadHanle;
void ChangeHandleData(LPPER_IO_DATA lpPerIoContext, int state)
{
/* *
Fucntion Description:
根據傳進來的state,來進行改變下一步的IO操作
Parameter:
lpPerIoContext:上一個IO操作的結果
state 上一個IO操作的狀態
Return Value:
返回值為空
* */
if(OP_READ == state)
{
// 完成了讀取客戶端數據,並且返回數據
lpPerIoContext->OperationType = OP_WRITE;
ZeroMemory(&(lpPerIoContext->Overlapped), sizeof(OVERLAPPED));
// 返回數據給客戶端
char *sendClientData = " sendClientData ";
lpPerIoContext->DataBuf.buf = sendClientData;
lpPerIoContext->DataBuf.len = BUFFER_SIZE;
}
if(OP_WRITE == state)
{
// 上一個IO數據發送客戶端完成,結束就用 OP_END
// 繼續接受,叫類型設置為OP_READ,注意清空下 buff
// 繼續發送,將數據設置為OP_WRITE
lpPerIoContext->OperationType = OP_READ;
lpPerIoContext->DataBuf.buf = lpPerIoContext->buffer;
lpPerIoContext->DataBuf.len = BUFFER_SIZE;
}
if(OP_ACCEPT == state)
{
// 立起來的連接,把句柄設為讀類型
lpPerIoContext->OperationType = OP_READ;
ZeroMemory(&(lpPerIoContext->Overlapped), sizeof(OVERLAPPED));
ZeroMemory(&(lpPerIoContext->DataBuf), sizeof(lpPerIoContext->DataBuf));
lpPerIoContext->DataBuf.buf = lpPerIoContext->buffer;
lpPerIoContext->DataBuf.len = BUFFER_SIZE;
cout << " 數據包操作類型被改為 : OP_READ "<<endl;
}
}
void SendHandleData(LPPER_IO_DATA lpPerIoContext,LPPER_HANDLE_DATA perHandData)
{
/* *
根據lpPerIoContext對象OperationType來進行下一步的操作
* */
DWORD dwIosize = 0;
int nResult = 0;
DWORD RecvBytes = 0;
DWORD nFlag = 0;
if(lpPerIoContext->OperationType == OP_WRITE)
{
/* 發送數據到客戶端 */
nResult = WSASend(perHandData->Socket,&(lpPerIoContext->DataBuf), 1,&dwIosize, 0,&(lpPerIoContext->Overlapped),NULL);
if((nResult == SOCKET_ERROR) && WSAGetLastError() != ERROR_IO_PENDING )
{
cout<< " WSASend error : "<<WSAGetLastError()<<endl;
::closesocket(perHandData->Socket);
return;
}
}
else if(lpPerIoContext->OperationType == OP_READ)
{
// 清空,准備下個I/O數據
ZeroMemory(&(lpPerIoContext->Overlapped), sizeof(OVERLAPPED));
// 注意:這里清空下buffer,要不然數據會跟上一條粘在一起
ZeroMemory(&(lpPerIoContext->buffer), sizeof(lpPerIoContext->buffer));
lpPerIoContext->DataBuf.len = BUFFER_SIZE;
if(SOCKET_ERROR == WSARecv(perHandData->Socket, &(lpPerIoContext->DataBuf), 1,&RecvBytes,&nFlag,&(lpPerIoContext->Overlapped),NULL)){
cout << " WSARecv() failed: " << WSAGetLastError() << endl;
return;
}
}
else if(lpPerIoContext->OperationType == OP_END)
{
::closesocket(perHandData->Socket);
}
}
int main()
{
SOCKET ListenSock;
SOCKET accpSocket;
PER_HANDLE_DATA *PerHandleData = NULL;
SOCKADDR_IN saRemote;
int RemoteLen;
cout<< " Server start...... "<<endl;
WSAStartup(MAKEWORD( 2, 2),&wsa);
GetSystemInfo(&Systeminfo);
// 創建一個IO完成端口
CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL, 0, 0);
if ( CompletionPort == INVALID_HANDLE_VALUE )
{
cout<< " 完成端口創建失敗!!!! "<<endl;
return 0;
}
// 創建IOCP工作線程(根據CPU個數 * 2)
for( int i = 0;i< Systeminfo.dwNumberOfProcessors * 2 ; ++ i)
{
// 創建線程
ThreadHanle = CreateThread(NULL, 0,ServerWorkerThread,CompletionPort, 0,NULL);
CloseHandle(ThreadHanle);
}
// 4. 創建一個監聽套接字,
ListenSock = WSASocket(AF_INET,SOCK_STREAM, 0,NULL, 0,WSA_FLAG_OVERLAPPED);
if(ListenSock == INVALID_SOCKET)
{
cout<< " 套接字創建 失敗!! "<<endl;
return 0;
}
InternetAddr.sin_family = AF_INET;
InternetAddr.sin_port = htons( 5000);
InternetAddr.sin_addr.s_addr = inet_addr( " 127.0.0.1 ");
if(SOCKET_ERROR == bind(ListenSock,(SOCKADDR*)&InternetAddr, sizeof(InternetAddr)))
{
cout<< " 服務器綁定地址信息 失敗!! errid: "<<GetLastError()<<endl;
closesocket(ListenSock);
return 0;
}
int ret = listen(ListenSock, 5);
if (ret == SOCKET_ERROR)
{
cout<< " 監聽套接字失敗!! errid: "<<GetLastError()<<endl;
closesocket(ListenSock);
return 0;
}
while(TRUE)
{
// 接受連接
RemoteLen = sizeof(saRemote);
// accpSocket = WSAAccept(ListenSock,NULL,NULL,NULL,0);
accpSocket = accept(ListenSock, (SOCKADDR*)&saRemote, &RemoteLen);
if(SOCKET_ERROR == accpSocket)
{
cout<< " 接受套接字錯誤!! errid: "<<GetLastError()<<endl;
closesocket(accpSocket);
return 0;
}
// 創建用來和套接字關聯句柄信息結構
PerHandleData =(LPPER_HANDLE_DATA)GlobalAlloc(GPTR, sizeof(PER_HANDLE_DATA));
if(PerHandleData == NULL)
{
cout<< " PerHandleData = null!! errid: "<<GetLastError()<<endl;
closesocket(accpSocket);
return 0;
}
cout<< " Socker number: "<< accpSocket << " connected " << endl;
PerHandleData->Socket = accpSocket;
memcpy(&PerHandleData->ClientAddr,&saRemote,RemoteLen);
// 將接受的套接字關聯到 完成端口
if(CreateIoCompletionPort((HANDLE)accpSocket,CompletionPort,(DWORD)PerHandleData, 0) == NULL)
{
cout<< " create complertion port error! errid: "<<GetLastError()<<endl;
return 0;
}
LPPER_IO_DATA PerIoData = (LPPER_IO_DATA)GlobalAlloc(GPTR, sizeof(PER_IO_DATA));;
ChangeHandleData(PerIoData,OP_ACCEPT);
SendHandleData(PerIoData,PerHandleData);
}
return 0;
}
// 創建線程函數
DWORD WINAPI ServerWorkerThread(LPVOID lpParam)
{
HANDLE CompletionPort = (HANDLE)lpParam;
DWORD ByresTransferred;
LPOVERLAPPED lpOverlapped = NULL;
LPPER_HANDLE_DATA PerHandleData = NULL;
// LPPER_IO_DATA PerHandleData = NULL;
LPPER_IO_DATA PerIoData = NULL;
DWORD SendBytes;
DWORD RecvBytes = 0;
DWORD Flags = 0;
BOOL bRet = FALSE;
while (TRUE)
{
bRet = GetQueuedCompletionStatus(CompletionPort,
&ByresTransferred, // 的I/O操作所傳送數據的字節數
(PULONG_PTR)&PerHandleData, // 用於存放與之關聯的Completion鍵
(LPOVERLAPPED*)&lpOverlapped,
INFINITE);
if(!bRet)
{
closesocket(PerHandleData->Socket);
::GlobalFree(PerHandleData);
::GlobalFree(PerIoData);
::GlobalFree(lpOverlapped);
cout<< " 失去客戶端連接!!!! " << bRet <<endl;
return 0;
}
PerIoData = (LPPER_IO_DATA)lpOverlapped;
// 先檢查一下,看看是否套接字有錯誤發生
if( 0 == ByresTransferred)
{
if(SOCKET_ERROR == closesocket(PerHandleData->Socket)){
cout<< " ByresTransferred SOCKET_ERROR err: " << GetLastError() << endl;
}
::GlobalFree(PerHandleData);
::GlobalFree(PerIoData);
cout<< " 發生錯誤! error " <<endl;
continue;
}
// 處理操作
switch(PerIoData->OperationType)
{
case OP_ACCEPT:
if(ByresTransferred)
{
// 第一次連接接受到的數據
ChangeHandleData(PerIoData,OP_ACCEPT);
SendHandleData(PerIoData,PerHandleData);
}
else
{
// 連接成功,數據沒接受到,繼續接受
ChangeHandleData(PerIoData,OP_ACCEPT);
SendHandleData(PerIoData,PerHandleData);
}
break;
case OP_READ:
cout<< " -----------------reader---------------------- "<< endl;
cout<< " 傳送數據的字節數: "<< ByresTransferred <<endl;
cout<< " 接受數據為: "<<PerIoData->DataBuf.buf<< endl;
cout<< " --------------------------------------------- "<< endl;
ChangeHandleData(PerIoData,OP_READ);
SendHandleData(PerIoData,PerHandleData);
break;
case OP_WRITE:
ChangeHandleData(PerIoData,OP_WRITE);
SendHandleData(PerIoData,PerHandleData);
break;
default:
// 其他操作
break;
}
}
return 0 ;
}