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

前言

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

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

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

COMSOL接触(高度非线性)仿真常见报错及解决方法总结

前言 由于COMSOL采用隐式求解器&#xff0c;相较于使用显式求解器的Dyna、Abaqus等软件。要在COMSOL中实现结构接触这一高度非线性问题难度较大&#xff0c;报错时有发生。究其原因&#xff0c;是当物体之间相互接触时&#xff0c;物体受到的应力、运动路径会发生突变&#xff…

LabVIEW高精度微小电容测量

LabVIEW高精度微小电容测量 在电子工程和科研领域&#xff0c;精确测量微小电容值是一项有一定要求的任务&#xff0c;尤其在涉及到高精度和低成本时。设计了一种基于LabVIEW高精度微小电容测量系统&#xff0c;旨在提供一个既经济又高效的解决方案。 该系统的核心在于使用FD…

C语言中的内存函数你知道多少呢?

目录 ​编辑 1. memcpy的使用和模拟实现 1.1函数介绍 ​编辑 1.2函数的使用 1.3模拟实现 2. memmove的使用和模拟实现 2.1函数介绍 2.2函数的使用 2.3模拟实现 3. memset函数的使用 3.1函数介绍 3.2函数的使用 ​编辑 4. memcmp函数的使用 4.1函数介绍 4.2函数…

Python循环语句——range语句

一、引言 在Python编程中&#xff0c;range函数是一个内置函数&#xff0c;用于生成一个不可变的数字序列。它常被用于循环结构&#xff0c;如for循环&#xff0c;来遍历一系列的数字。尽管其使用非常基础&#xff0c;但range的强大之处在于其提供了灵活性&#xff0c;可以创建…

【2024.2.5练习】砍竹子(25分)

题目描述 题目分析 考虑题目是否满足贪心。每次施展魔法会使一段连续的竹子高度变为一半左右的平方根。根据样例&#xff0c;似乎每次让最高的竹子变短就能得到最优解。 假设魔法一次只能对一根竹子使用&#xff0c;永远不出现连续相同高度的竹子&#xff0c;那么显然无论使用…

接口幂等性

接口幂等性 如何实现幂等性实现方式一&#xff1a;数据库唯一主键实现方式二&#xff1a;Token机制实现方式三&#xff1a;数据库乐观锁实现方式四、加分布式锁 如何实现幂等性 其实实现幂等性的方案有不少&#xff0c;但是呢&#xff0c;这就得需要你根据不同的业务场景去选择…

Stable Diffusion 模型下载:Disney Pixar Cartoon Type A(迪士尼皮克斯动画片A类)

文章目录 模型介绍生成案例案例一案例二案例三案例四案例五案例六案例七案例八案例九案例十 下载地址 模型介绍 目前还没有一个好的皮克斯迪士尼风格的卡通模型&#xff0c;所以我决定自己制作一个。这是将皮克斯风格模型与我自己的Loras合并在一起&#xff0c;创建一个通用的…

oracle 启动命令以及ORA-01033问题处理、删除归档日志

1 启动数据库:startup 2 关闭数据库&#xff1a;Shutdown immediate 3 查看监听状态&#xff1a;lsnrctl status 4 启动监听&#xff1a;lsnrctl start 5 停止监听&#xff1a;lsnrctl stop 常见问题 1、在服务器重启后会出现&#xff0c;Oracle ORA-01033: ORAC…

渗透测试-信息打点与架构分析细节梳理

渗透测试-信息打点与架构分析细节梳理 为了保障信息安全&#xff0c;我在正文中会去除除靶场环境的其他任何可能的敏感信息 什么是网站架构 网站架构包括网站的方方面面&#xff0c;下面是常见的内容&#xff1a; 前端&#xff08;Front-End&#xff09;&#xff1a; 使用Reac…

DAY14之二叉树理论基础及递归遍历和迭代遍历

理论基础 满二叉树 满二叉树&#xff1a;如果一棵二叉树只有度为0的结点和度为2的结点&#xff0c;并且度为0的结点在同一层上&#xff0c;则这棵二叉树为满二叉树。 如图所示&#xff1a; 这棵二叉树为满二叉树&#xff0c;也可以说深度为k&#xff0c;有2^k-1个节点的二叉…

