最近在学习Server-push的一些技术,websocket当然也要简单学习一下。
一个简单的websocket实现聊天室的例子:
websocket在tomcat中只有tomcat7支持,tomcat7以下的是没实现这个功能,而tomcat7以上的则是将其remove了,tomcat团队只是对version6中的一个bug作修复,不再继续开发,原因是被JSR356 websocket1.1的实现给代替了。
不过服务端使用JSR356的实现来开发websocket 服务端很方便,浏览器端使用websocket javascript api来编写也很简单方便,不足之处是浏览器对javascript websocket的支持还没那么广泛(Firefox39,chrome38和360都可以,ie8不行,其中360虽然是IE内核,但是它有自己的使用模块)。
IE不支持的解决方案:利用Flash实现websocket的通信功能,其实现请可以参照这位网友的代码:
websocket javascript api:websocket javascript api
一、websocket Server端基于annotation的实现
package org.wz.jsrapi.websocket.server;
import java.io.IOException;
import java.util.Calendar;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
/**
*
* @author wz
* 基于annotation的实现
*/
@ServerEndpoint(value = "/wsWithAnnotation")
public class WebsocketServerWithAnnotation {
private static final Calendar cl = Calendar.getInstance();
private static final String NICK_PREFIX = "user";
private static final AtomicInteger connectionIds = new AtomicInteger(0);
private static final Set<WebsocketServerWithAnnotation> connections = new CopyOnWriteArraySet<WebsocketServerWithAnnotation>();
private final String nickname;
private Session session;
public WebsocketServerWithAnnotation() {
nickname = NICK_PREFIX + connectionIds.getAndIncrement();
}
@OnOpen
public void open(Session session) {
this.session = session;
String message = String.format("%1$tF %1$tT %2$s ", cl, this.nickname + "joined!");
connections.add(this);
broadcast(message);
}
@OnMessage
public void handleMessage(String message) {
String newmessage = String.format("%1$tF %1$tT %2$s %3$s", cl, this.nickname, message);
broadcast(newmessage);
}
@OnClose
public void end() {
connections.remove(this);
String message = String.format("%1$tF %1$tT %2$s %3$s", cl, this.nickname, "has disconnected.");
broadcast(message);
}
@OnError
public void onError(Session session,Throwable t) throws Throwable {
System.err.println("chat error " + t.toString());
}
public static void broadcast(String message) {
for (WebsocketServerWithAnnotation ws : connections) {
try {
synchronized (ws) {
ws.session.getBasicRemote().sendText(message);
}
} catch (IOException e) {
try {
ws.session.close();
} catch (IOException e1) {
}
connections.remove(ws);
broadcast(String.format("%1$tF %1$tT %2$s %3$s", cl, ws.nickname, "has been disconnected."));
}
}
}
}
二、基于继承Endpoint的实现
package org.wz.jsrapi.websocket.server;
import java.io.IOException;
import java.util.Calendar;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;
import javax.websocket.CloseReason;
import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig;
import javax.websocket.MessageHandler;
import javax.websocket.Session;
/**
*
* @author wz
* 基于继承Endpoint的实现
*/
public class WebsocketServerInheritEndpoint extends Endpoint {
private static final Calendar cl = Calendar.getInstance();
private static final String NICK_PREFIX = "user";
private static final AtomicInteger connectionIds = new AtomicInteger(0);
private static final Set<WebsocketServerInheritEndpoint> connections = new CopyOnWriteArraySet<WebsocketServerInheritEndpoint>();
private final String nickname;
private Session session;
public WebsocketServerInheritEndpoint() {
nickname = NICK_PREFIX + connectionIds.getAndIncrement();
}
//没有onmessage方法,在建立连接后由MessageHandler处理
public void onOpen(Session session, EndpointConfig config) {
this.session = session;
String message = String.format("%1$tF %1$tT %2$s ", cl, this.nickname + " joined!");
connections.add(this);
session.addMessageHandler(new ChatMessageHandler());
broadcast(message);
}
//移到ChatMessageHandler中去
// public void handleMessage(String message) {
// String newmessage = String.format("%1$tF %1$tT %2$s %3$s", cl, this.nickname, message);
// broadcast(newmessage);
// }
@Override
public void onClose(Session session1, CloseReason closereason) {
connections.remove(this);
String message = String.format("%1$tF %1$tT %2$s %3$s", cl, this.nickname, "has disconnected.");
broadcast(message);
}
@Override
public void onError(Session session, Throwable t) {
System.err.println("chat error " + t.toString());
}
public static void broadcast(String message) {
for (WebsocketServerInheritEndpoint ws : connections) {
try {
synchronized (ws) {
ws.session.getBasicRemote().sendText(message);
}
} catch (IOException e) {
try {
ws.session.close();
} catch (IOException e1) {
}
connections.remove(ws);
broadcast(String.format("%1$tF %1$tT %2$s %3$s", cl, ws.nickname, "has been disconnected."));
}
}
}
private class ChatMessageHandler implements MessageHandler.Whole<String> {
@Override
public void onMessage(String message) {
String formatMessage = String.format("%1$tF %1$tT %2$s %3$s", cl, nickname, message);
broadcast(formatMessage);
}
}
}
三、部署配置类
package org.wz.jsrapi.websocket.config;
import java.util.HashSet;
import java.util.Set;
import javax.websocket.Endpoint;
import javax.websocket.server.ServerApplicationConfig;
import javax.websocket.server.ServerEndpointConfig;
import org.wz.jsrapi.websocket.server.WebsocketServerInheritEndpoint;
/**
*
* @author wz
*
* 将websocket应用部署在web应用里的配置:
* 1.要实现ServerApplicationConfig接口
* 2.使用web容器的扫描机制[在servlet3.0中定义的]来扫描websocket的实现类
* 3.一个容器中可以有多个ServerApplicationConfig
*/
public class WebsocketDeployConfig implements ServerApplicationConfig{
/**
* 扫描部署文件被加以@ServerEndPoint的class(可以写上自己的过滤代码),由容器调用
*
*/
@Override
public Set<Class<?>> getAnnotatedEndpointClasses(Set<Class<?>> set) {
//不作过滤了,直接返回set(其实里面就一个:WebsocketServerWithAnnotation)
return set;
}
/**
* 扫描部署文件中继承Endpoint的class(可以写上自己的过滤代码),由容器调用
* 参数是Endpoint的子类集合,返回的是ServerEndpoint的集合,所以我们要在其中作处理的
*
*/
@Override
public Set<ServerEndpointConfig> getEndpointConfigs(Set<Class<? extends Endpoint>> set) {
Set<ServerEndpointConfig> result = new HashSet<ServerEndpointConfig>();
//只写了这么一个,指定其访问path:/wsInheritEndpoint就Build它就OK了
if(set.contains(WebsocketServerInheritEndpoint.class)){
result.add(ServerEndpointConfig.Builder.create(WebsocketServerInheritEndpoint.class, "/wsInheritEndpoint").build());
}
return result;
}
}
四、HTMLclient端
<html>
<head>
<title>websocket test: chat</title>
<style type="text/css">
input#chat {
width: 410px
}
#console-container {
width: 400px;
}
#console {
border: 1px solid #CCCCCC;
border-right-color: #999999;
border-bottom-color: #999999;
height: 170px;
overflow-y: scroll;
padding: 5px;
width: 100%;
}
#console p {
padding: 0;
margin: 0;
}
</style>
<script type="text/javascript">
var Chat = {};
Chat.socket = null;
Chat.connect = (function(host) {
if ('WebSocket' in window) {
Chat.socket = new WebSocket(host);
} else if ('MozWebSocket' in window) {
Chat.socket = new MozWebSocket(host);
} else {
Console.log('Error: WebSocket is not supported by this browser.');
return;
}
Chat.socket.onopen = function () {
Console.log('Info: WebSocket connection opened.');
document.getElementById('chat').onkeydown = function(event) {
if (event.keyCode == 13) {
Chat.sendMessage();
}
};
};
Chat.socket.onclose = function () {
document.getElementById('chat').onkeydown = null;
Console.log('Info: WebSocket closed.');
};
Chat.socket.onmessage = function (message) {
Console.log(message.data);
};
Chat.socket.onerror = function (){
Chat.socket.close(1000);
Console.log('Info: WebSocket happened error!.');
};
});
Chat.initialize = function() {
if (window.location.protocol == 'http:') {
Chat.connect('ws://' + window.location.host + '/websocket_jsr356/wsInheritEndpoint');
//两种服务端实现方式切换
//Chat.connect('ws://' + window.location.host + '/websocket_jdk/wsWithAnnotation');
} else {
Chat.connect('wss://' + window.location.host + '/websocket_jsr356/wsInheritEndpoint');
//两种服务端实现方式切换
//Chat.connect('wss://' + window.location.host + '/websocket_jdk/wsWithAnotation');
}
};
//在直接关闭页面在chrome下会触发error事件,unload的时候调用websocket.close()显示关闭即可。
Chat.close = (function(){
Chat.socket.close(1000); //正常关闭,code=1000
Console.log('Info: WebSocket closed!');
});
Chat.sendMessage = (function() {
var message = document.getElementById('chat').value;
if (message != '') {
Chat.socket.send(message);
document.getElementById('chat').value = '';
}
});
var Console = {};
Console.log = (function(message) {
var console = document.getElementById('console');
var p = document.createElement('p');
p.style.wordWrap = 'break-word';
p.innerHTML = message;
console.appendChild(p);
while (console.childNodes.length > 25) {
console.removeChild(console.firstChild);
}
console.scrollTop = console.scrollHeight;
});
if (window.attachEvent){
//其实这儿暂时没用,因为IE根本不支持基于HTML5的websocket
window.attachEvent("onload", Chat.initialize);
window.attachEvent("onunload", Chat.close);
}else{
window.addEventListener("load", Chat.initialize, false);
window.addEventListener("unload", Chat.close, false);
}
</script>
</head>
<body>
<p>
<input type="text" placeholder="type and press enter to chat"
id="chat" />
</p>
<div id="console-container">
<div id="console" />
</div>
</div>
</body>
</html>
参考资料:
另外,推荐一个学习网站: