构建异步高并发服务器:Netty与Spring Boot的完美结合

news2025/5/24 4:14:17

前言

在这里插入图片描述
「作者主页」:雪碧有白泡泡
「个人网站」:雪碧的个人网站
请添加图片描述

ChatGPT体验地址

请添加图片描述

文章目录

  • 前言
  • IO
  • Netty
      • 1. 引入依赖
      • 2. 服务端
      • 4. 客户端
      • 结果
  • 总结
      • 引导类-Bootstarp和ServerBootstrap
      • 连接-NioSocketChannel
      • 事件组-EventLoopGroup和NioEventLoopGroup
  • 送书活动

IO

在Java基础中,IO流是一个重要操作,先上八股

在这里插入图片描述

  1. BIO:传统的IO,同步阻塞,一个连接一个线程。一般不怎么使用
  2. AIO:JDK7引入的,异步非阻塞IO
  3. NIO:JDK1.4之后新的API,是多路复用,允许你一次性处理多个连接,而不需要等待每个连接的完成。(NIO 多路复用的核心概念是 Selector(选择器)和 Channel(通道)通过Channel、Buffer和Selector来进行数据传输和事件处理)

Netty

Netty是建立在NIO之上的一个框架,提供了更高级的抽象,如ChannelHandler和EventLoop,简化了事件处理和网络编程。
执行流程如下图
在这里插入图片描述

具有高性能,高可靠性,高可扩展性,还支持多种协议

我们以聊天流程为例

  1. 服务端启动
  2. 客户端启动
  3. 客户端启动连接上的时候,告知服务端
  4. 服务端读取到客户端的信息后立即发送信息给客户端
  5. 客户端收到信息后也发送给服务端

1. 引入依赖

  		<dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.76.Final</version>
        </dependency>

2. 服务端

@Slf4j
public class NettyServer {

    private final static int PORT = 9012;

    public static void main(String[] args) throws InterruptedException {
        /**
         * 包含childGroup,childHandler,config,继承的父类AbstractBootstrap包括了parentGroup
         * */
        ServerBootstrap bootstrap = new ServerBootstrap();
        /**
         * EventLoopGroup用于处理所有ServerChannel和Channel的所有事件和IO
         * */
        EventLoopGroup parentGroup = new NioEventLoopGroup();
        EventLoopGroup childGroup = new NioEventLoopGroup();
        try {
            /**
             * 绑定两个事件组
             * */
            bootstrap.group(parentGroup, childGroup)
                    /**
                     * 初始化socket,定义tcp连接的实例
                     * 内部调用ReflectiveChannelFactory实现对NioServerSocketChannel实例化
                     * channelFactory是在AbstractBootstrap,也就是bootstrap的父类
                     * */
                    .channel(NioServerSocketChannel.class)
                    /**
                     * 添加处理器
                     * ChannelInitializer包括了Set<ChannelHandlerContext> initMap
                     *
                     * 这里比较有趣的事情就是使用被注册的channel去初始化其他的channel,
                     * 等初始化结束后移除该channel
                     * 所以SocketChannel是一个工具,
                     *
                     * 在bind绑定端口的时候,进行初始化和注册initAndRegister,
                     * 通过channel = channelFactory.newChannel()得到初始化channel
                     * init(channel)真正开始初始化,
                     * p = channel.pipeline()得到ChannelPipeline,
                     * p.addLast开始添加
                     * ch.eventLoop().execute将childHandler赋值并开启一个任务setAutoRead
                     * 所以最后在监听读取的时候将会按照下面添加的channel进行读取
                     *
                     * ChannelInitializer继承了ChannelInboundHandlerAdapter
                     * 间接继承ChannelHandlerAdapter,ChannelInboundHandler,
                     * */
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            /**
                             * ByteBuf和String之间的转换
                             *
                             *  Decoders解密
                             *  pipeline.addLast("frameDecoder", new {@link LineBasedFrameDecoder}(80))
                             *  pipeline.addLast("stringDecoder", new {@link StringDecoder}(CharsetUtil.UTF_8))
                             *
                             *  Encoder加密
                             *  pipeline.addLast("stringEncoder", new {@link StringEncoder}(CharsetUtil.UTF_8))
                             *
                             *  使用上面的加密解密后就可以直接读取字符串
                             *   void channelRead({@link ChannelHandlerContext} ctx, String msg) {
                             *       ch.write("Did you say '" + msg + "'?\n")
                             *  }
                             *
                             * */
                            pipeline.addLast(new StringDecoder());
                            pipeline.addLast(new StringEncoder());
                            //自定义处理器
                            pipeline.addLast(new ServerHandler1());
                        }
                    });
            ChannelFuture future = bootstrap.bind(PORT).sync();
            log.info("服务器已启动");
            future.channel().closeFuture().sync();
        } finally {
            parentGroup.shutdownGracefully();
            childGroup.shutdownGracefully();
        }
    }
}

