可以承载20k并发量的IOCP

更新时间:2023-07-19 10:21:03 阅读: 评论:0

对于开发一款高性能服务器程序,广大服务器开发人员在一直为之奋斗和努力.其中一个影响服务器的重要瓶颈就是服务器的网络处理模块.如果一款服务器程序不能及时的处理用户的数据.则服务器的上层业务逻辑再高效也是徒劳.所以一个服务器程序的网络处理能力直接影响到整个服务器的性能, 本文主要介绍在windows平台下开发高性能的网络处理模块以及自己在设计开发服务器网络模块遇到的一些问题和开发心得.本篇主要介绍TCP服务器的设计, 下一篇将主要介绍UDP服务器的设计.
众所周知, 对于服务器来说windows下网络I/O处理的最佳方式就是完成端口, 因此本服务器的开发主要基于完成端口的模式.完成端口(completion port)是应用程序使用线程池处理异步I/O请求的一种机制.将创建好的socket和完成端口绑定后就可以向该socket上投递相应的I/O操作, 当操作完成后I/O系统会向完成端口发送一个通知包;应用程序通过GetQueuedCompletionStatus()函数获取这些通知包并进行相应的处理.下面切入正题谈谈TCP服务器的开发.
本人在开发TCP服务器的经过了两个阶段, 第一次设计出来的TCP服务器网络层只能支持5000 – 8000个在线用户同时和服务器交互, 但是会出现一些莫名其妙的系统异常.所以网络层不是很稳定.这次开发主要用到一个系统的I/O线程池函数BindIoCompletionCallback() 该函数在win2000以后都支持, BindIoCompletion-
Callback()是一个I/O线程池函数,其主要功能是采用系统线程池进行I/O处理,优点是用户无需自己创建
完成端口和线程池,完成端口和工作者线程池的创建和管理都由系统维护.给用户带了很大的方便.用户只需要将自己创建的socket和I/O回调函数传递给BindIoCompletionCallback()函数即可, 然后就可以在该socket上投递相应的操作.当操作完成后系统会调用用户的回调函数通知用户.这种方式给开发者带来了很大的方便, 开发者甚至不需要去了解完成端口的工作机制就可以开发出一个较高性能的网络程序.但同时也带来了很多麻烦,用户无法知道在完成端口上到底有多少个工作者线程, 而且当连接到服务器上的用户量过大时会出现线程堆栈错误等异常,同时有1000-2000个用户断开连接后, 服务器就无法让后续用户连接到服务器. 在这种方式下的服务器网络层最多只支持4000 – 5000用户同时连接到服务器.用户量再大时就会出现一些系统异常而且无法解决.
借鉴于第一次开发的经验和教训, 在第二次开发服务器TCP层时决定自己创建完成端口和工作者线程池, 并对其进行维护和管理.这样做的好处是出了问题好定位和处理.下面将我开发的代码拿出来和大家切磋切磋, 如果什么地方写得问题还
希望能够指正出来, 欢迎邮件联系我: , QQ: 24633959, MSN:
1.        首先介绍网络上下文(NET_CONTEXT)的定义:
class NET_CONTEXT
{
public:
WSAOVERLAPPED m_ol;
SOCKET m_hSock; 
CHAR* m_pBuf;  //接收或发送数据的缓冲区
INT m_nOperation; //在该网络上下文上进行的操作 OP_ACCEPT…
static DWORD S_PAGE_SIZE;  //缓冲区的最大容量 
NET_CONTEXT();
virtual ~NET_CONTEXT();
static void InitReource();
static void ReleaReource();
private:
void* operator new (size_t nSize);
void operator delete(void* p);
static HANDLE s_hDataHeap;
static vector<char * > s_IDLQue;  //无效数据缓冲区的队列
static CRITICAL_SECTION s_IDLQueLock;  //访问s_IDLQue的互斥锁
};
NET_CONTEXT 是所有网络上下文的基类, 对于TCP的recv, nd, accep, connect的上下文都继承自该类.UDP的nd和recv的网络上下文也继承自该类. m_ol 必须放置在第一个位置否则当从完成封包取net_context不能得到正确的结果. S_PAGE_SIZE 为数据缓冲区m_pBuf的大小,其大小和相应的操作系统平台有关, win32下其值为4096, win64下其值为8192, 即为操作系统的一个内存页的大小.设置为一个内存页的原因是因为在投递重叠操作时系统会锁定投递的缓冲区, 在锁定时是按照内存页的边界来锁定的.因此即使你只发送一个1K字节数据系统也会锁定整个内存页(4096/8192). s_hDataHeap 为
自定义的BUF申请的堆.其优点是用户可以自己对堆进行管理和操作. s_IDLQue 为用过的BUF队列, 当用户用完相应的NET_CONTEXT后在执行析构操作时并不会真正把m_pBuf所占的内存释放掉而是将其放入到s_IDLQue队列中, 当下次申请新的NET_CONTEXT时只需从s_IDLQue中取出就可以使用, 避免频繁的new和delete操作.
2.        数据包头的定义:
struct PACKET_HEAD
{
LONG nTotalLen;  //数据包的总长度
ULONG nSerialNum;  //数据包的序列号
WORD nCurrentLen;  //当前数据包的长度
立体星星怎么折
WORD nType;    //数据包的类型
};
数据包头位于每一个接收到的或待发送的数据包的首部,用于确定接收到的数据包是否合法以及该数据包是做什么用的.用户可以定义自己包头.
3.        TCP_CONTEXT主要用于定义接收和发送数据的缓冲区, 继承自NET_CONTEXT
class TCP_CONTEXT : public NET_CONTEXT
{
friend class TcpServer;
protected:
DWORD m_nDataLen;  //TCP模式下累计发送和接收数据的长度
TCP_CONTEXT()
: m_nDataLen(0)
{
}
virtual ~TCP_CONTEXT() {}
void* operator new(size_t nSize);
void operator delete(void* p);
enum北京市第二幼儿园
{
E_TCP_HEAP_SIZE = 1024 * 1024* 10,
MAX_IDL_DATA = 20000,
};
private:
static vector<TCP_CONTEXT*
> s_IDLQue;  //无效的数据队列
static CRITICAL_SECTION s_IDLQueLock;  //访问s_IDLQue的互斥锁
static HANDLE s_hHeap; //TCP_CONTEXT的数据申请堆
};
TCP_CONTEXT类主要用在网络上发送和接收数据的上下文.每个连接到服务器的SOCKET都会有一个发送和接收数据的TCP_CONTEXT.这里重载了new和delete函数.这样做的优点在于当申请一个新的TCP_CONTEXT对象时会先判断无效的数据队列中是否有未使用的TCP_CONTEXT,若有则直接取出来使用否则从s_hHeap堆上新申请一个.new 函数的定义如下
void* TCP_CONTEXT::operator new(size_t nSize)
{
void* pContext = NULL;
try
沙虫干{
if (NULL == s_hHeap)
{
throw ((long)(__LINE__));
}
//为新的TCP_CONTEXT申请内存, 先从无效队列中找, 如无效队列为空则从堆上申请   
EnterCriticalSection(&s_IDLQueLock);
vector<TCP_CONTEXT* >::iterator iter = s_IDLQue.begin();
if (iter != d())
{
pContext = *iter;
a(iter);   
}
el
{
pContext = HeapAlloc(s_hHeap, HEAP_ZERO_MEMORY | HEAP_NO_SERIALIZE, nSize);
}
LeaveCriticalSection(&s_IDLQueLock);
if (NULL == pContext)
{
throw ((long)(__LINE__));
}
}
catch (const long& iErrCode)
{
pContext = NULL;
_TRACE("\r\nExcept : %s--%ld", __FILE__, iErrCode);
}
return pContext;
}
当使用完TCP_CONTEXT时调用delete函数进行对内存回收, 在进行内存回收时先查看无效队列已存放的数据是否达到MAX_IDL_DATA, 若没有超过MAX_IDL_DATA则将其放入到s_IDLQue中否则将其释放掉.delete函数的实现如下:
void TCP_CONTEXT::operator delete(void* p)
{
if (p) 
{
//若空闲队列的长度小于MAX_IDL_DATA, 则将其放入无效队列中否则释
//放之
EnterCriticalSection(&s_IDLQueLock);
const DWORD QUE_SIZE = (DWORD)(s_IDLQue.size());
TCP_CONTEXT* const pContext = (TCP_CONTEXT*)p;
if (QUE_SIZE <= MAX_IDL_DATA)
{
s_IDLQue.push_back(pContext);
}
el
{
HeapFree(s_hHeap, HEAP_NO_SERIALIZE, p);
}
LeaveCriticalSection(&s_IDLQueLock); 
}
return;
}
4.        ACCEPT_CONTEXT 主要用于投递AcceptEx操作, 继承自NET_CONTEXT类
class ACCEPT_CONTEXT : public NET_CONTEXT
{
friend class TcpServer;
protected:
SOCKET m_hRemoteSock;  //连接本服务器的客户端SOCKET
ACCEPT_CONTEXT()
: m_hRemoteSock(INVALID_SOCKET)
{
}
virtual ~ACCEPT_CONTEXT() {}
耽美排行榜void* operator new(size_t nSize);
void operator delete(void* p);
private:
static vector<ACCEPT_CONTEXT* > s_IDLQue;  //无效的数据队列
static CRITICAL_SECTION s_IDLQueLock;  //访问s_IDLQueµ互斥锁
static HANDLE s_
hHeap; //ACCEPT_CONTEXT的自定义堆
};
5.        TCP_RCV_DATA, 当服务器的某个socket从网络上收到数据后并且数据合法便为收到的数据申请一个新的TCP_RCV_DATA实例存储收到的数据.其定义如下:
class DLLENTRY TCP_RCV_DATA
{
friend class TcpServer;
public:
SOCKET m_hSocket;  //与该数据相关的socket
CHAR* m_pData;  //数据缓冲区地址
INT m_nLen;    //收到的数据的长度
TCP_RCV_DATA(SOCKET hSock, const CHAR* pBuf, INT nLen);
~TCP_RCV_DATA();
void* operator new(size_t nSize);
void operator delete(void* p);
enum
{
HEAP_SIZE = 1024 *1024* 50, 
DATA_HEAP_SIZE = 1024 *1024 * 100,
MAX_IDL_DATA = 100000,
};
private:
static vector<TCP_RCV_DATA* > s_IDLQue;  //无效数据队列
static CRITICAL_SECTION s_IDLQueLock;  //访问s_IDLQue的互斥锁
static HANDLE s_hHeap; 
static HANDLE s_DataHeap;
};
6.        前面讲的相关的数据结构都是为下面要探讨的TcpServer类服务的. TcpServer类是本文要探讨的核心数据结构;主要用于启动服务, 管理连接等操作.
class DLLENTRY TcpServer
{
public:
TcpServer();
~TcpServer();
怀念亲人的诗句/************************************************************************
* Desc : 初始化相关静态资源,在申请TCP实例之前必须先调用该方法对相关资
* 源进行初始化
************************************************************************/
static void InitReource();
/************************************************************************
* Desc : 释放相应的静态资源
************************************************************************/
static void ReleaReource();
/
****************************************************
* Name : StartServer()
* Desc : 启动TCP服务
****************************************************/
BOOL StartServer(
const char *szIp //要启动服务的本地地址, 若为NULL则采用默认地址
, INT nPort //要启动服务的端口
麻辣虾, LPCLOSE_ROUTINE pCloFun  //客户端socket关闭的通知函数
, LPVOID pParam    //clo函数的参数
);
/****************************************************
* Name : CloServer()
* Desc : 关闭TCP服务
****************************************************/
狮虎园
void CloServer();
清仓大甩卖图片/****************************************************
* Name : SendData()
* Desc : 对客户端hSock发送长度为nDataLen的数据
****************************************************/
BOOL SendData(SOCKET hSock, const CHAR* szData, INT nDataLen);
/****************************************************
* Name : GetRcvData()
* Desc : 从接收数据队列中获取一个接收数据包
* pQueLen 不为NULL时返回其长度
****************************************************/
TCP_RCV_DATA* GetRcvData(
DWORD* const pQueLen
);
protected:
enum
{
LISTEN_EVENTS = 2,    //监听socket的事件个数
MAX_ACCEPT = 50,      //每次最多投递的accept操作的个数
_SOCK_NO_RECV = 0xf0000000, //客户端socket已连接上但为发送数据
_SOCK_RECV = 0xf0000001 //客户端socket已连接上并也收到数据 
};
vector<TCP_RCV_DATA* > m_RcvDataQue;  //接收到的数据缓冲区队列
CRITICAL_SECTION m_RcvQueLock; //访问m_RcvDataQue的互斥锁
vector<SOCKET> m_SocketQue; //连接本服务器的客户端socket队列
CRITICAL_SECTION m_SockQueLock;  //访问m_SocketQue的互斥锁
LPCLOSE_ROUTINE m_pCloFun; //客户端socket关闭的通知函数
LPVOID m_pCloParam; //传递给m_pCloFun的用户参数
SOCKET m_hSock;    //要进行服务器监听的socket
long volatile m_bThreadRun; //是否允许后台线程继续运行
long volatile m_nAcceptCount;    //当前已投递的accept操作的个数
BOOL m_bSerRun; //服务是否正在运行
//accept的事件
HANDLE m_ListenEvents[LISTEN_EVENTS];
HANDLE *m_pThreads;    //创建的后台线程的句柄
HANDLE m_hCompletion;    //完成端口句柄
static LPFN_ACCEPTEX s_pfAcceptEx;    //AcceptEx地址
// GetAcceptExSockaddrs的地址
static LPFN_GETACCEPTEXSOCKADDRS s_pfGetAddrs;
/****************************************************
* Name : AcceptCompletionProc()
* Desc : acceptEx操作完成后回调函数
****************************************************/
void AcceptCompletionProc(BOOL bSuccess, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped);
/****************************************************
* Name : RecvCompletionProc()
* Desc : 接收操作完成后的回调函数
****************************************************/
void RecvCompletionProc(BOOL bSuccess, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped);
/****************************************************
* Name : SendCompletionProc()
* Desc : 发送操作完成后的回调函数
****************************************************/
void SendCompletionProc(BOOL bSuccess, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped);
/****************************************************
* Name : ListenThread()
* Desc : 监听线程
****************************************************/
static UINT WINAPI ListenThread(LPVOID lpParam);
/****************************************************
* Name : WorkThread()
* Desc : 在完成端口上工作的后台线程
****************************************************/
static UINT WINAPI WorkThread(LPVOID lpParam);
/*****************************

本文发布于:2023-07-19 10:21:03,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/fan/82/1104876.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:服务器   完成   数据
相关文章
留言与评论(共有 0 条评论)
   
验证码:
推荐文章
排行榜
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图