Netty实战(一) netty入门之创建echo服务器

news2024/9/21 14:32:36

目录

    • 一、理论知识
      • 1. 网络协议TCP/UDP
      • 2. netty简介
      • 3. 依赖
      • 4. netty核心类介绍
    • 二、开发实战
      • 1. 服务端
      • 2. 客户端
    • demo源码
    • 参考


一、理论知识

1. 网络协议TCP/UDP

TCP、UDP协议属于七层协议中传输层的协议,这两种主流协议的差异:

  1. TCP是一个面向连接的、可靠的、基于字节流(socket)的传输层通信协议;
  2. UDP是一个面向数据包(datagram)的、简单的、不可靠的通信协议。
  3. TCP进行有序无错的传输;
  4. UDP则可能出现数据包丢失、错误或重复。
  5. UDP不需要额外的保证,结构简单,只管发送数据,通常UDP的传输速度比TCP更快。
  6. UPD支持广播,TCP则不支持,TCP需要先建立端到端的连接才能发送数据。

基于以上特性,TCP、UDP的使用场景也不同:

  • TCP: HTTPS、HTTP、SMTP、FTP等等。
  • UDP:视频流、语音流、即时通信、广播等等。

在netty服务端中使用哪种协议取决于你在启动类AbstractBootstrap.channel(Class<? extends C> channelClass)中使用了哪种Channel接口实现类(这里的TCP/IP并非指TCP/IP协议簇,其表示的是传输层使用TCP传输,网络层使用IP来提供路由和寻址):

  • ServerSocketChannel:TCP/IP协议,主要实现类NioServerSocketChannelEpollServerSocketChannel
  • DatagramChannel:UDP/IP协议,主要实现类NioDatagramChannelEpollDatagramChannel
  • 其它协议……

如官网Echo源码中使用NioServerSocketChannel进行网络通信:

ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
 .channel(NioServerSocketChannel.class)
 .option(ChannelOption.SO_BACKLOG, 100)
……



2. netty简介

Nett是一个NIO的通信框架,在基于netty的核心框架上,netty实现了各种协议支持,很多功能都是开箱即用的netty-github。

下图分为三个部分:

  • 核心包:零拷贝、高扩展的事件模型、多协议统一的API
  • 传输服务:包括字节流&数据包传输(TCP&UDP)、HTTP隧道、虚拟管道等服务
  • 协议支持:这一部分提供了HTTP&websocket协议、SSL/TLS安全协议、zlib/gzip等压缩技术、大文件传输等等支持
    在这里插入图片描述



3. 依赖

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.91.Final</version>
    <scope>compile</scope>
</dependency>



4. netty核心类介绍

简单介绍一下netty框架中主要类的作用:

  1. AbstractBootstrap:抽象的引导类。通过该引导类可以配置整个端的生命周期。
  2. ServerBootstrap:AbstractBootstrap子类,用于创建方便创建服务端引导类。
  3. Bootstrap:AbstractBootstrap子类,用于创建方便创建客户端引导类。
  4. EventLoopGroup:事件循环执行组,用于循环执行某项Channel任务。每一个组中包含至少一个EventLoop,每个EventLoop负责执行一个个实际的Channel任务。
  5. Channel:netty的核心组件,它代表一个到实体(硬件设备、文件、网络套接字、或能执行一个或多个不同的I/O操作的程序组件)的开放连接,如进行读操作和写操作。每个与实体的连接都会封装为一个Channel,随后被注册到EventLoop中,然后由EventLoop负责调度处理。
  6. ChannelHandler:Channel的回调处理器,ChannelHandler的每个方法都是Channel的一个回调方法,当Channel触发了某个事件时,就会调用一系列ChannelHandler回调处理器中的某个回调方法,回调方法会对该事件进行各种处理。
  7. ChannelInboundHandler:实现ChannelHandler,是入站(入境,即是由外部传入的操作)事件的回调处理器。譬如远程建立连接或该连接失活数据读取用户事件错误事件都会触发一个入站事件。
  8. ChannelOutboundHandler: 实现ChannelHandler,是出站(出境,即是由内部传出的操作)事件的回调处理器。譬如主动创建或关闭对外连接将数据写到或冲刷到套接字
  9. ChannelHandlerAdapter:实现ChannelHandler,如果将ChannelInboundHandler、ChannelOutboundHandler看作是一种出入站的标记接口的话,那么ChannelHandlerAdapter就是实现各种出入站事件的核心骨架。你可以在各种出入站处理器中看见它的身影。
  10. ChannelInitializer:特殊的入站处理器,它将在一个Channel被注册到EventLoop时去初始化Channel,常用来设置Channel的ChannelPipeline。
  11. ChannelPipeline:一个Channel内部的出入站列表,包含了一系列的ChannelHandler。





