jetty8中已經(jīng)自帶有websocket功能,所以我們可以很方便搭建一個自己的websocket服務(wù)。
源程序:
http://sdrv.ms/N5BuKw啟動類:org.noahx.websocket.WebSocketServer
訪問地址:
http://127.0.0.1:8085/test.html1、外部依賴包如下(maven)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?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,寫一個服務(wù)器啟動類
WebSocketServer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
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服務(wù)端口我設(shè)置的為8085。通過向jetty server中加入WebSocketHandler就可以提供websocket服務(wù)了。由于WebSocketHandler是HandlerWrapper的子類,所以這個handler中還可以再加入一個ResourceHandler。這樣就可以在一個端口上同時提供websocket與http服務(wù)。ResourceHandler指向了類路徑org/noahx/websocket/http下,這個包下的所有資源都將可以發(fā)布為web資源給http請求。
3、開發(fā)MyWebSocketHandler與MyWebSocket
MyWebSocketHandler與MyWebSocket
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
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中實現(xiàn)doWebSocketConnect方法,返回我們需要的WebSocket對象。
doWebSocketConnect方法中可以取到請求的url,所以可以當(dāng)做分發(fā)器,通過url的不同分發(fā)到不同WebSocket。
如:ws://127.0.0.1:8085/ws1與ws://127.0.0.1:8085/ws2
MyWebSocket所實現(xiàn)的內(nèi)容是以每500毫秒的速度,向瀏覽器發(fā)送0-100的隨機數(shù)(注意這里是由服務(wù)器主動推送)。
MemTask
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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));
}
}
取隨機數(shù)的任務(wù)類4、開發(fā)前臺javascript客戶機(test.html)
使用到了以下內(nèi)容:
web-socket-js,
https://github.com/gimite/web-socket-js/就是這個解決了不支持html5的websocket的瀏覽器也可以調(diào)用websocket的問題。web-socket-js會自動判斷是不是支持html5的websocket,如果支持沒有什么區(qū)別。如果發(fā)現(xiàn)不支持將通過Flash自動調(diào)用websocket來做socket的中轉(zhuǎn)。
highcharts,
http://www.highcharts.com/這個是通過純javascript(jquery)來繪制圖表的js圖表框架。來配合websocket,做到實時的動態(tài)圖表。
test.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
<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建立與服務(wù)器的websocket連接,服務(wù)器發(fā)現(xiàn)連接后會主動推送數(shù)據(jù)給瀏覽器。
收到服務(wù)器推送過來的數(shù)據(jù)會觸發(fā)onmessage,這時在onmessage中對圖表進行繪制。從而達到效果。
*5、Flash Policy Server的配置
ie78可以使用websocket的關(guān)鍵。web-socket-js會調(diào)用flash請求websocket。
flash請求websocket也有一個前提。flash會檢查安全策略文件(xml)是否允許它這樣做。默認(rèn)flash會請求相同host的843端口,發(fā)送policy-file-request請求,這時返回正常的安全策略時,websocket才可以正常使用。當(dāng)然我們也可以通過WebSocket.loadFlashPolicyFile(方法重新指定策略文件url。樣例是指向了10843端口,因為如果監(jiān)聽<1000的端口需要root權(quán)限,所以內(nèi)嵌Java版的Flash Policy Server端口為10843。
但還是建議不要手動指定WebSocket.loadFlashPolicyFile,而是用843。因為flash總是先會從843找,如果找不到再從websocket端口找(8085)。所以如果設(shè)置其它url了會影響加載速度。
Flash Policy的資料可以參考:
http://www.adobe.com/devnet/flashplayer/articles/socket_policy_files.html里面提供了一個flashpolicyd_v0.6程序,可以做Flash Policy Server,但不是java實現(xiàn)的。
我也寫了一個Flash Policy Server,java版的
FlashPolicyServer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
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整合在一起。安全策略文件的樣例格式:
1
2
3
4
5
6
<?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也將有可能成為標(biāo)準(zhǔn)的api協(xié)議。
框架介紹:
AS3WebSocket,
https://github.com/Worlize/AS3WebSocket提供給ActiveScript3使用的websocket框架
jWebSocket,
http://jwebsocket.org/有服務(wù)器與客戶端,高一級別websocket框架,提供了對基礎(chǔ)websocket的擴展,如jsonSocket,xmlSocket,cvsSocket
kaazing(商業(yè)),
http://kaazing.com/商業(yè)級整套html5與websocket解決方案,效果最好,可惜是商業(yè)的