1. 1 不可撤销
  2. 2 小年兽 程嘉敏
  3. 3 手放开 李圣杰
  4. 4 迷人的危险3(翻自 dance flow) FAFA
  5. 5 山楂树之恋 程佳佳
  6. 6 summertime cinnamons / evening cinema
  7. 7 不谓侠(Cover 萧忆情Alex) CRITTY
  8. 8 神武醉相思(翻自 优我女团) 双笙
  9. 9 空山新雨后 音阙诗听 / 锦零
  10. 10 Wonderful U (Demo Version) AGA
  11. 11 广寒宫 丸子呦
  12. 12 陪我看日出 回音哥
  13. 13 春夏秋冬的你 王宇良
  14. 14 世界が终わるまでは… WANDS
  15. 15 多想在平庸的生活拥抱你 隔壁老樊
  16. 16 千禧 徐秉龙
  17. 17 我的一个道姑朋友 双笙
  18. 18 大鱼  (Cover 周深) 双笙
  19. 19 霜雪千年(Cover 洛天依 / 乐正绫) 双笙 / 封茗囧菌
  20. 20 云烟成雨(翻自 房东的猫) 周玥
  21. 21 情深深雨濛濛 杨胖雨
  22. 22 Five Hundred Miles Justin Timberlake / Carey Mulligan / Stark Sands
  23. 23 斑马斑马 房东的猫
  24. 24 See You Again Wiz Khalifa / Charlie Puth
  25. 25 Faded Alan Walker / Iselin Solheim
  26. 26 Natural J.Fla
  27. 27 New Soul Vox Angeli
  28. 28 ハレハレヤ(朗朗晴天)(翻自 v flower) 猫瑾
  29. 29 像鱼 王贰浪
  30. 30 Bye Bye Bye Lovestoned
  31. 31 Blame You 眠 / Lopu$
  32. 32 Believer J.Fla
  33. 33 书信 戴羽彤
  34. 34 柴 鱼 の c a l l i n g【已售】 幸子小姐拜托了
  35. 35 夜空中最亮的星(翻自 逃跑计划) 戴羽彤
  36. 36 慢慢喜欢你 LIve版(翻自 莫文蔚) 戴羽彤
  37. 37 病变(翻自 cubi) 戴羽彤
  38. 38 那女孩对我说 (完整版) Uu
  39. 39 绿色 陈雪凝
  40. 40 月牙湾 LIve版(翻自 F.I.R.) 戴羽彤
夜空中最亮的星(翻自 逃跑计划) - 戴羽彤
00:00 / 04:10

夜空中最亮的星 能否听清

那仰望的人 心底的孤独和叹息

夜空中最亮的星 能否记起

那曾与我同行 消失在风里的身影

我祈祷拥有一颗透明的心灵

和会流泪的眼睛

给我再去相信的勇气

越过谎言去拥抱你

每当我找不到存在的意义

每当我迷失在黑夜里

噢喔喔 夜空中最亮的星

请指引我靠近你

夜空中最亮的星 是否知道

那曾与我同行的身影 如今在哪里

夜空中最亮的星 是否在意

是等太阳先升起 还是意外先来临

我宁愿所有痛苦都留在心底

也不愿忘记你的眼睛

哦 给我再去相信的勇气

哦 越过谎言去拥抱你

每当我找不到存在的意义

每当我迷失在黑夜里

噢喔喔 夜空中最亮的星

请照亮我向前行 哒~

我祈祷拥有一颗透明的心灵

和会流泪的眼睛 哦

给我再去相信的勇气

哦 越过谎言去拥抱你

每当我找不到存在的意义

每当我迷失在黑夜里

噢喔喔 夜空中最亮的星

请照亮我向前行

spring-boot之webSocket · 上

前言

昨天我们已经分享完了security的相关知识点,所以从今天开始我们要开始学习spring-boot另一个组件——webSocket

websocket也算是spring-boot的一个核心组件,目前我能想到的应用场景就是群聊,所以我们今天的内容核心就是搭建一个简易版的网络聊天室。

webSocket

websocket是什么

在开始正文之前,我们先看下什么是webSocket,下面是我在一本springboot书籍上找到的解释:

