netty-websocket

简介: netty-websocket

历经万般红尘劫,犹如凉风轻拂面。——林清玄

今天用了这个netty-websocket-spring-boot-starter

那是相当的香啊

package com.ruben.xchat.controller;
import cn.hutool.core.exceptions.ExceptionUtil;
import com.alibaba.fastjson.JSON;
import com.ruben.xchat.pojo.to.ChatTransferObject;
import com.ruben.xchat.service.WebSocketService;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.timeout.IdleStateEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.yeauty.annotation.*;
import org.yeauty.pojo.Session;
import java.io.IOException;
import java.util.List;
import java.util.Map;
/**
 * WebSocket控制层
 * https://gitee.com/Yeauty/netty-websocket-spring-boot-starter
 *
 * @author <achao1441470436@gmail.com>
 * @since 2021/11/11 9:14
 */
@Slf4j
@Component
@ServerEndpoint(path = "${ws.path}", port = "${ws.port}")
public class WebSocketController {
    @Autowired
    private WebSocketService webSocketService;
    /**
     * 建立会话之前
     *
     * @param session 会话
     * @param headers 请求头
     * @param req     请求参数
     * @param reqMap  请求参数
     * @param arg     路径上的参数
     * @param pathMap 路径上的参数
     * @author <achao1441470436@gmail.com>
     * @since 2021/11/11 9:18
     */
    @BeforeHandshake
    public void handshake(Session session, HttpHeaders headers, @RequestParam String req, @RequestParam MultiValueMap<String, List<Object>> reqMap, @PathVariable String arg, @PathVariable Map<String, Object> pathMap) {
        session.setSubprotocols("stomp");
        // 进行认证
        try {
            webSocketService.verifyLogin(headers);
        } catch (Exception e) {
            session.close();
            ExceptionUtil.wrapAndThrow(e);
        }
    }
    /**
     * 当有新的WebSocket连接完成时
     *
     * @param session 会话
     * @param headers 请求头
     * @param req     请求参数
     * @param reqMap  请求参数
     * @param arg     路径上的参数
     * @param pathMap 路径上的参数
     * @author <achao1441470436@gmail.com>
     * @since 2021/11/11 9:21
     */
    @OnOpen
    public void onOpen(Session session, HttpHeaders headers, @RequestParam String req, @RequestParam MultiValueMap<String, List<Object>> reqMap, @PathVariable String arg, @PathVariable Map<String, Object> pathMap) {
        log.info("new connection");
        webSocketService.registerSession(headers, session);
    }
    /**
     * 连接关闭
     *
     * @param session 会话
     * @author <achao1441470436@gmail.com>
     * @since 2021/11/11 9:22
     */
    @OnClose
    public void onClose(Session session) throws IOException {
        log.info("one connection closed");
        webSocketService.removeSession(session);
    }
    /**
     * 连接发生异常
     *
     * @param session   会话
     * @param throwable 异常
     * @author <achao1441470436@gmail.com>
     * @since 2021/11/11 9:22
     */
    @OnError
    public void onError(Session session, Throwable throwable) {
        log.error("Connection error happened.", throwable);
        webSocketService.removeSession(session);
    }
    /**
     * 收到消息
     *
     * @param session 会话
     * @author <achao1441470436@gmail.com>
     * @since 2021/11/11 9:22
     */
    @OnMessage
    public void onMessage(Session session, String message) {
        System.out.println(message);
        ChatTransferObject chat = JSON.parseObject(message, ChatTransferObject.class);
        switch (chat.getChatToType()) {
            case CHAT_ONE:
                log.info("发送到个人:{}", chat);
                webSocketService.sendSomeone(session, chat);
                break;
            case CHAT_GROUP:
                log.error("发送到群聊");
                break;
            default:
                log.error("未识别的消息类型");
                break;
        }
    }
    /**
     * 收到二进制消息
     *
     * @param session 会话
     * @param bytes   消息
     * @author <achao1441470436@gmail.com>
     * @since 2021/11/11 9:22
     */
    @OnBinary
    public void onBinary(Session session, byte[] bytes) {
        for (byte b : bytes) {
            System.out.println(b);
        }
        session.sendBinary(bytes);
    }
    /**
     * 收到Netty事件
     *
     * @param session 会话
     * @param evt     事件
     * @author <achao1441470436@gmail.com>
     * @since 2021/11/11 9:22
     */
    @OnEvent
    public void onEvent(Session session, Object evt) {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent idleStateEvent = (IdleStateEvent) evt;
            switch (idleStateEvent.state()) {
                case READER_IDLE:
                    System.out.println("read idle");
                    break;
                case WRITER_IDLE:
                    System.out.println("write idle");
                    break;
                case ALL_IDLE:
                    System.out.println("all idle");
                    break;
                default:
                    break;
            }
        }
    }
}