这段代码实现了一个使用Netty框架的服务器端,它监听指定的端口并处理客户端的连接请求。

  1. 创建一个ServerBootstrap实例,用于启动服务器。
  2. 创建两个EventLoopGroup实例,parentGroup用于处理服务器的连接请求,childGroup用于处理客户端的数据通信。
  3. 绑定事件组到ServerBootstrap实例。
  4. 指定使用的NioServerSocketChannel作为服务器套接字通道的实现类。
  5. 添加处理器到ChannelInitializer中,该处理器负责初始化和配置新连接的SocketChannel
  6. 在处理器中,通过ChannelPipeline添加了如下处理器:
    • StringDecoder:处理传入的字节数据,并将其解码为字符串。
    • StringEncoder:处理传出的字符串数据,并将其编码为字节。
    • ServerHandler1:自定义的处理器,用于处理客户端发送的消息。
  7. 绑定服务器的端口号,启动服务器。
  8. 等待服务器的关闭事件。
  1. 处理器
@Slf4j
public class ServerHandler1 extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        log.info("Client Address ====== {},读取的信息:{}", ctx.channel().remoteAddress(),msg);
        ctx.channel().writeAndFlush("服务端writeAndFlush:我是服务端");
        ctx.fireChannelActive();
        //睡眠
        TimeUnit.MILLISECONDS.sleep(500);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        //打印异常
        cause.printStackTrace();
        //关闭Channel连接,并通知ChannelFuture,通常是出现异常或者是完成了操作
        ctx.close();
    }
}

4. 客户端

@Slf4j
public class NettyClient {
    private final static int PORT = 9012;
    private final static String IP = "localhost";

    public static void main(String[] args) throws InterruptedException {
        /**
         * 服务端是ServerBootstrap,客户端是Bootstrap
         * Bootstrap引导channel连接,UDP连接用bind方法,TCP连接用connect方法
         * */
        Bootstrap bootstrap = new Bootstrap();
        /**
         * 服务端是EventLoopGroup,客户端是NioEventLoopGroup
         * 这里创建默认0个线程,一个线程工厂,一个选择器提供者
         * */
        NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup();
        try {
            bootstrap.group(eventLoopGroup)
                    /**
                     * 初始化socket,定义tcp连接的实例
                     * */
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            /**
                             * 进行字符串的转换
                             * */
                            pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
                            pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
                            /**
                             * 自定义处理器
                             * */
                            pipeline.addLast(new ClientHandler1());
                        }
                    });
            ChannelFuture future = bootstrap.connect(IP, PORT).sync();
            log.info("客户端访问");
            future.channel().closeFuture().sync();
        } finally {
            eventLoopGroup.shutdownGracefully();
        }
    }
}

