SpringBoot整合webSocket

WebSocket简介

目的

HTML5 WebSocket设计出来的目的就是取代轮询和长连接,使客户端浏览器具备像C/S框架下桌面系统的即时通讯能力,实现了浏览器和服务器全双工通信,建立在TCP之上,虽然WebSocket和HTTP一样通过TCP来传输数据,但WebSocket可以主动的向对方发送或接收数据,就像Socket一样;并且WebSocket需要类似TCP的客户端和服务端通过握手连接,连接成功后才能互相通信。

优点

双向通信、事件驱动、异步、使用ws或wss协议的客户端能够真正实现意义上的推送功能。

缺点

少部分浏览器不支持。

示例

社交聊天(微信、QQ)、弹幕、多玩家玩游戏、协同编辑、股票基金实时报价、体育实况更新、视频会议/聊天、基于位置的应用、在线教育、智能家居等高实时性的场景。

WebSocket请求响应客户端服务器交互图
image

WebSocket方式减少了很多TCP打开和关闭连接的操作,WebSocket的资源利用率高。

java WebSocket实现

Oracle 发布的 java 的 WebSocket 的规范是 JSR356规范 ,Tomcat从7.0.27开始支持WebSocket,从7.0.47开始支持JSR-356。

1、初始化一个springboot项目
2、加入websocket依赖


    org.springframework.boot
    spring-boot-starter-websocket

pom.xml如下:


        
        
            org.springframework.boot
            spring-boot-starter-thymeleaf
        
        
        
            org.springframework.boot
            spring-boot-starter-web
        
        
        
            org.springframework.boot
            spring-boot-starter-websocket
        
        
        
            org.mybatis.spring.boot
            mybatis-spring-boot-starter
            2.0.0
        

        
        
            org.projectlombok
            lombok
            true
        
        
        
            org.springframework.boot
            spring-boot-starter-tomcat
            provided
        
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        

    
3、编写websocket的服务端

3.1 WebSocketEndPoint是websocket服务端的核心
@PathParam是javax.websocket.server下的注解,是将路径中绑定的占位符的值取出来
  在url中使用key和name,是想通过key和name对websocket的连接进行访问控制,这个key可以是用户登录后服务器给用户的令牌,通过令牌和和name进行权限验证(自己写拦截器或者继承权限框架实现),还可以通过key和name生成唯一值来进行在线websocket
连接的维护<(key+name), websocketSession>, 当然,我在这里没有这样做。

package com.geniuses.sewage_zero_straight.net.websocket;

import com.geniuses.sewage_zero_straight.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;

import static com.geniuses.sewage_zero_straight.net.websocket.WebSocketPool.*;
import static com.geniuses.sewage_zero_straight.net.websocket.WebSocketHandler.createKey;

@Slf4j
@Component
@ServerEndpoint("/net/websocket/{key}/{name}")//表明这是一个websocket服务的端点
public class WebSocketEndPoint {


    private static UserService userService;

    @Autowired
    public void setUserService(UserService userService){
        WebSocketEndPoint.userService = userService;
    }

    @OnOpen
    public void onOpen(@PathParam("key") String key, @PathParam("name") String name,  Session session){
        log.info("有新的连接:{}", session);
        add(createKey(key, name), session);
        WebSocketHandler.sendMessage(session, key + name);
        log.info("在线人数:{}",count());
        sessionMap().keySet().forEach(item -> log.info("在线用户:", item));
        for (Map.Entry item : sessionMap().entrySet()){
            log.info("12: {}", item.getKey());
        }
    }

    @OnMessage
    public void onMessage(String message){
        log.info("有新消息: {}", message);
    }

    @OnClose
    public void onClose(@PathParam("key") String key, @PathParam("name") String name,Session session){
        log.info("连接关闭: {}", session);
        remove(createKey(key, name));
        log.info("在线人数:{}",count());
        sessionMap().keySet().forEach(item -> log.info("在线用户:", (item.split("@"))[1]));
        for (Map.Entry item : sessionMap().entrySet()){
            log.info("12: {}", item.getKey());
        }
    }

