<摘录>详谈高性能UDP服务器的开发

本文详细介绍了一种高性能UDP服务器的设计思路与实现流程。该服务器利用完成端口进行高效的I/O操作,通过合理的缓存管理和线程调度实现了良好的并发性能。

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

 1
    class UDP_CONTEXT : protected NET_CONTEXT 2
    
{ 3
        friend 
class UdpSer; 4
    
protected: 5
        IP_ADDR m_RemoteAddr;            
//对端地址 6
 7
        
enum 8
        
{ 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的形式放入到数据接收队列中,其声明如下:

 1
    class DLLENTRY UDP_RCV_DATA 2
    
{ 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服务.其定义如下:

 1
    class DLLENTRY UdpSer 2
    
{ 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操作以接收客户端的数据.其实现如下:

 1
    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(), 从接收队列中取出一个数据包.

 1
    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() 发送指定长度的数据包.

 1
    BOOL UdpSer::SendData(const IP_ADDR& PeerAddr, const CHAR* szData, INT nLen) 2
    
{ 3
        BOOL bRet 
= TRUE; 4
        
try 5
        
{ 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() 关闭服务

1
    void UdpSer::CloseServer()2
    
{3
        m_bSerRun 
= FALSE;4
        closesocket(m_hSock);
5
    }


7. WorkThread() 在完成端口上工作的后台线程

 1
    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(), 接收操作完成后的回调函数

 1
    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
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值