重温WIN32 API ------ 一个简单的UDP服务器类

最近一个项目需要使用简单的UDP进行通信,为方便调用,使用C++类封装了一个简单的UDP服务器类。

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。

 

#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);
}


 

你可能感兴趣的:(重温WIN32 API ------ 一个简单的UDP服务器类)