使用注解进行配置,netty的各种配置例如端口、主机、都可以在yml中配置,文档就是gitee中的md,用来做即时通讯简直不要太香

netty-websocket-spring-boot-starter

image.png

English Docs

简介

本项目帮助你在spring-boot中使用Netty来开发WebSocket服务器,并像spring-websocket的注解开发一样简单

要求

  • jdk版本为1.8或1.8+

快速开始

  • 添加依赖:
<dependency>
  <groupId>org.yeauty</groupId>
  <artifactId>netty-websocket-spring-boot-starter</artifactId>
  <version>0.12.0</version>
</dependency>
  • 在端点类上加上@ServerEndpoint注解,并在相应的方法上加上@BeforeHandshake@OnOpen@OnClose@OnError@OnMessage@OnBinary@OnEvent注解,样例如下:
    @ServerEndpoint(path = "/ws/{arg}")
    public class MyWebSocket {
        @BeforeHandshake
        public void handshake(Session session, HttpHeaders headers, @RequestParam String req, @RequestParam MultiValueMap reqMap, @PathVariable String arg, @PathVariable Map pathMap){
            session.setSubprotocols("stomp");
            if (!"ok".equals(req)){
                System.out.println("Authentication failed!");
                session.close();
            }
        }
        @OnOpen
        public void onOpen(Session session, HttpHeaders headers, @RequestParam String req, @RequestParam MultiValueMap reqMap, @PathVariable String arg, @PathVariable Map pathMap){
            System.out.println("new connection");
            System.out.println(req);
        }
        @OnClose
        public void onClose(Session session) throws IOException {
           System.out.println("one connection closed"); 
        }
        @OnError
        public void onError(Session session, Throwable throwable) {
            throwable.printStackTrace();
        }
        @OnMessage
        public void onMessage(Session session, String message) {
            System.out.println(message);
            session.sendText("Hello Netty!");
        }
        @OnBinary
        public void onBinary(Session session, byte[] bytes) {
            for (byte b : bytes) {
                System.out.println(b);
            }
            session.sendBinary(bytes); 
        }
        @OnEvent
        public void onEvent(Session session, Object evt) {
            if (evt instanceof IdleStateEvent) {
                IdleStateEvent idleStateEvent = (IdleStateEvent) evt;
                switch (idleStateEvent.state()) {
                    case READER_IDLE:
                        System.out.println("read idle");
                        break;
                    case WRITER_IDLE:
                        System.out.println("write idle");
                        break;
                    case ALL_IDLE:
                        System.out.println("all idle");
                        break;
                    default:
                        break;
                }
            }
        }
    }
    • 打开WebSocket客户端,连接到ws://127.0.0.1:80/ws/xxx

    注解

    @ServerEndpoint

    当ServerEndpointExporter类通过Spring配置进行声明并被使用,它将会去扫描带有@ServerEndpoint注解的类 被注解的类将被注册成为一个WebSocket端点 所有的配置项都在这个注解的属性中 ( 如:@ServerEndpoint("/ws") )

    @BeforeHandshake

    当有新的连接进入时,对该方法进行回调 注入参数的类型:Session、HttpHeaders…

    @OnOpen

    当有新的WebSocket连接完成时,对该方法进行回调 注入参数的类型:Session、HttpHeaders…

    @OnClose

    当有WebSocket连接关闭时,对该方法进行回调 注入参数的类型:Session

    @OnError

    当有WebSocket抛出异常时,对该方法进行回调 注入参数的类型:Session、Throwable

    @OnMessage

    当接收到字符串消息时,对该方法进行回调 注入参数的类型:Session、String

    @OnBinary

    当接收到二进制消息时,对该方法进行回调 注入参数的类型:Session、byte[]

    @OnEvent

    当接收到Netty的事件时,对该方法进行回调 注入参数的类型:Session、Object

    配置

    所有的配置项都在这个注解的属性中

