- 浏览: 635924 次
文章分类
最新评论
-
涛声依旧是:
怎么提示掉线,怎么才能在线
基于Tomcat7、Java、WebSocket的服务器推送聊天室 -
dcode:
楼主写的不错,正好遇到点问题,看着你的文章解决了,感谢分享。。 ...
在 Visual Studio 2010 中配置SharpPcap
基于Chrome、Java、WebSocket、WebRTC实现浏览器视频通话
介绍
最近这段时间折腾了一下WebRTC,看了网上的https://apprtc.appspot.com/的例子(可能需要访问),这个例子是部署在Google App Engine上的应用程序,依赖GAE的环境,后台的语言是python,而且还依赖Google App Engine Channel API,所以无法在本地运行,也无法扩展。费了一番功夫研读了例子的python端的源代码,决定用Java实现,Tomcat7之后开始支持WebSocket,打算用WebSocket代替Google
App Engine Channel API实现前后台的通讯,在整个例子中Java+WebSocket起到的作用是负责客户端之间的通信,并不负责视频的传输,视频的传输依赖于WebRTC。
实例的特点是:
- HTML5
- 不需要任何插件
- 资源占用不是很大,对服务器的开销比较小,只要客户端建立连接,视频传输完全有浏览器完成
- 通过JS实现,理论上只要浏览器支持WebSocket,WebRTC就能运行(目前只在Chrome测试通过,Chrome版本24.0.1312.2 dev-m)
实现
对于前端JS代码及用到的对象大家可以访问http://www.html5rocks.com/en/tutorials/webrtc/basics/查看详细的代码介绍。我在这里只介绍下我改动过的地方,首先建立一个客户端实时获取状态的连接,在GAE的例子上是通过GAE Channel API实现,我在这里用WebSocket实现,代码:
function openChannel() { console.log("Opening channel."); socket = new WebSocket( "ws://192.168.1.102:8080/RTCApp/websocket?u=${user}"); socket.onopen = onChannelOpened; socket.onmessage = onChannelMessage; socket.onclose = onChannelClosed; }建立一个WebSocket连接,并注册相关的事件。这里通过Java实现WebSocket连接:
package org.rtc.servlet; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.catalina.websocket.StreamInbound; import org.apache.catalina.websocket.WebSocketServlet; import org.rtc.websocket.WebRTCMessageInbound; @WebServlet(urlPatterns = { "/websocket"}) public class WebRTCWebSocketServlet extends WebSocketServlet { private static final long serialVersionUID = 1L; private String user; public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.user = request.getParameter("u"); super.doGet(request, response); } @Override protected StreamInbound createWebSocketInbound(String subProtocol) { return new WebRTCMessageInbound(user); } }如果你想实现WebSocket必须得用Tomcat7及以上版本,并且引入:catalina.jar,tomcat-coyote.jar两个JAR包,部署到Tomcat7之后得要去webapps/应用下面去删除这两个AR包否则无法启动,WebSocket访问和普通的访问最大的不同在于继承了WebSocketServlet,关于WebSocket的详细介绍大家可以访问http://redstarofsleep.iteye.com/blog/1488639,在这里就不再赘述。大家可以看看WebRTCMessageInbound这个类的实现:
package org.rtc.websocket; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.CharBuffer; import org.apache.catalina.websocket.MessageInbound; import org.apache.catalina.websocket.WsOutbound; public class WebRTCMessageInbound extends MessageInbound { private final String user; public WebRTCMessageInbound(String user) { this.user = user; } public String getUser(){ return this.user; } @Override protected void onOpen(WsOutbound outbound) { //触发连接事件,在连接池中添加连接 WebRTCMessageInboundPool.addMessageInbound(this); } @Override protected void onClose(int status) { //触发关闭事件,在连接池中移除连接 WebRTCMessageInboundPool.removeMessageInbound(this); } @Override protected void onBinaryMessage(ByteBuffer message) throws IOException { throw new UnsupportedOperationException( "Binary message not supported."); } @Override protected void onTextMessage(CharBuffer message) throws IOException { } }WebRTCMessageInbound继承了MessageInbound,并绑定了两个事件,关键的在于连接事件,将连接存放在连接池中,等客户端A发起发送信息的时候将客户端B的连接取出来发送数据,看看WebRTCMessageInboundPool这个类:
package org.rtc.websocket; import java.io.IOException; import java.nio.CharBuffer; import java.util.HashMap; import java.util.Map; public class WebRTCMessageInboundPool { private static final Map<String,WebRTCMessageInbound > connections = new HashMap<String,WebRTCMessageInbound>(); public static void addMessageInbound(WebRTCMessageInbound inbound){ //添加连接 System.out.println("user : " + inbound.getUser() + " join.."); connections.put(inbound.getUser(), inbound); } public static void removeMessageInbound(WebRTCMessageInbound inbound){ //移除连接 connections.remove(inbound.getUser()); } public static void sendMessage(String user,String message){ try { //向特定的用户发送数据 System.out.println("send message to user : " + user + " message content : " + message); WebRTCMessageInbound inbound = connections.get(user); if(inbound != null){ inbound.getWsOutbound().writeTextMessage(CharBuffer.wrap(message)); } } catch (IOException e) { e.printStackTrace(); } } }WebRTCMessageInboundPool这个类中最重要的是sendMessage方法,向特定的用户发送数据。
大家可以看看这段代码:
function openChannel() { console.log("Opening channel."); socket = new WebSocket( "ws://192.168.1.102:8080/RTCApp/websocket?u=${user}"); socket.onopen = onChannelOpened; socket.onmessage = onChannelMessage; socket.onclose = onChannelClosed; }${user}是怎么来的呢?其实在进入这个页面之前是有段处理的:
package org.rtc.servlet; import java.io.IOException; import java.util.UUID; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang.StringUtils; import org.rtc.room.WebRTCRoomManager; @WebServlet(urlPatterns = {"/room"}) public class WebRTCRoomServlet extends HttpServlet { private static final long serialVersionUID = 1L; public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String r = request.getParameter("r"); if(StringUtils.isEmpty(r)){ //如果房间为空,则生成一个新的房间号 r = String.valueOf(System.currentTimeMillis()); response.sendRedirect("room?r=" + r); }else{ Integer initiator = 1; String user = UUID.randomUUID().toString().replace("-", "");//生成一个用户ID串 if(!WebRTCRoomManager.haveUser(r)){//第一次进入可能是没有人的,所以就要等待连接,如果有人进入了带这个房间好的页面就会发起视频通话的连接 initiator = 0;//如果房间没有人则不发送连接的请求 } WebRTCRoomManager.addUser(r, user);//向房间中添加一个用户 String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort() + request.getContextPath() +"/"; String roomLink = basePath + "room?r=" + r; String roomKey = r;//设置一些变量 request.setAttribute("initiator", initiator); request.setAttribute("roomLink", roomLink); request.setAttribute("roomKey", roomKey); request.setAttribute("user", user); request.getRequestDispatcher("index.jsp").forward(request, response); } } }这个是进入房间前的处理,然而客户端是怎么发起视频通话的呢?
function initialize() { console.log("Initializing; room=${roomKey}."); card = document.getElementById("card"); localVideo = document.getElementById("localVideo"); miniVideo = document.getElementById("miniVideo"); remoteVideo = document.getElementById("remoteVideo"); resetStatus(); openChannel(); getUserMedia(); } function getUserMedia() { try { navigator.webkitGetUserMedia({ 'audio' : true, 'video' : true }, onUserMediaSuccess, onUserMediaError); console.log("Requested access to local media with new syntax."); } catch (e) { try { navigator.webkitGetUserMedia("video,audio", onUserMediaSuccess, onUserMediaError); console .log("Requested access to local media with old syntax."); } catch (e) { alert("webkitGetUserMedia() failed. Is the MediaStream flag enabled in about:flags?"); console.log("webkitGetUserMedia failed with exception: " + e.message); } } } function onUserMediaSuccess(stream) { console.log("User has granted access to local media."); var url = webkitURL.createObjectURL(stream); localVideo.style.opacity = 1; localVideo.src = url; localStream = stream; // Caller creates PeerConnection. if (initiator) maybeStart(); } function maybeStart() { if (!started && localStream && channelReady) { setStatus("Connecting..."); console.log("Creating PeerConnection."); createPeerConnection(); console.log("Adding local stream."); pc.addStream(localStream); started = true; // Caller initiates offer to peer. if (initiator) doCall(); } } function doCall() { console.log("Sending offer to peer."); if (isRTCPeerConnection) { pc.createOffer(setLocalAndSendMessage, null, mediaConstraints); } else { var offer = pc.createOffer(mediaConstraints); pc.setLocalDescription(pc.SDP_OFFER, offer); sendMessage({ type : 'offer', sdp : offer.toSdp() }); pc.startIce(); } } function setLocalAndSendMessage(sessionDescription) { pc.setLocalDescription(sessionDescription); sendMessage(sessionDescription); } function sendMessage(message) { var msgString = JSON.stringify(message); console.log('发出信息 : ' + msgString); path = 'message?r=${roomKey}' + '&u=${user}'; var xhr = new XMLHttpRequest(); xhr.open('POST', path, true); xhr.send(msgString); }页面加载完之后会调用initialize方法,initialize方法中调用了getUserMedia方法,这个方法是通过本地摄像头获取视频的方法,在成功获取视频之后发送连接请求,并在客户端建立连接管道,最后通过sendMessage向另外一个客户端发送连接的请求,参数为当前通话的房间号和当前登陆人,下图是连接产生的日志:
package org.rtc.servlet; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import javax.servlet.ServletException; import javax.servlet.ServletInputStream; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import net.sf.json.JSONObject; import org.rtc.room.WebRTCRoomManager; import org.rtc.websocket.WebRTCMessageInboundPool; @WebServlet(urlPatterns = {"/message"}) public class WebRTCMessageServlet extends HttpServlet { private static final long serialVersionUID = 1L; public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { super.doPost(request, response); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String r = request.getParameter("r");//房间号 String u = request.getParameter("u");//通话人 BufferedReader br = new BufferedReader(new InputStreamReader((ServletInputStream)request.getInputStream())); String line = null; StringBuilder sb = new StringBuilder(); while((line = br.readLine())!=null){ sb.append(line); //获取输入流,主要是视频定位的信息 } String message = sb.toString(); JSONObject json = JSONObject.fromObject(message); if (json != null) { String type = json.getString("type"); if ("bye".equals(type)) {//客户端退出视频聊天 System.out.println("user :" + u + " exit.."); WebRTCRoomManager.removeUser(r, u); } } String otherUser = WebRTCRoomManager.getOtherUser(r, u);//获取通话的对象 if (u.equals(otherUser)) { message = message.replace("\"offer\"", "\"answer\""); message = message.replace("a=crypto:0 AES_CM_128_HMAC_SHA1_32", "a=xrypto:0 AES_CM_128_HMAC_SHA1_32"); message = message.replace("a=ice-options:google-ice\\r\\n", ""); } //向对方发送连接数据 WebRTCMessageInboundPool.sendMessage(otherUser, message); } }就这样通过WebSokcet向客户端发送连接数据,然后客户端根据接收到的数据进行视频接收:
function onChannelMessage(message) { console.log('收到信息 : ' + message.data); if (isRTCPeerConnection) processSignalingMessage(message.data);//建立视频连接 else processSignalingMessage00(message.data); } function processSignalingMessage(message) { var msg = JSON.parse(message); if (msg.type === 'offer') { // Callee creates PeerConnection if (!initiator && !started) maybeStart(); // We only know JSEP version after createPeerConnection(). if (isRTCPeerConnection) pc.setRemoteDescription(new RTCSessionDescription(msg)); else pc.setRemoteDescription(pc.SDP_OFFER, new SessionDescription(msg.sdp)); doAnswer(); } else if (msg.type === 'answer' && started) { pc.setRemoteDescription(new RTCSessionDescription(msg)); } else if (msg.type === 'candidate' && started) { var candidate = new RTCIceCandidate({ sdpMLineIndex : msg.label, candidate : msg.candidate }); pc.addIceCandidate(candidate); } else if (msg.type === 'bye' && started) { onRemoteHangup(); } }就这样通过Java、WebSocket、WebRTC就实现了在浏览器上的视频通话。
请教
还有一个就自己的一个疑问,我定义的WebSocket失效时间是20秒,时间太短了。希望大家指教一下如何设置WebSocket的失效时间。
截图
演示地址
你可以和你的朋友一起进入http://blog.csdn.net/leecho571/article/details/8207102,感受下Ext结合WebSocket、WebRTC构建的即时通讯
建议大家将chrome升级至最新版本http://www.google.cn/intl/zh-CN/chrome/browser/eula.html?extra=devchannel&platform=win
源码下载
大家可以按照这种思路去自己实现,建议大家最好用Chrome浏览器进行测试。
大家可以进群:197331959进行交流。
相关推荐
第五次作业函数第一题--
本项目旨在利用深度学习方法实现作物病害的自动诊断。作物病害是农业生产中的重要问题,及时诊断和处理对于减少产量损失至关重要。 我们采用深度学习算法,通过分析作物的图像,实现对病害的自动识别和分类。项目使用的数据集包括公开的作物病害图像数据集,如ISIC等,并进行了预处理,包括图像增强、分割和特征提取等。 在运行环境方面,我们使用Python编程语言,基于TensorFlow、PyTorch等深度学习框架进行开发。为了提高计算效率,我们还使用了GPU加速计算。此外,我们还采用了Docker容器技术,确保实验结果的可重复性。 项目完成后,将实现对作物病害的快速、准确诊断,为农业生产提供有力支持,有助于减少产量损失。同时,项目成果也可应用于其他图像识别和分类任务。
机械设计CD驱动印刷设备step非常好的设计图纸100%好用.zip
python烟花代码
附件中是一个简单的烟花效果的代码示例: 在Python中,可以使用多种方式来模拟烟花效果,其中一种常用的方法是使用turtle模块,它提供了一个画布和一个小海龟,可以用来绘制各种图形。 这段代码首先导入了turtle模块和random模块,然后在屏幕上绘制了10次烟花爆炸的效果。每次爆炸都是由5个小圆组成,颜色随机选择,圆的大小也是随机的。 请注意,这段代码需要在支持turtle模块的Python环境中运行,并且需要有图形界面的支持。如果你在没有图形界面的环境中(比如某些服务器或者命令行界面),这段代码可能无法正常运行。
商业化产品经理,到底如何实现产品商业化?.docx
Panduit 工业以太网部件内部销售指南
在Java中,实现一个三维装箱(也称为三维背包问题)的算法通常涉及到组合优化和动态规划。这个问题是一个典型的优化问题,其中目标是在三个维度的限制下最大化价值的总和。下面是一个简单的Java代码示例,它使用动态规划来解决三维装箱问题。 请注意,这个代码只是一个简单的示例,它假设所有物品的第三个维度的大小都是1,并且没有给出如何回溯选择物品的完整逻辑。在实际应用中,三维装箱问题可能更加复杂,需要考虑所有三个维度的限制,并且可能需要更复杂的算法来解决。 此外,这个问题的解决方案可能需要根据具体问题的要求进行调整,例如物品是否可以分割、是否允许超过一个的物品等。如果你有特定的问题描述或者需要进一步的帮助,请提供更多的细节。
常用品牌EPLAN部件库
单片机开发的教程可以分为以下几个步骤: 1. 了解单片机基础知识:在学习单片机开发之前,需要了解单片机的相关知识,包括单片机的基本结构、指令系统、编程语言等。 2. 选择开发板:选择一款适合自己学习开发板的型号和厂商,通常需要关注开发板的性价比、开发环境是否友好等因素。 3. 学习开发环境:根据所选的开发板,学习相关的开发环境和使用方法,例如Keil、IAR等集成开发环境。 4. 掌握编程语言:单片机常用的编程语言包括C语言和汇编语言,根据实际情况选择其中一种进行学习。 5. 基础操作:熟悉单片机的引脚定义和IO口配置,了解单片机的启动代码,可以通过修改启动代码进行基本功能调试。 6. 综合实践:根据具体项目需求,进行单片机开发的综合实践。在实践中需要掌握如何编写程序、如何进行硬件调试、如何使用相关工具软件等技能。 下面是一个单片机开发的简单教程介绍: 首先,确定所使用的单片机型号和开发板类型。在这个阶段,需要查阅相关资料,了解开发板的规格书、芯片规格等基本资料。 其次,安装并配置开发环境。根据所选的开发板,安装相应的集成开发环境(IDE),并配置好开发环境。 接着,学习并掌
Q1.ipynb
(自适应手机端)IT网络建站公司pbootcms模板 互联网营销企业网站源码下载.zip
Bematech 激光扫描器用户手册
激励视频接入文档.pdf
java jdk1.8 202版本下载window linux打包
Lite Beam M5快速指南
互联网金融导论.docx
字节跳动青训营——抖音项目
Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。