2024年最新WebRTC Native框架(五)(1),2024最新大厂前端面试真题解析

计算机网络

  • HTTP 缓存

  • 你知道 302 状态码是什么嘛?你平时浏览网页的过程中遇到过哪些 302 的场景?

  • HTTP 常用的请求方式,区别和用途?

  • HTTPS 是什么?具体流程

  • 三次握手和四次挥手

  • 你对 TCP 滑动窗口有了解嘛?

  • WebSocket与Ajax的区别

  • 了解 WebSocket 嘛?

  • HTTP 如何实现长连接?在什么时候会超时?

  • TCP 如何保证有效传输及拥塞控制原理。

  • TCP 协议怎么保证可靠的,UDP 为什么不可靠?

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

算法

  • 链表

  • 字符串

  • 数组问题

  • 二叉树

  • 排序算法

  • 二分查找

  • 动态规划

  • BFS

  • DFS

  • 回溯算法

各个目录的功能如下:

api目录:是对WebRTC功能件的封装,以更方便应用层调用,这里封装的内容包括audio、video、数据通道以及RTP传输,并在create_peerconnection_factory.h文件中定义了P2P通信的核心类PeerConnectionFactoryInterface;

audio目录:这里的audio层是用于发送和接收音频数据流的网络层,真实硬件的采集播放放在adm(audio device module),增强处理放在apm(audio processing module)里,adm和apm并不在这一目录下;

base目录:提供了一些依赖OS的基础函数,比如内存管理等;

build相关目录:使用与编译WebRTC的,在编译小节中会有编译说明;

call目录:从字面可以知道是用于通信用的,主要是RTP和RTCP相关协议的封装一遍WebRTC使用;

common_audio和common_video目录:音视频的各种算法都可能用到的,比如fir滤波,环形缓冲区,窗函数等;

examples:P2P等各个平台各种例子所在的目录;

media:是对video和audio增强和编解码的封装层,即video engine和audio engine。

modules:音视频具体功能实现所在的目录,如音视频编解码实现;音频混音、处理以及设备管理,视频采集播放以及数据发送和占用带宽估计等;

pc(peer connection)目录:P2P连接实现的核心目录;

sdk目录:android平台应用层Java和MAC平台应用层Object C访问natvie层的桥接层;

server端

STUN服务器:用于获取设备的外网地址;

TURN服务器:在P2P通信失败后用于中继;

ICE框架,整合了TURN和STUN。

信令服务器:负责端到端的连接,如SDP,candidate等;

因为由于IPv4地址数量不够用和安全的问题,开会的双方基本都在防火墙和NAT之后,通过运营商接入公网,当在家时手机、电脑、网络电视通过电信路由器上网时,路由器分配给我们的地址就是“192.168.XXX.XXX”,但是在公网上我们的数据头地址被转换成电信服务商提供的地址了,如果双发希望直接通信而不需要公共服务器中转(加大了延迟和丢包的不确定性)数据包,这时需要NAT穿透技术,STUN和TURN就是这种透传协议,ICE是一套整合了这两个协议的框架。

client端

Android IOS Windows MAC Linux 浏览器。

由于不同平台使用了不同的UI库和编程语言,他们的实现差异很大,但是不同的平台都会支持c/c++,所以为了适配不同的平台,WebRTC提供了SDK层,Linux平台基于使用GTK的c++,MAC和IOS平台基于使用cocoa库的Object c,android平台基于JAVA,为了让这些平台都能够调用c/c++核心函数,WebRTC提供了android和object c的封装层,这称为SDK层,android中使用了JNI机制使得UI层的JAVA程序和实现核心功能的c/c++程序可以互相调用,类似的object c使用了.mm扩展程序是得UI的.m程序可以和c/c++互相调用。由于访问各个平台都提供了C API(为了效率),所有已在音视频以及网络API都可以直接通过包含不同操作系统的头文件来实现跨平台差异化编译。

WebRTC官方工程里包括了一些应用程序例子,它们位于src/examples目录下,首先看下各个目录的作用。

