源码Gitub地址:https://github.com/heibaiying/spring-samples-for-all

一、说明

1.1 项目结构说明

  1. 项目模拟一个简单的群聊功能,为区分不同的聊天客户端,登录时候将临时用户名存储在session当中;
  2. webconfig 包是基础注解的方式配置web,在spring-base-annotation项目中已经讲解过每个类作用;
  3. CustomHander为消息的自定义处理器;
  4. CustomHandershakerInterceptor为自定义的 websocket 的握手拦截器;
  5. webSocketConfig 是websocket 的主要配置类;
  6. 项目以web的方式构建。

1.2 依赖说明

除了基本的spring 依赖外,还需要导入webSocket的依赖包

 <!--spring webSocket 的依赖包 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-websocket</artifactId>
    <version>5.1.3.RELEASE</version>
</dependency>

二、spring websocket

2.1 创建消息处理类,继承自TextWebSocketHandler

/**
 * @author : heibaiying
 * @description : 自定义消息处理类
 */
public class CustomHandler extends TextWebSocketHandler {

    private Map<String, WebSocketSession> nameAndSession = new ConcurrentHashMap<>();

    // 建立连接时候触发
    @Override
    public void afterConnectionEstablished(WebSocketSession session)  {
        String username = getNameFromSession(session);
        nameAndSession.putIfAbsent(username, session);
    }

    // 关闭连接时候触发
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
        String username = getNameFromSession(session);
        nameAndSession.remove(username);
    }

    // 处理消息
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        // 防止中文乱码
        String msg = URLDecoder.decode(message.getPayload(), "utf-8");
        String username = getNameFromSession(session);
        // 简单模拟群发消息
        TextMessage reply = new TextMessage(username + " : " + msg);
        nameAndSession.forEach((s, webSocketSession)
                -> {
            try {
                webSocketSession.sendMessage(reply);
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
    }

    private String getNameFromSession(WebSocketSession session) {
        Map<String, Object> attributes = session.getAttributes();
        return (String) attributes.get(Constant.USER_NAME);
    }
}

2.2 创建websocket 握手拦截器(如果没有权限拦截等需求,这一步不是必须的)

/**
 * @author : heibaiying
 * @description : 可以按照需求实现权限拦截等功能
 */
public class CustomHandshakeInterceptor extends HttpSessionHandshakeInterceptor {

    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
        InetSocketAddress remoteAddress = request.getRemoteAddress();
        InetAddress address = remoteAddress.getAddress();
        System.out.println(address);
        /*
         * 最后需要要显示调用父类方法,父类的beforeHandshake方法
         * 把ServerHttpRequest 中session中对应的值拷贝到WebSocketSession中。
         * 如果我们没有实现这个方法,我们在最后的handler处理中 是拿不到 session中的值
         * 作为测试 可以注释掉下面这一行 可以发现自定义处理器中session的username总是为空
         */
        return super.beforeHandshake(request, response, wsHandler, attributes);
    }
}

2.3 创建websocket的配置类

/**
 * @author : heibaiying
 * @description :websocket 配置
 */
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(new CustomHandler(), "/socket").addInterceptors(new CustomHandshakeInterceptor());
    }
}

2.4 前端 websocket 的实现

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>${sessionScope.get("username")}您好!欢迎进入群聊大厅!</title>
</head>
<body>
<input id="message" type="text">
<button id="btn">发送消息</button>
<div id="show">

</div>
<script>
    let btn = document.getElementById("btn");
    let message = document.getElementById("message");
    let show = document.getElementById("show");
    let ws = new WebSocket("ws://localhost:8080/socket");
    ws.onmessage = function (evt) {
        let node = document.createElement("div");
        node.innerHTML = "<h5>" + evt.data + "</h5>";
        show.appendChild(node);
    };
    btn.addEventListener("click", function () {
        let data = message.value;
        console.log(data);
        if (data) {
            ws.send(encodeURI(data));
        } else {
            alert("请输入消息后发送");
        }
        message.value = "";
    });
    // 关闭页面时候关闭ws
    window.addEventListener("beforeunload", function(event) {
        ws.close();
    });
</script>
</body>
</html>

2.5 简单登录的实现

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/login" method="post">
    <input name="username" type="text">
    <button id="btn">输入临时用户名后登录!</button>
</form>
</body>
</html>
@Controller
public class LoginController {

    @PostMapping("login")
    public String login(String username, HttpSession session){
        session.setAttribute(Constant.USER_NAME,username);
        return "chat";
    }
}

附:源码Gitub地址:https://github.com/heibaiying/spring-samples-for-all

