Catalog
  1. 1. 使用
  2. 2. 一些流程的关键函数
    1. 2.1. 收集本地候选
    2. 2.2. 处理 peer 候选
    3. 2.3. 连通测试
    4. 2.4. ICE 数据 & 媒体数据处理
  3. 3. 关键类
    1. 3.1. 关系
    2. 3.2. nr_ice_ctx_
    3. 3.3. nr_ice_peer_ctx_
    4. 3.4. nr_ice_candidate_
    5. 3.5. nr_ice_component_
    6. 3.6. nr_ice_cand_pair_
    7. 3.7. nr_ice_media_stream_
  4. 4. nr_stun_client_ctx_
    1. 4.1. nr_socket_
    2. 4.2. nr_ice_socket_
nicer库源码分析-Licode系列NO.8

nicer 是由 c 语言实现的 ICE 库。licode 原本使用了libnice 和 nicer 库, 后来在某个版本删除了 libnice (因为它依赖 glibc,比较重),仅支持 nicer 库。git地址。说起来 nicer 库也是很老的库了,并且似乎没有维护了,而 libnice 虽然重,但是仍然在不断更新和维护。项目做如此的选择着实是有些不明白。

使用

  1. 调用 nr_ice_ctx_create_with_credentials 创建 nr_ice_ctx_
  2. trickle ice 模式下,调用 nr_ice_ctx_set_trickle_cb 设置回调函数, 实际上是收集候选的回调。
  3. 创建 nr_ice_handler_vtbl,并设置一系列回到函数。包括状态通知回调、数据接收回调等等。
  4. 创建 nr_ice_handler
  5. 调用 nr_ice_peer_ctx_create 创建 nr_ice_peer_ctx
  6. 调用 nr_ice_add_media_stream 创建 nr_ice_media_stream
  7. 调用 nr_ice_ctx_set_port_range 设置端口使用范围
  8. 设置 turn 和 stun 服务地址
  9. 调用 nr_ice_gather 开始收集候选地址
  10. 处理回调

一些流程的关键函数

收集本地候选

1
2
3
4
5
6
7
8
// 初始化工作完成后,就主动调用这个函数开始收集候选地址
nr_ice_gather
// 发现主机候选地址,也就是读取本机网卡地址(服务器暴露在公网,所以只关心主机候选就可以了)
nr_stun_find_local_address
// 默认使用 udp 信道,这里会创建候选地址,初始化 socket
nr_ice_component_initialize_udp
// 候选收集完成的回调函数
nr_ice_candidate_fire_ready_cb

处理 peer 候选

1
2
3
4
5
6
7
8
9
10
11
12
// 解析 peer candidate 入口
nr_ice_peer_ctx_parse_trickle_candidate
// 创建 peer 候选地址,这里不会有 socket 创建。
nr_ice_peer_candidate_from_attribute
// 收到新的 peer 候选地址了,要用这个地址和本地候选构造候选地址对。
nr_ice_media_stream_pair_candidate
// 选定的 local candidate 和 peer candidate 组成地址对
nr_ice_candidate_pair_create
// 设置优先级
nr_ice_candidate_pair_set_priority
// 插入候选地址对列表, 最终会插入到 component 所对应的 stream 中
nr_ice_component_insert_pair

连通测试

1
2
3
4
5
6
7
8
9
10
11
// 收到 peer 候选并生成新的地址对后会被触发(不限于这一种触发方式)
nr_ice_media_stream_start_checks
// 检测入口,从 stream 的候选对中选择一个开始检测
nr_ice_media_stream_check_timer_cb
nr_ice_candidate_pair_start
// 构造一个 nr_ice_stun_ctx_,并设置 IO 回调函数
nr_ice_socket_register_stun_client
// 切换 stun_ctx_ 的 state 为 RUNNING 状态
nr_stun_client_start
// 发出 stun bind request
nr_stun_client_send_request

ICE 数据 & 媒体数据处理