属性 默认值 说明
path “/” WebSocket的path,也可以用value来设置
host “0.0.0.0” WebSocket的host,"0.0.0.0"即是所有本地地址
port 80 WebSocket绑定端口号。如果为0,则使用随机端口(端口获取可见 多端点服务)
bossLoopGroupThreads 0 bossEventLoopGroup的线程数
workerLoopGroupThreads 0 workerEventLoopGroup的线程数
useCompressionHandler false 是否添加WebSocketServerCompressionHandler到pipeline
optionConnectTimeoutMillis 30000 与Netty的ChannelOption.CONNECT_TIMEOUT_MILLIS一致
optionSoBacklog 128 与Netty的ChannelOption.SO_BACKLOG一致
childOptionWriteSpinCount 16 与Netty的ChannelOption.WRITE_SPIN_COUNT一致
childOptionWriteBufferHighWaterMark 64*1024 与Netty的ChannelOption.WRITE_BUFFER_HIGH_WATER_MARK一致,但实际上是使用ChannelOption.WRITE_BUFFER_WATER_MARK
childOptionWriteBufferLowWaterMark 32*1024 与Netty的ChannelOption.WRITE_BUFFER_LOW_WATER_MARK一致,但实际上是使用 ChannelOption.WRITE_BUFFER_WATER_MARK
childOptionSoRcvbuf -1(即未设置) 与Netty的ChannelOption.SO_RCVBUF一致
childOptionSoSndbuf -1(即未设置) 与Netty的ChannelOption.SO_SNDBUF一致
childOptionTcpNodelay true 与Netty的ChannelOption.TCP_NODELAY一致
childOptionSoKeepalive false 与Netty的ChannelOption.SO_KEEPALIVE一致
childOptionSoLinger -1 与Netty的ChannelOption.SO_LINGER一致
childOptionAllowHalfClosure false 与Netty的ChannelOption.ALLOW_HALF_CLOSURE一致
readerIdleTimeSeconds 0 IdleStateHandler中的readerIdleTimeSeconds一致,并且当它不为0时,将在pipeline中添加IdleStateHandler
writerIdleTimeSeconds 0 IdleStateHandler中的writerIdleTimeSeconds一致,并且当它不为0时,将在pipeline中添加IdleStateHandler
allIdleTimeSeconds 0 IdleStateHandler中的allIdleTimeSeconds一致,并且当它不为0时,将在pipeline中添加IdleStateHandler
maxFramePayloadLength 65536 最大允许帧载荷长度
useEventExecutorGroup true 是否使用另一个线程池来执行耗时的同步业务逻辑
eventExecutorGroupThreads 16 eventExecutorGroup的线程数
sslKeyPassword “”(即未设置) 与spring-boot的server.ssl.key-password一致
sslKeyStore “”(即未设置) 与spring-boot的server.ssl.key-store一致
sslKeyStorePassword “”(即未设置) 与spring-boot的server.ssl.key-store-password一致
sslKeyStoreType “”(即未设置) 与spring-boot的server.ssl.key-store-type一致
sslTrustStore “”(即未设置) 与spring-boot的server.ssl.trust-store一致
sslTrustStorePassword “”(即未设置) 与spring-boot的server.ssl.trust-store-password一致
sslTrustStoreType “”(即未设置) 与spring-boot的server.ssl.trust-store-type一致
corsOrigins {}(即未设置) 与spring-boot的@CrossOrigin#origins一致
corsAllowCredentials “”(即未设置) 与spring-boot的@CrossOrigin#allowCredentials一致

通过application.properties进行配置

所有参数皆可使用${...}占位符获取application.properties中的配置。如下:

  • 首先在@ServerEndpoint注解的属性中使用${...}占位符
@ServerEndpoint(host = "${ws.host}",port = "${ws.port}")
public class MyWebSocket {
    ...
}
  • 接下来即可在application.properties中配置
ws.host=0.0.0.0
ws.port=80

自定义Favicon

配置favicon的方式与spring-boot中完全一致。只需将favicon.ico文件放到classpath的根目录下即可。如下:

src/
  +- main/
    +- java/
    |   + <source code>
    +- resources/
        +- favicon.ico

自定义错误页面

配置自定义错误页面的方式与spring-boot中完全一致。你可以添加一个 /public/error 目录,错误页面将会是该目录下的静态页面,错误页面的文件名必须是准确的错误状态或者是一串掩码,如下:

src/
  +- main/
    +- java/
    |   + <source code>
    +- resources/
        +- public/
            +- error/
            |   +- 404.html
            |   +- 5xx.html
            +- <other public assets>

