其他分享
首页 > 其他分享> > Netty

Netty

作者:互联网

Netty

Netty介绍和应用场景

Netty的介绍

  1. Netty 是由 JBOSS 提供的一个 Java 开源框架,现为 Github上的独立项目。
  2. Netty 是一个异步的、基于事件驱动的网络应用框架,用以快速开发高性能、高可 靠性的网络 IO 程序
  3. Netty主要针对在TCP协议下,面向Clients端的高并发应用,或者Peer-to-Peer(P2P)场景下 的大量数据持续传输的应用。
  4. Netty本质是一个NIO框架,适用于服务器通讯相关的多种应用场景
  5. 要透彻理解Netty , 需要先学习 NIO , 这样我们才能阅读 Netty 的源码

Netty的应用场景

Netty的学习参考资料

Java BIO编程

I/O模型

I/O 模型基本说明

  1. I/O 模型简单的理解:就是用什么样的通道进行数据的发送和接收,很大程度上决定了程序通信的性能
  2. Java共支持3种网络编程模型/IO模式:BIO、NIO、AIO
  3. Java BIO : 同步并阻塞(传统阻塞型),服务器实现模式为一个连接一个线程,即客户端 有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成 不必要的线程开销
  4. Java NIO : 同步非阻塞,服务器实现模式为一个线程处理多个请求(连接),即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求就进行处理
  5. Java AIO(NIO.2) : 异步非阻塞,AIO 引入异步通道的概念,采用了 Proactor 模式,简化了程序编写,有效的请求才启动线程,它的特点是先由操作系统完成后才通知服务端程

BIO、NIO、AIO适用场景分析

  1. BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高, 并发局限于应用中,JDK1.4以前的唯一选择,但程序简单易理解。
  2. NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,弹幕 系统,服务器间通讯等。编程比较复杂,JDK1.4开始支持。
  3. AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分 调用OS参与并发操作,编程比较复杂,JDK7开始支持。

Java BIO 基本介绍

  1. Java BIO 就是传统的java io 编程,其相关的类和接口在 java.io
  2. BIO(blocking I/O) : 同步阻塞,服务器实现模式为一个连接一个线程,即客户端有连 接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造 成不必要的线程开销,可以通过线程池机制改善(实现多个客户连接服务器)。
  3. BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高, 并发局限于应用中,JDK1.4以前的唯一选择,程序简单易理解

Java BIO 工作机制

应用程序比如浏览器、电子邮件、文件传输服务器等产生的数据,会通过传输层协议进行传输。而应用程序是不会和传输层直接建立联系的,而是有一个能够连接应用层和传输层之间的套件,这个套件就是 Socket

Java BIO 应用实例

  1. 使用BIO模型编写一个服务器端,监听6666端口,当有客户端连接时,就启动一个线程与之通讯
  2. 要求使用线程池机制改善,可以连接多个客户端
  3. 服务器端可以接收客户端发送的数据(telnet 方式即)
1.什么是Telnet?
  对于Telnet的认识,不同的人持有不同的观点,可以把Telnet当成一种通信协议,但是对于入侵者而言,Telnet只是一种远程登录的工具。一旦入侵者与远程主机建立了Telnet连接,入侵者便可以使用目标主机上的软、硬件资源,而入侵者的本地机只相当于一个只有键盘和显示器的终端而已。
2.为什么需要telnet?
  telnet就是查看某个端口是否可访问。我们在搞开发的时候,经常要用的端口就是 8080。那么你可以启动服务器,用telnet 去查看这个端口是否可用。
  Telnet协议是TCP/IP协议家族中的一员,是Internet远程登陆服务的标准协议和主要方式。它为用户提供了在本地计算机上完成远程主机工作的能力。在终端使用者的电脑上使用telnet程序,用它连接到服务器。终端使用者可以在telnet程序中输入命令,这些命令会在服务器上运行,就像直接在服务器的控制台上输入一样。可以在本地就能控制服务器。要开始一个telnet会话,必须输入用户名和密码来登录服务器。Telnet是常用的远程控制Web服务器的方法。
