springboot整合websocket(一)

springboot整合websocket(一)

东西太多了,拆成几章来写(绝对不是骗流量^ w ^)

这一部分就简单做一个公共聊天室吧

1、引入相关依赖

springboot相关依赖就不写了,这里只写websocket的依赖

<!--websocket-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

2、写配置文件

这里注意不要漏了 @EnableWebSocket,用于开启websocket支持,同时 @Configuration将配置类注入spring容器

package com.websocket.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean;

@Configuration
//开启websocket支持
@EnableWebSocket
public class WebsocketConfig
{
     
    /**
     * 必须要有的
     *
     * @return
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter()
    {
     
        return new ServerEndpointExporter();
    }

    /**
     * websocket 配置信息
     *
     * @return
     */
    @Bean
    public ServletServerContainerFactoryBean createWebSocketContainer()
    {
     
        ServletServerContainerFactoryBean bean = new ServletServerContainerFactoryBean();
        //文本缓冲区大小
        bean.setMaxTextMessageBufferSize(8192);
        //字节缓冲区大小
        bean.setMaxBinaryMessageBufferSize(8192);
        return bean;
    }
}


3、开始愉快的使用啦

这里先介绍几个websocket的注解

注解 作用 备注
@ServerEndpoint 用于声明websocket响应类,有点像@RequestMapping @ServerEndpoint("/websocket")
@OnOpen websocket连接时触发 参数有:Session session, EndpointConfig config
@OnMessage 有消息时触发 参数很多,一会再说
@OnClose 连接关闭时触发 参数有:Session session, CloseReason closeReason
@OnError 有异常时触发 参数有:Session session, Throwable throwable

3.1(重要) 将@ServerEndpoint标注在类上,然后依次创建4个方法,参数见上表,方法名随意

注意类上需要有 @Component 扫描哦,我这里用的时@Controller

@Log4j2
@Controller
@ServerEndpoint("/websocket")
public class BaseWebsocketController
{
     

	//使用 ConcurrentHashMap, 保证线程安全, static全局共享 session
	
	//这里之所以static,是因为这个类不是单例的!!
	//他虽然有@Controller注解,但是不适用Ioc容器中拿对象,每一次请求过来都是一个新的对象
	
    //存放 session
    private final static Map<String, Session> sessions = new ConcurrentHashMap<>();

    //onopen 在连接创建(用户进入聊天室)时触发
    @OnOpen
    public void openSession(Session session, EndpointConfig config)
    {
     
        
    }

	//响应字符串
    @OnMessage
    public void onMessage(Session session, String message)
    {
     
        
    }

	//响应字节流
    @OnMessage
    public void onMessage(Session session, byte[] message)
    {
     
        
    }

    //onclose 在连接断开(用户离开聊天室)时触发
    @OnClose
    public void closeSession(Session session, CloseReason closeReason)
    {
     
        
    }

    @OnError
    public void sessionError(Session session, Throwable throwable)
    {
     
        
    }
}

说明

细心的小伙伴可能发现,我有两个 @OnMessage, 这是因为websocket能发送三种请求(我知道的三种),一种是字符串,一种是字节流(用于上传文件),一种是ping-pong(乒乓机制),因为js不好发送ping请求,我这里就只有响应字符串和字节流两种方法。

接下来的篇幅将只演示字符串的,字节流咱再另一篇说,不然太多了看的头痛

3.2 往方法里面写点简单的东西

里面注释写个很清楚哦,慢慢瞅,咱就解释一下流程
1、再OnOpen中将session存起来,并通知其他用户,有人来啦。
2、有消息来的时候,再OnMessage中,通知其他用户
3、OnClose中,通知其他用户,别人溜了
4、OnError中,有异常就关闭websocket

@Log4j2
@Controller
@ServerEndpoint("/websocket")
public class BaseWebsocketController
{
     

    //使用 ConcurrentHashMap, 保证线程安全, static全局共享 session

    //这里之所以static,是因为这个类不是单例的!!
    //他虽然有@Controller注解,但是不适用Ioc容器中拿对象,每一次请求过来都是一个新的对象

    //存放 session
    private final static Map<String, Session> sessions = new ConcurrentHashMap<>();

    //onopen 在连接创建(用户进入聊天室)时触发
    @OnOpen
    public void openSession(Session session, EndpointConfig config)
    {
     
        //将session存起来, 用于服务器向浏览器发送消息
        sessions.put(session.getId(), session);
        sendAll("[" + session.getId() + "]进入房间");
    }

    //响应字符串
    @OnMessage
    public void onMessage(Session session, String message)
    {
     
        sendAll("[" + session.getId() + "]" + message);
    }

    //响应字节流
    @OnMessage
    public void onMessage(Session session, byte[] message)
    {
     
        //这个咱以后再说
    }

    //onclose 在连接断开(用户离开聊天室)时触发
    @OnClose
    public void closeSession(Session session, CloseReason closeReason)
    {
     
        //记得移除相对应的session
        sessions.remove(session.getId());
        
        sendAll("[" + session.getId() + "]离开了房间");
    }

