libjingle主线程的消息响应

Posted wongdu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了libjingle主线程的消息响应相关的知识,希望对你有一定的参考价值。

 trunk\\talk\\app\\webrtc\\peerconnectionproxy.h文件中定义了PeerConnection的代理类,class PeerConnection : public PeerConnectionInterface,而#define BEGIN_PROXY_MAP(c) \\
  class c##Proxy : public c##Interface所以BEGIN_PROXY_MAP(PeerConnection)展开即为: class PeerConnectionProxy : public PeerConnectionInterface。 

      所以在peerconnection例子的talk\\examples\\peerconnection\\client\\conductor.cc文件Conductor::InitializePeerConnection()函数调用PeerConnectionFactory::CreatePeerConnection函数,该函数通过signaling_thread_对象调用PeerConnectionFactory::CreatePeerConnection_s,在该函数中调用PeerConnectionProxy::Create函数,trunk\\talk\\app\\webrtc\\proxy.h文件中对代理类的Create函数进行定义,形式如TestProxy::Create(Thread*, TestInterface*)。所以上面PeerConnectionFactory::CreatePeerConnection_s函数中调用PeerConnectionProxy::Create创建的就是PeerConnectionProxy指针对象,而且这个指针对象最终会返回给trunk\\talk\\examples\\peerconnection\\client\\conductor.h文件中的成员变量talk_base::scoped_refptr<webrtc::PeerConnectionInterface> peer_connection_;以后通过peer_connection_调用的函数其实都是PeerConnectionProxy::XXX。 

      又因为PeerConnectionProxy封装了一个talk_base::Thread* owner_thread_对象和PeerConnection指针对象,当通过PeerConnectionProxy::XXX调用函数时,会通过其成员变量指针(线程指针)调用PeerConnection::XXX,这里的线程指针就是trunk\\talk\\app\\webrtc\\peerconnectionfactory.h文件中PeerConnectionFactory类的成员变量talk_base::Thread* signaling_thread_;。 
      在trunk\\talk\\base\\win32socketserver.cc文件中,不太理解Win32SocketServer::Wait函数中的cms不为0时调用GetMessage(&msg, NULL, s_wm_wakeup_id, s_wm_wakeup_id);以及Win32SocketServer::MessageWindow::OnMessage函数中对消息s_wm_wakeup_id的处理。看了半天也没有明白,就只好在Win32SocketServer::Wait和Win32SocketServer::WakeUp中加OutputDebugPrintf来输出相关信息。
      在PeerConnectionFactory的构造函数中创建了两个talk_base::Thread线程signaling_thread_和worker_thread_。当双击呼叫对端时,主线程调用signaling_thread_线程指针对象发送MSG_INIT_FACTORY消息。然后是PeerConnectionFactory::Initialize_s函数对该消息的响应,在该函数中重置其成员变量talk_base::scoped_ptr<cricket::ChannelManager> channel_manager_;,并将worker_thread_指针对象传递给该成员,然后调用ChannelManager::Init初始化函数,继而调用ChannelManager::SetAudioOptions,在这个函数中通过worker_thread_指针以模板函数Thread::Invoke调用。又因为其响应函数ChannelManager::SetAudioOptions_w是在signaling_thread_线程消息响应中执行,所以不会执行到Win32SocketServer::Wait和Win32SocketServer::WakeUp函数中。 
      同样在响应MSG_INIT_FACTORY消息的PeerConnectionFactory::Initialize_s函数中,该函数调用的ChannelManager::Init中,当执行完上面ChannelManager::SetAudioOptions_w函数后,会调用ChannelManager::SetCaptureDevice函数,该函数类似于ChannelManager::SetAudioOptions函数,也通过worker_thread_指针以模板函数Thread::Invoke调用执行ChannelManager::SetCaptureDevice_w。 signaling_thread_线程指针发送的第二个消息是MSG_CREATE_PEERCONNECTION,第三个消息则是MSG_CREATE_AUDiosOURCE。
      第三个消息则是MSG_CREATE_AUDIOSOURCE,第四个消息则是MSG_CREATE_VIDEOSOURCE,这两个消息的发送是在双击呼叫对端后,调用Conductor::InitializePeerConnection()函数进而调用Conductor::AddStreams()函数,在该函数中依次调用PeerConnectionFactory::CreateAudioSource()和PeerConnectionFactory::CreateVideoSource()函数发送。 
      在Win32SocketServer::Wait和Win32SocketServer::WakeUp函数中添加OutputDebugPrintf来输出相关信息,当在主线程中调用signaling_thread_->Send时,进入到Thread::Send发现,先调用current_thread->socketserver()->Wait(kForever, false);将当前线程即主线程Wait阻塞,然后signaling_thread_线程的消息响应循环回去响应signaling_thread_->Send所发送的消息,即在Thread::ProcessMessages中调用MessageQueue::Get,然后在这个函数中去调用Thread::ReceiveSends,在这个函数中响应了signaling_thread_->Send所发送的消息后,调用smsg.thread->socketserver()->WakeUp();即主线程的Win32SocketServer::WakeUp,这样由于在Thread::Send函数中主线程调用了current_thread->socketserver()->Wait(kForever, false);被阻塞,现在就可以继续执行了,然后主线程中执行Thread::Send的最后一条语句current_thread->socketserver()->WakeUp();即Win32SocketServer::WakeUp。 
      这样当下次在主线程中调用signaling_thread_->Send时,在Thread::Send中调用current_thread->socketserver()->Wait(kForever, false);时并不会阻塞主线程,但是signaling_thread_->Send发送的消息还未被响应即ready变量还为false,直到signaling_thread_线程的消息响应循环在调用Thread::ReceiveSends()响应了这个消息后才将ready赋值为true。所以在Thread::Send函数中的while循环中会第二次调用current_thread->socketserver()->Wait(kForever, false);这次就会阻塞主线程。

      基于以上两段的分析,所以在主线程中调用signaling_thread_->Send时,Win32SocketServer::Wait和Win32SocketServer::WakeUp函数中消息的打印次序是,第一次调用先打印Wait,然后是打印两次WakeUp,当以后调用signaling_thread_->Send时,则是先打印两次Wait,然后再打印两次WakeUp。其中后面响应signaling_thread_->Send时,先打印的Wait事实上是相当于消耗掉在前一次执行signaling_thread_->Send发送消息时,Thread::Send函数中最后一条语句current_thread->socketserver()->WakeUp();。而第二个Wait才是阻塞当前的线程即主线程。当执行signaling_thread_->Send发送消息会打印两次WakeUp,其中第二次即Thread::Send函数中最后一条语句current_thread->socketserver()->WakeUp();而第一次则是signaling_thread_线程的消息响应循环种执行的Thread::ReceiveSends函数中的smsg.thread->socketserver()->WakeUp();。 
 在响应第四个消息MSG_CREATE_VIDEOSOURCE时,在响应该消息时会调用VideoCapturer::SetCaptureState函数,在该函数中通过主线程即main函数中的talk_base::Win32Thread w32_thread;来调用thread_->Post。由于虚函数实际执行的是MessageQueue::Post函数,该函数中会调用ss_->WakeUp();即Win32SocketServer::WakeUp,然后由于Thread::Send中while循环,此消息的响应并未完成,所以会继续Wait。 

      在响应第四个消息MSG_CREATE_VIDEOSOURCE时,PeerConnectionFactory::CreateVideoSource_s会调用VideoSource::Create函数进而调用VideoSource::Initialize,该函数又调用ChannelManager::StartVideoCapture,在这个函数中通过worker_thread_->Invoke来执行CaptureManager::StartVideoCapture,当worker_thread_线程消息响应循环中执行CaptureManager::StartVideoCapture中最终会调用VideoCapturer::SetCaptureState,而这个函数中通过主线程即main函数中的talk_base::Win32Thread w32_thread;来调用thread_->Post发送MSG_STATE_CHANGE消息。

      回到Thread::Send函数中,以主线程调用signaling_thread_->Send发送消息为例,检测到发送线程即this不是当前线程(主线程)后,就将消息调用sendlist_.push_back(smsg);保存起来,然后就开始等待消息的响应(Wait for a reply),当调用 ss_->WakeUp();(即PhysicalSocketServer::WakeUp)时,signaling_thread_线程的消息响应循环就会去响应该消息。所以有可能已经signaling_thread_线程的消息响应循环已经对该消息响应完而且调用了smsg.thread->socketserver()->WakeUp();,然后cpu才接着执行Thread::Send函数中ss_->WakeUp();之后的while循环,即current_thread->socketserver()->Wait(kForever, false);。当然这个时候会马上返回,而并不阻塞在Win32SocketServer::Wait函数中的GetMessage(&msg, NULL, s_wm_wakeup_id, s_wm_wakeup_id);,并且会执行Thread::Send函数的最后一条语句current_thread->socketserver()->WakeUp();。这些都是通过打印Win32SocketServer::Wait和Win32SocketServer::WakeUp函数中的相关信息验证得到的。 
      当双击呼叫对端候,主线程中调用signaling_thread_->Send发送消息依次为MSG_INIT_FACTORY、MSG_CREATE_PEERCONNECTION、MSG_CREATE_AUDIOSOURCE和MSG_CREATE_VIDEOSOURCE。然后在Conductor::AddStreams函数中继续执行,会继续调用signaling_thread_->Send发送消息。如下图所示:
      在Conductor::ConnectToPeer函数中调用Conductor::InitializePeerConnection函数,进而调用Conductor::AddStreams(),在Conductor::ConnectToPeer函数中执行完InitializePeerConnection后会调用PeerConnectionProxy::CreateOffer,由于PeerConnectionProxy关联了signaling_thread_所以也会调用signaling_thread_->Send发送消息。在这个步骤执行完之后就开始调用执行Win32SocketServer::MessageWindow::OnMessage函数中的ss_->Pump();(即Win32SocketServer::Pump()),最终会在该函数中调用MessageQueue::Dispatch执行VideoCapturer::OnMessage响应之前调用主线程(即talk_base::Win32Thread w32_thread;)发送的MSG_STATE_CHANGE消息。 
      为什么是在PeerConnectionProxy::CreateOffer执行后,才能执行到Win32SocketServer::MessageWindow::OnMessage函数的里面来执行ss_->Pump();,这是因为从调用signaling_thread_->Send发送第一个消息MSG_INIT_FACTORY开始,主线程中一直通过以下形式执行代码,即主线程通过调用Win32SocketServer::Wait函数中的GetMessage(&msg, NULL, s_wm_wakeup_id, s_wm_wakeup_id);来阻塞,然后Win32SocketServer::WakeUp()(signaling_thread_线程和主线程)执行PostMessage(wnd_.handle(), s_wm_wakeup_id, 0, 0);唤醒。主线程一直在响应双击呼叫对端事件,即一直在执行Conductor::ConnectToPeer,当执行完毕该函数后才有机会去在主线程中响应MSG_STATE_CHANGE消息,该消息是在Conductor::ConnectToPeer函数执行过程中通过主线程Post到主线程的消息队列中。
      接上一段当执行完PeerConnectionProxy::CreateOffer后,由于在Thread::Send的最后通过调用current_thread->socketserver()->WakeUp();,该函数即Win32SocketServer::WakeUp(),这个函数PostMessage(wnd_.handle(), s_wm_wakeup_id, 0, 0);,所以主线程中创建的Win32SocketServer对象的成员变量MessageWindow wnd_;会收到消息s_wm_wakeup_id,其实应该是主线程收到消息,因为主线程(即talk_base::Win32Thread w32_thread;)中创建了Win32SocketServer对象及其成员变量MessageWindow wnd_;,根据MSDN对PostMessage函数的解释"Places (posts) a message in the message queue associated with the thread that created the specified window and returns without waiting for the thread to process the message."。所以主线程的消息队列中收到s_wm_wakeup_id消息。这样说明了主线程通过Win32SocketServer::Wait函数的GetMessage(&msg, NULL, s_wm_wakeup_id, s_wm_wakeup_id);,而signaling_thread_线程通过Win32SocketServer::WakeUp函数中的PostMessage(wnd_.handle(), s_wm_wakeup_id, 0, 0);来唤醒,由于"函数GetMessage 是 从调用线程的消息队列里取得一个消息并将其放于指定的结构。GetMessage不接收属于其他线程或应用程序的消息。",但是通过PostMessage的MSDN注释可以明白子线程唤醒主线程。
