live555的核心数据结构值之闭环双向链表

live555关于RTSP协议交互流程

live555的核心数据结构值之闭环双向链表

live555 rtsp服务器实战之createNewStreamSource

live555搭建实时播放rtsp服务器

live555 rtsp服务器实战之doGetNextFrame

        live555是采用单进程,单线程的服务器,能够同时支持多个客户端连接,并且有条不紊的进行媒体流的调度,很大一部分原因在于对数据结构的巧妙应用。

        下面介绍live555核心的数据结构:闭环双向链表

什么是闭环双向链表?

描述:

        一个节点保存有前一个节点的地址和后一个节点的地址,并且最后一个节点的下一个节点地址指向第一个节点,第一个节点的前一个节点的地址指向最后一个节点;

        该链表的添加方式为头插法;

该链表有三个类进行管理(HandlerSet.hh文件)

节点的描述类(HandlerDescriptor)

class HandlerDescriptor {
  HandlerDescriptor(HandlerDescriptor* nextHandler);
  virtual ~HandlerDescriptor();

public:
  int socketNum;//套接字文件描述符
  int conditionSet;//要响应的状态(可读,可写,异常),回调函数执行的条件
  TaskScheduler::BackgroundHandlerProc* handlerProc;//执行函数
  void* clientData;//执行函数的参数

private:
  // Descriptors are linked together in a doubly-linked list:
  friend class HandlerSet;
  friend class HandlerIterator;
  HandlerDescriptor* fNextHandler;//后向指针
  HandlerDescriptor* fPrevHandler;//前向指针
};

        每个节点包含:套接字,要响应的状态,执行函数,执行函数的参数这四种信息;

链表管理类(HandlerSet)

class HandlerSet {
public:
  HandlerSet();
  virtual ~HandlerSet();

  void assignHandler(int socketNum, int conditionSet, TaskScheduler::BackgroundHandlerProc* handlerProc, void* clientData);//将套接字为socketNum的节点添加到链表
  void clearHandler(int socketNum);//删除套接字为socketNum的节点
  void moveHandler(int oldSocketNum, int newSocketNum);//修改套接字,套接字改变时,使用新的套接字替换旧的套接字

private:
  HandlerDescriptor* lookupHandler(int socketNum);//查找节点

private:
  friend class HandlerIterator;
  HandlerDescriptor fHandlers;
};

        该类提供了操作链表的方法:添加节点,删除节点,查找节点,修改节点(只修改套接字,执行函数,状态,参数都不变)

链表迭代器类(HandlerIterator)

class HandlerIterator {
public:
  HandlerIterator(HandlerSet& handlerSet);
  virtual ~HandlerIterator();

  HandlerDescriptor* next(); // 遍历到下个节点
  void reset();

private:
  HandlerSet& fOurSet;
  HandlerDescriptor* fNextPtr;
};

        这个类很简单,next方法仅仅是获取fNextPtr的下个节点的地址,然后把新节点的地址赋值给fNextPtr;如果下个节点是开始遍历时的节点返回NULL;

执行流程

        程序初始化时(创建BasicTaskScheduler对象的时候),链表初始化为自己指向自己;

HandlerSet::HandlerSet()
  : fHandlers(&fHandlers) {
  fHandlers.socketNum = -1; // shouldn't ever get looked at, but in case...
}

        创建套接字(tcp/udp)赋值给节点socketNum,将该套接字要执行的逻辑通过回调函数赋值给handlerProc,将要响应的状态赋值给conditionSet;如果套接字的状态变化为conditionSet,则执行handlerProc。

//将节点信息:套接字,状态,回调函数参数传入
//fOurSocket:赋值到socketNum
//SOCKET_READABLE | SOCKET_EXCEPTION : 赋值到conditionSet
//incomingRequestHandler : 赋值到handlerProc
//this : 赋值到clientData

envir().taskScheduler().setBackgroundHandling(fOurSocket, SOCKET_READABLE | SOCKET_EXCEPTION, incomingRequestHandler, this);

//头插法将节点添加到链表中
void BasicTaskScheduler ::setBackgroundHandling(int socketNum, int conditionSet, BackgroundHandlerProc *handlerProc, void *clientData)
{
  if (socketNum < 0)
    return;
#if !defined(__WIN32__) && !defined(_WIN32) && defined(FD_SETSIZE)
  if (socketNum >= (int)(FD_SETSIZE))
    return;
#endif
  FD_CLR((unsigned)socketNum, &fReadSet);
  FD_CLR((unsigned)socketNum, &fWriteSet);
  FD_CLR((unsigned)socketNum, &fExceptionSet);
  if (conditionSet == 0)
  {
    fHandlers->clearHandler(socketNum);
    if (socketNum + 1 == fMaxNumSockets)
    {
      --fMaxNumSockets;
    }
  }
  else
  {
    fHandlers->assignHandler(socketNum, conditionSet, handlerProc, clientData);//正式将节点插入到链表
    if (socketNum + 1 > fMaxNumSockets)
    {
      fMaxNumSockets = socketNum + 1;
    }
    if (conditionSet & SOCKET_READABLE)
      FD_SET((unsigned)socketNum, &fReadSet);
    if (conditionSet & SOCKET_WRITABLE)
      FD_SET((unsigned)socketNum, &fWriteSet);
    if (conditionSet & SOCKET_EXCEPTION)
      FD_SET((unsigned)socketNum, &fExceptionSet);
  }
}

        在主循环中doEventLoop->SingleStep会循环编译该链表,检查每个节点的套接字是否可读,可写,异常;如果某个节点有状态变化,会判断该状态变化是否为conditionSet,如果是就执行回调函数;

例如:

        有个tcp套接字;写个了逻辑函数赋值给handlerProc,设置conditionSet为READ(可读,回调函数执行的条件); 如果select监视到该套接字有状态变化,且状态是READ(可读)状态则执行回调函数;

主循环链表处理

  while ((handler = iter.next()) != NULL)//遍历链表
  {
    int sock = handler->socketNum; // alias
    int resultConditionSet = 0;
    //确定状态变化的状态
    if (FD_ISSET(sock, &readSet) && FD_ISSET(sock, &fReadSet) /*sanity check*/)
      resultConditionSet |= SOCKET_READABLE;
    if (FD_ISSET(sock, &writeSet) && FD_ISSET(sock, &fWriteSet) /*sanity check*/)
      resultConditionSet |= SOCKET_WRITABLE;
    if (FD_ISSET(sock, &exceptionSet) && FD_ISSET(sock, &fExceptionSet) /*sanity check*/)
      resultConditionSet |= SOCKET_EXCEPTION;
    //将状态变化的状态与handler->conditionSet比较,如果状态正确执行处理函数
    if ((resultConditionSet & handler->conditionSet) != 0 && handler->handlerProc != NULL)
    {
      fLastHandledSocketNum = sock;
      // Note: we set "fLastHandledSocketNum" before calling the handler,
      // in case the handler calls "doEventLoop()" reentrantly.
      (*handler->handlerProc)(handler->clientData, resultConditionSet);
      break;
    }
  }

        其实我觉得live555完全没必要使用双向闭环链表,整个流程都没用到前向节点的处理,各种处理,初始化都比较麻烦;就链表在live555中的表现来看,一个单链表就足够了;可能是为了以后添加更复杂的功能做兼容吧,方便扩展;

        至此链表在live555中起到的作用,处理流程,应用方法都很清楚了,如果有不足或者错误欢迎指正,一起探讨学习。

        后续会更新更多live555核心类,核心数据结构,接口用法等,通俗易通的方式讲解,关注我最早看到。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

迷茫的蜉蝣

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值