谢谢各位大哥回贴,近日翻了很多贴子,有很多人认为Iocp好,也有人认为它也有不足的地方,但在大富翁里面只有很少遍谈到了这些,所以我在CSDN上找到了一个示例:
1. stdafx.h 中的#include <windows.h>下添加
#include <stdio.h>
#include <time.h>
#include <assert.h>
#include "server.h"
// TODO: reference additional headers your program requires here
// WinSock 2.2
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib,"ws2_32.lib"
#define WM_SOCKET WM_USER + 1214
#define DATA_BUFSIZE 4096
// 重叠I/O 结构体
typedef struct tagPerHandleData
{
SOCKET Socket;
} PER_HANDLE_DATA, * LPPER_HANDLE_DATA;
typedef struct tagPerIoOperationData
{
OVERLAPPED Overlapped;
WSABUF DataBuf;
CHAR Buffer[DATA_BUFSIZE];
BOOL OperationType;
} PER_IO_OPERATION_DATA, * LPPER_IO_OPERATION_DATA;
typedef struct tagPDATA
{
SOCKET Socket; // 头
CHAR Buffer[DATA_BUFSIZE]; // 数据包
DWORD BytesTransferred; // 数据包大小
} PDATA, * LPPDATA;
#define RECV_POSTED 0
#define SEND_POSTED 1
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
2.stdafx.cpp 中内容不变
3.LoveServer.cpp中
// LoveServer.cpp : Defines the entry point for the application.
//
#include "stdafx.h"
////////////////////////////////////////////////////
HINSTANCE hInst;
HWND hWnd;
#define WINDOWS_TITLE "PCP Server"
#define WINDOWS_CLASS "PCP_SERVER_EXE_WIN_INC"
BOOL InitWindows(HINSTANCE hInstance, int nCmdShow); // 初始化窗体
LRESULT CALLBACK WinProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam); // 消息处理函数
BOOL RegWriteString(HKEY m_hKey, LPCTSTR lpSubKey, LPCTSTR lpValueName, LPCTSTR lpVal);
///////////////////////////////////////////////////
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
// TODO: Place code here.
#ifdef _DEBUG
AllocConsole();
freopen("CONOUT$", "a", stdout);
#endif
char szCurProPath[MAX_PATH];
// get current path
memset(szCurProPath,0,MAX_PATH);
GetModuleFileName(NULL,szCurProPath,MAX_PATH);
char *pos=strrchr(szCurProPath,'//');
szCurProPath[pos-szCurProPath+1]=0;
strcat(szCurProPath,"pcpServer.exe"
;
//RegWriteString(HKEY_LOCAL_MACHINE,"SOFTWARE//Microsoft//Windows//CurrentVersion//Run","pcpServer",szCurProPath);
// 加载winsock.dll
WSADATA wsa;
WSAStartup( MAKEWORD(2,2), &wsa );
// 创建窗体,并隐藏
// if (!InitWindows(hInstance, SW_HIDE))
// return 0;
printf("Server is Run.../n"
;
StartServer(8099);
// MSG msg;
// while(GetMessage(&msg, NULL, 0, 0))
// {
// TranslateMessage(&msg);
// DispatchMessage(&msg);
// }
// return msg.wParam;
// 卸载winsock.dll
WSACleanup();
return 0;
}
// 初始化窗体
BOOL InitWindows(HINSTANCE hInstance, int nCmdShow)
{
hInst=hInstance;
WNDCLASS wc;
wc.style = NULL;
wc.lpfnWndProc = (WNDPROC)WinProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(hInstance,MAKEINTRESOURCE(IDI_ICON1));
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH);
wc.lpszMenuName = NULL;
wc.lpszClassName = WINDOWS_CLASS;
RegisterClass(&wc);
hWnd = CreateWindow(WINDOWS_CLASS,
WINDOWS_TITLE,
WS_OVERLAPPEDWINDOW,
0,
0,
300,
300,
NULL,NULL,hInstance,NULL);
if( !hWnd ) return FALSE;
ShowWindow(hWnd,nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
//---------------------------------------------------------------
//-- 消息函数
//---------------------------------------------------------------
LRESULT CALLBACK WinProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{
switch(message)
{
case WM_CREATE:
break;
case WM_DESTROY: //程序是否存在
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
/********************************************************************
函数名: RegWriteString
参 数:
写入注册表RGB_SZ值
m_hKey - HKEY_XXXX_XXXX
lpSubKey - xxx/xxx/xxx
lpValueName
lpVal
返回值: TRUE时成功,返回FALSE时失败
********************************************************************/
BOOL RegWriteString(HKEY m_hKey, LPCTSTR lpSubKey, LPCTSTR lpValueName, LPCTSTR lpVal)
{
HKEY hKey;
long lReturn;
lReturn = RegOpenKeyEx(m_hKey,lpSubKey,0L,KEY_ALL_ACCESS,&hKey);
if(lReturn!=ERROR_SUCCESS)
{
DWORD dw;
lReturn=RegCreateKeyEx(m_hKey,lpSubKey,0L,NULL,REG_OPTION_VOLATILE,KEY_ALL_ACCESS,NULL,&hKey,&dw);
if(lReturn!=ERROR_SUCCESS)
{
return FALSE;
}
}
lReturn=RegSetValueEx(hKey,lpValueName,0L,REG_SZ,(const BYTE *) lpVal,strlen(lpVal)+1);
if(lReturn==ERROR_SUCCESS)
return TRUE;
return FALSE;
}
4.server.h中
// server.h: interface for the server class.
//
//////////////////////////////////////////////////////////////////////
#ifndef _SERVER_H
#define _SERVER_H
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
#define WELCOME_INFO "Welcome to China Honker's PCP! [1.0.1]/r/n/r/n"
typedef struct tagUserInfo
{
// 机器信息
SOCKET sock;
char UserName[50];
struct tagUserInfo* next; //链表指针
}USERINFO, *PUSERINFO;
typedef struct tagUserItem
{
char UserName[20];
}USERITEM, *PUSERITEM;
extern bool StartServer(unsigned int port); // 启动服务
extern void StopServer(void); // 停止服务
DWORD WINAPI ServerThreadProc(LPVOID pParam);
DWORD WINAPI ServerWorkerThread(LPVOID CompletionPortID);
DWORD WINAPI Ser_RecvProc(LPVOID pParam);
void OnBroadcast(char *Buffer, int len); // 广播
////////////////////////////////////////////////////////////////////
// 链表函数
BOOL AddNode(SOCKET sock, char UserName[20]);
extern void ClearLogin(void);
extern void Print();
BOOL FindNode(char *CorName);
BOOL FindNode(SOCKET sock);
void DeleteNode(SOCKET sock);
char* GetUserName(SOCKET sock);
#endif
5.server.cpp中
#include "stdafx.h"
#include "server.h"
static SOCKET ServerListen;
//////////////////////////////////////////////////////////////////////////
// 登录链表控制指针
USERINFO *user_head = NULL,*user_end = NULL;
DWORD user_num = 0;
/***********************************************************************
服务控制模块 ( Server Control Module )
************************************************************************/
// 1. 启动网络服务
bool StartServer(unsigned int port)
{
// CreateThread( NULL, 0, ServerThreadProc, (LPVOID)port, 0, NULL );
ServerThreadProc((LPVOID)port);
return TRUE;
}
// 2. 停止网络服务
void StopServer(void)
{
if (ServerListen != INVALID_SOCKET )
closesocket(ServerListen);
}
///////////////////////////////////////////////////////////////////////////////////
///////////////////////// ///////////////////////////////////
//////////////////////// Net Server ///////////////////////////////////
/////////////////////// ///////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//
// 网络处理函数
//
DWORD WINAPI ServerThreadProc(LPVOID pParam)
{
unsigned int port = (unsigned int)pParam;
HANDLE hCompletionPort; // 完成端口句柄
SYSTEM_INFO SystemInfo; // 系统信息
DWORD dwThreadID; // 线程ID
SOCKET Accept; // 接受套接字
LPPER_HANDLE_DATA PerHandleData; //Per句柄信息
LPPER_IO_OPERATION_DATA PerIoData;
DWORD dwFlag,RecvBytes;
struct sockaddr_in InternetAddr,addr; // 地址结构体
/***********************************
CreateIoCompletionPort(
HANDLE FileHandle, //指定一个要同完成端口关联在一起的套接字句柄
HANDLE ExistingCompletionPort, //指定一个现有的完成端口
DWORD CompletionKey, //指定要与某个特定的套接字句柄关联在一起的"单句柄数据"
DWORD NumberOfConcurrentThreads //并发线程数量
);
该函数有两个明显有别的目的:
1.创建一个完成端口对象
2.将一个句柄同完成端口关联到一起
该处用于创建一个完成端口,最开始创建一个完成端口时候,前三个参数将被忽略.第4个参数为0表示每个处理器一次只允许执行一个工作者线程
该处语句的作用是返回一个句柄,在为完成端口分配了一个套接字句柄后,用来对那个端口进行标定(引用)
***********************************/
/******1.创建一个完成端口**********/
hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); // 创建I/O完成端口
if (hCompletionPort == NULL)
{
MessageBox(NULL,"CreateIoCompletionPort failed!","NetFileServerThreadProc",0);
return 0;
}
/******2.判断系统内的处理器个数*********/
GetSystemInfo( &SystemInfo ); // 获得处理器的个数
for( DWORD i = 0; i < SystemInfo.dwNumberOfProcessors; i++ ) // 创建处理器个数相同的工作者线程
{
HANDLE hThread;
/******3.创建一个服务器工作者线程, 并且传递完成端口给它********/
hThread = CreateThread( NULL, 0, ServerWorkerThread, hCompletionPort, 0, &dwThreadID );
if (hThread == NULL)
{
MessageBox(NULL, "CreateThread() failed!", "NetFileServerThreadProc", 0);
CloseHandle(hCompletionPort);
return 0;
}
CloseHandle( hThread ); // 关闭线程句柄
}
/*******4.创建监听套接字********/
ServerListen = WSASocket( AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED );
if (ServerListen == INVALID_SOCKET)
{
MessageBox(NULL, "WSASocket() failed!", "NetFileServerThreadProc", 0);
CloseHandle(hCompletionPort);
return 0;
}
/*******5.绑定端口**********/
InternetAddr.sin_family = AF_INET;
InternetAddr.sin_addr.s_addr = htonl( INADDR_ANY );
InternetAddr.sin_port = htons( port );
if ( bind( ServerListen, (SOCKADDR *)&InternetAddr, sizeof(InternetAddr) ) == SOCKET_ERROR ) // 绑定IP和Port
{
MessageBox(NULL, "bind() failed!","NetFileServerProc",0);
closesocket(ServerListen);
CloseHandle(hCompletionPort);
return 0;
}
/*******6.准备监听***********/
if (listen( ServerListen, 5 ) == SOCKET_ERROR)// 准备监听
{
MessageBox(NULL, "listen() failed!", "NetFileServerProc", 0);
closesocket(ServerListen);
CloseHandle(hCompletionPort);
return 0;
}
while(TRUE)
{
/******7.接受进入的连接请求**************/
// 接受连接并且分配到完成端口上
if ((Accept = WSAAccept(ServerListen, (SOCKADDR *)&addr, NULL, NULL, 0 )) == SOCKET_ERROR)
{
break;
}
/******8.创建一个数据结构,用于容纳"单句柄数据",同时在结构中存入接受的套接字句柄************/
// 创建per-handle数据(单句柄数据)信息结构到socket上
PerHandleData = (LPPER_HANDLE_DATA) GlobalAlloc( GPTR, sizeof(PER_HANDLE_DATA) );
if (PerHandleData == NULL)
{
MessageBox(NULL, "GlobalAlloc() failed!","NetFileServerProc",0);
break;
}
PerHandleData->Socket = Accept;
/******9.将自accept返回的新套接字句柄同完成端口关联到一起,同时将单句柄数据结构传递给CreateIoCompletionPort****/
// 完成端口接受Socket
if (CreateIoCompletionPort( (HANDLE)Accept, hCompletionPort, (unsigned long)PerHandleData, 0 ) == NULL)
{
MessageBox(NULL, "CreateIoCompletionPort2() failed!","NetFileServerProc",0);
break;
}
/******10.启动I/O进程,抛出一个或多个WSASend() 或WSARecv() 调用请求******/
// 在Socket上使用重叠I/O
// 投递一次接收,由于接收都需要使用这个函数来投递一个接收的准备
PerIoData = (LPPER_IO_OPERATION_DATA) GlobalAlloc( GPTR, sizeof(PER_IO_OPERATION_DATA) );
if (PerIoData == NULL)
{
MessageBox(NULL, "GlobalAlloc() failed!","NetFileServerProc",0);
return 0;
}
ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED));
PerIoData->OperationType = RECV_POSTED;
PerIoData->DataBuf.len = DATA_BUFSIZE;
PerIoData->DataBuf.buf = PerIoData->Buffer;
dwFlag = 0;
if (WSARecv( Accept, &(PerIoData->DataBuf), 1, &RecvBytes, &dwFlag,
&(PerIoData->Overlapped), NULL) == SOCKET_ERROR)
{
if (WSAGetLastError() != ERROR_IO_PENDING)
{
MessageBox(NULL, "WSARecv() failed!", "NetFileServerProc",0);
return 0;
}
}
}
return 0;
}
////////////////////////////////需要的话修改这里以下/////////////////////////////
//
// 服务工作者线程
//
DWORD WINAPI ServerWorkerThread(LPVOID CompletionPortID)
{
// 获得完成端口
HANDLE CompletionPort = (HANDLE) CompletionPortID;
DWORD BytesTransferred;
LPPER_HANDLE_DATA PerHandleData;
LPPER_IO_OPERATION_DATA PerIoData;
DWORD SendBytes=0, RecvBytes;
DWORD Flags;
while(TRUE)
{
/************************************
将套接字句柄与一个完成端口关联在一起后,便可以以套接字为基础,投递发送与接受请求,开始对I/O请求处理
WINBASEAPI
BOOL
WINAPI
GetQueuedCompletionStatus(
HANDLE CompletionPort, //要在上面等待的完成端口
LPDWORD lpNumberOfBytesTransferred, //完成了一次I/O操作后(如WSASend或WSARecv),接收实际传输的字节数
LPDWORD lpCompletionKey, //单句柄数据
LPOVERLAPPED *lpOverlapped, //接收完成的I/O操作的重叠结果
DWORD dwMilliseconds //INFINITE,调用无休止的等待下去
);
************************************/
// 完成端口来消息了
if (GetQueuedCompletionStatus( CompletionPort, &BytesTransferred,
(LPDWORD)&PerHandleData, (LPOVERLAPPED *)&PerIoData, INFINITE) == 0)
{
if (GetLastError() == 64) // 异常中止通讯, 强行关闭此连接
{
char buffer[100];
sprintf(buffer, "%s 已经离开了!/r/n", GetUserName(PerHandleData->Socket));
OnBroadcast(buffer, strlen(buffer)+1);
DeleteNode(PerHandleData->Socket); // 移除当前用户的信息
Print();
closesocket(PerHandleData->Socket);
GlobalFree( PerHandleData );
GlobalFree( PerIoData );
continue;
}
else
{
MessageBox(NULL, "GetQueuedCompletionStatus() failed!","NetFileServerWorkerThread",0);
return 0;
}
}
/******检查在套接字上是否有错误发生,如果有,则关闭套接字,清空在该套接字上的PerHandleData,PerIoData******/
// 请求退出
if (BytesTransferred == 0 &&
(PerIoData->OperationType == RECV_POSTED ||
PerIoData->OperationType == SEND_POSTED))
{
char buffer[100];
sprintf(buffer, "%s 已经离开了!/r/n", GetUserName(PerHandleData->Socket));
OnBroadcast(buffer, strlen(buffer)+1);
DeleteNode(PerHandleData->Socket); // 移除当前用户的信息
Print();
closesocket(PerHandleData->Socket);
GlobalFree( PerHandleData );
GlobalFree( PerIoData );
continue;
}
// 接收消息
if (PerIoData->OperationType == RECV_POSTED)
{
PDATA *pData = new PDATA;
if (pData == NULL) continue;
// 填充数据
pData->Socket = PerHandleData->Socket;
pData->BytesTransferred = BytesTransferred;
memcpy(pData->Buffer, PerIoData->Buffer, DATA_BUFSIZE);;
CreateThread(NULL, 0, Ser_RecvProc, (LPVOID)pData, 0, NULL );
}
// 发送消息
if (PerIoData->OperationType == SEND_POSTED)
{
}
// 抛出等待接受下一次重叠请求
Flags = 0;
ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED));
PerIoData->DataBuf.len = DATA_BUFSIZE;
PerIoData->DataBuf.buf = PerIoData->Buffer;
PerIoData->OperationType = RECV_POSTED;
WSARecv(PerHandleData->Socket,
&(PerIoData->DataBuf), 1, &RecvBytes,
&Flags, &(PerIoData->Overlapped), NULL);
}
return 0;
}
// 接受消息处理线程
DWORD WINAPI Ser_RecvProc(LPVOID pParam)
{
LPPDATA lpData = (LPPDATA) pParam;
lpData->Buffer[lpData->BytesTransferred] = '/0';
// 3D 27 C2 E2 43 77 37 7B DD 90 79 62 A5 3E 13 EB ("pcp"的MD5串值)
if ( lpData->Buffer[0] == 101 &&
lpData->Buffer[1] == -64 &&
lpData->Buffer[2] == -3 &&
lpData->Buffer[3] == 49 )
{
if( AddNode(lpData->Socket, &lpData->Buffer[4]) )
{
send(lpData->Socket, "100 Login Success", 18, 0);
// 发送欢迎消息(个人)
char WelcomeBuf[1024], INIPath[MAX_PATH];
// 获得当前目录
memset(INIPath,0,MAX_PATH);
GetModuleFileName(NULL,INIPath,MAX_PATH);
char *pos=strrchr(INIPath,'//');
INIPath[pos-INIPath+1]=0;
strcat(INIPath, "pcpServer.ini"
;
GetPrivateProfileString("config","welcome",WELCOME_INFO, WelcomeBuf, 1024, INIPath);
send(lpData->Socket, WelcomeBuf, strlen(WelcomeBuf)+1, 0);
// 发送欢迎消息(全部)
char buffer[100];
sprintf(buffer, "欢迎 %s 加入我们!/r/n/r/n", &lpData->Buffer[4]);
OnBroadcast(buffer, strlen(buffer)+1);
}
else
send(lpData->Socket, "101 Login Failed", 17, 0);
}
else if ( strcmp(lpData->Buffer, "1 LIST"
== 0 ) // 获得在线用户列表
{
// 创建用户列表缓冲
char pInfo[1024];
USERINFO* temp;
temp = user_head;
while( temp != NULL )
{
send(lpData->Socket, temp->UserName, strlen(temp->UserName)+1, 0);
temp = temp->next;
}
sprintf(pInfo, "在线总人数: %d", user_num);
send(lpData->Socket, pInfo, strlen(pInfo)+1, 0);
}
else if ( lpData->Buffer[0] == '2' &&
lpData->Buffer[1] == ' ' &&
lpData->Buffer[2] == 'B' &&
lpData->Buffer[3] == 'O' ) // 广播消息
{
char Buffer[4096];
char date[20], time[20];
sprintf(Buffer, "%s (%s %s)/r/n%s/r/n", GetUserName(lpData->Socket), _strdate(date), _strtime(time), &lpData->Buffer[4]);
printf("Recv Message: %s/n", Buffer);
OnBroadcast(Buffer, strlen(Buffer)+1);
}
delete lpData;
return 0;
}
//////////////////////////这里以下不需要修改////////////////////////////////
// 添加帐号到登录链表中
BOOL AddNode(SOCKET sock, char* UserName)
{
if ( FindNode(UserName) ) return FALSE; // 重名登录, 不允许
printf("Add User: %s./n", UserName);
// 申请结点
USERINFO* node;
node = new USERINFO;
if (node == NULL)
return FALSE;
// 写入信息
node->sock = sock;
memcpy(node->UserName, UserName, 20);
node->next = NULL; // 结尾为NULL
if ( user_head == NULL ) // 新建头元素
{
user_head = node;
user_end = node;
}
else // 接上.
{
user_end->next = node;
user_end = node;
}
user_num++;
Print();
return TRUE;
}
// 添空登录链表
void ClearUser(void)
{
USERINFO *temp,*p;
p = user_head;
while( p != NULL )
{
temp = p;
p = p->next;
delete temp;
}
user_head = NULL;
user_end = NULL;
user_num = 0;
}
void Print()
{
USERINFO* temp;
temp = user_head;
printf("/n---------------/n"
;
printf("Print Start.../n"
;
int index=0;
while( temp != NULL )
{
printf("%d: %X/t%s/n", index++, temp->sock, temp->UserName);
temp = temp->next;
}
printf("---------------/n"
;
}
char* GetUserName(SOCKET sock)
{
USERINFO* temp;
temp = user_head;
while( temp != NULL )
{
if (sock == temp->sock)
return temp->UserName;
temp = temp->next;
}
return NULL;
}
BOOL FindNode(char *UserName)
{
USERINFO* temp;
temp = user_head;
while( temp != NULL )
{
if (strcmp(UserName, temp->UserName) == 0)
return TRUE;
temp = temp->next;
}
return FALSE;
}
BOOL FindNode(SOCKET sock)
{
USERINFO* temp;
temp = user_head;
while( temp != NULL )
{
if (sock == temp->sock)
return TRUE;
temp = temp->next;
}
return FALSE;
}
void DeleteNode(SOCKET sock)
{
USERINFO *temp, *p;
temp = user_head;
if (temp == NULL) return;
// 1. 删除链表头
if (user_head->sock == sock)
{
user_head = user_head->next;
delete temp;
user_num--;
return;
}
// 2. 任意一链表结点
while( temp != NULL )
{
if (temp->next->sock == sock) // 下一次就是要删除的
{
p = temp->next; // p为待删除的节点
temp->next = p->next; // 断开p节点, 首尾相连
delete p;
user_num--;
return;
}
temp = temp->next;
}
}
void OnBroadcast(char *Buffer, int len)
{
USERINFO* temp;
temp = user_head;
printf("/n Serach Client.../n"
;
while( temp != NULL )
{
if ( temp->sock != INVALID_SOCKET )
{
printf("/tSend %s./n", temp->UserName);
send(temp->sock, Buffer, len, 0);
}
temp = temp->next;
}
printf(" Send OK!/n/n"
;
}
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1473498
请大家多看一下,谢谢