    @OnError
    public void onError(Session session, Throwable throwable){
        try {
            session.close();
        } catch (IOException e) {
            log.error("onError Exception: {}", e);
        }
        log.info("连接出现异常: {}", throwable);
    }

}

3.2、WebSocketPool是websocket的在线连接池

package com.geniuses.sewage_zero_straight.net.websocket;

import lombok.extern.slf4j.Slf4j;

import javax.websocket.Session;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
public class WebSocketPool {

    //在线用户websocket连接池
    private static final Map ONLINE_USER_SESSIONS = new ConcurrentHashMap<>();

    /**
     * 新增一则连接
     * @param key
     * @param session
     */
    public static void add(String key, Session session){
        if (!key.isEmpty() && session != null){
            ONLINE_USER_SESSIONS.put(key, session);
        }
    }

    /**
     * 根据Key删除连接
     * @param key
     */
    public static void remove(String key){
        if (!key.isEmpty()){
            ONLINE_USER_SESSIONS.remove(key);
        }
    }

    /**
     * 获取在线人数
     * @return
     */
    public static int count(){
        return ONLINE_USER_SESSIONS.size();
    }

    /**
     * 获取在线session池
     * @return
     */
    public static Map sessionMap(){
        return ONLINE_USER_SESSIONS;
    }
}

3.3、WebSocketHandler是websocket的动作处理工具

package com.geniuses.sewage_zero_straight.net.websocket;

import lombok.extern.slf4j.Slf4j;

import javax.websocket.RemoteEndpoint;
import javax.websocket.Session;
import java.io.IOException;
import static com.geniuses.sewage_zero_straight.net.websocket.WebSocketPool.sessionMap;

@Slf4j
public class WebSocketHandler {


    /**
     * 根据key和用户名生成一个key值,简单实现下
     * @param key
     * @param name
     * @return
     */
    public static String createKey(String key, String name){
        return key + "@" + name;
    }

    /**
     * 给指定用户发送信息
     * @param session
     * @param msg
     */
    public static void sendMessage(Session session, String msg){
        if (session == null)
            return;
        final RemoteEndpoint.Basic basic = session.getBasicRemote();
        if (basic == null)
            return;
        try {
            basic.sendText(msg);
        } catch (IOException e) {
            log.error("sendText Exception: {}", e);
        }
    }


    /**
     * 给所有的在线用户发送消息
     * @param message
     */
    public static void sendMessageAll(String message){
        log.info("广播:群发消息");
        sessionMap().forEach((key, session) -> sendMessage(session, message));
    }
}
4、前端访问实现

4.1、index.html,页面引用了jquery和bootstrap样式,请自行应用




    
    chat room websocket
    
    

    
        

聊天室


<

4.2、页面访问控制器,由此来访问index.html

package com.geniuses.sewage_zero_straight.web;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@RequestMapping("/view")
@Controller
public class ViewController {

    /**
     * 返回首页
     * @return
     */
    @GetMapping("/index")
    public String index(){
        return "index";
    }
}
5、websocket配置
package com.geniuses.sewage_zero_straight.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;

@Configuration
@EnableWebSocket
public class WebSocketConfig {

    @Bean
    public ServerEndpointExporter serverEndpointExporter(){
        return new ServerEndpointExporter();
    }
}
6、注意:

在使用了@ServerEndpoint注解的类是无法直接使用@Autowired的,因为@ServerEndpoint表明当前类是websocket的服务端点,在spring容器启动时会初始化一次该类,当有新的websocket连接的时候,也会进行该类实例的创建(每一次连接时都会创建一个实例),所以在第二次往后创建该类实例的时候,就无法进行有效的@Autowired了,此时发现,即便第一次注入是有效的,但是也没有什么用。这个时候,将需要注入的变量置为类的变量,提供一个set方法(该方法为实例方法),在set方法上面进行依赖注入,这样就可以进行有效的注入了。

打个广告,本人博客地址是:风吟个人博客

你可能感兴趣的:(SpringBoot整合webSocket)