而且在Conductor::ConnectToPeer函数中会调用主线程发送MSG_STATE_CHANGE消息,会唤醒一个主线程的等待,但是在主线程通过Thread::Send函数中的while循环判断当前的消息并没有被响应会继续阻塞,即主线程消耗掉了一次唤醒,这样当Thread::Send执行到最后,即当前消息发送的流程执行完之后需要再一次唤醒主线程(或者叫当前线程,因为这里是以主线程为例)。看一下Thread::Send函数最后的注释很详细。 
      双击呼叫对端后会执行Conductor::OnMessageFromPeer函数,然后也会调用signaling_thread_->Send发送消息,如下图所示,其中PeerConnectionProxy::AddIceCandidate可能会执行多次。 
      双击呼叫对端后会执行Conductor::OnMessageFromPeer函数,然后也会调用signaling_thread_->Send发送消息,同时打印Win32SocketServer::MessageWindow::OnMessage函数中的ss_->Pump();,发现每调用PeerConnectionProxy::AddIceCandidate执行一次后,都会打印输出ss_->Pump();,而在VideoTrackProxy::AddRenderer之后,有可能打印输出也可能不打印输出。其他就没有再打印输出ss_->Pump();的地方了。 
      当执行Conductor::OnMessageFromPeer函数,第一次调用signaling_thread_->Send发送消息是在PeerConnectionProxy::SetRemoteDescription,此时通过PostThreadMessage将消息NEW_STREAM_ADDED发送给主线程,然后主线程通过Conductor::UIThreadCallback来响应该消息两次调用signaling_thread_->Send发送消息。至此,对trunk\\talk\\base\\win32socketserver.cc文件的Win32SocketServer::Wait函数中的GetMessage(&msg, NULL, s_wm_wakeup_id, s_wm_wakeup_id);以及Win32SocketServer::MessageWindow::OnMessage函数中的对s_wm_wakeup_id消息响应的理解告一段落了。
      当双击呼叫对端时,共创建两个VideoCapturer对象,一个是主动呼叫在Conductor::InitializePeerConnection函数中通过调用Conductor::AddStreams函数在OpenVideoCaptureDevice时,通过DeviceManager类的成员变量DefaultVideoCapturerFactory指针对象创建WebRtcVideoCapturer对象;另一个是接收到对端消息,在Conductor::OnMessageFromPeer函数中通过PeerConnectionProxy来调用PeerConnection::SetRemoteDescription,如下图所示: 
     在创建两个创建两个VideoCapturer对象时,第一次将主线程赋值为VideoCapturer的成员变量talk_base::Thread* thread_;,而第二次则是将PeerConnectionFactory构造函数中创建的signaling_thread_指针对象赋值给其成员变量talk_base::Thread* thread_;。当调用ChannelManager::StartVideoCapture函数时,最终会调用VideoCapturer::SetCaptureState函数,这个函数中通过其成员变量talk_base::Thread* thread_;。会调用thread_->Post(this, MSG_STATE_CHANGE, state_params);,由前面分析,一个是发送到主线程即talk_base::Win32Thread w32_thread;,另一个则是发送到signaling_thread_线程。两次调用ChannelManager::StartVideoCapture函数的流程如下所示: 
      一开始不太明白主线程在执行Conductor::OnMessageFromPeer函数中第一次调用了signaling_thread_->Send(通过peer_connection_->SetRemoteDescription,即PeerConnectionProxy)后,并没有去打印Win32SocketServer::MessageWindow::OnMessage函数中的ss_->Pump();,因为按照之前的分析在主线程中调用signaling_thread_->Send,在Thread::Send函数的最后会调用current_thread->socketserver()->WakeUp();(即Win32SocketServer::WakeUp),主线程在main函数中通过::GetMessage(&msg, NULL, 0, 0)会收到s_wm_wakeup_id消息,所以会在Win32SocketServer类成员变量MessageWindow wnd_;的窗口过程函数中调用Win32SocketServer::MessageWindow::OnMessage(由于父窗口的过程函数及多态)来打印ss_->Pump();。但是这里并没有打印是因为在响应Conductor::OnMessageFromPeer函数中的PeerConnectionProxy::SetRemoteDescription时,调用::PostThreadMessage函数发送NEW_STREAM_ADDED消息到主线程,然后才调用Win32SocketServer::WakeUp发送s_wm_wakeup_id,所以才没有打印,再结合在响应NEW_STREAM_ADDED消息时MediaStreamProxy::GetVideoTracks和VideoTrackProxy::AddRenderer两次调用signaling_thread_->Send可以验证,即MediaStreamProxy::GetVideoTracks执行时打印两次Win32SocketServer::Wait函数中的GetMessage(&msg, NULL, s_wm_wakeup_id, s_wm_wakeup_id);,因为第一次打印是因为前一次通过PeerConnectionProxy::SetRemoteDescription调用的signaling_thread_->Send,在Thread::Send函数的最后会调用current_thread->socketserver()->WakeUp();(即Win32SocketServer::WakeUp),而第二次才阻塞主线程。

以上是关于libjingle主线程的消息响应的主要内容,如果未能解决你的问题,请参考以下文章

Android多线程消息处理机制

主线程怎么给子线程发送消息

Android 使用handler实现线程间发送消息 (主线程 与 子线程之间)(子线程 与 子线程之间)

Android的消息机制

NSNotificationCenter 注意

c语言中,创建的子线程如何给主线程发消息?