本例子是为了实现在websocket通信,由有台来推送消息来改变前台的一些状态信息,下来就来介绍一个小例子,请大家多多指教!
在html5的规范中,websocket是一种很受欢迎的技术,作为下一代客服端与服务器异步通信方法,该方法取代了单个的TCP套接字,使用ws或wss协议,可用于任意的客户端和服务器程序。 WebSocket目前由W3C进行标准化。WebSocket已经受到Firefox 4、Chrome 4、Opera 10.70以及Safari 5等浏览器的支持。
轮询和 WebSocket 实现方式的网络负载对比图如下,凸显出websocket的巨大优势:
下面说说websocket的整个流程吧!
WebSocket需要通过握手连接,类似于TCP它也需要客户端和服务器端进行握手连接,连接成功后才能相互通信。 以下就是一个握手时序图:
WebSocket握手的过程:当Web应用程序调用new WebSocket(url)接口时,Browser就开始了与地址为url的WebServer建立握手连接的过程。两种情况为:1. Browser与WebSocket服务器通过TCP三次握手建立连接,如果这个建立连接失败,那么后面的过程就不会执行,Web应用程序将收到错误消息通知。2.在TCP建立连接成功后,Browser通过http协议传送WebSocket支持的版本号,协议的字版本号,原始地址,主机地址等等一些列字段给服务器端。
当Browser收到服务器回复的数据包后,如果数据包内容、格式都没有问题的话,就表示本次连接成功,触发onopen消息,此时Web开发者就可以在 此时通过send接口想服务器发送数据。否则,握手连接失败,Web应用程序会收到onerror消息,并且能知道连接失败的原因。 本实例实在Jetty7.5.1+Ext4.2.1上实现的目的是想Ext中使用websocket 推送后台消息,而不用浏览器去不停得请求,前台部分js代码为:
//用于展示来自后台的信息
Ext.define('MessageContainer', {
extend : 'Ext.view.View',
trackOver : true,
multiSelect : false,
itemCls : 'l-im-message',
itemSelector : 'div.l-im-message',
overItemCls : 'l-im-message-over',
selectedItemCls : 'l-im-message-selected',
style : {
overflow : 'auto',
backgroundColor : '#fff'
},
tpl : [
'<div class="l-im-message-warn">以下提示信息来自后台推送。</div>',
'<tpl for=".">',
'<div class="l-im-message">',
'<div class="l-im-message-header l-im-message-header-{source}">{from} ({timestamp}) :{content}</div>',
'</tpl>'],
messages : [],
initComponent : function() {
var me = this;
me.messageModel = Ext.define('Leetop.im.MessageModel', {
extend : 'Ext.data.Model',
fields : ['from', 'timestamp', 'content', 'source']
});
me.store = Ext.create('Ext.data.Store', {
model : 'Leetop.im.MessageModel',
data : me.messages
});
me.callParent();
},
//将服务器推送的信息展示到页面中
receive : function(message) {
var me = this;
message['timestamp'] = Ext.Date.format(new Date(message['timestamp']),
'H:i:s');
me.store.add(message);
if (me.el.dom) {
me.el.dom.scrollTop = me.el.dom.scrollHeight;
}
}
});
Ext.onReady(function() {
//创建消息展示容器
var output = Ext.create('MessageContainer', {
region : 'center'
});
Ext.cre
//展示窗口
var tip = Ext.create('Ext.window.Window', {
title : '后台消息提示',
layout : 'border',
iconCls : 'user-win',
width : 300,
animateTarget : 'websocket_button',
height : 200,
border : false,
html:'此为展示模块',
//bodyStyle: 'background:#ffc; padding:10px;',
y : 300,
x : 1100
});
tip.show();
var websocket;
//初始话WebSocket
function initWebSocket() {
if (window.WebSocket) {
websocket = new WebSocket(encodeURI('ws://localhost:8080/Chat/message'));
websocket.onopen = function() {
//连接成功
win.setTitle(title + ' (已连接后台)');
}
websocket.onerror = function() {
//连接失败
win.setTitle(title + ' (连接发生错误)');
}
websocket.onclose = function() {
//连接断开
win.setTitle(title + ' (已经断开连接)');
}
//消息接收
websocket.onmessage = function(message) {
var message = JSON.parse(message.data);
//接收用户发送的消息
if (message.type == 'message') {
output.receive(message);
} else if(message.type == 'add_height'){
tip.setHeight(300);
}else if(message.type == 'add_width'){
tip.setWidth(400);
}else if(message.type == 'alter_title'){
tip.setTitle('<font color="#ff0">笑话一则</font>');
}else if(message.type == 'alter_content'){
tip.update('<p>有一天,小明在房间里读古文,爸爸进来了问:小明,你在读什么书啊?' +
'小明:古文,爸爸很生气的说:你再说一遍?小明大声的喊了一句:古文,' +
'后来,小明莫名其妙的就被爸爸打了一顿。。。</p>');
}else if(message.type == 'hide_window'){
tip.setVisible(false);
}else if(message.type == 'show_window'){
tip.setVisible(true);
}else if(message.type == 'change_bg'){
tip.setBodyStyle('background:#ffc; padding:10px;');
}else if(message.type == 'change_icon'){
tip.setIconCls('user-online');
}
}
}
};
var title = 'Extjs+websocket测试';
//信息窗口
var win = Ext.create('Ext.window.Window', {
title : title+ ' (已连接后台)',
layout : 'border',
iconCls : 'user-win',
minWidth : 550,
minHeight : 360,
width : 550,
animateTarget : 'websocket_button',
height : 360,
items : [output],
border : false,
y : 300,
x : 400,
listeners : {
render : function() {
initWebSocket();
}
}
});
win.show();
});
代码中通过new websocket 得到链接触发onpen()提示前台已近取得连接,当连接断开或链接失败时,分别执行onclose(),onerror()给予提示,其中最重要的函数为onmessage(),由他来接收后台发送的消息,然后根据消息的type值来做出相应的反应,本例中就有多个type值,来达到修改Ext中不同属性的目的。其中ext代码为给出。
后台通过一个继承WebSocketServlet类的一个servlet类来处理ws或wss的请求,此类中实现一个doWebSocketConnect()方法,该方法返回一个Websocket对象,代码如下:
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.servlet.http.HttpServletRequest;
import org.eclipse.jetty.websocket.WebSocketServlet;
public class WebSocket extends WebSocketServlet{
/**
*
*/
private static final long serialVersionUID = 1L;
public static int ONLINE_USER_COUNT = 1;
public org.eclipse.jetty.websocket.WebSocket doWebSocketConnect(
HttpServletRequest arg0, String arg1) {
// TODO Auto-generated method stub
System.out.println("do websocketconnetion");
return new OnText();
}
}
接下来我需要新建一个实现了OnTextMessage的类OnText,在这个类中必须实现三个方法,它们是onClose(),onPen(),onMessage(),用处与前台对应,值得注意的是,在onPen()函数中我们拿到了connction对象,代码如下:
package net.haige;
import java.io.IOException;
import java.util.Date;
import net.sf.json.JSONObject;
import net.sf.json.util.NewBeanInstanceStrategy;
import org.eclipse.jetty.websocket.WebSocket.OnTextMessage;
public class OnText implements OnTextMessage ,Runnable {
public Connection conn;
public JSONObject result;
public static String CONSLE = "控制台";
public static String TYPE_1 = "message";
public static String TYPE_2 = "add_height";
public static String TYPE_3 = "add_width";
public static String TYPE_4 = "alter_title";
public static String TYPE_5 = "alter_content";
public static String TYPE_6 = "hide_window";
public static String TYPE_7 = "show_window";
public static String TYPE_8 = "change_bg";
public static String TYPE_9 = "change_icon";
public OnText(){
System.out.println("OnText");
};
public void onClose(int arg0, String arg1) {
// TODO Auto-generated method stub
System.out.println("onclose");
}
//链接事件触发
public void onOpen(Connection conn) {
// TODO Auto-generated method stub
System.out.println("onOpen");
this.conn = conn;
//向用户发送连线消息
/* JSONObject result = new JSONObject();
result.element("type", "message");
result.element("from", "控制台");
result.element("timestamp", new Date().getTime());
result.element("content", "");*/
String content = "已经通过websocket与后台取得了连接";
initMessage(CONSLE, TYPE_1, content);
sendMessage(result.toString());
String protocal = conn.getProtocol();
System.out.println("连接的端口号:"+conn.toString());
new Thread(this).start();
}
/*
* 本实例不会使用到来自客服端的消息
*/
public void onMessage(String message) {
// TODO Auto-generated method stub
System.out.println("onMessage");
}
//发送消息函数
public void sendMessage(String info){
try {
this.conn.sendMessage(info);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void run() {
// TODO Auto-generated method stub
try {
System.out.println("run......");
//增加高度
String content = "3秒钟后后台推送消息增加窗口高度(+100)";
initMessage(CONSLE, TYPE_1, content);
sendMessage(result.toString());
Thread.sleep(3000);
initMessage(CONSLE, TYPE_2, "");
sendMessage(result.toString());
content = "高度增加完成!";
initMessage(CONSLE, TYPE_1, content);
sendMessage(result.toString());
//增加宽度
Thread.sleep(2000);
content = "3秒钟后后台推送消息增加窗口宽度(+100)";
initMessage(CONSLE, TYPE_1, content);
sendMessage(result.toString());
Thread.sleep(3000);
initMessage(CONSLE, TYPE_3, content);
sendMessage(result.toString());
content = "宽度增加完成!";
initMessage(CONSLE, TYPE_1, content);
sendMessage(result.toString());
//更改标题
Thread.sleep(2000);
content = "3秒钟后后台推送消息修改窗口标题";
initMessage(CONSLE, TYPE_1, content);
sendMessage(result.toString());
Thread.sleep(3000);
initMessage(CONSLE, TYPE_4, content);
sendMessage(result.toString());
content = "标题修改完成!";
initMessage(CONSLE, TYPE_1, content);
sendMessage(result.toString());
//更改内容
Thread.sleep(2000);
content = "3秒钟后后台推送消息修改窗口内容";
initMessage(CONSLE, TYPE_1, content);
sendMessage(result.toString());
Thread.sleep(3000);
initMessage(CONSLE, TYPE_5, content);
sendMessage(result.toString());
content = "内容修改完成!";
initMessage(CONSLE, TYPE_1, content);
sendMessage(result.toString());
//更改内容
Thread.sleep(2000);
content = "3秒钟后后台推送消息窗口消失";
initMessage(CONSLE, TYPE_1, content);
sendMessage(result.toString());
Thread.sleep(3000);
initMessage(CONSLE, TYPE_6, content);
sendMessage(result.toString());
content = "窗口消失!";
initMessage(CONSLE, TYPE_1, content);
sendMessage(result.toString());
//更改内容
Thread.sleep(2000);
content = "3秒钟后后台推送消息窗口再现";
initMessage(CONSLE, TYPE_1, content);
sendMessage(result.toString());
Thread.sleep(3000);
initMessage(CONSLE, TYPE_7, content);
sendMessage(result.toString());
content = "窗口再现!";
initMessage(CONSLE, TYPE_1, content);
sendMessage(result.toString());
//更改背景
Thread.sleep(2000);
content = "3秒钟后后台推送消息窗口背景更改";
initMessage(CONSLE, TYPE_1, content);
sendMessage(result.toString());
Thread.sleep(3000);
initMessage(CONSLE, TYPE_8, content);
sendMessage(result.toString());
content = "窗口背景修改!";
initMessage(CONSLE, TYPE_1, content);
sendMessage(result.toString());
//更改图标
Thread.sleep(2000);
content = "3秒钟后后台推送消息窗口标题图标";
initMessage(CONSLE, TYPE_1, content);
sendMessage(result.toString());
Thread.sleep(3000);
initMessage(CONSLE, TYPE_9, content);
sendMessage(result.toString());
content = "窗口标题图标已修改!";
initMessage(CONSLE, TYPE_1, content);
sendMessage(result.toString());
//结束
System.out.println("end......");
content = "websocket消息推送在Extjs中使用事例结束";
initMessage(CONSLE, TYPE_1, content);
sendMessage(result.toString());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void initMessage(String from, String type,String content){
result = new JSONObject();
result.element("type", type);
result.element("from", from);
result.element("timestamp", new Date().getTime());
result.element("content", content);
}
}
最后展示例子运行效果截图如下: