本章我将谈谈如何开发一个高性能的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_CONTEXT
{
      friend class UdpSer;
      protected:
      IP_ADDR m_RemoteAddr;            //对端地址

      enum
     {
           HEAP_SIZE = 1024 * 1024 * 5,
           MAX_IDL_DATA = 10000,
      };

    public:
        UDP_CONTEXT() {}
        virtual ~UDP_CONTEXT() {}

       void* operator new(size_t nSize);
       void operator delete(void* p);

    private:
        static vector<UDP_CONTEXT* > s_IDLQue;
        static CRITICAL_SECTION s_IDLQueLock;
        static HANDLE s_hHeap;    
};

  UDP_CONTEXT的实现流程和TCP_CONTEXT的实现流程大致相同,此处就不进行详细介绍。

2. UDP_RCV_DATA,当服务器收到客户端发来的数据时会将数据以UDP_RCV_DATA的形式放入到数据接收队列中,其声明如下:

class DLLENTRY UDP_RCV_DATA
{
	friend class UdpSer;
public:
	CHAR* m_pData;                //数据缓冲区
	INT m_nLen;                    //数据的长度
	IP_ADDR m_PeerAddr;            //发送报文的地址

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

	void* operator new(size_t nSize);
	void operator delete(void* p);

	enum
	{
		RCV_HEAP_SIZE = 1024 * 1024 *50,        //s_Heap堆的大小
		DATA_HEAP_SIZE = 100 * 1024* 1024,    //s_DataHeap堆的大小
		MAX_IDL_DATA = 250000,
	};

private:
	static vector<UDP_RCV_DATA* > s_IDLQue;
	static CRITICAL_SECTION s_IDLQueLock;
	static HANDLE s_DataHeap;        //数据缓冲区的堆
	static HANDLE s_Heap;            //RCV_DATA的堆
};

  UDP_RCV_DATA的实现和TCP_RCV_DATA大致相同, 此处不在详细介绍.

下面将主要介绍UdpSer类, 该类主要用来管理UDP服务.其定义如下:

class DLLENTRY UdpSer
{
public:
	UdpSer();
	~UdpSer();

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

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

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

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

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

	/****************************************************
	* Name : CloseServer()
	* Desc : 关闭服务器
	****************************************************/
	void CloseServer();

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

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

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

	/****************************************************
	* Name : WorkThread()
	* Desc : I/O 后台管理线程
	****************************************************/
	static UINT WINAPI WorkThread(LPVOID lpParam);
};

  1. InitReource() 主要对相关的静态资源进行初始化.其实大致和TcpServer::InitReource()大致相同.在UdpSer实例使用之前必须调用该函数进行静态资源的初始化, 否则服务器无法正常使用.

2.ReleaseReource() 主要对相关静态资源进行释放.只有在应用程序结束时才能调用该函数进行静态资源的释放.

3. StartServer() 
该函数的主要功能启动一个UDP服务.其大致流程为先创建服务器UDP socket, 将其绑定到完成端口上然后投递一定数量的recv操作以接收客户端的数据.其实现如下:

