Windows版本的peerconnection_client demo是一個win32程序,入口函數為main.cc里面的wWinMain,程序整體流程就從這個入口函數下手開始分析。
1.peerconnection_client demo中主要的類的關系
整個demo中有3個主要的類分別是窗口類MainWnd,它的主要功能是實現了一個窗體程序,然后是PeerConnectionClient類,他的作用是與信令服務器來進行TCP通信,最后是聯系MainWnd和PeerConnectionClient的類Conductor,Conductor實現了MainWndCallback和PeerConnectionClientObserver接口,當PeerConnectionClient和MainWnd完成某個事件時,會通過調用相應的接口來通知Conductor。
然后從入口函數wWinMain開始來分析一下demo的函數調用流程
2.入口函數wWinMain分析
在函數的一開始,初始化了Windows socket,以及webRTC消息循環。
rtc::EnsureWinsockInit();
rtc::Win32Thread w32_thread;
rtc::ThreadManager::Instance()->SetCurrentThread(&w32_thread);
然后去處理啟動程序的時候傳入的命令行參數,感覺不太重要,這里略過。
處理完命令行參數之后,調用了MainWnd的Create函數創建了窗體
//創建窗體 MainWnd wnd(FLAG_server, FLAG_port, FLAG_autoconnect, FLAG_autocall); if (!wnd.Create()) { RTC_NOTREACHED(); return -1; }
緊接着就是初始化SSL以及創建PeerConnectionClient和Conductor
//創建PeerConnectionClient //PeerConnectionClient主要用來處理與信令服務器的tcp通訊 //它有兩個Win32Socket:control_socket_和hanging_get_, //在PeerConnectionClient::DoConnect()中創建,並在PeerConnectionClient::InitSocketSignals()中連接好socket的信號。 PeerConnectionClient client; //scoped_refptr 是一個智能指針 //RefCountedObject實現了一個線程安全的引用計數功能 //代碼的作用是創建了一個Conductor對象並用conductor指向它 rtc::scoped_refptr<Conductor> conductor( new rtc::RefCountedObject<Conductor>(&client, &wnd));
完成了上面的操作之后就進入了窗體消息循環,等待窗體上的操作
//窗體消息循環 // Main loop. MSG msg; BOOL gm; //GetMessage函數只有在接收到WM_QUIT消息時才返回0 while ((gm = ::GetMessage(&msg, NULL, 0, 0)) != 0 && gm != -1) { if (!wnd.PreTranslateMessage(&msg)) { //將虛擬鍵消息轉換為字符消息 ::TranslateMessage(&msg); //分派一個消息到窗口進程由窗口進程對消息進行處理 ::DispatchMessage(&msg); } } //上面的消息循環退出后,如果仍然鏈接着,繼續處理消息 if (conductor->connection_active() || client.is_connected()) { while ((conductor->connection_active() || client.is_connected()) && (gm = ::GetMessage(&msg, NULL, 0, 0)) != 0 && gm != -1) { if (!wnd.PreTranslateMessage(&msg)) { ::TranslateMessage(&msg); ::DispatchMessage(&msg); } } }
再以后是關閉SSL
rtc::CleanupSSL();
3.窗體消息分析
窗體的消息是在MainWnd的OnMessage函數中進行處理的。
當點擊connect按鈕時
//connect按鈕按下 case WM_COMMAND: if (button_ == reinterpret_cast<HWND>(lp)) { if (BN_CLICKED == HIWORD(wp)) OnDefaultAction(); } else if (listbox_ == reinterpret_cast<HWND>(lp)) { if (LBN_DBLCLK == HIWORD(wp)) { OnDefaultAction(); } } return true;
點擊connect按鈕和連接服務器成功之后點擊peer名都會進入OnDefaultAction函數
void MainWnd::OnDefaultAction() { if (!callback_) return; //點擊connect按鈕 if (ui_ == CONNECT_TO_SERVER) { std::string server(GetWindowText(edit1_)); std::string port_str(GetWindowText(edit2_)); int port = port_str.length() ? atoi(port_str.c_str()) : 0; //登陸服務器 callback_->StartLogin(server, port); //點擊peer名 } else if (ui_ == LIST_PEERS) { LRESULT sel = ::SendMessage(listbox_, LB_GETCURSEL, 0, 0); if (sel != LB_ERR) { LRESULT peer_id = ::SendMessage(listbox_, LB_GETITEMDATA, sel, 0); if (peer_id != -1 && callback_) { //連接到peer callback_->ConnectToPeer(peer_id); } } } else { MessageBoxA(wnd_, "OK!", "Yeah", MB_OK); } }
首先看一下怎么連接服務器的
void PeerConnectionClient::DoConnect() { //創建control_socket和hanging_get_兩個AsyncSocket,等待socket事件 //control_socket_和hanging_get_是兩個指向AsyncSocket的智能指針 control_socket_.reset(CreateClientSocket(server_address_.ipaddr().family())); hanging_get_.reset(CreateClientSocket(server_address_.ipaddr().family())); //連接socket信號和槽 InitSocketSignals(); char buffer[1024]; sprintfn(buffer, sizeof(buffer), "GET /sign_in?%s HTTP/1.0\r\n\r\n", client_name_.c_str()); onconnect_data_ = buffer; //control_socket_連接服務器,等待連接成功信號,調用OnConnect槽函數 bool ret = ConnectControlSocket(); if (ret) state_ = SIGNING_IN; if (!ret) { callback_->OnServerConnectionFailure(); } }
這里因為是異步的socket,通過注冊socket信號的槽函數,會在socket連接成功和讀socket的時候觸發相應的事件,從而調用和信號綁定的槽函數
void PeerConnectionClient::InitSocketSignals() { RTC_DCHECK(control_socket_.get() != NULL); RTC_DCHECK(hanging_get_.get() != NULL); // control_socket_關閉信號連接OnClose槽函數 control_socket_->SignalCloseEvent.connect(this, &PeerConnectionClient::OnClose); //hanging_get_關閉信號連接OnClose槽函數 hanging_get_->SignalCloseEvent.connect(this, &PeerConnectionClient::OnClose); //control_socket_連接信號連接OnConnect槽函數 control_socket_->SignalConnectEvent.connect(this, &PeerConnectionClient::OnConnect); //hanging_get_連接信號連接OnHangingGetConnect槽函數 hanging_get_->SignalConnectEvent.connect(this, &PeerConnectionClient::OnHangingGetConnect); //control_socket_讀信號連接了OnRead槽函數 control_socket_->SignalReadEvent.connect(this, &PeerConnectionClient::OnRead); //hanging_get_讀信號連接了OnHangingGetRead槽函數 hanging_get_->SignalReadEvent.connect(this, &PeerConnectionClient::OnHangingGetRead); }
所以直接去看PeerConnectionClient的OnConnect函數
void PeerConnectionClient::OnConnect(rtc::AsyncSocket* socket) { RTC_DCHECK(!onconnect_data_.empty()); //control_socket_連接服務器成功就發送 "GET /sign_in?%s HTTP/1.0\r\n\r\n" //成功后,服務器會返回當前 channel連接的其他peer ,"200 Added" size_t sent = socket->Send(onconnect_data_.c_str(), onconnect_data_.length()); RTC_DCHECK(sent == onconnect_data_.length()); onconnect_data_.clear(); }
連接服務器成功之后會向服務器發送登錄請求,成功之后,服務器返回當前channel連接的其他peer名,接着就去看一下PeerConnectionClient的OnRead函數
void PeerConnectionClient::OnRead(rtc::AsyncSocket* socket) { .... //peer連接成功 callback_->OnPeerConnected(id, name); .... //登錄服務器成功之后,切換到顯示已登錄用戶列表UI callback_->OnSignedIn(); }
這時就到了顯示peer名的界面了,當點擊peer名時會通過消息循環調用上面的OnDefaultAction函數
void Conductor::ConnectToPeer(int peer_id) { RTC_DCHECK(peer_id_ == -1); RTC_DCHECK(peer_id != -1); if (peer_connection_.get()) { main_wnd_->MessageBox("Error", "We only support connecting to one peer at a time", true); return; } //初始化PeerConnection if (InitializePeerConnection()) { peer_id_ = peer_id; //創建一個offer!!!! peer_connection_->CreateOffer(this, NULL); } else { main_wnd_->MessageBox("Error", "Failed to initialize PeerConnection", true); } }
bool Conductor::InitializePeerConnection() { RTC_DCHECK(peer_connection_factory_.get() == NULL); RTC_DCHECK(peer_connection_.get() == NULL); //創建PeerConnectionFactory peer_connection_factory_ = webrtc::CreatePeerConnectionFactory(); .... //添加stream,切換到stream UI AddStreams(); .... }
然后就開始進行通信了
void Conductor::AddStreams() { if (active_streams_.find(kStreamLabel) != active_streams_.end()) return; // Already added. rtc::scoped_refptr<webrtc::AudioTrackInterface> audio_track( peer_connection_factory_->CreateAudioTrack( kAudioLabel, peer_connection_factory_->CreateAudioSource(NULL))); rtc::scoped_refptr<webrtc::VideoTrackInterface> video_track( peer_connection_factory_->CreateVideoTrack( kVideoLabel, peer_connection_factory_->CreateVideoSource(OpenVideoCaptureDevice(), NULL))); main_wnd_->StartLocalRenderer(video_track); //創建MediaStream采集並傳送本地音視頻 rtc::scoped_refptr<webrtc::MediaStreamInterface> stream = peer_connection_factory_->CreateLocalMediaStream(kStreamLabel); stream->AddTrack(audio_track); stream->AddTrack(video_track); if (!peer_connection_->AddStream(stream)) { LOG(LS_ERROR) << "Adding stream to PeerConnection failed"; } typedef std::pair<std::string, rtc::scoped_refptr<webrtc::MediaStreamInterface> > MediaStreamPair; active_streams_.insert(MediaStreamPair(stream->label(), stream)); //切換到StreamingUI main_wnd_->SwitchToStreamingUI(); }
5.程序主要流程圖
demo的主要程序流程圖如下圖所示

6.后續計划
因為對整個p2p連接的建立和音視頻流的傳輸等過程還不是很熟悉,所以這篇文章只是淺顯的描述了一下demo的函數調用流程,后面會把整個過程的細節理一下,然后自己實現一個peerconnection_client。
參考文章:
http://blog.csdn.net/qq_24283329/article/category/6915582