本文分析的webrtc的版本是:m84
平台:win10
WebRTC PeerConnection Client源码分析1-main window
WebRTC PeerConnection Client源码分析2-PeerConnectionClient
WebRTC PeerConnection Client源码分析3-Conductor
本文用到的抓包数据,可以从这里下载:https://gitee.com/qiuguolu1108/blog
PeerConnectionClient用于和信令服务器进行交互,在Conductor和信令服务器之间起着桥梁的作用。其信令交互时序图如下:
登录信令服务器
void Conductor::StartLogin(const std::string& server, int port)
{
/*如果已经连接了信令服务器,则直接返回。*/
if (client_->is_connected())
return;
server_ = server;
client_->Connect(server, port, GetPeerName()); /*连接信令服务器*/
}
在connect界面点击Connect按钮后,会触发Conductor::StartLogin()函数,在此函数中会调用PeerConnectionClient::Connect()函数登录信令服务器。
void PeerConnectionClient::Connect(const std::string& server, int port,const std::string& client_name)
{
RTC_DCHECK(!server.empty());
RTC_DCHECK(!client_name.empty());
/*判断client是否处于连接状态*/
if (state_ != NOT_CONNECTED) {
RTC_LOG(WARNING) << "The client must not be connected before you can call Connect()";
/*调用Conductor::OnServerConnectionFailure,通知Conductor连接失败。*/
callback_->OnServerConnectionFailure();
return;
}
/*判断ip和client_name是否有效*/
if (server.empty() || client_name.empty()) {
callback_->OnServerConnectionFailure();
return;
}
/*端口号小于0时,使用默认的值。*/
if (port <= 0)
port = kDefaultServerPort; /*8888*/
/*保存信令服务器的ip和port*/
server_address_.SetIP(server);
server_address_.SetPort(port);
/*保存client name*/
client_name_ = client_name;
/*判断服务器地址是需要解析*/
if (server_address_.IsUnresolvedIP()){
state_ = RESOLVING; /*设置client的状态为域名解析状态*/
resolver_ = new rtc::AsyncResolver(); /*创建一个异步的域名解析器*/
/*注册信号,域名解析结束后会回调这个处理函数。*/
resolver_->SignalDone.connect(this, &PeerConnectionClient::OnResolveResult);
/*开始解析域名*/
resolver_->Start(server_address_);
} else { /*如果域名不需要解析,则直接连接信令服务器。*/
DoConnect();
}
}
用户提供的信令服务器地址需要解析的话,会创建一个异步的域名解析器,因为解析域名是异步的,所以需要注册一个回调函数,用于接收解析后的结果。如果不需要解析地址,则调用DoConnect()直接连接信令服务器。
void PeerConnectionClient::OnResolveResult(rtc::AsyncResolverInterface* resolver)
{
if (resolver_->GetError() != 0) { /*没有解析成功*/
callback_->OnServerConnectionFailure();
resolver_->Destroy(false);
resolver_ = NULL;
state_ = NOT_CONNECTED; /*设置为非连接状态*/
} else {
server_address_ = resolver_->address(); /*获取解析后的地址*/
DoConnect(); /*连接信令服务器*/
}
}
域名解析器解析结束后,会回调本函数。如果解析失败,则将PeerConnectionClient设置为NOT_CONNECTED
状态;若解析成功,则调用DoConnect()连接信令服务器。
void PeerConnectionClient::DoConnect()
{
/*创建异步socket*/
control_socket_.reset(CreateClientSocket(server_address_.ipaddr().family()));
hanging_get_.reset(CreateClientSocket(server_address_.ipaddr().family()));
/*初始化socket的信号与槽*/
InitSocketSignals();
/*连接成功后,需要发送的数据。*/
char buffer[1024];
snprintf(buffer, sizeof(buffer), "GET /sign_in?%s HTTP/1.0\r\n\r\n", client_name_.c_str());
onconnect_data_ = buffer;
/*向信令服务器发送连接请求*/
bool ret = ConnectControlSocket();
if (ret)
state_ = SIGNING_IN; /*如果连接请求发送成功,标识为SIGNING_IN状态。*/
if (!ret) {
callback_->OnServerConnectionFailure(); /*通知Conductor连接失败*/
}
}
PeerConnectionClient与信令服务器进行交互使用的是http协议,并且是短连接。此处用了两个socket,control_socket_用于主动的向信令服务器发送信令;hanging_get_用于向信令服务器请求信令消息,每次都向信令服务器发送wait
信令,等待信令服务器响应,当信令服务器有消息需要发送给客户端时,都会通过这个socket返回。
void PeerConnectionClient::InitSocketSignals()
{
RTC_DCHECK(control_socket_.get() != NULL);
RTC_DCHECK(hanging_get_.get() != NULL);
/*注册close事件槽函数*/
control_socket_->SignalCloseEvent.connect(this, &PeerConnectionClient::OnClose);
hanging_get_->SignalCloseEvent.connect(this, &PeerConnectionClient::OnClose);
/*注册connect事件槽函数*/
control_socket_->SignalConnectEvent.connect(this, &PeerConnectionClient::OnConnect);
hanging_get_->SignalConnectEvent.connect(this, &PeerConnectionClient::OnHangingGetConnect);
/*注册read事件槽函数*/
control_socket_->SignalReadEvent.connect(this, &PeerConnectionClient::OnRead);
hanging_get_->SignalReadEvent.connect(this, &PeerConnectionClient::OnHangingGetRead);
}
PeerConnectionClient使用了异步的socket,所以需要回调函数配合处理。这里使用了信号与槽简化了处理。更多信号与槽相关的知识,可以看我的这篇文章WebRTC源码分析之信号与槽-sigslot。
bool PeerConnectionClient::ConnectControlSocket()
{
/*检查socket的连接状态*/
RTC_DCHECK(control_socket_->GetState() == rtc::Socket::CS_CLOSED);
/*向信令服务器发送连接请求*/
int err = control_socket_->Connect(server_address_);
if (err == SOCKET_ERROR) {
Close();
return false;
}
return true;
}
向信令服务器发送的连接请求是异步的,成功的连接信令服务器后,会触发OnConnect()槽函数。
void PeerConnectionClient::OnConnect(rtc::AsyncSocket* socket)
{
RTC_DCHECK(!onconnect_data_.empty());
/*向信令服务器发送sign in信令*/
size_t sent = socket->Send(onconnect_data_.c_str(), onconnect_data_.length());
RTC_DCHECK(sent == onconnect_data_.length());
onconnect_data_.clear();
}
#登录信令服务器发送的登录信息
GET /sign_in?study@LAPTOP-HHU8I2T3 HTTP/1.0
成功连接信令服务器后,接着就是向信令服务器发送登录信息。
#第一个peer登录时,信令服务器对登录信息的响应。
HTTP/1.1 200 Added
Server: PeerConnectionTestServer/0.1
Cache-Control: no-cache
Connection: close
Content-Type: text/plain
Content-Length: 26
Pragma: 1
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, Content-Length, Connection, Cache-Control
Access-Control-Expose-Headers: Content-Length, X-Peer-Id
study@LAPTOP-HHU8I2T3,1,1
#第二个peer登录时,信令服务器对登录信息的响应。
HTTP/1.1 200 Added
Server: PeerConnectionTestServer/0.1
Cache-Control: no-cache
Connection: close
Content-Type: text/plain
Content-Length: 49
Pragma: 2
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, Content-Length, Connection, Cache-Control
Access-Control-Expose-Headers: Content-Length, X-Peer-Id
hp@DESKTOP-740K5HL,2,1
study@LAPTOP-HHU8I2T3,1,1
信令服务器接收到Peer的登录信息后,做出的响应。
void PeerConnectionClient::OnRead(rtc::AsyncSocket* socket)
{
size_t content_length = 0;
/*将信令服务器的响应读到control_data_中*/
if (ReadIntoBuffer(socket, &control_data_, &content_length))
{
size_t peer_id = 0, eoh = 0;
/*验证响应中的状态码,获取peer id。*/
bool ok = ParseServerResponse(control_data_, content_length, &peer_id, &eoh);
if (ok)
{
/*my_id_是peer的id,是由信令服务器分配的。在没有收到信令服务器分配的id时,其值为-1。*/
if (my_id_ == -1)
{
/*解析数据包得到信令服务器分配的id,并存储到自己的my_id_中。*/
RTC_DCHECK(state_ == SIGNING_IN);
my_id_ = static_cast<int>(peer_id);
RTC_DCHECK(my_id_ != -1);
/*响应包中除了有服务器分配给本客户端的id,服务器还发送了一个列表,列表中存储着登录服务器的客户端*/
if (content_length) { /*如果有http body*/
/*指向实际的body内容*/
size_t pos = eoh + 4;
/*解析body内容*/
while (pos < control_data_.size())
{
size_t eol = control_data_.find('\n', pos);
if (eol == std::string::npos)
break;
int id = 0;
std::string name;
bool connected;
/*解析一行数据*/
if (ParseEntry(control_data_.substr(pos, eol - pos), &name, &id, &connected) && id != my_id_) {
/*不是自己的id*/
peers_[id] = name; /*保存其他peer的信息*/
callback_->OnPeerConnected(id, name); /*通知Conductor其他用户的id和name*/
}
/*指向下一行*/
pos = eol + 1;
}
}
RTC_DCHECK(is_connected());
callback_->OnSignedIn(); /*通知Conductor登录成功*/
} else if (state_ == SIGNING_OUT) {
Close();
callback_->OnDisconnected();
} else if (state_ == SIGNING_OUT_WAITING) {
SignOut();
}
}
control_data_.clear();
/*如果处于SIGNING_IN,则表示登录成功。*/
if (state_ == SIGNING_IN) {
RTC_DCHECK(hanging_get_->GetState() == rtc::Socket::CS_CLOSED);
state_ = CONNECTED; /*更新连接状态*/
/*hanging_get socket连接服务器*/
hanging_get_->Connect(server_address_);
}
}
}
当信令服务器发送登录响应时,会触发PeerConnectionClient::OnRead()函数。
首先从socket读取响应信息至control_data_中,如果是短连接则需要关闭socket。接着验证响应中的状态码,获取信令服务器分配的peer id。
登录信令的响应中会包含其他登录客户端的信息,这些客户端的信令会显示到peer list界面上。解析处其他客户端的信息后,会触发Conductor::OnPeerConnected函数,在这个函数中会将客户端的信息显示到peer list界面上。
hp@DESKTOP-740K5HL,2,1
study@LAPTOP-HHU8I2T3,1,1
响应信息的格式是:peer的name,信令服务器分配的peer id,是否处于登录状态,1表示处于登录状态,0表示登出状态。
成功登录信令服务器后,hanging_get socket也开始登录信令服务器,用于接收信令服务器发送给客户端的信息。
bool PeerConnectionClient::ReadIntoBuffer(rtc::AsyncSocket* socket, std::string* data,size_t* content_length)
{
char buffer[0xffff];
/*从socket中读取网络数据*/
do {
int bytes = socket->Recv(buffer, sizeof(buffer), nullptr); /*读取网络数据*/
if (bytes <= 0)
break;
data->append(buffer, bytes); /*将读取的网络数据追加到buffer中*/
} while (true);
bool ret = false;
size_t i = data->find("\r\n\r\n"); /*查找http header结束的位置*/
if (i != std::string::npos) {
RTC_LOG(INFO) << "Headers received";
/*从http header中获取Content-Length字段*/
if (GetHeaderValue(*data, i, "\r\nContent-Length: ", content_length)) {
/*i指向\r\n\r\n, + 4 + 内容长度就是http的总大小。*/
size_t total_response_size = (i + 4) + *content_length;
if (data->length() >= total_response_size) {
ret = true;
std::string should_close;
const char kConnection[] = "\r\nConnection: ";
/*查找Connection字段*/
if (GetHeaderValue(*data, i, kConnection, &should_close) && should_close.compare("close") == 0)
{
// Connection: close
/*如果该字段的值为close,表示使用http短连接,那么这次请求完毕了,需要关闭socket。*/
socket->Close(); /*关闭socket*/
OnClose(socket, 0); /*通知关闭socket*/
}
} else {
// We haven't received everything. Just continue to accept data.
}
} else {
RTC_LOG(LS_ERROR) << "No content length field specified by the server.";
}
}
return ret;
}
从指定的socket读取响应信息,并做适当的处理。如果从响应中得知使用的是http短连接,那么需要关闭socket。
void PeerConnectionClient::OnClose(rtc::AsyncSocket* socket, int err)
{
RTC_LOG(INFO) << __FUNCTION__;
socket->Close(); /*关闭socket*/
#ifdef WIN32
if (err != WSAECONNREFUSED) { /*不是connect refused错误*/
#else
if (err != ECONNREFUSED) {
#endif
if (socket == hanging_get_.get()) { /*如果是haning_get_ socket*/
if (state_ == CONNECTED) {
hanging_get_->Close(); /*关闭之前的连接*/
hanging_get_->Connect(server_address_); /*建立新的连接*/
}
} else { /*control_get_ socket*/
callback_->OnMessageSent(err); /*通知Conductor,消息已经发送。*/
}
} else {
if (socket == control_socket_.get()) {
RTC_LOG(WARNING) << "Connection refused; retrying in 2 seconds";
/*如果连接失败,则投递一个异步的任务,2秒之后再次连接。*/
rtc::Thread::Current()->PostDelayed(RTC_FROM_HERE, kReconnectDelay, this, 0);
} else {
Close();
callback_->OnDisconnected(); /*通知Conductor连接断开。*/
}
}
}
/*异步任务的回调函数*/
void PeerConnectionClient::OnMessage(rtc::Message* msg) {
DoConnect(); /*再次重连信令服务器*/
}
主动调用OnClose
函数时,err的值为0,如果是hanging_get_,则关闭之前的连接,再建立一个新的连接;若是control_get_,则通知Conductor消息发送完成,可以处理发送其他消息了。
在连接信令服务器时,如果无法连接信令服务器,该函数也会被回调,此时err的值为WSAECONNREFUSED,若是hanging_get_,则关闭程序;如果是control_get_,则会投递一个异步的定时任务,2秒后会再次重连服务器。PeerConnectionClient继承自rtc::MessageHandler接口,当异步任务触发时,会回调OnMessage函数,处理异步的任务,在该函数中会再次连接信令服务器。
bool PeerConnectionClient::ParseServerResponse(const std::string& response, size_t content_length, size_t* peer_id, size_t* eoh)
{
/*解析响应中的状态码*/
int status = GetResponseStatus(response.c_str());
if (status != 200) { /*状态码不为200,则表示出错了。*/
RTC_LOG(LS_ERROR) << "Received error from server";
Close();
callback_->OnDisconnected();
return false;
}
*eoh = response.find("\r\n\r\n"); /*指向http header末尾*/
RTC_DCHECK(*eoh != std::string::npos);
if (*eoh == std::string::npos)
return false;
*peer_id = -1;
/*Pragma字段用于标识peer id*/
GetHeaderValue(response, *eoh, "\r\nPragma: ", peer_id);
return true;
}
该函数用于验证http响应中的状态码,解析并返回信令服务器分配的peer id。
peer_id和eoh传入的是指针,此处作为返回值参数使用。
void PeerConnectionClient::OnHangingGetConnect(rtc::AsyncSocket* socket)
{
char buffer[1024];
snprintf(buffer, sizeof(buffer), "GET /wait?peer_id=%i HTTP/1.0\r\n\r\n", my_id_);
int len = static_cast<int>(strlen(buffer));
/*向信令服务器发送wait信令*/
int sent = socket->Send(buffer, len);
RTC_DCHECK(sent == len);
}
#向信令服务器发送的wait信令
GET /wait?peer_id=1 HTTP/1.0
hanging_get_登录成功后,会触发该函数,在该函数中会发送wait信令,当信令服务器需要主动向客户端发送消息时,会作为这条信令的响应发送对应的消息。
void PeerConnectionClient::OnHangingGetRead(rtc::AsyncSocket* socket)
{
RTC_LOG(INFO) << __FUNCTION__;
size_t content_length = 0;
if (ReadIntoBuffer(socket, ¬ification_data_, &content_length)) {
size_t peer_id = 0, eoh = 0;
/*解析出响应中的peer id*/
bool ok = ParseServerResponse(notification_data_, content_length, &peer_id, &eoh);
if (ok) {
size_t pos = eoh + 4;
if (my_id_ == static_cast<int>(peer_id)) { /*其他客户端的登录、登出信令*/
int id = 0;
std::string name;
bool connected = false;
if (ParseEntry(notification_data_.substr(pos), &name, &id, &connected)){
if (connected) { /*有新的客户端登录信令服务器*/
peers_[id] = name;
callback_->OnPeerConnected(id, name); /*更新peer list界面列表*/
} else { /*已登录客户端登出信令服务器*/
peers_.erase(id);
callback_->OnPeerDisconnected(id); /*更新peer list界面列表*/
}
}
} else {
/*信令服务器转发的其他客户端的offer、answer、candidate、bye信息*/
OnMessageFromPeer(static_cast<int>(peer_id), notification_data_.substr(pos));
}
}
notification_data_.clear();
}
当信令服务器需要主动发送消息给客户端时,会包装成wait信令的响应信息。有其他客户端登录或登出信令服务器时,会通知本端,本端会根据信令服务器反馈的信息更新peer list界面的用户列表。
当收到信令服务器转发的其他客户端的offer、answer、candidate信息时,会进入OnMessageFromPeer()函数处理。
void PeerConnectionClient::OnMessageFromPeer(int peer_id, const std::string& message)
{
/*如果是bye信令,表明peer id的peer已经退出了信令服务器,将其从peer list中去掉。*/
if (message.length() == (sizeof(kByeMessage) - 1) && message.compare(kByeMessage) == 0) {
callback_->OnPeerDisconnected(peer_id); /*更新peer list界面用户列表*/
} else {
/*offer、answer、candidate信息需要发送给Conductor::OnMessageFromPeer()*/
callback_->OnMessageFromPeer(peer_id, message);
}
}
开启视频通话
登录信令服务器后,peer list界面会显示所有登录信令服务器的用户。点击任意一个用户就可以开始和此用户建立视频通话。
void Conductor::ConnectToPeer(int peer_id) {
...
/*创建offer*/
peer_connection_->CreateOffer(
this, webrtc::PeerConnectionInterface::RTCOfferAnswerOptions());
...
}
在peer list界面点击用户id会触发至本函数,该函数后面会详细介绍。此函数中很重要的一个操作是调用CreateOffer()函数创建offer。
void Conductor::OnSuccess(webrtc::SessionDescriptionInterface* desc)
{
...
std::string sdp;
desc->ToString(&sdp); /*将offer转成string*/
...
Json::StyledWriter writer;
Json::Value jmessage;
jmessage[kSessionDescriptionTypeName] = webrtc::SdpTypeToString(desc->GetType());
jmessage[kSessionDescriptionSdpName] = sdp;
/*发送offer信令*/
SendMessage(writer.write(jmessage));
}
WebRTC生成offer后会通过这个回调函数传回offer,在这个函数中会将offer组成json信令,然后调用SendMessage函数发送该信令。
void Conductor::SendMessage(const std::string& json_object) {
std::string* msg = new std::string(json_object);
/*向MainWnd投递消息*/
main_wnd_->QueueUIThreadCallback(SEND_MESSAGE_TO_PEER, msg);
}
此时依然处于WebRTC内部的线程中,而信令的发送必须由主线程完成,所以将待发送的信令投递到MainWnd中,让其发送该信令。后面会再次分析该点。
void Conductor::UIThreadCallback(int msg_id, void* data)
{
switch (msg_id) {
...
case SEND_MESSAGE_TO_PEER: {
RTC_LOG(INFO) << "SEND_MESSAGE_TO_PEER";
/*获取消息*/
std::string* msg = reinterpret_cast<std::string*>(data);
if (msg) {
pending_messages_.push_back(msg); /*将消息存放到队列中,保证消息有序发送。*/
}
/*如果有消息待发送,且PeerConnectionClient可以发送信令。*/
if (!pending_messages_.empty() && !client_->IsSendingMessage()) {
msg = pending_messages_.front(); /*从队首获取一条待发送的消息*/
pending_messages_.pop_front();
/*通过PeerConnectionClient放该消息*/
if (!client_->SendToPeer(peer_id_, *msg) && peer_id_ != -1) {
RTC_LOG(LS_ERROR) << "SendToPeer failed";
DisconnectFromServer();
}
delete msg;
}
if (!peer_connection_.get())
peer_id_ = -1;
break;
}
...
}
上面投递了消息后,会在这函数中得到处理,该函数运行在主线程中。
bool PeerConnectionClient::SendToPeer(int peer_id, const std::string& message)
{
if (state_ != CONNECTED)
return false;
RTC_DCHECK(is_connected());
RTC_DCHECK(control_socket_->GetState() == rtc::Socket::CS_CLOSED);
if (!is_connected() || peer_id == -1)
return false;
char headers[1024];
/*将待发送的信令包装到http的body中*/
snprintf(headers, sizeof(headers),
"POST /message?peer_id=%i&to=%i HTTP/1.0\r\n"
"Content-Length: %zu\r\n"
"Content-Type: text/plain\r\n"
"\r\n",
my_id_, peer_id, message.length());
onconnect_data_ = headers;
onconnect_data_ += message;
return ConnectControlSocket(); /*连接信令服务器,发送该请求。*/
}
PeerConnectionClient会将待发送的信令包装到http中,通过http协议发送给信令服务器。
#offer信令
POST /message?peer_id=1&to=2 HTTP/1.0
Content-Length: 4197
Content-Type: text/plain
{
"sdp" : "v=0\r\no=- 7038993275920826226 ...",
"type" : "offer"
}
客户端发送的offer信令,peer_id=1&to=2表示该信令需要信令服务器转发给peer id=2的客户端。
#offer信令的响应信息
HTTP/1.1 200 OK
Server: PeerConnectionTestServer/0.1
Cache-Control: no-cache
Connection: close
Content-Type: text/plain
Content-Length: 0
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, Content-Length, Connection, Cache-Control
Access-Control-Expose-Headers: Content-Length, X-Peer-Id
这是offer信令的响应信息
HTTP/1.1 200 OK
Server: PeerConnectionTestServer/0.1
Cache-Control: no-cache
Connection: close
Content-Type: text/plain
Content-Length: 4197
Pragma: 1
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, Content-Length, Connection, Cache-Control
Access-Control-Expose-Headers: Content-Length, X-Peer-Id
{
"sdp" : "v=0\r\no=- 7038993275920826226 ...",
"type" : "offer"
}
信令服务器会根据offer信令中的指示,将该信令转发给peer id=2的客户端。该信息会作为wait信令的响应信息返回。
void PeerConnectionClient::OnHangingGetRead(rtc::AsyncSocket* socket)
{
...
OnMessageFromPeer(static_cast<int>(peer_id), notification_data_.substr(pos));
...
}
信令服务器发送wait信令的响应信息时,会触发该函数。
void PeerConnectionClient::OnMessageFromPeer(int peer_id, const std::string& message)
{
...
callback_->OnMessageFromPeer(peer_id, message);
...
}
最后offer信令会被送至Conductor::OnMessageFromPeer()处理。
answer信令
和candidate信令
在PeerConnectionClient中的处理同offer,这里就不在赘述了。
bool MainWnd::PreTranslateMessage(MSG* msg)
{
...
} else if (msg->wParam == VK_ESCAPE) { /*esc键*/
if (callback_) {
if (ui_ == STREAMING) {
callback_->DisconnectFromCurrentPeer(); /*如果在视频界面,则断开当前连接。*/
...
}
void Conductor::DisconnectFromCurrentPeer() {
RTC_LOG(INFO) << __FUNCTION__;
if (peer_connection_.get()) {
client_->SendHangUp(peer_id_); /*发送bye信令*/
DeletePeerConnection();
}
if (main_wnd_->IsWindow())
main_wnd_->SwitchToPeerList(client_->peers()); /*切换至peer list界面*/
}
bool PeerConnectionClient::SendHangUp(int peer_id)
{
return SendToPeer(peer_id, kByeMessage); /*发送bye信令*/
}
在视频通话界面按ESC键,会触发bye信令。
登出信令服务器
Condutor可以调用PeerConnectionClient::SignOut()登出信令服务器。
bool PeerConnectionClient::SignOut()
{
if (state_ == NOT_CONNECTED || state_ == SIGNING_OUT)
return true;
if (hanging_get_->GetState() != rtc::Socket::CS_CLOSED)
hanging_get_->Close(); /*关闭hangin_get_ socket,不在接收信令服务器主动发送的消息了。*/
if (control_socket_->GetState() == rtc::Socket::CS_CLOSED) {
state_ = SIGNING_OUT; /*更新状态*/
if (my_id_ != -1) {
char buffer[1024];
snprintf(buffer, sizeof(buffer), "GET /sign_out?peer_id=%i HTTP/1.0\r\n\r\n", my_id_);
onconnect_data_ = buffer;
return ConnectControlSocket(); /*发送sign out信令*/
} else {
return true;
}
} else { /*如果control_socket_ 正在发送信令,则此时无法发送sign out信令。*/
state_ = SIGNING_OUT_WAITING; /*标记为等待登出状态*/
}
return true;
}
客户端已经需要登出信令服务器了,不再需要信令服务器主动发送的消息,所以关闭了hanging_get_ socket。
如果control_socket_正处于发送消息的过程中,那么此时还不能发送sign out信令,需要等待其处理完毕后,再发送sign out信令。此时先标记为SIGNING_OUT_WAITING
状态。
void PeerConnectionClient::OnRead(rtc::AsyncSocket* socket)
{
...
} else if (state_ == SIGNING_OUT_WAITING) {
SignOut();
}
...
}
control_socket_请求处理完毕后,会检查是否处于SIGNING_OUT_WAITING
状态,如果处于此状态,则发送sign out信令。
#sign out信令
GET /sign_out?peer_id=2 HTTP/1.0
#信令服务器对sign out信令的响应
HTTP/1.1 200 OK
Server: PeerConnectionTestServer/0.1
Cache-Control: no-cache
Connection: close
Content-Type: text/plain
Content-Length: 0
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, Content-Length, Connection, Cache-Control
Access-Control-Expose-Headers: Content-Length, X-Peer-Id
客户端登出信令服务器时,发送的sign out信令及其响应。
#信令服务器通知其他所有客户端,有客户端登出了信令服务器。
HTTP/1.1 200 OK
Server: PeerConnectionTestServer/0.1
Cache-Control: no-cache
Connection: close
Content-Type: text/plain
Content-Length: 23
Pragma: 1
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, Content-Length, Connection, Cache-Control
Access-Control-Expose-Headers: Content-Length, X-Peer-Id
hp@DESKTOP-740K5HL,2,0
一个客户端登出信令服务器后,信令服务器需要通告其他所有的客户端,该客户端登出了服务器。
void PeerConnectionClient::OnHangingGetRead(rtc::AsyncSocket* socket)
{
...
if (connected) {
...
} else { /*有客户端登出信令服务器,更新peer list界面用户列表。*/
peers_.erase(id);
callback_->OnPeerDisconnected(id);
}
...
}
有客户端登出信令服务器后,信令服务器会通过wait信令的响应通知其他在线的客户端。信令服务器发送的客户端登出消息会触发PeerConnectionClient::OnHangingGetRead函数进行处理。