5.6 信令状态机

在开始介绍端到端通信之前,必须先实现客户端的信令系统,让客户端与信令服务器可以互通,从而为端到端交换信息做好准备。那么客户端的信令系统该如何实现呢?

最简单的办法是通过状态机实现,其基本原理如下:每次发送/接收一个信令后,客户端都根据状态机当前的状态做相应的逻辑处理。比如当客户端刚启动时,其处于Init状态,在此状态下,用户只能向服务端发送join消息,待服务端返回joined消息后,客户端的状态机发生了变化,变成了joined状态后,才能开展后续工作。客户端的状态机如图5.1所示。

图5.1 信令状态机

从图中可以发现,客户端的状态机共有4种状态,分别是Init、joined、joined_unbind以及joined_conn。下面详述一下各种状态之间是如何变化的。

·客户端刚启动时,其初始状态为Init。

·在Init状态下,用户只能向服务器发送join消息;服务端收到join消息后,会返回joined消息;如果客户端能收到joined消息,则说明用户已经成功加入房间中,此时客户端状态更新为joined。

·在joined状态下,客户端有多种选择,根据不同的选择可以切换到不同的状态:

 –如果用户离开房间,客户端又回到了初始状态,即Init状态。

 –如果客户端收到第二个用户加入的消息(即other_joined消息),则切换到join_conn状态。在这种状态下,两个用户就可以进行通信了。

 –如果客户端收到第二个用户离开的消息(即bye消息),则需要将其状态切换到join_unbind。实际上,join_unbind状态与joined状态基本是一致的,不过可以通过这两种不同的状态值判断出用户之前的状态。

·如果客户端处于join_conn状态,当它收到bye消息时,会变成joined_unbind状态。

·如果客户端是joined_unbind状态,当它收到other_join消息时,会变成join_conn状态。

接下来看一下客户端状态机是如何实现的,参见代码5.9。

代码5.9 客户端状态机


1 var state = init;
2
3 // 连接信令服务器并根据信令更新状态机
4 function conn(){
5
6    // 建立socket.io 连接
7    socket = io.connect ();
8
9    // 收到joined 消息
10   socket.on('joined ', (roomid , id) =>
11      state = 'joined '; // 变更状态
12      …
13      // 创建连接
14      createPeerConnection ();
15      bindTracks ();
16      …
17   });
18
19   // 收到otherjoin 消息
20   socket.on('otherjoin ', (roomid) => {
21     …
22     state = 'joined_conn ';  // 更改状态
23     call();
24     …
25   });
26
27   // 收到full 消息
28   socket.on('full', (roomid , id) => {
29     …
30     hangup ();
31     socket.disconnect (); // 关闭连接
32     state = 'init'; // 回到初始化状态
33     …
34   });
35
36 // 收到用户离开的消息
37 socket.on('left', (roomid , id) => {
38     …
39     hangup ();
40     socket.disconnect ();
41     state='init' // 回到初始化状态
42     …
43   });
44
45 socket.on('bye', (room , id) => {
46     …
47     state = 'joined_unbind ';
48     hangup ();
49     …
50   });
51
52   …
53   // 向服务端发送join 消息
54   roomid = getQueryVariable('room');
55   socket.emit('join', roomid);
56
57  }
58
59   …
60   conn(); // 与信令服务器建立连接
61 …

在代码5.9中,首先执行第一行代码,将状态机的状态初始化为init;之后调用conn()函数,让客户端与信令服务器建立连接。而在conn()内部做了三件事:一是调用socket.io的connect()方法与信令服务器建立连接;二是向socket.io注册5个回调函数,分别对应5个信令消息,即joined、otherjoin、full、left以及bye消息,以便收到不同的消息时做不同的逻辑处理;三是向信令服务器发送join消息。至此客户端的运转就由信令驱动起来了。

当客户端收到服务端返回的joined消息后,会在回调之前注册到socket.io中的回调函数,因此上面代码的第10∼17行会被执行。在这段代码中,客户端首先变更自己当前的状态为joined,然后创建RTCPeerConnection(关于RTCPeerConnection的内容将会在5.7.1节详细介绍)对象,最后将采集到的音视频流绑定到之前创建好的RTCPeerConnection对象上。

当第二个用户上线后,第一个用户会收到服务端发来的otherjoin消息。上面代码中的第20∼25行会被执行。在这几行代码中,也是先变更客户端状态为joined_conn,然后调用call()函数。call()函数实现的是媒体协商,有关媒体协商相关的内容会在5.7.3节再做介绍,相关的代码也在5.7.3节中给出。

其他几种情况与前面介绍的两种情况是类似的,都是收到服务端的信令后回调对应的函数,在函数中变更状态,然后做相应的逻辑处理,这里就不再赘述了。