SpringBoot集成WebSocket长连接实际应用

前言:

一、WebSocket之初出茅驴

官方定义:WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。是真正的双向平等对话,属于服务器推送技术的一种。

太官方啦,还是博主过来翻译一下吧 :WebSocket技术只需要service和client建立一次连接,就能实现服务器和客户端双方相互频繁的发送请求和通信!(简单加粗暴的翻译有木有,哈哈!)

WebSocket经典的使用场景:网站在线聊天系统、弹幕系统…

臣附议:webSocket技术无法做到向下兼容,不兼容低版本的IE,因此依赖于浏览器版本,这也正是webSocket非常显著的缺陷。
—————————————————————————————————————————

二、WebSocket的出现到底为我们解决了什么实际问题?

  • 在传统的b/s架构中,要实现服务器向client进行实时消息推送功能,市场上常用的解决方案大致分为三类:
定时轮询客户端以一定的时间间隔向服务端发出请求
长轮询如果服务器没有可以立即返回给客户端的数据,则不会立刻返回一个空结果
流技术客户端隐藏的窗口向服务端发出一个长轮询的请求

长轮询机制:
在这里插入图片描述

综合这几种方案,您会发现这些目前我们所使用的所谓的实时技术并不是真正的实时技术,它们只是在用 Ajax 方式来模拟实时的效果,定时轮询需要实时获取取服务端信息的应用时, client需要频繁轮询server,为了拿到最新信息, client一直这样循环下去server如果一直没有新的消息, client的大多请求都是属于无效请求, 导致会带来很多无谓的网络传输,所以这是一种非常低效的实时方案。长轮询对服务器造成的压力非常大,并且如果服务端的数据变更非常频繁的话,这种方式无异于定时轮询。所以为了解决传统http请求的实际问题,WebSocket技术应运而生!下面博主给张图让大家生动的理解传统HTTP和WebSocket的差异化:
在这里插入图片描述

三、博主使用WebSocket的场景

博主最进在公司调用第三方影像采集系统,由于影像状态是异步返回给业务系统的,导致当业务系统收到第三方回调后,对于前台用户体验来说是无感知的,因此前台必须刷新页面才能获取到影像最新状态。这时候由service主动向client实时发送影像采集状态的通知是最好不过的方案!在上述提到的常用解决方案,像轮询这种比较low的实现,博主作为技术宅,肯定是不会作为技术选型的,哈哈…

四、不多bb,上代码!

本项目是基于SpringBoot环境开发

1、导入websocket坐标

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

2、封装WebSocketUtil工具类,用于提供对session链接、断开连接、推送消息的简单控制。

  public class WebsocketUtil {
    /**
     * 记录当前在线的Session
     */
    private static final Map<String, Session> ONLINE_SESSION = new ConcurrentHashMap<> ();

    /**
     * 添加session
     * @param userId
     * @param session
     */
    public static void addSession(String userId, Session session){
        // 此处只允许一个用户的session链接。一个用户的多个连接,我们视为无效。
        ONLINE_SESSION.putIfAbsent ( userId, session );
    }

    /**
     * 关闭session
     * @param userId
     */
    public static void removeSession(String userId){
        ONLINE_SESSION.remove ( userId );
    }

    /**
     * 给单个用户推送消息
     * @param session
     * @param message
     */
    public static void sendMessage(Session session, String message){
        if(session == null){
            return;
        }

        // 同步
        RemoteEndpoint.Async async  = session.getAsyncRemote ();
        async.sendText ( message );
    }

    /**
     * 向所有在线人发送消息
     * @param message
     */
    public static void sendMessageForAll(String message) {
        //jdk8 新方法
        ONLINE_SESSION.forEach((sessionId, session) -> sendMessage(session, message));
    }
}

3、 WebSocketController
如上,已经创建好了简单的session管理和消息管理,接下来要使用注解的方式,使用SpringBoot的websocket包提供的方法,实现OnOpen、OnClose、OnMessage三个方法,再实现一个OnError方法来应对异常。代码段如下:

/**
 * websocket接口处理类
 */
@Component
@ServerEndpoint ( value = "/chat/{userid}" )
public class WebsocketController {

    /**
     * 连接事件,加入注解
     * @param userId
     * @param session
     */
    @OnOpen
    public void onOpen( @PathParam  ( value = "userid" ) String userId, Session session ) {
        String message ="[" + userId + "]加入聊天室!!";

        // 添加到session的映射关系中
        WebsocketUtil.addSession ( userId, session );
        // 广播通知,某用户上线了
        WebsocketUtil.sendMessageForAll ( message );
    }

    /**
     * 连接事件,加入注解
     * 用户断开链接
     * @param userId
     * @param session
     */
    @OnClose
    public void onClose(@PathParam  ( value = "userId" ) String userId, Session session ) {
        String message ="[" + userId + "]退出了聊天室...";

        // 删除映射关系
        WebsocketUtil.removeSession ( userId );
        // 广播通知,用户下线了
        WebsocketUtil.sendMessageForAll ( message );
    }

    /**
     * 当接收到用户上传的消息
     * @param userId
     * @param session
     */
    @OnMessage
    public void onMessage(@PathParam  ( value = "userId" ) String userId, Session session ,String message) {
        String msg ="[" + userId + "]:"+message;

        // 直接广播
        WebsocketUtil.sendMessageForAll ( msg );
    }

    /**
     * 处理用户活连接异常
     * @param session
     * @param throwable
     */
    @OnError
    public void onError(Session session, Throwable throwable) {
        try {
            session.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        throwable.printStackTrace();
    }
}

4、添加 ServerEndpointExporter 启动Bean

public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    /**
     * 会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint
     * 要注意,如果使用独立的servlet容器,
     * 而不是直接使用springboot的内置容器,
     * 就不要注入ServerEndpointExporter,因为它将由容器自己提供和管理。
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

那些年踩过的坑:
注意:在websocketEndpoint中,使用@Autowired一些列注解注入Bean时候,一直无法注入,报空指针。原因在于spring管理的都是单例(singleton),和 websocket (多对象)相冲突。
解决办法:通过上下文获取bean实例:从Spring上下文获取bean实例的方法

end;
打码不易,谢谢关注!下期再见

  • 16
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值