Springboot webSocket(二)

简介: Springboot webSocket(二)

1、发送消息


在前面我们写了一个自动回复的小例子,用到了@MessageMapping("/hello")@SendTo("/topic/greetings")两个注解,其实我们还可以使用编程的方式发送消息。


其实很简单,直接引用该消息模板


@Autowired
private SimpMessagingTemplate messagingTemplate;


消息模板内置了一系列方法,比如


void convertAndSendToUser(String user, String destination, Object payload)
void convertAndSend(D destination, Object payload)


这俩方法会包装为消息并将其发送到给定的目的地。


2、STOMP监听


监听webSocket服务器的连接只需要实现ApplicationListener<>接口。代码如下:


@Component
public class STOMPConnectEventListener implements ApplicationListener<SessionConnectEvent> {
    @Override
    public void onApplicationEvent(SessionConnectEvent event) {
        StompHeaderAccessor sha = StompHeaderAccessor.wrap(event.getMessage());
        //判断客户端的连接状态
        switch (sha.getCommand()) {
            case CONNECT:
                System.out.println("上线");
                break;
            case DISCONNECT:
                System.out.println("下线");
                break;
            case SUBSCRIBE:
                System.out.println("订阅");
                break;
            case SEND:
                System.out.println("发送");
                break;
            case UNSUBSCRIBE:
                System.out.println("取消订阅");
                break;
            default:
                break;
        }
    }
}


监听的所有状态被封装在一个枚举类中,其实还有很多,这里不再一一列举。

有了上面这些知识,我们就可以基于此开发一对一聊天服务器。


3、Session存取


webSocket提供的是一个socket框架,并不会帮我们管理session,我们需要自己去编写session管理类,进行session的读写。代码如下:


@Component
public class SocketSessionMap {
    private final static ConcurrentMap<String, String> sessionMap = new ConcurrentHashMap<>();
    /**
     * 注册Session
     * @param userId
     * @param sessionId
     */
    public synchronized void registerSession(String userId, String sessionId) {
        sessionMap.put(userId,sessionId);
    }
    /**
     * 移除Session
     * @param userId
     * @param sessionId
     */
    public synchronized void removeSession(String userId, String sessionId) {
        sessionMap.remove(userId);
    }
    /**
     * 获取用户的SessionId
     * @param userId
     * @return
     */
    public String getUserSessionId(String userId){
        return sessionMap.get(userId);
    }
    /**
     * 获取所有Session集合
     * @return
     */
    public Map<String, String> queryAllSession(){
        return sessionMap;
    }
    /**
     * 获取集合的大小
     */
    public int onlineCount(){
        return sessionMap.size();
    }
}

4、监听注册


接着,对STOMP监听类进行扩展。


@Component
public class STOMPConnectEventListener implements ApplicationListener<SessionConnectEvent> {
    @Autowired
    SocketSessionMap socketSessionMap;
    @Override
    public void onApplicationEvent(SessionConnectEvent event) {
        StompHeaderAccessor sha = StompHeaderAccessor.wrap(event.getMessage());
        String userId = sha.getFirstNativeHeader("id"); 
        String sessionId = sha.getSessionId();
        switch (sha.getCommand()) {
            case CONNECT:
                System.out.println("上线:" + userId + "  " + sessionId);
                socketSessionMap.registerSession(userId, sessionId);
                break;
            default:
                break;
        }
    }
}


服务端通过sha.getFirstNativeHeader("id")读取到客户端的ID,这个值需要网页客户端手动在header头部信息中设置。


当服务端监听到客户端连接时,会将用户SessionId注册到Map中。


5、监听下线


这里我们使用更可靠的请求下线方式,代码如下:


@MessageMapping("/chatOut")
    public void sayHello(String userId) {
        String sessionId = socketSessionMap.getUserSessionId(userId);
        System.out.println("下线:" + userId + "  " + sessionId);
        socketSessionMap.removeSession(userId,sessionId);
    }


当收到下线请求时,移除SessionId。


关于Session,也可以设置一个最大值,超时自动移除。


6、一对一消息处理


在一对一服务器中,主要处理的就是一对一的消息发送。大致逻辑是接收客户端消息,分析消息结构,通过SessionMap判断对方是否在线,然后发送相应内容。代码如下:


