一. 安装

1.1. 下载

  • git
git clone https://github.com/coturn/coturn.git
  • 压缩包安装
wget https://github.com/coturn/coturn/archive/4.5.0.8.tar.gz

1.2. 安装

cd coturn
./configure
make 
sudo make install

注: 如果编译出错,记得安装相关依赖。

二. 配置

我这里在 /etc 下单独建立了个 turnserver 的目录,将 coturn 配置文件 turnserver.conf 拷贝过来。

sudo mkdir /etc/turnserver
sudo cp coturn/examples/etc/turnserver.conf /etc/turnserver/
sudo cp coturn/examples/etc/turn_server_*.pem /etc/turnserver/

配置 /etc/turnserver/turnserver.conf

# TURN 的监听端口,默认 3478
listening-port=3478

# 监听 relay server 的地址
listening-ip=192.168.110.146

# 可以和 listening IP 一样
relay-ip=192.168.110.146

# 同上
external-ip=192.168.110.146

# 输出详细过程
verbose

# TURN 消息中可使用 FINGERPRINT
fingerprint

# 开启长期证书机制
lt-cred-mech

# 设置静态账户
user=test:test

# 
realm=mytest

# 关闭 TLS 和 DTLS 监听
no-tls
no-dtls

# 连接的生命周期,默认 10min
stale-nonce=600

# 证书和私钥文件
cert=/etc/turnserver/turn_server_cert.pem
pkey=/etc/turnserver/turn_server_pkey.pem

# 屏蔽 loopback, multicast IP 地址
no-loopback-peers
no-multicast-peers

# 支持移动 ICE
mobility
no-cli

三. 启动 & 测试

3.1. Server

turnserver -v -L 192.168.110.146 -a -f -r mytest -c /etc/turnserver/turnserver.conf
  • -L 指定 relay 的监听 IP
  • -a 使用 long-term credential
  • -f fingerprints
  • -r 指定 realm
  • -c 指定配置文件

3.2. Client

turnutils_uclient -u test -w test -v -y 192.168.110.146
  • -u 指定用户名
  • -w 指定密码
  • -v 详细输出
  • -y 使用 client-to-client 连接

看到如下提示则为成功:

0: success: 0x4690
0: Total connect time is 0
1: start_mclient: msz=4, tot_send_msgs=0, tot_recv_msgs=0, tot_send_bytes ~ 0, tot_recv_bytes ~ 0
2: start_mclient: msz=4, tot_send_msgs=0, tot_recv_msgs=0, tot_send_bytes ~ 0, tot_recv_bytes ~ 0
3: start_mclient: msz=4, tot_send_msgs=1, tot_recv_msgs=1, tot_send_bytes ~ 100, tot_recv_bytes ~ 100
4: start_mclient: msz=4, tot_send_msgs=10, tot_recv_msgs=10, tot_send_bytes ~ 1000, tot_recv_bytes ~ 1000
5: start_mclient: msz=4, tot_send_msgs=15, tot_recv_msgs=15, tot_send_bytes ~ 1500, tot_recv_bytes ~ 1500
5: done, connection 0x1fcd600 closed.
5: done, connection 0x7f40c0d10010 closed.
5: done, connection 0x7f40c0b9a010 closed.
5: done, connection 0x7f40c0bbb010 closed.
5: start_mclient: tot_send_msgs=20, tot_recv_msgs=20
5: start_mclient: tot_send_bytes ~ 2000, tot_recv_bytes ~ 2000
5: Total transmit time is 5
5: Total lost packets 0 (0.000000%), total send dropped 0 (0.000000%)
5: Average round trip delay 0.000000 ms; min = 0 ms, max = 0 ms
5: Average jitter 0.650000 ms; min = 0 ms, max = 2 ms

四. 代码流程

注: 基于 coturn-4.5.0.8 版本

4.1. 大体框架涉及的函数

