#websocket实现方式
##原理
websocket的原理主要是,利用websocket提供的api,客户端只需要向服务器发起一次连接即可,然后服务器就可以主动地源源不断地向客户端发送数据,只要客户端不关闭浏览器,那么这个连接就会一直保持,从而达到真正意义上的长连接和服务器推。
优点:只需要建立一次连接,服务器就可以源源不断地推送数据,资源消耗少,持久连接
缺点:需要浏览器支持websocket技术
websocket的过程如图:
可以看到,只要客户端不关闭浏览器,连接会一直存在,相对于ajax轮询,这里服务器从被动变为主动,只要服务器喜欢,随时都可以向客户端发送数据。
##实例
websocket最经典的实例就是聊天室了,而且tomcat就自带了websocket聊天室的实例,需要导入websocket-api.jar,这个jar包也可以在tomcat的lib下找到,下面是服务器代码:
package com.myj.websocket.servlet;
import java.io.IOException;
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;
//import org.apache.juli.logging.Log;
//import org.apache.juli.logging.LogFactory;
import util.HTMLFilter;
//"/websocket/chat"为客户端发起连接的请求路径
@ServerEndpoint(value = "/websocket/chat")
public class ChatAnnotation {
//private static final Log log = LogFactory.getLog(ChatAnnotation.class);
private static final String GUEST_PREFIX = "Guest";
private static final AtomicInteger connectionIds = new AtomicInteger(0);
private static final Set<ChatAnnotation> connections =
new CopyOnWriteArraySet<ChatAnnotation>();
private final String nickname;
private Session session;
public ChatAnnotation() {
//每个客户端都有一个昵称
nickname = GUEST_PREFIX + connectionIds.getAndIncrement();
}
/**
* 客户端发起连接请求,服务器这边会调用这个start方法,
* 将该客户端的session和连接保存,然后向所有客户端广播消息
* @param session
*/
@OnOpen
public void start(Session session) {
this.session = session;
connections.add(this);
String message = String.format("* %s %s", nickname, "has joined.");
broadcast(message);
}
/**
* 如果有客户端关闭浏览器,连接就会断开,服务器会调用end方法,
* 将保存在connections集合中的相应客户端连接移除,向剩余的客户端广播该
* 客户端下线的消息
*/
@OnClose
public void end() {
connections.remove(this);
String message = String.format("* %s %s",
nickname, "has disconnected.");
broadcast(message);
}
/**
* 客户端调用send向服务器发送消息的时候,服务器这边会调用incoming方法,
* 将该客户端发送的消息向所有客户端进行广播
* @param message
*/
@OnMessage
public void incoming(String message) {
// Never trust the client
String filteredMessage = String.format("%s: %s",
nickname, HTMLFilter.filter(message.toString()));
broadcast(filteredMessage);
}
/**
* 出现异常时候会调用onError
* @param t
* @throws Throwable
*/
@OnError
public void onError(Throwable t) throws Throwable {
//log.error("Chat Error: " + t.toString(), t);
}
/**
* 向所有客户端广播消息的方法
* @param msg
*/
public static void broadcast(String msg) {
//遍历所有的客户端
for (ChatAnnotation client : connections) {
try {
synchronized (client) {
//向客户端推送消息
client.session.getBasicRemote().sendText(msg);
}
} catch (IOException e) {
//如果广播的时候出现异常会将该客户端移除,并向剩余的客户端广播消息
//log.debug("Chat Error: Failed to send message to client", e);
connections.remove(client);
try {
client.session.close();
} catch (IOException e1) {
// Ignore
}
String message = String.format("* %s %s",
client.nickname, "has been disconnected.");
broadcast(message);
}
}
}
}
下面是客户端的代码:
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<title>Apache Tomcat WebSocket Examples: Chattitle>
<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="application/javascript">
var Chat = {};
Chat.socket = null;
//根据浏览器创建相应的websocket实例,并向服务器发起连接
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;
}
//客户端websocket和服务器建立连接之后会调用onopen方法
Chat.socket.onopen = function () {
Console.log('Info: WebSocket connection opened.');
document.getElementById('chat').onkeydown = function(event) {
if (event.keyCode == 13) {
Chat.sendMessage();
}
};
};
//监听服务器是否关闭,服务器关闭之后会调用onclose方法
Chat.socket.onclose = function () {
document.getElementById('chat').onkeydown = null;
Console.log('Info: WebSocket closed.');
};
//监听服务器消息,服务器向客户端推送消息的时候,websocket会调用onmessage 方法
Chat.socket.onmessage = function (message) {
Console.log(message.data);
};
});
//传入url,创建websocket并和服务器建立连接
Chat.initialize = function() {
if (window.location.protocol == 'http:') {
Chat.connect('ws://' + window.location.host + '/WebSocketDemo/websocket/cha t');
} else {
Chat.connect('wss://' + window.location.host + '/WebSocketDemo/websocket/ch at');
}
};
//发送消息
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;
});
Chat.initialize();
document.addEventListener("DOMContentLoaded", function() {
// Remove elements with "noscript" class -
var noscripts = document.getElementsByClassName("noscript");
for (var i = 0; i < noscripts.length; i++) {
noscripts[i].parentNode.removeChild(noscripts[i]);
}
}, false);
script>
head>
<body>
<div class="noscript"><h2 style="color: #ff0000">Seems your browser doesn't support Jav ascript! Websockets rely on Javascript being enabled. Please enable
Javascript and reload this page!h2>div>
<div>
<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>
如果要利用websocket按照前面实现消息提醒的话,可以在后来创建一个监听器和定时器,在应用启动的时候就初始化定时器,定时在后台检测数据有没有发生变化,有变化之后随时调用websocket的方法,将数据推送给客户端。
下面是监听器和定时器的代码
监听器
package util;
import java.util.Calendar;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
/**
* 系统启动时的监听类 初始化系统数据
*
*/
public class InitListener implements ServletContextListener {
public void contextDestroyed(ServletContextEvent arg0) {
// TODO Auto-generated method stub
// context销毁时,销毁初始化数据
}
public void contextInitialized(ServletContextEvent event) {
// TODO Auto-generated method stub
try {
System.out.println("初始化监听...");
goTimer();
System.out.println("初始化完毕");
} catch (Exception e) {
System.out.println("失败:" + e.getMessage());
}
}
private void goTimer() {
Timer timmerTask = new Timer();
Calendar calEnviron = Calendar.getInstance();
// 每天的02:00.am开始执行
calEnviron.set(Calendar.HOUR_OF_DAY, 17);
calEnviron.set(Calendar.MINUTE, 01);
calEnviron.set(Calendar.SECOND, 00);
// date为制定时间
Date dateSetter = new Date();
dateSetter = calEnviron.getTime();
// nowDate为当前时间
Date nowDateSetter = new Date();
// 所得时间差为,距现在待触发时间的间隔
long intervalEnviron = dateSetter.getTime() - nowDateSetter.getTime();
if (intervalEnviron < 0) {
calEnviron.add(Calendar.DAY_OF_MONTH, 1);
dateSetter = calEnviron.getTime();
intervalEnviron = dateSetter.getTime() - nowDateSetter.getTime();
}
//每5秒执行一次
timmerTask.schedule(new UseTimer(), intervalEnviron, 5 * 1000);
}
}
定时器
package util;
import java.util.Timer;
import java.util.TimerTask;
import com.myj.websocket.servlet.ChatAnnotation;
/**
* 被调用执行类
*
*/
public class UseTimer extends TimerTask {
private int num = 0;
Timer timer = new Timer();
public UseTimer(){
}
public UseTimer(Timer timer) {
this.timer = timer;
}
public Timer getTimer() {
return timer;
}
public void setTimer(Timer timer) {
this.timer = timer;
}
/*
* 被调用具体的方法
*/
public void run() {
//模拟数据发生变化
num++;
//调用websocket的接口推送数据
ChatAnnotation.broadcast("你有"+num+"条消息!");
}
}