上文提到的UDPSocket,只是一个简单的socket封装,被动地调用。本文仿照MFC的CAsyncSocket方式做一个包装调用,不过事件响应采用select,不用MFC的窗口消息。
简单接口如下:
typedef void (*udp_data_cb)(int sockid, char *data, int len, int ip, int port, int timestamp, void* param);
class CUDPSession
{
CUDPSession();
virtual ~CUDPSession();
int Open(uint16_t usLocalPort, udp_data_cb cb, void* param);
int Close();
int Send(char* pBuff,uint32_t nLen,uint32_t ip,uint16_t port);
private:
udp_data_cb m_pCB;
void* m_pParam;
private:
static void* EventLoop(void* lpParameter );
void ProcessEvent();
void OnTimer();
int OnRecv(char* pBuff,uint32_t nLen, uint32_t ip,uint16_t port);
bool m_bEventLoopStarted;
CUDPSock m_udpIO;
pthread_t m_tid;
};
出于通用和简便上考虑,线程采用了pthread形式。在windwos下,可以参考:https://www.sourceware.org/pthreads-win32/。
这个项目把windows的线程使用重新封装了一下,话说windows原生的线程函数实在是.....。
class实现只展示几个重点函数,其他在源码里。
int CUDPSession::Open(uint16_t usLocalPort, udp_data_cb cb, void* param)
{
m_pCB = cb;
m_pParam = param;
if (m_bEventLoopStarted)
return 0;
int iResult = m_udpIO.Open(usLocalPort);
if (iResult > 0)
{
pthread_create (&m_tid, NULL, EventLoop, this);
}
return iResult;
}
int CUDPSession::Close()
{
if (m_bEventLoopStarted)
{
m_bEventLoopStarted = false;
pthread_join(tid, NULL);
m_udpIO.Close();
}
return 0;
}
void* CUDPSession::EventLoop(void* lpParameter )
{
CUDPSession* pThis = (CUDPSession*)lpParameter;
pThis->ProcessEvent();
return 0;
}
void CUDPSession::ProcessEvent()
{
timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 50000; //50ms
fd_set fsRead;
char szBuffer[1500];
int iReceived = 0;
int iErrorCode = 0;
m_bEventLoopStarted = true;
bool bLastSend = false;
while (m_bEventLoopStarted)
{
FD_ZERO( &fsRead );
FD_SET(m_udpIO.m_handle, &fsRead);
int ret = select (1024, &fsRead, NULL, NULL, &timeout);
if (ret < 0)
{
//err
}
else if (ret == 0)
{
OnTimer();
}
else
{
if(FD_ISSET( m_udpIO.m_handle, &fsRead))
{
uint32_t ip;
uint16_t port;
iReceived = m_udpIO.Receive(szBuffer, 1500, ip, port);
if (iReceived > 0)
{
OnRecv(szBuffer, iReceived, ip, port);
}
else
{
//error
}
}
}
timeout.tv_sec = 0;
timeout.tv_usec = 50000;
}
}
void CUDPSession::OnTimer()
{
struct timeval cur;
gettimeofday(&cur, NULL);
int timestamp = (cur.tv_sec-start_time.tv_sec)*1000 + (cur.tv_usec - start_time.tv_usec)/1000;
//start_time是一个时间标志,在合适的位置初始化一下,gettimeofday(&start_time, NULL);定时时间到就重置。
}
#if defined(__WIN32__) || defined(_WIN32)
// For Windoze, we need to implement our own gettimeofday()
// used to make sure that static variables in gettimeofday() aren't initialized simultaneously by multiple threads
static LONG initializeLock_gettimeofday = 0;
#if !defined(_WIN32_WCE)
#include
#endif
int gettimeofday(struct timeval* tp, int* /*tz*/) {
static LARGE_INTEGER tickFrequency, epochOffset;
static BOOL isInitialized = FALSE;
LARGE_INTEGER tickNow;
#if !defined(_WIN32_WCE)
QueryPerformanceCounter(&tickNow);
#else
tickNow.QuadPart = GetTickCount();
#endif
if (!isInitialized) {
if(1 == InterlockedIncrement(&initializeLock_gettimeofday)) {
#if !defined(_WIN32_WCE)
// For our first call, use "ftime()", so that we get a time with a proper epoch.
// For subsequent calls, use "QueryPerformanceCount()", because it's more fine-grain.
struct timeb tb;
ftime(&tb);
tp->tv_sec = tb.time;
tp->tv_usec = 1000*tb.millitm;
// Also get our counter frequency:
QueryPerformanceFrequency(&tickFrequency);
#else
/* FILETIME of Jan 1 1970 00:00:00. */
const LONGLONG epoch = 116444736000000000LL;
FILETIME fileTime;
LARGE_INTEGER time;
GetSystemTimeAsFileTime(&fileTime);
time.HighPart = fileTime.dwHighDateTime;
time.LowPart = fileTime.dwLowDateTime;
// convert to from 100ns time to unix timestamp in seconds, 1000*1000*10
tp->tv_sec = (long)((time.QuadPart - epoch) / 10000000L);
/*
GetSystemTimeAsFileTime has just a seconds resolution,
thats why wince-version of gettimeofday is not 100% accurate, usec accuracy would be calculated like this:
// convert 100 nanoseconds to usec
tp->tv_usec= (long)((time.QuadPart - epoch)%10000000L) / 10L;
*/
tp->tv_usec = 0;
// resolution of GetTickCounter() is always milliseconds
tickFrequency.QuadPart = 1000;
#endif
// compute an offset to add to subsequent counter times, so we get a proper epoch:
epochOffset.QuadPart
= tp->tv_sec * tickFrequency.QuadPart + (tp->tv_usec * tickFrequency.QuadPart) / 1000000L - tickNow.QuadPart;
// next caller can use ticks for time calculation
isInitialized = TRUE;
return 0;
} else {
InterlockedDecrement(&initializeLock_gettimeofday);
// wait until first caller has initialized static values
while(!isInitialized){
Sleep(1);
}
}
}
// adjust our tick count so that we get a proper epoch:
tickNow.QuadPart += epochOffset.QuadPart;
tp->tv_sec = (long)(tickNow.QuadPart / tickFrequency.QuadPart);
tp->tv_usec = (long)(((tickNow.QuadPart % tickFrequency.QuadPart) * 1000000L) / tickFrequency.QuadPart);
return 0;
}
#endif