跳转到正文

音视频服务部署 ​

本教程用于首次部署一对一语音、视频通话。先按选择安装方式完成 IM 安装,并让自己的域名可以通过 HTTPS 打开管理后台,再按本文配置通话服务。

示例采用 Ubuntu 24.04,与 IM 同一台服务器、同一个域名。普通、宝塔和 Docker 安装均可参考;下面的通话进程和中继服务运行在 Linux 宿主机上。已有通话服务的环境先保留原配置,不直接覆盖。

1. 准备域名与服务器信息 ​

需要准备以下信息。example.com 和两个示例 IP 均须换成自己的值:

信息示例到哪里查看
已配置 HTTPS 的 IM 域名example.com当前管理后台的域名
服务器公网 IPv4203.0.113.10云服务器控制台
服务器网卡 IPv410.0.0.10云服务器控制台的私网 IP,或服务器执行 ip -4 addr
已安装的服务端源码目录/srv/popoim目录中包含 start.php、tools/;宝塔通常为 /www/wwwroot/实际域名
域名证书和私钥fullchain.pem、privkey.pem当前 Nginx 或宝塔站点的 SSL 配置

域名的 A 记录应直连服务器公网 IP,通话地址不经过网站 CDN。本文按 IPv4 配置;没有同时配置 IPv6 时,不保留指向其他服务器的 AAAA 记录。云服务器通常填写公网 IP 与私网 IP;公网 IP 直接绑定在网卡上的服务器,两项 IP 填同一个公网地址。

登录服务器后进入 root 会话,在同一终端中依次执行本文命令:

bash
sudo -i

CALL_DOMAIN=example.com
CALL_PUBLIC_IP=203.0.113.10
CALL_LOCAL_IP=10.0.0.10
CALL_APP_DIR=/srv/popoim
CALL_CERT_CHAIN="/etc/letsencrypt/live/$CALL_DOMAIN/fullchain.pem"
CALL_CERT_KEY="/etc/letsencrypt/live/$CALL_DOMAIN/privkey.pem"

test -r "$CALL_CERT_CHAIN" && test -r "$CALL_CERT_KEY"

最后一行应正常结束。宝塔或手动上传的证书,请将最后两个变量改成站点实际使用的完整证书链和私钥路径。每个代码块执行成功后再进入下一步;重新登录终端时,需要重新填写这些变量。

2. 开放云安全组与系统防火墙 ​

云控制台的安全组 / 防火墙,以及服务器自身的防火墙,都要放行。 以下是入站规则;面向公网用户的通话端口需允许用户所在网络访问,不能只允许服务器自己的 IP。

端口协议用途来源范围
22(或实际 SSH 端口)TCP登录服务器运维人员 IP
80TCP网站访问与 HTTP 证书验证公网
443TCPHTTPS、后台与通话连接公网
3478TCP、UDP 都开放通话网络连接与中继入口公网
5349TCP加密中继入口公网
49160–49260UDP,整个范围中继媒体传输公网

音视频服务的公网入口与内部端口示意

只开放 3478,遗漏 UDP 中继范围,可能出现能呼叫、接听后没有声音或画面的情况。本文的 49160–49260 与后面的中继配置一致;扩容时可调整范围,但必须同时修改服务配置与两层防火墙。出站规则应允许中继向客户端公网地址发送 UDP;通常可保留服务器默认的允许出站策略。

如果服务器已启用 UFW,执行以下命令。SSH 使用其他端口时先替换 22,保留已有 SSH 放行规则:

bash
ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw allow 3478/tcp
ufw allow 3478/udp
ufw allow 5349/tcp
ufw allow 49160:49260/udp
ufw status

UFW 显示 inactive 时,上述规则尚未生效,但也没有由 UFW 拦截流量;仍需检查云安全组、宝塔防火墙或其他已启用的防火墙。本教程不要求切换现有防火墙管理方式。

不对公网开放 9193:它只监听 127.0.0.1,由 Nginx 转发。数据库 3306、IM 内部服务 8787 / 6060 / 2060 / 6070 和 Docker 本机入口 8080 也不需要为通话额外开放。服务器经过自建 NAT 或端口映射时,上述公网端口须映射到同一台服务器,UDP 中继范围保留原端口号。

3. 安装通话连接服务 ​

安装运行环境 ​