    @OnError
    public void sessionError(Session session, Throwable throwable)
    {
     
        //通常有异常会关闭session
        try {
     
            session.close();
        }
        catch (IOException e) {
     
            e.printStackTrace();
        }
    }

    private void sendAll(String message)
    {
     
        for (Session s : sessions.values()) {
     
            //获得session发送消息的对象
            //Basic是同步, 会阻塞
            //Async是异步, 这个会有多线程并发导致异常, 发送消息太快也会有并发异常, 需要有 消息队列 来辅助使用
            final RemoteEndpoint.Basic remote = s.getBasicRemote();
            try {
     
                //发送消息
                remote.sendText(message);
            }
            catch (IOException e) {
     
                e.printStackTrace();
            }
        }
    }
}

3.3 写个页面使用websocket

先上效果图


DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
  <meta charset="UTF-8">
  <title>websocket-demotitle>
  
  <link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.2.1/css/bootstrap.min.css">
head>
<body>
  <div class="container py-3">
    
    <div class="row">
      
      <div class="col-6">
        <div>
          <label for="messageArea">聊天信息:label>
        div>
        <div>
          <textarea id="messageArea" readonly class="w-100" style="height: 75vh;">textarea>
        div>
      div>
      
      <div class="col">
        
        <div class="my-1">
          <label for="messageArea">用 户 名:label>
        div>
        
        <div class="my-1">
          <input type="text" id="username" autocomplete="off">
        div>
        
        <div class="my-1">
          <button class="btn-info" id="joinRoomBtn">进入聊天室button>
          <button class="btn-warning" id="leaveRoomBtn">离开聊天室button>
        div>
        
        <hr/>
        
        <div class="my-1">
          <label for="sendMessage">输入消息:label>
        div>
        <div>
          <textarea id="sendMessage" rows="5" class="w-100" style="max-height: 50vh">textarea>
        div>
        
        <div class="my-1">
          <button class="btn-primary" id="sendBtn">发送消息button>
        div>
      
      div>
    
    div>
  
  div>
  
  <script src="https://s3.pstatp.com/cdn/expire-1-M/jquery/3.3.1/jquery.min.js">script>
  
  <script>
    let webSocket;
    //ip和端口号用自己项目的
    //{websocket}: 其实是刚刚那个@ServerEndpoint("/websocket")中定义的
    let url = 'ws://127.0.0.1:8080/websocket';
    
    $('#username').keyup(function (e) {
       
      let keycode = e.which;
      if (keycode == 13) {
       
        $('#joinRoomBtn').click();
      }
    });
    
    //进入聊天室
    $('#joinRoomBtn').click(function () {
       
      let username = $('#username').val();
      webSocket = new WebSocket(url);
      webSocket.onopen = function () {
       
        console.log('webSocket连接创建。。。');
      }
      webSocket.onclose = function () {
       
        console.log('webSocket已断开。。。');
        $('#messageArea').append('websocket已断开\n');
      }
      webSocket.onmessage = function (event) {
       
        $('#messageArea').append(event.data + '\n');
      }
      webSocket.onerror = function (event) {
       
        console.log(event)
        console.log('webSocket连接异常。。。');
      }
    });
    
    //退出聊天室
    $('#leaveRoomBtn').click(function () {
       
      if (webSocket) {
       
        //关闭连接
        webSocket.close();
      }
    });
    
    //发送消息
    $('#sendBtn').click(function () {
       
      var msg = $('#sendMessage').val();
      if (msg.trim().length === 0) {
       
        alert('请输入内容');
        return;
      }
      webSocket.send($('#sendMessage').val());
      
      $('#sendMessage').val('');
    });
  
  script>

body>
html>

4、最后填下坑

4.1@OnOpen、@OnMessage…那些参数是咋来滴?

当然是看注释啦!转到源码,再类的上边有一大串注释,百度翻译看下就行。
我上边的参数还有漏的哦~~太多了解释不过来。

4.2 里面有一段是这个

and Zero to n String or Java primitive parameters
annotated with the {@link javax.websocket.server.PathParam} annotation for server endpoints.

意思是可以再路径中加入参数,然后用 @PathParam注解获得该参数(是不是和web很像呢)

也就是说,可以这样

@ServerEndpoint("/websocket/{username}")

然后这样获得参数(所有方法都可以加),而且可以不止一个

//响应字符串
@OnMessage
public void onMessage(@PathParam("username") String username, Session session, String message)
{
     
    sendAll("[" + username + "]" + message);
}

再html连接时,url就这么写

let username = $('#username').val();
let url = 'ws://127.0.0.1:8080/websocket';
url = url+'/'+username;

所以啊,刚刚我们demo的用户名是可以显示出来的(这个就交给大家了,懒 >_<)。

End

以后有时间写下关于websocket文件上传的,着急的小伙伴下面评论,我开个小灶丫~

你可能感兴趣的:(spring,boot,websocket)