查看原文
其他

SpringBoot | 第十九章:web 应用开发之 WebSocket

ImportNew 2021-12-02

The following article is from 一枚趔趄的猿 Author 謝謝同学

(点击上方公众号,可快速关注)


来源:oKong ,

blog.lqdev.cn/2018/08/14/springboot/chapter-nineteen/


前言


web开发也讲解了三章了,这章节开始讲解关于与前端通信相关知识。实现一个在线聊天室类似的功能或者后端推送消息到前端,在没有WebSocket时,读大学那伙还有接触过DWR(Direct Web Remoting),也使用过轮询的方式,当Servlet3.0出来后,也有使用其异步连接机制进行前后端通信的。今天我们就来说说WebSocket。它是HTML5开始提供的。


关于WebSocket


WebSocket是HTML5开始提供的一种在单个TCP连接上进行全双工通讯的协议。


在WebSocket API中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。


浏览器通过JavaScript向服务器发出建立WebSocket连接的请求,连接建立以后,客户端和服务器端就可以通过TCP连接直接交换数据。


当获取Web Socket连接后,你可以通过 send() 方法来向服务器发送数据,并通过 onmessage事件来接收服务器返回的数据。


对于前端,创建一个WebSocket对象,如下:


var Socket = new WebSocket(url, [protocol] );


说明:第一个参数 url, 指定连接的 URL。第二个参数 protocol 是可选的,指定了可接受的子协议。


WebSocker属性


以下是WebSocket对象的属性。假定我们使用了以上代码创建了Socket对象:



WebSocket事件


以下是 WebSocket 对象的相关事件。假定我们使用了以上代码创建了 Socket 对象:



WebSocket方法


以下是 WebSocket 对象的相关方法。假定我们使用了以上代码创建了 Socket 对象:



WebSocket实践


前面介绍了在浏览器端中webSocket的相关知识点,现在我们就来搭建一个后台对接应用,以实现一个简单的在线聊天室。


一点知识


后端关于WebSocket的实现是基于JSR356标准的。该标准的出现,统一了

WebSocket的代码写法。只要支持web容器支持JSR356标准,那么实现方式是一致的。而目前实现方式有两种,一种是注解方式,另一种就是继承继承javax.websocket.Endpoint类了。


常用注解说明


@WebSocketEndpoint

注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端。注解的值将被用于监听用户连接的终端访问URL地址。


@onOpen

打开一个新连接,即有新连接时,会调用被此注解的方法。


@onClose

关闭连接时调用。


@onMessage

当服务器接收到客户端发送的消息时所调用的方法。


@PathParam

接收uri参数的,与@PathVariable功能差不多,可通过url获取对应值

搭建一个简易聊天室


0.加入POM依赖。


<dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-web</artifactId>

</dependency>

<dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-websocket</artifactId>

</dependency>


1.编写控制层,对应WebSocket的各事件。同时抽取了个公用类,进行通用方法调用。


WebSocketController.java


/**

 * websocket 简易聊天

 * @author oKong

 *

 */

//由于是websocket 所以原本是@RestController的http形式 

//直接替换成@ServerEndpoint即可,作用是一样的 就是指定一个地址

//表示定义一个websocket的Server端

@Component

@ServerEndpoint(value = "/my-chat/{usernick}")

@Slf4j

public class WebSocketController {

     

    /**

     * 连接事件 加入注解

     * @param session

     */

    @OnOpen

    public void onOpen(@PathParam(value = "usernick") String userNick,Session session) {

        String message = "有新游客[" + userNick + "]加入聊天室!";

        log.info(message);

        WebSocketUtil.addSession(userNick, session);    

        //此时可向所有的在线通知 某某某登录了聊天室         

        WebSocketUtil.sendMessageForAll(message);

    }

     

    @OnClose

    public void onClose(@PathParam(value = "usernick") String userNick,Session session) {

        String message = "游客[" + userNick + "]退出聊天室!";

        log.info(message);

        WebSocketUtil.remoteSession(userNick);  

        //此时可向所有的在线通知 某某某登录了聊天室         

        WebSocketUtil.sendMessageForAll(message);

    }

     

    @OnMessage

    public void OnMessage(@PathParam(value = "usernick") String userNick, String message) {

        //类似群发

        String info = "游客[" + userNick + "]:" + message;

        log.info(info);

        WebSocketUtil.sendMessageForAll(message);

    } 

     

    @OnError

