各位老铁们,大家好,今天由我来为大家分享如何搭建p2p网站源码分享,以及p2p平台怎么建立的相关问题知识,希望对大家有所帮助。如果可以帮助到大家,还望关注收藏下本站,您的支持是我们最大的动力,谢谢大家了哈,下面我们开始吧!
1、简介
本文将详细介绍如何利用WebRTC技术实现P2P音视频通话,并提供了一个跨平台的方案,包括:基于socket.io和Node.js实现的服务端,以及JavaScript和Android客户端。让我们一起来探讨如何搭建这个系统,以及如何编写代码吧。
由于server、js、android代码还在整理中,预计还需要2-3天时间。地址:github.com/yangkun1992…
下面是PC与IOS在不同网络环境下的效果图(WiFi<->移动网络):
2、服务端
2.1使用nodejs和socket.io实现信令服务器
我们借助上一篇信令服务的流程图,来实现一个nodejs信令服务器
我们先设计一个信令
join:当前用户和远端用户加入到房间中的信令
leave:当前用户和远端用户离开房间的信令
message:交换双方的SDP、ICE信令
首先,我们需要搭建一个Node.js服务端,用于处理信令交换。在这里,我们将使用socket.io库作为通信协议,借助http、https、fs等组件。实现一个简单的Node.js服务端实例:
createserver.js下面就是信令服务的核心代码
varlog4js=require(&39;);\nvarhttp=require(&39;);\nvarhttps=require(&39;);\nvarfs=require(&39;);\nvarsocketIo=require(&39;);\n\nvarexpress=require(&39;);\nvarserveIndex=require(&39;);\n\nvarUSERCOUNT=3;\n\n…\n\n//httpserver\nvarhttp_server=http.createServer(app);\nhttp_server.listen(80,&39;);\n\nvaroptions={\nkey:fs.readFileSync(&39;),\ncert:fs.readFileSync(&39;)\n}\n\n//httpsserver\nvarhttps_server=https.createServer(options,app);\nvario=socketIo.listen(https_server);\n\n\nio.sockets.on(&39;,(socket)=>{\nsocket.on(&39;,(room,data)=>{\nsocket.to(room).emit(&39;,room,data);//发送给当前房间的其它客户端\n});\n\nsocket.on(&39;,(room)=>{\nsocket.join(room);\nvarmyRoom=io.sockets.adapter.rooms[room];\nvarusers=(myRoom)?Object.keys(myRoom.sockets).length:0;\nlogger.debug(&39;+users);\n\nif(users<USERCOUNT){\nsocket.emit(&39;,room,socket.id);//发送给自己,相当于回调\nif(users>1){\nsocket.to(room).emit(&39;,room,socket.id);//发送给当前房间的其它客户端\n}\n\n}else{\nsocket.leave(room);\nsocket.emit(&39;,room,socket.id);\n}\n});\nsocket.on(&39;,(room)=>{\nvarmyRoom=io.sockets.adapter.rooms[room];\nvarusers=(myRoom)?Object.keys(myRoom.sockets).length:0;\nlogger.debug(&39;+(users-1));\nsocket.to(room).emit(&39;,room,socket.id);\nsocket.emit(&39;,room,socket.id);\n});\n});\n\nhttps_server.listen(443,&39;);
要运行上面的server.js信令服务器,您需要按照以下步骤进行安装和运行:
安装Node.js和npm:安装所需的依赖项
npminstallexpresssocket.iofshttphttps
启动server
nodeserver.js
2.2搭建sturn/turn服务器
由于网络环境的影响我们需要搭建一个sturn/turn服务器,以便提升P2P的成功率,下面是一个粗略的搭建方式,但是也够用了。
安装Coturn
在终端中输入以下命令,使用yum包管理器安装Coturn:
sudoyuminstallcoturn
配置Coturn
找到并编辑Coturn的配置文件/etc/coturn/turnserver.conf,根据您的需求修改以下配置项:
配置域名\nrealm=xxx.com\n配置日志文件路径\nlog-file=/root/log/turnserver.log
启动Coturn
在终端中输入以下命令,启动Coturn服务:
sudosystemctlstartcoturn\nsudosystemctlstopcoturn\nsudosystemctlrestartcoturn\nsudosystemctlstatuscoturn
测试coturn我们可以去trickle-ice测试网站进行测试
正如trickle-ice网站所说:如果你测试一个STUN服务器,你能收集到一个类型为“srflx”的候选者,它就可以工作。如果你测试一个TURN服务器,你能收集到一个类型为“relay”的候选人,它就会工作.
由此上图sturn和turn候选者地址都能成功连接。
C++音视频学习资料免费获取方法:关注音视频开发T哥,点击「链接」即可免费获取2023年最新C++音视频开发进阶独家免费学习大礼包!
3、客户端
WebRTC是一种基于Web技术的实时通信解决方案,可用于在浏览器中实现P2P音视频通话。当然,现在基本上所有上层平台都支持了。在WebRTC中,双方通信通过ICE协议进行连接,通过SDP协议交换媒体信息,通过DTLS协议进行加密,通过SRTP协议进行媒体传输。
下面,我们将为你介绍如何使用WebRTC在浏览器和Android中实现P2P音视频通话。
3.1Web
我们按照上面信令的流程来实现:
3.1.1获取媒体流
WebRTC支持从设备摄像头和麦克风获取视频和音频流。使用JavaScript的getUserMediaAPI,您可以请求用户授权,从摄像头和麦克风获取本地媒体流,并将其添加到一个MediaStream对象中。
functionstartCall(){\n\nif(!navigator.mediaDevices||\n!navigator.mediaDevices.getUserMedia){\nconsole.error(&39;);\nreturn;\n}else{\n\nvarconstraints={\nvideo:true,//传输视频\naudio:true//传输音频\n}\n\nnavigator.mediaDevices.getUserMedia(constraints)\n.then(getMediaStream)//打开成功的回调\n.catch(handleError);//打开失败\n}\n\n}
3.1.2连接信令服务器并加入到房间中
functionconnect(){\n//连接信令服务器\nsocket=io.connect();\n//加入成功的通知\nsocket.on(&39;,(roomid,id)=>{\n…\n});\n//远端加入\nsocket.on(&39;,(roomid)=>{\n…\n});\n//房间满了\nsocket.on(&39;,(roomid,id)=>{\n…\n});\n//接收自己离开房间的回调\nsocket.on(&39;,(roomid,id)=>{\n…\n});\n//收到对方挂断的消息\nsocket.on(&39;,(room,id)=>{\n…\n});\n//收到服务断开的消息\nsocket.on(&39;,(socket)=>{\n…\n});\n//收消息,用于交换SDP和ICE消息等\nsocket.on(&39;,(roomid,data)=>{\n…\n});\n//发送join消息到信令服务器并加入到123456房间中\nsocket.emit(&39;,123456);\n}
3.1.3创建PeerConnection并添加媒体轨道
当收到自己加入房间成功的消息后,连接到远程对等方,我们就需要创建一个RTCPeerConnection对象,并将本地媒体流添加到其中。然后,您需要创建一个RTCDataChannel对象,用于在对等方之间传输数据。
varpcConfig={\n&39;:[{\n&39;:&39;,\n&39;:&34;,\n&39;:&34;\n}]\n};\npc=newRTCPeerConnection(pcConfig);\n//当前icecandida数据\npc.onicecandidate=(e)=>{\n…\n}\n\n//datachannel传输通道\npc.ondatachannel=e=>{\n…\n}\n//添加远端的媒体流到<video>element\npc.ontrack=getRemoteStream;\n\n//最后添加媒体轨道到peerconnection对象中\nlocalStream.getTracks().forEach((track)=>{\npc.addTrack(track,localStream);\n});\n\n//创建一个非音视频的数据通道\ndc=pc.createDataChannel(&39;);\ndc.onmessage=receivemsg;//接收对端消息\ndc.onopen=dataChannelStateChange;//当打开\ndc.onclose=dataChannelStateChange;//当关闭\n\nfunctiongetRemoteStream(e){\nremoteStream=e.streams[0];\nremoteVideo.srcObject=e.streams[0];\n}
3.1.4发送createOffer数据到远端
当对方加入到房间中,我们需要把当前UserA的SDP信息告诉UserB用户,使用如下代码
varofferOptions={//同时接收远端的音、视频数据\nofferToRecieveAudio:1,\nofferToRecieveVideo:1\n}\n\npc.createOffer(offerOptions)\n.then(getOffer)//创建成功的回调\n.catch(handleOfferError);\n\nfunctiongetOffer(desc){\n//设置UserASDP信息\npc.setLocalDescription(desc);\nofferdesc=desc;\n\n//将usera的SDP发送到信令服务器,信令服务器再根据roomid进行转发\nsendMessage(roomid,offerdesc);\n\n}
3.1.5发送answer消息到对方
当UserB收到UserA发来的offer消息,我们需要设置UserA的SDP并且设置当前的SDP然后再讲自己的SDP发送给UserA,以进行媒体协商,如下代码:
//1.当收到UserAOFFER消息,设置SDP\npc.setRemoteDescription(newRTCSessionDescription(data));\n\n//2.然后创建answer消息\npc.createAnswer()\n.then(getAnswer)\n.catch(handleAnswerError);\n\n//3.当创建成功后,拿到UserB自己的SDP消息并设置当前的SDP信息,最后再讲SDP消息发给信令再转发给roomid房间中的客户端\nfunctiongetAnswer(desc){\npc.setLocalDescription(desc);\n\noptBw.disabled=false;\n//sendanswersdp\nsendMessage(roomid,desc);\n}
3.1.6接收answer消息,并设置UserB的SDP信息
当我们收到UserB发来的answersdp消息后告诉底层
pc.setRemoteDescription(newRTCSessionDescription(data));
3.1.7交换ICE候选
SDP协商完后,UserA/UserB交换ice消息,用于nat和转发媒体数据,如果都在局域网其实可以省略这一步
//userA/UserB收到onicecandidate回调然后将candidate发送给UserB\npc.onicecandidate=(e)=>{\nif(e.candidate){\nsendMessage(roomid,{\ntype:&39;,\nlabel:event.candidate.sdpMLineIndex,\nid:event.candidate.sdpMid,\ncandidate:event.candidate.candidate\n});\n}else{\nconsole.log(&39;);\n}\n}\n\n//当UserB/UserA接收到UserA/UserB的candidate后进行添加\nfunctionaddIcecandida(data){\n\nvarcandidate=newRTCIceCandidate({\nsdpMLineIndex:data.label,\ncandidate:data.candidate\n});\npc.addIceCandidate(candidate)\n.then(()=>{\nconsole.log(&39;);\n})\n.catch(err=>{\nconsole.error(err);\n});\n}
通过如上核心步骤代码,你已经完成了一个基于WebRTCJS版的跨平台P2P音视频通话系统。当然,这里展示的代码只是简化版示例,完整版的代码可以点击文末简介处有说明。
3.2Android
上面我们实现了服务端和跨平台的JS端,最后我们实现一个Android端,毕竟最开始我就是搞Android的。
对于Android客户端,您可以使用Google提供的WebRTC库。如下,当前也可以直接依赖java/c++源码。当前我们就是直接依赖的java/c++源码
依赖wertcsdk方式,在build.gradle文件中添加依赖项:
implementation&39;
依赖wertc源码方式,在build.gradle文件中添加如下设置:
externalNativeBuild{\ncmake{\nversion&34;\npath&39;\n}\n}
没错,我们通过编写cmake直接依赖的c++源码。好了,依赖方式就不再多说了,可以直接去看项目中的build.gradle文件即可。
Android上的实现步骤流程与JS几乎一样,我们来看一下如何实现吧。
3.2.1获取媒体流并初始化PeerConnectionFactory
这里我们直接通过Camera2来实现相机数据的采集
privateVideoCapturercreateVideoCapture(){\nfinalVideoCapturervideoCapturer;\nvideoCapturer=createCameraCapturer(newCamera2Enumerator(this));\nreturnvideoCapturer;\n}\n\n\n//设置本地预览窗口\nmLocalSurfaceView.init(mRootEglBase.getEglBaseContext(),null);\nmLocalSurfaceView.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL);\nmLocalSurfaceView.setMirror(true);\nmLocalSurfaceView.setEnableHardwareScaler(false/*enabled*/);\n\n//设置远端预览窗口\nmRemoteSurfaceView.init(mRootEglBase.getEglBaseContext(),null);\nmRemoteSurfaceView.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL);\nmRemoteSurfaceView.setMirror(true);\nmRemoteSurfaceView.setEnableHardwareScaler(true/*enabled*/);\nmRemoteSurfaceView.setZOrderMediaOverlay(true);\ncallStartedTimeMs=System.currentTimeMillis();\n//创建factory,pc是从factory里获得的\ncreatePeerConnectionFactory();\n\nprivatevoidcreatePeerConnectionFactory(){\nfinalStringfieldTrials=getFieldTrials(mPeerConnectionParameters);\nexecutor.execute(()->{\nLog.d(Constants.P2PTAG,&34;+fieldTrials);\nPeerConnectionFactory.initialize(\nPeerConnectionFactory.InitializationOptions.builder(mContext)\n.setFieldTrials(fieldTrials)\n.setEnableInternalTracer(true)\n.createInitializationOptions());\n});\nexecutor.execute(()->{\ncreatePeerConnectionFactoryInternal();\n});\n}
3.2.2连接信令服务器并加入到房间中
publicvoidconnectToRoom(RoomConnectionParametersparameters,ISignalEventListenersignalEventListener){\nmRoomConnectParameters=parameters;\nexecutor.execute(()->{\nif(mISignalClient!=null){\ntry{\nmISignalClient.connect(parameters.roomUrl,newISignalEventListener(){\n@Override\npublicvoidOnConnecting(){\nLog.i(Constants.P2PTAG,&34;);\n…\n}\n\n@Override\npublicvoidOnConnected(){\nLog.i(Constants.P2PTAG,&34;);\nLog.i(Constants.P2PTAG,&34;+parameters.roomId);\nmISignalClient.join(parameters.roomId);\n…\n}\n\n@Override\npublicvoidOnDisconnected(){\nif(signalEventListener!=null){\nsignalEventListener.OnConnecting();\n}\n}\n\n@Override\npublicvoidOnUserJoined(StringroomName,StringuserId,booleanisInitiator){\nif(signalEventListener!=null){\nsignalEventListener.OnUserJoined(roomName,userId,isInitiator);\n}\nLog.i(Constants.P2PTAG,&34;+roomName+&34;+userId+&34;+isInitiator);\nLog.i(Constants.P2PTAG,&34;);\n…\n}\n\n@Override\npublicvoidOnUserLeaved(StringroomName,StringuserId){\n…\n}\n\n@Override\npublicvoidOnRemoteUserJoined(StringroomName,StringuserId){\nLog.i(Constants.P2PTAG,&34;+roomName+&34;+userId);\n…\n}\n\n@Override\npublicvoidOnRemoteUserLeaved(StringroomName,StringuserId){\n…\n}\n\n@Override\npublicvoidOnRoomFull(StringroomName,StringuserId){\n…\n}\n\n@Override\npublicvoidOnMessage(JSONObjectmessage){\n…\n\n}\n});\n\n}catch(Exceptione){\nLog.e(TAG,e.getMessage());\n}\n}\n});\n}
3.2.3创建PeerConnection并添加媒体轨道
当收到自己加入房间成功的消息后,连接到远程对等方,我们就需要创建一个PeerConnection对象,并将本地媒体流添加到其中。然后,您需要创建一个DataChannel对象,用于在对等方之间传输数据。
简要代码如下:
//当连接成功并且进入到房间中执行\nprivatevoidcreatePeerConnection(){\nexecutor.execute(()->{\ntry{\ncreateMediaConstraintsInternal();\ncreatePeerConnectionInternal();\nLog.i(Constants.P2PTAG,&34;);\n}catch(Exceptione){\nLog.e(TAG,&34;+e.getMessage());\nthrowe;\n}\n});\n}\nprivatevoidcreateMediaConstraintsInternal(){\n//Createvideoconstraintsifvideocallisenabled.\n…\n//Createaudioconstraints.\nmAudioConstraints=newMediaConstraints();\n//addedforaudioperformancemeasurements\nif(mPeerConnectionParameters.noAudioProcessing){\nLog.d(TAG,&34;);\nmAudioConstraints.mandatory.add(\nnewMediaConstraints.KeyValuePair(AUDIO_ECHO_CANCELLATION_CONSTRAINT,&34;));\nmAudioConstraints.mandatory.add(\nnewMediaConstraints.KeyValuePair(AUDIO_AUTO_GAIN_CONTROL_CONSTRAINT,&34;));\nmAudioConstraints.mandatory.add(\nnewMediaConstraints.KeyValuePair(AUDIO_HIGH_PASS_FILTER_CONSTRAINT,&34;));\nmAudioConstraints.mandatory.add(\nnewMediaConstraints.KeyValuePair(AUDIO_NOISE_SUPPRESSION_CONSTRAINT,&34;));\n}\n//CreateSDPconstraints.\nmSdpMediaConstraints=newMediaConstraints();\nmSdpMediaConstraints.mandatory.add(\nnewMediaConstraints.KeyValuePair(&34;,&34;));\nmSdpMediaConstraints.mandatory.add(newMediaConstraints.KeyValuePair(\n&34;,Boolean.toString(isVideoCallEnabled())));\n\n}\n\nprivatevoidcreatePeerConnectionInternal(){\nif(mPeerConnectionFactory==null){\nLog.e(TAG,&34;);\nreturn;\n}\nLog.d(TAG,&34;);\nqueuedRemoteCandidates=newArrayList<>();\nList<PeerConnection.IceServer>iceServers=newArrayList<>();\n\niceServers.add(PeerConnection.IceServer\n.builder(&34;)\n.setPassword(&34;)\n.setUsername(&34;)\n.createIceServer());\nPeerConnection.RTCConfigurationrtcConfig=\nnewPeerConnection.RTCConfiguration(iceServers);\n//TCPcandidatesareonlyusefulwhenconnectingtoaserverthatsupports\n//ICE-TCP.\nrtcConfig.tcpCandidatePolicy=PeerConnection.TcpCandidatePolicy.DISABLED;\nrtcConfig.bundlePolicy=PeerConnection.BundlePolicy.MAXBUNDLE;\nrtcConfig.rtcpMuxPolicy=PeerConnection.RtcpMuxPolicy.REQUIRE;\nrtcConfig.continualGatheringPolicy=PeerConnection.ContinualGatheringPolicy.GATHER_CONTINUALLY;\n//UseECDSAencryption.\nrtcConfig.keyType=PeerConnection.KeyType.ECDSA;\nrtcConfig.sdpSemantics=PeerConnection.SdpSemantics.UNIFIED_PLAN;\nmPeerConnection=mPeerConnectionFactory.createPeerConnection(rtcConfig,pcObserver);\n\nif(dataChannelEnabled){\nDataChannel.Initinit=newDataChannel.Init();\ninit.ordered=mPeerConnectionParameters.dataChannelParameters.ordered;\ninit.negotiated=mPeerConnectionParameters.dataChannelParameters.negotiated;\ninit.maxRetransmits=mPeerConnectionParameters.dataChannelParameters.maxRetransmits;\ninit.maxRetransmitTimeMs=mPeerConnectionParameters.dataChannelParameters.maxRetransmitTimeMs;\ninit.id=mPeerConnectionParameters.dataChannelParameters.id;\ninit.protocol=mPeerConnectionParameters.dataChannelParameters.protocol;\nmDataChannel=mPeerConnection.createDataChannel(&34;,init);\n}\nisInitiator=false;\n//SetINFOlibjinglelogging.\n//NOTE:this_must_happenwhile`factory`isalive!\nLogging.enableLogToDebugOutput(Logging.Severity.LS_INFO);\nList<String>mediaStreamLabels=Collections.singletonList(&34;);\nif(isVideoCallEnabled()){\nmPeerConnection.addTrack(createVideoTrack(mVideoCapture),mediaStreamLabels);\n//Wecanaddtherenderersrightawaybecausewedon&34;PCCreateOFFER&34;offer&34;answer&34;type&34;sdp&34;CreatingANSWER…&34;offer&34;answer&34;type&34;sdp&34;onIceCandidate:&34;type&34;candidate&34;label&34;id&34;candidate”,iceCandidate.sdp);\nmISignalClient.sendSignalMessage(mRoomConnectParameters.roomId,message);\n}catch(JSONExceptione){\ne.printStackTrace();\n}\n\n}\n);\n}\n\n//当UserB/UserA接收到UserA/UserB的candidate后进行添加\nmPeerConnection.addIceCandidate(candidate,newAddIceObserver(){\n…\n}\n}
通过如上核心步骤代码,你已经完成了一个基于WebRTCAndroid版的P2P音视频通话系统。当然,这里展示的代码只是简化版示例,完整版的代码可以点击文末简介处有说明。
到此,你已经可以JS<–>JS、Android<–>Android、JS<–>Android平台下进行P2P的音视频通话了。
4、总结
本文为你介绍了如何基于WebRTC实现一个P2P音视频通话系统,和提供了一个跨平台的实现方案,主要包括以下三个部分:
服务端:使用Node.js和socket.io构建的信令服务器,负责协调通信和传递ICE候选、SDP信息。客户端(跨平台):基于WebRTC的JavaScript客户端,实现浏览器端的音视频通话功能。客户端:Android客户端,使用Google提供的WebRTC库构建音视频通话应用。
请注意,本文提供的代码是简化版示例,您可以根据项目需求进行扩展和优化。通过本教程,您应该对如何使用WebRTC构建P2P音视频通话系统有了更深入的了解,并能将其应用于实际项目中。
到此,P2P音视频通话系统我们已经实现完了,下一篇我们会介绍视频会议的实现方案,尽请期待吧。
原文链接:WebRTC实战:P2P音视频通话解决方案-掘金
关于如何搭建p2p网站源码分享的内容到此结束,希望对大家有所帮助。