前言

  之前写毕业设计的时候就想加上聊天系统,当时已经用ajax长轮询实现了一个(还不懂什么是轮询机制的,猛戳这里:https://www.cnblogs.com/hoojo/p/longPolling_comet_jquery_iframe_ajax.html),但由于种种原因没有加到毕设里面。后来回校答辩后研究了一下websocket,并参照网上资料写了一个简单的聊天,现在又重新整理并记录下来。

  以下介绍来自维基百科:
  WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket通信协议于2011年被IETF定为标准RFC 6455,并由RFC7936补充规范。WebSocket API也被W3C定为标准。
WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

  这里可以看一下官网介绍:http://www.websocket.org/aboutwebsocket.html
  官网里面介绍非常详细,我就不做搬运工了,要是有像我一样英语不好的同学,右键->翻译成简体中文

  spring对websocket的支持:https://docs.spring.io/spring/docs/4.3.13.RELEASE/spring-framework-reference/htmlsingle/#websocket
  这里有一份spring对websocket的详细介绍:https://docs.spring.io/spring/docs/5.1.8.RELEASE/spring-framework-reference/web.html#websocket

  四个大章节,内容很多,就不一一展开介绍了

  

  效果

  2019/04/30补充:我们这个登录、登出非常简单,就一个请求地址,连页面都没有,所以刚开始我就没有贴出来,导致博客文章阅读起来比较吃力,现在在这里补充一下(由于项目后面有所改动,请求地址少了 springboot/,不过大家看得懂就行),这里只是一个小demo,所有就怎么简单怎么来

  登录 http://localhost:10086/websocket/login/huanzi,

  登出 http://localhost:10086/websocket/logout/huanzi

    

  上下线有提示

  如果这时候发送消息给离线的人,则会收到系统提示消息

  群聊

  本例中,点击自己是群聊窗口

  huanzi一发送群聊,laowang跟xiaofang都不是在当前群聊窗口,出现小圆点+1

  

  huanzi一发送群聊,xiaofang在当前群聊窗口,直接追加消息,老王不在对应的聊天窗口,出现小圆点+1

  xiaofang回复,huanzi直接追加消息,laowang依旧小圆点+1

  laowang点击群聊窗口,小圆点消失,追加群聊消息

  laowang参与群聊

  xiaofang切出群聊窗口,laowang在群聊发送消息,xiaofang出现小圆点+1

  切回来,小圆点消失,聊天数据正常接收追加

  三方正常参与聊天

  私聊

  huanzis私聊xiaofang,xiaofang聊天窗口在群聊,小圆点+1,而laowang不受影响

  xiaofang切到私聊窗口,小圆点消失,数据正常追加;huanzi刚好处于私聊窗口,数据直接追加

  效果演示到此结束,下面贴出代码

  代码编写

  首先先介绍一下项目结构

  maven

        <!-- springboot websocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency> <!-- thymeleaf模板 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

  配置文件

#修改thymeleaf访问根路径
spring.thymeleaf.prefix=classpath:/view/
socketChart.css样式
body{
background-color: #efebdc;
}
#hz-main{
width: 700px;
height: 500px;
background-color: red;
margin: 0 auto;
} #hz-message{
width: 500px;
height: 500px;
float: left;
background-color: #B5B5B5;
} #hz-message-body{
width: 460px;
height: 340px;
background-color: #E0C4DA;
padding: 10px 20px;
overflow:auto;
} #hz-message-input{
width: 500px;
height: 99px;
background-color: white;
overflow:auto;
} #hz-group{
width: 200px;
height: 500px;
background-color: rosybrown;
float: right;
} .hz-message-list{
min-height: 30px;
margin: 10px 0;
}
.hz-message-list-text{
padding: 7px 13px;
border-radius: 15px;
width: auto;
max-width: 85%;
display: inline-block;
} .hz-message-list-username{
margin:;
} .hz-group-body{
overflow:auto;
} .hz-group-list{
padding: 10px;
} .left{
float: left;
color: #595a5a;
background-color: #ebebeb;
}
.right{
float: right;
color: #f7f8f8;
background-color: #919292;
}
.hz-badge{
width: 20px;
height: 20px;
background-color: #FF5722;
border-radius: 50%;
float: right;
color: white;
text-align: center;
line-height: 20px;
font-weight: bold;
opacity:;
}

  

