webrtc信令交互流程(信令服务器侧)

一、整体流程图


二、全流程抓包


webrtc的server仅处理三种method的http报文:GET、POST、OPTIONS。

GET承载的是Client发送给Server的sign in out信令。

POST承载的是Client通过Server转发给另外一个Client的信令。

OPTIONS暂时没接触到,未知。

1、Client->Server:GET/sign in信令,用于知会Server创建MemberChannel资源。分配Peer ID。在Server返回给Client的200OK的时候,会将Peer ID反馈给Client,用于后续两个Client之间传递协商信令的时候,指明传递路径。

2、Client->Server:GET/wait信令,Client每次收到Server的响应信令的时候,都会检测一下当前通讯的Socket是否释放,若已经释放,则发送wait信令,告知Server保留socket信息。后续还要发送信令报文。

3、Client->Server:POST/message信令,用于Client与另外一个Client交换音视频能力集和音视频通讯的地址信息。这部分使用的是SDP协议。详细请参见https://tools.ietf.org/html/rfc4566协议文档说明。

4、Client->Server:GET/sign out信令,在Server上释放本Client的MemberChannel信息,断开音视频通讯。

5、Server->Client:HTTP/1.1 200 OK 单纯对Client发送给Server的信令的响应。

6、Server->Client:HTTP/1.1 200 OK(text/plain) Server转发一端Client的信令给另一端Client。

三、源码分解

