turnserver测试是否正常运行

本文介绍如何配置TURN服务器以实现开机自动启动。通过创建一个启动脚本并设置权限,可以确保服务器重启后TURN服务能够自动运行。文章还提供了具体的脚本示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


比较简单的测试,如下:
服务器ip是8.8.8.8,turnserver默认监听端口是3478,则测试路径是http://8.8.8.8:3478,用浏览器打开该链接,如果看到“TURN Server”的字样说明turnserver是正常运行。如果访问不了,那就那可能是服务挂了或者说是服务没有开启。如果常常因为重启了服务而忘记启动该服务的话,建议写个脚本,开机自动运行服务。
怎么写呢?,其实谷歌一下基本就知道了,在这里还是写个简单的例子吧。

首先知道turnserver默认安装路径是/usr/local/bin/turnserver
启动指令是turnserver -L <public_ip_address> -c /etc/turnserver.conf -o -a -b /etc/turnuserdb.conf -f -r <system_domain_name|public_ip_address>
那么在/etc/init.d/目录下创建名为startturnserver文件,内容如下#!/bin/bash
# chkconfig: - 85 15
# description: turnserver start script
# processname: turnserver

start() {
echo -n "Starting turnserver: "
/usr/local/bin/turnserver -L <public_ip_address> -c /etc/turnserver.conf -o -a -b /etc/turnuserdb.conf -f -r <public_ip_address|system_domain_name>
echo "done."
}

case "$1" in
      start)
