欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-07-06 11:07:22
...

一、整体流程图

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

 

二、全流程抓包

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;
  }
}
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);
  }
} 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连接。