...各种初始化
read_config_file() // 读取配置文件
setup_server() // 设置
  -> setup_listener()
    -> turnipports_create()        // 设置 turn 端口范围
    -> turn_event_base_new()       // event
  -> allocate_relay_addrs_ports()  // relay 地址
  -> setup_listener()
  -> setup_general_relay_servers() // 设置 relay
  -> setup_auth_server()           // 认证相关

4.2. 设置 relay 相关

setup_general_relay_servers()
  -> setup_relay_server()
    -> relay_receive_message()
      -> handle_relay_message()    // 读取 event 发往 relay 的消息
        -> open_client_connection_session()
          -> client_input_handler()
            -> read_client_connection()
              -> handle_turn_command()     // 处理 TURN 协议的各种请求或数据发送与接收
              -> write_client_connection() // 返回数据给 Client
  • 设置 event 的回调函数 relay_receive_message()
  • relay_receive_message() 循环从 event buffer 中读取数据交给 handle_relay_message()

4.3. 处理请求或数据

handle_turn_command(server, ss, in_buffer, nbh, &resp_constructed, can_resume);
handle_turn_command()
  -> stun_is_request_str()     // 判断 method 是否是请求
    -> handle_turn_allocate()          // 处理 Allocate Request
    -> handle_turn_create_permission() // 处理 CreatePermission Request
    -> handle_turn_refresh()           // 处理 Refresh Request
    -> handle_turn_channel_bind()      // 处理 ChannelBind Request
  -> stun_is_indication_str() // 判断 method 是否是数据
    -> handle_turn_send()              // 处理 Send Request
  • nbh 是从 server 的 ioa_engine_handle 结构的 bufs 中创建并传入进来的
  • 处理结束后,将 resp_constructed 的值赋为 1。
struct _ioa_engine
{
  super_memory_t *sm;
  struct event_base *event_base;
  int deallocate_eb;
  int verbose;
  turnipports* tp;
  rtcp_map *map_rtcp;
  stun_buffer_list bufs;
  // ...
}
typedef struct _stun_buffer_list {
	stun_buffer_list_elem *head;
	size_t tsz;
} stun_buffer_list;

4.4. 分析请求并构造返回数据

handle_turn_allocate() 举例来说:

handle_turn_allocate(server, ss, &tid, resp_constructed,
    &err_code, &reason, unknown_attrs, &ua_num, in_buffer, nbh);
handle_turn_allocate()
  -> stun_attr_get_first_str()        // 获取第一个属性
  -> stun_attr_get_type()             // 依次获取其他属性
  -> stun_attr_get_len()              // 获取属性长度
  -> stun_attr_get_value()            // 获取属性 value
  -> stun_set_allocate_response_str() // 创建返回数据
    -> stun_attr_add_addr_str()       // 添加各种属性
  • 读取 in_buffer 中的 nbh 结构
  • 将 nbh 强转为 stun_buffer_list_elem 结构,其中 next 是链表的指针,buf 承载属性等信息,根据 len 和 offset 来获取属性信息
  • 填充 nbh 结构,用于返回数据
typedef struct _stun_buffer_list_elem {
	struct _stun_buffer_list_elem *next;
	stun_buffer buf;
} stun_buffer_list_elem;
typedef struct _stun_buffer {
  u08bits	channel[STUN_CHANNEL_HEADER_LENGTH];
  u08bits	buf[STUN_BUFFER_SIZE];
  size_t	len;
  u16bits	offset;
  u08bits	coffset;
} stun_buffer;

4.5. 真正的返回数据

write_client_connection(server, ss, nbh, TTL_IGNORE, TOS_IGNORE);
write_client_connection()
  -> send_data_from_ioa_socket_nbh()
    -> udp_send()
      -> sendto()
  • 参数中的 nbh 就是前面构造出来的
  • udp_send() 使用的 fd 是参数 server 的 client_socket 字段中的 fd
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