用jetty搭建websocket服務并與ie78兼容的方法

jetty8中已經自帶有websocket功能,所以我們可以很方便搭建一個自己的websocket服務。
啟動類:org.noahx.websocket.WebSocketServer
訪問地址:http://127.0.0.1:8085/test.html
1、外部依賴包如下(maven)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.noahx</groupId>
<artifactId>ws-test</artifactId>
<version>1.0</version>
<properties>
<jetty.version>8.1.5.v20120716</jetty.version>
<slf4j.version>1.6.1</slf4j.version>
</properties>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${jetty.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-websocket</artifactId>
<version>${jetty.version}</version>
</dependency>
</dependencies>
</project>
2、搭建的websocket server,寫一個服務器啟動類
WebSocketServer
package org.noahx.websocket;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ResourceHandler;
import org.eclipse.jetty.util.resource.FileResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
/**
* Created with IntelliJ IDEA.
* User: noah
* Date: 8/8/12
* Time: 5:10 PM
* To change this template use File | Settings | File Templates.
*/
public class WebSocketServer {
private Logger logger = LoggerFactory.getLogger(this.getClass());
private Server server;
private FlashPolicyServer fpServer;
private int port;
public static void main(String[] args) {
WebSocketServer server = new WebSocketServer(8085);
server.start();
}
public WebSocketServer(int port) {
this.port=port;
}
public void start(){
fpServer=new FlashPolicyServer(10843);
fpServer.start();
server = new Server(port);
MyWebSocketHandler myWebSocketHandler = new MyWebSocketHandler();
URL url=this.getClass().getClassLoader() .getResource("org/noahx/websocket/http");
ResourceHandler resourceHandler=new ResourceHandler();
try {
resourceHandler.setBaseResource(new FileResource(url));
} catch (IOException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
} catch (URISyntaxException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
myWebSocketHandler.setHandler(resourceHandler);
server.setHandler(myWebSocketHandler);
try {
server.start();
server.join();
} catch (Exception e) {
logger.error(e.getMessage(),e);
}
}
}
websocket服務端口我設置的為8085。通過向jetty server中加入WebSocketHandler就可以提供websocket服務了。由于WebSocketHandler是HandlerWrapper的子類,所以這個handler中還可以再加入一個ResourceHandler。這樣就可以在一個端口上同時提供websocket與http服務。ResourceHandler指向了類路徑org/noahx/websocket/http下,這個包下的所有資源都將可以發布為web資源給http請求。
3、開發MyWebSocketHandler與MyWebSocket
MyWebSocketHandler與MyWebSocket
package org.noahx.websocket;
import org.eclipse.jetty.websocket.WebSocket;
import org.eclipse.jetty.websocket.WebSocketHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Timer;
/**
* Created with IntelliJ IDEA.
* User: noah
* Date: 8/8/12
* Time: 5:16 PM
* To change this template use File | Settings | File Templates.
*/
public class MyWebSocketHandler extends WebSocketHandler {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) {
if (logger.isDebugEnabled()) {
logger.debug("url=" + request.getRequestURL() + ",protocol=" + protocol);
}
return new MyWebSocket();
}
public class MyWebSocket implements WebSocket.OnTextMessage {
private Logger logger = LoggerFactory.getLogger(this.getClass());
private Connection connection;
private Timer timer = new Timer();
@Override
public void onMessage(String data) {
if (logger.isDebugEnabled()) {
logger.debug("onMessage");
}
}
@Override
public void onOpen(Connection connection) {
if (logger.isDebugEnabled()) {
logger.debug("onOpen");
}
this.connection = connection;
timer.schedule(new MemTask(this), 0, 500);
}
@Override
public void onClose(int closeCode, String message) {
if (logger.isDebugEnabled()) {
logger.debug("onClose");
}
timer.cancel();
}
public void send(String msg) {
try {
if (logger.isDebugEnabled()) {
logger.debug("send:" + msg);
}
connection.sendMessage(msg);
} catch (IOException e) {
logger.error(e.getMessage(), e);
timer.cancel();
}
}
}
}
doWebSocketConnect中實現doWebSocketConnect方法,返回我們需要的WebSocket對象。 doWebSocketConnect方法中可以取到請求的url,所以可以當做分發器,通過url的不同分發到不同WebSocket。
如:ws://127.0.0.1:8085/ws1與ws://127.0.0.1:8085/ws2
MyWebSocket所實現的內容是以每500毫秒的速度,向瀏覽器發送0-100的隨機數(注意這里是由服務器主動推送)。
MemTask
package org.noahx.websocket;
import java.util.Random;
import java.util.TimerTask;
/**
* Created with IntelliJ IDEA.
* User: noah
* Date: 8/8/12
* Time: 5:31 PM
* To change this template use File | Settings | File Templates.
*/
public class MemTask extends TimerTask {
private MyWebSocketHandler.MyWebSocket myWebSocket;
public MemTask(MyWebSocketHandler.MyWebSocket myWebSocket) {
this.myWebSocket = myWebSocket;
}
@Override
public void run() {
myWebSocket.send("" +new Random().nextInt(100));
}
}
取隨機數的任務類
4、開發前臺javascript客戶機(test.html)
使用到了以下內容:
web-socket-js,https://github.com/gimite/web-socket-js/
就是這個解決了不支持html5的websocket的瀏覽器也可以調用websocket的問題。web-socket-js會自動判斷是不是支持html5的websocket,如果支持沒有什么區別。如果發現不支持將通過Flash自動調用websocket來做socket的中轉。
highcharts,http://www.highcharts.com/
這個是通過純javascript(jquery)來繪制圖表的js圖表框架。來配合websocket,做到實時的動態圖表。
test.html
<html>
<head>
<title></title>
<script type="text/javascript" src="swfobject.js"></script>
<script type="text/javascript" src="web_socket.js"></script>
<script type="text/javascript" src="jquery-1.7.2.min.js"></script>
<script src="hc/highcharts.js"></script>
<script src="hc/modules/exporting.js"></script>
</head>
<body>
<script type="text/javascript">
var host = window.location.host.split(":")[0];
WEB_SOCKET_SWF_LOCATION = "WebSocketMain.swf";
WEB_SOCKET_DEBUG = false;
try {
WebSocket.loadFlashPolicyFile("xmlsocket://" + host + ":10843");
} catch (e) {
}
var ws;
function init(series) {
ws = new WebSocket("ws://" + host + ":8085/");
ws.onopen = function () {
output("onOpen");
};
ws.onmessage = function (e) {
var dStr = e.data;
outputmem(dStr);
var x = (new Date()).getTime(), // current time
y = parseInt(dStr);
series.addPoint([x, y], true, true);
};
ws.onclose = function () {
output("onClose");
};
ws.onerror = function () {
output("onError");
};
}
function outputmem(str) {
var mem = document.getElementById("mem");
mem.innerHTML = str;
}
function output(str) {
var log = document.getElementById("log");
var escaped = str.replace(/&/, "&").replace(/</, "<").
replace(/>/, ">").replace(/"/, """); // "
log.innerHTML = escaped + "<br>" + log.innerHTML;
}
$(function () {
$(document).ready(function () {
Highcharts.setOptions({
global:{
useUTC:false
}
});
var chart;
chart = new Highcharts.Chart({
chart:{
renderTo:'container',
type:'spline',
marginRight:10,
events:{
load:function () {
// set up the updating of the chart each second
var series = this.series[0];
init(series);
}
}
},
title:{
text:'WebSocket random data'
},
xAxis:{
type:'datetime',
tickPixelInterval:150
},
yAxis:{
title:{
text:'Value'
},
plotLines:[
{
value:0,
width:1,
color:'#808080'
}
]
},
tooltip:{
formatter:function () {
return '<b>' + this.series.name + '</b><br/>' +
Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', this.x) + '<br/>' +
Highcharts.numberFormat(this.y, 2);
}
},
legend:{
enabled:false
},
exporting:{
enabled:false
},
series:[
{
name:'Zero data',
data:(function () {
var data = [],
time = (new Date()).getTime(),
i;
for (i = -19; i <= 0; i++) {
data.push({
x:time + i * 1000,
y:0
});
}
return data;
})()
}
]
});
});
});
</script>
<div id="mem"></div>
<div id="log"></div>
<div id="container" style="min-width: 400px; height: 400px; margin: 0 auto"></div>
</body>
</html>
原理是通過web-socket-js建立與服務器的websocket連接,服務器發現連接后會主動推送數據給瀏覽器。 收到服務器推送過來的數據會觸發onmessage,這時在onmessage中對圖表進行繪制。從而達到效果。
*5、Flash Policy Server的配置
ie78可以使用websocket的關鍵。web-socket-js會調用flash請求websocket。
flash請求websocket也有一個前提。flash會檢查安全策略文件(xml)是否允許它這樣做。默認flash會請求相同host的843端口,發送policy-file-request請求,這時返回正常的安全策略時,websocket才可以正常使用。當然我們也可以通過WebSocket.loadFlashPolicyFile(方法重新指定策略文件url。樣例是指向了10843端口,因為如果監聽<1000的端口需要root權限,所以內嵌Java版的Flash Policy Server端口為10843。
但還是建議不要手動指定WebSocket.loadFlashPolicyFile,而是用843。因為flash總是先會從843找,如果找不到再從websocket端口找(8085)。所以如果設置其它url了會影響加載速度。
Flash Policy的資料可以參考:http://www.adobe.com/devnet/flashplayer/articles/socket_policy_files.html
里面提供了一個flashpolicyd_v0.6程序,可以做Flash Policy Server,但不是java實現的。
我也寫了一個Flash Policy Server,java版的
FlashPolicyServer
package org.noahx.websocket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
/**
* Created with IntelliJ IDEA.
* User: noah
* Date: 8/8/12
* Time: 10:05 PM
* To change this template use File | Settings | File Templates.
*/
public class FlashPolicyServer {
private ServerSocket serverSocket;
private static Thread serverThread;
private int port;
private static boolean listening = true;
private Logger logger = LoggerFactory.getLogger(this.getClass());
public FlashPolicyServer() {
this(843);
}
public FlashPolicyServer(int port) {
this.port = port;
}
public void start() {
try {
serverThread = new Thread(new Runnable() {
public void run() {
try {
logger.info("FlashPolicyServer: Starting...");
serverSocket = new ServerSocket(port);
while (listening) {
final Socket socket = serverSocket.accept();
Thread t = new Thread(new Runnable() {
public void run() {
try {
if (logger.isDebugEnabled()) {
logger.debug("FlashPolicyServer: Handling Request...");
}
socket.setSoTimeout(10000);
InputStream in = socket.getInputStream();
byte[] buffer = new byte[23];
if (in.read(buffer) != -1 && (new String(buffer, "ISO-8859-1")).startsWith("<policy-file-request/>")) {
if (logger.isDebugEnabled()) {
logger.debug("PolicyServerServlet: Serving Policy File...");
}
OutputStream out = socket.getOutputStream();
byte[] bytes = ("<?xml version=\"1.0\"?>\n" +
"<!DOCTYPE cross-domain-policy SYSTEM \"/xml/dtds/cross-domain-policy.dtd\">\n" +
"<cross-domain-policy> \n" +
" <site-control permitted-cross-domain-policies=\"master-only\"/>\n" +
" <allow-access-from domain=\"*\" to-ports=\"*\" />\n" +
"</cross-domain-policy>").getBytes("ISO-8859-1");
out.write(bytes);
out.write(0x00);
out.flush();
out.close();
} else {
logger.warn("FlashPolicyServer: Ignoring Invalid Request");
logger.warn(" " + (new String(buffer)));
}
} catch (SocketException e) {
logger.error(e.getMessage(), e);
} catch (IOException e) {
logger.error(e.getMessage(), e);
} finally {
try {
socket.close();
} catch (Exception ex2) {
}
}
}
});
t.start();
}
} catch (IOException ex) {
logger.error("PolicyServerServlet Error---");
logger.error(ex.getMessage(), ex);
}
}
});
serverThread.start();
} catch (Exception ex) {
logger.error("PolicyServerServlet Error---");
logger.error(ex.getMessage(), ex);
}
}
public void stop() {
logger.info("FlashPolicyServer: Shutting Down...");
if (listening) {
listening = false;
}
if (!serverSocket.isClosed()) {
try {
serverSocket.close();
} catch (Exception ex) {
}
}
}
}
這樣就可以和websocket server整合在一起。
安全策略文件的樣例格式:
<?xml version="1.0"?> <!DOCTYPE cross-domain-policy SYSTEM "/xml/dtds/cross-domain-policy.dtd"> <cross-domain-policy> <site-control permitted-cross-domain-policies="master-only"/> <allow-access-from domain="*" to-ports="*" /> </cross-domain-policy>
6、websocket展望
繼ajax后websocket會給界面帶來更快更好的交互效果與體驗,伴隨移動市場的壯大websocket也將有可能成為標準的api協議。
框架介紹:
AS3WebSocket,https://github.com/Worlize/AS3WebSocket
提供給ActiveScript3使用的websocket框架
jWebSocket,http://jwebsocket.org/
有服務器與客戶端,高一級別websocket框架,提供了對基礎websocket的擴展,如jsonSocket,xmlSocket,cvsSocket
kaazing(商業),http://kaazing.com/
商業級整套html5與websocket解決方案,效果最好,可惜是商業的