作者 | LLLSQ

责编 | 郭芮

本文,我们来讲下SpringBoot集成WebSocket,打造一个聊天室。

WebSocket 是什么?

WebSocket 是一种网络通信协议,RFC6455 定义了它的通信标准。

了解计算机网络协议的人应该都知道,HTTP 协议是一种无状态的、无连接的、单向的应用层协议。它采用了请求/响应模型,通信请求只能由客户端发起,服务端对请求做出应答处理。

这种通信模型有一个弊端,HTTP 协议无法实现服务器主动向客户端发起消息。这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。大多数 Web 应用程序将通过频繁的异步JavaScript和XML(AJAX)请求实现长轮询。轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。

因此,工程师们一直在思考,有没有更好的方法。WebSocket 就是这样发明的。WebSocket 连接允许客户端和服务器之间进行全双工通信,以便任一方都可以通过建立的连接将数据推送到另一端。只需要建立一次连接,就可以一直保持连接状态。这相比于轮询方式的不停建立显然效率要大大提高。

Web 浏览器和服务器都必须实现 WebSockets 协议来建立和维护连接。由于 WebSockets 连接长期存在,与典型的HTTP连接不同,对服务器有重要的影响。基于多线程或多进程的服务器无法适用于 WebSockets,因为它旨在打开连接,尽可能快地处理请求,然后关闭连接。任何实际的 WebSockets 服务器端实现都需要一个异步服务器。

使用idea创建SpringBoot项目

如果不使用上述方法导入maven的,请使用以下代码:

<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>

Spring注入Bean

package com.example.websocket.demo;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Configurationpublic class WebSocketConfig {    @Bean    public ServerEndpointExporter serverEndpointExporter() {        return new ServerEndpointExporter();    }}

编写websocket服务类

package com.cloudkd.websocket;

import java.io.IOException;import java.util.concurrent.CopyOnWriteArraySet;

import javax.websocket.OnClose;import javax.websocket.OnError;import javax.websocket.OnMessage;import javax.websocket.OnOpen;import javax.websocket.Session;import javax.websocket.server.ServerEndpoint;

import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;

