Webrtc从理论到实践七: 官方demo源码走读(peerconnection_server)

系列文章目录

Webrtc从理论到实践一:初识
Webrtc从理论到实践二: 架构
Webrtc从理论到实践三: 角色
Webrtc从理论到实践四: 通信
Webrtc从理论到实践五: 编译webrtc源码
Webrtc从理论到实践六: Webrtc官方demo运行

文章目录

  • 系列文章目录
  • 前言
  • 一、peerconnection_server 目录结构
  • 二、类图
  • 三、工作流程
    • 1. 监听socket端口
    • 2. 开启事件循环
    • 3.根据socket请求类型进行相应处理
      • 3.1 创建新的连接
      • 3.1 接收socket发送的信令
    • 4.流程简图


前言

本文源码基于webrtc m89版本,先从peerconnection_server开始分析


一、peerconnection_server 目录结构


Webrtc从理论到实践七: 官方demo源码走读(peerconnection_server)_第1张图片
     从图上我们可以看出peerconnection_server的源文件仅有七个:其中main文件主要用于主流程的控制以及网络事件的分发,data_socket文件用于socket的创建与数据的读取和发
送,peer_channel文件用于信令的处理以及socket的管理,utils文件里存放了两个常用的字符串处理函数。

二、类图

Webrtc从理论到实践七: 官方demo源码走读(peerconnection_server)_第2张图片
    简单介绍一下上面几个类的作用:首先,SocketBase类是对win32 socket api的封装,包含了创建和关闭两个接口。然后,ListeningSocket和DataSocket类都继承了SocketBase类,从他们的命名我们就可以得知,DataSocket的主要职责是用于从socket接收/发送数据,对应的接口是OnDataAvailable()和send()方法。ListeningSocket是用于接收连接和创建DataSocket实例的,Listen()接口用于监听端口,当接收到一个新的连接时就会通过Accept()创建一个新的DataSocket实例。ChannelMember用于处理从DataSocket 接收到的信令,并且与DataSocket是一 一对应的,每个ChannelMember都会保存一个从0开始递增的id_用于标识。PeerChannel类是ChannelMember的管理类,内部创建了一个_members数组用于保存建立连接的ChannelMember,并且可以通过Lookup()接口查找ChannelMember对象,还可以通过AddMember()将ChannelMember对象保存起来。

三、工作流程

首先说一下整个server的模型是事件驱动模型,采用select()接口实现,以下代码片段已做删减

1. 监听socket端口

    监听socket端口主要分为两步:第一个解析命令行参数,从命令行参数中获取指定端口。第二个创建socket 并且监听端口。

// InitFieldTrialsFromString stores the char*, so the char array  //must outlive the application.
const std::string force_field_trials = absl::GetFlag(FLAGS_force_fieldtrials);
webrtc::field_trial::InitFieldTrialsFromString(force_field_trials.c_str());

int port = absl::GetFlag(FLAGS_port);
 
// 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);

2. 开启事件循环

使用了一个while循环,将listening socket 和所有建立连接创建的socket添加到socket_set中进行select()处理

PeerChannel clients;
typedef std::vector<DataSocket*> SocketArray;
SocketArray sockets;
bool quit = false;
while(!quit){
    fd_set socket_set;
    FD_ZERO(&socket_set);
    //将监听socket添加到socket集合中
    if (listener.valid())
        FD_SET(listener.socket(), &socket_set);

    for (SocketArray::iterator i = sockets.begin(); i   != sockets.end(); ++i)
      //后面会将新建立连接的socket放到sokets数组中,需要将sockets中所有的socket都放到socket_set集合中进行监听
      FD_SET((*i)->socket(), &socket_set);

    struct timeval timeout = {10, 0};
    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) {
      //根据socket请求类型进行相应处理
   ...
  }
    ...
    ...
}

3.根据socket请求类型进行相应处理

3.1 创建新的连接

    当没有客户端连接的时候,sockets为空,不会进到for循环内部。当listener监听到有新客户端连接时会创建一个新的DataSocket并添加到sockets数组中,接着进入下一轮循环

//将新连接的DataSocket加入到sockets数组中
 if (FD_ISSET(listener.socket(), &socket_set)) {
      DataSocket* s = listener.Accept();
      if (sockets.size() >= kMaxConnections) {
        delete s;  // sorry, that's all we can take.
        printf("Connection limit reached\n");
      } else {
        sockets.push_back(s);
        printf("New connection...\n");
      }
    }

3.1 接收socket发送的信令

    遍历sockets数组,如果某个socket可读,则通过OnDataAvailable()和request_received()判断当前信令是否可读。在循环之前已经创建了一个PeerChannel clients对象用于管理所有连接的客户端,如果当前数据可读,则通过clients.Lookup(s)查找是否已经包含当前DataSocket,如果不存在,则该用户是第一次发送信令,并且如果http请求的url是“/sign_in”,则根据该DataSocket创建一个ChannelMember对象并加到clients的members_数组中进行统一管理。如果不是新用户,则根据请求的url中的”to=“去查找目标客户端target并转发数据到对端。

for (SocketArray::iterator i = sockets.begin(); i != sockets.end(); ++i) {
      DataSocket* s = *i;
      bool socket_done = true;
      if (FD_ISSET(s->socket(), &socket_set)) {
        if (s->OnDataAvailable(&socket_done) && s->request_received()) {
          ChannelMember* member = clients.Lookup(s);
          if (member || PeerChannel::IsPeerConnection(s)) {
            if (!member) {
              if (s->PathEquals("/sign_in")) {
                clients.AddMember(s);
              } else {
                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.
              socket_done = false;
            } else {
              ChannelMember* target = clients.IsTargetedRequest(s);
              if (target) {
                member->ForwardRequestToPeer(s, target);
              } else if (s->PathEquals("/sign_out")) {
                s->Send("200 OK", true, "text/plain", "", "");
              } else {
                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);
            if (quit) {
              printf("Quitting...\n");
              FD_CLR(listener.socket(), &socket_set);
              listener.Close();
              clients.CloseAll();
            }
          }
        }
      } else {
        socket_done = false;
      }

      if (socket_done) {
        printf("Disconnecting socket\n");
        clients.OnClosing(s);
        assert(s->valid());  // Close must not have been called yet.
        FD_CLR(s->socket(), &socket_set);
        delete (*i);
        i = sockets.erase(i);
        if (i == sockets.end())
          break;
      }
    }

4.流程简图

以上流程可以简化为下面的流程图:
Webrtc从理论到实践七: 官方demo源码走读(peerconnection_server)_第3张图片

下一篇:Webrtc从理论到实践八: 官方demo源码走读(peerconnection_client)(上)

你可能感兴趣的:(流媒体开发,webrtc,网络,c++,音视频,后端)