一、入门简介
正常聊天程序需要使用消息组件ActiveMQ或者Kafka等,这里是一个Websocket入门程序。

有人有疑问这个技术有什么作用,为什么要有它?
其实我们虽然有http协议,但是它有一个缺陷就是不能主动向客户端发送消息,而我们的基于Tcp协议的Websocket能够做到,所以这在多台机器之间通信提供了大大的方便。

二、入门案例
本案例使用Springboot+WebSocket+Thymeleaf

1.1pom.xml
<!-- websocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- StringUtils -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
1
2
3
4
5
6
7
8
9
10
1.2WebSocketConfig
package cn.xdl.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
* 开启WebSocket支持
* @author liurui
*
*/
@Configuration
public class WebSocketConfig {

@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1.3CheckCenterController连接测试
package cn.xdl.controller;

import java.io.IOException;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

import cn.xdl.utils.result.AllResult;

/**
* 调用WebSocketServer
* @author liurui
*
*/
@Controller
@RequestMapping("/checkcenter")
public class CheckCenterController {

//页面请求
@GetMapping("/socket/{cid}")
public ModelAndView socket(@PathVariable String cid) {
ModelAndView mav=new ModelAndView("/socket");
mav.addObject("cid", cid);
return mav;
}
//推送数据接口
@ResponseBody
@RequestMapping("/socket/push/{cid}")
public AllResult pushToWeb(@PathVariable String cid,String message) {
try {
WebSocketServer.sendInfo(message,cid);
} catch (IOException e) {
e.printStackTrace();
return AllResult.build(500,cid+"#"+e.getMessage());
}
return AllResult.ok(cid);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
1.4测试socket.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>websocket</title>
<script type="text/javascript" src="http://nim.prec-robot.com/jzrobot/liurui/js/jquery.js"></script>
</head>
<body>
<h3>websocket测试页面</h3>
<input type="hidden" name="cid" th:value="${cid}" id="cid">
<input type="button" value="测试" onclick="push()">
<script>
var cid = $("#cid").val();
console.info("cid===="+cid);
var socket;
if(typeof(WebSocket) == "undefined") {
console.log("您的浏览器不支持WebSocket");
}else{
console.log("您的浏览器支持WebSocket");
//实现化WebSocket对象,指定要连接的服务器地址与端口 建立连接
//等同于socket = new WebSocket("ws://localhost:8083/checkcentersys/websocket/20");
socket = new WebSocket(("http://localhost:8888/hello-boot/websocket/"+cid).replace("http","ws"));
//打开事件
socket.onopen = function() {
console.log("Socket 已打开");
//socket.send("这是来自客户端的消息" + location.href + new Date());
};
//获得消息事件
socket.onmessage = function(msg) {
console.log(msg.data);
//发现消息进入 开始处理前端触发逻辑
};
//关闭事件
socket.onclose = function() {
console.log("Socket已关闭");
};
//发生了错误事件
socket.onerror = function() {
alert("Socket发生了错误");
//此时可以尝试刷新页面
}
//离开页面时,关闭socket
//jquery1.8中已经被废弃,3.0中已经移除
// $(window).unload(function(){
// socket.close();
//});
}
function push(){
var socket = new WebSocket(("http://localhost:8888/hello-boot/websocket/push/"+cid).replace("http","ws"));
}
</script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
1.5聊天ImController
package cn.xdl.controller;

import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;

import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;

import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;

import cn.xdl.utils.result.AllResult;
import lombok.extern.slf4j.Slf4j;

/**
* websocket通信
* @author liurui
*
*/
@ServerEndpoint("/im/{userId}")
@Component
@Slf4j
public class ImController {

//静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
private static int onlineCount = 0;
//旧:concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
//private static CopyOnWriteArraySet<ImController> webSocketSet = new CopyOnWriteArraySet<ImController>();
//与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;
//新:使用map对象,便于根据userId来获取对应的WebSocket
private static ConcurrentHashMap<String,ImController> websocketList = new ConcurrentHashMap<>();
//接收sid
private String userId="";
/**
* 连接建立成功调用的方法*/
@OnOpen
public void onOpen(Session session,@PathParam("userId") String userId) {
this.session = session;
websocketList.put(userId,this);
log.info("websocketList->"+JSON.toJSONString(websocketList));
//webSocketSet.add(this); //加入set中
addOnlineCount(); //在线数加1
log.info("有新窗口开始监听:"+userId+",当前在线人数为" + getOnlineCount());
this.userId=userId;
try {
sendMessage(JSON.toJSONString(AllResult.ok("连接成功")));
} catch (IOException e) {
log.error("websocket IO异常");
}
}

/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
if(websocketList.get(this.userId)!=null){
websocketList.remove(this.userId);
//webSocketSet.remove(this); //从set中删除
subOnlineCount(); //在线数减1
log.info("有一连接关闭!当前在线人数为" + getOnlineCount());
}
}

/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息*/
@OnMessage
public void onMessage(String message, Session session) {
log.info("收到来自窗口"+userId+"的信息:"+message);
if(StringUtils.isNotBlank(message)){
JSONArray list=JSONArray.parseArray(message);
for (int i = 0; i < list.size(); i++) {
try {
//解析发送的报文
JSONObject object = list.getJSONObject(i);
String toUserId=object.getString("toUserId");
String contentText=object.getString("contentText");
object.put("fromUserId",this.userId);
//传送给对应用户的websocket
if(StringUtils.isNotBlank(toUserId)&&StringUtils.isNotBlank(contentText)){
ImController socketx=websocketList.get(toUserId);
//需要进行转换,userId
if(socketx!=null){
log.debug("服务端发送给客户端信息--object:{}",object);
socketx.sendMessage(JSON.toJSONString(AllResult.ok(object)));
//此处可以放置相关业务代码,例如存储到数据库
}
}
}catch (Exception e){
e.printStackTrace();
}
}
}
}

/**
*
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("发生错误");
error.printStackTrace();
}
/**
* 实现服务器主动推送
*/
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
}

/**
* 群发自定义消息
* */
/*public static void sendInfo(String message,@PathParam("userId") String userId) throws IOException {
log.info("推送消息到窗口"+userId+",推送内容:"+message);
for (ImController item : webSocketSet) {
try {
//这里可以设定只推送给这个sid的,为null则全部推送
if(userId==null) {
item.sendMessage(message);
}else if(item.userId.equals(userId)){
item.sendMessage(message);
}
} catch (IOException e) {
continue;
}
}
}*/

public static synchronized int getOnlineCount() {
return onlineCount;
}

public static synchronized void addOnlineCount() {
ImController.onlineCount++;
}

public static synchronized void subOnlineCount() {
ImController.onlineCount--;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
1.6聊天socket_im.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<style>
.chat_div{
width:500px;
height:500px;
border:1px solid red;
text-align:center;
}
.chat_send_div{
float:left;
width:196px;
height:498px;
border:1px solid blue;
}
.chat_receive_div{
float:right;
width:290px;
height:498px;
border:1px solid green;
}
</style>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
<script>
var socket;
function openSocket() {
if(typeof(WebSocket) == "undefined") {
console.log("您的浏览器不支持WebSocket");
}else{
console.log("您的浏览器支持WebSocket");
//实现化WebSocket对象,指定要连接的服务器地址与端口 建立连接
//等同于socket = new WebSocket("ws://localhost:8888/xxxx/im/25");
//var socketUrl="${request.contextPath}/im/"+$("#userId").val();
var socketUrl="http://localhost:8888/hello-boot/im/"+$("#userId").val();
socketUrl=socketUrl.replace("https","ws").replace("http","ws");
console.log(socketUrl)
socket = new WebSocket(socketUrl);
//打开事件
socket.onopen = function() {
console.log("websocket已打开");
//socket.send("这是来自客户端的消息" + location.href + new Date());
};
//获得消息事件
socket.onmessage = function(msg) {
console.log(msg);
//发现消息进入 开始处理前端触发逻辑contentText
//$.parseJSON(msg);
var data = msg.data;
var result = $.parseJSON(data);
var str = '';
if(result.data=="连接成功"){
console.info("test----if----"+data);
str +='<h3 style="color:blue;">';
str +='server:'+result.data;
str +='</h3>';
$(".chat_receive_div").append(str);
}else{
console.info("test---else-----"+result.data.contentText);
str +='<h3 style="color:blue;">';
str +='server:'+result.data.contentText;
str +='</h3>';
$(".chat_receive_div").append(str);
}
};
//关闭事件
socket.onclose = function() {
console.log("websocket已关闭");
};
//发生了错误事件
socket.onerror = function() {
console.log("websocket发生了错误");
}
}
}
function sendMessage() {
if(typeof(WebSocket) == "undefined") {
console.log("您的浏览器不支持WebSocket");
}else {
var str = '';
console.log("您的浏览器支持WebSocket");
console.log('[{"toUserId":"'+$("#toUserId").val()+'","contentText":"'+$("#contentText").val()+'"}]');
socket.send('[{"toUserId":"'+$("#toUserId").val()+'","contentText":"'+$("#contentText").val()+'"}]');
str +='<h3 style="color:gold;">';
str +=$("#toUserId").val()+':'+$("#contentText").val();
str +='</h3>';
$(".chat_send_div").append(str);
}
}
</script>
<body>
<p>【userId】:<div><input id="userId" name="userId" type="text" value="25"></div>
<p>【toUserId】:<div><input id="toUserId" name="toUserId" type="text" value="26"></div>
<p>【toUserId】:<div><input id="contentText" name="contentText" type="text" value="嗷嗷嗷"></div>
<p>【操作】:<div><a onclick="openSocket()">开启socket</a></div>
<p>【操作】:<div><a onclick="sendMessage()">发送消息</a></div>
<div class="chat_div">
<div class="chat_send_div">
<h3>
发送消息
</h3>
</div>
<div class="chat_receive_div">
<h3>
接受消息
</h3>
</div>
</div>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
本程序实现SpringBoot基于websocket的网页聊天。
---------------------

SpringBoot基于websocket的网页聊天的更多相关文章

  1. 基于flask的网页聊天室(四)

    基于flask的网页聊天室(四) 前言 接前天的内容,今天完成了消息的处理 具体内容 上次使用了flask_login做用户登录,但是直接访问login_requare装饰的函数会报401错误,这里可 ...

  2. workerman-chat(PHP开发的基于Websocket协议的聊天室框架)(thinkphp也是支持socket聊天的)

    workerman-chat(PHP开发的基于Websocket协议的聊天室框架)(thinkphp也是支持socket聊天的) 一.总结 1.下面链接里面还有一个来聊的php聊天室源码可以学习 2. ...

  3. SpringBoot+Vue+WebSocket 实现在线聊天

    一.前言 本文将基于 SpringBoot + Vue + WebSocket 实现一个简单的在线聊天功能 页面如下: 在线体验地址:http://www.zhengqingya.com:8101 二 ...

  4. Java和WebSocket开发网页聊天室

    小编心语:咳咳咳,今天又是聊天室,到现在为止小编已经分享了不下两个了,这一次跟之前的又不大相同,这一次是网页聊天室,具体怎么着,还请各位看官往下看~ Java和WebSocket开发网页聊天室 一.项 ...

  5. 基于flask的网页聊天室(三)

    基于flask的网页聊天室(三) 前言 继续上一次的内容,今天完成了csrf防御的添加,用户头像的存储以及用户的登录状态 具体内容 首先是添加csrf的防御,为整个app添加防御: from flas ...

  6. 基于flask的网页聊天室(二)

    基于flask的网页聊天室(二) 前言 接上一次的内容继续完善,今天完成的内容不是很多,只是简单的用户注册登录,内容具体如下 具体内容 这次要加入与数据哭交互的操作,所以首先要建立相关表结构,这里使用 ...

  7. 基于flask的网页聊天室(一)

    基于flask的网页聊天室(一) 基本目标 基于flask实现的web聊天室,具有基本的登录注册,多人发送消息,接受消息 扩展目标 除基本目标外添加当前在线人数,消息回复,markdown支持,历史消 ...

  8. Springboot整合WebSocket实现网页版聊天,快来围观!

  9. TogetherJS本地部署,基于websocket的网页即时视频、语音、文字聊天

    TogetherJS分为两大部分,一个是hu文件夹中的服务端:另外一个是TogetherJS文件夹中的Together.JS文件,包含了所有的网页文字.语音等操作. 需要预先安装Node.js,可以百 ...

随机推荐

  1. 函数绑定 bind

    函数拓展-bind bind实现的是:对函数绑定作用域 更改作用域的方法:call,apply,with,eval,bind call 和 apply 的比较 相同点:1.都是在使用时候(使用即执行) ...

  2. JQuery编程demo练习

    JQuery练习demo:     编敲代码,实现:     1.选中当中一列的复选框时,该复选框所在行的背景色高亮显示(黄色). 2.取消选中复选框时,所在行的背景色恢复. ============ ...

  3. HDU 5411 CRB and puzzle (Dp + 矩阵高速幂)

    CRB and Puzzle Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others) T ...

