欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

spring websocket 教程,spring websocket 实现聊天室功能

程序员文章站 2022-05-17 09:30:39
...

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 表示连接状态,可以是以下值:

  • 0 - 表示连接尚未建立。

  • 1 - 表示连接已建立,可以进行通信。

  • 2 - 表示连接正在进行关闭。

  • 3 - 表示连接已经关闭或者连接不能打开。

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.实现效果图

spring websocket 教程,spring websocket 实现聊天室功能

本文参考:https://www.baidu.com/

源码下载地址:https://download.csdn.net/download/yuwen_forjava/10584951

下载前必读:源码案例仅仅只是一个可以跑起来的代码,对新手意义重大,很有参考价值。并且源码的扩继续拓展,如:实现单人聊天,实现用户头像,实现未读消息和历史记录查看(结合数据库)等等。

 

至此,完成。