Pymysql之Connection中常用API

Connection中常用API 1、open() &#xff1a;检测数据库是否连接。 connect.open&#xff1a;如果数据库连接返回Trhe&#xff0c;否则返回False。 2、ping(reconnectTrue) connect.ping(reconnectTrue):如果reconnectTrue表示连接断开后&#xff0c;重新进行连接。 import…

春节假期如何高效管理Shopee虾皮本土店?技巧都给你整理好了!

EasyBoss ERP 对于中国人最重要的春节即将来临&#xff0c;但对于运营Shopee、TikTok Shop等平台的卖家而言&#xff0c;他们的客户可不会过春节。为了不影响店铺的业绩&#xff0c;很多卖家在春节期间都还是照常运营店铺&#xff0c;但又不想错过和家人团圆的机会怎么办&…

《Java程序设计》实验报告(一)之Java语言基础

实验内容及步骤&#xff1a; 编写”hello world”应用程序。&#xff08;1&#xff09;代码&#xff1a; public class HelloWorld { public static void main(String[] args) { System.out.println("Hello world!"); } } &#xff08;2&#xff09;运行…

惟客数据地产经营分析解决方案-构建数字化经营体系,提高精细化管理能力

惟客数据地产经营分析解决方案以拉通数据底座&#xff0c;以管理行为、量化考核、预警机制为核心&#xff0c;强化对经营风险的识别和解决&#xff0c;以终为始&#xff0c;通过高频高价值场景的应用适配&#xff0c;支撑企业在数字化时代中不断创新、转型&#xff0c;提升企业…

使用Pillow来生成简单的红包封面

Pillow库&#xff08;Python Imaging Library的后继&#xff09;是一个强大而灵活的图像处理库&#xff0c;适用于Python。Pillow 库&#xff08;有时也称 PIL 库&#xff09; 是 Python 图像处理的基础库&#xff0c;它是一个免费开源的第三方库&#xff0c;由一群 Python 社区…

SSL协议是什么?关于SSL和TLS的常见问题解答

SSL&#xff08;安全套接字层&#xff09;及其后继者TLS&#xff08;传输层安全&#xff09;是用于在联网计算机之间建立经过身份验证和加密的链接的协议。尽管SSL协议在 1999年已经随着TLS 1.0的发布而被弃用&#xff0c;但我们仍将这些相关技术称为“SSL”或“SSL/TLS”。那么…

VM 虚拟机和容器技术之间有什么区别?

随着云计算技术的不断发展&#xff0c;虚拟机和容器技术作为两种常见的虚拟化技术&#xff0c;被广泛应用于云计算领域。虽然虚拟机和容器技术都是虚拟化技术&#xff0c;但它们之间存在一些重要的区别。本文将详细介绍虚拟机和容器技术的区别&#xff0c;以便读者更好地了解这…

2.7:二叉树创建、先中后遍历、各个节点度的个数、深度

1.二叉树的创建、先中后遍历、各个节点度的个数、深度 程序代码&#xff1a; 1 #include<stdio.h>2 #include<string.h>3 #include<stdlib.h>4 typedef char datatype;5 typedef struct node6 {7 datatype data;8 struct node *lchild;9 struct…

如何使用数据恢复软件恢复已删除的数据

本教程介绍如何使用奇客数据恢复软件有效地从各种存储系统中恢复已删除的数据&#xff1a; 您是否曾经不小心从系统中删除了数周或数月的工作成果&#xff1f; 如果是这样&#xff0c;那么请从您并不孤单这一事实中找到安慰。这种严重且后果严重的判断错误在世界各地的计算机…

【leetcode】深搜、暴搜、回溯、剪枝(C++)1

深搜、暴搜、回溯、剪枝&#xff08;C&#xff09;1 一、全排列1、题目描述2、代码3、解析 二、子集1、题目描述2、代码3、解析 三、找出所有子集的异或总和再求和1、题目描述2、代码3、解析 四、全排列II1、题目解析2、代码3、解析 五、电话号码的字母组合1、题目描述2、代码3…