重温WIN32 API ------ 一个简单的UDP服务器类
2014-12-22 18:07
260 查看
最近一个项目需要使用简单的UDP进行通信,为方便调用,使用C++类封装了一个简单的UDP服务器类。
多线程程序必须要考虑同步的问题。主线程通过线程安全地设置一个变量来通知UDP侦听线程退出,为防止侦听线程中的recvfrom()一直阻塞而无法退出线程,主线程采用closesocket()来强制侦听线程的recvfrom()返回。(其他让recvfrom退出的方法包括发送专用udp数据包)
另外类使用者需要注意的是,回调函数是在侦听线程中执行,所以要避免非UI线程直接更新UI的问题。回调函数如果涉及到窗口UI操作,需要处理数据后通过SendMessage()的方式通知UI线程,然后由UI线程来实际执行UI更新操作。
测试代码:
1基本思路
网络通信程序设计中最难的部分就是IO的处理,不同操作系统平台提供不同的IO处理机制,Windows平台有select模型、完成端口等,Linux平台则是poll和epoll。由于本项目要求简单,通信量也不大,所以没有采用这些与平台相关的IO模型,而是采用简单的专用线程来负责侦听。当收到数据包时,自动调用用户指定的回调函数,算是设计模式中”订阅模式“的简单实现,也是来自于模仿C#中的event机制。多线程程序必须要考虑同步的问题。主线程通过线程安全地设置一个变量来通知UDP侦听线程退出,为防止侦听线程中的recvfrom()一直阻塞而无法退出线程,主线程采用closesocket()来强制侦听线程的recvfrom()返回。(其他让recvfrom退出的方法包括发送专用udp数据包)
另外类使用者需要注意的是,回调函数是在侦听线程中执行,所以要避免非UI线程直接更新UI的问题。回调函数如果涉及到窗口UI操作,需要处理数据后通过SendMessage()的方式通知UI线程,然后由UI线程来实际执行UI更新操作。
2代码实现
就一个UDPServer类,头文件UDPServer.h,实现文件UDPServer.cpp。#pragmaonce #include<WinSock2.h> #include<Ws2tcpip.h> #include<iphlpapi.h> #pragmacomment(lib,"IPHLPAPI.lib") #pragmacomment(lib,"WS2_32") #include<vector> usingnamespacestd; typedefvoid(*UDPRecvFun)(void*sender,BYTE*,int);//收到UDP数据包后的回调函数原型 /* UDP服务器类 功能描述: 建立UDP侦听,建立一个专用线程负责接收UDP数据包。该类采用类似于C#的事件驱动设计模式。 使用样例: voidAfterRecv(void*sender,BYTE*data,intlen)//回调函数实现 { ...... } UDPServerpServer=newUDPServer("127.0.0.1",8888); pServer.AddCallback(AfterRecv);//增加一个回调,收到UDP包后会自动调用此函数 pServer->StartUDPServer();//开始侦听 pServer->StopUDPServer();//可选,因为析构函数会自动调用此方法 deletepServer; */ classUDPServer { public: UDPServer(unsignedintip,unsignedshortport); UDPServer(char*ip,unsignedshortport); ~UDPServer(); protected: staticDWORDWINAPIUDPServerThreadFunc(void*state); voidStartUDPServer(unsignedintip,unsignedshortport); voidStartUDPServer(char*ip,unsignedshortport); voidOnRecv(BYTE*,int); private: unsignedlongIP; unsignedshortport; SOCKETsock;//socket HANDLEtid;//侦听线程ID CRITICAL_SECTIONcs;//线程同步用 intisEnd;//是否终止侦听 vector<UDPRecvFun>listOnRecv;//收到UDP包后的回调函数列表 public: SOCKETGetSocket(){returnthis->sock;} protected: intGetIsEnd(); voidSetIsEnd(intn); public: voidStartUDPServer(); voidStopUDPServer(); voidAddCallback(UDPRecvFuncb);//增加一个回调函数 };
#include"stdafx.h" #include"UDPServer.h" #include"LogWriter.h" UDPServer::UDPServer(unsignedintip,unsignedshortport) { this->sock=NULL; this->tid=NULL; this->listOnRecv.clear(); this->IP=ip; this->port=port; ::InitializeCriticalSection(&this->cs); isEnd=0; } UDPServer::UDPServer(char*ip,unsignedshortport) { this->sock=NULL; this->tid=NULL; this->listOnRecv.clear(); this->IP=::inet_addr(ip); this->port=port; ::InitializeCriticalSection(&this->cs); isEnd=0; } UDPServer::~UDPServer() { if(this->tid!=NULL) { StopUDPServer(); } ::DeleteCriticalSection(&this->cs); } /* 功能:线程安全地设置isEnd */ voidUDPServer::SetIsEnd(intn) { ::EnterCriticalSection(&this->cs); this->isEnd=n; ::LeaveCriticalSection(&this->cs); } /* 功能:线程安全地读取isEnd */ intUDPServer::GetIsEnd() { ::EnterCriticalSection(&this->cs); intr=this->isEnd; ::LeaveCriticalSection(&this->cs); returnr; } /* 功能:停止UDP服务器(线程) */ voidUDPServer::StopUDPServer() { if(this->tid!=NULL) { this->SetIsEnd(1); //设置停止标记 ::closesocket(this->sock);//唤醒recvfrom()//::shutdown(this->sock,SD_BOTH);不好用 this->sock=NULL; ::WaitForSingleObject(this->tid,INFINITE);//等待UDP专用线程结束 this->tid=NULL; } } /* 功能:调用回调列表里的所有函数 */ voidUDPServer::OnRecv(BYTE*data,intlen) { vector<UDPRecvFun>::iteratorit; for(it=this->listOnRecv.begin();it!=this->listOnRecv.end();it++) { if(*it!=NULL) { (*it)(this,data,len); } } } /* 功能:启动UDP侦听服务器 参数:ip-侦听IP字符串;port-侦听端口 */ voidUDPServer::StartUDPServer(char*ip,unsignedshortport) { unsignedlongnIP=::inet_addr(ip); if(nIP==INADDR_NONE) { LOG(L"IP地址%s错误"); return; } this->StartUDPServer(nIP,port); } /* 线程函数传参结构,用于安全地向子线程传参 */ classUDPThreadState { public: UDPServer*obj; SOCKETscok; }; /* 功能:开启UDP侦听线程,本函数不阻塞 参数:ip-侦听IP,port-侦听端口 */ voidUDPServer::StartUDPServer(unsignedintip,unsignedshortport) { if(this->sock!=NULL)return; this->sock=::socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP); if(sock==INVALID_SOCKET) { this->sock=NULL; LOG(L"UDP服务器创建socket失败"); return; } sockaddr_insin; sin.sin_family=AF_INET; sin.sin_port=htons(port); sin.sin_addr.S_un.S_addr=ip; if(::bind(sock,(sockaddr*)&sin,sizeof(sin))==SOCKET_ERROR) { LOG(L"bind()failed"); return; } wchar_tlog[1024]; swprintf_s(log,L"UDP服务器绑定到了%S:%d", ::inet_ntoa(sin.sin_addr),::ntohs(sin.sin_port));//注意%S与%s LOG(log); UDPThreadState*state=newUDPThreadState(); state->obj=this; state->scok=sock; this->tid=::CreateThread(NULL,0,UDPServerThreadFunc,(void*)state,NULL,NULL); if(tid==NULL) { deletestate; } } /* 功能:UDP侦听线程函数,静态函数 参数:符合ThreadProc规范要求 */ DWORDUDPServer::UDPServerThreadFunc(void*state) { //解析线程函数参数 UDPThreadState*pstate=(UDPThreadState*)state; UDPServer*obj=pstate->obj; SOCKETsockServer=pstate->scok; deletepstate;pstate=NULL; wchar_tlog[100]; swprintf_s(log,L"UDP侦听线程%d已经启动",::GetCurrentThreadId()); LOG(log); charbuff[1024]; sockaddr_inremoteAddr; intnLen=sizeof(remoteAddr); while(1) { if(1==obj->GetIsEnd()) { break; } intn=::recvfrom(sockServer,buff,1024,0,(sockaddr*)&remoteAddr,&nLen); if(n==SOCKET_ERROR) { wchar_tlog[128]; swprintf_s(log,L"recvfrom返回错误号:%d",::WSAGetLastError()); LOG(log); } elseif(n==0) { LOG(L"socket关闭,recvfrom()退出"); } else { wchar_tlog[128]; swprintf_s(log,L"接收到UDP包,大小%d字节,来自%S:%d", n,::inet_ntoa(remoteAddr.sin_addr),::ntohs(remoteAddr.sin_port));//注意%S与%s LOG(log); obj->OnRecv((BYTE*)buff,n); } } swprintf_s(log,L"UDP侦听线程%d退出",::GetCurrentThreadId()); LOG(log); return0; } /* 功能:启动侦听 */ voidUDPServer::StartUDPServer() { this->StartUDPServer(this->IP,this->port); } /* 功能:向回调函数链表中增加一个回调函数 参数:cb-用户定义的回调函数名 */ voidUDPServer::AddCallback(UDPRecvFuncb) { this->listOnRecv.push_back(cb); }
测试代码:
Radar::Radar() { //建立服务器1 this->pUDPServer=newUDPServer(”172.16.35.144",8888); this->pUDPServer->AddCallback(Radar::AfterRecvUDP); //建立服务器2 pUDPServer2=newUDPServer("172.16.35.144",9999); pUDPServer2->AddCallback(Radar::AfterRecvUDP); }
//回调函数
voidRadar::AfterRecvUDP(void*sender,BYTE*data,intlen)
{
data[len]=0;
::MessageBoxA(::AfxGetApp()->GetMainWnd()->m_hWnd,(char*)data,"C",MB_OK);
}
相关文章推荐
- win32 api 创建窗口的一个简单例子
- Win32 API 多线程编程——一个简单实例(含消息参数传递)
- 用UDP实现可靠文件传输,如何利用UDX创建一个简单的WIN32程序
- 用UDP实现可靠文件传输,如何利用UDX创建一个简单的WIN32程序
- 重温WIN32 API ------ 最简单的Windows窗口封装类
- 一个简单的俄罗斯方块的Win32 API实现
- 一个简单的Win32程序(不用MFC)
- Win32汇编教程三 一个简单的对话框 --- 兼谈资源文件的使用
- Win32汇编教程三 一个简单的对话框 --- 兼谈资源文件的使用
- Win32汇编教程四 编写一个简单的窗口
- 用win32 api(非mfc)实现的一个圆形按钮以及WM_COMMAND消息
- 重温Struts,完整的一个简单例子
- 一个简单的 UDP网络程序
- 使用winsock+UDP写的一个简单接收端程序
- 一个Win32 API实例类(代码收集)
- 一个win32平台的socket简单程序
- 用C#设计一个基于UDP协议的简单网络聊天器
- 一个简单的UDP广播程序
- 一个简单的win32程序的结构
- Java持久性API(The Java Persistence API)-一个简单的实体持久性编程模型