这段代码实现了一个使用Netty框架的客户端,它连接到指定的服务器端并与服务器进行通信。

  1. 创建一个Bootstrap实例,用于启动客户端。
  2. 创建一个NioEventLoopGroup实例,用于处理客户端的事件和IO操作。
  3. 绑定事件组到Bootstrap实例。
  4. 指定使用的NioSocketChannel作为客户端套接字通道的实现类。
  5. 添加处理器到ChannelInitializer中,该处理器负责初始化和配置客户端连接的SocketChannel
  6. 在处理器中,通过ChannelPipeline添加了如下处理器:
    • StringDecoder:处理传入的字节数据,并将其解码为字符串。
    • StringEncoder:处理传出的字符串数据,并将其编码为字节。
    • ClientHandler1:自定义的处理器,用于处理与服务器之间的通信。
  7. 使用Bootstrapconnect()方法连接到指定的服务器IP和端口。
  8. 等待连接完成。
  9. 在连接成功后,打印日志信息。
  10. 等待客户端的关闭事件。
  1. 处理器
@Slf4j
public class ClientHandler1 extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        log.info("客户端读取的信息:{}", msg);
        ctx.channel().writeAndFlush("客户端writeAndFlush:我是客户端");
        TimeUnit.MILLISECONDS.sleep(5000);
    }

    /**
     * 当事件到达pipeline时候触发
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ctx.channel().writeAndFlush("客户端:开始聊天");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        //关闭Channel连接
        ctx.close();
    }

}

在这里插入图片描述

结果

服务端日志

Client Address ====== /127.0.0.1:63740,读取的信息:客户端:开始聊天
Client Address ====== /127.0.0.1:63740,读取的信息:客户端writeAndFlush:我是客户端
Client Address ====== /127.0.0.1:63740,读取的信息:客户端writeAndFlush:我是客户端

客户端日志

客户端读取的信息:服务端writeAndFlush:我是服务端
客户端读取的信息:服务端writeAndFlush:我是服务端

总结

在这里插入图片描述

引导类-Bootstarp和ServerBootstrap

Bootstarp和ServerBootstrap被称为引导类,使你的应用程序和网络层相隔离。类似java项目的启动类。

连接-NioSocketChannel

客户端和服务端的启动都是采用配置的channel去连接处理器,这里服务端和客户端是用NioSocketChannel

事件组-EventLoopGroup和NioEventLoopGroup

客户端使用的是NioEventLoopGroup,服务端使用的是EventLoopGroup。 服务端和客户端的引导类启动后实现了配置的运行,客户端和服务端的连接都是采用NioSocketChannel。 连接的流程:

  1. 客户端创建一个channel
  2. channel对应一个EventLoop,EventLoop存放到NioEventLoopGroup中
  3. 服务端监听到后,创建一个channel连接,channel对应一个EventLoop,EventLoop存放到子的EventLoopGroup,父的事件组负责监听,一个事件对应一个子事件组。
    在这里可以认为父是boss监听组,子是工作组。
  4. 当客户端发送信息的时候,先被父监听,然后将异步调用工作组。
  5. 消息会经过事件组的所有处理器。

实际上服务端的事件组也可以使用NioEventLoopGroup。
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/5e676670b49e40dd83155e76094e9017.png

送书活动

在这里插入图片描述

  • 🎁本次送书1~3本【取决于阅读量,阅读量越多,送的越多】👈
  • ⌛️活动时间:截止到2024-1月10号
  • ✳️参与方式:关注博主+三连(点赞、收藏、评论)
    购买链接:https://item.jd.com/13836258.html

在这里插入图片描述

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

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

相关文章

StarRocks 在小红书自助分析场景的应用与实践

作者&#xff1a;小红书 OLAP 研发负责人 王成 近两年 StarRocks 一直是小红书 OLAP 引擎体系里非常重要的部分&#xff0c;过去一年&#xff0c;小红书的 StarRocks 使用规模呈现出翻倍的增长速度&#xff0c;目前整体规模已经达到 30 个集群&#xff0c;CPU 规模已经达到了 3…

一文讲透怎样用SPSS做二项Logistic回归分析?结果如何解释?

推荐采用《SPSS统计分析入门与应用精解&#xff08;视频教学版&#xff09;》 杨维忠、张甜 清华大学出版社“7.4 二元Logistic回归分析” 的解答。 本节内容选自《SPSS统计分析入门与应用精解&#xff08;视频教学版&#xff09;》 杨维忠、张甜 清华大学出版社“7.4 二元Logi…

新手必看!STM32通用定时器-输入捕获!

一、用途与工作原理 用途&#xff1a;用于测量信号的参数&#xff0c;比如周期和频率。   工作原理&#xff1a;在输入捕获模式下&#xff0c;当捕获单元捕捉到外部信号的有效边沿(上升沿/下降 沿/双边沿)时&#xff0c;将计数器的当前值锁存到捕获/比较寄存器TIMx_CCR&#…

如何制定知识竞赛活动的竞赛规则

知识竞赛既然是知识的竞赛&#xff0c;就应该制定相应的细则或规则。竞赛规则一般应包括以下内容&#xff1a;竞赛编组、答题形式、每场题量、竞赛记分、违例处罚、观众答题、名次奖励及对参赛人员的要求。 1、竞赛编组 竞赛编组一般由抽签决定。在参赛人员较多、场次较多的…

JS数据类型转换注意事项【建议收藏】

ToPrimitive(obj, Number) 和 ToPrimitive(obj, String) 调用顺序不同在于: 区别在于调用 toString 方法和 valueOf 方法的顺序&#xff0c;区分这一点就行了。 ToPrimitive(obj, Number) > Number({}) 如果 obj 是基本类型&#xff0c;直接返回否则&#xff0c;调用 valueO…

简易机器学习笔记(九)LeNet实例 - 在眼疾识别数据集iChallenge-PM上的应用

前言 上一节大概讲了一下LeNet的内容&#xff0c;这一章就直接来用&#xff0c;实际上用一下LeNet来进行训练和分类试试。 调用的数据集&#xff1a; https://aistudio.baidu.com/datasetdetail/19065 说明&#xff1a; 如今近视已经成为困扰人们健康的一项全球性负担&…

【观察】Aginode安捷诺:坚守“长期主义”,服务中国数字经济

毫无疑问&#xff0c;随着整个社会加速数字化转型&#xff0c;尤其是5G、人工智能、大数据等技术兴起&#xff0c;以及智慧医疗、智慧金融、智能制造等应用加速落地&#xff0c;算力网络在经济社会发展中扮演了愈来愈重要的角色&#xff0c;成为支撑数字经济蓬勃发展的“新引擎…

2023-RunwayML-Gen-2 AI视频生成功能发展历程

RunwayML是一个人工智能工具&#xff0c;它为设计师、艺术家和创意人士提供了一种简单的方式来探索和应用机器学习技术。 RunwayML官方网页地址&#xff1a;Runway - Advancing creativity with artificial intelligence. RunwayML专区RunwayML-喜好儿aigcRunwayML 是一种先进…

大创项目推荐 深度学习卫星遥感图像检测与识别 -opencv python 目标检测

文章目录 0 前言1 课题背景2 实现效果3 Yolov5算法4 数据处理和训练5 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; **深度学习卫星遥感图像检测与识别 ** 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐…

如何在win7同样支持Webview2 在 WPF 中使用本地 Webview2 ,如何不依赖系统 Runtime

项目运行环境&#xff1a; .Net Framework 4.5.2 Windows 7 x64 Service Pack 1 WebView2 Microsoft.WebView2.FixedVersionRuntime.120.0.2210.91.x64 考虑到很多老项目&#xff0c;本项目使用的是.Net Framework 4.5.2&#xff0c;.Net 更高版本的其实也是可以支持的。 …

win2003搭建DNS服务器域名解析方法

可以搭建DNS服务器的系统有很多&#xff0c;这里以win2003举例。 要在Windows 2003上搭建DNS服务器&#xff0c;需要按照以下步骤操作&#xff1a; 一 配置DNS服务器 1、打开“控制面板”,选择“添加/删除程序”,点击“添加/删除Windows组件”。 2、在“Windows组件向导”中…

亚马逊促销效果不好怎么办?亚马逊促销规则是什么?-站斧浏览器

亚马逊促销效果不好怎么办&#xff1f; 分析原因&#xff1a;首先需要深入分析促销效果不佳的原因。可能是促销活动的设计不够吸引人&#xff0c;或者是目标受众定位不准确。 调整策略&#xff1a;根据分析结果调整促销策略。例如&#xff0c;优化广告文案、更改推广时段或调…

什么是负载均衡?什么情况下又会用到负载均衡

什么是负载均衡 在大型的网络应用中&#xff0c;使用多台服务器提供同一个服务是常有的事。平均分配每台服务器上的压力、将压力分散的方法就叫做负载均衡。 [利用 DNS来实现服务器流量的负载均衡&#xff0c;原理是“给网站访问者随机分配不同ip”] 什么情况下会用到负载均…

苹果Vision Pro将于1月27日上市!

在无数期待中&#xff0c;苹果全新产品Vision Pro头显终于定下上市日期。 彭博社记者马克古曼&#xff08;Mark Gurman&#xff09;于近日在X&#xff08;前推特&#xff09;平台爆料了这一信息&#xff0c;预计苹果Vision Pro头显将于2024年1月27日率先在美国上市。 在过去看…

实战SRC | api接口未授权 + 越权漏洞

本文由掌控安全学院 - zxl2605 投稿 一次在fofa上通过学习的fofa语句进行查询&#xff0c;无意中查询到了一个网址 其登录界面如下&#xff1a; 使用浏览器的F12打开开发者工具&#xff0c;查看JS寻找接口&#xff1a; 从JS代码中查询到一处接口如下&#xff1a; 发现是以p…

解决SyntaxError: future feature annotations is not defined,可适用其他包

方法&#xff1a;对报错的包进行降级 pip install tikzplotlib0.9.8site-packages后面是使用pip install安装的包&#xff0c;根据这个找到报错的包 想法来源&#xff1a; 环境是python3.6&#xff0c;完全按照作者要求进行环境配置&#xff0c;但仍报错。 我在网上找的解决…

视频号小店全新赛道,新手如何入驻?

我是电商珠珠 视频号小店为视频号团队所研发。距今为止也才发展了一年时间&#xff0c;在23年下半年掀起了不小的浪花。 我做视频号小店也有一年时间了&#xff0c;在他刚开始三个月的时候&#xff0c;就开始带着团队一起做。到现在也拥有了自己的视频号小店运营团队&#xf…

ArkTS语言应用开发入门指南与简单案例解析

文章目录 前言创建项目及其介绍简单案例学习本文总结问答回顾-学习前言 在前几节课中,我们已经了解了ArkTS语言的特点以及其基本语法。现在,我们将正式利用ArkTS来进行应用开发。本节课将通过一个快速入门案例,让大家熟悉开发工具的用法,并介绍UI的基础概念。 创建项目及…

yolo增加Shape-IoU,完美超越SIoU/EIoU/CIoU

论文地址&#xff1a;https://arxiv.org/pdf/2312.17663.pdf 代码地址&#xff1a;GitHub - malagoutou/Shape-IoU 摘要 作为检测定位分支的重要组成部分&#xff0c;边界框回归损失在目标检测任务中起着重要作用。现有的边界框回归方法通常考虑GT框和预测框之间的几何关系&…

数组指针和指针数组

首先&#xff0c;理解一下数组指针和指针数组这两个名词&#xff1a; “数组指针”和“指针数组”&#xff0c;只要在名词中间加上“的”字&#xff0c;就知道中心了—— 数组的指针&#xff1a;是一个指针&#xff0c;什么样的指针呢&#xff1f;指向数组的指针。 指针的数…