“服务器推”之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 发送过来的实时数据

前端如何接收 websocket 发送过来的实时数据标签: websocket, jquery, 实时数据传输技术WebSocket protocol 是HTML5一种新的协议,它实现了浏览器与服务器...
  • github_37483541
  • github_37483541
  • 2017年02月09日 11:26
  • 10260

WebSocket原理及技术简介

WebSocket原理及技术简介 目录 1    引言    1 2    WebSocket技术及协议    2 2.1    WebSocket API    2 2.1.1    ...
  • yinqingwang
  • yinqingwang
  • 2016年09月17日 20:32
  • 15054

java WebSocket 简易聊天消息推送

环境:
  • hzw2312
  • hzw2312
  • 2014年11月18日 20:02
  • 45959

WebSocket 协议及服务端实现

WebSocket 笔记 协议理解及服务器端实现 Bottle,  May 24 2016        bottle@fridayws.com        前言: HTML 从1993年的HTML...
  • oTypedef
  • oTypedef
  • 2016年05月24日 18:10
  • 10099

Web应用从服务器主动推送数据到客户端有哪些方式?

通常情况下,打开网页或app去查询或者刷新时,客户端向服务器发出请求然后返回数据,客户端与服务端对应的模式是: 客户端请求--服务端响应, 而在有些情况下,服务端会主动推送一些信息到客户端,例如:新闻...
  • shuo1992
  • shuo1992
  • 2017年03月02日 08:33
  • 1815

Apache 配置 WebSocket 协议

本文使用 http proxy 方式 实现 apache 支持  WebSocket 请求(JK 使用的 ajp 协议不能支持websocket) 关于 http proxy 方式 请参考:http...
  • conquer0715
  • conquer0715
  • 2016年03月01日 19:15
  • 13590

在Apache上搭建pywebsocket提供html5的websocket服务

参考:http://www.travisglines.com/web-coding/how-to-set-up-apache-to-serve-html5-websocket-applications...
  • fm0517
  • fm0517
  • 2012年03月22日 10:26
  • 10568

实现websocket的两种方式

简单说明 1.两种方式,一种使用tomcat的websocket实现,一种使用spring的websocket 2.tomcat的方式需要tomcat 7.x,JEE7的支持。 3.spring...
  • zzhao114
  • zzhao114
  • 2017年03月03日 23:10
  • 4044

推荐2个开源聊天服务器

1,关于移动互联网快速开发移动互联网应用,参考开源版本,快速进行搭建。 这样非常高效,可以完成产品验证,为市场推广节省时间。 虽然现在微信,qq已经非常火了,但是做一款自带聊天的app,还是能够黏...
  • freewebsys
  • freewebsys
  • 2016年07月05日 09:35
  • 11044

tomcat websocket连接

tomcat7(或8)中实现websocket连接,服务端建立有两种方式: 1. 注解@ServerEndpoint(value = " ")  2. 继承Endpoint 然而多数情况下是注解实现,...
  • jwq201288888888
  • jwq201288888888
  • 2015年07月29日 16:37
  • 5577
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:“服务器推”之websocket实现之简单聊天室
举报原因:
原因补充:

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