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

原创 2015年07月08日 16:40:17

最近在学习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就可以了。官方文档比较推荐将其作为单独的应用启动,不过要麻烦一点儿。

参考资料:

另外,推荐一个学习网站:
版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

WebSocket连接wss链接

参考链接:http://www.juliankrone.com/connect-and-transfer-data-with-secure-websockets-in-android/ WebSock...

tomcat websocket连接

tomcat7(或8)中实现websocket连接,服务端建立有两种方式: 1. 注解@ServerEndpoint(value = " ")  2. 继承Endpoint 然而多数情况下是注解实现,...

WebSocket学习笔记一

WebSocket通信学习笔记 --JAVAEE规则简要学习   一:端点(节点)--Endpoint类 在使用WebSocket通信时的每一个点,都称之为端点,使用Endpoint类定义,该...
  • r_s_m
  • r_s_m
  • 2015年03月23日 11:22
  • 301

websocket后台自动推送消息

websocket后台自动推送消息目前网上实现的都是客户端与服务端双向通信的例子。例如聊天室。之前做项目遇到这样的问题:客户端仅仅负责接收服务器后台数据,不做发送,这样如何实现呢? 其实实现的方法很...

基于WebSocet简单聊天室(NodeJS + node-websocket-server)

基于WebSocet简单聊天室(NodeJS + node-websocket-server) 3人收藏此文章, 收藏此文章 发表于24天前 , 已有135次阅读 共2个评论 3人收藏此文章 ...

websocket使用

websocket,html5中新一代全双工通信协议。其底层仍然是http协议。 传统 HTTP 请求响应客户端服务器交互图 WebSocket 请求响应客户端服务器交互图 WebSoc...
  • mafan121
  • mafan121
  • 2016年04月05日 21:10
  • 13446

基于tomcat运行HTML5 WebSocket echo例子

一:概述作为HTML5新特性之一的WebSocket组件,在实时性有一定要求的WEB应用开发中还是有一定用武之地,高版本的IE、Chrome、FF浏览器都支持Websocket,标准的Websocke...

入门 websocket

websocket 的概念如下链接: 点击这里 Java Websocket 如果要使用 Java 搭建一个 websocket 服务的话,在 J2EE7 当中给出了 websock...

使用WebSocket实现即使通讯(实现一个群聊的聊天室)

随着互联网的发展,传统的HTTP协议已经很难满足Web应用日益复杂的需求了。近年来,随着HTML5的诞生,WebSocket协议被提出,它实现了浏览器与服务器的全双工通信,扩展了浏览器与服务端的通信功...
  • canot
  • canot
  • 2016年04月13日 09:53
  • 15596

java WebSocket实现简单的聊天室(包括群发和点对点聊天)

今天突然看到了WebSocket然后就网上找了一个例子,然后修改了下,实现了简单的聊天室,包括群聊和点对点聊天。...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:“服务器推”之websocket实现之简单聊天室
举报原因:
原因补充:

(最多只允许输入30个字)