先看测试效果:
家用电脑做的测试:网络联通100M,cpu:锐龙1700 16线程,16G内存;
最多时候跑50W+连接没问题,家里的电脑,开多了提示缓冲用尽,只能同时开9W个连接测试。
测试中,iocp服务程序启动后,同时绑定监听三个不同的地址和端口;iocp服务端采用内存池处理数据;
测试端,是把测试软件远程投到外网公司的电脑上,在端口20011上,并发3W个实时通信连接,响应时间均值18毫秒;家里这台机器再开2个测试,分别开3W个连接,在不同端口和地址实时通信连接,响应时间800毫秒(不知道为什么家里响应时间反而更长),iocp服务cpu占用5%左右非常顺畅!!!
还有特别要注意,要关闭360安全卫士。不知道为什么开了360后会被限制连接数,且iocp服务一旦链接多了就会报毒(360逗逼,我也是醉了),关掉360一切就畅通无阻!!!
网络100M,基本占满(这个和我软件发送的内容多少有关)
下面是代码:里面有一些我自己写的类,我就不整理了,重载base类重写几个方法就行了
头文件:
CLIocpServeBase.h
#pragma once
#ifndef _CL_IOCPMGR_H_
#define _CL_IOCPMGR_H_
#include "../_cl_common/CLCommon.h"
#include
#include
#include
#include
#include
using namespace std;
//#define USEWSA //使用WSA函数
#define ACCEPT_SOCKET_NUM 10 //初始连接数
#define MAX_BUFFER_LEN 4096 //4kb
#define DEF_PORT 20011
//定义的事件枚举值
typedef enum _OpType
{
CPKEY_NULL,
CPKEY_ACP,
CPKEY_CLOSE,
CPKEY_RECV,
CPKEY_SEND,
} OPTYPE, *POPTYPE;
//绑定监听套接字目标的信息结构体
typedef class LISTENERSOK
{
public:
SOCKET m_sListen; // 监听的Socket
LPFN_ACCEPTEX m_lpfnAcceptEx; // 和监听sok绑定的控制函数
LPFN_GETACCEPTEXSOCKADDRS m_lpfnGetAcceptSockAddrs; // 和监听sok绑定的控制函数
TCHAR m_ip[50]; // 该监听socket监听的目标ip地址
USHORT m_port; // 该监听socket监听的目标ip地址对应的端口
LISTENERSOK()
{
reset();
}
~LISTENERSOK()
{
reset();
}
void reset()
{
m_sListen = INVALID_SOCKET;
m_lpfnAcceptEx = 0;
m_lpfnGetAcceptSockAddrs = 0;
_tcscpy_s(m_ip, _T("127.0.0.1"));
m_port = DEF_PORT;
}
} *PLISTENERSOK;
//内部iocp使用的重叠结构体扩展
typedef class IOINFO
{
public:
OVERLAPPED m_Overlapped; // 每一个重叠I/O网络操作都要有一个
SOCKET m_sSock; // 这个I/O操作所使用的Socket,每个连接的都是一样的
sockaddr_in m_addr; //对端地址结构
OPTYPE m_OpType; // 标志这个重叠I/O操作是做什么的,例如Accept/Recv等
WSABUF m_wsaBuf; // 存储数据的缓冲区,用来给重叠操作传递参数的,关于WSABUF后面还会讲
CHAR m_szBuffer[MAX_BUFFER_LEN]; // 对应WSABUF里的缓冲区
const LISTENERSOK *pSokPack; //监听结构体指针
IOINFO(void)
{
reset();
}
~IOINFO(void)
{
reset();
}
void resetOverlapped()
{
ZeroMemory(&m_Overlapped, sizeof(OVERLAPPED));
}
void resetBuffer()
{
ZeroMemory(m_szBuffer, MAX_BUFFER_LEN);
m_wsaBuf.buf = m_szBuffer;
m_wsaBuf.len = MAX_BUFFER_LEN;
}
void reset()
{
ZeroMemory(this, sizeof(IOINFO));
m_sSock = INVALID_SOCKET;
}
} *PIOINFO;
//每一个客户端对应的io请求
typedef class CLIENTIOINFO
{
public:
SOCKET m_Socket; // 每一个客户端连接的Socket
SOCKADDR_IN m_ClientAddr; // 这个客户端的地址
std::vector m_arrayIoContext; // 数组,所有客户端IO操作的参数,也就是说对于每一个客户端Socket是可以在上面同时投递多个IO请求的
} *PCLIENTIOINFO;
#define LOGECLIPSE 1
//iocp模型服务管理模块类,可由子类继承并重载关键函数来实现多态
class CLIocpServeBase : public CLTaskSvc
{
public:
CLIocpServeBase();
~CLIocpServeBase();
//注册一个监听地址和端口,并启动监听服务
BOOL startListen(USHORT port = DEF_PORT, LPCTSTR ip = _T("127.0.0.1"));
//设置工作组线程数,0表示自动
void setTrdCounts(size_t n = 0)
{
m_trdCounts = (n == 0 ? 2 * getCpuCoreCounts() : n);
};
//增加一个监听地址和端口,请在startListen之前增加,关闭服务后所有监听地址及端口列表会清空,再次启动服务需重新增加
BOOL addListen(USHORT port = DEF_PORT, LPCTSTR ip = _T("127.0.0.1"));
//关闭监听服务
BOOL closeServe();
protected:
//iocp句柄
HANDLE m_hIOCompletionPort = 0;
//监听套接字队列
std::vector m_ListenLst;
WSADATA wsaData = {
0 };
//等待accept的套接字,这些套接字是没有使用过的,数量为ACCEPT_SOCKET_NUM。同时会有10个套接字等待accept
std::vector m_vecAcps;
//已建立连接的信息,每个结构含有一个套接字、发送缓冲和接收缓冲,以及对端地址
std::vector m_vecContInfo;
//工作线程组启动数量
size_t m_trdCounts = 1;
//重载以便在在startWorkersThreads之前调用,返回false则不执行startWorkersThreads
virtual BOOL preStartWorkersThreads(size_t n)
{
return TRUE;
}
//启动工作线程组,返回启动工作线程的数量
int startWorkersThreads(size_t n = 0);
//初始化并绑定一个监听套接字进入运行系统
BOOL bindListener(LISTENERSOK *sokPack);
//初始化监听过程
BOOL initWinSockListener();
//获取绑定的控制函数指针
BOOL getFuncAddr(LISTENERSOK *sokPack);
//针对内部iocp的相关操作{--------------------------
HANDLE getIOCP() const
{
return m_hIOCompletionPort;
}
BOOL closeIOCP();
BOOL createIOCP(int nMaxConcurrency);
BOOL associateDevice(HANDLE hDevice, ULONG_PTR CompKey);
BOOL associateSocket(SOCKET hSocket, ULONG_PTR CompKey);
BOOL postStatus(ULONG_PTR CompKey, DWORD dwNumBytes, OVERLAPPED *po);
BOOL getStatus(ULONG_PTR *pCompKey, PDWORD pdwNumBytes, OVERLAPPED **ppo, DWORD dwMilliseconds);
//}------------------------------------------------
//线程主工作体
virtual DWORD svc(LPVOID *ppDataShare);
//重载该函数,处理从完成端口取得消息处理前的预处理事务,注意:返回值= -1安全退出,=0直接进入下一轮消息获取跳过doGetStatus,=1进入doGetStatus处理事务
virtual int preDoGetStatus(IOINFO *ol, DWORD NumberOfBytes, ULONG_PTR CompletionKey, LPVOID *ppDataShare)
{
return 1;
}
//重载该函数,以处理从完成端口取得消息成功事务,注意:若该函数 返回值 = FALSE 则工作线程将进入安全退出过程
virtual BOOL doGetStatus(IOINFO *ol, DWORD NumberOfBytes, ULONG_PTR CompletionKey, LPVOID *ppDataShare);
//重载该函数,处理从完成端口取得消息处理前的预处理事务,注意:返回值= -1安全退出,=0直接进入下一轮消息获取跳过doGetStatusError,=1进入doGetStatusError处理事务
virtual int preDoGetStatusError(int errorID, IOINFO *ol, DWORD NumberOfBytes, ULONG_PTR CompletionKey, LPVOID *ppDataShare)
{
return 1;
}
//重载该函数,以处理从完成端口取得消息失败事务,注意:若该函数 返回值 = FALSE 则工作线程将进去安全退出过程
virtual BOOL doGetStatusError(int errorID, IOINFO *ol, DWORD NumberOfBytes, ULONG_PTR CompletionKey, LPVOID *ppDataShare);
//从已连接socket列表中移除socket及释放空间
virtual BOOL deleteLink(SOCKET s);
//投递accept请求
virtual BOOL postAccept(PIOINFO ol);
//投递recv请求
virtual BOOL postRecv(IOINFO *ol);
//子类应实现的重载方法,安全退出线程组的信息投送过程,投递recv请求
virtual void postClose();
//处理外部客户机断开意外断开事件
virtual void doGetStatusError_NetNameDeleted(int errorID, IOINFO *ol, DWORD NumberOfBytes, ULONG_PTR CompletionKey, LPVOID *ppDataShare);
//处理GetStatus错误时候除Error = NetNameDeleted外的其他错误事件,内部用switch处理即可,注意:若该函数 返回值 = FALSE 则工作线程将进去安全退出过程
virtual BOOL doGetStatusError_default(int errorID, IOINFO *ol, DWORD NumberOfBytes, ULONG_PTR CompletionKey, LPVOID *ppDataShare);
//重载以便在在startListen之前调用,返回false则不执行start
virtual BOOL preStartListen(USHORT port, LPCTSTR ip)
{
return TRUE;
}
//处理accept请求,NumberOfBytes=0表示没有收到第一帧数据,>0表示收到第一帧数据
virtual BOOL doAccept(IOINFO *ol, DWORD NumberOfBytes, ULONG_PTR CompletionKey, LPVOID *ppDataShare);
//重载以处理GetAcceptSockAddrs调用后的后处理
virtual BOOL doGetAcceptSockAddrs(SOCKADDR_IN *LocalAddr, SOCKADDR_IN *clientAddr, int remoteLen, IOINFO *ol, DWORD NumberOfBytes, ULONG_PTR CompletionKey, LPVOID *ppDataShare)
{
return TRUE;
}
//重载以处理getpeername调用后的后处理
virtual void dogetpeername(int funcRet, int errorID, SOCKADDR_IN *clientAddr, int remoteLen, IOINFO *ol, DWORD NumberOfBytes, ULONG_PTR CompletionKey, LPVOID *ppDataShare);
//处理recv请求
virtual BOOL doRecv(IOINFO *ol, DWORD NumberOfBytes, ULONG_PTR CompletionKey, LPVOID *ppDataShare)
{
postRecv(ol);
return TRUE;
}
//处理send请求
virtual BOOL doSend(IOINFO *ol, DWORD NumberOfBytes, ULONG_PTR CompletionKey, LPVOID *ppDataShare)
{
postRecv(ol);
return TRUE;
}
//处理Default请求
virtual BOOL doDefault(IOINFO *ol, DWORD NumberOfBytes, ULONG_PTR CompletionKey, LPVOID *ppDataShare)
{
return TRUE;
}
//处理NumberOfBytes = 0的请求
virtual BOOL doGetStatusNoData(IOINFO *ol, DWORD NumberOfBytes, ULONG_PTR CompletionKey, LPVOID *ppDataShare)
{
deleteLink(ol->m_sSock);
return TRUE;
}
//输出socket错误信息
virtual void logError(int errorId, LPCTSTR inf1 = 0, LPCTSTR inf2 = 0) {
}
};
class CLIocpServeSample : public CLIocpServeBase
{
public:
//打开默认的显示输出
BOOL openLogout = FALSE;
protected:
//消息统计量
size_t m_msgStatisticCounts = 0;
inline size_t addStatisticCounts()
{
return ++m_msgStatisticCounts;
}
inline void resetStatisticCounts()
{
m_msgStatisticCounts = 0;
}
inline size_t getStatisticCounts()
{
return m_msgStatisticCounts;
}
//线程启动时最先运行的内部初始化过程函数,必须返回TRUE,才会执行后续的svc,否则直接进入existSvc
virtual BOOL initSvc(LPVOID *ppDataShare);
//线程退出时的最后运行函数,线程返回值由该函数决定
virtual DWORD existSvc(DWORD svcReturnValue, LPVOID *ppDataShare);
//重载该函数,以处理从完成端口取得消息成功事务,注意:若该函数 返回值 = FALSE 则工作线程将进去安全退出过程
//virtual BOOL doGetStatus(IOINFO* ol, DWORD NumberOfBytes, ULONG_PTR CompletionKey, LPVOID* ppDataShare);
//处理recv请求
virtual BOOL doRecv(IOINFO *ol, DWORD NumberOfBytes, ULONG_PTR CompletionKey, LPVOID *ppDataShare);
//处理send请求
virtual BOOL doSend(IOINFO *ol, DWORD NumberOfBytes, ULONG_PTR CompletionKey, LPVOID *ppDataShare);
//处理外部客户机断开意外断开事件
virtual void doGetStatusError_NetNameDeleted(int errorID, IOINFO *ol, DWORD NumberOfBytes, ULONG_PTR CompletionKey, LPVOID *ppDataShare);
virtual BOOL preStartWorkersThreads(size_t n)
{
resetStatisticCounts();
return TRUE;
}
virtual int preDoGetStatus(IOINFO *ol, DWORD NumberOfBytes, ULONG_PTR CompletionKey, LPVOID *ppDataShare)
{
addStatisticCounts();
return 1;
}
//重载以处理GetAcceptSockAddrs调用后的后处理
virtual BOOL doGetAcceptSockAddrs(SOCKADDR_IN *LocalAddr, SOCKADDR_IN *clientAddr, int remoteLen, IOINFO *ol, DWORD NumberOfBytes, ULONG_PTR CompletionKey, LPVOID *ppDataShare);
//重载以处理getpeername调用后的后处理
virtual void dogetpeername(int funcRet, int errorID, SOCKADDR_IN *clientAddr, int remoteLen, IOINFO *ol, DWORD NumberOfBytes