<摘录>详谈高性能UDP服务器的开发
上一篇文章我详细介绍了如何开发一款高性能的TCP服务器的网络传输层.本章我将谈谈如何开发一个高性能的UDP服务器的网络层.UDP服务器的网络层开 发相对与TCP服务器来说要容易和简单的多,UDP服务器的大致流程为创建一个socket然后将其绑定到完成端口上并投递一定数量的recv操作.当有 数据到来时从完成队列中取出数据发送到接收队列中即可。
测试结果如下:
WindowsXP Professional,Intel Core Duo E4600 双核2.4G , 2G内存。同时30K个用户和该UDP服务器进行交互其CPU使用率为10%左右,内存占用7M左右。
下面详细介绍该服务器的架构及流程:
1. 首先介绍服务器的接收和发送缓存UDP_CONTEXT。
class UDP_CONTEXT : protected NET_CONTEXT2
{3
friend class UdpSer;4
protected:5
IP_ADDR m_RemoteAddr; //对端地址6

7
enum8
{9
HEAP_SIZE = 1024 * 1024 * 5,10
MAX_IDL_DATA = 10000,11
};12

13
public:14
UDP_CONTEXT() {}15
virtual ~UDP_CONTEXT() {}16

17
void* operator new(size_t nSize);18
void operator delete(void* p);19

20
private:21
static vector<UDP_CONTEXT* > s_IDLQue;22
static CRITICAL_SECTION s_IDLQueLock;23
static HANDLE s_hHeap; 24
};UDP_CONTEXT的实现流程和TCP_CONTEXT的实现流程大致相同,此处就不进行详细介绍。
2. UDP_RCV_DATA,当服务器收到客户端发来的数据时会将数据以UDP_RCV_DATA的形式放入到数据接收队列中,其声明如下:
class DLLENTRY UDP_RCV_DATA2
{3
friend class UdpSer;4
public:5
CHAR* m_pData; //数据缓冲区6
INT m_nLen; //数据的长度7
IP_ADDR m_PeerAddr; //发送报文的地址8

9
UDP_RCV_DATA(const CHAR* szBuf, int nLen, const IP_ADDR& PeerAddr);10
~UDP_RCV_DATA();11

12
void* operator new(size_t nSize);13
void operator delete(void* p);14

15
enum16
{17
RCV_HEAP_SIZE = 1024 * 1024 *50, //s_Heap堆的大小18
DATA_HEAP_SIZE = 100 * 1024* 1024, //s_DataHeap堆的大小19
MAX_IDL_DATA = 250000,20
};21

22
private:23
static vector<UDP_RCV_DATA* > s_IDLQue;24
static CRITICAL_SECTION s_IDLQueLock;25
static HANDLE s_DataHeap; //数据缓冲区的堆26
static HANDLE s_Heap; //RCV_DATA的堆27
};UDP_RCV_DATA的实现和TCP_RCV_DATA大致相同, 此处不在详细介绍.
下面将主要介绍UdpSer类, 该类主要用来管理UDP服务.其定义如下:
class DLLENTRY UdpSer2
{3
public:4
UdpSer();5
~UdpSer();6

7
/************************************************************************8
* Desc : 初始化静态资源,在申请UDP实例对象之前应先调用该函数, 否则程序无法正常运行9
************************************************************************/10
static void InitReource();11

12
/************************************************************************13
* Desc : 在释放UDP实例以后, 掉用该函数释放相关静态资源14
************************************************************************/15
static void ReleaseReource();16

17
//用指定本地地址和端口进行初始化18
BOOL StartServer(const CHAR* szIp = "0.0.0.0", INT nPort = 0);19

20
//从数据队列的头部获取一个接收数据, pCount不为null时返回队列的长度21
UDP_RCV_DATA* GetRcvData(DWORD* pCount);22

23
//向对端发送数据24
BOOL SendData(const IP_ADDR& PeerAddr, const CHAR* szData, INT nLen);25

26
/****************************************************27
* Name : CloseServer()28
* Desc : 关闭服务器29
****************************************************/30
void CloseServer();31

32
protected:33
SOCKET m_hSock;34
vector<UDP_RCV_DATA* > m_RcvDataQue; //接收数据队列35
CRITICAL_SECTION m_RcvDataLock; //访问m_RcvDataQue的互斥锁36
long volatile m_bThreadRun; //是否允许后台线程继续运行37
BOOL m_bSerRun; //服务器是否正在运行38

39
HANDLE *m_pThreads; //线程数组40
HANDLE m_hCompletion; //完成端口句柄41

42
void ReadCompletion(BOOL bSuccess, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped);43

44
/****************************************************45
* Name : WorkThread()46
* Desc : I/O 后台管理线程47
****************************************************/48
static UINT WINAPI WorkThread(LPVOID lpParam);49
};1. InitReource() 主要对相关的静态资源进行初始化.其实大致和TcpServer::InitReource()大致相同.在UdpSer实例使用之前必须调用该函数进行静态资源的初始化, 否则服务器无法正常使用.
2.ReleaseReource() 主要对相关静态资源进行释放.只有在应用程序结束时才能调用该函数进行静态资源的释放.
3. StartServer()
该函数的主要功能启动一个UDP服务.其大致流程为先创建服务器UDP socket, 将其绑定到完成端口上然后投递一定数量的recv操作以接收客户端的数据.其实现如下:
BOOL UdpSer::StartServer(const CHAR* szIp /* = */, INT nPort /* = 0 */)2
{3
BOOL bRet = TRUE;4
const int RECV_COUNT = 500;5
WSABUF RcvBuf = { NULL, 0 };6
DWORD dwBytes = 0;7
DWORD dwFlag = 0;8
INT nAddrLen = sizeof(IP_ADDR);9
INT iErrCode = 0;10

11
try12
{13
if (m_bSerRun)14
{15
THROW_LINE;16
}17

18
m_bSerRun = TRUE;19
m_hSock = WSASocket(AF_INET, SOCK_DGRAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);20
if (INVALID_SOCKET == m_hSock)21
{22
THROW_LINE;23
}24
ULONG ul = 1;25
ioctlsocket(m_hSock, FIONBIO, &ul);26

27
//设置为地址重用,优点在于服务器关闭后可以立即启用28
int nOpt = 1;29
setsockopt(m_hSock, SOL_SOCKET, SO_REUSEADDR, (char*)&nOpt, sizeof(nOpt));30

31
//关闭系统缓存,使用自己的缓存以防止数据的复制操作32
INT nZero = 0;33
setsockopt(m_hSock, SOL_SOCKET, SO_SNDBUF, (char*)&nZero, sizeof(nZero));34
setsockopt(m_hSock, SOL_SOCKET, SO_RCVBUF, (CHAR*)&nZero, sizeof(nZero));35

36
IP_ADDR addr(szIp, nPort);37
if (SOCKET_ERROR == bind(m_hSock, (sockaddr*)&addr, sizeof(addr)))38
{39
closesocket(m_hSock);40
THROW_LINE;41
}42

43
//将SOCKET绑定到完成端口上44
CreateIoCompletionPort((HANDLE)m_hSock, m_hCompletion, 0, 0);45

46
//投递读操作47
for (int nIndex = 0; nIndex < RECV_COUNT; nIndex++)48
{49
UDP_CONTEXT* pRcvContext = new UDP_CONTEXT();50
if (pRcvContext && pRcvContext->m_pBuf)51
{52
dwFlag = 0;53
dwBytes = 0;54
nAddrLen = sizeof(IP_ADDR);55
RcvBuf.buf = pRcvContext->m_pBuf;56
RcvBuf.len = UDP_CONTEXT::S_PAGE_SIZE;57

58
pRcvContext->m_hSock = m_hSock;59
pRcvContext->m_nOperation = OP_READ; 60
iErrCode = WSARecvFrom(pRcvContext->m_hSock, &RcvBuf, 1, &dwBytes, &dwFlag, (sockaddr*)(&pRcvContext->m_RemoteAddr)61
, &nAddrLen, &(pRcvContext->m_ol), NULL);62
if (SOCKET_ERROR == iErrCode && ERROR_IO_PENDING != WSAGetLastError())63
{64
delete pRcvContext;65
pRcvContext = NULL;66
}67
}68
else69
{70
delete pRcvContext;71
}72
}73
}74
catch (const long &lErrLine)75
{ 76
bRet = FALSE;77
_TRACE("Exp : %s -- %ld ", __FILE__, lErrLine); 78
}79

80
return bRet;81
}4. GetRcvData(), 从接收队列中取出一个数据包.
UDP_RCV_DATA *UdpSer::GetRcvData(DWORD* pCount)2
{3
UDP_RCV_DATA* pRcvData = NULL;4

5
EnterCriticalSection(&m_RcvDataLock);6
vector<UDP_RCV_DATA* >::iterator iterRcv = m_RcvDataQue.begin();7
if (iterRcv != m_RcvDataQue.end())8
{9
pRcvData = *iterRcv;10
m_RcvDataQue.erase(iterRcv);11
}12

13
if (pCount)14
{15
*pCount = (DWORD)(m_RcvDataQue.size());16
}17
LeaveCriticalSection(&m_RcvDataLock);18

19
return pRcvData;20
}5. SendData() 发送指定长度的数据包.
BOOL UdpSer::SendData(const IP_ADDR& PeerAddr, const CHAR* szData, INT nLen)2
{3
BOOL bRet = TRUE;4
try5
{6
if (nLen >= 1500)7
{8
THROW_LINE;9
}10

11
UDP_CONTEXT* pSendContext = new UDP_CONTEXT();12
if (pSendContext && pSendContext->m_pBuf)13
{14
pSendContext->m_nOperation = OP_WRITE;15
pSendContext->m_RemoteAddr = PeerAddr; 16

17
memcpy(pSendContext->m_pBuf, szData, nLen);18

19
WSABUF SendBuf = { NULL, 0 };20
DWORD dwBytes = 0;21
SendBuf.buf = pSendContext->m_pBuf;22
SendBuf.len = nLen;23

24
INT iErrCode = WSASendTo(m_hSock, &SendBuf, 1, &dwBytes, 0, (sockaddr*)&PeerAddr, sizeof(PeerAddr), &(pSendContext->m_ol), NULL);25
if (SOCKET_ERROR == iErrCode && ERROR_IO_PENDING != WSAGetLastError())26
{27
delete pSendContext;28
THROW_LINE;29
}30
}31
else32
{33
delete pSendContext;34
THROW_LINE;35
}36
}37
catch (const long &lErrLine)38
{39
bRet = FALSE;40
_TRACE("Exp : %s -- %ld ", __FILE__, lErrLine); 41
}42

43
return bRet;44
}6. CloseServer() 关闭服务
void UdpSer::CloseServer()2
{3
m_bSerRun = FALSE;4
closesocket(m_hSock);5
}7. WorkThread() 在完成端口上工作的后台线程
UINT WINAPI UdpSer::WorkThread(LPVOID lpParam)2
{3
UdpSer *pThis = (UdpSer *)lpParam;4
DWORD dwTrans = 0, dwKey = 0;5
LPOVERLAPPED pOl = NULL;6
UDP_CONTEXT *pContext = NULL;7

8
while (TRUE)9
{10
BOOL bOk = GetQueuedCompletionStatus(pThis->m_hCompletion, &dwTrans, &dwKey, (LPOVERLAPPED *)&pOl, WSA_INFINITE);11

12
pContext = CONTAINING_RECORD(pOl, UDP_CONTEXT, m_ol);13
if (pContext)14
{15
switch (pContext->m_nOperation)16
{17
case OP_READ:18
pThis->ReadCompletion(bOk, dwTrans, pOl);19
break;20
case OP_WRITE:21
delete pContext;22
pContext = NULL;23
break;24
}25
}26

27
if (FALSE == InterlockedExchangeAdd(&(pThis->m_bThreadRun), 0))28
{29
break;30
}31
}32

33
return 0;34
}8.ReadCompletion(), 接收操作完成后的回调函数
void UdpSer::ReadCompletion(BOOL bSuccess, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped)2
{3
UDP_CONTEXT* pRcvContext = CONTAINING_RECORD(lpOverlapped, UDP_CONTEXT, m_ol);4
WSABUF RcvBuf = { NULL, 0 };5
DWORD dwBytes = 0;6
DWORD dwFlag = 0;7
INT nAddrLen = sizeof(IP_ADDR);8
INT iErrCode = 0;9

10
if (TRUE == bSuccess && dwNumberOfBytesTransfered <= UDP_CONTEXT::S_PAGE_SIZE)11
{12
#ifdef _XML_NET_13
EnterCriticalSection(&m_RcvDataLock);14

15
UDP_RCV_DATA* pRcvData = new UDP_RCV_DATA(pRcvContext->m_pBuf, dwNumberOfBytesTransfered, pRcvContext->m_RemoteAddr);16
if (pRcvData && pRcvData->m_pData)17
{18
m_RcvDataQue.push_back(pRcvData);19
} 20
else21
{22
delete pRcvData;23
}24

25
LeaveCriticalSection(&m_RcvDataLock);26
#else27
if (dwNumberOfBytesTransfered >= sizeof(PACKET_HEAD))28
{29
EnterCriticalSection(&m_RcvDataLock);30

31
UDP_RCV_DATA* pRcvData = new UDP_RCV_DATA(pRcvContext->m_pBuf, dwNumberOfBytesTransfered, pRcvContext->m_RemoteAddr);32
if (pRcvData && pRcvData->m_pData)33
{34
m_RcvDataQue.push_back(pRcvData);35
} 36
else37
{38
delete pRcvData;39
}40

41
LeaveCriticalSection(&m_RcvDataLock);42
}43
#endif44

45
//投递下一个接收操作46
RcvBuf.buf = pRcvContext->m_pBuf;47
RcvBuf.len = UDP_CONTEXT::S_PAGE_SIZE;48

49
iErrCode = WSARecvFrom(pRcvContext->m_hSock, &RcvBuf, 1, &dwBytes, &dwFlag, (sockaddr*)(&pRcvContext->m_RemoteAddr)50
, &nAddrLen, &(pRcvContext->m_ol), NULL);51
if (SOCKET_ERROR == iErrCode && ERROR_IO_PENDING != WSAGetLastError())52
{53
ATLTRACE("\r\n%s -- %ld dwNumberOfBytesTransfered = %ld, LAST_ERR = %ld"54
, __FILE__, __LINE__, dwNumberOfBytesTransfered, WSAGetLastError());55
delete pRcvContext;56
pRcvContext = NULL;57
}58
}59
else60
{61
delete pRcvContext;62
}63
}<摘录>详谈高性能UDP服务器的开发的更多相关文章
- <摘录>详谈高性能TCP服务器的开发
对于开发一款高性能服务器程序,广大服务器开发人员在一直为之奋斗和努力.其中一个影响服务器的重要瓶颈就是服务器的网络处理模块.如果一款服务器程序不能及时的处理用户的数据.则服务器的上层业务逻辑再高效也是 ...
- Qt for Windows:使用WinPcap开发高性能UDP服务器
首先介绍一下WinPcap WinPcap是Windows下一个网络库,性能极其强悍而且能够接收各种包. 大名鼎鼎的WireShark就是基于这个库开发的. 那么这个库性能到底有多高呢. 我测试了UD ...
- 【Network】高性能 UDP 服务应该怎么搞?
参考资料: Netty系列之Netty高性能之道 C++高性能服务框架revover:rudp总体介绍(可靠UDP传输) - zerok的专栏 - 博客频道 - CSDN.NET 高性能异步Socke ...
- LwIP应用开发笔记之二:LwIP无操作系统UDP服务器
前面我们已经完成了LwIP协议栈基于逻辑的基本移植,在这一节我们将以RAW API来实现UDP服务器. 1.UDP协议简述 UDP协议全称是用户数据报协议,在网络中它与TCP协议一样用于处理数据包, ...
- 【Network】高性能 UDP 应该怎么做?
参考资料: EPOLL-UDP-GOLANG golang udp epoll - Google 搜索 go - golang: working with multiple client/server ...
- 高性能Linux服务器 第11章 构建高可用的LVS负载均衡集群
高性能Linux服务器 第11章 构建高可用的LVS负载均衡集群 libnet软件包<-依赖-heartbeat(包含ldirectord插件(需要perl-MailTools的rpm包)) l ...
- 高性能Linux服务器 第10章 基于Linux服务器的性能分析与优化
高性能Linux服务器 第10章 基于Linux服务器的性能分析与优化 作为一名Linux系统管理员,最主要的工作是优化系统配置,使应用在系统上以最优的状态运行.但硬件问题.软件问题.网络环境等 ...
- Netty实现高性能RPC服务器优化篇之消息序列化
在本人写的前一篇文章中,谈及有关如何利用Netty开发实现,高性能RPC服务器的一些设计思路.设计原理,以及具体的实现方案(具体参见:谈谈如何使用Netty开发实现高性能的RPC服务器).在文章的最后 ...
- SSDB:高性能数据库服务器
SSDB是一个开源的高性能数据库服务器, 使用Google LevelDB作为存储引擎, 支持T级别的数据, 同时支持类似Redis中的zset和hash等数据结构, 在同时需求高性能和大数据的条件下 ...
随机推荐
- hdu-4302-Holedox Eating-线段树-单点更新,有策略的单点查询
一開始实在是不知道怎么做,后来经过指导,猛然发现,仅仅须要记录某个区间内是否有值就可以. flag[i]:代表i区间内,共同拥有的蛋糕数量. 放置蛋糕的时候非常好操作,单点更新. ip:老鼠当前的位置 ...
- hdu 2276 Kiki & Little Kiki 2
点击打开hdu 2276 思路: 矩阵快速幂 分析: 1 题目给定一个01字符串然后进行m次的变换,变换的规则是:如果当前位置i的左边是1(题目说了是个圆,下标为0的左边是n-1),那么i就要改变状态 ...
- 死锁 android ANR
以下为一段ANR的LOG,主要是在WindowManagerService.java和ActivityManagerService.java中实现. W/WindowManager( 2183): K ...
- ListView属性解释
1.android:scrollbarStyle 定义滚动条的样式和位置 参考:http://www.trinea.cn/android/android-scrollbarstyle/ 2.andro ...
- PHP - 防止 XSS(跨站脚本攻击)
<?PHP /** * @blog http://www.phpddt.com * @param $string * @param $low 安全别级低 */ function clean_xs ...
- 解析stm32的时钟
STM32 时钟系统 http://blog.chinaunix.net/uid-24219701-id-4081961.html STM32的时钟系统 *** http://www.cnblo ...
- BZOJ 1196: [HNOI2006]公路修建问题( MST )
水题... 容易发现花费最大最小即是求 MST 将每条边拆成一级 , 二级两条 , 然后跑 MST . 跑 MST 时 , 要先加 k 条一级road , 保证满足题意 , 然后再跑普通的 MST . ...
- Python 第九篇:队列Queue、生产者消费者模型、(IO/异步IP/Select/Poll/Epool)、Mysql操作
Mysql操作: grant select,insert,update,delete on *.* to root@"%" Identified by "123456&q ...
- c语言string.h和memory.h某些函数重复问题
在C语言中,为了使用memset()函数,你是选择#include <string.h>还是<memory.h>?两个都可以,如何选择? <string.h>,标准 ...
- 高级特性(6)- 高级Swing
6.1 列表 6.1.1 JList构件 6.1.2 列表模式 6.1.3 插入和移除值 6.1.4 值的绘制6.2 表格 6.2.1 简单表格 6.2.2 表格模型 6.2.3 对行和列的操作 6. ...