Netty异步NIO框架(二)websocket 前端后端聊天 私聊及群聊

简介: Netty异步NIO框架(二)websocket 前端后端聊天 私聊及群聊

1. 引入Netty依赖

<!--后端采用springboot项目,netty只需引入这一个依赖 -->
<!--netty依赖 -->
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
</dependency>

2. 创建netty服务器

package com.cnpc.modules.netty;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
/**
 * @author wuzhenyong
 * ClassName:NettyWebSocketServer.java
 * date:2022-05-05 8:48
 * Description: Netty服务器
 */
@Component
public class NettyWebSocketServer implements ApplicationListener<ContextRefreshedEvent> {
    @Autowired
    private WebSocketChannelInit webSocketChannelInit;
    /**
     * 容器初始化完成后调用
     *
     * @param contextRefreshedEvent
     */
    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        // 启动netty服务器
        this.init();
    }
    private EventLoopGroup bossGroup = new NioEventLoopGroup(1);
    private EventLoopGroup workerGroup = new NioEventLoopGroup();
    public void init() {
        try {
            //1.创建服务端启动助手
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            //2.设置线程组
            serverBootstrap.group(bossGroup, workerGroup);
            //3.设置参数
            serverBootstrap.channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler(LogLevel.DEBUG))
                    .childHandler(webSocketChannelInit);
            //4.启动  绑定端口不能和服务端口一致
            ChannelFuture channelFuture = serverBootstrap.bind(9090).sync();
            System.out.println("--Netty服务端启动成功---");
            channelFuture.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

3. 创建通道初始化对象

package com.cnpc.modules.netty;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
 * @author wuzhenyong
 * ClassName:WebSocketChannelInit.java
 * date:2022-05-05 8:53
 * Description: 通道初始化对象
 */
@Component
public class WebSocketChannelInit extends ChannelInitializer {
    @Autowired
    private WebSocketHandler webSocketHandler;
    @Override
    protected void initChannel(Channel channel) throws Exception {
        ChannelPipeline pipeline = channel.pipeline();
        //对http协议的支持.
        pipeline.addLast(new HttpServerCodec());
        // 对大数据流的支持
        pipeline.addLast(new ChunkedWriteHandler());
        //post请求分三部分. request line / request header / message body
        // HttpObjectAggregator将多个信息转化成单一的request或者response对象
        pipeline.addLast(new HttpObjectAggregator(8000));
        // 将http协议升级为ws协议. websocket的支持
        pipeline.addLast(new WebSocketServerProtocolHandler("/ws", null, true, 65536, false, true));
        // 自定义处理handler
        pipeline.addLast(webSocketHandler);
    }
}

4. 创建自定义处理类

package com.cnpc.modules.netty;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.util.concurrent.GlobalEventExecutor;
import org.springframework.stereotype.Component;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
 * @author wuzhenyong
 * ClassName:WebSocketHandler.java
 * date:2022-05-05 8:54
 * Description: 自定义处理类
 */
@Component
@ChannelHandler.Sharable
public class WebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
  /**
     * 管理channel的组,可以理解为channel的池 —— 客服使用
     */
    public static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    /**
     * 用户通道管理
     */
    public static ConcurrentHashMap<String, Channel> channelMap = new ConcurrentHashMap<>(16);
    /**
     * 通道绑定管理
     */
    public static ConcurrentHashMap<String, String> bindMap = new ConcurrentHashMap<>(16);
    public static List<Channel> channelList = new ArrayList<>();
    /**
     * 通道就绪事件
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        // channel.read();
        //当有新的客户端连接的时候, 将通道放入集合
        SocketAddress socketAddress = ctx.channel().remoteAddress();
        // 放入通道组
        channels.add(channel);
        System.out.println("有新的连接." + socketAddress);
    }
    /**
     * 用户事件触发 token校验
     *
     * @param ctx ctx
     * @param evt evt
     * @throws Exception 异常
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        System.out.println("触发事件");
        if (evt instanceof WebSocketServerProtocolHandler.HandshakeComplete) {
            WebSocketServerProtocolHandler.HandshakeComplete complete = (WebSocketServerProtocolHandler.HandshakeComplete) evt;
            HttpHeaders httpHeaders = complete.requestHeaders();
            // 自行处理鉴权问题
            System.out.println("uri: " + uri);
            System.out.println("握手成功");
            channelMap.put(paramValue, ctx.channel());
        }
        super.userEventTriggered(ctx, evt);
    }
    /**
     * 收到消息事件
     *
     * @param ctx 通道处理程序上下文
     * @param textWebSocketFrame    文本框架网络套接字
     * @throws Exception 异常
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame textWebSocketFrame) throws Exception {
        // 按照自己公司的逻辑进行处理消息
        System.out.println("通道: " + ctx.channel().remoteAddress() + "发送了消息");
        String msg = textWebSocketFrame.text();
        System.out.println("msg:" + msg);
        String[] params = msg.split(":");
        if (params[1].contains("yangmingquan")) {
            // 私发
            channelMap.get("yangmingquan").writeAndFlush(new TextWebSocketFrame(msg));
        }
        if (params[1].contains("wuzhenyong")) {
            // 私发
            channelMap.get("wuzhenyong").writeAndFlush(new TextWebSocketFrame(msg));
        }
        if (params[1].contains("all")) {
            // 群发
            channels.writeAndFlush(new TextWebSocketFrame(msg));
        }
    }
    /**
     * 通道未就绪--channel下线
     *
     * @param ctx ctx
     * @throws Exception 异常
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        SocketAddress socketAddress = ctx.channel().remoteAddress();
        System.out.println("通道:" + socketAddress + "已下线");
        //当有客户端断开连接的时候,就移除对应的通道
        channelList.remove(channel);
        ctx.close();
    }
    /**
     * 异常处理事件
     *
     * @param ctx   ctx
     * @param cause 导致
     * @throws Exception 异常
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        Channel channel = ctx.channel();
        //移除集合
        channelList.remove(channel);
        ctx.close();
    }
}

5. 创建常量类

package com.cnpc.modules.netty;
/**
 * @author wuzhenyong
 * date:2022-05-05 8:35
 * Description:
 */
public final class WebSocketConstant {
    /**
     * websocket head参数key
     */
    public static final String SEC_WEBSOCKET_PROTOCOL = "Sec-WebSocket-Protocol";
}

6. 前端js

//1.创建websocket客户端
    var wsServer = 'ws://ip/';
    var limitConnect = 3;  // 断线重连次数
    var timeConnect = 0;
    function websocket() {
        //这里需要注意的是,prompt有两个参数,前面是提示的话,后面是当对话框出来后,在对话框里的默认值
        var username = prompt("请输入您的名字", ""); //将输入的内容赋给变量 name ,
        //建立WebSocket通讯
        //注意:如果你要兼容ie8+,建议你采用 socket.io 的版本。下面是以原生WS为例
        wsServer = 'ws://127.0.0.1:9090/ws?username=' + username;
        var socket = new WebSocket(wsServer);
        //连接成功时触发
        socket.onopen = function() {
        };
        //收到的消息事件 按自己需求处理
        socket.onmessage = function(res) {
            //res为接受到的值,如 {"emit": "messageName", "data": {}}
            //emit即为发出的事件名,用于区分不同的消息
            console.log('接收到消息:', res)
        } ;
        socket.onclose = function() {
            reconnect();
        };
        // 另外还有onclose、onerror,分别是在链接关闭和出错时触发
    }
    // 重连
    function reconnect() {
        // lockReconnect加锁,防止onclose、onerror两次重连
        if (limitConnect > 0) {
            limitConnect--;
            timeConnect++;
            console.log("第" + timeConnect + "次重连");
            // 进行重连
            setTimeout(function() {
                websocket();
            },2000);
        } else {
            console.log("TCP连接已超时");
        }
    }

7. 以上就可以使用websocket的方式进行聊天了

8. 遇到的问题

  • websocket路径传参问题 通道初始化对象 WebSocketChannelInit类
// 将http协议升级为ws协议. websocket的支持
pipeline.addLast(new WebSocketServerProtocolHandler("/ws", null, true, 65536, false, true));
// 最后一个参数设置为true则连接websocket可以进行路径传参,否则传参连接不成功

websocket路径参数获取问题 自定义处理器 WebSocketHandler类

// 重写userEventTriggered方法  用户连接触发事件
/**
 * 用户事件触发 token校验
 *
 * @param ctx ctx
 * @param evt evt
 * @throws Exception 异常
 */
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
    System.out.println("触发事件");
    if (evt instanceof WebSocketServerProtocolHandler.HandshakeComplete) {
        WebSocketServerProtocolHandler.HandshakeComplete complete = (WebSocketServerProtocolHandler.HandshakeComplete) evt;
        // 获取header信息
        HttpHeaders httpHeaders = complete.requestHeaders();
        // 自行处理鉴权问题
        System.out.println("uri: " + uri);
        System.out.println("握手成功");
        channelMap.put(paramValue, ctx.channel());
    }
    super.userEventTriggered(ctx, evt);
}

