﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-不急不徐，持之以恒。-随笔分类-即时通讯(IM)</title><link>http://www.blogjava.net/linli/category/54569.html</link><description>http://blog.gopersist.com/</description><language>zh-cn</language><lastBuildDate>Tue, 21 Apr 2015 02:39:08 GMT</lastBuildDate><pubDate>Tue, 21 Apr 2015 02:39:08 GMT</pubDate><ttl>60</ttl><item><title>P2P中NAT之间的打洞可能性</title><link>http://www.blogjava.net/linli/archive/2014/10/23/418968.html</link><dc:creator>老林</dc:creator><author>老林</author><pubDate>Thu, 23 Oct 2014 06:17:00 GMT</pubDate><guid>http://www.blogjava.net/linli/archive/2014/10/23/418968.html</guid><wfw:comment>http://www.blogjava.net/linli/comments/418968.html</wfw:comment><comments>http://www.blogjava.net/linli/archive/2014/10/23/418968.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/linli/comments/commentRss/418968.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/linli/services/trackbacks/418968.html</trackback:ping><description><![CDATA[<div>我们看看不同NAT之间的NAT打洞。NAT打洞需要Server配合，需要2种Server：</div><div>1. 类似WebRTC中的信令服务器，作用是帮助客户机沟通IP和PORT信息；</div><div>2. STUN Server，用来让客户机判断自己所在的NAT环境。</div><div></div><div>现在假设客户端和Server的通讯都没问题，客户端知道自己所处环境，并且将自己的信息通过服务器发送给了另一方客户端，它们可能的打洞情况如下：</div><div>1. Full Cone NAT 与 Full Cone NAT：通讯很容易，各自通过STUN Server获取外部IP和Port后，通过信令服务器通知另一方，即可通讯。</div><div><img src="http://www.blogjava.net/images/blogjava_net/linli/nat/Full_Cone-Full_Cone.png" width="1218" height="447" alt="" /></div><div>2. Full Cone NAT 与 Restricted Cone NAT或Port Restricted Cone NAT在互相告知IP和Port后，如果由Full Cone NAT端先发送数据包，会失败，必须由Restricted Cone NAT或Port Restricted Cone NAT端先发送数据包给Full Cone NAT，之后双方即可互相通讯。</div><div><img src="http://www.blogjava.net/images/blogjava_net/linli/nat/Full_Cone-Port_Restricted_Cone.png" width="1219" height="442" alt="" /></div><div>3. Full Cone NAT 与 Symmetric NAT通讯时，必须先由Symmetric NAT端发送数据包给Full Cone NAT端，Full Cone NAT端通过发来的数据包获得目标的新端口号，之后通过这个新端口号完成互相通讯。</div><div><img src="http://www.blogjava.net/images/blogjava_net/linli/nat/Full_Cone-Symmetric_NAT.png" width="1219" height="452" alt="" /></div><div>4. Restricted Cone NAT 与 Restricted Cone NAT、Restricted Cone NAT 与 Port Restricted Cone NAT、Port Restricted Cone NAT 与 Port Restricted Cone NAT之间通讯时，先发送数据包的一方会失败，之后另一方发送数据包成功后，可互相通讯。</div><div><img src="http://www.blogjava.net/images/blogjava_net/linli/nat/Port_Restricted_Cone-Port_Restricted_Cone.png" width="1219" height="442" alt="" /></div><div>5. Restricted Cone NAT 与 Symmetric NAT通讯时，先由Restricted Cone NAT发送数据包给Symmetric NAT，发送数据会失败，只是为了下次能接收从Symmetric NAT端发送过来的数据包。然后由Symmetric NAT发送数据包到Restricted Cone NAT端，Restricted Cone NAT端会收到数据包，并且将新的端口号记下，使用新的端口号可与Symmetric NAT端通讯。</div><div><img src="http://www.blogjava.net/images/blogjava_net/linli/nat/Restricted_Cone-Symmetric_NAT.png" width="1219" height="452" alt="" /></div><div>6. Port Restricted Cone NAT 与 Symmetric NAT通讯时，由于Port Restricted Cone NAT会对IP:PORT对进行限制，所以当Symmetric NAT端使用新PORT发来数据包时，Port Restricted Cone NAT端收不到，它们之间无法通讯。</div><div><img src="http://www.blogjava.net/images/blogjava_net/linli/nat/Port_Restricted_Cone-Symmetric_NAT.png" width="1219" height="452" alt="" /></div><div>7. Symmetric NAT 与 Symmetric NAT也无法通讯 。</div><div><img src="http://www.blogjava.net/images/blogjava_net/linli/nat/Symmetric_NAT-Symmetric_NAT.png" width="1219" height="452" alt="" /></div><img src ="http://www.blogjava.net/linli/aggbug/418968.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/linli/" target="_blank">老林</a> 2014-10-23 14:17 <a href="http://www.blogjava.net/linli/archive/2014/10/23/418968.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>针对UDP数据的4种NAT行为</title><link>http://www.blogjava.net/linli/archive/2014/10/23/418965.html</link><dc:creator>老林</dc:creator><author>老林</author><pubDate>Thu, 23 Oct 2014 05:56:00 GMT</pubDate><guid>http://www.blogjava.net/linli/archive/2014/10/23/418965.html</guid><wfw:comment>http://www.blogjava.net/linli/comments/418965.html</wfw:comment><comments>http://www.blogjava.net/linli/archive/2014/10/23/418965.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/linli/comments/commentRss/418965.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/linli/services/trackbacks/418965.html</trackback:ping><description><![CDATA[<div>针对收发UDP数据，NAT可分为Full Cone、Restricted Cone、Port Restricted Cone、Symmetric NAT四类，在RFC3489中有定义(http://datatracker.ietf.org/doc/rfc3489/?include_text=1)。</div><div></div><div>1. Full Cone：所有从相同的内部IP和PORT发出的请求都映射为相同的外部IP和PORT，而后任何外部主机只要发送数据包给NAT的IP和PORT就会被转发给内部主机。</div><div><img src="http://www.blogjava.net/images/blogjava_net/linli/nat/Full_Cone.png" width="904" height="551" alt="" /></div><div>从图中可以看到，只要内部主机通过NAT访问了一次外部主机，在Mapping Table中会增加一条内部IP:Port映射到NAT的端口，那么外部的任何主机都可以通过NAT的IP:PORT将数据发给内部主机。</div><div></div><div>2. Restricted Cone：所有从相同的内部IP和PORT发出的请求都映射为相同的外部IP和PORT，但只有内部主机曾发送过数据的外部IP才可将数据包通过NAT的IP:PORT发给内部主机。</div><div><img src="http://www.blogjava.net/images/blogjava_net/linli/nat/Restricted_Cone.png" width="904" height="544" alt="" /></div><div>从图中可以看到，因为内部主机没有发过数据包给外部主机B，所以外部主机发到NAT的数据包无法发给内部主机。</div><div></div><div>3.&nbsp;Port&nbsp;Restricted Cone：和Restricted Cone类似，但是除了IP的限制外增加了PORT的限制，即只有内部主机曾发送过数据的外部IP:PORT才可将数据包通过NAT的IP:PORT发给内部主机。</div><div><img src="http://www.blogjava.net/images/blogjava_net/linli/nat/Port_Restricted_Cone.png" width="904" height="546" alt="" /></div><div>从图中可以看到，外部主机1用另一个PORT无法将数据发到内部主机。</div><div></div><div>4. Symmetric NAT：从内部主机相同的IP和PORT发出的请求，当访问不同外部IP和PORT时，都会在NAT上创建不同的映射。</div><div><img src="http://www.blogjava.net/images/blogjava_net/linli/nat/Symmetric_NAT.png" width="904" height="573" alt="" /></div><div>上图中虽然内部IP和PORT相同，但访问不同的外部IP/PORT对，都会映射为不同的NAT PORT。当外部主机发数据包给内部主机时，也只能使用对应的PORT。</div><img src ="http://www.blogjava.net/linli/aggbug/418965.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/linli/" target="_blank">老林</a> 2014-10-23 13:56 <a href="http://www.blogjava.net/linli/archive/2014/10/23/418965.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Ubuntu下安装TURN Server (rfc5766-turn-server)</title><link>http://www.blogjava.net/linli/archive/2014/10/22/418935.html</link><dc:creator>老林</dc:creator><author>老林</author><pubDate>Wed, 22 Oct 2014 06:23:00 GMT</pubDate><guid>http://www.blogjava.net/linli/archive/2014/10/22/418935.html</guid><wfw:comment>http://www.blogjava.net/linli/comments/418935.html</wfw:comment><comments>http://www.blogjava.net/linli/archive/2014/10/22/418935.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.blogjava.net/linli/comments/commentRss/418935.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/linli/services/trackbacks/418935.html</trackback:ping><description><![CDATA[<div>在使用WebRTC进行即时通讯时，需要使浏览器进行P2P通讯，但是由于NAT环境的复杂性，并不是所有情况下都能进行P2P，这时需要TURN Server来帮助客户端之间转发数据。rfc5766-turn-server是一个高性能的开源TURN Server实现。</div><div></div><div>以下是在EC2上使用Ubuntu操作系统安装rfc5766-turn-server：</div><div>1. 下载安装包：</div><div>$ wget http://ftp.cn.debian.org/debian/pool/main/r/rfc5766-turn-server/rfc5766-turn-server_3.2.4.4-1_amd64.deb</div><div></div><div>2. 安装：</div><div>$ sudo apt-get update</div><div>$ sudo apt-get install gdebi-core</div><div>$ sudo gdebi rfc5766-turn-server_3.2.4.4-1_amd64.deb</div><div></div><div><em>安装完后，在/usr/share/doc/rfc5766-turn-server下有很多文档可参考。</em></div><div></div><div>3. 配置：</div><div>$ sudo vi /etc/turnserver.conf</div><div>---------------------------------------</div><div>// 配置IP，EC2下需要配置listening-ip和external-ip</div><div>listening-ip=172.31.4.37</div><div>external-ip=54.223.149.60</div><div></div><div>// 当TURN Server用于WebRTC时，必须使用long-term credential mechanism</div><div>lt-cred-mech</div><div>// 增加一个用户</div><div>user=username1:password1</div><div>// 设定realm</div><div>realm=mycompany.org</div><div>---------------------------------------</div><div></div><div>4. 启动：</div><div>sudo turnserver -c /etc/turnserver.conf --daemon</div><div></div><div>5. 服务启动后，在上一个WebRTC示例中更改iceServers后测试：</div><div>"iceServers": [{</div><div>&nbsp; &nbsp; "url": "stun:stun.l.google.com:19302"</div><div>}, {</div><div>&nbsp; &nbsp; "url": "turn:54.223.149.60",</div><div>&nbsp; &nbsp; "username": "username1",</div><div>&nbsp; &nbsp; "credential": "password1"</div><div>}]</div><div></div><div>更多安装信息在：http://turnserver.open-sys.org/downloads/v3.2.4.4/INSTALL</div><div></div><div>rfc5766-turn-server当然也有STUN Server的能力，但是需要给它配置2个IP，以帮助探测客户端所在NAT环境的行为，这里没有做。</div><img src ="http://www.blogjava.net/linli/aggbug/418935.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/linli/" target="_blank">老林</a> 2014-10-22 14:23 <a href="http://www.blogjava.net/linli/archive/2014/10/22/418935.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>最简单的WebRTC示例</title><link>http://www.blogjava.net/linli/archive/2014/10/21/418910.html</link><dc:creator>老林</dc:creator><author>老林</author><pubDate>Tue, 21 Oct 2014 09:21:00 GMT</pubDate><guid>http://www.blogjava.net/linli/archive/2014/10/21/418910.html</guid><wfw:comment>http://www.blogjava.net/linli/comments/418910.html</wfw:comment><comments>http://www.blogjava.net/linli/archive/2014/10/21/418910.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.blogjava.net/linli/comments/commentRss/418910.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/linli/services/trackbacks/418910.html</trackback:ping><description><![CDATA[在学习WebRTC时，网上的示例大多代码较多，以下是参考那些代码简化的一个WebRTC一对一的示例，在chrome 37下测试通过。其中iceServer可省略，没有iceServer时在同一个局域网下仍可通讯。<br /><br />客户端代码：<br /><div style="background-color:#eeeeee;font-size:13px;border:1px solid #CCCCCC;padding-right: 5px;padding-bottom: 4px;padding-left: 4px;padding-top: 4px;width: 98%;word-break:break-all"><!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--><span style="color: #0000FF; ">&lt;</span><span style="color: #800000; ">html</span><span style="color: #0000FF; ">&gt;</span><br /><span style="color: #0000FF; ">&lt;</span><span style="color: #800000; ">body</span><span style="color: #0000FF; ">&gt;</span><br />&nbsp;&nbsp;&nbsp;&nbsp;Local:&nbsp;<span style="color: #0000FF; ">&lt;</span><span style="color: #800000; ">br</span><span style="color: #0000FF; ">&gt;</span><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">&lt;</span><span style="color: #800000; ">video&nbsp;</span><span style="color: #FF0000; ">id</span><span style="color: #0000FF; ">="localVideo"</span><span style="color: #FF0000; ">&nbsp;autoplay</span><span style="color: #0000FF; ">&gt;&lt;/</span><span style="color: #800000; ">video</span><span style="color: #0000FF; ">&gt;&lt;</span><span style="color: #800000; ">br</span><span style="color: #0000FF; ">&gt;</span><br />&nbsp;&nbsp;&nbsp;&nbsp;Remote:&nbsp;<span style="color: #0000FF; ">&lt;</span><span style="color: #800000; ">br</span><span style="color: #0000FF; ">&gt;</span><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">&lt;</span><span style="color: #800000; ">video&nbsp;</span><span style="color: #FF0000; ">id</span><span style="color: #0000FF; ">="remoteVideo"</span><span style="color: #FF0000; ">&nbsp;autoplay</span><span style="color: #0000FF; ">&gt;&lt;/</span><span style="color: #800000; ">video</span><span style="color: #0000FF; ">&gt;</span><br /><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">&lt;</span><span style="color: #800000; ">script</span><span style="color: #0000FF; ">&gt;</span><span style="background-color: #f5f5f5;"><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #F5F5F5; color: #008000; ">//</span><span style="background-color: #F5F5F5; color: #008000; ">&nbsp;仅仅用于控制哪一端的浏览器发起offer，#号后面有值的一方发起</span><span style="background-color: #F5F5F5; color: #008000; "><br /></span><span style="background-color: #f5f5f5;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #F5F5F5; color: #0000FF; ">var</span><span style="background-color: #f5f5f5;">&nbsp;isCaller&nbsp;</span><span style="background-color: #f5f5f5;">=</span><span style="background-color: #f5f5f5;">&nbsp;window.location.href.split('#')[</span><span style="background-color: #f5f5f5;">1</span><span style="background-color: #f5f5f5;">];<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #F5F5F5; color: #008000; ">//</span><span style="background-color: #F5F5F5; color: #008000; ">&nbsp;与信令服务器的WebSocket连接</span><span style="background-color: #F5F5F5; color: #008000; "><br /></span><span style="background-color: #f5f5f5;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #F5F5F5; color: #0000FF; ">var</span><span style="background-color: #f5f5f5;">&nbsp;socket&nbsp;</span><span style="background-color: #f5f5f5;">=</span><span style="background-color: #f5f5f5;">&nbsp;</span><span style="background-color: #F5F5F5; color: #0000FF; ">new</span><span style="background-color: #f5f5f5;">&nbsp;WebSocket(</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">ws://127.0.0.1:3000</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">);<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #F5F5F5; color: #008000; ">//</span><span style="background-color: #F5F5F5; color: #008000; ">&nbsp;stun和turn服务器</span><span style="background-color: #F5F5F5; color: #008000; "><br /></span><span style="background-color: #f5f5f5;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #F5F5F5; color: #0000FF; ">var</span><span style="background-color: #f5f5f5;">&nbsp;iceServer&nbsp;</span><span style="background-color: #f5f5f5;">=</span><span style="background-color: #f5f5f5;">&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">iceServers</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">:&nbsp;[{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">url</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">:&nbsp;</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">stun:stun.l.google.com:19302</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;"><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;},&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">url</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">:&nbsp;</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">turn:numb.viagenie.ca</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">,<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">username</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">:&nbsp;</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">webrtc@live.com</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">,<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">credential</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">:&nbsp;</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">muazkh</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;"><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}]<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;};<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #F5F5F5; color: #008000; ">//</span><span style="background-color: #F5F5F5; color: #008000; ">&nbsp;创建PeerConnection实例&nbsp;(参数为null则没有iceserver，即使没有stunserver和turnserver，仍可在局域网下通讯)</span><span style="background-color: #F5F5F5; color: #008000; "><br /></span><span style="background-color: #f5f5f5;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #F5F5F5; color: #0000FF; ">var</span><span style="background-color: #f5f5f5;">&nbsp;pc&nbsp;</span><span style="background-color: #f5f5f5;">=</span><span style="background-color: #f5f5f5;">&nbsp;</span><span style="background-color: #F5F5F5; color: #0000FF; ">new</span><span style="background-color: #f5f5f5;">&nbsp;webkitRTCPeerConnection(iceServer);<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #F5F5F5; color: #008000; ">//</span><span style="background-color: #F5F5F5; color: #008000; ">&nbsp;发送ICE候选到其他客户端</span><span style="background-color: #F5F5F5; color: #008000; "><br /></span><span style="background-color: #f5f5f5;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pc.onicecandidate&nbsp;</span><span style="background-color: #f5f5f5;">=</span><span style="background-color: #f5f5f5;">&nbsp;</span><span style="background-color: #F5F5F5; color: #0000FF; ">function</span><span style="background-color: #f5f5f5;">(event){<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #F5F5F5; color: #0000FF; ">if</span><span style="background-color: #f5f5f5;">&nbsp;(event.candidate&nbsp;</span><span style="background-color: #f5f5f5;">!==</span><span style="background-color: #f5f5f5;">&nbsp;</span><span style="background-color: #F5F5F5; color: #0000FF; ">null</span><span style="background-color: #f5f5f5;">)&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;socket.send(JSON.stringify({<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">event</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">:&nbsp;</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">_ice_candidate</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">,<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">data</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">:&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">candidate</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">:&nbsp;event.candidate<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}));<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;};<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #F5F5F5; color: #008000; ">//</span><span style="background-color: #F5F5F5; color: #008000; ">&nbsp;如果检测到媒体流连接到本地，将其绑定到一个video标签上输出</span><span style="background-color: #F5F5F5; color: #008000; "><br /></span><span style="background-color: #f5f5f5;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pc.onaddstream&nbsp;</span><span style="background-color: #f5f5f5;">=</span><span style="background-color: #f5f5f5;">&nbsp;</span><span style="background-color: #F5F5F5; color: #0000FF; ">function</span><span style="background-color: #f5f5f5;">(event){<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;document.getElementById('remoteVideo').src&nbsp;</span><span style="background-color: #f5f5f5;">=</span><span style="background-color: #f5f5f5;">&nbsp;URL.createObjectURL(event.stream);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;};<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #F5F5F5; color: #008000; ">//</span><span style="background-color: #F5F5F5; color: #008000; ">&nbsp;发送offer和answer的函数，发送本地session描述</span><span style="background-color: #F5F5F5; color: #008000; "><br /></span><span style="background-color: #f5f5f5;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #F5F5F5; color: #0000FF; ">var</span><span style="background-color: #f5f5f5;">&nbsp;sendOfferFn&nbsp;</span><span style="background-color: #f5f5f5;">=</span><span style="background-color: #f5f5f5;">&nbsp;</span><span style="background-color: #F5F5F5; color: #0000FF; ">function</span><span style="background-color: #f5f5f5;">(desc){<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pc.setLocalDescription(desc);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;socket.send(JSON.stringify({&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">event</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">:&nbsp;</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">_offer</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">,<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">data</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">:&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">sdp</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">:&nbsp;desc<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}));<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;},<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sendAnswerFn&nbsp;</span><span style="background-color: #f5f5f5;">=</span><span style="background-color: #f5f5f5;">&nbsp;</span><span style="background-color: #F5F5F5; color: #0000FF; ">function</span><span style="background-color: #f5f5f5;">(desc){<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pc.setLocalDescription(desc);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;socket.send(JSON.stringify({&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">event</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">:&nbsp;</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">_answer</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">,<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">data</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">:&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">sdp</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">:&nbsp;desc<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}));<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;};<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #F5F5F5; color: #008000; ">//</span><span style="background-color: #F5F5F5; color: #008000; ">&nbsp;获取本地音频和视频流</span><span style="background-color: #F5F5F5; color: #008000; "><br /></span><span style="background-color: #f5f5f5;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;navigator.webkitGetUserMedia({<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">audio</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">:&nbsp;</span><span style="background-color: #F5F5F5; color: #0000FF; ">true</span><span style="background-color: #f5f5f5;">,<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">video</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">:&nbsp;</span><span style="background-color: #F5F5F5; color: #0000FF; ">true</span><span style="background-color: #f5f5f5;"><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;},&nbsp;</span><span style="background-color: #F5F5F5; color: #0000FF; ">function</span><span style="background-color: #f5f5f5;">(stream){<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #F5F5F5; color: #008000; ">//</span><span style="background-color: #F5F5F5; color: #008000; ">绑定本地媒体流到video标签用于输出</span><span style="background-color: #F5F5F5; color: #008000; "><br /></span><span style="background-color: #f5f5f5;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;document.getElementById('localVideo').src&nbsp;</span><span style="background-color: #f5f5f5;">=</span><span style="background-color: #f5f5f5;">&nbsp;URL.createObjectURL(stream);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #F5F5F5; color: #008000; ">//</span><span style="background-color: #F5F5F5; color: #008000; ">向PeerConnection中加入需要发送的流</span><span style="background-color: #F5F5F5; color: #008000; "><br /></span><span style="background-color: #f5f5f5;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pc.addStream(stream);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #F5F5F5; color: #008000; ">//</span><span style="background-color: #F5F5F5; color: #008000; ">如果是发起方则发送一个offer信令</span><span style="background-color: #F5F5F5; color: #008000; "><br /></span><span style="background-color: #f5f5f5;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #F5F5F5; color: #0000FF; ">if</span><span style="background-color: #f5f5f5;">(isCaller){<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pc.createOffer(sendOfferFn,&nbsp;</span><span style="background-color: #F5F5F5; color: #0000FF; ">function</span><span style="background-color: #f5f5f5;">&nbsp;(error)&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;console.log('Failure&nbsp;callback:&nbsp;'&nbsp;</span><span style="background-color: #f5f5f5;">+</span><span style="background-color: #f5f5f5;">&nbsp;error);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;});<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;},&nbsp;</span><span style="background-color: #F5F5F5; color: #0000FF; ">function</span><span style="background-color: #f5f5f5;">(error){<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #F5F5F5; color: #008000; ">//</span><span style="background-color: #F5F5F5; color: #008000; ">处理媒体流创建失败错误</span><span style="background-color: #F5F5F5; color: #008000; "><br /></span><span style="background-color: #f5f5f5;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;console.log('getUserMedia&nbsp;error:&nbsp;'&nbsp;</span><span style="background-color: #f5f5f5;">+</span><span style="background-color: #f5f5f5;">&nbsp;error);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;});<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #F5F5F5; color: #008000; ">//</span><span style="background-color: #F5F5F5; color: #008000; ">处理到来的信令</span><span style="background-color: #F5F5F5; color: #008000; "><br /></span><span style="background-color: #f5f5f5;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;socket.onmessage&nbsp;</span><span style="background-color: #f5f5f5;">=</span><span style="background-color: #f5f5f5;">&nbsp;</span><span style="background-color: #F5F5F5; color: #0000FF; ">function</span><span style="background-color: #f5f5f5;">(event){<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #F5F5F5; color: #0000FF; ">var</span><span style="background-color: #f5f5f5;">&nbsp;json&nbsp;</span><span style="background-color: #f5f5f5;">=</span><span style="background-color: #f5f5f5;">&nbsp;JSON.parse(event.data);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;console.log('onmessage:&nbsp;',&nbsp;json);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #F5F5F5; color: #008000; ">//</span><span style="background-color: #F5F5F5; color: #008000; ">如果是一个ICE的候选，则将其加入到PeerConnection中，否则设定对方的session描述为传递过来的描述</span><span style="background-color: #F5F5F5; color: #008000; "><br /></span><span style="background-color: #f5f5f5;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #F5F5F5; color: #0000FF; ">if</span><span style="background-color: #f5f5f5;">(&nbsp;json.event&nbsp;</span><span style="background-color: #f5f5f5;">===</span><span style="background-color: #f5f5f5;">&nbsp;</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">_ice_candidate</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">&nbsp;){<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pc.addIceCandidate(</span><span style="background-color: #F5F5F5; color: #0000FF; ">new</span><span style="background-color: #f5f5f5;">&nbsp;RTCIceCandidate(json.data.candidate));<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;</span><span style="background-color: #F5F5F5; color: #0000FF; ">else</span><span style="background-color: #f5f5f5;">&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pc.setRemoteDescription(</span><span style="background-color: #F5F5F5; color: #0000FF; ">new</span><span style="background-color: #f5f5f5;">&nbsp;RTCSessionDescription(json.data.sdp));<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #F5F5F5; color: #008000; ">//</span><span style="background-color: #F5F5F5; color: #008000; ">&nbsp;如果是一个offer，那么需要回复一个answer</span><span style="background-color: #F5F5F5; color: #008000; "><br /></span><span style="background-color: #f5f5f5;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #F5F5F5; color: #0000FF; ">if</span><span style="background-color: #f5f5f5;">(json.event&nbsp;</span><span style="background-color: #f5f5f5;">===</span><span style="background-color: #f5f5f5;">&nbsp;</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">_offer</span><span style="background-color: #f5f5f5;">"</span><span style="background-color: #f5f5f5;">)&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pc.createAnswer(sendAnswerFn,&nbsp;</span><span style="background-color: #F5F5F5; color: #0000FF; ">function</span><span style="background-color: #f5f5f5;">&nbsp;(error)&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;console.log('Failure&nbsp;callback:&nbsp;'&nbsp;</span><span style="background-color: #f5f5f5;">+</span><span style="background-color: #f5f5f5;">&nbsp;error);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;});<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;};<br />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000FF; ">&lt;/</span><span style="color: #800000; ">script</span><span style="color: #0000FF; ">&gt;</span><br /><span style="color: #0000FF; ">&lt;/</span><span style="color: #800000; ">body</span><span style="color: #0000FF; ">&gt;</span><br /><span style="color: #0000FF; ">&lt;/</span><span style="color: #800000; ">html</span><span style="color: #0000FF; ">&gt;</span></div><br />实现WebRTC时，信令服务器是必须的，它帮助客户端之间进行沟通。<br />这里使用Node.js的ws模块来实现一个WebSocket服务作为信令服务器。另外使用express模块让它提供html页面的访问。<br />server.js代码如下：<br /><div style="background-color:#eeeeee;font-size:13px;border:1px solid #CCCCCC;padding-right: 5px;padding-bottom: 4px;padding-left: 4px;padding-top: 4px;width: 98%;word-break:break-all"><span style="color: #0000FF; ">var</span>&nbsp;express&nbsp;=&nbsp;require('express'),<br />app&nbsp;=&nbsp;express(),<br />server&nbsp;=&nbsp;require('http').createServer(app);<br /><br />server.listen(3000);<br /><br />app.get('/',&nbsp;<span style="color: #0000FF; ">function</span>(req,&nbsp;res)&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;res.sendfile(__dirname&nbsp;+&nbsp;'/webrtc.html');<br />});<br /><br /><span style="color: #0000FF; ">var</span>&nbsp;WebSocketServer&nbsp;=&nbsp;require('ws').Server,<br />wss&nbsp;=&nbsp;<span style="color: #0000FF; ">new</span>&nbsp;WebSocketServer({server:&nbsp;server});<br /><br /><span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;存储socket的数组，这里只能有2个socket，每次测试需要重启，否则会出错</span><span style="color: #008000; "><br /></span><span style="color: #0000FF; ">var</span>&nbsp;wsc&nbsp;=&nbsp;[],<br />index&nbsp;=&nbsp;1;<br /><br /><span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;有socket连入</span><span style="color: #008000; "><br /></span>wss.on('connection',&nbsp;<span style="color: #0000FF; ">function</span>(ws)&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;console.log('connection');<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;将socket存入数组</span><span style="color: #008000; "><br /></span>&nbsp;&nbsp;&nbsp;&nbsp;wsc.push(ws);<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;记下对方socket在数组中的下标，因为这个测试程序只允许2个socket</span><span style="color: #008000; "><br /></span>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;所以第一个连入的socket存入0，第二个连入的就是存入1</span><span style="color: #008000; "><br /></span>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;otherIndex就反着来，第一个socket的otherIndex下标为1，第二个socket的otherIndex下标为0</span><span style="color: #008000; "><br /></span>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">var</span>&nbsp;otherIndex&nbsp;=&nbsp;index--,<br />&nbsp;&nbsp;&nbsp;&nbsp;desc&nbsp;=&nbsp;<span style="color: #0000FF; ">null</span>;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">if</span>&nbsp;(otherIndex&nbsp;==&nbsp;1)&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;desc&nbsp;=&nbsp;'first&nbsp;socket';<br />&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;<span style="color: #0000FF; ">else</span>&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;desc&nbsp;=&nbsp;'second&nbsp;socket';<br />&nbsp;&nbsp;&nbsp;&nbsp;}<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #008000; ">//</span><span style="color: #008000; ">&nbsp;转发收到的消息</span><span style="color: #008000; "><br /></span>&nbsp;&nbsp;&nbsp;&nbsp;ws.on('message',&nbsp;<span style="color: #0000FF; ">function</span>(message)&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">var</span>&nbsp;json&nbsp;=&nbsp;JSON.parse(message);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;console.log('received&nbsp;('&nbsp;+&nbsp;desc&nbsp;+&nbsp;'):&nbsp;',&nbsp;json);<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;wsc[otherIndex].send(message,&nbsp;<span style="color: #0000FF; ">function</span>&nbsp;(error)&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF; ">if</span>&nbsp;(error)&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;console.log('Send&nbsp;message&nbsp;error&nbsp;('&nbsp;+&nbsp;desc&nbsp;+&nbsp;'):&nbsp;',&nbsp;error);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;});<br />&nbsp;&nbsp;&nbsp;&nbsp;});<br />});</div><br />使用npm安装需要的模块后使用node server.js启动服务。<br />测试时使用Chrome浏览器：<br />第一个浏览器窗口访问页面：http://127.0.0.1:3000，在弹出的提示中允许使用摄像头和麦克风。<br />第二个浏览器窗口访问页面：http://127.0.0.1:3000#true，#true表示它是一个发起方，在弹出的提示中同样允许使用摄像头和麦克风。<br />这时页面中应当可以看到2个画面，一个是本地的，一个是远端的。<br /><br />将代码中的IP稍做调整后部署到外网，即可在2个不同的地点访问这个页面进行实时通讯。<br /><br /><a title="作者博客：http://blog.gopersist.com" href="http://blog.gopersist.com"><br /></a><span style="color: #111111; font-family: Helvetica, Arial, sans-serif; font-size: 16px; line-height: 24px; background-color: #fdfdfd;">微信订阅号：</span><img src="http://blog.gopersist.com/images/about/weixin.jpg" width="100" height="100" alt="" style="color: #111111; font-family: Helvetica, Arial, sans-serif; font-size: 16px; line-height: 24px; max-width: 100%; vertical-align: middle;" /><br style="color: #111111; font-family: Helvetica, Arial, sans-serif; font-size: 16px; line-height: 24px;" /><span style="color: #111111; font-family: Helvetica, Arial, sans-serif; font-size: 16px; line-height: 24px; background-color: #fdfdfd;">源文地址：<a href="http://blog.gopersist.com/2014/10/21/webrtc-simple/">http://blog.gopersist.com/2014/10/21/webrtc-simple/</a></span><img src ="http://www.blogjava.net/linli/aggbug/418910.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/linli/" target="_blank">老林</a> 2014-10-21 17:21 <a href="http://www.blogjava.net/linli/archive/2014/10/21/418910.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>