Java小强个人技术博客站点    手机版
当前位置: 首页 >> 框架 >> SpringBoot和WebSocket开发的聊天室

SpringBoot和WebSocket开发的聊天室

16920 框架 | 2021-5-14

开头还是说点废话吧。WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。

WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

在 WebSocket API 中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。

现在,很多网站为了实现推送技术,所用的技术都是 Ajax 轮询。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。

HTML5 定义的 WebSocket 协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。

崔素强博客使用websocket



从GitEE上搞了一个项目下来,然后精简和调整,尽量把代码调整到一看就懂。

项目就是普通的SpringBoot,没有用的东西全部移除,只需要一张表,当然也可以不用登陆,这样更精简。


核心代码就是ServerEndpoint的处理类:


package boot.spring.service;

import java.io.IOException;
import java.util.Date;
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.springframework.stereotype.Component;

import com.alibaba.fastjson.JSON;

import boot.spring.po.Message;

@ServerEndpoint("/webSocket/{username}")
@Component
public class WebSocketServer {

	// concurrent包的线程安全Set,用来存放每个客户端对应的WebSocketServer对象。
	private static ConcurrentHashMap<String, Session> sessionPools = new ConcurrentHashMap<>();

	// 建立连接成功调用
	@OnOpen
	public void onOpen(Session session, @PathParam(value = "username") String userName) {
		// 链接建立,存储链接对象
		sessionPools.put(userName, session);
		// 广播上线消息
		Message msg = new Message();
		msg.setFrom("系统消息");
		msg.setDate(new Date());
		msg.setTo("0");
		msg.setText(userName);
		broadcast(JSON.toJSONString(msg, true));
	}

	// 关闭连接时调用
	@OnClose
	public void onClose(@PathParam(value = "username") String userName) {
		sessionPools.remove(userName);
		// 广播下线消息
		Message msg = new Message();
		msg.setFrom("系统消息");
		msg.setDate(new Date());
		msg.setTo("-2");
		msg.setText(userName);
		broadcast(JSON.toJSONString(msg, true));
	}

	// 收到客户端信息后,根据接收人的username把消息推下去或者群发
	// to=-1群发消息
	@OnMessage
	public void onMessage(String message) throws IOException {
		Message msg = JSON.parseObject(message, Message.class);
		msg.setDate(new Date());
		if (msg.getTo().equals("-1")) {
			broadcast(JSON.toJSONString(msg, true)); // -1群发
		} else {
			sendInfo(msg.getTo(), JSON.toJSONString(msg, true));
		}
	}

	// 错误时调用
	@OnError
	public void onError(Session session, Throwable throwable) {
		throwable.printStackTrace();
	}