bash
apt update
apt install -y ca-certificates curl xz-utils openssl coturn

通话连接服务需要 Node.js 22 或以上。以下安装独立运行环境,不依赖宝塔的 Node 项目管理器:

bash
CALL_NODE_VERSION=v22.23.4
case "$(uname -m)" in
  x86_64) CALL_NODE_ARCH=x64 ;;
  aarch64|arm64) CALL_NODE_ARCH=arm64 ;;
  *) echo '请使用 x64 或 ARM64 Linux 服务器'; exit 1 ;;
esac
CALL_NODE_FILE="node-$CALL_NODE_VERSION-linux-$CALL_NODE_ARCH.tar.xz"
CALL_NODE_DOWNLOAD=$(mktemp -d)
cd "$CALL_NODE_DOWNLOAD"
curl -fSLO "https://nodejs.org/dist/$CALL_NODE_VERSION/$CALL_NODE_FILE"
curl -fSLO "https://nodejs.org/dist/$CALL_NODE_VERSION/SHASUMS256.txt"
awk -v file="$CALL_NODE_FILE" '$2 == file' SHASUMS256.txt > selected.sha256
test -s selected.sha256 && sha256sum -c selected.sha256
install -d -m 0755 /opt/popoim-node
tar -xJf "$CALL_NODE_FILE" -C /opt/popoim-node --strip-components=1
/opt/popoim-node/bin/node --version

校验应显示 OK,最后输出 v22.23.4。下载来源与版本可在 Node.js 官方发布目录核对。

官方下载较慢时,可在自己的电脑下载同版本、同架构的压缩包与校验文件,上传到服务器后再执行校验和解压。

上传配套服务文件 ​

从已安装的服务端源码中复制配套通话服务文件。以下命令在 tools/ 下定位服务目录,检查文件齐全后复制到 /opt/popoim-rtc/:

bash
CALL_SERVICE_SOURCE=$(find "$CALL_APP_DIR/tools" -maxdepth 2 -type f \
  -name server.mjs -exec dirname {} \;)
test -f "$CALL_SERVICE_SOURCE/package.json"
test -f "$CALL_SERVICE_SOURCE/package-lock.json"
install -d -m 0755 /opt/popoim-rtc
install -m 0644 "$CALL_SERVICE_SOURCE"/{server.mjs,package.json,package-lock.json} \
  /opt/popoim-rtc/

复制后目录应为:

text
/opt/popoim-rtc/
├── server.mjs
├── package.json
└── package-lock.json

文件直接放在该目录内,不再套一层子目录,也不放进网站的 public/。若文件检查失败,先核对 CALL_APP_DIR 是否指向当前交付源码。然后安装锁定依赖:

bash
cd /opt/popoim-rtc
PATH=/opt/popoim-node/bin:$PATH npm ci --omit=dev --ignore-scripts
chmod -R a+rX /opt/popoim-rtc

生成密钥并启动服务 ​

以下命令只在配置文件不存在时生成一次随机密钥。连接服务、中继服务和后台使用同一份密钥,后续重复部署时保留该文件。

bash
if [ ! -f /etc/popoim-rtc.env ]; then
  (umask 077
   printf 'RTC_SECRET=%s\nHOST=127.0.0.1\nPORT=9193\n' \
     "$(openssl rand -hex 32)" > /etc/popoim-rtc.env)
fi
chown root:root /etc/popoim-rtc.env
chmod 600 /etc/popoim-rtc.env
. /etc/popoim-rtc.env

cat > /etc/systemd/system/popoim-rtc.service <<'EOF'
[Unit]
Description=Popo IM call service
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=nobody
Group=nogroup
WorkingDirectory=/opt/popoim-rtc
EnvironmentFile=/etc/popoim-rtc.env
ExecStart=/opt/popoim-node/bin/node /opt/popoim-rtc/server.mjs
Restart=on-failure
RestartSec=2
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable --now popoim-rtc
systemctl is-active popoim-rtc
curl --fail http://127.0.0.1:9193/rtc/health

预期输出包含 active 和 {"status":"ok"}。失败时先查看 journalctl -u popoim-rtc -n 50 --no-pager,确认文件齐全、依赖安装成功及 Node 路径正确。

4. 在现有 HTTPS 站点加入通话路由 ​