二、开发实战

1. 服务端

  1. 服务端两个EventLoopGroup,bossGroup用于监听连接,有连接接入后转发到workerGroup;workerGroup用于处理与Channel的操作事件。
  2. bossGroup注册事件处理器:
    • LoggingHandler:日志处理器,DEBUG模式
  3. workerGroup注册事件处理器:
    • LoggingHandler:日志处理器,DEBUG模式
    • StringDecoder:字符串解码,将字节流转为字符
    • StringEncoder:字符串编码,将字符串转为字节流
    • ServerStringHandler:自定义处理器,将接收的字符原封不动的写回

服务器可以直接设置port,也可以像netty官方示例代码一样使用System.getProperty()来获取java启动命令的键值-Dkey=value

public class EchoServerRunner {
    private final int port;
    
    public EchoServerRunner(Integer port) {
        this.port = port;
    }
    public static void main(String[] args) throws Exception {
        EchoServerRunner server = new EchoServerRunner(8001);
        server.start();
    }
    public void start() throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    // 新的Channel 如何进行数据传输
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 100)
                    // 打印bossGroup处理日志
                    .handler(new LoggingHandler(LogLevel.DEBUG))
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            // 设置出入消息的处理链
                            ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG), new StringDecoder(), new StringEncoder(), new ServerStringHandler());
                        }
                    });
            // 绑定监听服务端口,并开始接收进来的连接
            ChannelFuture f = b.bind(port).sync();
            f.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

ServerStringHandler,自定义处理器代码:

public class ServerStringHandler extends SimpleChannelInboundHandler<String> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        System.out.println("服务端接收到消息:" + msg);
        ctx.writeAndFlush(msg);
    }
}

ok,一个echo服务器的功能就写好了,我们可以通过cmd的telnet命令来测试连接

telnet localhost 8001

连接成功后,服务端显示日志:
在这里插入图片描述

这时我们就可以开始传输数据了,先后输入1 c,控制台打印1cc(前面的一个c是本地输入回显)。
在这里插入图片描述

服务端的日志打印中,服务器接收(READ)到两次1byte(1B)的数据:31 63,解码后的原始数据为1 c

在这里插入图片描述


解释一下下面的一段内容:

         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 31                                              |1               |
+--------+-------------------------------------------------+----------------+
  • 0 1 2 3 4 5 6 7 8 9 a b c d e f表示的是一个包中的第几个字节流(8bit = 1byte),其最大值也表示了一个包的最大容量。
  • 00000000表示的是包的序号,32位,TCP为了不丢包,就会给每一个包设置一个序号,服务端会按着序号顺序逐步接收,接收成功一个包即返回ack,这也保证了数据接收的有序性。
  • 31表示的是传输的数据,一行中的一列占8字节,这里用的十六进制ASCII表示。
  • 1表示的是该包转码后的数据。

ASCII码的对照表:
ascii-code
ascii

ok,服务端看起来没什么问题了,我们接下来就用netty来模拟一个用户给echo服务器发送数据。



2. 客户端

  1. 客户端只有一个EventLoopGroup,因为我们只需要处理消息收发,并不需要监听某个端口的连接。
  2. workerGroup注册事件处理器:
    • LoggingHandler:日志处理器,DEBUG模式
    • StringDecoder:字符串解码,将字节流转为字符
    • StringEncoder:字符串编码,将字符串转为字节流
    • ClientStringHandler:自定义处理器,用来处理服务器回复
public class EchoClientRunner {
    private String host;
    private Integer port;

    public EchoClientRunner(String host, Integer port) {
        this.host = host;
        this.port = port;
    }
    public static void main(String[] args) throws Exception {
        EchoClientRunner client = new EchoClientRunner("127.0.0.1", 8001);
        client.start();
    }
    public void start() throws Exception {
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(workerGroup)
                    // 新的Channel 如何接收进来的连接
                    .channel(NioSocketChannel.class)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            // 设置出入消息的处理链
                            ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG), new StringDecoder(), new StringEncoder(), new ClientStringHandler());
                        }
                    });
            // 创建一个连接
            ChannelFuture f = b.connect(host, port).sync();
            // 创建连接后手动发送一个请求
            f.channel().writeAndFlush("Hello!");
            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
        }
    }
}

先启动echo服务器,再启动客户端
服务器日志如下:
在这里插入图片描述

客户端日志如下:
在这里插入图片描述

echo服务端 客户端交互成功。


不过也行你会对上面的ChannelPipeline中的执行器顺序感到疑惑。这里就不得不提一下入境(ChannelInboundHandler)、出境(ChannelOutboundHandler)以及同时有出入境(ChannelDuplexHandler)处理器的执行顺序的区别。

