“服务器推”之websocket实现之简单聊天室

来源:互联网 发布:淘宝网店卖的钱在哪里 编辑:程序博客网 时间:2024/05/16 13:44

最近在学习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();}@OnOpenpublic 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);}@OnMessagepublic void handleMessage(String message) {String newmessage = String.format("%1$tF %1$tT %2$s  %3$s", cl, this.nickname, message);broadcast(newmessage);}@OnClosepublic void end() {connections.remove(this);String message = String.format("%1$tF %1$tT %2$s %3$s", cl, this.nickname, "has disconnected.");broadcast(message);}@OnErrorpublic 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);//}@Overridepublic 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);}@Overridepublic 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> {@Overridepublic 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(可以写上自己的过滤代码),由容器调用 *  */@Overridepublic Set<Class<?>> getAnnotatedEndpointClasses(Set<Class<?>> set) {//不作过滤了,直接返回set(其实里面就一个:WebsocketServerWithAnnotation)return set;}/** * 扫描部署文件中继承Endpoint的class(可以写上自己的过滤代码),由容器调用 * 参数是Endpoint的子类集合,返回的是ServerEndpoint的集合,所以我们要在其中作处理的 *  */@Overridepublic 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>

这种方式使用的是容器自动扫描,并不需要在web.xml中配置servlet,直接启动tomcat就可以了。官方文档比较推荐将其作为单独的应用启动,不过要麻烦一点儿。

参考资料:
http://tomcat.apache.org/tomcat-7.0-doc/websocketapi/index.html
https://jcp.org/aboutJava/communityprocess/final/jsr356/index.html
https://jcp.org/en/egc/view?id=356

另外,推荐一个学习网站:
http://ajaxpatterns.org/IFrame_Call

0 0
原创粉丝点击