@MessageMapping("/chat")
    public void sayHello(Message user) {
        System.out.println(user.getId()+"-->"+user.getPid()+":"+user.getContent());
        String userPid = String.valueOf(user.getPid());
        String userId = String.valueOf(user.getId());
        String sendTo = "/topic/chat/"+userPid;
        String content = user.getId()+":"+user.getContent();
        if (socketSessionMap.getUserSessionId(userPid)!=null){
            messagingTemplate.convertAndSend(sendTo, HtmlUtils.htmlEscape(content));
        }else {
            sendTo = "/topic/chat/"+userId;
            content = "对方已下线";
            messagingTemplate.convertAndSend(sendTo, HtmlUtils.htmlEscape(content));
        }
    }


值得一体的是,关于用户ID的处理,这里使用的是自定义客户端地址,不同的地址表示不同的用户。最后通过convertAndSend()方法发送,这种方式比较可靠方便。

Message.java


public class Message {
    private int id; //用户ID
    private String content;//发送内容
    private int pid;    //发送到用户


7、聊天网页


服务端使用FreeMarker模板引擎返回html网页,代码如下:


@RequestMapping("/chat/{id}")
    public String chat_page(@PathVariable int id, ModelMap model) {
        model.addAttribute("id", id);
        int count = socketSessionMap.onlineCount();
        model.addAttribute("count", count);
        return "chat";
    }


通过RESTful形式的URl注册ID。


chat.html


<!DOCTYPE html >
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Hello WebSocket</title>
    <link href="/webjars/bootstrap/css/bootstrap.min.css" rel="stylesheet">
    <link href="/main.css" rel="stylesheet">
    <link rel="shortcut icon" href="/favicon.ico" />
    <script src="/webjars/jquery/jquery.min.js"></script>
    <script src="/webjars/sockjs-client/sockjs.min.js"></script>
    <script src="/webjars/stomp-websocket/stomp.min.js"></script>
    <script src="/chat.js"></script>
</head>
<body>
<noscript><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websocket relies on Javascript being
    enabled. Please enable
    Javascript and reload this page!</h2></noscript>
<div id="main-content" class="container">
    <div class="row">
        <div class="col-md-6">
            <form class="form-inline">
                <div class="form-group">
                    <label for="connect">WebSocket connection:</label>
                    <button id="connect" class="btn btn-default" type="submit">Connect</button>
                    <button id="disconnect" class="btn btn-default" type="submit" disabled="disabled">Disconnect
                    </button>
                </div>
            </form>
        </div>
        <div class="col-md-6">
            <form class="form-inline">
                <div class="form-group">
                    <label th:text="'Online:'+${count}"> </label>
                    <input type="text" id="id" class="form-control" th:value="${id}"required>
                    <input type="text" id="content" class="form-control" placeholder="Your name here..."required>
                    <input type="text" id="pid" class="form-control" placeholder="Your PID"required>
                </div>
                <button id="send" class="btn btn-default" type="submit">Send</button>
            </form>
        </div>
    </div>
    <div class="row">
        <div class="col-md-12">
            <table id="conversation" class="table table-striped">
                <thead>
                <tr>
                    <th>Greetings</th>
                </tr>
                </thead>
                <tbody id="greetings">
                </tbody>
            </table>
        </div>
    </div>
</div>
</body>
</html>


chat.js


var stompClient = null;
function setConnected(connected) {
    $("#connect").prop("disabled", connected);
    $("#disconnect").prop("disabled", !connected);
    if (connected) {
        $("#conversation").show();
    }
    else {
        $("#conversation").hide();
    }
    $("#greetings").html("");
}
function connect() {
    var socket = new SockJS('/gs-guide-websocket');
    stompClient = Stomp.over(socket);
    stompClient.connect({"id": $("#id").val()}, function (frame) { //客户端ID
        setConnected(true);
        console.log('Connected: ' + frame);
        stompClient.subscribe('/topic/chat/' + $("#id").val(), function (greeting) { //表明客户端地址
            showGreeting(greeting.body);
        }, {"id": "Host_" + $("#id").val()});
    });
}
function disconnect() {
    if (stompClient !== null) {
        stompClient.send("/app/chatOut", {},$("#id").val());
        stompClient.disconnect();
    }
    setConnected(false);
    console.log("Disconnected");
}
function sendName() {
    stompClient.send("/app/chat", {}, JSON.stringify({
        'content': $("#content").val(),
        'id': $("#id").val(),
        'pid': $("#pid").val()
    }));
    showGreeting("我:" + $("#content").val())
}
function showGreeting(message) {
    $("#greetings").append("<tr><td>" + message + "</td></tr>");
}
$(function () {
    $("form").on('submit', function (e) {
        e.preventDefault();
    });
    $("#connect").click(function () {
        connect();
    });
    $("#disconnect").click(function () {
        disconnect();
    });
    $("#send").click(function () {
        sendName();
    });
});


网页客户端的主要逻辑在chat.js中。非常有必要的是在stompClient.connect()方法的第

一个参数中传入header头信息,该头部信息必须设置id字段的值,因为服务端会读取该ID值,该值最终会取自URL中的参数。


其次是在用户断开链接前,会向服务端发送断开通知。


stompClient.send("/app/chatOut", {},$("#id").val());


8、程序演示


打开两个网页,URl分别为http://localhost:8080/chat/100http://localhost:8080/chat/101,点击连接。


image.png


查看控制台输出:


image.png


接下来演示用户101向用户100发送消息:


image.png


再次查看控制输出:


image.png


消息成功发送!

当用户断开链接时,控制台输出为:


image.png

目录
相关文章
|
3月前
|
前端开发 网络协议 JavaScript
在Spring Boot中实现基于WebSocket的实时通信
在Spring Boot中实现基于WebSocket的实时通信
|
3天前
|
前端开发 Java C++
RSocket vs WebSocket:Spring Boot 3.3 中的两大实时通信利器
本文介绍了在 Spring Boot 3.3 中使用 RSocket 和 WebSocket 实现实时通信的方法。RSocket 是一种高效的网络通信协议,支持多种通信模式,适用于微服务和流式数据传输。WebSocket 则是一种标准协议,支持全双工通信,适合实时数据更新场景。文章通过一个完整的示例,展示了如何配置项目、实现前后端交互和消息传递,并提供了详细的代码示例。通过这些技术,可以大幅提升系统的响应速度和处理效率。
|
2月前
|
开发框架 网络协议 Java
SpringBoot WebSocket大揭秘:实时通信、高效协作,一文让你彻底解锁!
【8月更文挑战第25天】本文介绍如何在SpringBoot项目中集成WebSocket以实现客户端与服务端的实时通信。首先概述了WebSocket的基本原理及其优势,接着详细阐述了集成步骤:添加依赖、配置WebSocket、定义WebSocket接口及进行测试。通过示例代码展示了整个过程,旨在帮助开发者更好地理解和应用这一技术。
170 1
|
2月前
|
小程序 Java API
springboot 微信小程序整合websocket,实现发送提醒消息
springboot 微信小程序整合websocket,实现发送提醒消息
|
2月前
|
JavaScript 前端开发 网络协议
WebSocket在Java Spring Boot+Vue框架中实现消息推送功能
在现代Web应用中,实时消息提醒是一项非常重要的功能,能够极大地提升用户体验。WebSocket作为一种在单个TCP连接上进行全双工通信的协议,为实现实时消息提醒提供了高效且低延迟的解决方案。本文将详细介绍如何在Java Spring Boot后端和Vue前端框架中利用WebSocket实现消息提醒功能。
113 0
|
3月前
|
监控 网络协议 Java
如何在Spring Boot中使用WebSocket
如何在Spring Boot中使用WebSocket
|
3月前
|
监控 前端开发 网络协议
如何使用Spring Boot实现WebSocket通信
如何使用Spring Boot实现WebSocket通信
|
5月前
|
网络协议 前端开发 Java
SpringBoot 整合 WebSocket
WebSocket是基于TCP协议的一种网络协议,它实现了浏览器与服务器全双工通信,支持客户端和服务端之间相互发送信息。在有WebSocket之前,如果服务端数据发生了改变,客户端想知道的话,只能采用定时轮询的方式去服务端获取,这种方式很大程度上增大了服务器端的压力,有了WebSocket之后,如果服务端数据发生改变,可以立即通知客户端,客户端就不用轮询去换取,降低了服务器的压力。目前主流的浏览器都已经支持WebSocket协议了。
|
4月前
|
前端开发 JavaScript 安全
集成WebSocket在Spring Boot中可以用于实现实时的双向通信
集成WebSocket在Spring Boot中可以用于实现实时的双向通信
81 4
|
4月前
|
监控 前端开发 网络协议
如何使用Spring Boot实现WebSocket通信
如何使用Spring Boot实现WebSocket通信