网络 IO 事件都是异步的,所以需要循环调用 IO 事件检测函数来检测是否有事件发生。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// IOWorker 中循环调用这个时间检测函数
NR_async_event_wait2
// IO 回调
nr_ice_socket_readable_cb
// 处理 stun 返回消息,并设置 stun_ctx_ 状态为 DONE
nr_stun_client_process_response
// 检测结束时调用的函数用于触发下面那个回调函数
nr_stun_client_fire_finished_cb
// 检测返回后的回调函数,处理返回结果
nr_ice_candidate_pair_stun_cb
// 如果是成功状态,那么需要根据标志位(比如是否是激进的提名,又或者 peer 设置了提名标识)来判断是否发起提名
nr_ice_component_nominated_pair
// 流准备完成的回调
stream_ready

媒体数据的入口与 ICE 数据一致, 在 nr_ice_socket_readable_cb 中,根据消息类型,选择 nr_ice_ctx_deliver_packets 分支。

关键类

下面列出了一下关键类,加上了一些注释做备忘,可以跳过不看。基本上很多类的名字都与 rfc 标准文档里的定义匹配得上,所以看一下 rfc 对理解这个库实现有一些帮助。

关系

TIM截图20200122105615.png

nr_ice_ctx_ 抽象本地的数据, nr_ice_peer_ctx_ 抽象 peer 的数据。大体上这两边的数据结构差异不大,区别体现在少数字段上。可能是设计者觉得这样设计代码更加清晰。

nr_ice_ctx_

维护本地地址列表
维护 nr_ice_media_stream_ 列表(为什么有列表?)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
struct nr_ice_ctx_ {
UINT4 flags; // ?
char *label; // media stream id
char *ufrag; // ICE 用户名
char *pwd; // ICE 用户密码
UINT4 Ta; // 值为 20, 硬编码, 不知用途。
nr_ice_stun_server *stun_servers; /* The list of stun servers */
int stun_server_ct;
nr_ice_turn_server *turn_servers; /* The list of turn servers */
int turn_server_ct;
nr_local_addr *local_addrs; /* The list of available local addresses and corresponding interface information 本地地址列表*/
int local_addr_ct; // 本地地址数量

nr_resolver *resolver; /* The resolver to use */
nr_interface_prioritizer *interface_prioritizer; /* Priority decision logic */
nr_socket_wrapper_factory *turn_tcp_socket_wrapper; /* The TURN TCP socket wrapper to use */
nr_socket_factory *socket_factory; // 用户创建 nr_socket 的方法。
/*
*默认的 factory
nr_socket_local_create, // 其内部调用当前平台的 socket api 创建 socket
no_op //空函数,仅返回 0
*/

nr_ice_foundation_head foundations;

nr_ice_media_stream_head streams; /* Media streams */
int stream_ct;
nr_ice_socket_head sockets; /* The sockets we're using */
int uninitialized_candidates; // 未初始化的候选地址(component中)数量

UINT4 gather_rto;
UINT4 stun_delay;

UINT4 test_timer_divider;

nr_ice_peer_ctx_head peers; // 对端 ctx
nr_ice_stun_id_head ids;

NR_async_cb done_cb;
void *cb_arg;

nr_ice_trickle_candidate_cb trickle_cb;
void *trickle_cb_arg;

char force_net_interface[MAXIFNAME];
nr_ice_stats stats;
uint16_t min_port;
uint16_t max_port;
};

nr_ice_peer_ctx_

ICE 流程中 peer 的抽象, 这个和 ctx_ 差异并不是非常大

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
struct nr_ice_peer_ctx_ {
int state;
#define NR_ICE_PEER_STATE_UNPAIRED 1
#define NR_ICE_PEER_STATE_PAIRED 2

char *label;
nr_ice_ctx *ctx;
nr_ice_handler *handler;

// 是否是 offer 方
UCHAR controlling; /* 1 for controlling, 0 for controlled。 ice 连通测试中的角色*/
UCHAR controlling_conflict_resolved;
UINT8 tiebreaker;

char *peer_ufrag;
char *peer_pwd;
int peer_lite;
int peer_ice_mismatch;

nr_ice_media_stream_head peer_streams; // peer stream 对象,初始化时,内容和本地 stream 一致
int active_streams;
int waiting_pairs;
UCHAR checks_started;

void *connected_cb_timer;
UCHAR reported_connected;
void *trickle_grace_period_timer;

STAILQ_ENTRY(nr_ice_peer_ctx_) entry;
};