  4. Java小白手记:WEB项目等

    机缘巧合之下,工作中得以用一下java.我向来对java很感兴趣,想从.NET转到java久矣,机会难得,久旱逢甘霖. 这次主要是跟web项目有关.在此之前,我了解到JAVA分为三大块:j2se.j2 ...

  5. Codeforces Round #319 (Div. 2) C. Vasya and Petya's Game 数学题

                                                     C. Vasya and Petya's Game                           ...

  6. windows console Kill PID 端口查看

    开始--运行--cmd 进入命令提示符 输入netstat -ano 即可看到所有连接的PID 之后在任务管理器中找到这个PID所对应的程序如果任务管理器中没有PID这一项,可以在任务管理器中选&qu ...

  7. 关于Web API中使用ajax发送请求方式的问题

    参见以下文章 http://www.west-wind.com/weblog/posts/2012/May/08/Passing-multiple-POST-parameters-to-Web-API ...

  8. iOS10 国行iPhone联网权限问题处理

    在iOS10上安装新App, 第一次打开时会询问用户"是否允许应用使用数据"(仅针对国行且需要连接移动网络的设备), 如下图所示, 在用户点击允许之前, App是无法联网的, 这意 ...

  9. bzoj4407

    http://www.lydsy.com/JudgeOnline/problem.php?id=4407 以前写过一次线性筛 发现不是很理解 写了个欧拉筛的 t了 其实每次推式子,都会先推出一组的解法 ...

  10. NTFS中的ADS的一些问题

    有关ADS的简单说明请看http://www.xfocus.net/articles/200212/466.html 可以看到ADS在很久以前就被一些安全人员所关注,并且也提出了一些经典的利用,比如隐 ...