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

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

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

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

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

至此,完成。

猜你喜欢

转载自blog.csdn.net/Yuwen_forJava/article/details/81434552