int main(int argc, char* argv[]) {
  std::string program_name = argv[0];                                   //程序名
  std::string usage = "Example usage: " + program_name + " --port=8888";//usage 提示
  webrtc::test::CommandLineParser parser;                               //配置监听端口参数
  parser.Init(argc, argv);
  parser.SetUsageMessage(usage);
  parser.SetFlag("port", "8888");
  parser.SetFlag("help", "false");
  parser.ProcessFlags();

  if (parser.GetFlag("help") == "true") {
    parser.PrintUsageMessage();
    return 0;
  }

  int port = strtol((parser.GetFlag("port")).c_str(), NULL, 10);

  // Abort if the user specifies a port that is outside the allowed
  // range [1, 65535].
  if ((port < 1) || (port > 65535)) {                               //端口号合法性判断
    printf("Error: %i is not a valid port.\n", port);
    return -1;
  }

  ListeningSocket listener;                                         //创建监听类
  if (!listener.Create()) {
    printf("Failed to create server socket\n");
    return -1;
  } else if (!listener.Listen(port)) {                              //指定监听端口
    printf("Failed to listen on server socket\n");
    return -1;
  }

  printf("Server listening on port %i\n", port);

  PeerChannel clients;                                              //创建客户端类,一个room对应一个Clients。
  typedef std::vector<DataSocket*> SocketArray;                      
  SocketArray sockets;                                              //创建socket数组。
  bool quit = false;
  while (!quit) {
    fd_set socket_set;
    FD_ZERO(&socket_set);
    if (listener.valid())
      FD_SET(listener.socket(), &socket_set);

    for (SocketArray::iterator i = sockets.begin(); i != sockets.end(); ++i)
      FD_SET((*i)->socket(), &socket_set);                        //有报文输入,这里就FD_SET socket参数。

    struct timeval timeout = { 10, 0 };                            //设置非阻塞式,超时时间为10秒。
    if (select(FD_SETSIZE, &socket_set, NULL, NULL, &timeout) == SOCKET_ERROR) {
      printf("select failed\n");
      break;
    }

    for (SocketArray::iterator i = sockets.begin(); i != sockets.end(); ++i) {
      DataSocket* s = *i;
      bool socket_done = true;//true表示该收的报文都已经接受完毕,释放socket,不进入这个检测循环。减少系统空掉时间。
      if (FD_ISSET(s->socket(), &socket_set)) {  //若socket没有配置,下次循环到FD_SET重新配置。一个异常保护。
        if (s->OnDataAvailable(&socket_done) && s->request_received()) {//过滤不能处理的报文,仅处理收到的http报文。
          ChannelMember* member = clients.Lookup(s);      //仅处理"/wait", "/sign_out", "/message"这三类请求。       
          if (member || PeerChannel::IsPeerConnection(s)) {
            if (!member) {      //member资源没有申请
              if (s->PathEquals("/sign_in")) {//并且是sign_in请求。在client中增加Peer成员。
                clients.AddMember(s);
              } else {//否则就是非法报文,直接返回client 500 Err。
                printf("No member found for: %s\n",
                    s->request_path().c_str());
                s->Send("500 Error", true, "text/plain", "",
                        "Peer most likely gone.");
              }
            } else if (member->is_wait_request(s)) {
              // no need to do anything.   //member资源已经创建,并且收到http wait请求,不释放socket资源。
              socket_done = false; //webrtc原版代码,设计思想是在每次client要发送message报文前,都先发送几个wait出来。所以这里要hold住socket资源。
            } else {//member已经创建,并且非wait消息
              ChannelMember* target = clients.IsTargetedRequest(s);//除了sign in,其他的http信令都会携带对端的Peer ID,根据这个ID寻找对端的member信息。
              if (target) {
                member->ForwardRequestToPeer(s, target); //处理要转发给另一个Peer资源的消息。包括协商信令、bye命令等。
              } else if (s->PathEquals("/sign_out")) {//对端的member已经释放,并且是sign out信息,直接返回200OK。
                s->Send("200 OK", true, "text/plain", "", "");  //返回CLient登出信令200 OK响应。
              } else {//找不到对端的member信息,返回Client 500 Error
                printf("Couldn't find target for request: %s\n",
                    s->request_path().c_str());
                s->Send("500 Error", true, "text/plain", "",
                        "Peer most likely gone.");
              }
            }
          } else {
            HandleBrowserRequest(s, &quit);//quit信令。用来销毁room和监听端口的。也就是说释放server资源的信令。
            if (quit) {
              printf("Quitting...\n");
              FD_CLR(listener.socket(), &socket_set);
              listener.Close();//释放监听端口。
              clients.CloseAll();//释放所有client资源,销毁room
            }
          }
        }
      } else {
        socket_done = false;
      }

      if (socket_done) {//client每次发给server的源端口号都一直在变化,所以只要当次收包处理完毕,没有报文再过来的时候,都要释放socket资源。防止资源泄露并且空转。
        printf("Disconnecting socket\n");
        clients.OnClosing(s); //当ChannelMember链接还在,仅仅释放socket。若ChannelMember链接不在,注销ChannelMember信息。
        assert(s->valid());  // Close must not have been called yet.
        FD_CLR(s->socket(), &socket_set);
        delete (*i);//从socket队列组里面释放socket成员。
        i = sockets.erase(i);//清除该socket信息。
        if (i == sockets.end())
          break;
      }
    }
    clients.CheckForTimeout(); //超时判断,若30秒,peer终端没有消息给Server发送http wait消息,就会注销ChannelMember信息。
    if (FD_ISSET(listener.socket(), &socket_set)) {
      DataSocket* s = listener.Accept();
      if (sockets.size() >= kMaxConnections) {//socket资源超过最大连接数。
        delete s;  // sorry, that's all we can take.
        printf("Connection limit reached\n");
      } else {
        sockets.push_back(s);//push新的socket资源到sockets队列里面。
        printf("New connection...\n");
      }
    }
  }

  for (SocketArray::iterator i = sockets.begin(); i != sockets.end(); ++i)
    delete (*i);//收到quit信令,释放所有socket资源。
  sockets.clear();

  return 0;
}

四、超时单点分析

ChannelMember::TimedOut函数根据waiting_socket_和time判断是否超时。

bool ChannelMember::TimedOut() {
  return waiting_socket_ == NULL && (time(NULL) - timestamp_) > 30;
}

