Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1282571
  • 博文数量: 79
  • 博客积分: 1959
  • 博客等级: 上尉
  • 技术积分: 2719
  • 用 户 组: 普通用户
  • 注册时间: 2007-11-19 12:07
个人简介

樽中酒不空

文章分类

全部博文(79)

文章存档

2024年(3)

2020年(4)

2019年(1)

2017年(2)

2016年(2)

2015年(7)

2014年(11)

2013年(13)

2012年(18)

2011年(2)

2010年(16)

分类: C/C++

2014-10-10 09:48:35

      上文提到的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下,可以参考:。
这个项目把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);定时时间到就重置。
}


代码非常简单,只要熟悉select的使用就全理解了。主要讲讲OnTimer,这里是借用select实现的一个定时器,(这里是50ms,根据情况可以调整精度,不过时间过小,会引起CPU占用升高)。但是当有事件时,timeout.tv_usec = 50000; 并不精确,所以需要gettimeofday得到当前的时间,再减去开始的时间start_time,得到一个时间跨度。gettimeofday是linux函数,windows api没有提供,不过很多开源项目里都有,这里从live555 copy了一个:





#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

以上是一个UDP会话的整个封装,用来传文件等自定义大小的数据直接用就可以了。不过,用来传实时视频的话,部分视频帧肯定超过1500了,所以需要分包,收到后再生组。下文主要讲分包重组,然后代码再一起提交。


阅读(5823) | 评论(0) | 转发(2) |
给主人留下些什么吧!~~