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

最近在学习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>

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

参考资料:

另外,推荐一个学习网站:
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值