检测到该Client的的waiting_socket_长达30秒,没有信令交互,注销Client的MemberChannel信息。

下面主要分析waiting_socket_和time处理流程:

  • ChannelMember::ChannelMember函数:

    Peer向Server发送sign_in信令,创建ChannelMember,初始化waiting_socket_指针为NULL。获取当前timestamp_时间。

ChannelMember::ChannelMember(DataSocket* socket)
  : waiting_socket_(NULL), id_(++s_member_id_),
    connected_(true), timestamp_(time(NULL)) {
  assert(socket);
  assert(socket->method() == DataSocket::GET);
  assert(socket->PathEquals("/sign_in"));
  name_ = rtc::s_url_decode(socket->request_arguments());
  if (name_.empty())
    name_ = "peer_" + int2str(id_);
  else if (name_.length() > kMaxNameLength)
    name_.resize(kMaxNameLength);

  std::replace(name_.begin(), name_.end(), ',', '_');
}
  • ChannelMember::OnClosing函数:

  Server主函数main检测到socket_done为true时,调用clients.OnClosing(s)函数,当判断出要close的socket与 waiting_socket_一致,则初始化waiting_socket_指针为NULL。

void ChannelMember::OnClosing(DataSocket* ds) {
  if (ds == waiting_socket_) {
    waiting_socket_ = NULL;
    timestamp_ = time(NULL);
  }
}
  • ChannelMember::SetWaitingSocket函数:

    当Server收到Peer发送的wait消息的时候,当queue_里面有消息要发送,直接发送。若没有消息发送,则将新的socket赋值给waiting_socket_。

void ChannelMember::SetWaitingSocket(DataSocket* ds) {
  assert(ds->method() == DataSocket::GET);
  if (ds && !queue_.empty()) {
    assert(waiting_socket_ == NULL);
    const QueuedResponse& response = queue_.front();
    ds->Send(response.status, true, response.content_type,
             response.extra_headers, response.data);
    queue_.pop();
  } else {
    waiting_socket_ = ds;
  }
}
  • ChannelMember::QueueResponse函数:

     PeerA转发消息给PeerB,当waiting_socket_不为空,先发送包出去,然后把waiting_socket_指针置空。若waiting_socket_为空,先把消息缓存在消息队列里面。

void ChannelMember::QueueResponse(const std::string& status,
                                  const std::string& content_type,
                                  const std::string& extra_headers,
                                  const std::string& data) {
  if (waiting_socket_) {
    assert(queue_.size() == 0);
    assert(waiting_socket_->method() == DataSocket::GET);
    bool ok = waiting_socket_->Send(status, true, content_type, extra_headers,
                                    data);
    if (!ok) {
      printf("Failed to deliver data to waiting socket\n");
    }
    waiting_socket_ = NULL;
    timestamp_ = time(NULL);
  } else {
    QueuedResponse qr;
    qr.status = status;
    qr.content_type = content_type;
    qr.extra_headers = extra_headers;
    qr.data = data;
    queue_.push(qr);
  }
}

总结下来:

1、waiting_socket_只有收到Client的wait报文,才会被赋值为有效值。

2、当转发结束peer消息到另外一端的时候,会释放waiting_socket_信息。

3、当长时间检测不到Peer与Server有通讯的时候,也会释放waiting_socket_信息。

webrtc这么做的原因是当Server和Client部署在不同的NAT后,当长时间没有socket通讯的时候,会释放NAT上的Session连接资源,下次再连接的时候,会更新Client测的IP地址和端口号信息。所以Server上保存老的Client地址信息也是无效的。干脆就删除这个注册资源。当前的视频通讯走P2P或者中转服务器路径,也用不到这个信息了。

若想长时间保留Client的MemberChannel信息,Client可以修改方案,定时给Server发送wait心跳信息,保存NAT的Session连接。



猜你喜欢

转载自blog.csdn.net/CrystalShaw/article/details/80746379