WebSocket 协议是基于 TCP 的一种新的网络协议 。它实现了浏览器与服务器全双工( full-duplex )通信一一允许服务器主动发送信息给客户端,这样就可以实现从客户端发送消息到服务器 ,而服务器又可以转发消息到客户端,这样就能够实现客户端之间的交互。对于WebSocket 的 开发 ,Spring也提供了 良好 的支持 。
目前很多浏览器己经实现了Web Socket 协议 ,但是依旧存在着很多浏览器没有实现该协议,为了 兼容那 些没有实现该协议的浏览器 , 往往还需要通过 STOMP 协议来完成这些兼容。

简单来说,webSocket就是一种新的网络协议,在这种协议的加持下,运行服务端给客户端直接发送消息,而且服务器也可以把消息转发给客户端。

在以前的网络协议中,服务端只能被动接受客户端的请求,然后才能给客户端发送数据,但是有了webSocket协议,我们就可以实现类似于打电话这样的双工通信,确实方便了很多。

简易聊天室

下面我们通过webSocket来搭建一个简易的网络聊天室。

项目依赖

首先创建一个spring-boot项目,然后引入websocket的依赖:

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

同时我还加入了securitythymeleaf等附属依赖:

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

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

这两个依赖就不过多说明了,security昨天才分享完,还是热乎的。

websocket配置类

websocket的配置比较简单,主要就是创建一个服务端实例,就相当于往容器中注入了一个ServerEndpointExporter实例对象。

@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}
websokcet服务实现

这里就是websocket服务的关键,也就是服务提供者。

@ServerEndpoint("/ws")
@Service
public class WebSocketService {
    private final Logger logger = LoggerFactory.getLogger(WebSocketService.class);

    private Map<String, String> nameMap = Maps.newHashMap();

    {
        nameMap.put("nezha", "哪吒");
        nameMap.put("pangu", "盘古");
        nameMap.put("zhongkui", "钟馗");
        nameMap.put("fuxi", "伏羲");
        nameMap.put("shennongshi", "神农氏");
        nameMap.put("kuafu", "夸父");
        nameMap.put("nvwa", "女娲");
        nameMap.put("jiangziya", "姜子牙");
        nameMap.put("jingwei", "精卫");
    }

    // 在线数量
    private static AtomicInteger onlineCount = new AtomicInteger(0);
	// 保存已建立连接的客户端(在线)
    private static CopyOnWriteArraySet<WebSocketService> webSocketServiceSet = Sets.newCopyOnWriteArraySet();

    private Session session;

    public Session getSession() {
        return session;
    }

    public void setSession(Session session) {
        this.session = session;
    }

    @OnOpen
    public void onOpen(Session session) {
        String name = nameMap.get(session.getUserPrincipal().getName());
        this.session = session;
        webSocketServiceSet.add(this);
        addOnlineCount();
        logger.info("有新连接加入!当前在线人数为: {}", onlineCount.get());
        webSocketServiceSet.parallelStream().forEach(item -> {
            try {
                sendMessage(item.getSession(), String.format("%s加入群聊!", name));
            } catch (Exception e) {
                logger.error("发送消息异常:", e);
            }
        });
    }

