netty之基于Netty搭建WebSocket,模仿微信聊天页面

news2024/12/26 14:08:19

前言
模仿微信聊天页面,开发一个基于Netty搭建WebSocket通信案例。Netty的应用方面非常广;聊天、MQ、RPC、数据等等,在5G到来的时候更加需要大量数据传输,Netty的应用也会更加广阔。
1:案例使用SpringBoot+Netty+WebSocket搭建功能。
2:使用Netty提供的HttpServerCodec、HttpObjectAggregator、3:ChunkedWriteHandler进行编码解码处理。
通信逻辑尽可能简化到只了解根本,便于后续个人应用及开发的拓展。



public class ClientMsgProtocol {

    private int type;       //1请求个人信息,2发送聊天信息
    private String msgInfo; //消息

    public int getType() {
        return type;
    }

    public void setType(int type) {
        this.type = type;
    }

    public String getMsgInfo() {
        return msgInfo;
    }

    public void setMsgInfo(String msgInfo) {
        this.msgInfo = msgInfo;
    }
}

public class ServerMsgProtocol {

    private int type;             //链接信息;1自发信息、2群发消息
    private String channelId;     //通信管道ID,实际使用中会映射成用户名
    private String userHeadImg;   //用户头像[模拟分配]
    private String msgInfo;       //通信消息

    public int getType() {
        return type;
    }

    public void setType(int type) {
        this.type = type;
    }

    public String getChannelId() {
        return channelId;
    }

    public void setChannelId(String channelId) {
        this.channelId = channelId;
    }

    public String getUserHeadImg() {
        return userHeadImg;
    }

    public void setUserHeadImg(String userHeadImg) {
        this.userHeadImg = userHeadImg;
    }

    public String getMsgInfo() {
        return msgInfo;
    }

    public void setMsgInfo(String msgInfo) {
        this.msgInfo = msgInfo;
    }
}

public class MyChannelInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel channel) {
        channel.pipeline().addLast("http-codec", new HttpServerCodec());
        channel.pipeline().addLast("aggregator", new HttpObjectAggregator(65536));
        channel.pipeline().addLast("http-chunked", new ChunkedWriteHandler());
        // 在管道中添加我们自己的接收数据实现方法
        channel.pipeline().addLast(new MyServerHandler());
    }

}

public class MyServerHandler extends ChannelInboundHandlerAdapter {

    private Logger logger = LoggerFactory.getLogger(MyServerHandler.class);

    private WebSocketServerHandshaker handshaker;

