源码分析JRTPLIB之PollThread

       读下PollThread源码和ThreadPool源码(https://github.com/progschj/ThreadPool)可以发现采用的线程间通信都是用的全局变量stop。这里提一下PollThread应该不是指的线程池,而是一种线程内部的管理机制。这两个都采用了同一种思想就是通过读取全局变量来控制线程的情况,当有函数修改了全局变量要求线程停止,其他处理函数读取到这个变量值后把手头这个工作交代一下就停止了。就像全局变量是不定时的开饭一样,开饭了就通过全局变量通知,大家在各自的处理过程中会访问是否开饭,当得知开饭了就准备准备结束当前工作。每个人在工作的流程中不一定会同时知道开饭了,而是通过自己访问这个全局变量获知的,所以一段时间过后大家都会得知开饭这个消息,等到各自处理完结束程序之后就会来到饭堂掐饭咯。

        整体的线程通信机制了解之后接着我们就来分析里面涉及到的线程处理过程。
        在ThreadPool中处理还是比较简单的,先获取到线程中参数的类型然后通过enqueue加入任务队列tasks中,线程运行时只用考虑处理tasks中的task()函数即可(通过std::function转为函数类型),并用future来获得返回结果,以及全局变量stop的控制。workers来控制任务队列的开启以及最后的同步join收尾工作。
在PollThread中大概也是这么一种思路只是将入队以及处理任务队列中的任务操作转化为了读取Poll的数据并进行处理。其中涉及到:

  1. transmitter中的WaitForIncomingData()、Poll()、AbortWait()
  2. rtpsession中的OnPollThreadState()、OnPollThreadError()、ProcessPolledData()、OnPollThreadStep()和OnPollThreadStop()

其中transmitter负责从缓冲区Poll获得到来的数据,session负责处理数据并控制线程的生命周期。
      (我的目前理解)在transmitter和session中并没有出现JThread,JThread出现在了PollThread中,也就说明外部不用考虑多线程机制,内部考虑线程机制对每开一个PollThread就产生一个thread对该连接进行维护,一个协议的产生几条RTP连接,对于复杂并发,来一个连接建一个JThread管理就好了。ThreadPool中的多线程出现在workers中std::vector< std::thread > workers;从而每个线程可以获取到一个任务进行处理,并且可以一直管理该任务直到阻塞Join。

       现在我们来考虑涉及到的几个Poll调度函数,transmitter中的WaitForIncomingData()中的参数是由rtcpschedule中的GetTransmissionDelay()来获取取数据的等待时延,超时则该PollThread自动关闭。rtcpschedule是根据几个RTCP包中字段来判断网络情况的,这个之后涉及到packet时再详细说。
      当没有超时的时候我们进行下一步transmitter中的Poll()获取数据的情况。Poll()中调用了PollSocket()函数,这个函数也就是之前在上一篇源码分析中提到的从缓冲区取得到达的数据并封装一下放入rawpacketlist中。可以在GetNextPacket()函数中取出。

源码分析JRTPLIB之PollThread_第1张图片

       AbortWait()也就是用的信号机制,关于abort的信号控制
在transmitter中调用rtpabortdescriptors.h中的函数做了几件事一个是发送终止信号SendAbortSignal(),一个是Destroy(),一个是读取终止信号ReadSignallingByte(),这几个函数用于RTPUDPv4Transmitter中进行PollThread的终止控制

源码分析JRTPLIB之PollThread_第2张图片

可以看到在RTPAbortDescriptors.h中定义了两种方法:
1.一种是winsock,新建了两个PF_INET的套接字,一个m_descriptors[0]绑定读端口一个m_descriptors[1]绑定写端口,m_descriptors[1]然后当需要发送终止信号就发送至m_descriptors[1],由于m_descriptors[1]监听m_descriptors[0],判断是否接收到终止信号就从m_descriptors[0]读一下buf,有"*"则表明需要终止。
2.一种是unix-style使用的是读写管道的方法来传递终止信号,也就是fd[1]和fd[0]形成了一个管道,要发送终止信号时从fd[1]写入,当判断是否读取时从fd[0]管道端读出即可。

int RTPAbortDescriptors::Init()//管道的写法,使用read和write。
//如果用的是两个套接字则是使用send和recv。
{
	if (m_init)
		return ERR_RTP_ABORTDESC_ALREADYINIT;

	if (pipe(m_descriptors) < 0)
		return ERR_RTP_ABORTDESC_CANTCREATEPIPE;

	m_init = true;
	return 0;
}

void RTPAbortDescriptors::Destroy()
{
	if (!m_init)
		return;

	close(m_descriptors[0]);
	close(m_descriptors[1]);
	m_descriptors[0] = RTPSOCKERR;
	m_descriptors[1] = RTPSOCKERR;

	m_init = false;
}

int RTPAbortDescriptors::SendAbortSignal()
{
	if (!m_init)
		return ERR_RTP_ABORTDESC_NOTINIT;

	if (write(m_descriptors[1],"*",1))
	{
		// To get rid of __wur related compiler warnings
	}

	return 0;
}

int RTPAbortDescriptors::ReadSignallingByte()
{
	if (!m_init)
		return ERR_RTP_ABORTDESC_NOTINIT;

	unsigned char buf[1];

	if (read(m_descriptors[0],buf,1))
	{
		// To get rid of __wur related compiler warnings
	}
	return 0;
}

        由rtpsession中的ProcessPolledData()函数(ProcessPolledData()中会调用transmitter的GetNextPacket()函数取得链表结构,也就是设置了一个链表用于存储Poll从缓冲区得到的数据)将Poll得到的数据进行处理,这个处理会拆封取得的数据并封装成新的数据包对外发出。取得什么数据?发出的数据是音视频数据流没错的,发出的数据等到时候分析packet的时候再在这里补回来。
       其他几个相关的OnPollThread*()函数定义成为虚函数,如果需要定义可以创建自己的RTPSession中并重写这些函数,默认这些函数是未定义的。并且也是在支持Poll线程的情况下可用,说是这么说要是不下载JThread库编译都过不了的。

源码分析JRTPLIB之PollThread_第3张图片

你可能感兴趣的:(源码分析JRTPLIB之PollThread)