BOOL UdpSer::StartServer(const CHAR* szIp /* =  */, INT nPort /* = 0 */)
{
	BOOL bRet = TRUE;
	const int RECV_COUNT = 500;
	WSABUF RcvBuf = { NULL, 0 };
	DWORD dwBytes = 0;
	DWORD dwFlag = 0;
	INT nAddrLen = sizeof(IP_ADDR);
	INT iErrCode = 0;

	try
	{
		if (m_bSerRun)
		{
			THROW_LINE;
		}

		m_bSerRun = TRUE;
		m_hSock = WSASocket(AF_INET, SOCK_DGRAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
		if (INVALID_SOCKET == m_hSock)
		{
			THROW_LINE;
		}
		ULONG ul = 1;
		ioctlsocket(m_hSock, FIONBIO, &ul);

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

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

		IP_ADDR addr(szIp, nPort);
		if (SOCKET_ERROR == bind(m_hSock, (sockaddr*)&addr, sizeof(addr)))
		{
			closesocket(m_hSock);
			THROW_LINE;
		}

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

		//投递读操作
		for (int nIndex = 0; nIndex < RECV_COUNT; nIndex++)
		{
			UDP_CONTEXT* pRcvContext = new UDP_CONTEXT();
			if (pRcvContext && pRcvContext->m_pBuf)
			{
				dwFlag = 0;
				dwBytes = 0;
				nAddrLen = sizeof(IP_ADDR);
				RcvBuf.buf = pRcvContext->m_pBuf;
				RcvBuf.len = UDP_CONTEXT::S_PAGE_SIZE;

				pRcvContext->m_hSock = m_hSock;
				pRcvContext->m_nOperation = OP_READ;            
				iErrCode = WSARecvFrom(pRcvContext->m_hSock, &RcvBuf, 1, &dwBytes, &dwFlag, (sockaddr*)(&pRcvContext->m_RemoteAddr)
					, &nAddrLen, &(pRcvContext->m_ol), NULL);
				if (SOCKET_ERROR == iErrCode && ERROR_IO_PENDING != WSAGetLastError())
				{
					delete pRcvContext;
					pRcvContext = NULL;
				}
			}
			else
			{
				delete pRcvContext;
			}
		}
	}
	catch (const long &lErrLine)
	{            
		bRet = FALSE;
		_TRACE("Exp : %s -- %ld ", __FILE__, lErrLine);            
	}

	return bRet;
}

  4. GetRcvData(), 从接收队列中取出一个数据包.

UDP_RCV_DATA *UdpSer::GetRcvData(DWORD* pCount)
{
    UDP_RCV_DATA* pRcvData = NULL;

    EnterCriticalSection(&m_RcvDataLock);
    vector<UDP_RCV_DATA* >::iterator iterRcv = m_RcvDataQue.begin();
    if (iterRcv != m_RcvDataQue.end())
    {
        pRcvData = *iterRcv;
        m_RcvDataQue.erase(iterRcv);
    }

    if (pCount)
    {
        *pCount = (DWORD)(m_RcvDataQue.size());
    }
    LeaveCriticalSection(&m_RcvDataLock);

    return pRcvData;
}

5. SendData() 发送指定长度的数据包.

BOOL UdpSer::SendData(const IP_ADDR& PeerAddr, const CHAR* szData, INT nLen)
{
    BOOL bRet = TRUE;
    try
    {
        if (nLen >= 1500)
        {
            THROW_LINE;
        }

        UDP_CONTEXT* pSendContext = new UDP_CONTEXT();
        if (pSendContext && pSendContext->m_pBuf)
        {
            pSendContext->m_nOperation = OP_WRITE;
            pSendContext->m_RemoteAddr = PeerAddr;        

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

            WSABUF SendBuf = { NULL, 0 };
            DWORD dwBytes = 0;
            SendBuf.buf = pSendContext->m_pBuf;
            SendBuf.len = nLen;

            INT iErrCode = WSASendTo(m_hSock, &SendBuf, 1, &dwBytes, 0, (sockaddr*)&PeerAddr, sizeof(PeerAddr), &(pSendContext->m_ol), NULL);
            if (SOCKET_ERROR == iErrCode && ERROR_IO_PENDING != WSAGetLastError())
            {
                delete pSendContext;
                THROW_LINE;
            }
        }
        else
        {
            delete pSendContext;
            THROW_LINE;
        }
    }
    catch (const long &lErrLine)
    {
        bRet = FALSE;
        _TRACE("Exp : %s -- %ld ", __FILE__, lErrLine);            
    }

    return bRet;
}

6. CloseServer() 关闭服务

void UdpSer::CloseServer()
{
    m_bSerRun = FALSE;
    closesocket(m_hSock);
}

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

UINT WINAPI UdpSer::WorkThread(LPVOID lpParam)
{
    UdpSer *pThis = (UdpSer *)lpParam;
    DWORD dwTrans = 0, dwKey = 0;
    LPOVERLAPPED pOl = NULL;
    UDP_CONTEXT *pContext = NULL;

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

        pContext = CONTAINING_RECORD(pOl, UDP_CONTEXT, m_ol);
        if (pContext)
        {
            switch (pContext->m_nOperation)
            {
            case OP_READ:
                pThis->ReadCompletion(bOk, dwTrans, pOl);
                break;
            case OP_WRITE:
                delete pContext;
                pContext = NULL;
                break;
            }
        }

        if (FALSE == InterlockedExchangeAdd(&(pThis->m_bThreadRun), 0))
        {
            break;
        }
    }

    return 0;
}

8.ReadCompletion(), 接收操作完成后的回调函数

void UdpSer::ReadCompletion(BOOL bSuccess, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped)
{
    UDP_CONTEXT* pRcvContext = CONTAINING_RECORD(lpOverlapped, UDP_CONTEXT, m_ol);
    WSABUF RcvBuf = { NULL, 0 };
    DWORD dwBytes = 0;
    DWORD dwFlag = 0;
    INT nAddrLen = sizeof(IP_ADDR);
    INT iErrCode = 0;

    if (TRUE == bSuccess && dwNumberOfBytesTransfered <= UDP_CONTEXT::S_PAGE_SIZE)
    {
#ifdef _XML_NET_
        EnterCriticalSection(&m_RcvDataLock);

        UDP_RCV_DATA* pRcvData = new UDP_RCV_DATA(pRcvContext->m_pBuf, dwNumberOfBytesTransfered, pRcvContext->m_RemoteAddr);
        if (pRcvData && pRcvData->m_pData)
        {
            m_RcvDataQue.push_back(pRcvData);
        }    
        else
        {
            delete pRcvData;
        }

        LeaveCriticalSection(&m_RcvDataLock);
#else
        if (dwNumberOfBytesTransfered >= sizeof(PACKET_HEAD))
        {
            EnterCriticalSection(&m_RcvDataLock);

            UDP_RCV_DATA* pRcvData = new UDP_RCV_DATA(pRcvContext->m_pBuf, dwNumberOfBytesTransfered, pRcvContext->m_RemoteAddr);
            if (pRcvData && pRcvData->m_pData)
            {
                m_RcvDataQue.push_back(pRcvData);
            }    
            else
            {
                delete pRcvData;
            }

            LeaveCriticalSection(&m_RcvDataLock);
        }
#endif

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

        iErrCode = WSARecvFrom(pRcvContext->m_hSock, &RcvBuf, 1, &dwBytes, &dwFlag, (sockaddr*)(&pRcvContext->m_RemoteAddr)
            , &nAddrLen, &(pRcvContext->m_ol), NULL);
        if (SOCKET_ERROR == iErrCode && ERROR_IO_PENDING != WSAGetLastError())
        {
            ATLTRACE("\r\n%s -- %ld dwNumberOfBytesTransfered = %ld, LAST_ERR = %ld"
                , __FILE__, __LINE__, dwNumberOfBytesTransfered, WSAGetLastError());
            delete pRcvContext;
            pRcvContext = NULL;
        }
    }
    else
    {
        delete pRcvContext;
    }
}

 

 

posted on 2012-04-18 18:09  jeffery_lush  阅读(444)  评论(0编辑  收藏  举报