// 服务端
ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG), new StringDecoder(), new StringEncoder(), new ServerStringHandler());
// 客户端
ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG), new StringDecoder(), new StringEncoder(), new ClientStringHandler());

以服务端举例,当建立连接,客户端发送过来数据时,这时就会进入入境的处理链,执行顺序是ChannelInboundHandler注入的先后顺序:

  1. LoggingHandler(ChannelDuplexHandler)
  2. StringDecoder(ChannelInboundHandler)
  3. ServerStringHandler(ChannelInboundHandler)

当入境执行链处理完毕后,入境也就执行完成了。但是在ServerStringHandler中,我们又向客户端回复了信息,这时进入出境的处理链,执行顺序为ChannelOutboundHandler注入的相反方向:

  1. StringEncoder(ChannelOutboundHandler)
  2. LoggingHandler(ChannelDuplexHandler)

到这里一个完整的echo服务器就开发完成了。





demo源码

netty-demo





参考

netty实战
User guide for 5.x
java/io/netty/example

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

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

相关文章

ArcGIS Pro遥感影像分类:随机森林、支持向量机方法

本文介绍在ArcGIS Pro软件中&#xff0c;基于随机森林、支持向量机等多种算法&#xff0c;对遥感影像数据加以监督分类的具体方法。 在文章ArcGIS中ArcMap栅格遥感影像的监督分类&#xff08;https://blog.csdn.net/zhebushibiaoshifu/article/details/126905442&#xff09;中…

【已解决】Couldn‘t find a tree builder with the features you requested: lxml

这是一个常见于Python爬虫代码的报错。 报错代码&#xff1a; soup BeautifulSoup(r.text, xml) 报错原因&#xff1a; BeautifulSoup的解析方法之一&#xff0c;xml&#xff0c;需要安装好lxml库才行 解决办法&#xff1a; 安装 lxml 库即可。 pip install lxml 安装好…

HTML的Input(type)的属性都有哪些

&#x1f607;作者介绍&#xff1a;一个有梦想、有理想、有目标的&#xff0c;且渴望能够学有所成的追梦人。 &#x1f386;学习格言&#xff1a;不读书的人,思想就会停止。——狄德罗 ⛪️个人主页&#xff1a;进入博主主页 &#x1f33c;欢迎小伙伴们访问到博主的文章内容&am…

笨笨的刷题日记

关注我&#xff0c;带你一起学习&#xff0c;共同成长。 LeetCode 还记得三年前找实习的时候 leetCode 还是 1000 题左右&#xff0c;现在都飙到 3000 题了&#xff0c;还有前端狗专用的 JavaScript 系列。这个世界真实太疯狂了。 leetCode 部分习题参考答案 正在更新中 标号…

C++primer(第五版)第十五章(面向对象程序设计)

15.1 OOP:概述 面向对象程序设计(object-oriented programming)的核心思想是数据抽象,继承和动态绑定(个人认为应该是多态,但是书里原话是动态绑定,因此不太确定). 一开始,C只是C加上一些面向对象特性.C最初的名称C with Classes 也反映了这个血缘关系 …

摆动排序 II · Wiggle Sort II

链接&#xff1a; 题解&#xff1a; 1.先用partition函数&#xff0c;求得n/2的位置的排序 2.然后选取首尾指针&#xff08;奇数选择1和length-1&#xff0c;偶数选择为1和length-2&#xff09;&#xff0c;进行swap交换 3.每次首指针每次2&#xff0c;尾指针每次-2 九章算…

使用 Sa-Token 实现不同的登录模式:单地登录、多地登录、同端互斥登录

一、需求分析 如果你经常使用腾讯QQ&#xff0c;就会发现它的登录有如下特点&#xff1a;它可以手机电脑同时在线&#xff0c;但是不能在两个手机上同时登录一个账号。 同端互斥登录&#xff0c;指的就是&#xff1a;像腾讯QQ一样&#xff0c;在同一类型设备上只允许单地点登…

Spring:Bean生命周期

Bean 生命周期 生命周期 Bean 生命周期是 bean 对象从创建到销毁的整个过程。 简单的 Bean 生命周期的过程&#xff1a; 1.实例化&#xff08;调用构造方法对 bean 进行实例化&#xff09; 2.依赖注入&#xff08;调用 set 方法对 bean 进行赋值&#xff09; 3.初始化&#x…

IDEA使用教程 安装教程

16. Codota 插件 Codota 插件可以根据使用频率优先显示较常用的类和方法。然而&#xff0c;是否使用该插件取决于个人的偏好。有时工具只能作为参考&#xff0c;仍然需要依靠个人记忆来确保准确性。 17. 快速查看类和字段的注释 按下 F2 键可以快速查看某个类或字段的文档注…