编辑当前 IM 域名监听 443 的 server 块,加入以下配置,保留原来的 /ws、网站路由与证书配置。同名路由已经存在时替换原配置,不重复添加。

nginx
location ^~ /rtc/ {
    proxy_pass http://127.0.0.1:9193;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_read_timeout 70s;
    proxy_send_timeout 70s;
}

普通安装通常修改 /etc/nginx/sites-available/popoim。Docker 安装修改宿主机的 HTTPS Nginx;宝塔在对应网站的「配置文件」中修改,并使用宝塔管理的 Nginx。此处上游为 127.0.0.1:9193,不是 Docker 的 app 容器地址。

bash
nginx -t && systemctl reload nginx
bash
/www/server/nginx/sbin/nginx -t && /etc/init.d/nginx reload

接着检查实际域名与证书:

bash
curl --fail "https://$CALL_DOMAIN/rtc/health"

应返回 {"status":"ok"}。出现证书错误时修正域名或证书链,不通过跳过证书校验来继续部署;返回 404 通常是路由加在了其他站点,502 则先检查通话连接服务。这个检查仅说明连接入口可用,中继和实际通话还要继续检查。

5. 配置音视频中继 ​

写入中继配置 ​

下面使用同一份服务密钥,并将公网地址映射到本机网卡地址。中继端口范围必须与第 2 步完全一致。

bash
cp -a /etc/turnserver.conf /etc/turnserver.conf.before-popoim
. /etc/popoim-rtc.env
cat > /etc/turnserver.conf <<EOF
listening-port=3478
tls-listening-port=5349
listening-ip=0.0.0.0
relay-ip=$CALL_LOCAL_IP
external-ip=$CALL_PUBLIC_IP/$CALL_LOCAL_IP
min-port=49160
max-port=49260
fingerprint
use-auth-secret
static-auth-secret=$RTC_SECRET
realm=$CALL_DOMAIN
server-name=$CALL_DOMAIN
cert=/etc/coturn/fullchain.pem
pkey=/etc/coturn/privkey.pem
no-dtls
no-cli
no-tcp-relay
no-multicast-peers
denied-peer-ip=0.0.0.0-0.255.255.255
denied-peer-ip=10.0.0.0-10.255.255.255
denied-peer-ip=127.0.0.0-127.255.255.255
denied-peer-ip=169.254.0.0-169.254.255.255
denied-peer-ip=172.16.0.0-172.31.255.255
denied-peer-ip=192.168.0.0-192.168.255.255
denied-peer-ip=224.0.0.0-255.255.255.255
user-quota=12
stale-nonce=600
no-software-attribute
syslog
EOF
chown root:turnserver /etc/turnserver.conf
chmod 640 /etc/turnserver.conf

这里保留了公网中继的访问限制;本文面向公网部署,不把中继开放成访问服务器内网的代理。中继配置项的含义可查阅 coturn 官方说明。

安装证书并启动 ​

中继需要读取有效证书。以下脚本将当前站点证书复制到专用目录;只有证书内容变化时才更新并重启中继。

bash
cat > /usr/local/sbin/popoim-call-cert <<EOF
#!/bin/sh
set -eu
if cmp -s "$CALL_CERT_CHAIN" /etc/coturn/fullchain.pem &&
   cmp -s "$CALL_CERT_KEY" /etc/coturn/privkey.pem; then
    exit 0
fi
install -d -m 0750 -o root -g turnserver /etc/coturn
install -m 0640 -o root -g turnserver "$CALL_CERT_CHAIN" /etc/coturn/fullchain.pem
install -m 0640 -o root -g turnserver "$CALL_CERT_KEY" /etc/coturn/privkey.pem
systemctl try-restart coturn
EOF
chmod 750 /usr/local/sbin/popoim-call-cert
/usr/local/sbin/popoim-call-cert
systemctl enable --now coturn
systemctl is-active coturn
ss -lntup | grep -E ':(3478|5349|9193)\b'

确认中继为 active,3478 同时有 TCP、UDP 监听,5349 有 TCP 监听,9193 只监听本机。UDP 中继范围在实际通话分配后才使用,空闲时不会显示全部端口监听。

接上证书续期 ​

如果证书由 Certbot 自动续期,将同一脚本接入其成功续期钩子:

