技术从一开始ajax轮询后来改成websocket 碰到的一些问题的处理:
websocket的pom依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-websocket</artifactId> <version>4.0.5.RELEASE</version> </dependency>
首先是配置处理器
import javax.annotation.Resource; import org.springframework.stereotype.Component; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.socket.config.annotation.WebSocketConfigurer; import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; /** * WebScoket配置处理器 * @author Goofy * @Date 2015年6月11日 下午1:15:09 */ @Component @EnableWebSocket public class WebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer { @Resource MyWebSocketHandler handler; public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(handler, "/ws").addInterceptors(new HandShake()); registry.addHandler(handler, "/ws/sockjs").addInterceptors(new HandShake()).withSockJS(); } }
2.请求所经过的握手拦截器 主要用来将ServerHttpRequest里的session的用户信息存放在attributes里 到处理的handle中 会自动存入websocketsession的attribute里
import java.util.Map; import javax.servlet.http.HttpSession; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.server.HandshakeInterceptor; /** * Socket建立连接(握手)和断开 * * @author Goofy * @Date 2015年6月11日 下午2:23:09 */ public class HandShake implements HandshakeInterceptor { public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception { System.out.println("Websocket:用户[ID:" + ((ServletServerHttpRequest) request).getServletRequest().getSession(false).getAttribute("uid") + "]已经建立连接"); if (request instanceof ServletServerHttpRequest) { ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request; HttpSession session = servletRequest.getServletRequest().getSession(false); // 标记用户 Long uid = (Long) session.getAttribute("uid"); if(uid!=null){ attributes.put("uid", uid); }else{ return false; } } return true; } public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) { } }
Socket处理器 处理连接通信 或错误 关闭等
import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import org.springframework.stereotype.Component; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.WebSocketMessage; import org.springframework.web.socket.WebSocketSession; import org.xdemo.example.websocket.entity.Message; import com.google.gson.Gson; import com.google.gson.GsonBuilder; /** * Socket处理器 * * @author Goofy * @Date 2015年6月11日 下午1:19:50 */ @Component public class MyWebSocketHandler implements WebSocketHandler { public static final Map<Long, WebSocketSession> userSocketSessionMap; static { userSocketSessionMap = new HashMap<Long, WebSocketSession>(); } /** * 建立连接后 */ public void afterConnectionEstablished(WebSocketSession session) throws Exception { Long uid = (Long) session.getAttributes().get("uid"); if (userSocketSessionMap.get(uid) == null) { userSocketSessionMap.put(uid, session); } } /** * 消息处理,在客户端通过Websocket API发送的消息会经过这里,然后进行相应的处理 */ public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception { if(message.getPayloadLength()==0)return; Message msg=new Gson().fromJson(message.getPayload().toString(),Message.class); msg.setDate(new Date()); sendMessageToUser(msg.getTo(), new TextMessage(new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create().toJson(msg))); } /** * 消息传输错误处理 */ public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { if (session.isOpen()) { session.close(); } Iterator<Entry<Long, WebSocketSession>> it = userSocketSessionMap .entrySet().iterator(); // 移除Socket会话 while (it.hasNext()) { Entry<Long, WebSocketSession> entry = it.next(); if (entry.getValue().getId().equals(session.getId())) { userSocketSessionMap.remove(entry.getKey()); System.out.println("Socket会话已经移除:用户ID" + entry.getKey()); break; } } } /** * 关闭连接后 */ public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception { System.out.println("Websocket:" + session.getId() + "已经关闭"); Iterator<Entry<Long, WebSocketSession>> it = userSocketSessionMap .entrySet().iterator(); // 移除Socket会话 while (it.hasNext()) { Entry<Long, WebSocketSession> entry = it.next(); if (entry.getValue().getId().equals(session.getId())) { userSocketSessionMap.remove(entry.getKey()); System.out.println("Socket会话已经移除:用户ID" + entry.getKey()); break; } } } public boolean supportsPartialMessages() { return false; } /** * 给所有在线用户发送消息 * * @param message * @throws IOException */ public void broadcast(final TextMessage message) throws IOException { Iterator<Entry<Long, WebSocketSession>> it = userSocketSessionMap .entrySet().iterator(); // 多线程群发 while (it.hasNext()) { final Entry<Long, WebSocketSession> entry = it.next(); if (entry.getValue().isOpen()) { // entry.getValue().sendMessage(message); new Thread(new Runnable() { public void run() { try { if (entry.getValue().isOpen()) { entry.getValue().sendMessage(message); } } catch (IOException e) { e.printStackTrace(); } } }).start(); } } } /** * 给某个用户发送消息 * * @param userName * @param message * @throws IOException */ public void sendMessageToUser(Long uid, TextMessage message) throws IOException { WebSocketSession session = userSocketSessionMap.get(uid); if (session != null && session.isOpen()) { session.sendMessage(message); } } }
页面
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <% String path = request.getContextPath(); String basePath = request.getServerName() + ":" + request.getServerPort() + path + "/"; String basePath2 = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/"; %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title></title> <script type="text/javascript" src="<%=basePath2%>resources/jquery.js"></script> <style> textarea { height: 300px; width: 100%; resize: none; outline: none; } input[type=button] { float: right; margin: 5px; width: 50px; height: 35px; border: none; color: white; font-weight: bold; outline: none; } .clear { background: red; } .send { background: green; } .clear:active { background: yellow; } .send:active { background: yellow; } .msg { width: 100%; height: 25px; outline: none; } #content { border: 1px solid gray; width: 100%; height: 400px; overflow-y: scroll; } .from { background-color: green; width: 80%; border-radius: 10px; height: 30px; line-height: 30px; margin: 5px; float: left; color: white; padding: 5px; font-size: 22px; } .to { background-color: gray; width: 80%; border-radius: 10px; height: 30px; line-height: 30px; margin: 5px; float: right; color: white; padding: 5px; font-size: 22px; } .name { color: gray; font-size: 12px; } .tmsg_text { color: white; background-color: rgb(47, 47, 47); font-size: 18px; border-radius: 5px; padding: 2px; } .fmsg_text { color: white; background-color: rgb(66, 138, 140); font-size: 18px; border-radius: 5px; padding: 2px; } .sfmsg_text { color: white; background-color: rgb(148, 16, 16); font-size: 18px; border-radius: 5px; padding: 2px; } .tmsg { clear: both; float: right; width: 80%; text-align: right; } .fmsg { clear: both; float: left; width: 80%; } </style> <script> var path = ‘<%=basePath%>‘; var uid=${uid eq null?-1:uid}; if(uid==-1){ location.href="<%=basePath2%>"; } var from=uid; var fromName=‘${name}‘; var to=uid==1?2:1; var websocket; if (‘WebSocket‘ in window) { websocket = new WebSocket("ws://" + path + "/ws?uid="+uid); } else if (‘MozWebSocket‘ in window) { websocket = new MozWebSocket("ws://" + path + "/ws"+uid); } else { websocket = new SockJS("http://" + path + "/ws/sockjs"+uid); } websocket.onopen = function(event) { console.log("WebSocket:已连接"); console.log(event); }; websocket.onmessage = function(event) { var data=JSON.parse(event.data); console.log("WebSocket:收到一条消息",data); var textCss=data.from==-1?"sfmsg_text":"fmsg_text"; $("#content").append("<div class=‘fmsg‘><label class=‘name‘>"+data.fromName+" "+data.date+"</label><div class=‘"+textCss+"‘>"+data.text+"</div></div>"); scrollToBottom(); }; websocket.onerror = function(event) { console.log("WebSocket:发生错误 "); console.log(event); }; websocket.onclose = function(event) { console.log("WebSocket:已关闭"); console.log(event); } function sendMsg(){ var v=$("#msg").val(); if(v==""){ return; }else{ var data={}; data["from"]=from; data["fromName"]=fromName; data["to"]=to; data["text"]=v; websocket.send(JSON.stringify(data)); $("#content").append("<div class=‘tmsg‘><label class=‘name‘>我 "+new Date().Format("yyyy-MM-dd hh:mm:ss")+"</label><div class=‘tmsg_text‘>"+data.text+"</div></div>"); scrollToBottom(); $("#msg").val(""); } } function scrollToBottom(){ var div = document.getElementById(‘content‘); div.scrollTop = div.scrollHeight; } Date.prototype.Format = function (fmt) { //author: meizz var o = { "M+": this.getMonth() + 1, //月份 "d+": this.getDate(), //日 "h+": this.getHours(), //小时 "m+": this.getMinutes(), //分 "s+": this.getSeconds(), //秒 "q+": Math.floor((this.getMonth() + 3) / 3), //季度 "S": this.getMilliseconds() //毫秒 }; if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length)); for (var k in o) if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length))); return fmt; } function send(event){ var code; if(window.event){ code = window.event.keyCode; // IE }else{ code = e.which; // Firefox } if(code==13){ sendMsg(); } } function clearAll(){ $("#content").empty(); } </script> </head> <body> 欢迎:${sessionScope.name } <div id="content"></div> <input type="text" placeholder="请输入要发送的信息" id="msg" class="msg" onkeydown="send(event)"> <input type="button" value="发送" class="send" onclick="sendMsg()" > <input type="button" value="清空" class="clear" onclick="clearAll()"> </body> </html>
碰到的一些问题 以及处理的想法
1.之前在拦截器的请求里面得不到session 使用从页面传进来的get方式得到参数uid(后来也没改什么 调试的时候发现可以得到session)
2.客服防止多处登陆的处理:
每次登陆获取loginusermap 如果没有该用户 新增有就更新 然后替换map到session中
登陆拦截器校验登陆的用户的sessionid和map里的是否一致 不一致说明被挤 重新跳转
loginUserMap.put(user.getFid(), sessionId);
request.getSession().getServletContext().setAttribute("loginUserMap", loginUserMap);
3.
按钮嵌在右侧的工具栏 点击固定只弹出一个聊天网页
var op;
op = window.open("${webRoot}customerChat.json","newWin1");
op.focus();
4.
兼容ios(看了请求头里的sec-websocket-extensions不一致 就想的馊主意改头 结果可用)
if(request.getHeaders().get("sec-websocket-extensions").contains("x-webkit-deflate-frame")){
List<String> list = new ArrayList<String>();
list.add("permessage-deflate");
request.getHeaders().remove("sec-websocket-extensions");
request.getHeaders().put("sec-websocket-extensions",list);
}
兼容nginx
upstream tjxm.com{
server 127.0.0.1:8080;
}
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://tjxm.com/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
哈哈 第一次写博客 有点菜鸡 websokcet部分也是从网上找的demo