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

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

前言

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

关于WebSocket

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

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

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

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

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

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

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

WebSocker属性

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

属性 描述
Socket.readyState 只读属性 readyState 表示连接状态,可以是以下值:
0 - 表示连接尚未建立。
1 - 表示连接已建立,可以进行通信。
2 - 表示连接正在进行关闭。
3 - 表示连接已经关闭或者连接不能打开。
Socket.bufferedAmount 只读属性 bufferedAmount 已被 send() 放入正在队列中等待传输,但是还没有发出的 UTF-8 文本字节数。

WebSocket事件

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

事件 事件处理程序 描述
open Socket.onopen 连接建立时触发
message Socket.onmessage 客户端接收服务端数据时触发
error Socket.onerror 通信发生错误时触发
close Socket.onclose 连接关闭时触发

WebSocket方法

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

方法 描述
Socket.send() 使用连接发送数据
Socket.close() 关闭连接

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));
    }
}

注意点:

  1. @ServerEndpoint的value值填写时,开头需要加上/,不然会提示路径无效。
  2. 需要加上类型@Component注解,使得能被扫描到。
  3. 这里的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系列教程,如有雷同,请多多包涵了。本文是作者在电脑前一字一句敲的,每一步都是自己实践的。若文中有所错误之处,还望提出,谢谢。

老生常谈

  • 个人QQ:499452441
  • 微信公众号:lqdevOps
公众号

个人博客:http://blog.lqdev.cn

完整示例:https://github.com/xie19900123/spring-boot-learning/tree/master/chapter-19

原文地址:http://blog.lqdev.cn/2018/08/14/springboot/chapter-nineteen/

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,864评论 6 494
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,175评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,401评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,170评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,276评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,364评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,401评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,179评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,604评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,902评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,070评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,751评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,380评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,077评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,312评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,924评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,957评论 2 351

推荐阅读更多精彩内容

  • Spring Web MVC Spring Web MVC 是包含在 Spring 框架中的 Web 框架,建立于...
    Hsinwong阅读 22,369评论 1 92
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,644评论 18 139
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,789评论 6 342
  • 27 谁的人生,没有些值得回首的往事呢? 谁的心里,没装过些曾经有缘认识,继而交往的人呢? 只是有人让这人,这事,...
    雁韧阅读 223评论 2 16
  • 宝宝出生后,还没满月就有湿疹,先是脖子下细小的红点子,然后发展到胳膊上,胸前,面积越发大起来。我建议给她涂...
    汾湖秀阅读 531评论 12 2