start
;;
*)
echo "Usage: $0 {start}"
esac
exit 0
给startturnserver文件添加运行权限,并执行chkconfig startturnserver on
重启后,你会发现turnserver已经启动了。
<think>我们将创建一个测试类,用于验证与Coturn TURN服务器的连接。这个测试类将尝试建立WebRTC连接,并收集ICE候选信息,特别是检查是否有中继类型的候选(即通过TURN服务器)出现,以确认连接成功。 步骤: 1. 初始化PeerConnectionFactory。 2. 创建PeerConnection,并配置ICE服务器(使用传入的TURN服务器信息)。 3. 创建一个虚拟的媒体流(虽然不需要实际传输媒体,但为了触发ICE收集,通常需要添加一个媒体流)。 4. 创建Offer,触发ICE候选收集过程。 5. 监听ICE候选事件,检查是否有中继候选(relay)生成。 6. 设置超时机制,如果在规定时间内没有收集到中继候选,则认为测试失败。 注意:由于我们只关心ICE候选的收集,因此不需要实际的视频捕获和渲染。 我们将实现一个回调接口来返回测试结果(成功/失败)以及收集到的ICE候选信息。 代码结构: - 定义一个回调接口 `TestCallback`,用于返回测试结果。 - 创建一个类 `TurnConnectionTest`,包含测试方法。 注意:测试完成后要释放资源。 由于WebRTC需要运行在Looper线程上,我们将在后台线程执行测试,并通过Handler与主线程交互。 实现:</think>下面是一个基于 Kotlin 的 WebRTC 测试类,用于验证与 Coturn TURN 服务器的连接及 ICE 候选获取功能: ```kotlin import org.webrtc.* import org.webrtc.PeerConnection.IceConnectionState import org.webrtc.PeerConnection.IceGatheringState import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit class TurnConnectionTest( private val turnServerUrl: String, private val turnUsername: String, private val turnPassword: String ) : PeerConnection.Observer { // 测试结果封装类 data class TestResult( val isSuccess: Boolean, val iceCandidates: List<IceCandidate>, val connectionState: IceConnectionState, val errorMessage: String? = null ) private lateinit var peerConnectionFactory: PeerConnectionFactory private lateinit var peerConnection: PeerConnection private val iceCandidates = mutableListOf<IceCandidate>() private val resultLatch = CountDownLatch(1) private var testResult: TestResult? = null // 执行测试 fun testConnection(timeoutSeconds: Long = 10): TestResult { try { initializePeerConnectionFactory() createPeerConnection() createDummyOffer() // 等待测试完成或超时 if (!resultLatch.await(timeoutSeconds, TimeUnit.SECONDS)) { return TestResult( isSuccess = false, iceCandidates = iceCandidates, connectionState = peerConnection.iceConnectionState(), errorMessage = "测试超时" ) } return testResult ?: TestResult( isSuccess = false, iceCandidates = iceCandidates, connectionState = peerConnection.iceConnectionState(), errorMessage = "未获取测试结果" ) } catch (e: Exception) { return TestResult( isSuccess = false, iceCandidates = iceCandidates, connectionState = peerConnection?.iceConnectionState() ?: IceConnectionState.FAILED, errorMessage = "测试异常: ${e.message}" ) } finally { cleanupResources() } } private fun initializePeerConnectionFactory() { PeerConnectionFactory.initialize( PeerConnectionFactory.InitializationOptions.builder(applicationContext) .setEnableInternalTracer(true) .createInitializationOptions() ) peerConnectionFactory = PeerConnectionFactory.builder() .setOptions(PeerConnectionFactory.Options()) .createPeerConnectionFactory() } private fun createPeerConnection() { // 配置TURN服务器 val iceServers = listOf( PeerConnection.IceServer.builder("turn:$turnServerUrl:3478") .setUsername(turnUsername) .setPassword(turnPassword) .createIceServer() ) // 创建PeerConnection配置 val rtcConfig = PeerConnection.RTCConfiguration(iceServers).apply { iceTransportsType = PeerConnection.IceTransportsType.RELAY bundlePolicy = PeerConnection.BundlePolicy.MAXBUNDLE rtcpMuxPolicy = PeerConnection.RtcpMuxPolicy.REQUIRE tcpCandidatePolicy = PeerConnection.TcpCandidatePolicy.DISABLED sdpSemantics = PeerConnection.SdpSemantics.UNIFIED_PLAN } peerConnection = peerConnectionFactory.createPeerConnection(rtcConfig, this)!! } private fun createDummyOffer() { // 创建虚拟媒体流触发ICE收集 val audioSource = peerConnectionFactory.createAudioSource(MediaConstraints()) val audioTrack = peerConnectionFactory.createAudioTrack("dummy_audio", audioSource) val localStream = peerConnectionFactory.createLocalMediaStream("dummy_stream") localStream.addTrack(audioTrack) peerConnection.addStream(localStream) // 创建Offer触发ICE候选收集 peerConnection.createOffer(object : SdpObserver { override fun onCreateSuccess(desc: SessionDescription) { peerConnection.setLocalDescription(this, desc) } override fun onSetSuccess() {} override fun onSetFailure(error: String) { completeTest(TestResult( isSuccess = false, iceCandidates = iceCandidates, connectionState = peerConnection.iceConnectionState(), errorMessage = "SDP设置失败: $error" )) } override fun onCreateFailure(error: String) { completeTest(TestResult( isSuccess = false, iceCandidates = iceCandidates, connectionState = peerConnection.iceConnectionState(), errorMessage = "SDP创建失败: $error" )) } }, MediaConstraints()) } // ICE候选收集回调 override fun onIceCandidate(candidate: IceCandidate) { iceCandidates.add(candidate) // 检查是否获取到中继候选 if (candidate.serverUrl?.contains("turn") == true) { completeTest(TestResult( isSuccess = true, iceCandidates = iceCandidates, connectionState = peerConnection.iceConnectionState() )) } } // ICE连接状态变化 override fun onIceConnectionChange(state: IceConnectionState) { if (state == IceConnectionState.CONNECTED) { completeTest(TestResult( isSuccess = true, iceCandidates = iceCandidates, connectionState = state )) } else if (state == IceConnectionState.FAILED) { completeTest(TestResult( isSuccess = false, iceCandidates = iceCandidates, connectionState = state, errorMessage = "ICE连接失败" )) } } // ICE收集状态变化 override fun onIceGatheringChange(state: IceGatheringState) { if (state == IceGatheringState.COMPLETE) { completeTest(TestResult( isSuccess = iceCandidates.any { it.serverUrl?.contains("turn") == true }, iceCandidates = iceCandidates, connectionState = peerConnection.iceConnectionState(), errorMessage = if (iceCandidates.isEmpty()) "未收集到ICE候选" else "未检测到TURN候选" )) } } private fun completeTest(result: TestResult) { testResult = result resultLatch.countDown() } private fun cleanupResources() { peerConnection.dispose() peerConnectionFactory.dispose() } // 其他PeerConnection.Observer方法的空实现 override fun onSignalingChange(p0: PeerConnection.SignalingState?) = Unit override fun onIceConnectionReceivingChange(p0: Boolean) = Unit override fun onConnectionChange(p0: PeerConnection.PeerConnectionState?) = Unit override fun onAddStream(p0: MediaStream?) = Unit override fun onRemoveStream(p0: MediaStream?) = Unit override fun onDataChannel(p0: DataChannel?) = Unit override fun onRenegotiationNeeded() = Unit override fun onAddTrack(p0: RtpReceiver?, p1: Array<out MediaStream>?) = Unit } ``` ### 使用示例 ```kotlin // 在Activity或测试用例中调用 fun runTurnConnectionTest() { val test = TurnConnectionTest( turnServerUrl = "your.turn.server.com", turnUsername = "your_username", turnPassword = "your_password" ) val result = test.testConnection(15) // 15秒超时 // 输出测试结果 Log.d("TurnTest", "测试结果: ${if (result.isSuccess) "成功" else "失败"}") Log.d("TurnTest", "ICE连接状态: ${result.connectionState}") Log.d("TurnTest", "收集到${result.iceCandidates.size}个ICE候选") result.iceCandidates.forEachIndexed { index, candidate -> Log.d("TurnTest", "候选#$index: ${candidate.sdp} (类型: ${candidate.serverUrl})") } result.errorMessage?.let { Log.e("TurnTest", "错误信息: $it") } } ``` ### 关键功能说明 1. **TURN 服务器配置**: ```kotlin PeerConnection.IceServer.builder("turn:$turnServerUrl:3478") .setUsername(turnUsername) .setPassword(turnPassword) ``` 使用标准 TURN URL 格式配置 Coturn 服务器[^1] 2. **UDP 优先传输**: ```kotlin iceTransportsType = PeerConnection.IceTransportsType.RELAY tcpCandidatePolicy = PeerConnection.TcpCandidatePolicy.DISABLED ``` 强制使用中继并禁用 TCP 传输[^1] 3. **ICE 候选验证**: ```kotlin if (candidate.serverUrl?.contains("turn") == true) { completeTest(TestResult(isSuccess = true, ...)) } ``` 检测是否成功获取 TURN 中继候选 4. **超时处理机制**: ```kotlin if (!resultLatch.await(timeoutSeconds, TimeUnit.SECONDS)) { return TestResult(isSuccess = false, errorMessage = "测试超时") } ``` 防止测试永久阻塞 5. **测试结果分析**: ```kotlin data class TestResult( val isSuccess: Boolean, val iceCandidates: List<IceCandidate>, val connectionState: IceConnectionState, val errorMessage: String? = null ) ``` 封装完整的测试结果信息 ### 测试结果分析 1. **成功标志**: - 检测到至少一个 `turn:` 类型的 ICE 候选 - ICE 连接状态变为 `CONNECTED` 2. **失败情况**: - 超时未收到任何 ICE 候选 - ICE 连接状态变为 `FAILED` - 仅收到非 TURN 类型的候选(如 `stun:`) 3. **关键指标**: - ICE 候选数量 - 候选类型分布(TURN/STUN/HOST) - 连接建立时间 ### 依赖配置 在 `build.gradle` 中添加: ```gradle dependencies { implementation 'org.webrtc:google-webrtc:1.0.32006' } ``` ### 权限配置 在 `AndroidManifest.xml` 中添加: ```xml <uses-permission android:name="android.permission.INTERNET"/> ``` 此测试类可帮助您: 1. 验证 Coturn 服务器配置是否正确 2. 检测 ICE 候选收集是否成功 3. 确认 UDP 传输是否正常工作 4. 诊断 TURN 服务器连接问题 5. 优化 WebRTC 连接可靠性[^1]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值