spring 5.x 系列第18篇 —— 整合websocket (代码配置方式)的更多相关文章

  1. spring 5.x 系列第14篇 —— 整合RabbitMQ (代码配置方式)

    源码Gitub地址:https://github.com/heibaiying/spring-samples-for-all 一.说明 1.1 项目结构说明 本用例关于rabbitmq的整合提供简单消 ...

  2. spring 5.x 系列第12篇 —— 整合memcached (代码配置方式)

    文章目录 一.说明 1.1 XMemcached客户端说明 1.2 项目结构说明 1.3 依赖说明 二.spring 整合 memcached 2.1 单机配置 2.2 集群配置 2.3 存储基本类型 ...

  3. spring 5.x 系列第10篇 —— 整合mongodb (代码配置方式)

    源码Gitub地址:https://github.com/heibaiying/spring-samples-for-all 一.说明 1.1 项目结构说明 配置文件位于com.heibaiying. ...

  4. spring 5.x 系列第17篇 —— 整合websocket (xml配置方式)

    源码Gitub地址:https://github.com/heibaiying/spring-samples-for-all 一.说明 1.1 项目结构说明 项目模拟一个简单的群聊功能,为区分不同的聊 ...

  5. spring 5.x 系列第16篇 —— 整合dubbo (代码配置方式)

    文章目录 一. 项目结构说明 二.项目依赖 三.公共模块(dubbo-ano-common) 四. 服务提供者(dubbo-ano-provider) 4.1 提供方配置 4.2 使用注解@Servi ...

  6. spring 5.x 系列第2篇 —— springmvc基础 (代码配置方式)

    文章目录 一.搭建hello spring工程 1.1 项目搭建 1.2 相关注解说明 二.配置自定义拦截器 三.全局异常处理 四.参数绑定 4.1 参数绑定 4.2 关于日期格式转换的三种方法 五. ...

  7. spring 5.x 系列第13篇 —— 整合RabbitMQ (xml配置方式)

    源码Gitub地址:https://github.com/heibaiying/spring-samples-for-all 一.说明 1.1 项目结构说明 本用例关于rabbitmq的整合提供简单消 ...

  8. spring 5.x 系列第11篇 —— 整合memcached (xml配置方式)

    文章目录 一.说明 1.1 XMemcached客户端说明 1.2 项目结构说明 1.3 依赖说明 二.spring 整合 memcached 2.1 单机配置 2.2 集群配置 2.3 存储基本类型 ...

  9. spring 5.x 系列第9篇 —— 整合mongodb (xml配置方式)

    源码Gitub地址:https://github.com/heibaiying/spring-samples-for-all 一.说明 1.1 项目结构说明 配置文件位于resources下,项目以单 ...

随机推荐

  1. Method of Seamless Integration and Independent Evolution of Information-Centric Networking via Software Defined Networking

    A method of transferring data between a software defined network (SDN) and an information-centric ne ...

  2. 简明Python3教程 6.基础

    你肯定不满足于只打印"Hello World"吧? 你想要的更多 - 你希望得到一些输入,操纵它后再从中得到某些东西.我们可以使用python中的常量和变量实现这些功能. 字面常量 ...

  3. Linux内核源代码情景分析-fork()

    父进程fork子进程: child = fork() fork经过系统调用.来到了sys_fork.具体过程请參考Linux内核源码情景分析-系统调用. asmlinkage int sys_fork ...

  4. ASP.NET Core 视图 - ASP.NET Core 基础教程 - 简单教程,简单编程

    原文:ASP.NET Core 视图 - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core 视图 花了几章节,终于把 ASP.NET Core MVC 中的 C 控 ...

  5. Angular路由守卫 canActivate

    作用 canActivate 控制是否允许进入路由. canActivateChild 等同 canActivate,只不过针对是所有子路由. 关键代码 创建路由守卫 import { Injecta ...

  6. POJ 1986 Distance Queries LCA两点距离树

    标题来源:POJ 1986 Distance Queries 意甲冠军:给你一棵树 q第二次查询 每次你问两个点之间的距离 思路:对于2点 u v dis(u,v) = dis(root,u) + d ...

  7. INCORRECT PERMISSIONS ON /USR/LIB/PO1KIT-AGENT-HELPER-1(NEEDS TO BE SETUID ROOT)

    INCORRECT PERMISSIONS ON /USR/LIB/PO1KIT-AGENT-HELPER-1(NEEDS TO BE SETUID ROOT) # sudo chmod +s /us ...

  8. XF 标签和文本控件

    <?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http:/ ...

  9. qmake 时复制文件(自动在编译前做一些操作,且写在.pro文件里)

    有时在编译前需要准备一些文件,例如修改了 QtCreator 的编译输出目录: Build & Run > Default build directory,使用 Promote 后需要在 ...

  10. GIS基础软件及操作(三)

    原文 GIS基础软件及操作(三) 练习三.地图配准操作 1.对无坐标信息的地形图(图片格式)进行地图配准操作2.编辑器的使用(点要素.线要素.多边形要素的数字化) 本例主要介绍如何给无坐标信息的地形图 ...