目标:使用java的websocket作为datachannl建立连接
让jslinux可以实现两个浏览器数据互通。
效果图
原理:
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