//@ServerEndpoint("/websocket/{user}")@ServerEndpoint("/websocket")@Componentpublic class WebSocketServer {    private static final Logger log = LoggerFactory.getLogger(WebSocketServer.class);    //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。    private static int onlineCount = 0;    //concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。    private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>();    //与某个客户端的连接会话,需要通过它来给客户端发送数据    private Session session;

    /**     * 连接建立成功调用的方法     */    @OnOpen    public void onOpen(Session session) {        this.session = session;        //加入set中        webSocketSet.add(this);        //在线数加1        addOnlineCount();        log.info("有新连接加入!当前在线人数为" + getOnlineCount());        try {            sendMessage("连接成功");        } catch (IOException e) {            log.error("websocket IO异常");        }    }    // //连接打开时执行    // @OnOpen    // public void onOpen(@PathParam("user") String user, Session session) {    //    currentUser = user;    //    System.out.println("Connected ... " + session.getId());    // }

    /**     * 连接关闭调用的方法     */    @OnClose    public void onClose() {        webSocketSet.remove(this);  //从set中删除        subOnlineCount();           //在线数减1        log.info("有一连接关闭!当前在线人数为" + getOnlineCount());    }

    /**     * 收到客户端消息后调用的方法     *     * @param message 客户端发送过来的消息     */    @OnMessage    public void onMessage(String message, Session session) {        log.info("来自客户端的消息:" + message);        //群发消息        for (WebSocketServer item : webSocketSet) {            try {                item.sendMessage(message);            } catch (IOException e) {                e.printStackTrace();            }        }    }

    /**     * @param session     * @param error     */    @OnError    public void onError(Session session, Throwable error) {        log.error("发生错误");        error.printStackTrace();    }

    public void sendMessage(String message) throws IOException {        this.session.getBasicRemote().sendText(message);    }

    /**     * 群发自定义消息     */    public static void sendInfo(String message) {        log.info(message);        for (WebSocketServer item : webSocketSet) {            try {                item.sendMessage(message);            } catch (IOException ignored) {            }        }    }

    private static synchronized int getOnlineCount() {        return onlineCount;    }

    private static synchronized void addOnlineCount() {        WebSocketServer.onlineCount++;    }

    private static synchronized void subOnlineCount() {        WebSocketServer.onlineCount--;    }}

编写一个前端客户端

图中位置创建一个简单的index.html页面:

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>Title</title>    <script>        var websocket = null;        //判断当前浏览器是否支持WebSocket        if ('WebSocket' in window) {//这里ws://192.168.1.111:8080/websocket 写自己的ip和端口号            websocket = new WebSocket("ws://192.168.1.111:8080/websocket");        }        else {            alert('Not support websocket')        }

        //连接发生错误的回调方法        websocket.onerror = function () {            setMessageInnerHTML("error");        };

        //连接成功建立的回调方法        websocket.onopen = function (event) {            setMessageInnerHTML("open");        }

        //接收到消息的回调方法        websocket.onmessage = function (event) {            setMessageInnerHTML(event.data);        }

        //连接关闭的回调方法        websocket.onclose = function () {            setMessageInnerHTML("close");        }

        //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。        window.onbeforeunload = function () {            websocket.close();        }

        //将消息显示在网页上        function setMessageInnerHTML(innerHTML) {            document.getElementById('message').innerHTML += innerHTML + '<br/>';        }

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

        //发送消息        function send() {            var message = document.getElementById('text').value;            websocket.send(message);        }    </script></head><body>    <h3>Welcome</h3><br/>    <input id="text" type="text"/>    <button onclick="send()">Send</button>    <button onclick="closeWebSocket()">Close</button>    <div id="message"></div></body></html>

启动项目,做个测试

点击启动项目:

启动完成。访问index.html测试一下:

okay,成功启动。后台日志也有记录:

说个话试试:

也可以多开几个页面测试下。好了,下面来完成服务端向客户端推消息。

服务端向客户端推消息(后台主动)

产生消息的场景有多种,HTTP(s)、定时任务、MQ等,这里我用一个HTTP请求的controller代码完成。

编写一个pushWebController类:

package com.example.websocket.demo;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;import java.util.HashMap;import java.util.Map;@RestControllerpublic class PushWebController {    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @GetMapping(value = "/pushWeb")    public Map<String, Object> pushVideoListToWeb(String message) {        Map<String, Object> result = new HashMap<String, Object>();        try {            WebSocketServer.sendInfo("有新客户呼入,message:" + message);            result.put("operationResult", true);        } catch (Exception e) {            result.put("operationResult", true);        }        return result;    }}

重新启动项目。测试下:

成功了!到此位置,demo已经成功实现了。如果你恰好也有可以用WebSocket实现的类似场景,希望对你有帮助。

Github地址:https://github.com/liangyisen/springboot_websocket。

作者:LLLSQ,一只有着悲惨故事的北漂程序员,为读者提供热点技术文章和IT实时热点新闻、架构、面试信息等最新讯息。

声明:本文为作者投稿,版权归其个人所有。

推荐阅读:

  • 老罗的锤子手机是怎么做到一年被“死”N 次的?| 畅言

  • 帝都程序员大型面基现场,没想到是这样的!

  • “双十一”即将来临,先来看看快递物流企业的大数据

  • Web 2.0 已死,Web 3.0 当立!这一次竟是金融行业被淘汰?

  • @程序员,这个官宣了解下!

  • 走出腾讯:一个80后技术人的信仰

  • 10行代码爬取全国所有A股/港股/新三板上市公司信息

如何打造程序员专属聊天室?相关推荐

  1. 速领,我给大家做了程序员专属红包封面~

    了不起的程序员们,新年快乐!最近微信红包提供了定制封面,很多品牌都纷纷定制了自己的专属红包封面. 但是,作为具有数百万群体的程序员们,怎么能没有一款专属于他们的红包封面呢? 于是,真的有人设计了一款专 ...

  2. fastposter v2.6.2 发布 程序员专属海报生成器

    fastposter v2.6.2 发布 程序员专属海报生成器

  3. 《带你体验程序员专属编辑器Markdown编辑器|CSDN编辑器测评》

    一.前言   Markdown编辑器编辑可以说是程序员专属文档编辑器了,为什么这么说呢,因为在使用Markdown编辑器时需要学习一定语法,有一定的代码功底,CSDN为广大博友提供Markdown编辑 ...

  4. 文本编辑--程序员专属技能

    文本编辑–获得更加强大的文档处理能力(程序员专属) 文章目录 文本编辑--获得更加强大的文档处理能力(程序员专属) 1.typora(markdown神器) 1.1 概述 1.2 安装 1.3 官网 ...

  5. OSChina 周二乱弹 ——程序员在聊天中注意观察什么细节

    2019独角兽企业重金招聘Python工程师标准>>> Osc乱弹歌单(2017)请戳(这里) [今日歌曲] @达尔文  : 分享买辣椒也用券的单曲<起风了(Cover 高橋優 ...

  6. 程序员专属表情包,正在疯传中!

    相信很多人都和小编一样喜欢收藏表情包,那么,今天就给大家搜集了一套程序员专属表情包! 程序员被BUG逼入魔的幻想 当一个刚毕业的程序员新手说在我的代码里发现了一个 bug 时程序员的样子 程序员的蜗居 ...

  7. 程序员专属红包封面来了,一共四款

    大家好,我是校长. 到今天,一共做了四款红包封面,其实,挺火爆的,微信官方一共给我的四个号累计赠送了 25 万个红包封面名额,所以,我也做了四款红包封面设计. 现在全部开放,送给大家. 第一款:程序员 ...

  8. 做一份程序员专属的日历

    做一份程序员专属的日历 #嘿小猿代码日历# 曾经用过挺多日历类的产品,比如果壳的物种日历,豆瓣的电影日历,每每翻开一页纸就有一种收获一份小礼物的欣喜. 作为一名程序猿,我对日历这样桌面小物件的使用方式 ...

  9. fastposter v2.6.1 发布 程序员专属海报生成器

    fastposter v2.6.1 发布 程序员专属海报生成器 fastposter电商级海报生成器,程序员专属海报生成器,一分钟完成海报开发,轻松在线作图.支持Java.Python.PHP. Go ...

最新文章

  1. Ubuntu 14.04.1 安装 python mysqldb
  2. 网页编程html link,Web--CSS控制页面(link与import方式区别)详解
  3. 数据结构 — B+ 树
  4. linux 29900端口,USB2.0接口100M以太网芯片SR9900(A)的应用
  5. python帮助文档中查看内置函数_PYTHON官方文档内置函数整理
  6. Qt creator5.7 OpenCV249之中值滤波(含源码下载)
  7. 2345联盟通过流氓软件推广挖矿工具, 众多用户电脑沦为“肉鸡”
  8. 7月25日训练赛签到题HDU1257
  9. Google Earth Engine ——LANDSAT8——TOA系列数据
  10. ZT - 谷歌微软等三巨头掀电视革命:智能电视年底成真
  11. ORACLE11g数据库安装-刘建-专题视频课程
  12. 版权符号圈c的输入方法
  13. 英语基础语法-语态(被动语态Be done)
  14. Redis 客户端之Lettuce配置使用(基于Spring Boot 2.x)
  15. c++ lamber表达式
  16. 《茅山后裔》全集(叶欣周铁版),MP3完整资源下载
  17. 本地交通基础设施存在的问题
  18. 大数据集群迁移的那一夜是怎么过的|回忆录
  19. 《光明传说》主城建筑图文详解の冒险者商店
  20. Tomcat禁止打印日志

热门文章

  1. 在docker container中为gsutil认证gcloud
  2. Golang结构体与面向对象
  3. 区块链中的基础数据结构
  4. 2021-2025年中国船用炉灶行业市场供需与战略研究报告
  5. android 颜色0x00,Android 状态栏颜色兼容方案
  6. ARP攻击的心得体会
  7. 从入门到入土:Python爬虫学习|实例练手|爬取百度产品列表|Xpath定位标签爬取|代码注释详解
  8. JAVA Swing GUI设计 WindowBuilder Pro Container使用大全4——JSplitPane使用
  9. 编程语言“鄙视链” +1?亚马逊力捧 Rust,Go 技术负责人连发 14 条推特抵制“拉踩”
  10. 牛人三个月内花不到 5000 美元拿到 CS 学位