让jslinux可以实现两个浏览器数据互通。
效果图
![](http://dl2.iteye.com/upload/attachment/0112/3544/604c9a5c-2b27-3b87-80b4-9728d42eec23.jpg)
原理:
jslinux已经实现了/dev/clipboard与浏览器的textarea的联通,
利用datachannel把两个浏览器的textarea交互,实现jslinux之间的数据交互
增加room概念,进入同一个room的两个页面才可以互相通信
要求tomcat7.0.62
java代码,java的websocket使给datachannel建立连接.
package com.hao; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.util.HashMap; import java.util.Map; import javax.servlet.http.HttpServletRequest; import org.apache.catalina.websocket.MessageInbound; import org.apache.catalina.websocket.StreamInbound; import org.apache.catalina.websocket.WebSocketServlet; import org.apache.catalina.websocket.WsOutbound; public class RoomWebSocketServlet extends WebSocketServlet { public static Map<String, MyMessageInbound> slist = new HashMap<String, MyMessageInbound>(); protected StreamInbound createWebSocketInbound(String subProtocol, HttpServletRequest request) { System.out.println("room:"+request.getParameter("r")); String arg="0"; if(null!=request.getParameter("r")){ arg=request.getParameter("r"); } return new MyMessageInbound(arg); } public int getUserCount() { return slist.size(); } private class MyMessageInbound extends MessageInbound { WsOutbound myoutbound; String mykey; String room; public MyMessageInbound(String room){ this.room=room; } @Override public void onOpen(WsOutbound outbound) { try { System.out.println("websocket Client open."); this.myoutbound = outbound; mykey = "" + System.currentTimeMillis(); slist.put(room+"::"+mykey, this); System.out.println("slist size:" + slist.size()); } catch (Exception e) { e.printStackTrace(); } } @Override public void onClose(int status) { System.out.println("websocket Close."+mykey); slist.remove(mykey); } @Override protected void onTextMessage(CharBuffer message) throws IOException { System.out.println("onTextMessage-------->"); for (Map.Entry<String, MyMessageInbound> entry : slist.entrySet()) { String socketroom=entry.getKey().split("::")[0]; String socketkey=entry.getKey().split("::")[1]; if(this.room.equals(socketroom)&&!this.mykey.equals(socketkey)){//向room相同但是不是当前websocket发送消息 System.out.println("-------->" + message.toString()); System.out.println("--->socketroom:"+socketroom+",socketkey:"+socketkey); MyMessageInbound mmib = (MyMessageInbound) entry.getValue(); CharBuffer buffer = CharBuffer.wrap(message); mmib.myoutbound.writeTextMessage(buffer); mmib.myoutbound.flush(); } } } @Override protected void onBinaryMessage(ByteBuffer arg0) throws IOException { } } }
web.xml中:
配置websocket的servlet
<servlet> <servlet-name>room</servlet-name> <servlet-class>com.hao.RoomWebSocketServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>room</servlet-name> <url-pattern>/hao/room</url-pattern> </servlet-mapping>
jslinux中的index修改
加入datachannel和<textarea>的交互
index.html为
<!DOCTYPE html> <html> <head> <title>Javascript PC Emulator</title> <style> .term { font-family: courier,fixed,swiss,monospace,sans-serif; font-size: 14px; color: #f0f0f0; background: #000000; } .termReverse { color: #000000; background: #00ff00; } #note { font-size: 12px; } #copyright { font-size: 10px; } #clipboard { font-size: 12px; } </style> </head> <body > <input type="button" value="start linux" onclick="start();"> <table border="0"> <tr valign="top"><td> <script type="text/javascript" src="utils.js"></script> <script type="text/javascript" src="term.js"></script> <script type="text/javascript" src="cpux86.js"></script> <script type="text/javascript" src="jslinux.js"></script> <div id="copyright">© 2011 Fabrice Bellard - <a href="news.html">News</a> - <a href="faq.html">FAQ</a> - <a href="tech.html">Technical notes</a></div> <input type="button" value="Clear clipboard" onclick="clear_clipboard();"><br><textarea row="4" cols="16" id="text_clipboard" ></textarea></br> </table> <script type="text/javascript" > function GetRequest() { var url = location.search; //获取url中"?"符后的字串 var theRequest = new Object(); if (url.indexOf("?") != -1) { var str = url.substr(1); strs = str.split("&"); for(var i = 0; i < strs.length; i ++) { theRequest[strs[i].split("=")[0]]=unescape(strs[i].split("=")[1]); } } return theRequest; } var Request = new Object(); Request = GetRequest(); var iceServer = null; var pc = new window.webkitRTCPeerConnection(iceServer,{optional: [{RtpDataChannels: true}]}); var isCaller = Request['c'];//window.location.href.split('#')[1]; console.log("isCaller--------"+isCaller); var socket = new WebSocket("ws://killinux.com/hao/room?r="+Request['r']); var thissend = function (message, callback) { this.waitForConnection(function () { socket.send(message); if (typeof callback !== 'undefined') { callback(); } }, 1000); }; this.waitForConnection = function (callback, interval) { if (socket.readyState === 1) { callback(); } else { var that = this; // optional: implement backoff for interval here setTimeout(function () { that.waitForConnection(callback, interval); }, interval); } }; socket.onmessage = function(event){ var json = JSON.parse(event.data); //console.log('onmessage: ', json); //如果是一个ICE的候选,则将其加入到PeerConnection中,否则设定对方的session描述为传递过来的描述 if( json.event === "_ice_candidate" ){ pc.addIceCandidate(new RTCIceCandidate(json.data.candidate)); } else { pc.setRemoteDescription(new RTCSessionDescription(json.data.sdp)); console.log("---------------->pc.setRemote"); // 如果是一个offer,那么需要回复一个answer if(json.event === "_offer") { console.log("------->createAnswer"); pc.createAnswer(function(desc){ pc.setLocalDescription(desc); console.log("---------------->pc.setLocal"); thissend(JSON.stringify({ "event": "_answer", "data": { "sdp": desc } })); }, function (error) { console.log('Failure callback: ' + error); }); }else{ console.log("------->receive Answer---('"+json.event+"')"); } } }; try { sendChannel = pc.createDataChannel('sendDataChannel',{reliable: true}); } catch (e) { alert('createDataChannel() failed with exception: ' + e.message); } sendChannel.onopen = console.log('--Send channel open state is : ' +sendChannel.readyState); sendChannel.onclose = console.log('--Send channel close state is: ' +sendChannel.readyState); // 发送ICE候选到其他客户端 pc.onicecandidate = function(event){ console.log("onicecandidate----------->"); if (event.candidate !== null) { console.log("event.candidate != null"); thissend(JSON.stringify({ "event": "_ice_candidate", "data": { "candidate": event.candidate } })); }else{ console.log("event.candidate == null"); } }; sendChannel.onmessage = function(event) { console.log("-sendChannel.onmessage--"); document.getElementById('text_clipboard').value = event.data; }; function sendData() { var data = document.getElementById('text_clipboard').value; console.log("---->>>>sendData():"+data); sendChannel.send(data); } if(isCaller){ console.log("------->createOffer"); pc.createOffer(function(desc){ // console.log(desc); pc.setLocalDescription(desc); console.log("---------------->pc.setLocal"); thissend(JSON.stringify({ "event": "_offer", "data": { "sdp": desc } })); }, function (error) { console.log('Failure callback: ' + error); }); } console.log("---over"); </script> <!--button id="sendButton" onclick="sendData()">Send</button> <textarea id="dataChannelSend" >abc</textarea> <div id="result"></div--> </body> </html>
jslinux.js增加datachannel的senddata操作
jslinux.js修改为:
/* Linux launcher Copyright (c) 2011-2012 Fabrice Bellard Redistribution or commercial use is prohibited without the author's permission. */ "use strict"; var term, pc, boot_start_time, init_state; function term_start() { term = new Term(80, 130, term_handler); term.open(); } /* send chars to the serial port */ function term_handler(str) { pc.serial.send_chars(str); } console.log("hao:version 1.2") function clipboard_set(val) { console.log("hao:clipboard_set---sendData->"); var el; el = document.getElementById("text_clipboard"); el.value = val; sendData(); } function clipboard_get() { console.log("hao:clipboard_get---->"); var el; el = document.getElementById("text_clipboard"); return el.value; } function clear_clipboard() { var el; el = document.getElementById("text_clipboard"); el.value = ""; } /* just used to display the boot time in the VM */ function get_boot_time() { return (+new Date()) - boot_start_time; } function start() { var params; init_state = new Object(); params = new Object(); /* serial output chars */ params.serial_write = term.write.bind(term); /* memory size (in bytes) */ params.mem_size = 16 * 1024 * 1024; /* clipboard I/O */ params.clipboard_get = clipboard_get; params.clipboard_set = clipboard_set; params.get_boot_time = get_boot_time; /* IDE drive. The raw disk image is split into files of * 'block_size' KB. */ params.hda = { url: "bin/hda%d.bin", block_size: 64, nb_blocks: 912 }; pc = new PCEmulator(params); init_state.params = params; //pc.load_binary("vmlinux-2.6.20.bin", 0x00100000, start2); pc.load_binary("vmlinux26.bin", 0x00100000, start2); // pc.load_binary("vmlinuxtest.bin", 0x00100000, start2); //pc.load_binary("vmlinuxnono.bin", 0x00100000, start2); //pc.load_binary("vmlinux319.bin", 0x00100000, start2); //pc.load_binary("vmlinux319clip.bin", 0x00100000, start2); } function start2(ret) { if (ret < 0) return; init_state.start_addr = 0x10000; //pc.load_binary("linuxstartnew.bin", init_state.start_addr, start3); pc.load_binary("linuxstart.bin", init_state.start_addr, start3); } function start3(ret) { var block_list; if (ret < 0) return; /* Preload blocks so that the boot time does not depend on the * time to load the required disk data (optional) */ block_list = [ 0, 7, 3, 643, 720, 256, 336, 644, 781, 387, 464, 475, 131, 589, 468, 472, 474, 776, 777, 778, 779, 465, 466, 473, 467, 469, 470, 512, 592, 471, 691, 697, 708, 792, 775, 769 ]; pc.ide0.drives[0].bs.preload(block_list, start4); } function start4(ret) { var cmdline_addr; if (ret < 0) return; /* set the Linux kernel command line */ cmdline_addr = 0xf800; pc.cpu.write_string(cmdline_addr, "console=ttyS0 root=/dev/hda ro init=/sbin/init notsc=1 hdb=none"); pc.cpu.eip = init_state.start_addr; pc.cpu.regs[0] = init_state.params.mem_size; /* eax */ pc.cpu.regs[3] = 0; /* ebx = initrd_size (no longer used) */ pc.cpu.regs[1] = cmdline_addr; /* ecx */ boot_start_time = (+new Date()); pc.start(); } term_start();
其中websocket放在aws上会有
Uncaught InvalidStateError: Failed to execute 'send' on 'WebSocket': Still in CONNECTING State.
的错误
参考
http://stackoverflow.com/questions/23898477/tornado-websockets-invalidstateerror-still-in-connecting-state
测试的时候
http://killinux.com/jslinux/?r=5555
http://killinux.com/jslinux/?r=5555&c=true
打开两个网页,r为room号,任意起,两个页面相同即可
然后在一个页面的jslinux中
tail -f /dev/clipboard
echo sssssssssss >/dev/clipboard
具体代码
https://github.com/killinux/rtclinux
微博: http://weibo.com/killinux