WebSocket消息推送
WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。在 WebSocket API 中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。现在,很多网站为了实现推送技术,所用的技术都是 Ajax 轮询。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。HTML5 定义的 WebSocket 协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。
SpringBoot集成WebSocket实现消息推送和聊天Demo
效果图
为了体现WebSocket的特性我尽量把代码写的简短。除第一个Action用作发布公告其它开一个游览器窗口会自动分配一个用户名。
开窗步骤
localhost:8080/ 发布公告页
localhost:8080/firstPage 用户aa
localhost:8080/firstPage 用户bb
localhost:8080/firstPage 用户cc
localhost:8080/firstPage 用户dd
在页面发送消息时加上 用户名 如:"aa你好吗"
代码结构
一个Controller发布两个Action:发布公告、聊天页
一个WebSocket配置类:发布WebSocket端点(Endpoint)
一个业务逻辑类:实现推送公告和聊天功能
两个Html5页面
gradle引入依赖
implementation 'org.springframework.boot:spring-boot-starter-websocket'
测试用的Controller
@Controller
public class TestController {
@RequestMapping("/")
public String HomeIndexPage(){
return "Index";
}
@RequestMapping("/firstPage")
public String HomeIndexPage1(){
return "First";
}
@RequestMapping("/publishanews")
public String publishnewsproc(){
WebSocketTest webSocketTest = new WebSocketTest();
webSocketTest.sendMessageForAllClient("这是一条公告!");
return "Index";
}
}
两个测试页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>发布公告页</title>
<script src="../static/jquery-3.3.1/jquery-3.3.1.js"></script>
<script src="/jquery-3.3.1/jquery-3.3.1.js"></script>
</head>
<script>
$(document).ready(function () {
var websocket = null;
//判断当前浏览器是否支持WebSocket
if('WebSocket' in window){
websocket = new WebSocket("ws://localhost:8080/websocket");
}
else{
alert('Not support websocket')
}
//连接发生错误的回调方法
websocket.onerror = function(){
setMessageInnerHTML("error");
};
//连接成功建立的回调方法
websocket.onopen = function(event){
setMessageInnerHTML("open");
}
//接收到消息的回调方法
websocket.onmessage = function(event){
setMessageInnerHTML(event.data);
}
//连接关闭的回调方法
websocket.onclose = function(){
setMessageInnerHTML("close");
}
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function(){
websocket.close();
}
//将消息显示在网页上
function setMessageInnerHTML(innerHTML){
document.getElementById('message').innerHTML += innerHTML + '<br/>';
}
//关闭连接
function closeWebSocket(){
websocket.close();
}
//发送消息
function send(){
var message = document.getElementById('text').value;
websocket.send(message);
}
})
</script>
<body>
<h2 id="cont">Good Evening</h2>
<a href="/publishanews">发布公告</a>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>聊天室</title>
<body>
<input id="text" type="text" /><button onclick="send()">Send</button> <button onclick="closeWebSocket()">Close</button>
<div id="message">
</div>
</body>
<script type="text/javascript">
var websocket = null;
//判断当前浏览器是否支持WebSocket
if('WebSocket' in window){
websocket = new WebSocket("ws://localhost:8080/websocket");
}
else{
alert('Not support websocket')
}
//连接发生错误的回调方法
websocket.onerror = function(){
setMessageInnerHTML("error");
};
//连接成功建立的回调方法
websocket.onopen = function(event){
setMessageInnerHTML("open");
}
//接收到消息的回调方法
websocket.onmessage = function(event){
setMessageInnerHTML(event.data);
}
//连接关闭的回调方法
websocket.onclose = function(){
setMessageInnerHTML("close");
}
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function(){
websocket.close();
}
//将消息显示在网页上
function setMessageInnerHTML(innerHTML){
document.getElementById('message').innerHTML += innerHTML + '<br/>';
}
//关闭连接
function closeWebSocket(){
websocket.close();
}
//发送消息
function send(){
var message = document.getElementById('text').value;
websocket.send(message);
// websocket.send()
}
</script>
</html>
WebSocket的Endpoint配置类
这点和WebServices一样需要播出个端点出去。
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
}
业务逻辑类
@Service
@ServerEndpoint("/websocket")
public class WebSocketTest {
private static Vector<Session> sessions = new Vector<>();
private static TreeMap<String,Session> sessionTreeMap = new TreeMap<>();
private static int loginNumber = 0;
private Session session ;
@OnOpen
public void onopenproc(Session session) throws IOException {
System.out.println("hava a client connected");
this.session = session;
sessions.add(session);
if(loginNumber == 0)
loginNumber++;
else if(loginNumber == 1) {
sessionTreeMap.put("aa", session);
loginNumber++;
sendMessageToClient("我是用户 aa " , session);
}
else if(loginNumber == 2) {
sessionTreeMap.put("bb", session);
loginNumber++;
sendMessageToClient("我是用户 bb " , session);
}
else if(loginNumber == 3) {
sessionTreeMap.put("cc", session);
loginNumber++;
sendMessageToClient("我是用户 cc " , session);
}
else if(loginNumber == 4) {
sessionTreeMap.put("dd", session);
loginNumber++;
sendMessageToClient("我是用户 dd " , session);
}
}
@OnClose
public void oncloseproc(){
System.out.println("had a client is disconneted");
}
@OnMessage
public void onmessageproc(String message , Session session) throws IOException {
if(message!=null) {
StringBuffer stringBuffer = new StringBuffer();
sessionTreeMap.forEach((k,v)->{
if(v.equals(session)){
stringBuffer.append(k);
}
});
switch (message.substring(0,2)){
case "aa" :{
sendMessageToClient("From : "+stringBuffer.toString() + " : " + message , sessionTreeMap.get("aa"));
}break;
case "bb":{
sendMessageToClient("From : "+stringBuffer.toString() + " : " + message , sessionTreeMap.get("bb"));
}break;
case "cc":{
sendMessageToClient("From : "+stringBuffer.toString() + " : " + message , sessionTreeMap.get("cc"));
}break;
case "dd":{
sendMessageToClient("From : "+stringBuffer.toString() + " : " + message , sessionTreeMap.get("dd"));
}break;
}
System.out.println(message);
}
}
public void sendMessage(String msg) throws IOException {
if(this.session!=null)
this.session.getBasicRemote().sendText("hello everyone!");
this.session.getBasicRemote().sendText(msg);
}
public void sendMessageForAllClient(String msg){
if(!sessions.isEmpty()){
sessions.forEach(i->{
try {
if(i.isOpen()) {
i.getBasicRemote().sendText(msg+" : "+new Date().toString());
}
} catch (IOException e) {
e.printStackTrace();
}
});
}
}
public void sendMessageToClient(String msg , Session session) throws IOException {
if(session.isOpen())
session.getBasicRemote().sendText(msg);
}
}