netty-websocket

2022-08-17 21:34:45 浏览数 (1)

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

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

那是相当的香啊

代码语言:javascript复制
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

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

  • jdk版本为1.8或1.8

快速开始

  • 添加依赖:

代码语言:javascript复制
<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注解,样例如下:

代码语言:javascript复制
@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 配置 所有的配置项都在这个注解的属性中

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

  • 首先在@ServerEndpoint注解的属性中使用${...}占位符

代码语言:javascript复制
@ServerEndpoint(host = "${ws.host}",port = "${ws.port}")
public class MyWebSocket {
    ...
}

  • 接下来即可在application.properties中配置

代码语言:javascript复制
ws.host=0.0.0.0
ws.port=80

自定义Favicon

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

代码语言:javascript复制
src/
   - main/
     - java/
    |     <source code>
     - resources/
         - favicon.ico

自定义错误页面

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

代码语言:javascript复制
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

0 人点赞