    @OnMessage
    public void onMessage(String message, Session session) {
        logger.info("来自客户端的消息:{}", message);
        webSocketServiceSet.parallelStream().forEach(item -> {
            String name = nameMap.get(session.getUserPrincipal().getName());
            logger.info("{}发送了一条消息:{}", name, message);
            try {
                item.sendMessage(item.getSession(), String.format("%s:%s", name, message));
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
    }

    @OnClose
    public void onClose() {
        webSocketServiceSet.remove(this);
        subOnlineCount();
    }

    @OnError
    public void onError(Session session, Throwable t) {
        logger.error("发生错误:", t);
    }

    /**
     * 在线人数加一
     */
    private void addOnlineCount() {
        onlineCount.incrementAndGet();
    }

    /**
     * 在线人数减一
     */
    private void subOnlineCount() {
        onlineCount.decrementAndGet();
    }

    private void sendMessage(Session session, String message) throws IOException {
        session.getBasicRemote().sendText(message);
    }
}

@ServerEndpoint注解制定了我们服务的节点路径,这样也确定了我们wesocket服务的访问地址:

ws://localhost:8080/ws

地址中的ws表示协议类别,也就是websocket的缩写,紧跟着的是我们springboot服务的地址(主机、端口等),然后就是我们的websocket的节点地址。

@service注解也就是我们最常用的服务注解,就是把他标记成springboot可以管理的组件,没有这个注解,websocket是访问不到的:

紧接着,我们写了四个监听方法,方法上都有对应的注解标注:

  • OnOpen:客户端首次连接服务端时会调用该方法
  • OnMessage:客户端发送消息时会调用该方法
  • OnClose:客户端断开连接时,会调用该方法
  • OnError:发生错误时会调用该方法
用户登录配置

为了更好的演示,我加入security组件,这样用户登录之后,session中就保留了用户的用户信息,方便前端对数据进行展示:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("zhongkui").password(passwordEncoder.encode("123456")).roles("user")
                .and().withUser("fuxi").password(passwordEncoder.encode("123456")).roles("user")
                .and().withUser("pangu").password(passwordEncoder.encode("123456")).roles("user")
                .and().withUser("nezha").password(passwordEncoder.encode("123456")).roles("user")
                .and().withUser("nvwa").password(passwordEncoder.encode("123456")).roles("user")
                .and().withUser("jiangziya").password(passwordEncoder.encode("123456")).roles("user")
        .and().passwordEncoder(passwordEncoder);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin().and()
                .httpBasic()
                .and().logout().logoutUrl("/logout");
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}

websocket服务中,我还构建了用户名和用户姓名的映射,这样在用户建立连接的时候或者发送消息的时候,我就可以根据session的用户名拿到用户的姓名了。

前端页面实现

这里最核心的就是websocket连接的那段js了:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>test page</title>
</head>
<body>
websocket测试<br>
<input id = "message" type="text">
<button onclick="sendMessage()">发送消息</button>
<button onclick="closeWebSocket()">关闭websocket连接</button>
<button onclick="logout()">退出登录</button>
<div id="context"></div>

<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
<script type="application/javascript">
    function logout() {
        closeWebSocket();
        $.ajax({
            url: "/logout",
            type: "POST",
            success: function (rsp) {
                console.log("退出登录成功")
                console.log(rsp)
            }
        })
    }

    var websocket = null;
    // 判断当前浏览榕是否支持 WebSocket
    if ('WebSocket' in window) {
        // 创建 WebSocket 对象,连接服务器端点
        websocket = new WebSocket("ws://localhost:8080/ws");
    } else {
        alert('Not support websocket')
    }
    // 连接发生错误的 回调方法
    websocket.onerror = function () {
        appendMessage("error");
    }
    // 连接成功建立的回调方法
    websocket.onopen = function (event) {
            appendMessage("open ");
        }
    // 接收到消息的回调方法
    websocket.onmessage = function (event) {
        appendMessage(event.data);
    }
    // 连接关闭的回调方法
    websocket.onclose = function () {
            appendMessage(" close ");
        }
    // 监听窗口关闭事件,当窗口关闭时,主动关闭 websocket 连接
    // 防止连接还没断开就关闭窗口,server 端会抛异常
    window.onbeforeunload = function () {
        websocket.close();
    }

    // 将消息显示在网页上
    function appendMessage(message) {
        var context = $("#context").html() + "<br/>" + message;
        $("#context").html(context);
    }

    // 关闭连接
    function closeWebSocket() {
        websocket.close();
        logout();
    }

    // 发送消息
    function sendMessage() {
        var message = $("#message").val();
        websocket.send(message);
    }
</script>

</body>
</html>

首先我们判断浏览器是否支持WebSocket,如果支持会建立websocket连接,然后设定WebSocket的一些回调函数,和服务器端对应,而且页面还是比较简单的。

测试

下面我们简单测试下,我们分别登录三个账号:nezhanvwa伏羲,然后用三个账号分别发送消息:

效果还是可以的,首先是哪吒三太子加入群聊,然后时女娲加入群聊,然后他们分别发送消息,接着伏羲加入群聊,发送消息。第一个进群的人,会收到后面进群的所有人的消息,是不是和我们的微信差不多呢?

总结

websocket还是蛮有意思的,而且很容易上手。如果你有做一款自己的聊天工具,那么websocket应该是最佳选择,相比于socket,它更轻量,也更灵活,相比于传统的http通信,它支持双工通信。

总之,用websocket做一款聊天工具,真的是太简单了。后面有时间的话,用它做一个简易版的微信。好了,今天就先到这里吧!

最后,附上今天项目的源码地址,有兴趣的小伙伴可以自己动手练练,还挺有意思的:

https://github.com/Syske/learning-dome-code/tree/dev/sping-boot-websocket-demo
posted @ 2021-07-26 14:24  云中志  阅读(60)  评论(0编辑  收藏  举报