peerconnection:windows和linux平台下使用Native API进行P2P通信的例子,其中客户端应用程序在client目录,服务器端应用程序在server目录下。客户端具有简单的音视频功能,服务器端使得客户端程序能够通过信令开启会议。整个会议过程需要服务器先启动服务,如./peerconnection_server --port=8888,正确启动后会有如下输出:

Server listening on port 8888

然后启动客户端,客户端UI包括如下部分,connecting to a server:在客户端程序启动时,需要指定服务器IP地址,然后可以点击connect按钮;其中localhost表示本机地址。

select a peer:当不同的client端连接到server时,他们就会互相发现对方如下所示(这是因为两个client是在同一个电脑不同终端启动的,所以两边看到的都是gsc@240),这是可以直接双击或者选中后回车建立P2P连接;

视频会议:当成功建立端到端的链接后,桌面会以全屏的形式显示视频界面,如下;

Ending chat session:当按Esc键时会推回到P2P列表选择界面;

Ending connection:继续按Esc将回到server连接界面;

工程编译

export PKG_CONFIG_PATH=$WEBRTCBUILDS_FOLDER/lib/Release/pkgconfig

Go to the peerconnection server folder

g++ -o peerconnection_server main.cc data_socket.cc peer_channel.cc utils.cc \

( p k g − c o n f i g − − c f l a g s − − l i b s − − d e f i n e − v a r i a b l e = p r e f i x = (pkg-config --cflags --libs --define-variable=prefix= (pkgconfigcflagslibsdefinevariable=prefix=WEBRTCBUILDS_FOLDER libwebrtc_full)

Go to the peerconnection client folder

g++ -o peerconnection_client linux/main.cc linux/main_wnd.cc conductor.cc defaults.cc peer_connection_client.cc \

( p k g − c o n f i g − − c f l a g s − − l i b s − − d e f i n e − v a r i a b l e = p r e f i x = (pkg-config --cflags --libs --define-variable=prefix= (pkgconfigcflagslibsdefinevariable=prefix=WEBRTCBUILDS_FOLDER libwebrtc_full) \

$(pkg-config --cflags --libs gtk±2.0) \

$(pkg-config --cflags --libs x11)

WebRTC peerconnection分析

网络一部分是用于和signal服务器交互,一部分是P2P之间用,在会议开始前,需要知道通信双方的多媒体能力(不如支不支持视频,视频的编码能力等)以便双方能够通信,这被称为握手通信,为了实时性和效率,还会进行打洞(即上文说的TUN、STUN、ICE等)。

在WebRTC自带的端到端连接的例子中,客户端A和客户端B均通过socket和信令服务器建立TCP连接,object C下可以使用cocoaAsyncSocket框架;

然后客户端A通过voiceegine和videoengine获取多媒体数据并创建PeerConnection对象,然后通过AddTracks将音视频添加到PeerConnection中;

接下来客户端A调用PeerConnection对象的CreateOffer方法创建SDP offer,然后将这个SDP通过signaling server转发给客户端B,B在收到服务端转发的SDP offer之后,调用CreateAnswer创建SDP 应答,并通过信令服务器转发给A,客户端A收到SDP 应答之后,两边就完成了多媒体能力的协商,为音视频流的接收做好了准备;

接下来打洞以便进行P2P多媒体数据传输,首先客户端A通过PeerConnection创建时的参数等待来自ICE服务器的通信,获取自己的candidate,当获取到candidate时客户端会自动调用OnIceCandidate,客户端A通过signaling server将自己的candidate发送给客户端B,客户端B用类似的逻辑(同一套代码)将自己的candidate发送给客户端A,至此candidate握手完成,在SDP和candidate握手完成之后,两个客户端之间就建立了一条P2P连接,视频流就可以不通过Server端而直接进行传输;

PeerConnection应用层

这小节主要讲述应用程序是如何和native层代码交互的,使用WebRTC工具编译的native层仓库代码会链接到libjingle_peerconnection.so库中,上层调用这个库提供的音频、视频以及网络功能实现控制。其中linux和windows的上层界面开发较为相似,MAC/IOS(涉及object c)和android(涉及java)较为相似,Linux和windows通过c++直接调用natvie层代码,相对而言比较容易入门。

server端逻辑

server端首先启动,使用了socket编程,然后等待客户端连接,当客户端A连接时,会记录客户端A的信息,然后又有客户端B接入(和A一样会有HTTP的GET请求),这是会记录客户端B的信息,并将客户端A推动给客户端B,客户端B推送给客户端A,使得它们互相之间能够发现对方,然后客户端A点解连接客户端B,这是它们会启动SDP协议,以协商通信能力,并最终启动视频会议,服务端的逻辑较为简单,这里以核心代码片段说明:

int main(int argc, char* argv[]) {

//首先创建侦听套接字,默认端口号位8888

ListeningSocket listener;

if (!listener.Create()) {

PeerChannel clients;

typedef std::vector<DataSocket*> SocketArray;

SocketArray sockets;

//quit 标识是是否退出的标志,否则这个循环一直会侦听网络端口数据情况

while (!quit) {

fd_set socket_set;

FD_ZERO(&socket_set);

if (listener.valid())

FD_SET(listener.socket(), &socket_set);

//在新客户端接入时,会进入以下逻辑,

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 {

//如果新连接被成功添加到SocketArray中,则中断会打印改信息

sockets.push_back(s);

printf(“New connection…\n”);

}

}

//在后续的循环中,由于SocketArray中有新的连接,所以会进入如下逻辑

for (SocketArray::iterator i = sockets.begin(); i != sockets.end(); ++i) {

DataSocket* s = *i;

if (FD_ISSET(s->socket(), &socket_set)) {

if (s->OnDataAvailable(&socket_done) && s->request_received()) {

//从服务端注册列表中,检索这个client,由于是for循环从begin到end的遍历,

//所以遍历完了之后,所有客户端界面都会显示连上服务器的其它客户端,这样想和谁通信,

//客户端上点击对应的客户端项就行了,如果未找到,返回NULL

ChannelMember* member = clients.Lookup(s);

if (member || PeerChannel::IsPeerConnection(s)) {

//如果发现s是注册,并且这个s并没有在服务端的注册列表中,则会用AddMember将其添加到注册列表中

if (!member) {

if (s->PathEquals(“/sign_in”)) {

clients.AddMember(s);//这里的AddMember会打印记录的客户端名,如截图的gsc@240

}

//如果客户端是等待状态,这是什么也不用做,等到有message时才进行处理

} else if (member->is_wait_request(s)) {

// no need to do anything.

socket_done = false;

//流程到这里,说明这个客户端已经在服务器注册了,并且这次收到的HTTP消息是message,不是wait,sign_out之类的

} else {

//这里找到想要通信客户端(设为B)的ChannelMember对象,如果server端没找到,则返回NULL

ChannelMember* target = clients.IsTargetedRequest(s);

if (target) {

//这里将包含客户端A SDP协议信息转送给B

member->ForwardRequestToPeer(s, target);

} else if (s->PathEquals(“/sign_out”)) {

s->Send(“200 OK”, true, “text/plain”, “”, “”);

}

}

}

}

}

当client点击connect之后,客户端给服务器发过去"/sign_in"的get请求,服务器给当前客户列表中的每个都发一遍新用户的信息,格式是"<client_name>,<client_id>,1\r\n",服务器将现有的peers列表信息以如下格式连接"<client_name>,<client_id>,if_connected?1:0\r\n"发送给新来的client.得到这些信息后客户端就会把他们显示在pees列表中。客户端每次给服务器发送消息之后都要发送一次"/wait"的get请求,目的是提供response对象,以便服务器在需要给客户端发消息时使用。当客户端双击某个peer发起连接时,会给服务器发来"/message"的post请求,请求的peer_id参数是发起方的id,to参数是要连接的peer的id,内容是sdp信息;服务器把内容转发给相应id的客户端,客户端接收到后解析sdp,得到媒体流信息(是否含音视频,音视频参数等),在本地建立媒体流通道,以便之后接收媒体数据和传给主窗体显示;完了创建本地的sdp,并发回给对方。客户端从STUN/TURN服务器获取本地地址(candidate),经由服务器中转发给对方(同样走/messgae),对方接收到后会向该地址发送消息等待回应以验证是否可连通,联通成功peerconnection就此建立完成,音视频数据就可以通过基于udp的rtp/rtcp协议在peerconnection之间传输。

客户端逻辑

客户端稍微复杂些,和server端相比,多了多媒体内容,但是WebRTC的例子非常巧妙,使用较少的代码就实现待UI的视频通信的完整例子。

Conductor是封装了windows和PeerConnection两个主要类,是该工程的核心,conductor通过CreatePeerConnectionFactory方法创建PeerConnectionFactoryInterface接口的实现对象,通过改接口创建WebRTC核心协议的PeerConnectionInterface接口对象,PeerConnectionFactoryInterface还提供了创建本地音视频功能的接口,conductor的回调通过PeerConnectionObserver接口完成。

Linux UI

linux UI层使用了GTK显示框架,将点击、输入等事件使用信号槽的机制,将界面和需要执行的动作关联起来,如点击加入会议涉及到PeerConnection创建和连接,这里clicked是Button触发的信号,而Button触发时会自动触发row-activated信号,所以下面的两个回调都会被调用到,OnClickedCallback获取用户输入的登录服务IP地址和端口号,并向服务器发起HTTP登录请求,而row-activated信号的回调PeerConnection初始化并创建和发送SDP包。代码实现上将PeerConnection和windows作为两个独立的组件,用conductor类管理起来。

g_signal_connect(button, “clicked”, G_CALLBACK(OnClickedCallback), this);

g_signal_connect(peer_list_, “row-activated”,

G_CALLBACK(OnRowActivatedCallback), this);

P2P的创建过程有些琐碎,这里罗列如下:

peer_connection_factory_ = webrtc::CreatePeerConnectionFactory(

nullptr /* network_thread */, nullptr /* worker_thread */,

nullptr /* signaling_thread */, nullptr /* default_adm */,

webrtc::CreateBuiltinAudioEncoderFactory(),

webrtc::CreateBuiltinAudioDecoderFactory(),

webrtc::CreateBuiltinVideoEncoderFactory(),

webrtc::CreateBuiltinVideoDecoderFactory(), nullptr /* audio_mixer */,

nullptr /* audio_processing */);

//这里config的一些配置包括dtls是否启用等标识

peer_connection_ = peer_connection_factory_->CreatePeerConnection(

config, nullptr, nullptr, this);

rtc::scoped_refptrwebrtc::AudioTrackInterface audio_track(

peer_connection_factory_->CreateAudioTrack(

kAudioLabel, peer_connection_factory_->CreateAudioSource(

cricket::AudioOptions())));

auto result_or_error = peer_connection_->AddTrack(audio_track, {kStreamId});

rtc::scoped_refptr video_device =

CapturerTrackSource::Create();

if (video_device) {

rtc::scoped_refptrwebrtc::VideoTrackInterface video_track_(

peer_connection_factory_->CreateVideoTrack(kVideoLabel, video_device));

main_wnd_->StartLocalRenderer(video_track_);

result_or_error = peer_connection_->AddTrack(video_track_, {kStreamId});

发送完登录请求和SDP请求之后,由网络线程一直检测来自网络的数据,并触发相应的函数,如更新列表和对端发来的数据,最终调用OnMessageFromPeer处理对端发来的数据,如果对端是新来的,则会创建InitializePeerConnection对象,这在主动发起会议时已经见过,如果是主动发起方回调了这个函数,就不会再创建这个对象了,但是依然要解析对端的SDP信息,SDP信息是JSON格式。

std::unique_ptrwebrtc::SessionDescriptionInterface session_description =

webrtc::CreateSessionDescription(type, sdp, &error);

peer_connection_->SetRemoteDescription(

DummySetSessionDescriptionObserver::Create(),

session_description.release());

if (type == webrtc::SdpType::kOffer) {

peer_connection_->CreateAnswer(

this, webrtc::PeerConnectionInterface::RTCOfferAnswerOptions());

}

拿到的信息除了SDP外还可能是打洞信息,则也需要将他们保存下来。

std::unique_ptrwebrtc::IceCandidateInterface candidate(

webrtc::CreateIceCandidate(sdp_mid, sdp_mlineindex, sdp, &error));

if (!peer_connection_->AddIceCandidate(candidate.get())) {

RTC_LOG(WARNING) << “Failed to apply the received candidate”;

return;

}

MAC UI

这里以MAC平台示例说明如何使用WebRTC,下图是object c所写的应用程序的界面,即上一节UI层相关内容:

程序最开始的执行函数在examples/objc/AppRTCMobile/mac目录的main.m(object c下的c扩展文件以.m结尾,c++扩展以.mm结尾),这个main函数如下:

APPRTCAppDelegate.h文件

@interface APPRTCAppDelegate : NSObject

@end

APPRTCAppDelegate.m文件

@implementation APPRTCAppDelegate {

APPRTCViewController* _viewController;

NSWindow* _window;

}

main.m文件

#import <AppKit/AppKit.h>

#import “APPRTCAppDelegate.h”

int main(int argc, char* argv[]) {

@autoreleasepool {

[NSApplication sharedApplication];

APPRTCAppDelegate* delegate = [[APPRTCAppDelegate alloc] init];

[NSApp setDelegate:delegate];

[NSApp run];

}

}

这个函数是object的语法,NSApplicationDelegate是cocoa库里的应用程序代理,本身这个应用程序定理很多方法,如run运行等,@interface 表明APPRTCAppDelegate是继承?了NSApplicationDelegate协议,这个类本在实现时(@implementation)定义了两种组件,APPRTCViewController和NSWindow,NSWindow是上图看到的图像界面,比如长宽之类都在这里,APPRTCViewController用于和用户交互,如输入框和按钮。当应用程序在mian.m里执行到run时,会触发APPRTCAppDelegate.m(这和cocoa里的应用程序生命周期有关)。

- (void)applicationDidFinishLaunching:(NSNotification*)notification {

RTCInitializeSSL();

NSScreen* screen = [NSScreen mainScreen];

NSRect visibleRect = [screen visibleFrame];

NSRect windowRect = NSMakeRect(NSMidX(visibleRect),

NSMidY(visibleRect),

1320,

1140);

NSUInteger styleMask = NSTitledWindowMask | NSClosableWindowMask;

_window = [[NSWindow alloc] initWithContentRect:windowRect

styleMask:styleMask

backing:NSBackingStoreBuffered

defer:NO];

_window.delegate = self;

[_window makeKeyAndOrderFront:self];

[_window makeMainWindow];

_viewController = [[APPRTCViewController alloc] initWithNibName:nil

bundle:nil];

[_window setContentView:[_viewController view]];

}

其中-号表示的私有实现,这里就是设置显示窗的参数,并初始化控制组件(APPRTCViewController),APPRTCViewController负责显示local和remote视频流,会议号和会议控制,当点击加入会议后会执行APPRTCViewController.m中的如下方法:

- (void)startCall:(id)sender {

NSString* roomString = _roomField.stringValue;

// Generate room id for loopback options.

if (_loopbackButton.intValue && [roomString isEqualToString:@“”]) {

roomString = [NSUUID UUID].UUIDString;

roomString = [roomString stringByReplacingOccurrencesOfString:@“-” withString:@“”];

}

[self.delegate appRTCMainView:self

didEnterRoomId:roomString

loopback:_loopbackButton.intValue];

[self setNeedsUpdateConstraints:YES];

}

roomString是前面输入的房间号字符串,如果空的就是会测试模式.

@interface APPRTCViewController ()

<ARDAppClientDelegate, APPRTCMainViewDelegate>

@property(nonatomic, readonly) APPRTCMainView* mainView;

@end

这个控件因为要管理signaling和多媒体传输以及状态和多媒体显示,这里使用ARDAppClientDelegate实现前者,而APPRTCMainViewDelegate实现状态和多媒体显示;尖括号表示APPRTCViewController遵循上面两个protocol,点击start call最终会调到ARDAppClientDelegate里的connectToRoomWithId方法,下面的代码片段是mac目录掉到上一级目录AppRTCMobile的核心代码段。

mac/APPRTCViewController.m

ARDAppClient* client = [[ARDAppClient alloc] initWithDelegate:self];

[client connectToRoomWithId:roomId

settings:[[ARDSettingsModel alloc] init]  // Use default settings.

这个方法定义于src/examples/objc/AppRTCMobile/ARDAppClient.h。

- (void)connectToRoomWithId:(NSString *)roomId

settings:(ARDSettingsModel *)settings

isLoopback:(BOOL)isLoopback {

NSParameterAssert(roomId.length);

NSParameterAssert(_state == kARDAppClientStateDisconnected);

_settings = settings;

_isLoopback = isLoopback;

self.state = kARDAppClientStateConnecting;

//初始化视频编解码参数

RTCDefaultVideoDecoderFactory *decoderFactory = [[RTCDefaultVideoDecoderFactory alloc] init];

RTCDefaultVideoEncoderFactory *encoderFactory = [[RTCDefaultVideoEncoderFactory alloc] init];

encoderFactory.preferredCodec = [settings currentVideoCodecSettingFromStore];

_factory = [[RTCPeerConnectionFactory alloc] initWithEncoderFactory:encoderFactory

decoderFactory:decoderFactory];

#if defined(WEBRTC_IOS)

if (kARDAppClientEnableTracing) {

NSString *filePath = [self documentsFilePathForFileName:@“webrtc-trace.txt”];

RTCStartInternalCapture(filePath);

}

#endif

// 申请 TURN.

__weak ARDAppClient *weakSelf = self;

[_turnClient requestServersWithCompletionHandler:^(NSArray *turnServers,

NSError *error) {

if (error) {

RTCLogError(@“Error retrieving TURN servers: %@”, error.localizedDescription);

}

ARDAppClient *strongSelf = weakSelf;

[strongSelf.iceServers addObjectsFromArray:turnServers];

strongSelf.isTurnComplete = YES;

[strongSelf startSignalingIfReady];//关键

}];

// 通过room server加入开会房间

[_roomServerClient joinRoomWithRoomId:roomId

isLoopback:isLoopback

completionHandler:^(ARDJoinResponse *response, NSError *error) {

ARDAppClient *strongSelf = weakSelf;

if (error) {

[strongSelf.delegate appClient:strongSelf didError:error];

return;

}

NSError *joinError =

[[strongSelf class] errorForJoinResultType:response.result];

if (joinError) {

RTCLogError(@“Failed to join room:%@ on room server.”, roomId);

[strongSelf disconnect];

[strongSelf.delegate appClient:strongSelf didError:joinError];

return;

}

RTCLog(@“Joined room:%@ on room server.”, roomId);

strongSelf.roomId = response.roomId;

strongSelf.clientId = response.clientId;

strongSelf.isInitiator = response.isInitiator;

for (ARDSignalingMessage *message in response.messages) {

if (message.type == kARDSignalingMessageTypeOffer ||

message.type == kARDSignalingMessageTypeAnswer) {

strongSelf.hasReceivedSdp = YES;

[strongSelf.messageQueue insertObject:message atIndex:0];

} else {

[strongSelf.messageQueue addObject:message];

}

}

strongSelf.webSocketURL = response.webSocketURL;

strongSelf.webSocketRestURL = response.webSocketRestURL;

最后

我可以将最近整理的前端面试题分享出来,其中包含HTML、CSS、JavaScript、服务端与网络、Vue、浏览器、数据结构与算法等等,还在持续整理更新中,希望大家都能找到心仪的工作。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

篇幅有限,仅展示部分截图:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值