nr_ice_candidate_

候选地址信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
struct nr_ice_candidate_ {
char *label;
char codeword[5];
int state;
#define NR_ICE_CAND_STATE_CREATED 1
#define NR_ICE_CAND_STATE_INITIALIZING 2
#define NR_ICE_CAND_STATE_INITIALIZED 3
#define NR_ICE_CAND_STATE_FAILED 4
#define NR_ICE_CAND_PEER_CANDIDATE_UNPAIRED 9
#define NR_ICE_CAND_PEER_CANDIDATE_PAIRED 10
struct nr_ice_ctx_ *ctx;
// isock & osock 最终都指向同一个 socket
nr_ice_socket *isock; /* The socket to read from
(it contains all other candidates
on this socket) */
nr_socket *osock; /* The socket to write to */
nr_ice_media_stream *stream; /* 对应的 stream 对象 */
nr_ice_component *component; /* The component this is associated with */
nr_ice_candidate_type type; /* The type of the candidate (S 4.1.1) */
nr_socket_tcp_type tcp_type;
UCHAR component_id; /* The component id (S 4.1.2.1) */
nr_transport_addr addr; /* The advertised address;
JDR calls this the candidate */
nr_transport_addr base; /* The base address (S 2.1)*/
char *foundation; /* Foundation for the candidate (S 4) */
UINT4 priority; /* The priority value (S 5.4 */
nr_ice_stun_server *stun_server;
nr_transport_addr stun_server_addr; /* Resolved STUN server address */
void *delay_timer;
void *resolver_handle;

/* Holding data for STUN and TURN */
union {
struct {
nr_stun_client_ctx *stun;
void *stun_handle;
/* If this is a srflx that is piggybacking on a relay candidate, this is
* a back pointer to that relay candidate. */
nr_ice_candidate *relay_candidate;
} srvrflx;
struct {
nr_turn_client_ctx *turn;
nr_ice_turn_server *server;
nr_ice_candidate *srvflx_candidate;
nr_socket *turn_sock;
void *turn_handle;
} relayed;
} u;

NR_async_cb done_cb;
void *cb_arg;

NR_async_cb ready_cb;
void *ready_cb_arg;
void *ready_cb_timer;

TAILQ_ENTRY(nr_ice_candidate_) entry_sock;
TAILQ_ENTRY(nr_ice_candidate_) entry_comp;
};

nr_ice_component_

实际的通道对象, 被 Stream 维护。
管理本地的候选地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
struct nr_ice_component_ {
// 当前的状态
int state;
#define NR_ICE_COMPONENT_UNPAIRED 0
#define NR_ICE_COMPONENT_RUNNING 1
// 有提名候选
#define NR_ICE_COMPONENT_NOMINATED 2
#define NR_ICE_COMPONENT_FAILED 3
#define NR_ICE_COMPONENT_DISABLED 4
struct nr_ice_ctx_ *ctx;
struct nr_ice_media_stream_ *stream;
nr_ice_component *local_component; // peer 端有效,与之对应的本地 component

int component_id;
nr_ice_socket_head sockets; // 每个地址都创建一个 socket
nr_ice_candidate_head candidates; // 候选地址
int candidate_ct; //候选地址数量
nr_ice_pre_answer_request_head pre_answer_reqs;

int valid_pairs;
struct nr_ice_cand_pair_ *nominated; /* 优先级最高的提名候选 */
struct nr_ice_cand_pair_ *active; // 这个和上面应该是同一个

// 处理 stun 消息(包括连通测试也是 stun 协议)
nr_stun_client_ctx *consent_ctx;
void *consent_timer;
void *consent_timeout;
void *consent_handle;
int can_send;
int disconnected;
struct timeval consent_last_seen;

STAILQ_ENTRY(nr_ice_component_)entry;
};