bash
install -d -m 0755 /etc/letsencrypt/renewal-hooks/deploy
ln -sfn /usr/local/sbin/popoim-call-cert \
  /etc/letsencrypt/renewal-hooks/deploy/popoim-call-cert

如果使用宝塔证书,在宝塔「计划任务」添加每天执行一次的 Shell 任务,脚本内容为 /usr/local/sbin/popoim-call-cert,并检查首次执行日志。脚本使用前面填写的站点实际证书路径,证书未变化时不会重启服务。续期方式见 Certbot 官方说明。手动更新证书后也执行一次该脚本。

6. 填写后台并启用通话 ​

打开「系统设置 → 音视频通话」,按自己的域名填写:

后台字段填写示例
启用音视频通话开启
信令地址wss://example.com/rtc/signal
STUN 地址stun:example.com:3478
TURN 地址以下三行,每行一条
服务密钥/etc/popoim-rtc.env 中 RTC_SECRET 的值

TURN 地址完整填写为:

text
turn:example.com:3478?transport=udp
turn:example.com:3478?transport=tcp
turns:example.com:5349?transport=tcp

管理后台的音视频通话服务配置

配图是已部署环境,示例中的域名要换成自己的。需要复制服务密钥时,在自己的服务器终端执行:

bash
sed -n 's/^RTC_SECRET=//p' /etc/popoim-rtc.env

只将输出粘贴到后台「服务密钥」,不加 RTC_SECRET= 前缀,不将密钥放入客户端或公开截图。保存后,再到「系统设置 → 功能开关」分别开启语音通话、视频通话。接入自有 App 时,还需按对应 SDK 指引接好配套媒体模块、设备权限和通话界面;后台开关不会替代客户端接入。

7. 检查服务并实际通话 ​

先确认以下结果,再用客户端检查声音和画面:

检查正常结果
systemctl is-active popoim-rtc coturn两个服务均为 active
curl --fail "https://$CALL_DOMAIN/rtc/health"{"status":"ok"},无证书错误
从自己的电脑执行 openssl s_client -connect example.com:5349 -servername example.com -verify_hostname example.com -verify_return_error </dev/null可连接,证书域名与链校验通过
云安全组与系统防火墙第 2 步 TCP、UDP 规则均已配置

用两个不同账号登录与服务端配套、已接入通话能力的客户端,一台连接 Wi-Fi,另一台使用移动网络,允许麦克风与摄像头,然后依次检查:

  1. A 呼叫 B,B 能收到来电并接听;再交换双方角色。
  2. 语音通话中双方都能听见,静音与音频输出切换正常。
  3. 视频通话中双方都能看到画面,摄像头切换正常。
  4. 持续通话后正常挂断,两端回到聊天;再次呼叫可正常接通。

同一 Wi-Fi 下能通话,不能替代跨网络检查;健康接口正常也不能证明 UDP 媒体链路可用。测试中应同时确认中继有成功分配记录,避免直连成功掩盖中继配置错误:

bash
journalctl -u coturn --since '10 minutes ago' --no-pager
journalctl -u popoim-rtc --since '10 minutes ago' --no-pager

如果当前两台设备始终直连,尚未证明中继链路可用;需在受控测试网络中验证中继后再认定公网部署完成。

常见问题 ​

现象优先检查
后台提示服务未配置完成信令地址、TURN 地址、服务密钥是否齐全;功能开关是否开启
没有呼叫入口客户端是否已接入配套通话模块与界面,是否同步了语音 / 视频能力
/rtc/health 返回 404 / 502路由是否加入当前域名的 HTTPS 站点,popoim-rtc 是否运行
能呼叫但接听后无声音、无画面UDP 3478 和 49160–49260 是否在两层防火墙放行,公网 / 私网 IP 映射是否正确
同一网络正常,换网络失败是否仅验证了直连;重点核对中继端口范围、地址与出站规则
中继认证失败后台、连接服务和中继是否使用同一个密钥,服务器时间是否准确
5349 无法连接或证书校验失败TCP 端口、域名证书、证书文件权限,以及续期后是否同步到中继
只有一方无声音或无画面两端的麦克风 / 摄像头权限、设备占用与客户端接入是否正常

服务密钥变更时同步更新三处并重启 popoim-rtc、coturn;这会影响现有通话,应安排维护窗口。日常后台操作见后台配置,接口与客户端接入见音视频通话。