spring websocket 教程,spring websocket 实现聊天室功能
1.背景
了解计算机网络协议的人,应该都知道:HTTP 协议是一种无状态的、无连接的、单向的应用层协议。它采用了请求/响应模型。通信请求只能由客户端发起,服务端对请求做出应答处理。
这种通信模型有一个弊端:HTTP 协议无法实现服务器主动向客户端发起消息。
这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。大多数 Web 应用程序将通过频繁的异步JavaScript和XML(AJAX)请求实现长轮询。轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。
因此,工程师们一直在思考,有没有更好的方法。WebSocket 就是这样发明的。WebSocket 连接允许客户端和服务器之间进行全双工通信,以便任一方都可以通过建立的连接将数据推送到另一端。WebSocket 只需要建立一次连接,就可以一直保持连接状态。这相比于轮询方式的不停建立连接显然效率要大大提高。
2.websocket事件
open | Socket.onopen | 连接建立时触发 |
message | Socket.onmessage | 客户端接收服务端数据时触发 |
error | Socket.onerror | 通信发生错误时触发 |
close | Socket.onclose | 连接关闭时触发 |
3.websocket方法
Socket.send() |
使用连接发送数据 |
Socket.close() |
关闭连接 |
4.websocket属性
Socket.readyState |
只读属性 readyState 表示连接状态,可以是以下值:
|
Socket.bufferedAmount |
只读属性 bufferedAmount 已被 send() 放入正在队列中等待传输,但是还没有发出的 UTF-8 文本字节数。 |
5.实例
前端代码
<%@ page language="java" contentType="text/html; charset=utf-8"
pageEncoding="utf-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Insert title here</title>
<link href="${pageContext.request.contextPath }/resources/css/demo.css"
rel="stylesheet" type="text/css">
</head>
<body>
<h4>欢迎您:<span>${SESSION_USERNAME }</span></h4>
<div id="box">
<div id="title">
<p id="receiver" style="background: #cacaca;text-align: center;">websocket聊天室</p>
</div>
<div id="firends">
<P id="xxp">在线好友</P>
<ul>
</ul>
</div>
<div id="divxx"></div>
</div>
<div>
<textarea id="context"></textarea>
</div>
<button id="dianji">发送</button>
</body>
</html>
<script type="text/javascript"
src="${pageContext.request.contextPath }/resources/js/jquery-3.0.0.js"></script>
<script type="text/javascript"
src="${pageContext.request.contextPath }/resources/js/demo.js"></script>
<script type="text/javascript">
var websocket = null;
var target = {}; // 封装发送数据的json
if('WebSocket' in window) {
websocket = new WebSocket("ws://localhost:8080${pageContext.request.contextPath}/websocket");
} else if('MozWebSocket' in window) {
websocket = new MozWebSocket("ws://localhost:8080${pageContext.request.contextPath}/websocket");
}
websocket.onopen = onOpen;
websocket.onmessage = onMessage;
websocket.onerror = onError;
websocket.onclose = onClose;
function onOpen(openEvt) {}
function onMessage(evt) {
var val = $("#context").val();
var msg = JSON.parse(evt.data);
if(msg.type=="firendList"){
var fir =msg.data;
$("ul").empty();
for(var i=0;i<fir.length;i++){
if(fir[i]!='${SESSION_USERNAME }'){
$("ul").append(
"<li>"+fir[i]+"</li>"
);
}
}
}else if(msg.type=="message"){
if(msg.sender=='${SESSION_USERNAME }'){
$("#divxx").append(
"<div class='clearFolat'>" +
"<div id='msg' style='folat:right;'>" +
"<p id='nickname' style='text-align: right;' >${SESSION_USERNAME }</p>" +
"<p id='message' style='text-align: right;'>" +
val +
"</p>" +
"</div>" +
"<div style='folat:right;' id='log'><img src='${pageContext.request.contextPath }/resources/img/lol.jpg' width='50px' height='50px' /></div>" +
"</div>"
);
}else{
$("#divxx").append(
"<div class='clearFolat'>" +
"<div id='log'><img src='${pageContext.request.contextPath }/resources/img/lol.jpg' width='50px' height='50px' /></div>" +
"<div id='msg' >" +
"<p id='nickname'>"+msg.sender+"</p>" +
"<p id='message'>" +
msg.data +
"</p>" +
"</div>" +
"</div>"
);
}
}
document.getElementById("context").value = "";
duiqi();
}
function onError() {}
function onClose() {}
function doSend() {
if(websocket.readyState == websocket.OPEN) {
var msg = document.getElementById("context").value;
target.data = msg;
target.sender = '${SESSION_USERNAME }';
console.log(target)
websocket.send(JSON.stringify(target)); // 调用后台handleTextMessage方法
console.log(JSON.stringify(target))
} else {
alert("连接失败!");
}
}
$("#dianji").click(function() {
doSend();
flag = false;
duiqi()
})
//保证新消息可以看到,消息列表底部对齐
function duiqi(){
var div = document.getElementById('box');
div.scrollTop = div.scrollHeight;
}
window.onclose=function(){
onClose();
}
</script>
后端实现步骤
1.创建websocket配置类,实现WebSocketConfigurer接口
@Configuration
@EnableWebSocket
public class MyWebsocketconfig implements WebSocketConfigurer {
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(webSocketHandler(), "/websocket")
.addInterceptors(new SpringWebSocketHandlerInterceptor());
}
@Bean
public TextWebSocketHandler webSocketHandler() {
return new SpringWebSocketHandler();
}
}
2.创建websocket拦截器,处理自己的业务,可以不实现
public class SpringWebSocketHandlerInterceptor extends HttpSessionHandshakeInterceptor {
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
Map<String, Object> attributes) throws Exception {
// TODO Auto-generated method stub
System.out.println("Before Handshake");
if (request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
HttpServletRequest httpServletRequest = ((ServletServerHttpRequest) request).getServletRequest();
HttpSession session = httpServletRequest.getSession(false);
if (session != null) {
// 使用userName区分WebSocketHandler,以便定向发送消息
String userName = (String) session.getAttribute("SESSION_USERNAME");
attributes.put("WEBSOCKET_USERNAME", userName);
}
}
return super.beforeHandshake(request, response, wsHandler, attributes);
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
Exception ex) {
// TODO Auto-generated method stub
super.afterHandshake(request, response, wsHandler, ex);
}
}
3.创建websocket处理类,处理前端各种请求,实现业务
public class SpringWebSocketHandler extends TextWebSocketHandler {
private static final Map<String, WebSocketSession> userMap;
static {
userMap = new HashMap<String, WebSocketSession>();
}
public SpringWebSocketHandler() {
}
/**
* 连接成功时候,会触发页面上onopen方法
*/
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
// TODO Auto-generated method stub
// 这块会实现自己业务
userMap.put(getUsername(session), session);
Result result = new Result();
result.setType("firendList");
result.setData(userMap.keySet());
sendMessageToUsers(new TextMessage(JSONArray.toJSONString(result)));
}
/**
* 关闭连接时触发
*/
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
String username = (String) session.getAttributes().get("WEBSOCKET_USERNAME");
System.out.println("用户" + username + "已退出!");
userMap.remove(getUsername(session));
System.out.println("剩余在线用户" + userMap.keySet().size());
Result result = new Result();
result.setType("firendList");
result.setData(userMap.keySet());
sendMessageToUsers(new TextMessage(JSONArray.toJSONString(result)));
}
/**
* js调用websocket.send时候,会调用该方法
*/
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
System.out.println("收到的消息--" + message.getPayload());
JSONObject json = JSONObject.parseObject(message.getPayload());
Result result = new Result();
result.setData(json.getString("data"));
result.setType("message");
result.setSender(json.getString("sender"));
sendMessageToUsers(new TextMessage(JSONArray.toJSONString(result)));
}
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
if (session.isOpen()) {
session.close();
}
userMap.remove(getUsername(session));
}
public boolean supportsPartialMessages() {
return false;
}
/**
* 给某个用户发送消息
*
* @param userName
* @param message
*/
public void sendMessageToUser(String userName, TextMessage message) {
Set<String> set = userMap.keySet();
for (String username : set) {
if (username.equals(userName)) {
WebSocketSession session = userMap.get(username);
if (session.isOpen()) {
try {
session.sendMessage(message);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
break;
}
}
}
/**
* 给所有在线用户发送消息
*
* @param message
*/
public void sendMessageToUsers(TextMessage message) {
for (String username : userMap.keySet()) {
WebSocketSession session = userMap.get(username);
try {
if (session.isOpen()) {
session.sendMessage(message);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public String getUsername(WebSocketSession session) {
String username = null;
username = (String) session.getAttributes().get("WEBSOCKET_USERNAME");
if (username != null) {
return username;
}
return username;
}
}
4.实现效果图
源码下载地址:https://download.csdn.net/download/yuwen_forjava/10584951
下载前必读:源码案例仅仅只是一个可以跑起来的代码,对新手意义重大,很有参考价值。并且源码的扩继续拓展,如:实现单人聊天,实现用户头像,实现未读消息和历史记录查看(结合数据库)等等。
至此,完成。
推荐阅读
-
Spring Boot实现STOMP协议的WebSocket的方法步骤
-
spring boot websocket stomp 实现广播通信和一对一通信聊天
-
基于spring实现websocket实时推送实例
-
微信小程序 websocket 实现SpringMVC+Spring+Mybatis
-
spring boot项目中集成WebSocket,实现消息推送
-
Spring boot WebSocket实现简单的多人聊天
-
spring boot集成javacv + websocket实现实时视频推流回放(延时1-2秒)
-
基于node+websocket+html实现腾讯课堂聊天室聊天功能
-
spring cloud gateway websocket 路由底层实现
-
Django实现WebSocket在线聊天室功能(channels库)