多端点服务

  • 快速启动的基础上,在多个需要成为端点的类上使用@ServerEndpoint@Component注解即可
  • 可通过ServerEndpointExporter.getInetSocketAddressSet()获取所有端点的地址
  • 当地址不同时(即host不同或port不同),使用不同的ServerBootstrap实例
  • 当地址相同,路径(path)不同时,使用同一个ServerBootstrap实例
  • 当多个端点服务的port为0时,将使用同一个随机的端口号
  • 当多个端点的port和path相同时,host不能设为"0.0.0.0",因为"0.0.0.0"意味着绑定所有的host
相关文章
|
算法 Java 应用服务中间件
cas5.3:CAS Server搭建
cas5.3:CAS Server搭建
1423 0
|
开发框架 前端开发 网络协议
Spring Boot结合Netty和WebSocket,实现后台向前端实时推送信息
【10月更文挑战第18天】 在现代互联网应用中,实时通信变得越来越重要。WebSocket作为一种在单个TCP连接上进行全双工通信的协议,为客户端和服务器之间的实时数据传输提供了一种高效的解决方案。Netty作为一个高性能、事件驱动的NIO框架,它基于Java NIO实现了异步和事件驱动的网络应用程序。Spring Boot是一个基于Spring框架的微服务开发框架,它提供了许多开箱即用的功能和简化配置的机制。本文将详细介绍如何使用Spring Boot集成Netty和WebSocket,实现后台向前端推送信息的功能。
3561 1
|
6月前
|
Oracle Java 关系型数据库
Java 17 采用率增长 430%
1995年,Sun Microsystems发布Java语言,推动现代多媒体应用发展。凭借“一次编写,到处运行”的优势,Java迅速成为主流编程语言。New Relic最新发布的《2023年Java生态系统现状》报告显示,Java 11以超56%的使用率稳居榜首,Java 8仍占近33%。尽管Oracle每半年更新一次Java版本,但开发者更倾向使用长期支持(LTS)版本。Java 17的采用率在过去一年增长430%,潜力巨大。此外,Amazon已成为最受欢迎的JDK供应商,市场份额达31%。容器化应用也已成为主流,70%的Java应用来自容器。
|
存储 对象存储 Python
Python中使用阿里云OSS存储实现文件上传和下载功能
Python中使用阿里云OSS存储实现文件上传和下载功能
3505 2
|
Kubernetes 监控 开发者
专家级实践:利用Cloud Toolkit进行微服务治理与容器化部署
【10月更文挑战第19天】在当今的软件开发领域,微服务架构因其高可伸缩性、易于维护和快速迭代的特点而备受青睐。然而,随着微服务数量的增加,管理和服务治理变得越来越复杂。作为阿里巴巴云推出的一款免费且开源的开发者工具,Cloud Toolkit 提供了一系列实用的功能,帮助开发者在微服务治理和容器化部署方面更加高效。本文将从个人的角度出发,探讨如何利用 Cloud Toolkit 来应对这些挑战。
228 2
|
Unix Linux 开发者
在Linux中,什么是GPL、GNU,自由由软件?
在Linux中,什么是GPL、GNU,自由由软件?
|
机器学习/深度学习 存储 人工智能
AI在出行场景的应用实践:路线规划、ETA、动态事件挖掘…
本文是#春招专栏#系列的第1篇,根据高德机器学习研发部负责人damon在AT技术讲坛所分享的《AI在出行领域的应用实践》的内容整理而成。
|
Java Maven 开发者
深入剖析Spring Boot在Java Web开发中的优势与应用
深入剖析Spring Boot在Java Web开发中的优势与应用
562 3
关于处理电商系统订单状态的流转,分享下我的技术方案(附带源码)
关于处理电商系统订单状态的流转,分享下我的技术方案(附带源码)
792 0
|
Kubernetes 监控 容器
k9s常用的指令
K9s 是一个用于 Kubernetes 群集管理的命令行工具,它提供了一系列常用的指令,用于查看、管理和监控 Kubernetes 资源。以下是一些常用的 K9s 指令: 1. **查看资源列表:** - `:po`:查看 Pod 列表。 - `:svc`:查看 Service 列表。 - `:deploy`:查看 Deployment 列表。 - `:ns`:查看 Namespace 列表。 2. **在资源列表中的操作:** - 使用上下箭头键浏览资源列表。 - `Enter` 键进入资源的详细信息视图。 - `d`:删除选定的资源。
1179 4