nr_ice_cand_pair_

候选地址对

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
struct nr_ice_cand_pair_ {
nr_ice_peer_ctx *pctx;
char codeword[5]; // 使用 ssrc lable, 通过特殊的计算得到的字符串,作用不明。
char *as_string;
int state; /* The current state (S 5.7.3) */
// ICE 连接状态
#define NR_ICE_PAIR_STATE_FROZEN 1 // 冻结, 初始状态
#define NR_ICE_PAIR_STATE_WAITING 2 // 等待
#define NR_ICE_PAIR_STATE_IN_PROGRESS 3 // 正在检测
#define NR_ICE_PAIR_STATE_FAILED 4 // 检测失败
#define NR_ICE_PAIR_STATE_SUCCEEDED 5 // 检测成功
#define NR_ICE_PAIR_STATE_CANCELLED 6

// 对端提名当前 cand
UCHAR peer_nominated; /* The peer sent USE-CANDIDATE
on this check */
UCHAR nominated; /* Is this nominated or not 这个地址对是否被提名 */

UCHAR triggered; /* Ignore further trigger check requests */

UINT8 priority; /* The priority for this pair 改地址对的优先级 */
nr_ice_candidate *local; /* The local candidate 本地候选地址*/
nr_ice_candidate *remote; /* The remote candidate peer 候选地址*/
char *foundation; /* The combined foundations */

nr_stun_client_ctx *stun_client; /* STUN context when acting as a client */
void *stun_client_handle;

void *stun_cb_timer;
void *restart_role_change_cb_timer;
void *restart_nominated_cb_timer;

TAILQ_ENTRY(nr_ice_cand_pair_) check_queue_entry; /* the check list */
TAILQ_ENTRY(nr_ice_cand_pair_) triggered_check_queue_entry; /* the trigger check queue */
};

nr_ice_media_stream_

媒体对象,维护了多个(根据实际情况)通道(component), 如果没有开启 rtp/rtcp 通道复用的话,就会有 2 个 component, 否则只有 1 个。
维护候选地址对列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
struct nr_ice_media_stream_ {
char *label; // ICE ssrc 媒体标识
struct nr_ice_ctx_ *ctx;
struct nr_ice_peer_ctx_ *pctx;

struct nr_ice_media_stream_ *local_stream; /* peer_ctx 中的stream时使用, 存放对应的本地的 stream 对象 */
int component_ct; // component 数量
nr_ice_component_head components; // 通道

char *ufrag; /* ICE username */
char *pwd; /* ICE password */
char *r2l_user; /* The username for incoming requests */
char *l2r_user; /* The username for outgoing requests */
Data r2l_pass; /* The password for incoming requests */
Data l2r_pass; /* The password for outcoming requests */
int ice_state; // 初始状态NR_ICE_MEDIA_STREAM_UNPAIRED

// 这里定义的并不是 ICE 连接状态
#define NR_ICE_MEDIA_STREAM_UNPAIRED 1 // 没有配对
#define NR_ICE_MEDIA_STREAM_CHECKS_FROZEN 2 // 冻结
#define NR_ICE_MEDIA_STREAM_CHECKS_ACTIVE 3 // 正在检查
#define NR_ICE_MEDIA_STREAM_CHECKS_CONNECTED 4 // 连接成功
#define NR_ICE_MEDIA_STREAM_CHECKS_FAILED 5 // 连接失败

int disconnected;

#define NR_ICE_MEDIA_STREAM_CONNECTED 0
#define NR_ICE_MEDIA_STREAM_DISCONNECTED 1

// 待检测的候选地址对列表 (WAITING & FROZEN)
nr_ice_cand_pair_head check_list;
//
nr_ice_cand_pair_head trigger_check_queue;
void *timer; /* Check list periodic timer */

/* nr_ice_cand_pair_head valid_list; */

STAILQ_ENTRY(nr_ice_media_stream_) entry;
};