	// 给指定用户发送信息
	public void sendInfo(String userName, String message) {
		Session session = sessionPools.get(userName);
		try {
			sendMessage(session, message);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	// 群发消息
	public void broadcast(String message) {
		for (Session session : sessionPools.values()) {
			try {
				sendMessage(session, message);
			} catch (Exception e) {
				e.printStackTrace();
				continue;
			}
		}
	}

	// 发送消息
	public void sendMessage(Session session, String message) throws IOException {
		if (session != null) {
			synchronized (session) {
				session.getBasicRemote().sendText(message);
			}
		}
	}
	
	public static ConcurrentHashMap<String, Session> getSessionPools() {
		return sessionPools;
	}
}


聊天室页面代码:

<!DOCTYPE>
<html>
<head>
<title>聊天室</title>
<link rel="stylesheet" href="./css/bootstrap.min.css" />
<script src="./js/jquery-1.12.3.min.js"></script>
<script src="./js/bootstrap.min.js"></script>
<style>
body {
	margin-top: 5px;
}
</style>
</head>
<body>
	<div class="container">
		<div class="row">
			<div class="col-md-3">
				<div class="panel panel-primary">
					<div class="panel-heading">
						<h3 class="panel-title">当前登录用户</h3>
					</div>
					<div class="panel-body">
						<div class="list-group">
							<a href="#" class="list-group-item">你好,<span id="user"></span></a>
							<a href="logout" class="list-group-item">退出</a>
						</div>
					</div>
				</div>
				<div class="panel panel-primary" id="online">
					<div class="panel-heading">
						<h3 class="panel-title">其他在线用户</h3>
					</div>
					<div class="panel-body">
						<div class="list-group" id="users"></div>
					</div>
				</div>
				<div class="panel panel-primary">
					<div class="panel-heading">
						<h3 class="panel-title">消息发送</h3>
					</div>
					<div class="panel-body">
						<input type="text" class="form-control" id="msg" /><br>
						<button id="broadcast" type="button" class="btn btn-primary">发送</button>
						&nbsp;&nbsp;
						<button id="send" type="button" class="btn btn-primary">私聊发送</button>
					</div>
				</div>
				<div class="col-md-9">
					<div class="panel panel-primary">
						<div class="panel-heading">
							<h3 class="panel-title" id="talktitle"></h3>
						</div>
						<div class="panel-body">
							<div class="well" id="log-container" style="height:400px;overflow-y:scroll"></div>
						</div>
					</div>
				</div>
			</div>
		</div>
	</div>
	<script>
		var username;
		var uid;
	
		$(document).ready(function() {
			// 指定websocket路径
			var websocket;
			$.get("/currentuser", function(data) {
				username = data.username;
				uid = data.uid;
				$("#user").html(username);
				if ('WebSocket' in window) { // 浏览器支持 WebSocket
					websocket = new WebSocket("ws://localhost:8080/webSocket/" + username); // 打开一个 web socket
				}
				websocket.onmessage = function(event) {
					var data = JSON.parse(event.data);
					if (data.to == 0) { //上线消息
						if (data.text != username) {
							$("#users").append('<a href="#" onclick="talk(this)" class="list-group-item">' + data.text + '</a>');
							$("#log-container").append("<div class='bg-info'><label class='text-danger'>" + data.from + "&nbsp;" + data.date + "</label><div class='text-success'>" + data.text + "上线了" + "</div></div><br>");
							scrollToBottom();
						}
					} else if (data.to == -2) { //下线消息
						if (data.text != username) {
							$("#users > a").remove(":contains('" + data.text + "')");
							$("#log-container").append("<div class='bg-info'><label class='text-danger'>" + data.from + "&nbsp;" + data.date + "</label><div class='text-success'>" + data.text + "下线了" + "</div></div><br>");
							scrollToBottom();
						}
					} else { // 普通消息
						$("#log-container").append("<div class='bg-info'><label class='text-danger'>" + data.from + "&nbsp;" + data.date + "</label><div class='text-success'>" + data.text + "</div></div><br>");
						scrollToBottom();
					}
				};
				// 加载当前用户
				$.post("/onlineusers?currentuser=" + username, function(data) {
					for (var i = 0; i < data.length; i++) {
						$("#users").append('<a href="#" onclick="talk(this)" class="list-group-item">' + data[i] + '</a>');
					}
				});
			});
	
			// 发送
			$("#broadcast").click(function() {
				var data = {};
				data["from"] = username;
				data["to"] = -1;
				data["text"] = $("#msg").val();
				websocket.send(JSON.stringify(data)); // 使用 send() 方法发送数据
				$("#msg").val("");
			});
	
			// 私聊发送
			$("#send").click(function() {
				if ($("body").data("to") == undefined) {
					alert("请选择聊天对象");
					return false;
				}
				var data = {};
				data["from"] = username;
				data["to"] = $("body").data("to");
				data["text"] = $("#msg").val();
				websocket.send(JSON.stringify(data)); // 使用 send() 方法发送数据
				// 单独发送给某人的,不会广播,所以自己这里要手动加上
				$("#log-container").append("<div class='bg-success'><label class='text-info'>我&nbsp;" + new Date().format("yyyy-MM-dd hh:mm:ss") + "</label><div class='text-info'>" + $("#myinfo").val() + "</div></div><br>");
				scrollToBottom();
				$("#msg").val("");
			});
	
		});
	
		function talk(a) {
			$("#talktitle").text("与" + a.innerHTML + "的聊天");
			$("body").data("to", a.innerHTML);
		}
		
		// 滚动条滚动到最低部
		function scrollToBottom() {
			var div = document.getElementById('log-container');
			div.scrollTop = div.scrollHeight;
		}
	
		Date.prototype.format = function(fmt) {
			var o = {
				"M+" : this.getMonth() + 1, //月份 
				"d+" : this.getDate(), //日 
				"h+" : this.getHours(), //小时 
				"m+" : this.getMinutes(), //分 
				"s+" : this.getSeconds(), //秒 
				"q+" : Math.floor((this.getMonth() + 3) / 3), //季度 
				"S" : this.getMilliseconds() //毫秒 
			};
			if (/(y+)/.test(fmt)) {
				fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
			}
			for (var k in o) {
				if (new RegExp("(" + k + ")").test(fmt)) {
					fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
				}
			}
			return fmt;
		}
	</script>

</body>
</html>


登陆效果图如下,用aa,bb,cc登陆:

崔素强博客聊天室


三个人依次进入聊天室后效果如下,可以群发消息和点击登陆的用户私聊


崔素强博客聊天室


私聊时效果如下


崔素强博客聊天室


代码下载:

springBootChart.zip


END

推荐您阅读更多有关于“ springboot WebSocket 长连接 聊天室 ”的文章

上一篇:HTML压缩库HtmlCompressor 下一篇:你都奋斗了十八年,何必急着跟人家喝咖啡?

猜你喜欢

发表评论: