网页版的即时聊天如果用ajax轮询,在线人数少无所谓,一旦在线人数特别多,服务器压力就很大,速度会特别慢,而且ajax的定时刷新也不是真正意义上的即时聊天,是由网页刷新时间决定的
那么,websocket就是一个特别好的选择
首先前端代码:
<!DOCTYPE html>
<html>
<head>
<title>websocket example</title>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=8,9,10" />
<style type="text/css">
#connect-container {
float: left;
width: 400px
}
#connect-container div {
padding: 5px;
}
#console-container {
float: left;
margin-left: 15px;
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 src="http://cdn.sockjs.org/sockjs-0.3.min.js"></script> -->
<script type="text/javascript" src="/sockjs-0.3.4.min.js"></script>
<script type="text/javascript">
var ws = null;//这个id其实是区分聊天室的,聊天室一的消息不应该在聊天室二里出现
var id = (window.location.href).charAt((window.location.href).length-1);
var url = 'ws://xx.xx.com/base/websocket?roomId='+id;
var transports = [];
var userId = 0;
function setConnected(connected) {
document.getElementById('connect').disabled = connected;
document.getElementById('disconnect').disabled = !connected;
document.getElementById('echo').disabled = !connected;
}
function connect() {
alert("url:"+url);
if (!url) {
alert('Select whether to use W3C WebSocket or SockJS');
return;
}
ws = new WebSocket(url); //申请一个WebSocket对象
//连接socket方法
ws.onopen = function () {
setConnected(true);
log('Info: connection opened.');
};//发送消息方法
ws.onmessage = function (event) {
//var msgJson=eval("("+event.data+")");
if (JSON.parse(event.data)["fromId"]){
userId = JSON.parse(event.data)["fromId"];
}
log('发送人: '+userId+' \n ' +JSON.parse(event.data)["content"]);
};//关闭socket方法
ws.onclose = function (event) {
setConnected(false);
log('Info: connection closed.');
log(event);
};
}
function disconnect() {
if (ws != null) {
ws.close();
ws = null;
}
setConnected(false);
}
function echo() {
if (ws != null) {
var message = document.getElementById('message').value;
log('SentUser:我');
var msg = '"content":"'+ message + '"';
ws.send(message);
} else {
alert('connection not established, please connect.');
}
}
function updateUrl(urlPath) {
if (window.location.protocol == 'http:') {
url = 'ws://' + window.location.host + urlPath;
} else {
url = 'wss://' + window.location.host + urlPath;
}
}
function log(message) {
var console = document.getElementById('console');
var p = document.createElement('p');
p.style.wordWrap = 'break-word';
p.appendChild(document.createTextNode(message));
console.appendChild(p);
while (console.childNodes.length > 25) {
console.removeChild(console.firstChild);
}
console.scrollTop = console.scrollHeight;
}
</script>
</head>
<script type="text/javascript">
</script>
<body>
<div>
<div id="connect-container">
<input id="radio1" type="radio" name="group1" οnclick="updateUrl('/base/websocket');">
<label for="radio1">W3C WebSocket</label>
<div>
<button id="connect" οnclick="connect();">Connect</button>
<button id="disconnect" disabled="disabled" οnclick="disconnect();">Disconnect</button>
</div>
<div>
<textarea id="message" style="width: 350px">Here is a message!</textarea>
</div>
<div>
<button id="echo" οnclick="echo();" disabled="disabled">Echo message</button>
</div>
</div>
<div id="console-container">
<div id="console"></div>
</div>
</div>
</body>
</html>
后台代码:
pom文件:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-messaging</artifactId>
<version>${org.springframework.version}</version>
</dependency><!--这个包千万不要引,会有冲突,报握手失败的错误-->
<!-- <dependency>
<groupId>javax.websocket</groupId>
<artifactId>javax.websocket-api</artifactId>
<version>1.1</version>
</dependency> -->
socketConfig类,配置访问连接,不过要在spring配置文件中扫描这个类,启动tomcat会加载它
package com.anbai.safecloud.modules.workorder.socket;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.handler.TextWebSocketHandler;/**
* 这个类是配置类,所以需要在spring mvc配置文件中加入对这个类的扫描,
* 第一个addHandler是对正常连接的配置,第二个是如果浏览器不支持websocket,
* 使用socketjs模拟websocket的连接。
*
* @author AnbaiDev
*
*/
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(chatMessageHandler(),"/base/websocket").addInterceptors(new SocketShakeInterceptor()).setAllowedOrigins("*");
registry.addHandler(chatMessageHandler(), "/sockjs/base/websocket").addInterceptors(new SocketShakeInterceptor()).withSockJS();}
@Bean
public TextWebSocketHandler chatMessageHandler() {
return new WebSocketServer();
}
}
SocketShakeInterceptor 握手前后的执行类
package com.anbai.safecloud.modules.workorder.socket;
import java.util.Map;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
public class SocketShakeInterceptor extends HttpSessionHandshakeInterceptor {
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
Map<String, Object> attributes) throws Exception {
boolean result = super.beforeHandshake(request, response, wsHandler, attributes);
return result;
}@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
Exception ex) {
super.afterHandshake(request, response, wsHandler, ex);
}}
package com.anbai.safecloud.modules.workorder.socket;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;import org.apache.log4j.Logger;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;import com.alibaba.fastjson.JSONObject;
import com.anbai.safecloud.utils.Tools;/**
* 这个类是对消息的一些处理,比如是发给一个人,还是发给所有人,并且前端连接时触发的一些动作
*
* @author AnbaiDev
*
*/
@Componentpublic class WebSocketServer extends TextWebSocketHandler {
// private static final ArrayList<WebSocketSession> users;
//用Map来存储,key为业务id,value对应某一个聊天组的聊天用户信息
private static final Map<String, Map<Integer, WebSocketSession>> chartMap = new HashMap<String, Map<Integer, WebSocketSession>>();
private static Logger logger = Logger.getLogger(TextWebSocketHandler.class);// static {
// users = new ArrayList<WebSocketSession>();
// }/**
* 连接成功时候,会触发UI上onopen方法
*/
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
System.out.println("connect to the websocket success......");
URI uri = session.getUri();
String uriStr = uri.toString();
JSONObject json = parseUri(uriStr);
String roomId = json.getString("roomId");
//从session获取登录信息,信息来源由过登录过滤器通过httpRequest获取
JSONObject loginInfo = JSONObject.parseObject(session.getAttributes().get("loginInfo").toString());
int userId = loginInfo.getIntValue("user_id");
if (!chartMap.containsKey(roomId)){
Map<Integer, WebSocketSession> mapSession = new HashMap<Integer, WebSocketSession>();
mapSession.put(userId, session);
chartMap.put(roomId, mapSession);
}
else{
Map<Integer, WebSocketSession> mapSession = chartMap.get(roomId);
if (!mapSession.containsKey(userId)){
mapSession.put(userId, session);
}
}
// 这块会实现自己业务,比如,当用户登录后,会把离线消息推送给用户
// TextMessage returnMessage = new TextMessage("你将收到的离线");
// session.sendMessage(returnMessage);
}/**
* 在UI在用js调用websocket.send()时候,会调用该方法
*/
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
//session为发送者的session
JSONObject json = parseUri(session.getUri().toString());
session.getAttributes().get("loginInfo");
JSONObject loginInfo = JSONObject.parseObject(session.getAttributes().get("loginInfo").toString());
int userId = loginInfo.getIntValue("user_id");
if (json.containsKey("roomId")){
String roomId = json.getString("roomId");
sendMessageToUsers(message,roomId,userId);
}else{
}
}/**
* 给某个用户发送消息
*
* @param userName
* @param message
*/
// public void sendMessageToUser(String userName, TextMessage message) {
// for (WebSocketSession user : users) {
// if (user.getAttributes().get("").equals(userName)) {
// try {
// if (user.isOpen()) {
// user.sendMessage(message);
// }
// } catch (IOException e) {
// e.printStackTrace();
// }
// break;
// }
// }
// }/**
* 给所有在线用户发送消息
*
* @param message
*/
public void sendMessageToUsers(TextMessage message,String roomId,int fromId) {
for(Map.Entry<String, Map<Integer, WebSocketSession>> entry : chartMap.entrySet()){
//roomId用于区别聊天室,向某个聊天室的所有在线用户发送消息
if (!roomId.equals(entry.getKey())){
continue;
}
Map<Integer, WebSocketSession> mapEntry = entry.getValue();
for (Map.Entry<Integer, WebSocketSession> entry1 : mapEntry.entrySet()){
WebSocketSession user = entry1.getValue();
try {
if (user.isOpen()) {
user.getAttributes().get("mid");
JSONObject msgJson = new JSONObject();
msgJson.put("fromId", fromId);
msgJson.put("toId", entry1.getKey());
msgJson.put("content", message.getPayload());
WebSocketMessage<String> message1 = new TextMessage(msgJson.toJSONString());
user.sendMessage(message1);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
if (session.isOpen()) {
session.close();
}
logger.debug("websocket connection closed......");
// users.remove(session);
}@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
logger.debug("websocket connection closed......");
// users.remove(session);
}@Override
public boolean supportsPartialMessages() {
return false;
}
/**
* 获取用户id
*
* @return int userId
*/
protected int getUserId(){
return Integer.parseInt("" + Tools.getLoginInfo("user_id"));
}
private JSONObject parseUri(String uri){
JSONObject json = new JSONObject();
String strUri = "";
String strUriParams = "";
if (uri.contains("?")){
String[] uriPattern = uri.split("\\?");
strUri = uriPattern[0];
strUriParams = uriPattern[1];
json.put("uri", strUri);
}
else{
json.put("uri", uri);
return json;
}
//解析参数
String[] params = null;
if (strUriParams.contains("&")){
params = strUriParams.split("&");
}
else{
params = new String[]{strUriParams};
}
for(String p:params){
if (p.contains("=")){
String[] param = p.split("=");
if (param.length == 1){
json.put(param[0], "");
}else{
json.put(param[0], param[1]);
}
}
}
return json;
}}