首先,启动主线程,接收来自客户端的请求。并启动4个子线程接收已经建连的客户端发来的消息。此时主线不阻塞,继续接收新的注册请求。4个子线程处理发来的消息,并解析消息,将要做的任务交给线程池处理。自己继续处理发来的消息。
IOCP服务器实现#pragma once
#include
#include
#include
#include
#include
#include "CThreadPool.h"
#include "WorkA.h"
#include "WorkB.h"
using namespace std;
#pragma comment(lib, "Ws2_32.lib") // Socket编程需用的动态链接库
//#pragma comment(lib, "Kernel32.lib")
#define DefaultIP "127.0.0.1"
#define DefaultPort 9999
#define DefaultClientNum 6000
#define MessMaxLen 1024
#define DataBuffSize 2 * 1024
/**
* 结构体名称:PER_IO_DATA
* 结构体功能:重叠I/O需要用到的结构体,临时记录IO数据
**/
typedef struct
{
OVERLAPPED overlapped;
WSABUF databuff;
char buffer[ DataBuffSize ];
int BufferLen;
int operationType;
SOCKET socket;
}PER_IO_OPERATEION_DATA, *LPPER_IO_OPERATION_DATA, *LPPER_IO_DATA, PER_IO_DATA;
/**
* 结构体名称:PER_HANDLE_DATA
* 结构体存储:记录单个套接字的数据,包括了套接字的变量及套接字的对应的客户端的地址。
* 结构体作用:当服务器连接上客户端时,信息存储到该结构体中,知道客户端的地址以便于回访。
**/
typedef struct
{
SOCKET socket;
SOCKADDR_STORAGE ClientAddr;
}PER_HANDLE_DATA, *LPPER_HANDLE_DATA;
//单例类
class CMYIOCPServer
{
public:
~CMYIOCPServer(void);
bool ServerSetUp();
void SetServerIp(const string & sIP=DefaultIP);
void SetPort(const int &iPort=DefaultPort);
void SetMaxClientNum(const int &iMaxNum = DefaultClientNum);
static DWORD WINAPI ServerWorkThread(LPVOID CompletionPortID);
static void SendMessage(SOCKET &tSOCKET,char MessAge[MessMaxLen]);
static CMYIOCPServer* GetInstance();
private:
//私有方法
CMYIOCPServer(void);
bool LoadWindowsSocket();
bool InitServerSocket();
bool CreateServerSocker();
static void HandleMessage();
//私有数据
string m_sServerIP;
int m_iLisenPoint;
string m_sError;
int m_iMaxClientNum;
vector m_vclientGroup;//保持客户端的连接信息
static HANDLE m_hMutex;//多线程访问互斥变量
static HANDLE m_completionPort;
SOCKET m_srvSocket;
static CMYIOCPServer *m_pInstance;
static char m_byteMsg[MessMaxLen];
static CWorkQueue m_CWorkQueue;//线程池
};
#include "StdAfx.h"
#include "MYIOCPServer.h"
HANDLE CMYIOCPServer::m_completionPort = NULL;
HANDLE CMYIOCPServer:: m_hMutex = NULL;
CMYIOCPServer* CMYIOCPServer:: m_pInstance = NULL;
char CMYIOCPServer::m_byteMsg[MessMaxLen] = {0} ;
CWorkQueue CMYIOCPServer:: m_CWorkQueue;
/**************************
获得单例对象
***************************/
CMYIOCPServer* CMYIOCPServer::GetInstance()
{
if(NULL == m_pInstance)
{
m_pInstance = new CMYIOCPServer();
}
m_CWorkQueue.Create(10);
return m_pInstance;
}
/**************************
类的构造函数
**************************/
CMYIOCPServer::CMYIOCPServer(void)
{
m_iLisenPoint = DefaultPort;
}
/**************************
类的析构函数
**************************/
CMYIOCPServer::~CMYIOCPServer(void)
{
m_CWorkQueue.Destroy(5);
}
/**************************
设置服务器IP
**************************/
void CMYIOCPServer::SetServerIp(const string & sIP)
{
m_sServerIP = sIP;
}
/**************************
设置服务器端口
**************************/
void CMYIOCPServer::SetPort(const int &iPort)
{
m_iLisenPoint = iPort;
}
/**************************
设置最大的客户端连接数目
**************************/
void CMYIOCPServer::SetMaxClientNum(const int &iMaxNum)
{
m_iMaxClientNum = iMaxNum;
}
/**************************
服务器接收客户端消息,
工作线程
**************************/
DWORD WINAPI CMYIOCPServer::ServerWorkThread(LPVOID CompletionPortID)
{
HANDLE CompletionPort = (HANDLE)CompletionPortID;
DWORD BytesTransferred;
LPOVERLAPPED IpOverlapped;
LPPER_HANDLE_DATA PerHandleData = NULL;
LPPER_IO_DATA PerIoData = NULL;
DWORD RecvBytes;
DWORD Flags = 0;
BOOL bRet = false;
while(true){
bRet = GetQueuedCompletionStatus(m_completionPort, &BytesTransferred, (PULONG_PTR)&PerHandleData, (LPOVERLAPPED*)&IpOverlapped, INFINITE);
if(bRet == 0){
cerr <
continue;
//这里不能返回,返回子线程就结束了
//return -1;
}
PerIoData = (LPPER_IO_DATA)CONTAINING_RECORD(IpOverlapped, PER_IO_DATA, overlapped);
// 检查在套接字上是否有错误发生
if(0 == BytesTransferred){
closesocket(PerHandleData->socket);
GlobalFree(PerHandleData);
GlobalFree(PerIoData);
continue;
}
//得到消息码流
memset(m_byteMsg,0,MessMaxLen);
memcpy(m_byteMsg,PerIoData->databuff.buf,MessMaxLen);
//得到客户端SOCKET信息
SOCKET sClientSocket = PerHandleData->socket;
printf("message is %s \n",m_byteMsg);
HandleMessage();
//SendMessage(sClientSocket,m_byteMsg);
// 为下一个重叠调用建立单I/O操作数据
ZeroMemory(&(PerIoData->overlapped), sizeof(OVERLAPPED)); // 清空内存
PerIoData->databuff.len = 1024;
PerIoData->databuff.buf = PerIoData->buffer;
PerIoData->operationType = 0; // read
WSARecv(PerHandleData->socket, &(PerIoData->databuff), 1, &RecvBytes, &Flags, &(PerIoData->overlapped), NULL);
}
return 0;
}
/*******************
处理消息类
*******************/
void CMYIOCPServer::HandleMessage()
{
printf("thread is %d\n",GetCurrentThreadId());
WorkItemBase *pCworkItem = NULL;
byte iCommand = m_byteMsg[0];
switch(iCommand)
{
case '0':
pCworkItem = new CWorkA();
break;
case '1':
pCworkItem = new CWorkB();
break;
case '2':
break;
default:
break;
}
//将任务交给线程池处理
if(NULL != pCworkItem)
{
m_CWorkQueue.InsertWorkItem(pCworkItem);
}
printf("Finish Handle Message\n");
}
/**************************
发送消息给制定客户端
**************************/
void CMYIOCPServer::SendMessage(SOCKET &tSOCKET,char MessAge[MessMaxLen])
{
// 开始数据处理,接收来自客户端的数据
WaitForSingleObject(m_hMutex,INFINITE);
send(tSOCKET, MessAge, MessMaxLen, 0); // 发送信息
ReleaseMutex(m_hMutex);
}
/**************************
初始化SOCKET对象,创建端口
和线程数组
**************************/
bool CMYIOCPServer::LoadWindowsSocket()
{
// 加载socket动态链接库
WORD wVersionRequested = MAKEWORD(2, 2); // 请求2.2版本的WinSock库
WSADATA wsaData; // 接收Windows Socket的结构信息
DWORD err = WSAStartup(wVersionRequested, &wsaData);
if (0 != err){ // 检查套接字库是否申请成功
m_sError = "Request Windows Socket Library Error!\n";
return false;
}
if(LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2){// 检查是否申请了所需版本的套接字库
WSACleanup();
m_sError ="Request Windows Socket Version 2.2 Error!\n";
system("pause");
return false;
}
// 创建IOCP的内核对象
/**
* 需要用到的函数的原型:
* HANDLE WINAPI CreateIoCompletionPort(
* __in HANDLE FileHandle, // 已经打开的文件句柄或者空句柄,一般是客户端的句柄
* __in HANDLE ExistingCompletionPort, // 已经存在的IOCP句柄
* __in ULONG_PTR CompletionKey, // 完成键,包含了指定I/O完成包的指定文件
* __in DWORD NumberOfConcurrentThreads // 真正并发同时执行最大线程数,一般推介是CPU核心数*2
* );
**/
m_completionPort = CreateIoCompletionPort( INVALID_HANDLE_VALUE, NULL, 0, 0);
if (NULL == m_completionPort){ // 创建IO内核对象失败
m_sError ="CreateIoCompletionPort failed. Error:\n";
return false;
}
// 创建IOCP线程--线程里面创建线程池
// 确定处理器的核心数量
SYSTEM_INFO mySysInfo;
GetSystemInfo(&mySysInfo);
// 基于处理器的核心数量创建线程
for(DWORD i = 0; i
// 创建服务器工作器线程,并将完成端口传递到该线程
HANDLE ThreadHandle = CreateThread(NULL, 0, &CMYIOCPServer::ServerWorkThread, m_completionPort, 0, NULL);
if(NULL == ThreadHandle){
m_sError ="Create Thread Handle failed. Error::\n";
}
CloseHandle(ThreadHandle);
}
return true;
}
/*************************
初始化服务器SOCKET信息
*************************/
bool CMYIOCPServer::InitServerSocket()
{
// 建立流式套接字
m_srvSocket = socket(AF_INET, SOCK_STREAM, 0);
// 绑定SOCKET到本机
SOCKADDR_IN srvAddr;
srvAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
srvAddr.sin_family = AF_INET;
srvAddr.sin_port = htons(m_iLisenPoint);
int bindResult = bind(m_srvSocket, (SOCKADDR*)&srvAddr, sizeof(SOCKADDR));
if(SOCKET_ERROR == bindResult){
m_sError = "Bind failed. Error:";
return false;
}
return true;
}
/**************************
创建服务器端的监听信息
**************************/
bool CMYIOCPServer::CreateServerSocker()
{
// 将SOCKET设置为监听模式
int listenResult = listen(m_srvSocket, 10);
if(SOCKET_ERROR == listenResult){
m_sError = "Listen failed. Error: " ;
return false;
}
// 开始处理IO数据
cout <
int icount = 0;
while(true){
PER_HANDLE_DATA * PerHandleData = NULL;
SOCKADDR_IN saRemote;
int RemoteLen;
SOCKET acceptSocket;
// 接收连接,并分配完成端,这儿可以用AcceptEx()
RemoteLen = sizeof(saRemote);
acceptSocket = accept(m_srvSocket, (SOCKADDR*)&saRemote, &RemoteLen);
if(SOCKET_ERROR == acceptSocket){ // 接收客户端失败
cerr <
m_sError = "Accept Socket Error: " ;
icount++;
if(icount > 50)
{
return false;
}
continue;
}
icount = 0;
// 创建用来和套接字关联的单句柄数据信息结构
PerHandleData = (LPPER_HANDLE_DATA)GlobalAlloc(GPTR, sizeof(PER_HANDLE_DATA)); // 在堆中为这个PerHandleData申请指定大小的内存
PerHandleData -> socket = acceptSocket;
memcpy (&PerHandleData -> ClientAddr, &saRemote, RemoteLen);
m_vclientGroup.push_back(PerHandleData); // 将单个客户端数据指针放到客户端组中
// 将接受套接字和完成端口关联
CreateIoCompletionPort((HANDLE)(PerHandleData -> socket), m_completionPort, (DWORD)PerHandleData, 0);
// 开始在接受套接字上处理I/O使用重叠I/O机制
// 在新建的套接字上投递一个或多个异步
// WSARecv或WSASend请求,这些I/O请求完成后,工作者线程会为I/O请求提供服务
// 单I/O操作数据(I/O重叠)
LPPER_IO_OPERATION_DATA PerIoData = NULL;
PerIoData = (LPPER_IO_OPERATION_DATA)GlobalAlloc(GPTR, sizeof(PER_IO_OPERATEION_DATA));
ZeroMemory(&(PerIoData -> overlapped), sizeof(OVERLAPPED));
PerIoData->databuff.len = 1024;
PerIoData->databuff.buf = PerIoData->buffer;
PerIoData->operationType = 0; // read
DWORD RecvBytes;
DWORD Flags = 0;
WSARecv(PerHandleData->socket, &(PerIoData->databuff), 1, &RecvBytes, &Flags, &(PerIoData->overlapped), NULL);
}
//销毁资源
DWORD dwByteTrans;
PostQueuedCompletionStatus(m_completionPort, dwByteTrans, 0, 0);
closesocket(listenResult);
return true;
}
/*********************
启动服务器
*********************/
bool CMYIOCPServer::ServerSetUp()
{
if(false == LoadWindowsSocket())
{
return false;
}
if(false == InitServerSocket())
{
return false;
}
if(false == CreateServerSocker())
{
return false;
}
return true;
}
SOCKET客户端实现
// IOCPClient.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include
#include
#include
#include
#include
#include
using namespace std;
#pragma comment(lib, "Ws2_32.lib") // Socket编程需用的动态链接库
SOCKET sockClient; // 连接成功后的套接字
HANDLE bufferMutex; // 令其能互斥成功正常通信的信号量句柄
const int DefaultPort = 9999;
int _tmain(int argc, _TCHAR* argv[])
{
// 加载socket动态链接库(dll)
WORD wVersionRequested;
WSADATA wsaData; // 这结构是用于接收Wjndows Socket的结构信息的
wVersionRequested = MAKEWORD( 2, 2 ); // 请求2.2版本的WinSock库
int err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) { // 返回值为零的时候是表示成功申请WSAStartup
return -1;
}
if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 2 ) { // 检查版本号是否正确
WSACleanup( );
return -1;
}
// 创建socket操作,建立流式套接字,返回套接字号sockClient
sockClient = socket(AF_INET, SOCK_STREAM, 0);
if(sockClient == INVALID_SOCKET) {
printf("Error at socket():%ld\n", WSAGetLastError());
WSACleanup();
return -1;
}
// 将套接字sockClient与远程主机相连
// int connect( SOCKET s, const struct sockaddr* name, int namelen);
// 第一个参数:需要进行连接操作的套接字
// 第二个参数:设定所需要连接的地址信息
// 第三个参数:地址的长度
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); // 本地回路地址是127.0.0.1;
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(DefaultPort);
while(SOCKET_ERROR == connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR))){
// 如果还没连接上服务器则要求重连
cout <
char choice;
while(cin >> choice && (!((choice != 'Y' && choice == 'N') || (choice == 'Y' && choice != 'N')))){
cout <
cin.sync();
cin.clear();
}
if (choice == 'Y'){
continue;
}
else{
cout <
system("pause");
return 0;
}
}
cin.sync();
cout <
send(sockClient, "\nAttention: A Client has enter...\n", 200, 0);
bufferMutex = CreateSemaphore(NULL, 1, 1, NULL);
DWORD WINAPI SendMessageThread(LPVOID IpParameter);
DWORD WINAPI ReceiveMessageThread(LPVOID IpParameter);
HANDLE sendThread = CreateThread(NULL, 0, SendMessageThread, NULL, 0, NULL);
HANDLE receiveThread = CreateThread(NULL, 0, ReceiveMessageThread, NULL, 0, NULL);
WaitForSingleObject(sendThread, INFINITE); // 等待线程结束
closesocket(sockClient);
CloseHandle(sendThread);
CloseHandle(receiveThread);
CloseHandle(bufferMutex);
WSACleanup(); // 终止对套接字库的使用
printf("End linking...\n");
printf("\n");
system("pause");
return 0;
}
DWORD WINAPI SendMessageThread(LPVOID IpParameter)
{
while(1){
string talk;
char talkbuffer[200];
//gets(talkbuffer);
int iRand = rand()%3;
Sleep(iRand * 1000);
if(1 == iRand)
{
talkbuffer[0]='0';
}
else
{
talkbuffer[0]='1';
}
talkbuffer[1] = '\0';
int len;
for (len = 0; talkbuffer[len] != '\0'; ++len){
// 找出这个字符组的长度
}
talkbuffer[len] = '\n';
talkbuffer[++len] = '\0';
talk = talkbuffer;
WaitForSingleObject(bufferMutex, INFINITE); // P(资源未被占用)
if("quit" == talk){
talk.push_back('\0');
send(sockClient, talk.c_str(), 200, 0);
break;
}
else{
talk.append("\n");
}
printf("\nI Say:(\"quit\"to exit):");
// cout <
//talk = talk + "\n\0";
// string talka = "1234\n";
string talkb = "aaaaaaaaaaaaaaaaaaaaaaa";
string talka = "123\n";
talk = talk + "\0";
// talk = talk + "\n";
send(sockClient, talkbuffer, 200, 0); // 发送信息
send(sockClient, talka.c_str(), 200, 0); // 发送信息
// send(sockClient, "\nAttention: A Client has enter...\n", 200, 0);
ReleaseSemaphore(bufferMutex, 1, NULL); // V(资源占用完毕)
}
return 0;
}
DWORD WINAPI ReceiveMessageThread(LPVOID IpParameter)
{
while(1){
char recvBuf[300];
recv(sockClient, recvBuf, 200, 0);
WaitForSingleObject(bufferMutex, INFINITE); // P(资源未被占用)
printf("%s Says: %s", "Server", recvBuf); // 接收信息
ReleaseSemaphore(bufferMutex, 1, NULL); // V(资源占用完毕)
}
return 0;
}