    public void onError(Session session, Throwable throwable) {

        log.error("异常:", throwable);

        try {

            session.close();

        } catch (IOException e) {

            e.printStackTrace();

        }

        throwable.printStackTrace();

    }

 

}


WebSocketUtil.java


public class WebSocketUtil {

 

    /**

     * 简单使用map进行存储在线的session

     * 

     */

    private static final Map<String, Session> ONLINE_SESSION = new ConcurrentHashMap<>();

     

    public static void addSession(String userNick,Session session) {

        //putIfAbsent 添加键—值对的时候,先判断该键值对是否已经存在

        //不存在:新增,并返回null

        //存在:不覆盖,直接返回已存在的值

//      ONLINE_SESSION.putIfAbsent(userNick, session);

        //简单示例 不考虑复杂情况。。怎么简单怎么来了。。

        ONLINE_SESSION.put(userNick, session);

    }

     

    public static void remoteSession(String userNick) {

        ONLINE_SESSION.remove(userNick);

    }

     

    /**

     * 向某个用户发送消息

     * @param session 某一用户的session对象

     * @param message

     */

    public static void sendMessage(Session session, String message) {

        if(session == null) {

            return;

        }

        // getAsyncRemote()和getBasicRemote()异步与同步

        Async async = session.getAsyncRemote();

        //发送消息

        async.sendText(message);

    }

     

    /**

     * 向所有在线人发送消息

     * @param message

     */

    public static void sendMessageForAll(String message) {

        //jdk8 新方法

        ONLINE_SESSION.forEach((sessionId, session) -> sendMessage(session, message));

    }

}


注意点:


  • @ServerEndpoint的value值填写时,开头需要加上/,不然会提示路径无效。

  • 需要加上类型@Component注解,使得能被扫描到。

  • 这里的session等,都在包javax.websocket包下的,注意区分。


2.编写主启动类,主要是加入注解@EnableWebSocket和申明一个Websocket endpoint类。


@SpringBootApplication

@EnableWebSocket

@Slf4j

public class Chapter19Application {

 

    public static void main(String[] args) {

        SpringApplication.run(Chapter19Application.class, args);

        log.info("Chapter19启动!");

    }

     

    /**

     * 会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint

     * 要注意,如果使用独立的servlet容器,

     * 而不是直接使用springboot的内置容器,

     * 就不要注入ServerEndpointExporter,因为它将由容器自己提供和管理。

     */

    @Bean

    public ServerEndpointExporter serverEndpointExporter() {

        return new ServerEndpointExporter();

    }

}


3.启动应用,利用在线的测试工具进行测试。这里直接使用了http://coolaf.com/tool/chattest进行测试。当然也可以自己写一个html了。


首先,输入我们的服务地址:ws://127.0.0.1:8080/my-chat/okong,连接后就可以看见服务器返回的消息了。



我们再开一个标签页,然后继续以另一个身份进入:



这时,可以看见第一个页面开的,也收到消息了。现在我们发送一条消息:



然后,其中一个断开连接:



然后可以愉快聊天了,简单的一个聊天室就完成了。


参考资料


  1. https://docs.spring.io/spring/docs/4.3.18.RELEASE/spring-framework-reference/htmlsingle/#websocket

  2. https://docs.spring.io/spring-boot/docs/1.5.15.RELEASE/reference/htmlsingle/#boot-features-websockets

  3. http://www.oracle.com/technetwork/articles/java/jsr356-1937161.html

  4. http://www.runoob.com/html/html5-websocket.html


总结


本章节主要是讲解了WebSocket的使用。因为有统一标准的存在,编写webSocket也是很简单的。对于如何一对一聊天,大家可以自行编写下,因为知道了对方名称,就能找出对方的session然后就能发送消息了。


最后


目前互联网上很多大佬都有SpringBoot系列教程,如有雷同,请多多包涵了。本文是作者在电脑前一字一句敲的,每一步都是自己实践的。若文中有所错误之处,还望提出,谢谢。


系列



【关于投稿】


如果大家有原创好文投稿,请直接给公号发送留言。


① 留言格式:
【投稿】+《 文章标题》+ 文章链接

② 示例:
【投稿】《不要自称是程序员,我十多年的 IT 职场总结》:http://blog.jobbole.com/94148/

③ 最后请附上您的个人简介哈~



看完本文有收获?请转发分享给更多人

关注「ImportNew」,提升Java技能

: . Video Mini Program Like ,轻点两下取消赞 Wow ,轻点两下取消在看

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存