nr_stun_client_ctx_

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
struct nr_stun_client_ctx_ {
int state;
#define NR_STUN_CLIENT_STATE_INITTED 0
#define NR_STUN_CLIENT_STATE_RUNNING 1
#define NR_STUN_CLIENT_STATE_DONE 2
#define NR_STUN_CLIENT_STATE_FAILED 3
#define NR_STUN_CLIENT_STATE_TIMED_OUT 4
#define NR_STUN_CLIENT_STATE_CANCELLED 5
#define NR_STUN_CLIENT_STATE_WAITING 6

int mode;
#define NR_STUN_CLIENT_MODE_BINDING_REQUEST_SHORT_TERM_AUTH 1
#define NR_STUN_CLIENT_MODE_BINDING_REQUEST_LONG_TERM_AUTH 2
#define NR_STUN_CLIENT_MODE_BINDING_REQUEST_NO_AUTH 3
#define NR_STUN_CLIENT_MODE_KEEPALIVE 4
#define NR_STUN_CLIENT_MODE_BINDING_REQUEST_STUND_0_96 5
#ifdef USE_ICE
#define NR_ICE_CLIENT_MODE_USE_CANDIDATE 10
#define NR_ICE_CLIENT_MODE_BINDING_REQUEST 11
#endif /* USE_ICE */
#ifdef USE_TURN
#define NR_TURN_CLIENT_MODE_ALLOCATE_REQUEST 20
#define NR_TURN_CLIENT_MODE_REFRESH_REQUEST 21
#define NR_TURN_CLIENT_MODE_SEND_INDICATION 22
#define NR_TURN_CLIENT_MODE_DATA_INDICATION 24
#define NR_TURN_CLIENT_MODE_PERMISSION_REQUEST 25
#endif /* USE_TURN */

char *label;
// peer 返回的我的地址,也就是 peer 收到包的源地址
nr_transport_addr my_addr;
// 最终更新为实际收到包的源地址
nr_transport_addr peer_addr;
nr_socket *sock; // local candidate 的 osock
nr_stun_client_auth_params auth_params;
nr_stun_client_params params;
// stun bind 请求结果
nr_stun_client_results results;
char *nonce;
char *realm;
void *timer_handle;
int request_ct;
UINT2 retransmit_ct;
UINT4 rto_ms; /* retransmission time out */
double retransmission_backoff_factor;
UINT4 maximum_transmits;
UINT4 maximum_transmits_timeout_ms;
UINT4 mapped_addr_check_mask; /* What checks to run on mapped addresses */
int timeout_ms;
struct timeval timer_set;
int retry_ct;
NR_async_cb finished_cb;
void *cb_arg;
nr_stun_message *request;
nr_stun_message *response;
UINT2 error_code;
};

nr_socket_

1
2
3
4
struct nr_socket_ {
void *obj; // 指向一个 nr_socket_local
nr_socket_vtbl *vtbl; // 一些函数指针,指向 nr_socket_local_vtbl, 用于操作 socket
};

nr_ice_socket_

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
typedef struct nr_ice_socket_ {
int type;
#define NR_ICE_SOCKET_TYPE_DGRAM 1
#define NR_ICE_SOCKET_TYPE_STREAM_TURN 2
#define NR_ICE_SOCKET_TYPE_STREAM_TCP 3

nr_socket *sock;
nr_ice_ctx *ctx;

nr_ice_candidate_head candidates;
nr_ice_component *component;

TAILQ_HEAD(nr_ice_stun_ctx_head_,nr_ice_stun_ctx_) stun_ctxs;

nr_stun_server_ctx *stun_server;
void *stun_server_handle;

STAILQ_ENTRY(nr_ice_socket_) entry;
} nr_ice_socket;
Author: 42
Link: http://blog.ikernel.cn/2020/01/09/nicer%E5%BA%93%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.

Comment