3.Windows XP怎么执行telnet 命令?
	1)、点击开始 → 运行 → 输入CMD,回车。
	2)、在出来的DOS界面里,输入telnet测试端口命令: telnet IP 端口 或者 telnet 域名 端口,回车。
  如果端口关闭或者无法连接,则显示不能打开到主机的链接,链接失败;端口打开的情况下,链接成功,则进入telnet页面(全黑的),证明端口可用。
  Telnet 客户端命常用命令:
  open : 使用 openhostname 可以建立到主机的 Telnet 连接。
  close : 使用命令 close 命令可以关闭现有的 Telnet 连接。
  display : 使用 display 命令可以查看 Telnet 客户端的当前设置。
  send : 使用 send 命令可以向 Telnet 服务器发送命令。支持以下命令:
  ao : 放弃输出命令。
  ayt : “Are you there”命令。
  esc : 发送当前的转义字符。
  ip : 中断进程命令。
  synch : 执行 Telnet 同步操作。
  brk : 发送信号。
  上表所列命令以外的其他命令都将以字符串的形式发送至 Telnet 服务器。例如,sendabcd 将发送字符串 abcd 至 Telnet 服务器,这样,Telnet 会话窗口中将出现该字符串。
  quit :使用 quit 命令可以退出 Telnet 客户端。
  
windows10 中telnet默认是关闭的,需要打开才能使用
控制面板 -> 程序 -> 启动或关闭Windows功能 -> 勾选 Telnet客户端 后点击确定
如果 Telnet客户端 依旧无法使用或无法建立多个连接,则需重启计算机

使用步骤
1. window+r —> cmd —> telnet回车 —> 输入quit退出telnet模式
2. telnet IP PORT -> 连接成功后会跳到Tenlet控制页面(全黑,啥也看不到),输入crlt+] 进入命令模式 -> send hello world -> 退出Telnet控制台,quit指令
/**
 * 线程工厂
 *
 * @author 
 * @since 
 */
public class ThreadFactoryBuild {

    private final String NUM = "%d";

    private final String STR = "%s";

    /**
     * %d 数字
     * %s 字符串
     */
    private String nameType = STR;

    private String name;

    private final ThreadFactory nameThreadFactory;

    public ThreadFactoryBuild() {
        nameThreadFactory = r -> {
            if (STR.equals(nameType)) {
                name = UUID.randomUUID().toString().replace("-", "");
            } else {
                name = String.valueOf(System.currentTimeMillis());
            }
            return new Thread(r, name);
        };
    }

    public ThreadFactoryBuild setName(String nameType) {
        this.nameType = nameType;
        return this;
    }

    public ThreadFactory build() {
        return this.nameThreadFactory;
    }
}

/**
 * BIO 服务器
 *
 * @author 
 * @since 
 */
public class BioService {
    /**
     * 自定义线程名称,方便出错的时候朔源
     */
    private static final ThreadFactory NAME_THREAD_FACTORY = new ThreadFactoryBuild().setName("%s").build();

    /**
     * 编码集
     */
    private static final Charset GBK = Charset.forName("GBK");

    /**
     * 引导程序
     *
     * @param port 端口号
     */
    public static void bootstrap(int port) throws IOException {
        // 思路
        // 1. 创建一个线程池
        // 2. 如果有客户端链接就创建一个线程与之通讯(单独写一个方法)
        // 获取CPU核数
        int processors = Runtime.getRuntime().availableProcessors();
        System.out.println("CPU cores: " + processors);
        // 线程池机制
        ThreadPoolExecutor pool = new ThreadPoolExecutor(processors, processors, 0L, TimeUnit.MICROSECONDS, new LinkedBlockingQueue<>(), NAME_THREAD_FACTORY);
        // 创建ServerSocket
        ServerSocket serverSocket = new ServerSocket(port);
        System.out.println("服务器启动了");
        while (true) {
            System.out.println("线程信息 id = " + Thread.currentThread().getId() + " 名称 = " + Thread.currentThread().getName());
            // 监听客户端链接
            System.out.println("等待连接...");
            final Socket socket = serverSocket.accept();
            System.out.println("连接到一个客户端");
            // 创建一个线程与之通讯(单独写一个方法)
            pool.execute(() -> {
                handler(socket);
            });
        }
    }

