SpringBoot + WebSocket实现简易聊天室

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot + WebSocket实现简易聊天室相关的知识,希望对你有一定的参考价值。

①:什么是 WebSocket?

? WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议

? 根据这个定义有两个注意的地方:

1. 什么是协议?

? 协议就是相互通信的计算机双方必须共同遵守的一组约定。

2. WebSocket 协议和HTTP协议的区别?

? 1)HTTP协议基于 TCP 协议,建立链接必须通过三次握手才能发送信息。

? 2)http链接分为短链接,长链接,短链接是每次请求都要三次握手才能发送自己的信息。即每一个request 对应一个 response。长链接是在一定的期限内保持链接。保持TCP连接不断开。客户端与服务器通信,必须要有客户端发起然后服务器返回结果。客户端是主动的,服务器是被动的。

? 3)WebSocket 他是为了解决客户端发起多个 http 请求到服务器资源浏览器必须要经过长时间的轮训问题而生的,他实现了多路复用,他是全双工通信。在 webSocket 协议下客服端和浏览器可以同时发送信息。

②:使用基于STOMP协议的WebSocket+Springboot实现简易聊天室
1. 编写配置文件
@Configuration
@EnableWebSocketMessageBroker   //通过此注解开启 WebSocket 消息代理
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        //设置消息代理的前缀
        // 如果消息的前缀是 /topic 就会将消息转发给消息代理(broker)再由消息代理转发给所有连接的客户端
        config.enableSimpleBroker("/topic");    //客户端接收服务端消息的地址前缀

        //配置一个或多个前缀,通过这些前缀过滤出需要被注解方法处理的消息。
        // 例如前缀为"/app"的 destination 可以通过 @MessageMapping 注解的方法处理,而其他 destination("/topic","/query") 将被直接交给 broker 处理
        config.setApplicationDestinationPrefixes("/app");   //客户端给服务端发消息的地址前缀
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        //定义一个前缀为 "chat" 的endpoint,并开启 sockJs支持。
        // sockJs 可以解决对 WebSocket 的兼容性问题,客户端将通过这里配置的 url 建立 WebSocket 连接
        registry.addEndpoint("/chat").withSockJS();
    }
}
2. 编写控制器
@Controller
public class GreetingController {

    /**
     * 执行步骤:
     *  1,由 WebSocketConfig 中的配置,@MessageMapping 注解接收 "/app/hello" 路径发来的消息
     *  2,注解方法对消息进行处理后,将消息转发到 @SendTo 定义的路径上
     *  3,@SendTo 定义的路径是一个前缀为 "/topic" 的路径,由配置文件,此消息将被交给消息代理 broker,由 broker 进行广播
     * @param message
     * @return
     */
    @MessageMapping("/hello")
    @SendTo("/topic/greetings")
    public Message greeting(Message message) {
        return message;
    }
}
3.html代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>群聊</title>
    <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="/app.js"></script>
</head>
<body>
    <div>
        <label for="name">请输入用户名</label>
        <input type="text" id="name" placeholder="用户名">
    </div>
    <div>
        <button id="connect"type="button">连接</button>
        <button id="disconnect"type="button" disabled="disabled">断开连接</button>
    </div>
    <div id="chat" style="display: none;">
        <div>
            <label for="name">请输入聊天内容</label>
            <input type="text" id="content" placeholder="聊天内容">
        </div>
        <button id="send" type="button">发送</button>
        <div id="greetings">
            <div id="conversation" style="display: none">群聊进行中</div>
        </div>
    </div>
</body>
</html>
4. js文件
var stompClient = null;
//页面显示设置
function setConnected(connected) {
    $("#connect").prop("disabled", connected);
    $("#disconnect").prop("disabled", !connected);
    if (connected) {
        $("#conversation").show();
        $("#chat").show();
    } else {
        $("#conversation").hide();
        $("#chat").hide();
    }
    $("#greeting").html("");
}

//建立一个 WebSocket 连接,建立连接之前必须输入用户名
function connect() {
    if (!$("#name").val()) {
        return;
    }
    //创建一个 SockeJS 实例
    var socket = new SockJS(‘/chat‘);
    //使用stomp.over方式创建一个stompClient,完成客户端的创建。
    stompClient = Stomp.over(socket);
    stompClient.connect({}, function (frame) {
        //进行页面设置
        setConnected(true);
        //使用 subscribe 方法订阅服务端发送回来的消息,并将服务端发送的消息展示出来
        stompClient.subscribe(‘/topic/greetings‘, function (greetings) {
            showGreeting(JSON.parse(greetings.body));
        })
    })
}

//断开 WebSocket 连接
function disconnect() {
    if (stompClient != null) {
        stompClient.disconnect();
    }
    setConnected(false)
}

