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参数信息']);
相关文章
|
30天前
|
前端开发 NoSQL Java
【Java若依框架】RuoYi-Vue的前端和后端配置步骤和启动步骤
本文介绍了如何配置和启动基于Java的若依(RuoYi)项目,涵盖后端和前端的详细步骤。首先,准备Redis、MySQL以及IDE(如Idea和VS)。接着,通过GitHub获取代码并导入到IDE中,执行必要的SQL文件和配置数据库密码。然后,启动Redis并进行相关配置。最后,按照前端配置步骤克隆前端代码库,打开终端执行命令完成前端配置。整个过程详细记录了每一步的操作,帮助开发者顺利部署若依项目。 如果你觉得有帮助,请点赞、关注和收藏,这将是我持续分享的动力!
320 1
|
2月前
|
开发框架 小程序 前端开发
圈子社交app前端+后端源码,uniapp社交兴趣圈子开发,框架php圈子小程序安装搭建
本文介绍了圈子社交APP的源码获取、分析与定制,PHP实现的圈子框架设计及代码编写,以及圈子小程序的安装搭建。涵盖环境配置、数据库设计、前后端开发与接口对接等内容,确保平台的安全性、性能和功能完整性。通过详细指导,帮助开发者快速搭建稳定可靠的圈子社交平台。
|
2月前
|
机器学习/深度学习 前端开发 算法
婚恋交友系统平台 相亲交友平台系统 婚恋交友系统APP 婚恋系统源码 婚恋交友平台开发流程 婚恋交友系统架构设计 婚恋交友系统前端/后端开发 婚恋交友系统匹配推荐算法优化
婚恋交友系统平台通过线上互动帮助单身男女找到合适伴侣,提供用户注册、个人资料填写、匹配推荐、实时聊天、社区互动等功能。开发流程包括需求分析、技术选型、系统架构设计、功能实现、测试优化和上线运维。匹配推荐算法优化是核心,通过用户行为数据分析和机器学习提高匹配准确性。
179 3
|
4月前
|
JavaScript 前端开发 测试技术
前端全栈之路Deno篇(五):如何快速创建 WebSocket 服务端应用 + 客户端应用 - 可能是2025最佳的Websocket全栈实时应用框架
本文介绍了如何使用Deno 2.0快速构建WebSocket全栈应用,包括服务端和客户端的创建。通过一个简单的代码示例,展示了Deno在WebSocket实现中的便捷与强大,无需额外依赖,即可轻松搭建具备基本功能的WebSocket应用。Deno 2.0被认为是最佳的WebSocket全栈应用JS运行时,适合全栈开发者学习和使用。
213 7
|
4月前
|
前端开发 小程序 Java
java基础:map遍历使用;java使用 Patten 和Matches 进行正则匹配;后端传到前端展示图片三种情况,并保存到手机
这篇文章介绍了Java中Map的遍历方法、使用Pattern和matches进行正则表达式匹配,以及后端向前端传输图片并保存到手机的三种情况。
50 1
|
4月前
|
前端开发 JavaScript Java
导出excel的两个方式:前端vue+XLSX 导出excel,vue+后端POI 导出excel,并进行分析、比较
这篇文章介绍了使用前端Vue框架结合XLSX库和后端结合Apache POI库导出Excel文件的两种方法,并对比分析了它们的优缺点。
1558 0
|
4月前
|
前端开发 JavaScript 小程序
前端uni开发后端用PHP的圈子系统该 如何做源码?
圈子系统系统基于TP6+Uni-app框架开发;客户移动端采用uni-app开发,管理后台TH6开发。系统支持微信公众号端、微信小程序端、H5端、PC端多端账号同步,可快速打包生成APP
|
4月前
|
开发框架 前端开发 网络协议
Spring Boot结合Netty和WebSocket,实现后台向前端实时推送信息
【10月更文挑战第18天】 在现代互联网应用中,实时通信变得越来越重要。WebSocket作为一种在单个TCP连接上进行全双工通信的协议,为客户端和服务器之间的实时数据传输提供了一种高效的解决方案。Netty作为一个高性能、事件驱动的NIO框架,它基于Java NIO实现了异步和事件驱动的网络应用程序。Spring Boot是一个基于Spring框架的微服务开发框架,它提供了许多开箱即用的功能和简化配置的机制。本文将详细介绍如何使用Spring Boot集成Netty和WebSocket,实现后台向前端推送信息的功能。
1055 1
|
4月前
|
前端开发 Java C++
RSocket vs WebSocket:Spring Boot 3.3 中的两大实时通信利器
本文介绍了在 Spring Boot 3.3 中使用 RSocket 和 WebSocket 实现实时通信的方法。RSocket 是一种高效的网络通信协议,支持多种通信模式,适用于微服务和流式数据传输。WebSocket 则是一种标准协议,支持全双工通信,适合实时数据更新场景。文章通过一个完整的示例,展示了如何配置项目、实现前后端交互和消息传递,并提供了详细的代码示例。通过这些技术,可以大幅提升系统的响应速度和处理效率。
|
6月前
|
开发框架 网络协议 Java
SpringBoot WebSocket大揭秘:实时通信、高效协作,一文让你彻底解锁!
【8月更文挑战第25天】本文介绍如何在SpringBoot项目中集成WebSocket以实现客户端与服务端的实时通信。首先概述了WebSocket的基本原理及其优势,接着详细阐述了集成步骤:添加依赖、配置WebSocket、定义WebSocket接口及进行测试。通过示例代码展示了整个过程,旨在帮助开发者更好地理解和应用这一技术。
511 1

热门文章

最新文章

  • 1
    【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
  • 2
    【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
  • 3
    【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
  • 4
    详解智能编码在前端研发的创新应用
  • 5
    巧用通义灵码,提升前端研发效率
  • 6
    智能编码在前端研发的创新应用
  • 7
    【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
  • 8
    【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
  • 9
    抛弃node和vscode,如何用记事本开发出一个完整的vue前端项目
  • 10
    大前端之前端开发接口测试工具postman的使用方法-简单get接口请求测试的使用方法-简单教学一看就会-以实际例子来说明-优雅草卓伊凡