    /**
     * 与客户端通讯处理
     *
     * @param socket 套接字
     */
    private static void handler(Socket socket) {
        long id = Thread.currentThread().getId();
        try {
            System.out.println("线程信息 id = " + id + " 名称 = " + Thread.currentThread().getName());
            byte[] buff = new byte[1024];
            // 通过socket获取输入流
            InputStream inputStream = socket.getInputStream();
            // 循环读取客户端发送数据
            while (true) {
                System.out.println("id = " + id + "=>read...");
                int read = inputStream.read(buff);
                if (read != -1) {
                    // 输出客户端发送数据
                    System.out.println("id = " + id + "=>" + new String(buff, 0, read, GBK));
                } else {
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            System.out.println("id = " + id + "=>关闭与client的连接");
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

/**
 * BIO 启动类
 *
 * @author 
 * @since 
 */
public class BioStudy {
    public static void main(String[] args) throws IOException {
        BioService.bootstrap(6666);
    }
}

Java BIO 问题分析

  1. 每个请求都需要创建独立的线程,与对应的客户端进行数据 Read,业务处理,数据 Write 。
  2. 当并发数较大时,需要创建大量线程来处理连接,系统资源占 用较大。
  3. 连接建立后,如果当前线程暂时没有数据可读,则线程就阻塞 在 Read 操作上,造成线程资源浪费

Java NIO编程

Java NIO 基本介绍

  1. Java NIO 全称 java non-blocking IO,是指 JDK 提供的新 API。从 JDK1.4 开始,Java 提供了一系列改进的输入/输出 的新特性,被统称为 NIO(即 New IO),是同步非阻塞
  2. NIO 相关类都被放在 java.nio 包及子包下,并且对原 java.io 包中的很多类进行改写。
  3. NIO 有三大核心部分:Channel(通道),Buffer(缓冲区), Selector(选择器)
  4. NIO是 面向缓冲区 ,或者面向 块 编程的。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动,这就 增加了处理过程中的灵活性,使用它可以提供非阻塞式的高伸缩性网络
  5. Java NIO的非阻塞模式,使一个线程从某通道发送请求或者读取数据,但是它仅能得 到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线 程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞 写也是如此,一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情
  6. 通俗理解:NIO是可以做到用一个线程来处理多个操作的。假设有10000个请求过来, 根据实际情况,可以分配50或者100个线程来处理。不像之前的阻塞IO那样,非得分配10000个
  7. HTTP2.0使用了多路复用的技术,做到同一个连接并发处理多个请求,而且并发请求的数量比HTTP1.1大了好几个数量级

案例说明NIO Buff

public class BasicBuffer {
    public static void main(String[] args) {
        // 举例说明Buffer的使用(简单说明)
        // 创建一个Buffer, 大小为5,即可以存放5个int
        IntBuffer buffer = IntBuffer.allocate(5);
        // 向buffer存放数据 <code>buffer.put(5) | buffer.put(2) | buffer.put(0)</code>
        for (int i = 0; i < buffer.capacity(); i++) {
            buffer.put(i * 2);
        }
        // 如何从buffer读取数据
        // 将 buffer 转换, 读写切换(!!!)
        buffer.flip();
        while (buffer.hasRemaining()) {
            System.out.println(buffer.get());
        }
    }
}

NIO 和 BIO 的比较

  1. BIO 以流的方式处理数据,而 NIO 以块的方式处理数据,块 I/O 的效率比流 I/O 高很多
  2. BIO 是阻塞的,NIO 则是非阻塞的
  3. BIO基于字节流和字符流进行操作,而 NIO 基于 Channel(通道)和 Buffer(缓冲区)进 行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。 Selector(选择器)用于监听多个通道的事件(比如:连接请求,数据到达等),因此使用单个线程就可以监听多个客户端通道

NIO 三大核心原理示意

graph TD; Thread --- Selector Selector --> Channel_1 --> Buffer_1 --> 程序_1 Selector --> Channel_2 --> Buffer_2 --> 程序_2 Selector --> Channel_3 --> Buffer_3 --> 程序_3

缓冲区(Buffer)

基本介绍

缓冲区(Buffer):缓冲区本质上是一个可以读写数据的内存块,可以理解成是一个 容器对象(含数组),该对象提供了一组方法,可以更轻松地使用内存块,,缓冲区对 象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况。Channel 提供从文件、 网络读取数据的渠道,但是读取或写入的数据都必须经由 Buffer

flowchart LR; NIO程序 <-->|data| 缓冲区 <-->|Channel| 文件[/文件/]

标签:Netty,NIO,BIO,Telnet,线程,客户端
来源: https://www.cnblogs.com/Zzzyyw/p/16182198.html