启动失败问题 Netty服务器 NettyWebSocketServer类

//init方法更换为一下代码,删除异常捕获关闭事件
public void init() throws InterruptedException {
    EventLoopGroup bossGroup = new NioEventLoopGroup(1);
    EventLoopGroup workerGroup = new NioEventLoopGroup();
    //1.创建服务端启动助手
    ServerBootstrap serverBootstrap = new ServerBootstrap();
    //2.设置线程组
    serverBootstrap.group(bossGroup, workerGroup);
    //3.设置参数
    serverBootstrap.channel(NioServerSocketChannel.class)
        .handler(new LoggingHandler(LogLevel.DEBUG))
        .childHandler(webSocketChannelInit);
    //4.启动  绑定端口不能和服务端口一致
    ChannelFuture channelFuture = serverBootstrap.bind(9090).sync();
    System.out.println("--Netty服务端启动成功---");
}

js websocket传入header参数

var ws = new WebSocket("地址", ['header参数信息']);
相关文章
|
11月前
|
安全 Java Ruby
我尝试了所有后端框架 — — 这就是为什么只有 Spring Boot 幸存下来
作者回顾后端开发历程,指出多数框架在生产环境中难堪重负。相比之下,Spring Boot凭借内置安全、稳定扩展、完善生态和企业级支持,成为构建高可用系统的首选,真正经受住了时间与规模的考验。
740 2
|
JavaScript 前端开发 Java
制造业ERP源码,工厂ERP管理系统,前端框架:Vue,后端框架:SpringBoot
这是一套基于SpringBoot+Vue技术栈开发的ERP企业管理系统,采用Java语言与vscode工具。系统涵盖采购/销售、出入库、生产、品质管理等功能,整合客户与供应商数据,支持在线协同和业务全流程管控。同时提供主数据管理、权限控制、工作流审批、报表自定义及打印、在线报表开发和自定义表单功能,助力企业实现高效自动化管理,并通过UniAPP实现移动端支持,满足多场景应用需求。
1313 1
|
Web App开发 JavaScript 前端开发
深入浅出Node.js后端框架
【10月更文挑战第34天】在数字化时代,后端开发如同一座桥梁,连接着用户界面与数据处理的两端。本文将通过Node.js这一轻量级、高效的平台,带领读者领略后端框架的魅力。我们将从基础概念出发,逐步深入到实战应用,最后探讨如何通过代码示例来巩固学习成果,使读者能够在理论与实践之间架起自己的桥梁。
|
前端开发 Java 物联网
智慧班牌源码,采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署
智慧班牌系统是一款基于信息化与物联网技术的校园管理工具,集成电子屏显示、人脸识别及数据交互功能,实现班级信息展示、智能考勤与家校互通。系统采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署与私有化定制。核心功能涵盖信息发布、考勤管理、教务处理及数据分析,助力校园文化建设与教学优化。其综合性和可扩展性有效打破数据孤岛,提升交互体验并降低管理成本,适用于日常教学、考试管理和应急场景,为智慧校园建设提供全面解决方案。
787 70
|
消息中间件 缓存 网络协议
Netty基础—4.NIO的使用简介
本文详细介绍了Java NIO(New Input/Output)的核心概念与编程模型。首先,讲解了Buffer缓冲区的作用及4个核心概念:capacity、limit、position、mark,并通过Direct模式创建的Buffer示例展示了其高性能特点。接着,分析了Channel通道的概念,说明其与Buffer的关系以及FileChannel在文件读写中的应用,包括顺序写、随机写和多线程安全特性。 随后,对比了BIO(Blocking IO)编程模型的局限性,如线程资源耗尽问题,引出伪异步IO编程的改进方案,但指出其仍存在级联故障风险。进一步探讨了长连接与短连接的区别及其实现代码。
|
缓存 负载均衡 JavaScript
构建高效后端服务:Node.js与Express框架实践
在数字化时代的浪潮中,后端服务的重要性不言而喻。本文将通过深入浅出的方式介绍如何利用Node.js及其强大的Express框架来搭建一个高效的后端服务。我们将从零开始,逐步深入,不仅涉及基础的代码编写,更会探讨如何优化性能和处理高并发场景。无论你是后端新手还是希望提高现有技能的开发者,这篇文章都将为你提供宝贵的知识和启示。
|
JavaScript 前端开发 测试技术
前端全栈之路Deno篇(五):如何快速创建 WebSocket 服务端应用 + 客户端应用 - 可能是2025最佳的Websocket全栈实时应用框架
本文介绍了如何使用Deno 2.0快速构建WebSocket全栈应用,包括服务端和客户端的创建。通过一个简单的代码示例,展示了Deno在WebSocket实现中的便捷与强大,无需额外依赖,即可轻松搭建具备基本功能的WebSocket应用。Deno 2.0被认为是最佳的WebSocket全栈应用JS运行时,适合全栈开发者学习和使用。
1125 7
|
Web App开发 JavaScript 中间件
构建高效后端服务:Node.js与Express框架的完美结合
【10月更文挑战第21天】本文将引导你走进Node.js和Express框架的世界,探索它们如何共同打造一个高效、可扩展的后端服务。通过深入浅出的解释和实际代码示例,我们将一起理解这一组合的魅力所在,并学习如何利用它们来构建现代Web应用。
557 1
|
Web App开发 JavaScript 前端开发
构建高效后端服务:Node.js与Express框架的实践
【10月更文挑战第33天】在数字化时代的浪潮中,后端服务的效率和可靠性成为企业竞争的关键。本文将深入探讨如何利用Node.js和Express框架构建高效且易于维护的后端服务。通过实践案例和代码示例,我们将揭示这一组合如何简化开发流程、优化性能,并提升用户体验。无论你是初学者还是有经验的开发者,这篇文章都将为你提供宝贵的见解和实用技巧。
|
Web App开发 JavaScript 中间件
构建高效后端服务:Node.js与Express框架的融合之道
【10月更文挑战第31天】在追求快速、灵活和高效的后端开发领域,Node.js与Express框架的结合如同咖啡遇见了奶油——完美融合。本文将带你探索这一组合如何让后端服务搭建变得既轻松又充满乐趣,同时确保你的应用能够以光速运行。
484 0

热门文章

最新文章