这里演示如何使用xengine开发高性能网络服务器.
这是一个模板,提供了基于网络服务,线程池,流式包管理器.使用者可以使用这个直接处理业务逻辑即可
这篇文章适用XEngine windows和linux版本
在windows下面,需要使用IOCP提供网络服务器.网络服务器
在开始的地方,需要先启动一个网络服务器,代码如下:下面演示了一个WINDOWS IOCP 服务器启动流程,在启动函数编写的时候,需要设置IOCP服务的回调函数,来处理网络事件
BOOL CTestFor_IOCPServerDlg::IOCP_CBConnect_Func(LPCSTR lpszClientAddr, SOCKET hSocket,LPVOID lParam)
{
CTestFor_IOCPServerDlg *pClass_This = (CTestFor_IOCPServerDlg *)lParam;
TCHAR tszMsgBuffer[4096];
TCHAR tszClientMsg[2048];
memset(tszClientMsg,'\0',sizeof(tszClientMsg));
memset(tszMsgBuffer,'\0',sizeof(tszMsgBuffer));
pClass_This->m_EditRecvMsg.GetWindowText(tszMsgBuffer,sizeof(tszMsgBuffer));
NetCore_TCPIocp_SetLimitEx(pClass_This->xhNet, lpszClientAddr, 10, _T("hello"));
_stprintf_s(tszClientMsg,_T("有客户进入:%s\r\n%s"),lpszClientAddr,tszMsgBuffer);
pClass_This->m_EditRecvMsg.SetWindowText(tszClientMsg);
return TRUE;
}
void CTestFor_IOCPServerDlg::IOCP_CBRecv_Func(LPCSTR lpszClientAddr, SOCKET hSocket,LPCTSTR lpszRecvBuffer,int nLen,LPVOID lParam)
{
CTestFor_IOCPServerDlg *pClass_This = (CTestFor_IOCPServerDlg *)lParam;
TCHAR tszMsgBuffer[4096];
TCHAR tszClientMsg[2048];
memset(tszClientMsg,'\0',sizeof(tszClientMsg));
memset(tszMsgBuffer,'\0',sizeof(tszMsgBuffer));
pClass_This->m_EditRecvMsg.GetWindowText(tszMsgBuffer,sizeof(tszMsgBuffer));
_stprintf_s(tszClientMsg,_T("有数据到达客户端:%s:长度:%d,数据:%s\r\n%s"),lpszClientAddr,nLen,lpszRecvBuffer,tszMsgBuffer);
pClass_This->m_EditRecvMsg.SetWindowText(tszClientMsg);
}
void CTestFor_IOCPServerDlg::IOCP_CBLeave_Func(LPCSTR lpszClientAddr, SOCKET hSocket,LPVOID lParam)
{
CTestFor_IOCPServerDlg *pClass_This = (CTestFor_IOCPServerDlg *)lParam;
TCHAR tszMsgBuffer[4096];
TCHAR tszClientMsg[2048];
memset(tszClientMsg,'\0',sizeof(tszClientMsg));
memset(tszMsgBuffer,'\0',sizeof(tszMsgBuffer));
pClass_This->m_EditRecvMsg.GetWindowText(tszMsgBuffer,sizeof(tszMsgBuffer));
_stprintf_s(tszClientMsg,_T("有客户离开:%s\r\n%s"),lpszClientAddr,tszMsgBuffer);
pClass_This->m_EditRecvMsg.SetWindowText(tszClientMsg);
}
void CTestFor_IOCPServerDlg::OnBnClickedButton1()
{
// TODO: 在此添加控件通知处理程序代码
if (!NetCore_TCPIocp_StartEx(&xhNet, 5000))
{
AfxMessageBox(_T("初始化失败!"));
return;
}
NetCore_TCPIocp_RegisterCallBackEx(xhNet, IOCP_CBConnect_Func, IOCP_CBRecv_Func, IOCP_CBLeave_Func, this, this, this);
AfxMessageBox(_T("开启成功"));
}
如果是LINUX版本,你需要启用EPOLL_IOCP,请注意:无论是IOCP还是EPOLL,底层都是多线程顺序网络.你无需关心底层细节
XNETHANDLE xhNet;
BOOL NetCore_CBLogin(LPCTSTR lpszClientAddr,SOCKET hSocket,LPVOID lParam)
{
NetCore_TCPEPoll_SetLimitEx(xhNet, lpszClientAddr, 10, _T("hello"));
printf("%s,%d,进入服务器\n",lpszClientAddr,hSocket);
return TRUE;
}
void NetCore_CBRecv(LPCTSTR lpszClientAddr,SOCKET hSocket,LPCTSTR lpszRecvMsg,int nMsgLen,LPVOID lParam)
{
printf("%s,%d,数据:%d,%s\n",lpszClientAddr,hSocket,nMsgLen,lpszRecvMsg);
}
void NetCore_CBLeave(LPCTSTR lpszClientAddr,SOCKET hSocket,LPVOID lParam)
{
printf("%s,%d离开服务器\n",lpszClientAddr,hSocket);
}
int main()
{
if (!NetCore_TCPEPoll_StartEx(&xhNet))
{
perror("main");
}
NetCore_TCPEPoll_RegisterCallBackEx(xhNet,NetCore_CBLogin,NetCore_CBRecv,NetCore_CBLeave);
printf("启动成功\n");
for (int i = 0;i < 999999;i++)
{
sleep(100);
}
NetCore_TCPEPoll_DestroyEx(xhNet);
return 0;
}
这样,就启动了服务器.
他们监听端口是5000.在使用客户端的时候,可以进行测试连接到TCP 5000端口.IP地址使用本地IP 127.0.0.1
这样,你的高性能网络服务器就启动完毕了,你可以在回调函数里面处理数据,当然,我们并不推荐你这样做,因为这样会阻塞网络IO.对于一个C/S服务器,或者B/S服务器,服务端都需要通过指定协议和来判断要处理的程序来编写代码流程.而且,由于是TCP是流式的,所以你需要自己定义一个包头来解决流式TCP所造成的"沾包"问题.
这里,你可以使用我们的组包模块来解决这个问题,使用这个模块,你必须使用我们的标准协议头,这个协议头是 NETENGINE_PROTOCOLHDR
//在你的启动函数插入初始化组包模块代码,这里的第四个参数的4 表示我们使用了4个业务线程来处理业务.后面会说到如何使用线程池来处理业务
if (!HelpComponents_Datas_Init(&xhPacket, 10000, 0, 4))
{
printf("HelpComponents_Datas_Init:%lX\n", Packets_GetLastError());
return -1;
}
修改你的网络代码,在用户连接成功后,执行下面的代码,来创建一个组包服务
//第三个参数0表示由模块确定这个客户端由哪个任务池返回
if (!HelpComponents_Datas_CreateEx(xhPacket, lpszClientAddr, 0))
{
printf("HelpComponents_Datas_CreateEx:%lX\n", Packets_GetLastError());
return -1;
}
现在,我们可以投递数据了,在你的服务中,在接受数据的回调函数里面直接调用下面的代码,让数据投递给组包模块处理
HelpComponents_Datas_PostEx(xhPacket, lpszClientAddr, tszMsgBuffer,nMsgLen);
然后,我们通过下面的函数来获得要处理的包,当然,这个处理函数需要在一个单独的线程中来处理
HelpComponents_Datas_WaitEventEx(xhPacket, 1);
list<HELPCOMPONENT_PACKET_CLIENT> stl_ListAddr;
HelpComponents_Datas_GetPoolEx(xhPacket, 1, &stl_ListAddr);
list<HELPCOMPONENT_PACKET_CLIENT>::const_iterator stl_ListIterator = stl_ListAddr.begin();
for (; stl_ListIterator != stl_ListAddr.end(); stl_ListIterator++)
{
for (int i = 0;i < stl_ListIterator->nPktCount;i++)
{
HelpComponents_Datas_GetEx(xhPacket, stl_ListIterator->tszClientAddr, tszMsgBuffer, &nMsgLen, &st_ProtocolHdr);
printf("%d=%s\n", nMsgLen, tszMsgBuffer);
}
}
wait函数表示等待一个完整的数据包.getpool表示获取第一个任务池中有任务的客户端地址,然后进行循环处理.getex会获得指定客户端的一个完整的数据包.这个操作不会照成空命中.这样的代码可以让你在多线程下进行多任务协作并行处理