Netty实战与源码剖析(一)——浅谈NIO编程

1 前言很久之前就想写与Netty相关的博客了,但由于个人时间安排的问题一直拖到了现在,目前我在字节是写Go语言,也很久一段时间没有接触Java了,借助这个机会,重新温习Java高级编程的同时,也把Netty实战以及源码剖析分享给各位读者。2 Netty是什么?Netty is a NIO client server framework which enables quick and easy development of network applications such as protocol
摘要由CSDN通过智能技术生成

1 前言

很久之前就想写与Netty相关的博客了,但由于个人时间安排的问题一直拖到了现在,借助这个机会,重新温习Java高级编程的同时,也把Netty实战以及源码剖析分享给各位读者。

2 Netty是什么?

Netty is a NIO client server framework which enables quick and easy development of network applications such as protocol servers and clients. It greatly simplifies and streamlines network programming such as TCP and UDP socket server. ‘Quick and easy’ doesn’t mean that a resulting application will suffer from a maintainability or a performance issue. Netty has been designed carefully with the experiences earned from the implementation of a lot of protocols such as FTP, SMTP, HTTP, and various binary and text-based legacy protocols. As a result, Netty has succeeded to find a way to achieve ease of development, performance, stability, and flexibility without a compromise.

摘自官网,翻译过来就是:Netty是一个基于NIO的客户端-服务端框架,能过快速而简单地开发像客户端-服务端协议的网络应用。它极大地精简了 TCP 和 UDP 套接字服务器等网络编程。“快速而简单”并不意味着生成的应用程序会受到可维护性或性能问题的影响。Netty 是根据从许多协议(如 FTP、SMTP、HTTP 以及各种二进制和基于文本的遗留协议)的实现中获得的经验精心设计的。结果,Netty 成功地找到了一种方法,可以在不妥协的情况下实现易于开发、性能、稳定性和灵活性。

3 Java I/O模型简介

要说到网络通信,就离不开I/O模型,可以把I/O模型简单理解为使用什么通道进行数据的发送和接收。

Java共支持三种网络编程模型:BIO、NIO、AIO

  • BIO,同步阻塞IO,服务器实现模式为一个连接一个线程,即客户端有一个请求连接服务器时,服务器就会启动一个线程进行处理,可见当有多个客户端发出请求时,服务器需要启动等量的线程,而且当客户端没有响应时,线程也必须一直等待,长期下来需要大量的线程且线程利用率低,会造成浪费。

  • NIO,同步非阻塞,服务器用一个线程来处理多个请求,客户端发送的请求会注册到多路复用器(selector选择器)上,有I/O请求的客户端分配线程处理。

  • AIO,异步非阻塞,AIO引入了异步通道的概念,采用Proactor模式,简化程序编写,有效的请求才启动线程,特点是要先由操作系统完成后才通知服务的程序启动线程去处理,一般适用于连接数较多且连接时间较长的应用。客户端发送的请求先交给操作系统处理,OS处理后再通知线程

Netty其实就是基于Java的NIO的。接下来,我们通过编写代码来体验一下这三种IO模型吧

3.1 BIO代码实现

package com.Zhongger;

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author zhongmingyi
 * @date 2021/9/15 1:29 下午
 */
public class BIOServer {
   
    public static void main(String[] args) throws IOException {
   
        ExecutorService threadPool = Executors.newCachedThreadPool();
        ServerSocket serverSocket = new ServerSocket(8989);
        System.out.println("服务端已启动");
        while (true) {
   
            Socket socket = serverSocket.accept();
            threadPool.execute(new Runnable() {
   
                @Override
                public void run() {
   
                    handle(socket);
                }
            });
        }

    }

    public static void handle(Socket socket) {
   
        byte[] bytes = new byte[1024];
        try {
   
            InputStream inputStream = socket.getInputStream();
            int read = 0;
            while (true) {
   
                read = inputStream.read(bytes);
                if (read != -1) {
   
                    System.out.println("客户端发送给服务端的数据:" + new String(bytes, 0, read));
                } else {
   
                    break;
                }
            }
        } catch (IOException e) {
   
            e.printStackTrace();
        } finally {
   
            try {
   
                socket.close();
            } catch (IOException e) {
   
                e.printStackTrace();
            }
        }
    }
}