//发送信息
function sendName() {
    stompClient.send("/app/hello", {},
        JSON.stringify({‘name‘: $(‘#name‘).val(), ‘content‘: $(‘#content‘).val()}));
}

//展示信息
function showGreeting(message) {
    $(‘#greetings‘)
        .append("<div>" + message.name + ":" + message.content + "</div>")
}

$(function () {
    //建立连接
    $("#connect").click(function () {
        connect();
    });
    //断开连接
    $("#disconnect").click(function () {
        disconnect();
    });
    //发送信息
    $("#send").click(function () {
        sendName();
    });
})
5.注意事项

? 1)maven 引入依赖错误(尽量去 maven 的中央仓库拷贝依赖)

? 2)stomp 协议的引入

    使用STOMP的好处在于,它完全就是一种消息队列模式,你可以使用生产者与消费者的思想来认识它,发送消息的是生产者,接收消息的是消费者。而消费者可以通过订阅不同的destination,来获得不同的推送消息,不需要开发人员去管理这些订阅与推送目的地之前的关系。

? 案例见spring官网就有一个简单的spring-boot的stomp-demo,如果是基于springboot,大家可以根据spring上面的教程试着去写一个简单的demo。

③:换种方式实现群发消息
控制器
    /**
     *  1. @MessageMapping("/hello") Spring提供一个 @MessageMapping 注解实现了对 WebScoket 的封装
     *  2. SimpMessagingTemplate 是 Spring-WebSocket 内置的一个消息发送的工具
     * @param message
     * @throws Exception
     */
    @Autowired
    private SimpMessagingTemplate simpMessagingTemplate;
    @MessageMapping("/hello")
    public void  greeting(Message message) throws Exception{
        //使用这个方法进行消息的转发发送
        simpMessagingTemplate.convertAndSend("/topic/greetings",message);
    }
④:实现点对点通信

? 刚刚实现的功能是群发消息,下面看下私聊是如何实现的。点对点通信需要配置多个用户,我们用 SpringSecurity 添加两个用户。

1. 添加 SpringSecurity 依赖
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
2. SpringSecurity 配置文件
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                //添加两个用户 admin,sang,密码设为123。
                .withUser("admin")
                .password("$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq")
                .roles("admin")
                .and()
                .withUser("sang")
                .password("$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq")
                .roles("user");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin().permitAll();
    }
}
3. 修改 WebSocket 配置文件
@Configuration
@EnableWebSocketMessageBroker   //通过此注解开启 WebSocket 消息代理
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {

        //客户端接收服务端消息的地址前缀
        //在群发的基础上,添加一个客户端接收地址的前缀。
        config.enableSimpleBroker("/topic","/queue");   

        //客户端给服务端发消息的地址前缀
        config.setApplicationDestinationPrefixes("/app");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        //定义一个前缀为 "chat" 的endpoint,并开启 sockJs支持。
        // sockJs 可以解决对 WebSocket 的兼容性问题,客户端将通过这里配置的 url 建立 WebSocket 连接
        registry.addEndpoint("/chat").withSockJS();
    }
}
4. 修改控制器
@Controller
public class GreetingController {

    @Autowired
    private SimpMessagingTemplate simpMessagingTemplate;

    //群发消息使用 @SendTo 注解
    @MessageMapping("/hello")
    @SendTo("/topic/greetings")
    public Message greeting(Message message) throws Exception{
        return message;
    }

    //点对点发送消息使用 SimpMessagingTemplate 实现
    @MessageMapping("/chat")    //来自 "/app/chat" 的消息将会被此方法处理
    public void chat(Principal principal, Chat chat)throws Exception{
        String from = principal.getName();
        chat.setFrom(from);
        simpMessagingTemplate.convertAndSendToUser(chat.getTo(),"/queue/chat",chat);
    }

}
5. onlinechat.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>群聊</title>
    <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>

    <div>
        <div id="chatsContent"></div>
        <div>
            请输入聊天内容:<input type="text" id="content" placeholder="聊天内容">
            目标用户:<input type="text" id="to" placeholder="目标用户">
            <button type="button" id="send">发送</button>
        </div>
    </div>

</body>
</html>
6. chat.js
var stompClient = null;

//建立一个 WebSocket 连接,建立连接之前必须输入用户名
function connect() {

    //创建一个 SockeJS 实例
    var socket = new SockJS(‘/chat‘);
    stompClient = Stomp.over(socket);
    stompClient.connect({}, function (frame) {
        //使用 subscribe 方法订阅服务端发送回来的消息,并将服务端发送的消息展示出来
        stompClient.subscribe(‘/user/queue/chat‘, function (chat) {
            showGreeting(JSON.parse(chat.body));
        })
    })
}

//发送信息
function sendMsg() {
    stompClient.send("/app/chat", {},
        JSON.stringify({‘content‘: $(‘#content‘).val(), ‘to‘: $(‘#to‘).val()}));
}

//展示信息
function showGreeting(message) {
    $(‘#chatsContent‘)
        .append("<div>" + message.from + ":" + message.content + "</div>")
}

$(function () {
   connect();
   $(‘#send‘).click(function () {
       sendMsg();
   });
})
7. 目录结构及演示效果

演示效果时请使用不同用户登录的同一浏览器或者不同浏览器演示

技术图片
技术图片
技术图片

以上是关于SpringBoot + WebSocket实现简易聊天室的主要内容,如果未能解决你的问题,请参考以下文章

Springboot实现websocket

Springboot实现websocket

Springboot +WebSocket学习

SpringBoot+Vue+WebSocket 实现在线聊天

springboot - websocket实现及原理

2020-04-05-SpringBoot+WebSocket基于Redis订阅发布实现集群化