最近一个项目需要使用简单的UDP进行通信,为方便调用,使用C++类封装了一个简单的UDP服务器类。
网络通信程序设计中最难的部分就是IO的处理,不同操作系统平台提供不同的IO处理机制,Windows平台有select模型、完成端口等,Linux平台则是poll和epoll。由于本项目要求简单,通信量也不大,所以没有采用这些与平台相关的IO模型,而是采用简单的专用线程来负责侦听。当收到数据包时,自动调用用户指定的回调函数,算是设计模式中”订阅模式“的简单实现,也是来自于模仿C#中的event机制。
多线程程序必须要考虑同步的问题。主线程通过线程安全地设置一个变量来通知UDP侦听线程退出,为防止侦听线程中的recvfrom()一直阻塞而无法退出线程,主线程采用closesocket()来强制侦听线程的recvfrom()返回。(其他让recvfrom退出的方法包括发送专用udp数据包)
另外类使用者需要注意的是,回调函数是在侦听线程中执行,所以要避免非UI线程直接更新UI的问题。回调函数如果涉及到窗口UI操作,需要处理数据后通过SendMessage()的方式通知UI线程,然后由UI线程来实际执行UI更新操作。
就一个UDPServer类,头文件UDPServer.h,实现文件UDPServer.cpp。
#pragma once #include <WinSock2.h> #include <Ws2tcpip.h> #include <iphlpapi.h> #pragma comment(lib, "IPHLPAPI.lib") #pragma comment(lib, "WS2_32") #include <vector> using namespace std; typedef void(*UDPRecvFun)(void* sender, BYTE*, int); // 收到UDP数据包后的回调函数原型 /* UDP服务器类 功能描述: 建立UDP侦听,建立一个专用线程负责接收UDP数据包。该类采用类似于C#的事件驱动设计模式。 使用样例: void AfterRecv(void* sender, BYTE* data, int len) // 回调函数实现 { ...... } UDPServer pServer = new UDPServer("127.0.0.1", 8888); pServer.AddCallback(AfterRecv); // 增加一个回调, 收到UDP包后会自动调用此函数 pServer->StartUDPServer(); // 开始侦听 pServer->StopUDPServer(); // 可选,因为析构函数会自动调用此方法 delete pServer; */ class UDPServer { public: UDPServer(unsigned int ip, unsigned short port); UDPServer(char* ip, unsigned short port); ~UDPServer(); protected: static DWORD WINAPI UDPServerThreadFunc(void* state); void StartUDPServer(unsigned int ip, unsigned short port); void StartUDPServer(char* ip, unsigned short port); void OnRecv(BYTE*, int); private: unsigned long IP; unsigned short port; SOCKET sock; // socket HANDLE tid; // 侦听线程ID CRITICAL_SECTION cs; // 线程同步用 int isEnd; // 是否终止侦听 vector<UDPRecvFun> listOnRecv; // 收到UDP包后的回调函数列表 public: SOCKET GetSocket(){ return this->sock; } protected: int GetIsEnd(); void SetIsEnd(int n); public: void StartUDPServer(); void StopUDPServer(); void AddCallback(UDPRecvFun cb); // 增加一个回调函数 };
#include "stdafx.h" #include "UDPServer.h" #include "LogWriter.h" UDPServer::UDPServer(unsigned int ip, unsigned short port) { this->sock = NULL; this->tid = NULL; this->listOnRecv.clear(); this->IP = ip; this->port = port; ::InitializeCriticalSection(&this->cs); isEnd = 0; } UDPServer::UDPServer(char* ip, unsigned short port) { 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 */ void UDPServer::SetIsEnd(int n) { ::EnterCriticalSection(&this->cs); this->isEnd = n; ::LeaveCriticalSection(&this->cs); } /* 功能: 线程安全地读取isEnd */ int UDPServer::GetIsEnd() { ::EnterCriticalSection(&this->cs); int r = this->isEnd; ::LeaveCriticalSection(&this->cs); return r; } /* 功能:停止UDP服务器(线程) */ void UDPServer::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; } } /* 功能:调用回调列表里的所有函数 */ void UDPServer::OnRecv(BYTE* data, int len) { vector<UDPRecvFun>::iterator it; for (it = this->listOnRecv.begin(); it != this->listOnRecv.end(); it++) { if (*it != NULL) { (*it)(this, data, len); } } } /* 功能:启动UDP侦听服务器 参数:ip-侦听IP字符串; port-侦听端口 */ void UDPServer::StartUDPServer(char* ip, unsigned short port) { unsigned long nIP = ::inet_addr(ip); if (nIP == INADDR_NONE) { LOG(L"IP 地址%s 错误"); return; } this->StartUDPServer(nIP, port); } /* 线程函数传参结构,用于安全地向子线程传参 */ class UDPThreadState { public: UDPServer* obj; SOCKET scok; }; /* 功能:开启UDP侦听线程, 本函数不阻塞 参数:ip - 侦听IP,port-侦听端口 */ void UDPServer::StartUDPServer(unsigned int ip, unsigned short port) { 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_in sin; 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_t log[1024]; swprintf_s(log, L"UDP服务器绑定到了 %S:%d", ::inet_ntoa(sin.sin_addr), ::ntohs(sin.sin_port)); // 注意%S与%s LOG(log); UDPThreadState* state = new UDPThreadState(); state->obj = this; state->scok = sock; this->tid = ::CreateThread(NULL, 0, UDPServerThreadFunc, (void*)state, NULL, NULL); if (tid == NULL) { delete state; } } /* 功能:UDP 侦听线程函数,静态函数 参数:符合ThreadProc规范要求 */ DWORD UDPServer::UDPServerThreadFunc(void* state) { // 解析线程函数参数 UDPThreadState* pstate = (UDPThreadState*)state; UDPServer* obj = pstate->obj; SOCKET sockServer = pstate->scok; delete pstate; pstate = NULL; wchar_t log[100]; swprintf_s(log, L"UDP侦听线程 %d 已经启动", ::GetCurrentThreadId()); LOG(log); char buff[1024]; sockaddr_in remoteAddr; int nLen = sizeof(remoteAddr); while (1) { if (1 == obj->GetIsEnd()) { break; } int n = ::recvfrom(sockServer, buff, 1024, 0, (sockaddr*)&remoteAddr, &nLen); if (n == SOCKET_ERROR) { wchar_t log[128]; swprintf_s(log, L"recvfrom返回错误号:%d", ::WSAGetLastError()); LOG(log); } else if (n == 0) { LOG(L"socket关闭,recvfrom() 退出"); } else { wchar_t log[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); return 0; } /* 功能:启动侦听 */ void UDPServer::StartUDPServer() { this->StartUDPServer(this->IP, this->port); } /* 功能:向回调函数链表中增加一个回调函数 参数:cb-用户定义的回调函数名 */ void UDPServer::AddCallback(UDPRecvFun cb) { this->listOnRecv.push_back(cb); }
测试代码:
Radar::Radar() { // 建立服务器1 this->pUDPServer = new UDPServer(”172.16.35.144", 8888); this->pUDPServer->AddCallback(Radar::AfterRecvUDP); // 建立服务器2 pUDPServer2 = new UDPServer("172.16.35.144", 9999); pUDPServer2->AddCallback(Radar::AfterRecvUDP); }
// 回调函数
void Radar::AfterRecvUDP(void* sender, BYTE* data, int len) { data[len] = 0; ::MessageBoxA(::AfxGetApp()->GetMainWnd()->m_hWnd, (char*)data, "C", MB_OK); }