上述代码中:

  • 首先是服务器开启了一个ServerSocket,绑定在8989端口上,循环等待接受客户端的连接
  • 当客户端连接到了服务器后,ServerSocket.accept方法可以获取到客户端的Socket
  • 每当有一个客户端连接了服务器,线程池就会启动一个线程去处理Socket中的IO数据流,通过InputStream的read方法读取客户端发给服务器的数据,并输出打印;当InputStream没有数据了,最后将Socket关闭,该线程会回收到线程池中

可以看到,BIO模型里,服务器的实现模式为一个Socket连接对应一个线程。 BIO的知识点就介绍到这里,相信大家在【计算机网络】课程的学习中,肯定有接触过Socket编程,实现一个简易版的聊天工具。

4 Java NIO

4.1 基本介绍

JDK 1.4中的java.nio.*包中引入新的Java I/O库,NIO其实有两种解释:

  • New I/O:原因在于它相对于之前的I/O类库是新增的。
  • Non-block I/O:由于之前老的I/O类库是阻塞I/O,New I/O类库的目标就是要让Java支持非阻塞I/O,所以,更多的人喜欢称之为非阻塞I/O。

NIO有三个核心组件:

  • Buffer缓冲区 (相当于运载了货物的火车)
  • Channel管道(相当于轨道,负责运输Buffer)
  • Selector选择器(相当于车票,用来选择火车应该通过哪个Channel去运输)

NIO是面向块(缓冲区)的处理,数据读取到一个它稍后处理的缓冲区,需要时可以在缓冲区里前后移动,增加了在处理过程中的灵活性,使用NIO可以提供非阻塞式的高伸缩性网络。这使得一个线程可以在Buffer里有数据的时候去读取,没有可用数据时就可以去做其他事情,不会阻塞读;线程也可以写入一些数据到Buffer中,无需等待写入所有的数据,也可以去做别的事情,不会阻塞写。

4.2 三大核心组件的关系

三大核心组件的关系简单描述图如下:
在这里插入图片描述
如图所示:

  • 每个Thread对应一个Selector,每个Selector对应多个Channel
  • 每个Channel都会有一个对应的Buffer,Channel是双向的,可以返回底层操作系统的情况(比如Linux,通道就是双向的),这与BIO中流是单向的不同
  • Selector切换到哪个Channel进行处理是由事件Event决定的
  • Buffer是一个内存块,底层就是数组,可以写入数据,可以通过flip方法来切换成读取数据,Buffer也是双向的

4.3 Buffer缓冲区

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

Buffer是一个顶层的抽象类,它的子类有多种实现,常用的子类如下:

  • ByteBuffer:用于操作字节缓冲区
  • CharBuffer:用于操作字符缓冲区
  • ShortBuffer:用于操作短整型缓冲区
  • IntBuffer:用于操作整型缓冲区
  • LongBuffer:用于操作长整型缓冲区
  • FloatBuffer:用于操作浮点型缓冲区
  • DoubleBuffer:用于操作双精度浮点型缓冲区

上述缓冲区的管理方式基本上一致,都可以用类的allocate(int capacity) 方法去获取缓冲区对象。前面说到,Buffer是和数据打交道的载体,也就是读取缓冲区的数据或者把写数据到缓冲区中。所以,Buffer缓冲区的核心方法就是 put()方法和get()方法以及对应的重载方法、扩展方法等。

Buffer类中有以下四个属性:

// Invariants: mark <= position <= limit <= capacity
private int mark = -1; 
private int position = 0;
private int limit;
private int capacity;
  • Capacity:Buffer缓冲区能够容纳的数据元素的最大数量,容量在缓冲区创建时被设定,中途无法被修改。Capacity也就规定了Buffer中底层的数组的大小
  • Limit:Buffer缓冲区里的当前的终点,不能对缓冲区超过Limit的位置进行读写操作,Limit是可以被修改的
  • Position:Buffer缓冲区中下一个要被读或写的元素的索引位置,Position会自动由相应的 ge
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值