WebSocket 是一种网络通信协议。RFC6455 定义了它的通信标准。
WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。
HTTP 协议是一种无状态的、无连接的、单向的应用层协议。它采用了请求/响应模型。通信请求只能由客户端发起,服务端对请求做出应答处理。
这种通信模型有一个弊端:HTTP 协议无法实现服务器主动向客户端发起消息。
这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。大多数 Web 应用程序将通过频繁的异步JavaScript和XML(AJAX)请求实现长轮询。轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。
因此,工程师们一直在思考,有没有更好的方法。WebSocket 就是这样发明的。WebSocket 连接允许客户端和服务器之间进行全双工通信,以便任一方都可以通过建立的连接将数据推送到另一端。WebSocket 只需要建立一次连接,就可以一直保持连接状态。这相比于轮询方式的不停建立连接显然效率要大大提高。
Web浏览器和服务器都必须实现 WebSockets 协议来建立和维护连接。由于 WebSockets 连接长期存在,与典型的HTTP连接不同,对服务器有重要的影响。
基于多线程或多进程的服务器无法适用于 WebSockets,因为它旨在打开连接,尽可能快地处理请求,然后关闭连接。任何实际的 WebSockets 服务器端实现都需要一个异步服务器。
这里,我们基于Spring整合的WebSocket,实现简单的IM聊天功能。
代码可以在Spring组件化构建https://www.pomit.cn/java/spring/spring.html中的WebSocket组件中查看,并下载。
品茗IT:提供在线快速构建Spring项目工具。
**如果大家正在寻找一个java的学习环境,或者在开发中遇到困难,可以<a
href="https://jq.qq.com/?_wv=1027&k=52sgH1J"
target="_blank">
加入我们的java学习圈,点击即可加入
</a>
,共同学习,节约学习时间,减少很多在学习中遇到的难题。**
本文假设你已经引入Spring必备的一切了,已经是个Spring项目了,如果不会搭建,可以打开这篇文章看一看《Spring和Spring Mvc 5整合详解》。
<?xml version="1.0"?>
<project
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.pomit</groupId>
<artifactId>SpringWork</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>Websocket</artifactId>
<packaging>jar</packaging>
<name>Websocket</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
</dependency>
<!--log4j-core -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</dependency>
<!-- log4j-web -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-web</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
</dependencies>
<build>
<finalName>Websocket</finalName>
</build>
</project>
父模块可以在https://www.pomit.cn/spring/SpringWork/pom.xml获取。
该xml文件仅作为配置文件的引入使用。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd ">
<bean id="annotationPropertyConfigurerWebSocket"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="order" value="1" />
<property name="ignoreUnresolvablePlaceholders" value="true" />
<property name="locations">
<list>
<value>classpath:websocket.properties</value>
</list>
</property>
</bean>
</beans>
websocket.properties保存一些自定义配置,这里没用上,就不贴出来了。
Spring整合WebSocket需要配置websocket的监听url、配置WebSocketInterceptor(连接握手配置)、webSocketHandler(连接成功配置)。
SpringWebSocketConfig:
package cn.pomit.springwork.websocket.config;
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 cn.pomit.springwork.websocket.handler.WebSocketHandler;
import cn.pomit.springwork.websocket.handler.WebSocketInterceptor;
@Configuration
@EnableWebSocket
public class SpringWebSocketConfig implements WebSocketConfigurer {
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(webSocketHandler(), "/websocket").addInterceptors(new WebSocketInterceptor());
registry.addHandler(webSocketHandler(), "/sockjs").addInterceptors(new WebSocketInterceptor()).withSockJS();
}
@Bean
public WebSocketHandler webSocketHandler() {
return new WebSocketHandler();
}
}
这个是将子项目/模块的页面映射到chat/路径上,如果不是maven子模块/项目,这个配置可以忽略
package cn.pomit.springwork.websocket.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@EnableWebMvc
@Configuration
public class WebStaticConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/chat/**").addResourceLocations("classpath:/static/");
}
}
WebSocketInterceptor对WebSocket的连接进行过滤,可以对连接前和连接后自定义处理。
WebSocketInterceptor:
package cn.pomit.springwork.websocket.handler;
import java.util.Map;
import javax.servlet.http.HttpSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
/**
* websocket 握手处理器
* @author fufei
*
*/
@Component
public class WebSocketInterceptor extends HttpSessionHandshakeInterceptor {
private Logger log = LoggerFactory.getLogger(this.getClass());
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
Map<String, Object> attributes) throws Exception {
log.info("收到握手请求。");
if (request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
HttpSession session = servletRequest.getServletRequest().getSession(false);
if (session == null) {
return false;
}
// 使用userName区分WebSocketHandler,以便定向发送消息
String userName = (String) session.getAttribute("userName");
if (StringUtils.isEmpty(userName)) {
return false;
}
log.info("获取到用户信息:{}", userName);
attributes.put("userId", userName);
}
return super.beforeHandshake(request, response, wsHandler, attributes);
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
Exception ex) {
log.info("成功握手。");
super.afterHandshake(request, response, wsHandler, ex);
}
}
WebsocketHandler负责处理消息发送接收的逻辑:
package cn.pomit.springwork.websocket.handler;
import java.io.IOException;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import org.springframework.web.socket.AbstractWebSocketMessage;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import com.alibaba.fastjson.JSONObject;
import cn.pomit.springwork.websocket.dto.MessageDTO;
import cn.pomit.springwork.websocket.memory.WebSocketUser;
/**
* websocket消息处理器
*
* @author fufei
*
*/
public class WebSocketHandler extends TextWebSocketHandler {
protected Logger logger = LoggerFactory.getLogger(getClass());
public static WebSocketUser users = new WebSocketUser();
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
logger.info("收到websocket消息:{}", message.toString());
super.handleTextMessage(session, message);
String msg = message.getPayload();
logger.info("收到websocket消息的消息体:{}", msg);
if (!StringUtils.isEmpty(msg)) {
MessageDTO messageDTO = JSONObject.parseObject(msg, MessageDTO.class);
sendMessageToUser(messageDTO.getTargetUserName(), message);
}
}
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
String userName = (String) session.getAttributes().get("userName");
if (StringUtils.isEmpty(userName)) {
logger.error("用户不能为空!");
}
WebSocketUser.add(userName, session);
MessageDTO messageDTO = new MessageDTO();
messageDTO.setFromUserName("");
messageDTO.setTargetUserName(userName);
messageDTO.setMessageType(MessageDTO.Type.TYPE_NEW.getMessageType());
messageDTO.setMessage("欢迎您!");
session.sendMessage(new TextMessage(JSONObject.toJSONString(messageDTO)));
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
String userName = (String) session.getAttributes().get("userName");
if (StringUtils.isEmpty(userName)) {
logger.error("用户不能为空!");
}
WebSocketUser.removeWebSocketSession(userName, session);
super.afterConnectionClosed(session, status);
}
/**
* 给某个用户发送消息
*
* @param userName
* @param message
*/
public void sendMessageToUser(String userName, AbstractWebSocketMessage<?> message) {
List<WebSocketSession> webUsers = WebSocketUser.getSessionByUserName(userName);
if (webUsers == null || webUsers.size() == 0) {
logger.error("发送给{},当前无session", userName);
return;
}
logger.info("发送给{},当前session个数为:{}", userName, webUsers.size());
for (int i = 0; i < webUsers.size(); i++) {
WebSocketSession session = webUsers.get(i);
try {
if (!session.isOpen()) {
WebSocketUser.removeWebSocketSession(userName, session);
}
session.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
WebSocketUser是使用内存来存储用户:
package cn.pomit.springwork.websocket.memory;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.util.CollectionUtils;
import org.springframework.web.socket.WebSocketSession;
public class WebSocketUser {
private static Map<String, List<WebSocketSession>> userNameWebsession = new ConcurrentHashMap<>();
/**
* 添加昵称相关的WebSocketSession
*
* @param nickName
* @return
*/
public static void add(String userName, WebSocketSession webSocketSession) {
userNameWebsession.computeIfAbsent(userName, v -> new ArrayList<WebSocketSession>()).add(webSocketSession);
}
/**
* 根据昵称拿WebSocketSession
*
* @param nickName
* @return
*/
public static List<WebSocketSession> getSessionByUserName(String userName) {
return userNameWebsession.get(userName);
}
/**
* 移除失效的WebSocketSession
*
* @param webSocketSession
*/
public static void removeWebSocketSession(String userName, WebSocketSession webSocketSession) {
if (webSocketSession == null)
return;
List<WebSocketSession> webSessoin = userNameWebsession.get(userName);
if (webSessoin == null || CollectionUtils.isEmpty(webSessoin))
return;
webSessoin.remove(webSocketSession);
}
public static Set<String> getUserList(){
return userNameWebsession.keySet();
}
}
如果我们想发送文件,需要上传文件后转为二进制数据并使用websocket发送。另外,这里使用setUser来产生用户,并提供获取当前用户,用户列表的接口。
package cn.pomit.springwork.websocket.web;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.socket.BinaryMessage;
import cn.pomit.springwork.websocket.dto.ResultModel;
import cn.pomit.springwork.websocket.handler.WebSocketHandler;
import cn.pomit.springwork.websocket.memory.WebSocketUser;
@RestController
@RequestMapping("/im")
public class WebSocketController {
protected Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private WebSocketHandler websocketHandler;
@RequestMapping(value = "/fileUpload")
public ResultModel fileUpload(@RequestParam("userName") String userName, @RequestParam MultipartFile[] myfiles,
HttpServletRequest request) {
logger.info("收到发往用户[{}]的文件上传请求;文件数量:{}", userName, myfiles.length);
int count = 0;
for (MultipartFile myfile : myfiles) {
if (myfile.isEmpty()) {
count++;
}
logger.info("文件原名:{};文件类型:", myfile.getOriginalFilename(), myfile.getContentType());
try (ByteArrayOutputStream swapStream = new ByteArrayOutputStream();
InputStream is = myfile.getInputStream();) {
byte[] buff = new byte[100]; // buff用于存放循环读取的临时数据
int rc = 0;
while ((rc = is.read(buff, 0, 100)) > 0) {
swapStream.write(buff, 0, rc);
}
byte[] in_b = swapStream.toByteArray(); // in_b为转换之后的结果
logger.info("正在发送文件: ");
websocketHandler.sendMessageToUser(userName, new BinaryMessage(in_b));
} catch (IOException e) {
logger.error("文件原名:{}", myfile.getOriginalFilename(), e);
e.printStackTrace();
count++;
continue;
}
}
return ResultModel.ok(count);
}
@RequestMapping(value = "/setUser")
public ResultModel setUser(@RequestParam("userName") String userName, HttpServletRequest request) {
logger.info("设置用户[{}]", userName);
request.getSession().setAttribute("userName", userName);
return ResultModel.ok();
}
@RequestMapping(value = "/user")
public ResultModel user(HttpServletRequest request) {
Object userName = request.getSession().getAttribute("userName");
if(userName == null)return ResultModel.error("无用户");
return ResultModel.ok(userName);
}
@RequestMapping(value = "/userList")
public ResultModel userList() {
return ResultModel.ok(WebSocketUser.getUserList());
}
}
为了实现我们的简单聊天功能,我们需要前端进行配合。
chat.html实现了简单的聊天室,支持文字、表情、文件等:
该html需要很多js配合,下面贴出html和websocket.js,其他js都是很普遍的js,如果需要我发送,加入群聊向群主索要。
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>品茗IT-WebSocket测试</title>
<!-- CSS -->
<link href="https://lib.baomitu.com/material-design-icons/3.0.1/iconfont/material-icons.min.css" rel="stylesheet">
<link href="https://lib.baomitu.com/materialize/0.100.2/css/materialize.min.css" type="text/css" rel="stylesheet" media="screen,projection"/>
<style>
body { text-align:left; margin:0; font:normal 12px Verdana, Arial;
background:#FFEEFF } form { margin:0; font:normal 12px Verdana,
Arial; } table,input { font:normal 12px Verdana, Arial; }
a:link,a:visited{ text-decoration:none; color:#333333; } a:hover{
text-decoration:none; color:#FF6600 } #main { width:400px;
position:absolute; left:600px; top:100px; background:#EFEFFF;
text-align:left; filter:Alpha(opacity=90) } #ChatHead {
text-align:right; padding:3px; border:1px solid #003399;
background:#DCDCFF; font-size:20px; color:#3366FF; cursor:move; }
#ChatHead a:link,#ChatHead a:visited, { font-size:14px;
font-weight:bold; padding:0 3px } #ChatBody { border:1px solid
#003399; border-top:none; padding:2px; } #ChatContent {
height:200px; padding:6px; overflow-y:scroll; word-break: break-all
}#ChatBtn { border-top:1px solid #003399; padding:2px }
</style>
</head>
<script type="text/javascript">
var ws = null;
var curUser=null;
var chatUser = null;
var imgName = null;
var fileImgSize = 0;
window.onbeforeunload = function()
{
disconnect(ws);
}
function gs(d) {
var t = document.getElementById(d);
if (t) {
return t.style;
} else {
return null;
}
}
function gs2(d, a) {
if (d.currentStyle) {
var curVal = d.currentStyle[a]
} else {
var curVal = document.defaultView
.getComputedStyle(d, null)[a]
}
return curVal;
}
function ChatHidden() {
gs("ChatBody").display = "none";
}
function ChatShow() {
gs("ChatBody").display = "";
}
function ChatClose() {
gs("main").display = "none";
}
function ChatNew(userId) {
gs("main").display = "";
chatUser = userId;
$("#ChatUsers").html(chatUser);
$('.emotion').qqFace({
id : 'facebox',
assign:'saytext',
path: './img/arclist/' //表情存放的路径
});
}
function ChatClear(obj) {
$("#ChatContent").html("");
}
function ChatRead() {
if(document.getElementById(chatUser)){
document.getElementById(chatUser).setAttribute('src', './img/users.png');
}
}
function ChatSend(obj) {
var o = obj.ChatValue;
var msg = replace_em(o.value);
if (o.value.length > 0) {
$("#ChatContent").append(
"<p align=\"right\"><strong>" + curUser + "(我) :</strong>" + msg
+ "</p>");
var number = $("#ChatContent").scrollTop();
number += 16;
$("#ChatContent").scrollTop(number);
if(ws!=null){
var json={"fromUserName":curUser,"targetUserName":chatUser,"message":o.value,"messageType":"1000"};
// encodeURI(o.value)
console.log(json);
ws.send(JSON.stringify(json));
}
o.value = '';
}
var img = obj.ChatFile;
if (img.value.length > 0){
$("#ChatContent").append(
"<p align=\"right\"><strong>" + nickName + "(我) :</strong>" + img.value
+ "</p><br/>");
imgName = nickName+'(我)';
fileImgSize = img.files.length;
//alert(fileImgSize);
$.ajaxFileUpload({
//处理文件上传操作的服务器端地址(可以传参数,已亲测可用)
url:'im/fileUpload?userId='+muserId,
secureuri:true, //是否启用安全提交,默认为false
fileElementId:'ChatFile', //文件选择框的id属性
dataType:'text', //服务器返回的格式,可以是json或xml等
success:function(data, status){ //服务器响应成功时的处理函数
//$("#ChatContent").append("<p align=\"right\">" + data + "</p><br/>");
},
error:function(data, status, e){ //服务器响应失败时的处理函数
$("#ChatContent").append('<p align=\"right\">图片上传失败,请重试!!</p><br/>');
imgName = msgUser;
}
});
}
}
if (document.getElementById) {
(function() {
if (window.opera) {
document.write("<input type='hidden' id='Q' value=' '>");
}
var n = 500;
var dragok = false;
var y, x, d, dy, dx;
function move(e) {
if (!e)
e = window.event;
if (dragok) {
d.style.left = dx + e.clientX - x + "px";
d.style.top = dy + e.clientY - y + "px";
return false;
}
}
function down(e) {
if (!e)
e = window.event;
var temp = (typeof e.target != "undefined") ? e.target
: e.srcElement;
if (temp.tagName != "HTML" | "BODY"
&& temp.className != "dragclass") {
temp = (typeof temp.parentNode != "undefined") ? temp.parentNode
: temp.parentElement;
}
if ('TR' == temp.tagName) {
temp = (typeof temp.parentNode != "undefined") ? temp.parentNode
: temp.parentElement;
temp = (typeof temp.parentNode != "undefined") ? temp.parentNode
: temp.parentElement;
temp = (typeof temp.parentNode != "undefined") ? temp.parentNode
: temp.parentElement;
}
if (temp.className == "dragclass") {
if (window.opera) {
document.getElementById("Q").focus();
}
dragok = true;
temp.style.zIndex = n++;
d = temp;
dx = parseInt(gs2(temp, "left")) | 0;
dy = parseInt(gs2(temp, "top")) | 0;
x = e.clientX;
y = e.clientY;
document.onmousemove = move;
return false;
}
}
function up() {
dragok = false;
document.onmousemove = null;
}
document.onmousedown = down;
document.onmouseup = up;
})();
}
function toIndex(){
window.location.href= contextPath + "/index.jsp";
}
</script>
<body>
<div id="main" class="dragclass" onclick="ChatRead()" style="left: 400px; top: 200px;">
<div id="ChatUsers" style="width:100px; padding:3px; font-size:15px;float:left; display:inline"></div>
<div id="ChatHead">
<a href="#" onclick="ChatHidden();">-</a> <a href="#"
onclick="ChatShow();">+</a> <a href="#" onclick="ChatClose();">x</a>
</div>
<div id="ChatBody">
<div id="ChatContent"></div>
<div id="ChatBtn">
<form action="" name="chat" method="post">
<textarea name="ChatValue" id="saytext" rows="3" style="width: 350px"></textarea>
<input name="Submit" type="button" value="发送"
onclick="ChatSend(this.form);" />
<input name="ClearMsg" type="button" value="清空记录"
onclick="ChatClear(this.form);" />
<input type="button" class="emotion" value="表情">
<input id="ChatFile" type="file" name="myfiles" multiple>
</form>
</div>
</div>
</div>
<div id="modalAddUser" class="modal modal-fixed-footer" style="max-width:400px;max-height:400px">
<div class="modal-content">
<h4>生成用户名</h4>
<div class="row center">
<input class="browser-default searchInput" placeholder="请输入用户名" style="margin-top:50px;margin-left:20px;max-width:300px" id="catoryAddText" type="text" >
</div>
<div class="row center">
<a class="waves-effect waves-light btn" id="userAddBtn" style="color:white;"><i class="material-icons" style="font-size:1.1rem">添用户</i></a>
</div>
</div>
<div class="modal-footer">
<a href="#!" class=" modal-action modal-close waves-effect waves-green btn-flat">关闭</a>
</div>
</div>
<div align="left" style="margin-top: 50px;margin-left: 20px;">
<p>欢迎您,
<span id="userName">匿名用户</span>
</p>
<a id="addUser" class="btn waves-effect waves-light white cyan-text" style="border-radius: 40px;">添加用户</a>
<p id="content"></p>
</div>
<script src="https://lib.baomitu.com/jquery/3.3.0/jquery.min.js"></script>
<script src="https://lib.baomitu.com/materialize/0.100.2/js/materialize.min.js"></script>
<script src="./js/websocket.js"></script>
<script src="./js/ajaxfileupload.js"></script>
<script src="./js/jquery-browser.js"></script>
<script src="./js/jquery.qqFace.js"></script>
<script>
function getUser(){
$.ajax({
type : "get",
url : "../im/user",
dataType : "json",
data : {} ,
success : function(data) {
if(data.errorCode == "0000"){
$("#userName").html(data.data);
curUser = data.data;
}
},
error : function(XMLHttpRequest, textStatus, errorThrown) {
alert(errorThrown);
}
});
}
function addUser(userName){
$.ajax({
type : "post",
url : "../im/setUser",
dataType : "json",
data : {"userName":userName} ,
success : function(data) {
if(data.errorCode == "0000"){
$("#userName").html(userName);
curUser = data.data;
}
},
error : function(XMLHttpRequest, textStatus, errorThrown) {
alert(errorThrown);
}
});
}
function userList(){
$.ajax({
type : "get",
url : "../im/userList",
dataType : "json",
data : {} ,
success : function(data) {
if(data.errorCode == "0000"){
var content = "";
for(var i =0;i<data.data.length;i++){
var userId = data.data[i];
content += "<img src=\"./img/msgget.gif\" id=\""
+ userId
+ "\" alt=\"\" style=\"cursor: pointer\" width='40px' "
+ "onclick=\"ChatNew('"+userId+"')\" />"
+ userId
+ "<br><br>";
}
$("#content").append(content);
}
},
error : function(XMLHttpRequest, textStatus, errorThrown) {
alert(errorThrown);
}
});
}
window.onbeforeunload = function(){
disconnect(ws);
}
$(function () {
$('.modal').modal({
dismissible: true, // 点击模态外面模态消失关闭
opacity: 0.1, // 相对于背景的不透明度
in_duration: 300, // 显示特效的时间
out_duration: 200, // 消失特效时间
starting_top: '80%', // 启动时的样式属性
ending_top: '20%', // 结束时的样式属性
ready: function(modal, trigger) { // 模态加载完成触发事件
},
complete: function() {
} // 关闭时触发的事件
});
getUser();
$("#addUser").click(function() {
$('#modalAddUser').modal('open');
});
$("#userAddBtn").click(function() {
var catory = $('#catoryAddText').val();
addUser(catory);
});
userList();
if (ws == null) {
var url = getUrl();
//alert("url:"+url);
if (!url) {
return;
}
console.log(url);
ws = new WebSocket(url);
connect(ws);
ChatClose();
}
});
</script>
</body>
<script>
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?e553ae2bb23494dee9b6f43415a1ce5a";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
</script>
</html>
这个html需要websocket.js配合:
function getUrl() {
var urlPath = "/websocket";
if (window.location.protocol == 'http:') {
url = 'ws://' + window.location.host + urlPath;
} else {
url = 'wss://' + window.location.host + urlPath;
}
return url;
}
function disconnect(ws) {
if (ws != null) {
ws.close();
ws = null;
}
}
function replace_em(str){
str = str.replace(/\[em_([0-9]*)\]/g,'<img src="../../img/arclist/$1.gif" border="0" />');
return str;
}
function connect(ws) {
ws.onopen = function () {
$("#ChatContent").append("<small>连接成功。。。</small><br>");
};
ws.onmessage = function (event) {
if(typeof(event.data)=="string"){
var dataAll = event.data;
var jsonData = JSON.parse(dataAll);
console.log(jsonData);
var msgType = jsonData.messageType;
if(msgType == "0000"){
$("#ChatContent").append("<strong>系统消息:</strong>" + jsonData.message + "<br>");
}else{
var data = jsonData.message;
var userId = jsonData.fromUserName;
var msg = jsonData.message;
var result = replace_em(msg);
if(document.getElementById(userId)){
document.getElementById(userId).setAttribute('src', './img/msgget.gif');
var number = $("#ChatContent").scrollTop();
//var number = $("#ChatContent").height();
number += 15;
$("#ChatContent").scrollTop(number);
$("#ChatContent").append("<strong>"+userId+" :</strong>" + result + "<br>");
}else{
var content = "<img src=\"./img/msgget.gif\" id=\""
+ userId
+ "\" alt=\"\" style=\"cursor: pointer\" width='40px' "
+ "onclick=\"ChatNew()\" />"
+ userId
+ "<br><br>";
$("#content").append(content);
$("#ChatContent").append("<strong>"+userId+" :</strong>" + result + "<br>");
}
}
}else{
var reader = new FileReader();
reader.onload = function(event){
if(event.target.readyState == FileReader.DONE){
var url = event.target.result;
if (imgName != msgUser){
$("#ChatContent").append("<p align=\"right\"><strong>"+imgName+" :</strong>"+"<img src = "+url+" width='100px'/></p><br>");
}else{
$("#ChatContent").append("<strong>"+imgName+" :</strong>"+"<img src = "+url+" width='100px'/><br>");
}
if (fileImgSize != 0){
fileImgSize = fileImgSize - 1;
}else{
imgName = msgUser;
}
}
}
reader.readAsDataURL(event.data);
}
};
ws.onclose = function (event) {
//alert('网络连接失败!');
};
}
聊天室界面如下:
在这里插入图片描述
MessageDTO:
package cn.pomit.springwork.websocket.dto;
public class MessageDTO {
private String fromUserName;
private String targetUserName;
private String message;
private String messageType;
public String getFromUserName() {
return fromUserName;
}
public void setFromUserName(String fromUserName) {
this.fromUserName = fromUserName;
}
public String getTargetUserName() {
return targetUserName;
}
public void setTargetUserName(String targetUserName) {
this.targetUserName = targetUserName;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getMessageType() {
return messageType;
}
public void setMessageType(String messageType) {
this.messageType = messageType;
}
public static enum Type {
TYPE_NEW("0000"), TYPE_TEXT("1000"), TYPE_BYTE("1001");
private String messageType;
Type(String messageType) {
this.messageType = messageType;
}
public String getMessageType() {
return messageType;
}
public void setMessageType(String messageType) {
this.messageType = messageType;
}
}
}
ResultModel:
package cn.pomit.springwork.websocket.dto;
/**
* @author cff
*/
public class ResultModel {
private String errorCode;
private String message;
private Object data;
public ResultModel() {
}
public ResultModel(String errorCode, String message) {
this.errorCode = errorCode;
this.message = message;
}
public ResultModel(String errorCode, String message, Object data) {
this.errorCode = errorCode;
this.message = message;
this.data = data;
}
public String geterrorCode() {
return errorCode;
}
public void seterrorCode(String errorCode) {
this.errorCode = errorCode;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static ResultModel ok() {
return new ResultModel("0000","成功");
}
public static ResultModel ok(Object data) {
return new ResultModel("0000","成功", data);
}
public static ResultModel error() {
return new ResultModel("1111","失败");
}
public static ResultModel error(String msg) {
return new ResultModel("1111","失败", msg);
}
public static ResultModel error(String msg, Object data) {
return new ResultModel("1111", msg, data);
}
}