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
相关文章
|
前端开发 JavaScript API
netty系列之:使用netty搭建websocket客户端
netty系列之:使用netty搭建websocket客户端
|
12月前
|
Kubernetes 监控 开发者
专家级实践:利用Cloud Toolkit进行微服务治理与容器化部署
【10月更文挑战第19天】在当今的软件开发领域,微服务架构因其高可伸缩性、易于维护和快速迭代的特点而备受青睐。然而,随着微服务数量的增加,管理和服务治理变得越来越复杂。作为阿里巴巴云推出的一款免费且开源的开发者工具,Cloud Toolkit 提供了一系列实用的功能,帮助开发者在微服务治理和容器化部署方面更加高效。本文将从个人的角度出发,探讨如何利用 Cloud Toolkit 来应对这些挑战。
166 2
|
消息中间件 前端开发 JavaScript
Spring Boot+Netty+Websocket实现后台向前端推送信息
Spring Boot+Netty+Websocket实现后台向前端推送信息
|
前端开发 JavaScript Java
Springboot+Netty+WebSocket搭建简单的消息通知
这样,你就建立了一个简单的消息通知系统,使用Spring Boot、Netty和WebSocket实现实时消息传递。你可以根据具体需求扩展和改进该系统。
333 1
|
存储 数据安全/隐私保护
Netty实战(十三)WebSocket协议(一)
WebSocket 协议是完全重新设计的协议,旨在为 Web 上的双向数据传输问题提供一个切实可行的解决方案,使得客户端和服务器之间可以在任意时刻传输消息,因此,这也就要求它们异步地处理消息回执。
722 0
|
前端开发 Java 应用服务中间件
SpringBoot整合Netty搭建高性能Websocket服务器(实现聊天功能)
之前使用Springboot整合了websocket,实现了一个后端向前端推送信息的基本小案例,这篇文章主要是增加了一个新的框架就是Netty,实现一个高性能的websocket服务器,并结合前端代码,实现一个基本的聊天功能。你可以根据自己的业务需求进行更改。 这里假设你已经了解了Netty和websocket的相关知识,仅仅是想通过Springboot来整合他们。根据之前大家的需求,代码已经上传到了github上。在文末给出。 废话不多说,直接看步骤代码。
2139 0
SpringBoot整合Netty搭建高性能Websocket服务器(实现聊天功能)
|
JSON 安全 Dubbo
实践指南:WebSocket 鉴权的最佳实践
WebSocket 作为实时通信的利器,越来越受到开发者的青睐。然而,为了确保通信的安全性和合法性,鉴权成为不可或缺的一环。本文将深入探讨 WebSocket 的鉴权机制,为你呈现一揽子的解决方案,确保你的 WebSocket 通信得心应手。
实践指南:WebSocket 鉴权的最佳实践
|
Go
Go命令行解析神器入门 - 10分钟上手flag包
Go命令行解析神器入门 - 10分钟上手flag包
417 0
|
自然语言处理 API 索引
LLM-Client一个轻量级的LLM集成工具
大型语言模型(llm)已经彻底改变了我们与文本交互的方式,OpenAI、Google、AI21、HuggingfaceHub、Anthropic和众多开源模型提供了不同的功能和优势。但是每个模型都有其独特的体系结构、api和兼容性需求,集成这些模型是一项耗时且具有挑战性的任务。
525 2