简易Java web在线聊天-websocket
来源:互联网 发布:浙江软件行业协会 编辑:程序博客网 时间:2024/05/20 15:41
公司做了伪在线客服系统。自己为了技术,提前研究了使用websocket进行长连接通信。写了个简单的在线聊天demo,算是对自己的交代,后期会抽时间优化,完善流程等。
语言:Java
客户端:html5 实现的socket作为客户端。
服务器:使用spring-mvc + spring-websocket+spring-mesaging作为信息接收处理。
测试容器:jetty9+
1.【websocket是什么】
WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端。WebSocket通信协议于2011年被IETF定为标准RFC 6455,并被RFC7936所补充规范。
在实现websocket连线过程中,需要通过浏览器发出websocket连线请求,然后服务器发出回应,这个过程通常称为“握手” 。在 WebSocket API,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。在此WebSocket 协议中,为我们实现即时服务带来了两大好处:
1. Header 互相沟通的Header是很小的-大概只有 2 Bytes
2. Server Push 服务器的推送,服务器不再被动的接收到浏览器的请求之后才返回数据,而是在有新数据时就主动推送给浏览器。
------来源百度百科
2.【websocket API简单介绍】
var ws = new WebSocket("ws://www.websocket.org",[protocol]);
说明:WebSocket的构造函数需要一个URL参数和一个可选的协议参数(一个或者多个协议的名字),协议的参数例如XMPP(Extensible Messaging and Presence Protocol)、SOAP(Simple Object Access Protocol)或者自定义协议。而URL参数需要以WS://或者WSS://开头,例如:ws://www.websocket.org。
在第一次握手之后,和协议的名称一起,客户端会发送一个Sec-WebSocket-Protocol 头,服务端会选择0个或一个协议,响应会带上同样的Sec-WebSocket-Protocol 头,否则会关闭连接。通过协议协商(Protocol negotiation ),我们可以知道给定的WebSocket服务器所支持的协议和版本,然后应用选择协议使用。
2.1 【websocket事件】
WebSocket API是纯事件驱动,通过监听事件可以处理到来的数据和改变的链接状态。客户端不需要为了更新数据而轮训服务器。服务端发送数据后,消息和事件会异步到达。
WebSocket编程遵循一个异步编程模型,只需要对WebSocket对象增加回调函数就可以监听事件。你也可以使用addEventListener()方法来监听。而一个WebSocket对象分四类不同事件。
Open事件:一旦服务器响应连接,就会触发open事件。响应的回调函数为onopen。
// Event handler for the WebSocket connection openingws.onopen = function(e) { console.log("Connection open...");};
Message事件:open事件后,意味着协议握手结束,服务器端同意客户端连接,进做好通信准备。发送消息触发的回调为:onmessage。
ws.onmessage = function(e) { if(typeof e.data === "string"){ console.log("String message received", e, e.data); } else { console.log("Other message received", e, e.data); }};Error事件:如果发生意外的失败则会触发error事件,error事件会导致连接关闭,很快就会收到关闭事件回调。意外失败的回调为:onerror。
ws.onerror = function(e) { console.log("WebSocket Error: " , e); //Custom function for handling errors handleErrors(e);};
close事件:当连接关闭的时候,触发改事件。对应的回调方法为:onclose。
ws.onclose = function(e) { console.log("Connection closed", e);};
2.2:【websocket方法】
send方法:当onopen触发后,连接正常。则可以使用send方法向服务器发送消息, send方法还可以发送二进制文本。如:ws.send("hello world");
close方法:关闭websocket连接。如果连接已经关闭,则该方法什么也不做。
2.3 【websocket属性】
属性值状态WebSocket.CONNECTING0连接正在进行,但还没有建立WebSocket.OPEN1连接已经建立,可以发送消息。WebSocket.CLOSING2连接正在进行关闭握手WebSocket.CLOSED3连接已经关闭或不能打开
3 【Demo示例】
demo模拟多人在线聊天,当用户选中要发送信息的在线用户,则可进行在线聊天。
3.1主要依赖关系
<!-- spring dependency start --><dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring-version}</version></dependency><dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring-version}</version></dependency><dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${spring-version}</version></dependency><dependency> <groupId>org.springframework</groupId> <artifactId>spring-websocket</artifactId> <version>${spring-version}</version></dependency><dependency> <groupId>org.springframework</groupId> <artifactId>spring-messaging</artifactId> <version>${spring-version}</version></dependency><!-- spring dependency end --><dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.20</version></dependency>
3.2 【消息处理类】
简单的消息处理,继承spring-websocket,文本消息处理类,实现文本消息处理。并缓存当前在线用户,当连接失败、发送消息失败,简单认为用户离线删除session。未处理socket session 连接超时、用户长时间无通信容器session超时、页面刷新重连等。
package cc.hu.test.websocket.skthandler;import java.io.IOException;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.WebSocketSession;import org.springframework.web.socket.handler.AbstractWebSocketHandler;import com.alibaba.fastjson.JSON;import cc.hu.test.websocket.common.User;import cc.hu.test.websocket.controller.MainController;/** * @author root * */@Componentpublic class MyTextWebSocketHandler extends AbstractWebSocketHandler {public static Map<Long, WebSocketSession> userWebsocketSessionMap;static {userWebsocketSessionMap = new HashMap<Long, WebSocketSession>();}@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {Long uid = (Long) session.getAttributes().get("uid");userWebsocketSessionMap.put(uid, session);User u = MainController.loginUserMap.get(uid);CCMsg welcome = new CCMsg();welcome.setTo(uid);welcome.setMsg("[系统消息]欢迎[" + u.getNickName() + "]进入聊天");welcome.setMsgType(MsgType.CHAT_MSG);welcome.setFromName("系统");welcome.setSendDate(new Date());welcome.setFrom(0L);session.sendMessage(new TextMessage(JSON.toJSONString(welcome)));//群发通知在线列表更新sendOnlineUserMsg();}@Overridepublic void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {if (session.isOpen())session.close();Iterator<Entry<Long, WebSocketSession>> mapItr = userWebsocketSessionMap.entrySet().iterator();while (mapItr.hasNext()) {final Entry<Long, WebSocketSession> one = mapItr.next();Long key = one.getKey();WebSocketSession value = one.getValue();if (value.getId().equals(session.getId())) {mapItr.remove();System.out.println("发送消息错误, 移除socket[key:" + key + "\t" + MainController.loginUserMap.get(key));//移除在线用户MainController.loginUserMap.remove(key);break;}}exception.printStackTrace();//群发通知在线列表更新sendOnlineUserMsg();}@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {System.out.println("websocket用户关闭:" + JSON.toJSONString(status));Iterator<Entry<Long, WebSocketSession>> mapItr = userWebsocketSessionMap.entrySet().iterator();while (mapItr.hasNext()) {final Entry<Long, WebSocketSession> one = mapItr.next();Long key = one.getKey();WebSocketSession value = one.getValue();if (value.getId().equals(session.getId())) {mapItr.remove();System.out.println("用户关闭, 移除socket[key:" + key + "\t" + MainController.loginUserMap.get(key));MainController.loginUserMap.remove(key);break;}}//群发通知在线列表更新sendOnlineUserMsg();}@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {if (message.getPayloadLength() == 0)return;System.out.println("[收到消息====>]" + message.getPayload().toString());CCMsg msg = JSON.parseObject(message.getPayload().toString(), CCMsg.class);WebSocketSession toSession = userWebsocketSessionMap.get(msg.getTo());if (toSession != null && toSession.isOpen()) {msg.setSendDate(new Date());toSession.sendMessage(new TextMessage(JSON.toJSONString(msg)));} else {CCMsg welcome = new CCMsg();welcome.setTo(msg.getFrom());welcome.setMsg("[系统消息]对方已下线,请稍后重试...");welcome.setMsgType(MsgType.CHAT_MSG);welcome.setFromName("系统");welcome.setSendDate(new Date());welcome.setFrom(0L);session.sendMessage(new TextMessage(JSON.toJSONString(welcome)));//群发通知在线列表更新sendOnlineUserMsg();}}//发送在线信息更新消息public static void sendOnlineUserMsg() {CCMsg msg = new CCMsg();msg.setMsg(JSON.toJSONString(MainController.getOnLineUserList()));msg.setMsgType(MsgType.ONLINE_USER_MSG);msg.setFromName("系统");msg.setSendDate(new Date());msg.setFrom(0L);Iterator<Entry<Long, WebSocketSession>> mapItr = userWebsocketSessionMap.entrySet().iterator();while (mapItr.hasNext()) {final Entry<Long, WebSocketSession> one = mapItr.next();Long key = one.getKey();msg.setTo(key);WebSocketSession value = one.getValue();try {value.sendMessage(new TextMessage(JSON.toJSONString(msg)));} catch (IOException e) {e.printStackTrace();}}}}
3.3 【websocket握手拦截器】
package cc.hu.test.websocket.skthandler;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;/** * spring websocket 握手拦截器 * @author sen.hu * */public class HandShake implements HandshakeInterceptor {@Overridepublic boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,Map<String, Object> attributes) throws Exception {System.out.println("Websocket用户:[uid:" + ((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);} elsereturn false;}return true;}@Overridepublic void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,Exception exception) {}}
3.4 【spring websocket配置类】
package cc.hu.test.websocket.skthandler;import javax.annotation.Resource;import org.springframework.context.annotation.Configuration;import org.springframework.stereotype.Component;import org.springframework.web.servlet.config.annotation.EnableWebMvc;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;@Configuration@EnableWebMvc@EnableWebSocketpublic class WebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer {//@Resource//MyWebSocketHandler handler;@ResourceMyTextWebSocketHandler textHandler;@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {/*registry.addHandler(handler, "/ws").addInterceptors(new HandShake());//sock js处理类registry.addHandler(handler, "/ws/sockjs").addInterceptors(new HandShake()).withSockJS();*/registry.addHandler(textHandler, "/ws").addInterceptors(new HandShake());//sock js处理类registry.addHandler(textHandler, "/ws/sockjs").addInterceptors(new HandShake()).withSockJS();}}
3.5 【聊天页面及JS】
页面显示:
<%@page language="java" contentType="text/html; charset=UTF-8"%><!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width"><meta http-equiv="X-UA-Compatible" content="IE=edge"><title>cc的聊天</title><link rel="stylesheet" type="text/css" href="http://127.0.0.1:8087/static/css/style.css"></head><body><input type="hidden" value="${uid }" id="uid"/><input type="hidden" value="${userName }" id="username"/><input type="hidden" value="${path }" id="path"/><input type="hidden" value="" id="toUser"/><!----><div>欢迎[${userName }]</div><div><div class="online_list"> <ul id="onlineList" class="onlinelist"></ul></div><div id="content" class="content"></div></div><input type="text" placeholder="请输入要发送的信息" id="msg" class="msg"><input type="button" value="发送" class="send" onclick="sendMsgToServer(2)" ><input type="button" value="清空" class="clear" onclick="clearAll()"><script type="text/javascript" src="http://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script><script type="text/javascript" src="http://127.0.0.1:8087/static/js/talk.js"></script></body></html>页面JS:
/** * Created by root on 7/15/17. */window.path = $("#path").val();window.uid = $("#uid").val();window.fromUserId = uid;window.sendTo = $("#toUser").val();window.fromUserName = $("#username").val();window.ws;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 changeBgColor(id, type) {var toid = $("#toUser").val(); if (type == 1) {//add css $("#li"+ id).css("cursor", "hand"); if (toid != id) { $("#li" + toid).css("background", "white"); } $("#li"+ id).css("background", "burlywood"); } else if (type == 2) { if (toid != id) { $("#li"+ id).css("background", "white"); } $("#li"+ toid).css("background", "burlywood"); $("#li"+ id).css("cursor", "normal"); }}function sendMsgToServer(type) { var msg = ""; var toUserId = $("#toUser").val(); if (toUserId == fromUserId) { alert("不能给自己发消息!"); return; } switch(type) { case 1: msg = "hello"; break; case 2: msg = $("#msg").val(); break; default: msg = $("#msg").val(); } msg = $.trim(msg); if (msg.length == 0) { alert("发送信息不能为空!"); return; } if (ws.readyState == WebSocket.OPEN) { var sendData = {}; sendData["from"] = fromUserId; sendData["fromName"] = fromUserName; sendData["to"] = toUserId; sendData["msg"] = msg; console.log("发送消息====>" + JSON.stringify(sendData)); ws.send(JSON.stringify(sendData)); $("#msg").val(""); $("#content").append("<div class='tmsg'><label class='name'>我 "+new Date().Format("yyyy-MM-dd hh:mm:ss")+"</label><div class='tmsg_text'>"+sendData.msg+"</div></div>"); } else { alert("Websocket:连接状态异常"); console.log("Websocket:连接状态异常,readyState=" + ws.readyState); }}function setToUserId(toUserId) { $("#toUser").val(toUserId); $("#li"+ toUserId).css("background", "burlywood");}function updateOnlineList(rcv) { console.log("在线用户更新:" + rcv.msg); var onlineList = eval(rcv.msg); var html=""; for (var o in onlineList) { console.log(o); html += "<li id='li" + onlineList[o].userId +"' onclick='setToUserId("+ onlineList[o].userId+ ")' onmouseout='changeBgColor("+onlineList[o].userId+", 2)' onmouseenter='changeBgColor("+onlineList[o].userId+", 1)'>" + onlineList[o].nickName + "</li>"; } $("#onlineList").html(html);}function updateMsgContent(rcv) { var textCss = rcv.from == 0 ? "sfmsg_text" : "fmsg_text"; var html = "<div class='fmsg'>" + "<label class='name'>" + rcv.fromName + " " + rcv.sendDate + "</label>" + "<div class='" + textCss + "'>" + rcv.msg + "</div>" + "</div>"; $("#content").append(html); if (rcv.from != 0) { $("#toUser").val(rcv.from); $("#li"+ rcv.from).css("background", "burlywood"); } scrollToBottom();}/**初始化websocket*/function initWebsocket() { if ("WebSocket" in window) ws = new WebSocket("ws://" + path + "/ws?uid=" + uid); else if ("MozWebSocket" in window) ws = new WebSocket("ws://" + path + "/ws?uid" + uid); else ws = new SockJS("http://" + path + "/ws/sockjs?uid=" + uid); ws.onopen = function(event) { console.log("websocket:链接...OK!"); console.log(event); //sendMsgToServer(1); }; ws.onmessage = function (event) { var rcv = JSON.parse(event.data); console.log("websocket:收到一条信息<=====" + rcv); console.log(event); var msgType = rcv.msgType; switch (msgType) { case 0x01: updateOnlineList(rcv); break; case 0x10: updateMsgContent(rcv); break; default: updateMsgContent(rcv); } }; ws.onerror = function (event) { console.log("websocket:发生错误!"); console.log(event); }; ws.onclose = function (event) { console.log("WebSocket:已关闭"); console.log(event); }}function scrollToBottom() { var div = document.getElementById('content'); div.scrollTop = div.scrollHeight;}function clear() { $("#content").empty();}window.onload = function() { initWebsocket(); document.onkeydown = function(event) { var code; if (window.event) code = window.event.keyCode; else code = event.which; if (code == 13) sendMsgToServer(); }}
3.6【登陆界面及登陆处理Controller】
简单的登陆界面及登陆处理逻辑,代码及具体实现【略】
后记:只是一个简单的demo示例,很多细节没有考虑,处理。只实现了最简单文本消息处理,以后可以尝试二进制,增加自定义协议、及安全处理等,完善demo。
阅读全文
1 0
- 简易Java web在线聊天-websocket
- Java WebSocket构建简易聊天web程序
- Spring -websocket实现简易在线聊天
- java WebSocket 简易聊天消息推送
- java WebSocket 简易聊天消息推送
- java WebSocket 简易聊天消息推送
- java WebSocket 简易聊天消息推送
- java WebSocket 简易聊天消息推送
- 基于Tomcate、java、websocket 简单在线聊天
- Java实现WebSocket聊天
- SpringMvc+Mybatis企业级集成Websocket在线聊天
- JavaWeb--使用Websocket实现在线聊天功能
- tomcat websocket 实现网页在线即时聊天
- [java]局域网简易聊天
- [java]局域网简易聊天
- html5 websocket java 联合开发---仿qq多人在线聊天demo
- 基于Java和websocket的在线聊天程序(可群发和选择用户)
- websocket点对点聊天java实例
- Android Volley完全解析(二),使用Volley加载网络图片
- 链表面试题3/链表带环问题--判断链表是否带环?
- php学习(4)---文件的操作
- myBatis工作流程
- 工作日记2017.07.17
- 简易Java web在线聊天-websocket
- E
- 正则表达式
- C++:1.设计一个类不能被继承 2.设计一个类只能在堆上创建对象。 3.设计一个类只能在栈上创建对象。
- 组合数
- 面试常见问题——架构
- Python定时任务的启动和停止
- Android视频背景,动态背景
- 循环链表处理约瑟夫环