    /**
     * 当客户端主动链接服务端的链接后,这个通道就是活跃的了。也就是客户端与服务端建立了通信通道并且可以传输数据
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        SocketChannel channel = (SocketChannel) ctx.channel();
        logger.info("链接报告开始");
        logger.info("链接报告信息:有一客户端链接到本服务端");
        logger.info("链接报告IP:{}", channel.localAddress().getHostString());
        logger.info("链接报告Port:{}", channel.localAddress().getPort());
        logger.info("链接报告完毕");
        ChannelHandler.channelGroup.add(ctx.channel());
    }

    /**
     * 当客户端主动断开服务端的链接后,这个通道就是不活跃的。也就是说客户端与服务端的关闭了通信通道并且不可以传输数据
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        logger.info("客户端断开链接{}", ctx.channel().localAddress().toString());
        ChannelHandler.channelGroup.remove(ctx.channel());
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

        //http
        if (msg instanceof FullHttpRequest) {

            FullHttpRequest httpRequest = (FullHttpRequest) msg;

            if (!httpRequest.decoderResult().isSuccess()) {

                DefaultFullHttpResponse httpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST);

                // 返回应答给客户端
                if (httpResponse.status().code() != 200) {
                    ByteBuf buf = Unpooled.copiedBuffer(httpResponse.status().toString(), CharsetUtil.UTF_8);
                    httpResponse.content().writeBytes(buf);
                    buf.release();
                }

                // 如果是非Keep-Alive,关闭连接
                ChannelFuture f = ctx.channel().writeAndFlush(httpResponse);
                if (httpResponse.status().code() != 200) {
                    f.addListener(ChannelFutureListener.CLOSE);
                }

                return;
            }

            WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory("ws:/" + ctx.channel() + "/websocket", null, false);
            handshaker = wsFactory.newHandshaker(httpRequest);

            if (null == handshaker) {
                WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
            } else {
                handshaker.handshake(ctx.channel(), httpRequest);
            }

            return;
        }

        //ws
        if (msg instanceof WebSocketFrame) {

            WebSocketFrame webSocketFrame = (WebSocketFrame) msg;

            //关闭请求
            if (webSocketFrame instanceof CloseWebSocketFrame) {
                handshaker.close(ctx.channel(), (CloseWebSocketFrame) webSocketFrame.retain());
                return;
            }

            //ping请求
            if (webSocketFrame instanceof PingWebSocketFrame) {
                ctx.channel().write(new PongWebSocketFrame(webSocketFrame.content().retain()));
                return;
            }

            //只支持文本格式,不支持二进制消息
            if (!(webSocketFrame instanceof TextWebSocketFrame)) {
                throw new Exception("仅支持文本格式");
            }

            String request = ((TextWebSocketFrame) webSocketFrame).text();
            System.out.println("服务端收到:" + request);

            ClientMsgProtocol clientMsgProtocol = JSON.parseObject(request, ClientMsgProtocol.class);
            //1请求个人信息
            if (1 == clientMsgProtocol.getType()) {
                ctx.channel().writeAndFlush(MsgUtil.buildMsgOwner(ctx.channel().id().toString()));
                return;
            }
            //群发消息
            if (2 == clientMsgProtocol.getType()) {
                TextWebSocketFrame textWebSocketFrame = MsgUtil.buildMsgAll(ctx.channel().id().toString(), clientMsgProtocol.getMsgInfo());
                ChannelHandler.channelGroup.writeAndFlush(textWebSocketFrame);
            }

        }

    }

    /**
     * 抓住异常,当发生异常的时候,可以做一些相应的处理,比如打印日志、关闭链接
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
        logger.info("异常信息:\r\n" + cause.getMessage());
    }

}

@Component("nettyServer")
public class NettyServer {

    private Logger logger = LoggerFactory.getLogger(NettyServer.class);

    //配置服务端NIO线程组
    private final EventLoopGroup parentGroup = new NioEventLoopGroup(); //NioEventLoopGroup extends MultithreadEventLoopGroup Math.max(1, SystemPropertyUtil.getInt("io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
    private final EventLoopGroup childGroup = new NioEventLoopGroup();
    private Channel channel;

    public ChannelFuture bing(InetSocketAddress address) {
        ChannelFuture channelFuture = null;
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(parentGroup, childGroup)
                    .channel(NioServerSocketChannel.class)    //非阻塞模式
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childHandler(new MyChannelInitializer());

            channelFuture = b.bind(address).syncUninterruptibly();
            channel = channelFuture.channel();
        } catch (Exception e) {
            logger.error(e.getMessage());
        } finally {
            if (null != channelFuture && channelFuture.isSuccess()) {
                logger.info("itstack-demo-netty server start done. {关注明哥,获取源码}");
            } else {
                logger.error("itstack-demo-netty server start error. {关注明哥,获取源码}");
            }
        }
        return channelFuture;
    }

    public void destroy() {
        if (null == channel) return;
        channel.close();
        parentGroup.shutdownGracefully();
        childGroup.shutdownGracefully();
    }

    public Channel getChannel() {
        return channel;
    }

}

@SpringBootApplication
@ComponentScan("com.lm.demo.netty")
public class NettyApplication implements CommandLineRunner {

    @Value("${netty.host}")
    private String host;
    @Value("${netty.port}")
    private int port;
    @Autowired
    private NettyServer nettyServer;

    public static void main(String[] args) {
        SpringApplication.run(NettyApplication.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        InetSocketAddress address = new InetSocketAddress(host, port);
        ChannelFuture channelFuture = nettyServer.bing(address);
        Runtime.getRuntime().addShutdownHook(new Thread(() -> nettyServer.destroy()));
        channelFuture.channel().closeFuture().syncUninterruptibly();
    }

}

@Controller
public class NettyController {

    @Resource
    private NettyServer nettyServer;

    @RequestMapping("/index")
    public String index(Model model){
        model.addAttribute("name", "xiaohao");
        return "index";
    }

}

在这里插入图片描述
打包运行 如下
在这里插入图片描述
浏览器访问
http://localhost:8081/index
在这里插入图片描述
好了到这里就结束了netty之基于Netty搭建WebSocket,模仿微信聊天页面的学习,大家一定要跟着动手操作起来。需要的源码的 可si我获取;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2186478.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

c++MFC项目结构中有:外部依赖项、头文件,他们两个有啥区别,好像都是.h文件吧?

目录 头文件&#xff08;Header Files&#xff09; 特点 示例 外部依赖项&#xff08;External Dependencies&#xff09; 特点 示例 区别 作用和来源不同 内容不同 使用方式不同 结合 示例 总结 在C项目结构中&#xff0c;外部依赖项和头文件确实可能都包含.h文件…

力扣(leetcode)每日一题 983 最低票价 |动态规划

983. 最低票价 题干 在一个火车旅行很受欢迎的国度&#xff0c;你提前一年计划了一些火车旅行。在接下来的一年里&#xff0c;你要旅行的日子将以一个名为 days 的数组给出。每一项是一个从 1 到 365 的整数。 火车票有 三种不同的销售方式 &#xff1a; 一张 为期一天 的通…

Linux复习--Linux服务管理类(SSH服务、DHCP+FTP、DNS服务、Apache服务、Nginx服务、HTTP状态码)

前言&#xff1a;本博客仅作记录学习使用&#xff0c;部分图片出自网络&#xff0c;如有侵犯您的权益&#xff0c;请联系删除 一、SSH服务 1、问题引出 哪些设置能够提升SSH远程管理的安全等级&#xff1f; 2、SSH的登录验证方式-口令登录 3、SSH的登录验证方式-密钥登录 4、…

高质量的翻译如何提高界面可用性

翻译质量在提高界面可用性方面起着重要作用&#xff0c;特别是在全球受众使用的应用程序、网站和软件中。有效地翻译界面可以确保清晰度、连贯性和易用性&#xff0c;从而改善不同地区和语言的用户体验。以下是高质量翻译提高界面可用性的关键方法&#xff1a; 清晰直观的导航…

SAP HCM PE51工资单教程

作者&#xff1a;vivi&#xff0c;来源&#xff1a;osinnovation 一&#xff1a;故事背景 最近在做一个越南的ecp集成事情&#xff0c;因为客户是越南&#xff0c;所以对工资单没有国内一样&#xff0c;用微信小程序一套单独开发&#xff0c;而是使用标准的paylisp&#xff0…

【路径规划】路径平滑算法,A星算法拐点的圆弧化处理

摘要 A算法广泛应用于路径规划中&#xff0c;但其生成的路径通常在拐点处呈现不平滑的折线。为了提升路径的平滑性&#xff0c;本文提出了一种基于圆弧的平滑处理方法&#xff0c;用于对A算法产生的路径拐点进行优化。通过在MATLAB中进行仿真验证&#xff0c;该方法能够有效减…

9. 正则表达式

编程工具和技术是以一种混乱、进化的方式生存和传播的。获胜的并不总是最好或最杰出的工具&#xff0c;而是那些在合适的利基市场中发挥足够好的功能&#xff0c;或者恰好与另一项成功的技术相结合的工具。 在本章中&#xff0c;我将讨论这样一种工具--正则表达式。正则表达式是…

YOLOv11改进 | 上采样篇 | YOLOv11引入DySample轻量级动态上采样器

1. DySample介绍 1.1 摘要&#xff1a;我们提出了DySample&#xff0c;一个超轻量和有效的动态上采样器。虽然最近的基于内核的动态上采样器&#xff08;如CARAFE、FADE和SAPA&#xff09;的性能提升令人印象深刻&#xff0c;但它们引入了大量工作负载&#xff0c;主要是由于耗…

Spring Boot 中的拦截器 Interceptors

​ 博客主页: 南来_北往 系列专栏&#xff1a;Spring Boot实战 前言 Spring Boot中的拦截器&#xff08;Interceptor&#xff09;是一种用于拦截和处理HTTP请求的机制&#xff0c;它基于Spring MVC框架中的HandlerInterceptor接口实现。拦截器允许在请求到达控制器&#…

C++函数模板、选择排序实现(从大到小)

template <class T> void mysw (T &a , T &b) {T temp b;b a;a temp; }template <class T> void muSort( T &arr ,int len) {//该实现为选择排序(高到低)for (int i 0; i < len; i) {int max i ; //首先默认本次循环首位元素为最大for (int j …

scrapy爬取汽车、车评数据【中】

这个爬虫我想分三期来写&#xff1a; ✅ 第一期写如何爬取汽车的车型信息&#xff1b; ✅ 第二期写如何爬取汽车的车评&#xff1b; ✅ 第三期写如何对车评嵌入情感分析结果&#xff0c;以及用简单的方法把数据插入mysql中&#xff1b; 技术基于scrapy框架、BERT语言模型、mysq…

SQL Server中关于个性化需求批量删除表的做法

在实际开发中&#xff0c;我们常常会遇到需要批量删除表&#xff0c;且具有共同特征的情况&#xff0c;例如&#xff1a;找出表名中数字结尾的表之类的&#xff0c;本文我将以3中类似情况为例&#xff0c;来示范并解说此类需求如何完成&#xff1a; 第一种&#xff0c;批量删除…

【Godot4.3】图形碰撞相关函数库ShapeTests

概述 最近积累了一些图形重叠检测&#xff0c;以及求图形的轴对齐包围盒Rect2&#xff0c;还有求Rect2的外接圆等函数。感觉可以作为一个单独的函数库&#xff0c;提供日常的使用&#xff0c;所以汇总成了ShapeTests。 注意&#xff1a;函数名和写法可能会不断改进。 代码 …

基于SSM的北京冬奥会志愿者服务系统

文未可获取一份本项目的java源码和数据库参考。 本课题国内外研究现状 当前&#xff0c;国外志愿者服务活动开展的十分活跃。志愿服务正以其突出的社会效益受到越来越多国家政府的重视。许多国家的志愿服务活动起步早、规模大&#xff0c;社会效益好。他们在国内有广泛的群众…

第四届生物医学与智能系统国际学术会议(IC-BIS 2025)

在线投稿&#xff1a;学术会议-学术交流征稿-学术会议在线-艾思科蓝 2025年第四届生物医学与智能系统国际学术会议&#xff08;IC-BIS 2025&#xff09; 将于2025年4月11-13日在意大利隆重举行。 该会议旨在汇集全球学术界和工业界的研究人员、专家和从业人员&#xff0c;共…

C(十)for循环 --- 黑神话情景

前言&#xff1a; "踏过三界宝刹&#xff0c;阅过四洲繁华。笑过五蕴痴缠&#xff0c;舍过六根牵挂。怕什么欲念不休&#xff0c;怕什么浪迹天涯。步履不停&#xff0c;便是得救之法。" 国际惯例&#xff0c;开篇先喝碗鸡汤。 今天&#xff0c;杰哥写的 for 循环相…

android Activity生命周期

android 中一个 activity 在其生命周期中会经历多种状态。 您可以使用一系列回调来处理状态之间的转换。下面我们来介绍这些回调。 onCreate&#xff08;创建阶段&#xff09; 初始化组件&#xff1a;在这个阶段&#xff0c;Activity的主要工作是进行初始化操作。这包括为Ac…

【Bug】STM32F1的PB3和PB4无法正常输出

Bug 使用标准库配置STM32F103C8T6的PB3和PB4引脚输出控制LED灯时&#xff0c;发现引脚电平没有变化无法正常输出高低电平&#xff0c;配置代码如下&#xff1a; GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE ); GPIO_InitStruc…

Maven项目管理入门:POM文件详解与依赖管理

目录 1、pom配置详解 2、依赖管理 2.1 Maven坐标 2.2 依赖导入 1、使用IDEA工具导入&#xff1a; 2、从远程仓库中获取坐标 3、maven插件 maven是一个项目管理工具&#xff0c;其中依靠于一个很重要的xml文件&#xff1a;pom.xml。我们接下来学习下pom.xml文件的配置。 …

论文阅读:PET/CT Cross-modal medical image fusion of lung tumors based on DCIF-GAN

摘要 背景&#xff1a; 基于GAN的融合方法存在训练不稳定&#xff0c;提取图像的局部和全局上下文语义信息能力不足&#xff0c;交互融合程度不够等问题 贡献&#xff1a; 提出双耦合交互式融合GAN&#xff08;Dual-Coupled Interactive Fusion GAN&#xff0c;DCIF-GAN&…