在新建一个聊天室之前我们首先要了解什么是websock以及websocket api的一些常用知识
一、什么是WebSocket ?
在HTML5规范中,我最喜欢的Web技术就是正迅速变得流行的WebSocket API。WebSocket提供了一个受欢迎的技术,以替代我们过去几年一直在用的Ajax技术。这个新的API提供了一个方法,从客户端使用简单的语法有效地推动消息到服务器。
一、什么是WebSocket API?
WebSocket API是下一代客户端-服务器的异步通信方法。该通信取代了单个的TCP套接字,使用ws或wss协议,可用于任意的客户端和服务器程序。WebSocket目前由W3C进行标准化。WebSocket已经受到Firefox 4、Chrome 4、Opera 10.70以及Safari 5等浏览器的支持。
WebSocket API最伟大之处在于服务器和客户端可以在给定的时间范围内的任意时刻,相互推送信息。WebSocket并不限于以Ajax(或XHR)方式通信,因为Ajax技术需要客户端发起请求,而WebSocket服务器和客户端可以彼此相互推送信息;XHR受到域的限制,而WebSocket允许跨域通信。
Ajax技术很聪明的一点是没有设计要使用的方式。WebSocket为指定目标创建,用于双向推送消息。
WebSocket api 常用的事件:
open
message
error
close
下面是一个比较常见的js websocket例子:
var websocket = null;
//判断当前浏览器是否支持WebSocket
if('WebSocket' in window){
websocket = new WebSocket("ws://localhost/ChatRoom/websocket/"+"${roomid}");
}
else{
alert('Not support websocket')
}
//连接发生错误的回调方法
websocket.onerror = function(){
setMessageInnerHTML("error");
};
//连接成功建立的回调方法
websocket.onopen = function(event){
setMessageInnerHTML("open");
}
//接收到消息的回调方法
websocket.onmessage = function(event){
setMessageInnerHTML(event.data);
}
//连接关闭的回调方法
websocket.onclose = function(){
setMessageInnerHTML("close");
}
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function(){
websocket.close();
}
//将消息显示在网页上
function setMessageInnerHTML(obj){
var data = JSON.parse(obj);
var msg = data.message;
var name = data.name;
var message =
'<article class="row">'+
'<div class="col-lg-8">'+
'<div class="panel panel-default">'+
'<div class="panel-heading">'+name+'</div>'+
'<div class="panel-body">'+msg+
'</div>'+
'</div>'+
'</div>'+
'</article>';
document.getElementById('chat-room-container').innerHTML += message;
}
//关闭连接
function closeWebSocket(){
websocket.close();
}
//发送消息
function send(){
var text = document.getElementById('message').value;
var name = 'Annal';
var json = {'name':name,'message':text};
var message = JSON.stringify(json);
websocket.send(message);
}
代码部分已经解释的够详细了,接下来我们就以java 的tomcat为例,来做一个聊天室吧
在tomcat 7点多版本中,已经为我们的websocket实现好了接口的方法,我们只需要调用即可,如下所示:
package com.cmh.main;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.servlet.http.HttpSession;
import javax.websocket.EncodeException;
import javax.websocket.EndpointConfig;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import net.sf.json.JSONObject;
import com.cmh.beans.Message;
import com.cmh.cfg.GetHttpSessionConfigurator;
import com.cmh.encoding.MessageEncoding;
@ServerEndpoint(value="/websocket/{roomid}", encoders = { MessageEncoding.class },configurator = GetHttpSessionConfigurator.class)
public class ServerSocket {
private static HashMap<String,ServerSocket> webSocketSet = new HashMap<String,ServerSocket>();
private Session session;
private static HttpSession httpSession;
private static HashMap<String, Object> userInfo = new HashMap<String, Object>();
private Object userlock = new Object();
private Lock lock1 = new ReentrantLock();
private String roomid = "0";
@SuppressWarnings("rawtypes")
@OnMessage
public void onMessage(@PathParam("roomid")String roomid,String message){
lock1.lock();
try {
Iterator iter = webSocketSet.entrySet().iterator();
String id = session.getId();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
String key = entry.getKey().toString();
ServerSocket item = (ServerSocket)entry.getValue();
Message msg = jsonToMessage(message);
item.sendMessage3(roomid,id,msg);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
lock1.unlock();
}
/*for(ServerSocket item: webSocketSet){
try {
Message msg = jsonToMessage(message);
item.sendObject(msg);
//item.sendMessage(message);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
continue;
}
}*/
}
@OnOpen
public void onOpen(@PathParam("roomid")String roomid,Session session,EndpointConfig config){
synchronized (userlock) {
System.out.println(roomid);
setRoomid(roomid);
this.session = session;
System.out.println(session.getId());
httpSession = (HttpSession) config.getUserProperties().get(
HttpSession.class.getName());
System.out.println(httpSession.getAttribute("name"));
webSocketSet.put(session.getId(),this);
userInfo.put(session.getId(), httpSession);
}
}
public String getRoomid() {
return roomid;
}
public void setRoomid(String roomid) {
this.roomid = roomid;
}
@SuppressWarnings("static-access")
public void setHttpSession(String sessionid){
this.httpSession =(HttpSession) userInfo.get(sessionid);
}
@OnClose
public void onClose(){
webSocketSet.remove(this); //从set中删除
}
@OnError
public void onError(Session session, Throwable error){
System.out.println("发生错误");
error.printStackTrace();
}
public void sendMessage(String message) throws IOException{
this.session.getBasicRemote().sendText(message);
}
public void sendMessage3(String roomid,String sessionid,Message message) throws IOException{
try {
if(this.getRoomid().equals(roomid)){
setHttpSession(sessionid);
message.setName(httpSession.getAttribute("name").toString());
this.session.getBasicRemote().sendObject(message);
}
} catch (EncodeException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@SuppressWarnings("static-access")
public Message jsonToMessage(String jsonStr){
JSONObject obj = new JSONObject().fromObject(jsonStr);
Message ms = (Message)JSONObject.toBean(obj,Message.class);
return ms;
}
public String messageToJson(Message mssage){
JSONObject json = JSONObject.fromObject(mssage);//将java对象转换为json对象
String str = json.toString();//将json对象转换为字符串
return str;
}
}
在我们这个类中,同样也有几个方法,分别和websocket对应起来。
值得注意的是@ServerEndpoint(value=”/websocket/{roomid}”, encoders = { MessageEncoding.class },configurator = GetHttpSessionConfigurator.class)这里面的参数的含义,第一个参数表示的是我们的解码比方说sendobject这个方法,需要将对象转化为json字符串,这里就需要进行配置解码类,如下:
package com.cmh.encoding;
import javax.websocket.EncodeException;
import javax.websocket.Encoder;
import javax.websocket.EndpointConfig;
import com.cmh.beans.Message;
import com.cmh.util.JsonUtil;
public class MessageEncoding implements Encoder.Text<Message> {
@Override
public void destroy() {
// TODO Auto-generated method stub
}
@Override
public void init(EndpointConfig arg0) {
// TODO Auto-generated method stub
}
@Override
public String encode(Message mssage) throws EncodeException {
// TODO Auto-generated method stub
return JsonUtil.messageToJson(mssage);
}
}
以上这个类的解码是基于json-lib的
我们再来看看
configurator = GetHttpSessionConfigurator.class表示的是什么意思。我们知道,websocket中所维护的session和httpsession 两者是没有什么卵关系的,那么问题来了,当一个用户进来时,我们如何获取这个用户的名字呢,显然有两种方法,一种是通过@PathParam注入来获取,另一种是通过httpsession来获取,这里我都有使用,目的是了解其用法,我们通过配置GetHttpSessionConfigurator这个类来获取httpsession中的数据,如下:
package com.cmh.cfg;
import javax.servlet.http.HttpSession;
import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;
public class GetHttpSessionConfigurator extends
ServerEndpointConfig.Configurator {
@Override
public void modifyHandshake(ServerEndpointConfig config,
HandshakeRequest request, HandshakeResponse response) {
// TODO Auto-generated method stub
HttpSession httpSession = (HttpSession) request.getHttpSession();
// ActionContext.getContext().getSession()
config.getUserProperties().put(HttpSession.class.getName(), httpSession);
}
}
整个流程就差不多是这样,我们来看看运行的效果
上面的图是同一个房间内的两个不同用户之间的对话
下载资源