编译运行Secure Value Recovery Service v2

下载项目 git clone https://github.com/signalapp/SecureValueRecovery2.git编译 make dockersh报错 修改Dockerfile ARG PROTOC_GEN_GO_GITREV6875c3d7242d1a3db910ce8a504f124cb840c23a RUN go env -w GOPROXYhttps://goproxy.cn,direct RUN go install google.golang.org/p…

阿里云轻量应用服务器和云服务器的区别

阿里云服务器ECS和轻量应用服务器有什么区别&#xff1f;云服务器ECS是明星级云服务器&#xff0c;轻量应用服务器可以理解为简化版的云服务器ECS&#xff0c;轻量适用于单机应用&#xff0c;云服务器ECS适用于集群类高可用高容灾应用&#xff0c;阿里云百科来详细说下阿里云轻…

MachineLearningWu_10_NeuralNetwork

x.1 课程目录 为了开始我们的学习&#xff0c;我们会先列出我们的课程目录&#xff0c;诸如以下&#xff0c; x.2 NN的发展 NN一开始是为了模仿人类大脑&#xff0c;但随着时间的演进&#xff0c;逐渐被使用在各种应用之中&#xff0c; 深度学习DL为何最近几年突飞猛进呢&…

IDEA使用插件绘制UML类图+PlantUML语法讲解

安装 IDEA安装插件 安装完插件记得重启一下IDEA 安装Graphviz&#xff08;亲测win11可以使用&#xff09; 安装完插件之后&#xff0c;还需要安装Graphviz才可以渲染图形。 Graphviz安装包下载地址 安装过程很简单&#xff0c;直接双击或者管理员身份运行即可&#xff0c;注…

高性能内存对象缓存 Memcached

高性能内存对象缓存 Memcached Memcached 概述 一套开源的高性能分布式内存对象缓存系统 所有的数据都存储在内存中 支持任意存储类型的数据 提高网站的访问速度 Memcached 是典型的 C/S 架构&#xff0c;因此需要安装 Memcached 服务端与 Memcached API 客户端。 数据存…

实验三(OSPF)7 8

解题思路&#xff1a; 先配置好路由的环回及规划好IP地址&#xff0c;确保正确&#xff1b; &#xff08;由于r8模拟为运营商&#xff0c;因此r1,r2,r3各写一条缺省指向r8 并测试&#xff09; hub-spoke网络结构&#xff0c;需要在r1-r2-r3建立隧道0配置MGRE-多点通用路由协…

FreeRTOS ~(七)互斥量 ~ (3/3)互斥量的缺陷和递归锁

前情提要 FreeRTOS ~&#xff08;四&#xff09;同步互斥与通信 ~ &#xff08;2/3&#xff09;互斥的缺陷 FreeRTOS ~&#xff08;五&#xff09;队列的常规使用 ~ &#xff08;2/5&#xff09;队列解决互斥缺陷 FreeRTOS ~&#xff08;六&#xff09;信号量 ~ &#xff08;2/…

超详细 | 遗传-粒子群自适应优化算法及其实现(Matlab)

作者在前面的文章中介绍了两种经典的优化算法——遗传算法(GA)和粒子群算法(PSO)&#xff0c;这些智能优化算法解决问题的方式和角度各不相同&#xff0c;都有各自的适用域和局限性&#xff0c;对智能优化算法自身做的改进在算法性能方面得到了一定程度的提升&#xff0c;但算法…

前端如何实现面向对象

交流所记&#xff1a; 面向对象的三要素&#xff1a; 封装&#xff1a;把客观事物封装成抽象的类&#xff0c;并且类可以把自己的数据和方法只让可信的类或者对象操作&#xff0c;对不可信的类或者对象隐藏信息继承&#xff1a;它可以使用现有类的所有功能&#xff0c;并在无…

链上衍生品协议 ProsperEx:探索 RWA 叙事,打造 DeFi 变异黑马

“ProsperEx 本身背靠着具备潜力的链上衍生品赛道&#xff0c;同时也是 RWA 领域早期的布局者之一&#xff0c;其有望成为 Web2 世界与 Web3 世界的早期连接点&#xff0c;并具备广泛且规模性捕获用户的能力。” 从2020年DeFi之夏链上世界迎来了爆发后&#xff0c;链上衍生品赛…

SQL21 浙江大学用户题目回答情况

解法一:left JOIN select t1.device_id,question_id,result from question_practice_detail t1 left JOIN user_profile t2 on t1.device_id t2.device_id where university 浙江大学解法二:子查询 select device_id, question_id, result from question_practice_detail …