“服务器推”之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 connections = new CopyOnWriteArraySet();

	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 connections = new CopyOnWriteArraySet();

	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 {

		@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> getAnnotatedEndpointClasses(Set> set) {
		//不作过滤了,直接返回set(其实里面就一个:WebsocketServerWithAnnotation)
		return set;		
	}

	
	/**
	 * 扫描部署文件中继承Endpoint的class(可以写上自己的过滤代码),由容器调用
	 * 参数是Endpoint的子类集合,返回的是ServerEndpoint的集合,所以我们要在其中作处理的
	 * 
	 */
	@Override
	public Set getEndpointConfigs(Set> set) {
		
		Set result = new HashSet();
		
		//只写了这么一个,指定其访问path:/wsInheritEndpoint就Build它就OK了
		if(set.contains(WebsocketServerInheritEndpoint.class)){
			result.add(ServerEndpointConfig.Builder.create(WebsocketServerInheritEndpoint.class, "/wsInheritEndpoint").build());
		}
		return result;
	}

}

四、HTMLclient端



websocket test: chat




	


这种方式使用的是容器自动扫描,并不需要在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

你可能感兴趣的:(Java_服务器推,websocket,Server-push,chat,JSR356,websocket1.1)