• netty框架学习记录


    1. 简介

    官方定义为:”Netty 是一款异步的事件驱动的网络应用程序框架,支持快速地开发可维护的高性能的面向协议的服务器
    和客户端”。

    纵观Java系的多种服务器/大数据框架,都离不开Netty做出的贡献,本文对Netty做一个简单的概述

    2. 主要特性

    Netty有很多重要的特性,主要特性如下:
    - 优雅的设计
    - 统一的API接口,支持多种传输类型,例如OIO,NIO
    - 简单而强大的线程模型
    - 丰富的文档
    - 卓越的性能
    - 拥有比原生Java API 更高的性能与更低的延迟
    - 基于池化和复用技术,使资源消耗更低
    - 安全性
    - 完整的SSL/TLS以及StartTLS支持
    - 可用于受限环境,如Applet以及OSGI

    3. 主要术语


    在正式开始之前,先对Netty涉及到的一些术语做个简单的说明

    3.1 IO模型:BIO/NIO/Netty

    3.1.1 BIO(Blocking IO):阻塞IO

    Socket 创建一个新的 Thread,线程模型如下图所示:

     

    BIO 有的称之为 basic(基本) IO,有的称之为 block(阻塞) IO,主要应用于文件 IO 和网络 IO, 这里不再说文件 IO, 在 JDK1.4 之前,我们建立网络连接的时候只能采用 BIO,需要先在服务端启动一个 ServerSocket,然后在客户端启动 Socket 来对服务端进行通信,默认情况下服务端需要对每个请求建立一个线程等待请求,而客户端发送请求后,先咨询服务端是否有线程响应,如果没有则会一直等待或者遭到拒绝,如果有的话,客户端线程会等待请求结束后才继续执行, 这就是阻塞式 IO。

     //BIO 服务器端程序
        public class TCPServer {
            public static void main(String[] args) throws Exception {
                //1.创建 ServerSocket 对象
                ServerSocket ss = new ServerSocket(9999);
                while (true) {
                    //2.监听客户端
                    Socket s = ss.accept(); //阻塞
                    // 3.从连接中取出输入流来接收消息
                    InputStream is = s.getInputStream();
                    //阻塞
                    byte[] b = new byte[10];
                    is.read(b);
                    String clientIP = s.getInetAddress().getHostAddress();
                    System.out.println(clientIP + "说:" + new String(b).trim());
                    //4.从连接中取出输出流并回话
                    OutputStream os = s.getOutputStream();
                    os.write("hello".getBytes());
                    //5.关闭
                    s.close();
                }
            }
        }

    上述代码编写了一个服务器端程序,绑定端口号 9999,accept 方法用来监听客户端连接, 如果没有客户端连接,就一直等待,程序会阻塞到这里。

     //BIO 客户端程序
        public class TCPClient {
            public static void main(String[] args) throws Exception {
                while (true) {
                    //1.创建 Socket 对象
                    Socket s = new Socket("127.0.0.1", 9999);
                    //2.从连接中取出输出流并发消息 
                    OutputStream os = s.getOutputStream();
                    System.out.println("请输入:");
                    Scanner sc = new Scanner(System.in);
                    String msg = sc.nextLine();
                    os.write(msg.getBytes());
                    //3.从连接中取出输入流并接收回话 
                    InputStream is = s.getInputStream();
                    //阻塞 
                    byte[] b = new byte[20];
                    is.read(b);
                    System.out.println("说:" + new String(b).trim());
                    //4.关闭
                    s.close();
                }
            }
        }

    上述代码编写了一个客户端程序,通过 9999 端口连接服务器端,getInputStream 方法用来 等待服务器端返回数据,如果没有返回,就一直等待,程序会阻塞到这里。

    这个仅仅只是简单的演示代码,如果真正的使用怎么做呢?加线程呗,一个用户过来就新建一个线程,然后每个线程里面一个while循环阻塞在里面,这是很恐怖的一件事,这些就会带来下面三个问题:

    1、资源受限,大量的线程阻塞在那里,对于服务器来说是很浪费资源的一件事。
    2、线程切换频繁,我们知道java线程如果优先级相同是抢占式的也就是随机的,线程数量过多,对于单核cpu切换来说是很影响性能的。
    3、上面例子可以看到Bio是以byte为单位的。

    3.1.2 NIO(Non Blocking IO):非阻塞IO

    Java的NIO特性在JDK 1.4中引入,其结构如下:

    Jdk1.4之后提出了Nio,NIO 和 BIO 有着相同的目的和作用,但是它们的实现方式完全不同,BIO 以流的方式处理数据,**而 NIO 以块的方式处理数据,**块 I/O 的效率比流 I/O 高很多。另外,NIO 是非阻塞式的, 这一点跟 BIO 也很不相同,使用它可以提供非阻塞式的高伸缩性网络。 NIO 主要有三大核心部分:Channel(通道),Buffer(缓冲区), Selector(选择器)。传统的 BIO 基于字节流和字符流进行操作,而 NIO 基于 Channel(通道)和 Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择区)用于监听多个通 道的事件(比如:连接请求,数据到达等),因此使用单个线程就可以监听多个客户端通道。

    虽然Java 的NIO在性能上比BIO已经相当的优秀,但是要做到如此正确和安全并
    不容易。特别是,在高负载下可靠和高效地处理和调度 I/O 操作是一项繁琐而且容易出错的任务,此时就时Netty上场的时间了。

    3.1.3 Netty

    Netty对NIO的API进行了封装,通过以下手段让性能又得到了一定程度的提升
    1. 使用多路复用技术,提高处理连接的并发性
    2. 零拷贝:
    1. Netty的接收和发送数据采用DIRECT BUFFERS,使用堆外直接内存进行Socket读写,不需要进行字节缓冲区的二次拷贝
    2. Netty提供了组合Buffer对象,可以聚合多个ByteBuffer对象进行一次操作
    3. Netty的文件传输采用了transferTo方法,它可以直接将文件缓冲区的数据发送到目标Channel,避免了传统通过循环write方式导致的内存拷贝问题
    3. 内存池:为了减少堆外直接内存的分配和回收产生的资源损耗问题,Netty提供了基于内存池的缓冲区重用机制
    4. 使用主从Reactor多线程模型,提高并发性
    5. 采用了串行无锁化设计,在IO线程内部进行串行操作,避免多线程竞争导致的性能下降
    6. 默认使用Protobuf的序列化框架
    7. 灵活的TCP参数配置

     Socket

    简介

    套接字是通信的基石,是支持TCP/IP协议的路通信的基本操作单元。可以将套接字看作不同主机间的进程进行双间通信的端点,它构成了单个主机内及整个网络间的编程界面。套接字存在于通信域中,通信域是为了处理一般的线程通过套接字通信而引进的一种抽象概念。套接字通常和同一个域中的套接字交换数据(数据交换也可能穿越域的界限,但这时一定要执行某种解释程序),各种进程使用这个相同的域互相之间用Internet协议簇来进行通信
    Socket(套接字)可以看成是两个网络应用程序进行通信时,各自通信连接中的端点,这是一个逻辑上的概念。它是网络环境中进程间通信的API(应用程序编程接口),也是可以被命名和寻址的通信端点,使用中的每一个套接字都有其类型和一个与之相连进程。通信时其中一个网络应用程序将要传输的一段信息写入它所在主机的 Socket中,该 Socket通过与网络接口卡(NIC)相连的传输介质将这段信息送到另外一台主机的 Socket中,使对方能够接收到这段信息。 Socket是由IP地址和端口结合的,提供向应用层进程传送数据包的机制 

    表示方法

    编辑 语音
    套接字Socket=(IP地址:端口号),套接字的表示方法是点分十进制的lP地址后面写上端口号,中间用冒号或逗号隔开。每一个传输层连接唯一地被通信两端的两个端点(即两个套接字)所确定。例如:如果IP地址是210.37.145.1,而端口号是23,那么得到套接字就是(210.37.145.1:23)
     

    Spring Boot使用Netty实现客户端与服务器通信

    https://blog.csdn.net/qmqm011/article/details/100156010/
     

    什么是Decoder和Encoder

    在Netty里面,有四个核心概念,它们分别是:

    • Channel:一个客户端与服务器通信的通道。
    • ChannelHandler:业务逻辑处理器, 通常情况下,业务逻辑都是存在于ChannelHandler之中。
      • ChannelInboundHandler:输入处理器
      • ChannelOutboundHandler:输出处理器
    • ChannelPipeline:用于存放ChannelHandler的双向链表。
    • ChannelContext:通信管道的上下文

    它们之间的交互流程是:

    • 事件传递给 ChannelPipeline 的第一个 ChannelHandler
    • ChannelHandler 通过关联的 ChannelHandlerContext 传递事件给 ChannelPipeline 中的 下一个

    而我们要讲的Decoder和Encoder,就是ChannelInboundHandler和ChannelOutboundHandler,分别用于在数据流进来的时候将字节码转换为消息对象和数据流出去的时候将消息对象转换为字节码。

     

    socket的标准参数,并不是netty自己的

    具体为:

    • ChannelOption.SO_BACKLOG, 1024

           BACKLOG用于构造服务端套接字ServerSocket对象,标识当服务器请求处理线程全满时,用于临时存放已完成三次握手的请求的队列的最大长度。如果未设置或所设置的值小于1,Java将使用默认值50。

    • ChannelOption.SO_KEEPALIVE, true

          是否启用心跳保活机制。在双方TCP套接字建立连接后(即都进入ESTABLISHED状态)并且在两个小时左右上层没有任何数据传输的情况下,这套机制才会被激活。

    • ChannelOption.TCP_NODELAY, true

          在TCP/IP协议中,无论发送多少数据,总是要在数据前面加上协议头,同时,对方接收到数据,也需要发送ACK表示确认。为了尽可能的利用网络带宽,TCP总是希望尽可能的发送足够大的数据。这里就涉及到一个名为Nagle的算法,该算法的目的就是为了尽可能发送大块数据,避免网络中充斥着许多小数据块。

          TCP_NODELAY就是用于启用或关于Nagle算法。如果要求高实时性,有数据发送时就马上发送,就将该选项设置为true关闭Nagle算法;如果要减少发送次数减少网络交互,就设置为false等累积一定大小后再发送。默认为false。

    • ChannelOption.SO_REUSEADDR, true

          SO_REUSEADDR允许启动一个监听服务器并捆绑其众所周知端口,即使以前建立的将此端口用做他们的本地端口的连接仍存在。这通常是重启监听服务器时出现,若不设置此选项,则bind时将出错。                SO_REUSEADDR允许在同一端口上启动同一服务器的多个实例,只要每个实例捆绑一个不同的本地IP地址即可。对于TCP,我们根本不可能启动捆绑相同IP地址和相同端口号的多个服务器。                              SO_REUSEADDR允许单个进程捆绑同一端口到多个套接口上,只要每个捆绑指定不同的本地IP地址即可。这一般不用于TCP服务器。 SO_REUSEADDR允许完全重复的捆绑:当一个IP地址和端口绑定到某个套接口上时,还允许此IP地址和端口捆绑到另一个套接口上。一般来说,这个特性仅在支持多播的系统上才有,而且只对UDP套接口而言(TCP不支持多播)

    • ChannelOption.SO_RCVBUF AND ChannelOption.SO_SNDBUF
      定义接收或者传输的系统缓冲区buf的大小,
    • ChannelOption.ALLOCATOR

          Netty4使用对象池,重用缓冲区
          bootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
          bootstrap.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);

  • 相关阅读:
    Java自学-类和对象 引用
    Java自学-数组 Arrays
    Java自学-数组 二维数组
    Java自学-数组 复制数组
    Java自学-数组 增强型for循环
    IDEA 创建普通的maven+java Project
    git -- 项目开发最常用操作记录
    操作系统-Windows操作系统的线程调度了解这些
    操作系统-CPU调度
    操作系统-线程引入
  • 原文地址:https://www.cnblogs.com/h-c-g/p/15469142.html
Copyright © 2020-2023  润新知