socketChart.html页面
<!DOCTYPE>
<!--解决idea thymeleaf 表达式模板报红波浪线-->
<!--suppress ALL -->
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>聊天页面</title>
<!-- jquery在线版本 -->
<script src="http://libs.baidu.com/jquery/2.1.4/jquery.min.js"></script>
<!--引入样式-->
<link th:href="@{/css/socketChart.css}" rel="stylesheet" type="text/css"/>
</head>
<body>
<div id="hz-main">
<div id="hz-message">
<!-- 头部 -->
正在与<span id="toUserName"></span>聊天
<hr style="margin: 0px;"/>
<!-- 主体 -->
<div id="hz-message-body">
</div>
<!-- 功能条 -->
<div id="">
<button>表情</button>
<button>图片</button>
<button id="videoBut">视频</button>
<button onclick="send()" style="float: right;">发送</button>
</div>
<!-- 输入框 -->
<div contenteditable="true" id="hz-message-input"> </div>
</div>
<div id="hz-group">
登录用户:<span id="talks" th:text="${username}">请登录</span>
<br/>
在线人数:<span id="onlineCount">0</span>
<!-- 主体 -->
<div id="hz-group-body"> </div>
</div>
</div>
</body>
<script type="text/javascript" th:inline="javascript">
//项目根路径
var ctx = [[${#request.getContextPath()}]];//登录名
var username = /*[[${username}]]*/'';
</script>
<script th:src="@{/js/socketChart.js}"></script>
</html>

  

socketChart.js 逻辑代码
    //消息对象数组
var msgObjArr = new Array(); var websocket = null; //判断当前浏览器是否支持WebSocket, springboot是项目名
if ('WebSocket' in window) {
websocket = new WebSocket("ws://localhost:10086/springboot/websocket/"+username);
} else {
console.error("不支持WebSocket");
} //连接发生错误的回调方法
websocket.onerror = function (e) {
console.error("WebSocket连接发生错误");
}; //连接成功建立的回调方法
websocket.onopen = function () {
//获取所有在线用户
$.ajax({
type: 'post',
url: ctx + "/websocket/getOnlineList",
contentType: 'application/json;charset=utf-8',
dataType: 'json',
data: {username:username},
success: function (data) {
if (data.length) {
//列表
for (var i = 0; i < data.length; i++) {
var userName = data[i];
$("#hz-group-body").append("<div class=\"hz-group-list\"><span class='hz-group-list-username'>" + userName + "</span><span id=\"" + userName + "-status\">[在线]</span><div id=\"hz-badge-" + userName + "\" class='hz-badge'>0</div></div>");
} //在线人数
$("#onlineCount").text(data.length);
}
},
error: function (xhr, status, error) {
console.log("ajax错误!");
}
});
} //接收到消息的回调方法
websocket.onmessage = function (event) {
var messageJson = eval("(" + event.data + ")"); //普通消息(私聊)
if (messageJson.type == "1") {
//来源用户
var srcUser = messageJson.srcUser;
//目标用户
var tarUser = messageJson.tarUser;
//消息
var message = messageJson.message; //最加聊天数据
setMessageInnerHTML(srcUser.username,srcUser.username, message);
} //普通消息(群聊)
if (messageJson.type == "2"){
//来源用户
var srcUser = messageJson.srcUser;
//目标用户
var tarUser = messageJson.tarUser;
//消息
var message = messageJson.message; //最加聊天数据
setMessageInnerHTML(username,tarUser.username, message);
} //对方不在线
if (messageJson.type == "0"){
//消息
var message = messageJson.message; $("#hz-message-body").append(
"<div class=\"hz-message-list\" style='text-align: center;'>" +
"<div class=\"hz-message-list-text\">" +
"<span>" + message + "</span>" +
"</div>" +
"</div>");
} //在线人数
if (messageJson.type == "onlineCount") {
//取出username
var onlineCount = messageJson.onlineCount;
var userName = messageJson.username;
var oldOnlineCount = $("#onlineCount").text(); //新旧在线人数对比
if (oldOnlineCount < onlineCount) {
if($("#" + userName + "-status").length > 0){
$("#" + userName + "-status").text("[在线]");
}else{
$("#hz-group-body").append("<div class=\"hz-group-list\"><span class='hz-group-list-username'>" + userName + "</span><span id=\"" + userName + "-status\">[在线]</span><div id=\"hz-badge-" + userName + "\" class='hz-badge'>0</div></div>");
}
} else {
//有人下线
$("#" + userName + "-status").text("[离线]");
}
$("#onlineCount").text(onlineCount);
} } //连接关闭的回调方法
websocket.onclose = function () {
//alert("WebSocket连接关闭");
} //将消息显示在对应聊天窗口 对于接收消息来说这里的toUserName就是来源用户,对于发送来说则相反
function setMessageInnerHTML(srcUserName,msgUserName, message) {
//判断
var childrens = $("#hz-group-body").children(".hz-group-list");
var isExist = false;
for (var i = 0; i < childrens.length; i++) {
var text = $(childrens[i]).find(".hz-group-list-username").text();
if (text == srcUserName) {
isExist = true;
break;
}
}
if (!isExist) {
//追加聊天对象
msgObjArr.push({
toUserName: srcUserName,
message: [{username: msgUserName, message: message, date: NowTime()}]//封装数据
});
$("#hz-group-body").append("<div class=\"hz-group-list\"><span class='hz-group-list-username'>" + srcUserName + "</span><span id=\"" + srcUserName + "-status\">[在线]</span><div id=\"hz-badge-" + srcUserName + "\" class='hz-badge'>0</div></div>");
} else {
//取出对象
var isExist = false;
for (var i = 0; i < msgObjArr.length; i++) {
var obj = msgObjArr[i];
if (obj.toUserName == srcUserName) {
//保存最新数据
obj.message.push({username: msgUserName, message: message, date: NowTime()});
isExist = true;
break;
}
}
if (!isExist) {
//追加聊天对象
msgObjArr.push({
toUserName: srcUserName,
message: [{username: msgUserName, message: message, date: NowTime()}]//封装数据
});
}
} // 对于接收消息来说这里的toUserName就是来源用户,对于发送来说则相反
var username = $("#toUserName").text(); //刚好打开的是对应的聊天页面
if (srcUserName == username) {
$("#hz-message-body").append(
"<div class=\"hz-message-list\">" +
"<p class='hz-message-list-username'>"+msgUserName+":</p>" +
"<div class=\"hz-message-list-text left\">" +
"<span>" + message + "</span>" +
"</div>" +
"<div style=\" clear: both; \"></div>" +
"</div>");
} else {
//小圆点++
var conut = $("#hz-badge-" + srcUserName).text();
$("#hz-badge-" + srcUserName).text(parseInt(conut) + 1);
$("#hz-badge-" + srcUserName).css("opacity", "1");
}
} //发送消息
function send() {
//消息
var message = $("#hz-message-input").html();
//目标用户名
var tarUserName = $("#toUserName").text();
//登录用户名
var srcUserName = $("#talks").text();
websocket.send(JSON.stringify({
"type": "1",
"tarUser": {"username": tarUserName},
"srcUser": {"username": srcUserName},
"message": message
}));
$("#hz-message-body").append(
"<div class=\"hz-message-list\">" +
"<div class=\"hz-message-list-text right\">" +
"<span>" + message + "</span>" +
"</div>" +
"</div>");
$("#hz-message-input").html("");
//取出对象
if (msgObjArr.length > 0) {
var isExist = false;
for (var i = 0; i < msgObjArr.length; i++) {
var obj = msgObjArr[i];
if (obj.toUserName == tarUserName) {
//保存最新数据
obj.message.push({username: srcUserName, message: message, date: NowTime()});
isExist = true;
break;
}
}
if (!isExist) {
//追加聊天对象
msgObjArr.push({
toUserName: tarUserName,
message: [{username: srcUserName, message: message, date: NowTime()}]//封装数据[{username:huanzi,message:"你好,我是欢子!",date:2018-04-29 22:48:00}]
});
}
} else {
//追加聊天对象
msgObjArr.push({
toUserName: tarUserName,
message: [{username: srcUserName, message: message, date: NowTime()}]//封装数据[{username:huanzi,message:"你好,我是欢子!",date:2018-04-29 22:48:00}]
});
}
} //监听点击用户
$("body").on("click", ".hz-group-list", function () {
$(".hz-group-list").css("background-color", "");
$(this).css("background-color", "whitesmoke");
$("#toUserName").text($(this).find(".hz-group-list-username").text()); //清空旧数据,从对象中取出并追加
$("#hz-message-body").empty();
$("#hz-badge-" + $("#toUserName").text()).text("0");
$("#hz-badge-" + $("#toUserName").text()).css("opacity", "0");
if (msgObjArr.length > 0) {
for (var i = 0; i < msgObjArr.length; i++) {
var obj = msgObjArr[i];
if (obj.toUserName == $("#toUserName").text()) {
//追加数据
var messageArr = obj.message;
if (messageArr.length > 0) {
for (var j = 0; j < messageArr.length; j++) {
var msgObj = messageArr[j];
var leftOrRight = "right";
var message = msgObj.message;
var msgUserName = msgObj.username;
var toUserName = $("#toUserName").text(); //当聊天窗口与msgUserName的人相同,文字在左边(对方/其他人),否则在右边(自己)
if (msgUserName == toUserName) {
leftOrRight = "left";
} //但是如果点击的是自己,群聊的逻辑就不太一样了
if (username == toUserName && msgUserName != toUserName) {
leftOrRight = "left";
} if (username == toUserName && msgUserName == toUserName) {
leftOrRight = "right";
} var magUserName = leftOrRight == "left" ? "<p class='hz-message-list-username'>"+msgUserName+":</p>" : ""; $("#hz-message-body").append(
"<div class=\"hz-message-list\">" +
magUserName+
"<div class=\"hz-message-list-text " + leftOrRight + "\">" +
"<span>" + message + "</span>" +
"</div>" +
"<div style=\" clear: both; \"></div>" +
"</div>");
}
}
break;
}
}
}
}); //获取当前时间
function NowTime() {
var time = new Date();
var year = time.getFullYear();//获取年
var month = time.getMonth() + 1;//或者月
var day = time.getDate();//或者天
var hour = time.getHours();//获取小时
var minu = time.getMinutes();//获取分钟
var second = time.getSeconds();//或者秒
var data = year + "-";
if (month < 10) {
data += "0";
}
data += month + "-";
if (day < 10) {
data += "0"
}
data += day + " ";
if (hour < 10) {
data += "0"
}
data += hour + ":";
if (minu < 10) {
data += "0"
}
data += minu + ":";
if (second < 10) {
data += "0"
}
data += second;
return data;
}

  java代码有三个类,MyEndpointConfigure,WebSocketConfig,WebSocketServer;

  MyEndpointConfigure

/**
* 解决注入其他类的问题,详情参考这篇帖子:webSocket无法注入其他类:https://blog.csdn.net/tornadojava/article/details/78781474
*/
public class MyEndpointConfigure extends ServerEndpointConfig.Configurator implements ApplicationContextAware { private static volatile BeanFactory context; @Override
public <T> T getEndpointInstance(Class<T> clazz){
return context.getBean(clazz);
} @Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
MyEndpointConfigure.context = applicationContext;
}
}

  WebSocketConfig

/**
* WebSocket配置
*/
@Configuration
public class WebSocketConfig{ /**
* 用途:扫描并注册所有携带@ServerEndpoint注解的实例。 @ServerEndpoint("/websocket")
* PS:如果使用外部容器 则无需提供ServerEndpointExporter。
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
} /**
* 支持注入其他类
*/
@Bean
public MyEndpointConfigure newMyEndpointConfigure (){
return new MyEndpointConfigure ();
}
}

  WebSocketServer

/**
* WebSocket服务
*/
@RestController
@RequestMapping("/websocket")
@ServerEndpoint(value = "/websocket/{username}", configurator = MyEndpointConfigure.class)
public class WebSocketServer { /**
* 在线人数
*/
private static int onlineCount = 0; /**
* 在线用户的Map集合,key:用户名,value:Session对象
*/
private static Map<String, Session> sessionMap = new HashMap<String, Session>(); /**
* 注入其他类(换成自己想注入的对象)
*/
@Autowired
private UserService userService; /**
* 连接建立成功调用的方法
*/
@OnOpen
public void onOpen(Session session, @PathParam("username") String username) {
//在webSocketMap新增上线用户
sessionMap.put(username, session); //在线人数加加
WebSocketServer.onlineCount++; //通知除了自己之外的所有人
sendOnlineCount(session, "{'type':'onlineCount','onlineCount':" + WebSocketServer.onlineCount + ",username:'" + username + "'}");
} /**
* 连接关闭调用的方法
*/
@OnClose
public void onClose(Session session) {
//下线用户名
String logoutUserName = ""; //从webSocketMap删除下线用户
for (Entry<String, Session> entry : sessionMap.entrySet()) {
if (entry.getValue() == session) {
sessionMap.remove(entry.getKey());
logoutUserName = entry.getKey();
break;
}
}
//在线人数减减
WebSocketServer.onlineCount--; //通知除了自己之外的所有人
sendOnlineCount(session, "{'type':'onlineCount','onlineCount':" + WebSocketServer.onlineCount + ",username:'" + logoutUserName + "'}");
} /**
* 服务器接收到客户端消息时调用的方法
*/
@OnMessage
public void onMessage(String message, Session session) {
try {
//JSON字符串转 HashMap
HashMap hashMap = new ObjectMapper().readValue(message, HashMap.class); //消息类型
String type = (String) hashMap.get("type"); //来源用户
Map srcUser = (Map) hashMap.get("srcUser"); //目标用户
Map tarUser = (Map) hashMap.get("tarUser"); //如果点击的是自己,那就是群聊
if (srcUser.get("username").equals(tarUser.get("username"))) {
//群聊
groupChat(session,hashMap);
} else {
//私聊
privateChat(session, tarUser, hashMap);
} //后期要做消息持久化 } catch (IOException e) {
e.printStackTrace();
}
} /**
* 发生错误时调用
*/
@OnError
public void onError(Session session, Throwable error) {
error.printStackTrace();
} /**
* 通知除了自己之外的所有人
*/
private void sendOnlineCount(Session session, String message) {
for (Entry<String, Session> entry : sessionMap.entrySet()) {
try {
if (entry.getValue() != session) {
entry.getValue().getBasicRemote().sendText(message);
}
} catch (IOException e) {
e.printStackTrace();
}
}
} /**
* 私聊
*/
private void privateChat(Session session, Map tarUser, HashMap hashMap) throws IOException {
//获取目标用户的session
Session tarUserSession = sessionMap.get(tarUser.get("username")); //如果不在线则发送“对方不在线”回来源用户
if (tarUserSession == null) {
session.getBasicRemote().sendText("{\"type\":\"0\",\"message\":\"对方不在线\"}");
} else {
hashMap.put("type", "1");
tarUserSession.getBasicRemote().sendText(new ObjectMapper().writeValueAsString(hashMap));
}
} /**
* 群聊
*/
private void groupChat(Session session,HashMap hashMap) throws IOException {
for (Entry<String, Session> entry : sessionMap.entrySet()) {
//自己就不用再发送消息了
if (entry.getValue() != session) {
hashMap.put("type", "2");
entry.getValue().getBasicRemote().sendText(new ObjectMapper().writeValueAsString(hashMap));
}
}
} /**
* 登录
*/
@RequestMapping("/login/{username}")
public ModelAndView login(HttpServletRequest request, @PathVariable String username) {
return new ModelAndView("socketChart.html", "username", username);
} /**
* 登出
*/
@RequestMapping("/logout/{username}")
public String loginOut(HttpServletRequest request, @PathVariable String username) {
return "退出成功!";
} /**
* 获取在线用户
*/
@RequestMapping("/getOnlineList")
private List<String> getOnlineList(String username) {
List<String> list = new ArrayList<String>();
//遍历webSocketMap
for (Entry<String, Session> entry : WebSocketServer.sessionMap.entrySet()) {
if (!entry.getKey().equals(username)) {
list.add(entry.getKey());
}
}
return list;
} }

  后记

  后期把所有功能都补全就完美了,表情、图片都算比较简单,之前用轮询实现的时候写过了,但是没加到这里来;音视频聊天的话可以用WbeRTC来做,之前也研究了一下,不过还没搞完,这里贴一下维基百科对它的介绍,想了解更多的自行Google:

  WebRTC,名称源自网页即时通信(英语:Web Real-Time Communication)的缩写,是一个支持网页浏览器进行实时语音对话或视频对话的API。它于2011年6月1日开源并在Google、Mozilla、Opera支持下被纳入万维网联盟的W3C推荐标准。

  最后在加上持久化存储,注册后才能聊天,离线消息上线后接收,再加上用Redis或者其他的缓存技术支持,完美。不过聊天记录要做存储,表设计不知如何设计才合理,如果哪位大佬愿意分享可以留言给我,大家一起进步!

  补充

  2019-07-03补充:这里补充贴出pom代码,在子类引入父类,如果我们没有父类,只有一个子类,把两个整合一下就可以了

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.huanzi.qch</groupId>
<artifactId>parent</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging> <parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.0.RELEASE</version>
<relativePath/>
</parent> <description>SpringBoot系列demo代码</description> <!-- 在父类引入一下通用的依赖 -->
<dependencies>
<!-- spring-boot-starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency> <!-- springboot web(MVC)-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency> <!-- springboot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency> <!--lombok插件 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency> <!--热部署工具dev-tools-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
<scope>runtime</scope>
</dependency>
</dependencies> <!--构建工具-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<finalName>${project.artifactId}</finalName>
<outputDirectory>../package</outputDirectory>
</configuration>
</plugin>
</plugins>
</build>
</project>

parent.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>springboot-websocket</artifactId>
<version>0.0.1</version>
<name>springboot-websocket</name>
<description>SpringBoot系列——WebSocket</description> <!--继承父类-->
<parent>
<groupId>cn.huanzi.qch</groupId>
<artifactId>parent</artifactId>
<version>1.0.0</version>
</parent> <dependencies>
<!-- springboot websocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency> <!-- thymeleaf模板 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies> <build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build> </project>

springboot-websocket.xml

  在后记的部分我们就提到要加上持久化存储,事实上我们已经开始慢慢在写一套简单的IM即时通讯,已经实现到第三版了,持续更新中...

  一套简单的web即时通讯——第一版

  一套简单的web即时通讯——第二版

  一套简单的web即时通讯——第三版

  代码开源

  代码已经开源、托管到我的GitHub、码云:

  GitHub:https://github.com/huanzi-qch/springBoot

  码云:https://gitee.com/huanzi-qch/springBoot

WebSocket+Java 私聊、群聊实例的更多相关文章

  1. 【Java分享客栈】SpringBoot整合WebSocket+Stomp搭建群聊项目

    前言 前两周经常有大学生小伙伴私信给我,问我可否有偿提供毕设帮助,我说暂时没有这个打算,因为工作实在太忙,现阶段无法投入到这样的领域内,其中有两个小伙伴又问到我websocket该怎么使用,想给自己的 ...

  2. WebSocket 实现链接 群聊(low low low 版本)

    py 文件: """ 下载 gevent-websocket 0.10.1 基于Flask + geventWebSocket 建立连接,发送消息,实现群消息功能. &q ...

  3. 第五讲 smart qq poll包处理 以及 私聊 群聊消息收发

    发送 poll包 public static void Login_PostPoll() { try { string url = "http://d1.web2.qq.com/channe ...

  4. websocket学习和群聊实现

    WebSocket协议可以实现前后端全双工通信,从而取代浪费资源的长轮询.在此协议的基础上,可以实现前后端数据.多端数据,真正的实时响应.在学习WebSocket的过程中,实现了一个简化版群聊,过程和 ...

  5. 基于websocket搭建简易群聊

    1.前端HTML <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset=&qu ...

  6. Java Socket通信实现私聊、群聊

    前言 闲言少叙,上代码! 代码编写 server服务端 /** * 服务端 */ public class Server { private static ServerSocket server = ...

  7. Java 网络编程 -- 基于TCP 实现聊天室 群聊 私聊

    分析: 聊天室需要多个客户端和一个服务端. 服务端负责转发消息. 客户端可以发送消息.接收消息. 消息分类: 群聊消息:发送除自己外所有人 私聊消息:只发送@的人 系统消息:根据情况分只发送个人和其他 ...

  8. Flask 实现 WebSocket 通讯---群聊和私聊

    一.WebSocket介绍 WebSocket是一种在单个TCP连接实现了服务端和客户端进行双向文本或二进制数据通信的一种通信的协议. WebSocket使得客户端和服务器之间的数据交换变得更加简单, ...

  9. spring websocket 和socketjs实现单聊群聊,广播的消息推送详解

    spring websocket 和socketjs实现单聊群聊,广播的消息推送详解 WebSocket简单介绍 随着互联网的发展,传统的HTTP协议已经很难满足Web应用日益复杂的需求了.近年来,随 ...

随机推荐

  1. 无知小子踏入python web大门

    学习python flask的第一天,费劲我小白的脑子,总算完成了环境配置 详情如下,其他小白误走弯路,希望和我一样爱好python的人坚信:python或许是最好的语言! 那么,开始喽,有不对的地方 ...

  2. Pi的计算

    百度百科           圆周率用希腊字母 π(读作pài)表示,是一个常数(约等于3.141592654),是代表圆周长和直径的比值.它是一个无理数,即无限不循环小数.在日常生活中,通常都用3. ...

  3. 安装easygui

    1.下载0.96的easygui 官网: http://easygui.sourceforge.net/ 2.解压后得到文件夹,里面有两个文件分别为,setup.py和easygui.py 3.在py ...

  4. Using iSCSI On Ubuntu 10.04 (Initiator And Target)

    This guide explains how you can set up an iSCSI target and an iSCSI initiator (client), both running ...

  5. Docker学习笔记-磁盘挂载运行.netcore

    前言: 环境:centos7.5 64 位 正文: 首先我们在宿主机上安装 .NET Core SDK sudo rpm --import https://packages.microsoft.com ...

  6. 客户端ip获取蹲坑启示: 不要侥幸

    怎么获取一个客户端ip ? 我想这个问题,在网上遍地都是答案! 而且多半是像下面这样: public static String getIpAddress(HttpServletRequest req ...

  7. 图片格式PGM缩写

    PGM是Portable Gray Map的缩写.它是灰度图像格式中一种最简单的格式标准.另外两种与之相近的图片格式是PBM和PPM.它们分别相应着黑白图像和彩色图像. PGM的数据存放方式相比于JP ...

  8. 排除Transformation Errors

    当运行session时,会产生大量的Transformation Errors,这些Error会导致性能变慢 1 Transformation Errors导致性能降低的原因 当有大量记录有Trans ...

  9. 'QueryDict' object is not callable 错误解析

    我把request内置库和 requests库  给搞混了 requests使用来发送请求的, request 而是用来获取数据的 别看只有一个单词只差,却让我找了大半天 requests.post( ...

  10. Spring Boot到底是怎么运行的,你知道吗?

    导读 Spring Boot方式的项目开发已经逐步成为Java应用开发领域的主流框架,它不仅可以方便地创建生产级的Spring应用程序,还能轻松地通过一些注解配置与目前比较流行的微服务框架Spring ...