JAVA网络IO之NIO/BIO

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 本文介绍了Java网络编程的基础与历史演进,重点阐述了IO和Socket的概念。Java的IO分为设备和接口两部分,通过流、字节、字符等方式实现与外部的交互。

前言

  • Java的IO ,就是 输入/输出 (Input/Output),分为IO设备IO接口两个部分。
  • 常听输入输出流、输入输出字节、输入输出字符...Java与外部交互都可转化为流、字节字符进而封装为对象、进而方便程序员编程。
  • Java与网络交互就是网络IO、Java与磁盘交互就是磁盘IO。
  • Java网络IO是什么?用系统调用read从socket中读取数据。

一、Java网络编程基础

1、Socket

  • 网络上两个程序通过一个双向通讯连接实现数据的交换。
  • 这个双向通讯链路两端端点称为Socket,通常用来实现连接用。
  • 一个socket必须由IP+端口号port组成。
  • socket是个支持TCP/IP等协议的编程界面。

2、Java中的Socket

  • Java中socket主要是基于TCP/IP。
  • Java的java.net包中提供了socket(客户端)和serverSocket(服务端)。
  • Java中socket使用方法:
  1. 创建socket
  2. 打开连接到socket的输入/输出流
  3. 按照协议对socket的读取/写入
  4. 关闭socket  

3、Java中的IO  

Socket建设完毕,网络数据的传输通路没问题,那么数据,该怎么读取呢?

  • 关于读取JDK 1.0就有读取的包提供——java.io
  • Java 的 I/O输入输出系统解决的问题是:
  • 各种I/O源端和与之通信的接收端(文件/控制台/网络链接...)
  • 多种不同方式进行通信:顺序/随机/缓冲/二进制/按字符、按字、按行...
  • Java的“流”屏蔽了实现I/O设备中处理数据的细节

二、Java网络IO的历史演进

与其说是Java的IO历史,不如说是操作系统的网络IO历史

  • read是操作系统的方法,java只是调用这个接口。

以Linux为例:

第一阶段:调用read读取socket数据,有数据则读取,没数据则等待。

第二阶段:调用read读取socket数据,有数据则读取,没数据则返回-1,

                 errno设置为EAGAIN。

第三阶段:监听socket,有数据则通知。

第一阶段  Java网络编程情况

第一阶段:调用read读取socket数据,有数据则读取,没数据则等待。

在此前提下如何设计Java程序呢:

  • 线程若阻塞在read中,那么为了程序继续向下执行,就只能开启新的线程。

1、为代码编程示例

A) ServerSocket服务端通道建设伪代码示例

java

代码解读

复制代码

public class ServerSocketDemo {
    public static void main(String[] args) throws IOException {
        // 创建一个线程池
        ExecutorService threadPool = Executors.newCachedThreadPool();
        // 创建ServerSocket
        ServerSocket serverSocket = new ServerSocket(3000);
        // 死循环(监听serverSocket),等待客户端连接
        while (true) {
            Socket clientSocket = serverSocket.accept(); 
            // 创建一个线程用于处理通信数据
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    //handler方法为处理数据的方法:见下文 B)IO处理数据为代码示例
                    handler(clientSocket); 
                }
            });
        }
    }
}

B) IO处理数据为代码示例

arduino

代码解读

复制代码

private static void handler(Socket clientSocket) throws IOException {
        //接收数据,没有数据可读时就阻塞
        int read = clientSocket.getInputStream().read(new byte[1024]);
        if (read != -1) {
            //处理数据的业务方法
        }
}

2、弊端

read() 操作卡住的(阻塞),如果单线程很可能卡死住,如果多线程呢(如上),可以解决卡住问题,但是带来哪些影响?

  • 每个线程处理一个网络请求,1000个并发请求就开1000个线程。
  • 每个线程占用一定内存做为线程栈,每个1M,1000个就是1G。
  • 都没数据时候,这1000个线程闲着。
  • 如果用线程池,就限制了并发的数量。

3、总结

这种调用read读取socket数据,有数据则读取,没数据则等待。称之为BIO,即阻塞IO。


第二阶段  Java网络编程情况

第二阶段:调用read读取socket数据,有数据则读取,没数据则返回-1,

errno设置为EAGAIN。


Java为此做了哪些改变呢?NIO模型登场!

  • NIO新增缓冲区(Buffer)。
  • 面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。
  • NIO数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。增加了处理的灵活性。
  • buffer 底层就是个数组。
  • NIO新增双向通道(Channel)。
  • 一个单独的线程现在可以管理多个输入和输出通道(channel)。
  • NIO新增多路复用器(Selector)。
  • 将Channel注册在Selector上
  • Selector可以监听Channel的四种状态(Connect、Accept、Read、Write)
  • 监听到某一Channel的某个状态时,才对Channel进行相应的操作

对应的操作系统是什么呢?

  • NIO底层在JDK1.4版本是用linux的内核函数select()或poll()来实现。
  • NIO底层在JDK1.5版本是用linux的内核函数epoll()基于事件响应机制来优化NIO。

I/O多路复用底层主要用的Linux 内核·函数(select,poll,epoll)来实现

windows不支持epoll实现,windows底层是基于winsock2的select函数实现的(不开源)

1、NIO如何使用? 客户端代码示例

scss

代码解读

复制代码

public class NioServer {

    public static void main(String[] args) throws IOException, InterruptedException {

        // 创建NIO ServerSocketChannel
        ServerSocketChannel serverSocket = ServerSocketChannel.open();
        serverSocket.socket().bind(new InetSocketAddress(9000));
        // 设置ServerSocketChannel为非阻塞
        serverSocket.configureBlocking(false);
        // 打开Selector处理Channel,即创建epoll
        Selector selector = Selector.open();
        // 把ServerSocketChannel注册到selector上,并且selector对客户端accept连接操作感兴趣
        serverSocket.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
            // 阻塞等待需要处理的事件发生
            selector.select();
            // 获取selector中注册的全部事件的 SelectionKey 实例
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            // 遍历SelectionKey对事件进行处理
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                // 如果是OP_ACCEPT事件,则进行连接获取和事件注册
                if (key.isAcceptable()) {
                    ServerSocketChannel server = (ServerSocketChannel) key.channel();
                    SocketChannel socketChannel = server.accept();
                    socketChannel.configureBlocking(false);
                    // 这里只注册了读事件,如果需要给客户端发送数据可以注册写事件
                    socketChannel.register(selector, SelectionKey.OP_READ);
                    //客户端连接成功
                // 如果是OP_READ事件,则进行读取和打印
                } else if (key.isReadable()) {  
                    SocketChannel socketChannel = (SocketChannel) key.channel();
                    ByteBuffer byteBuffer = ByteBuffer.allocate(128);
                    int len = socketChannel.read(byteBuffer);
                    // 如果有数据
                    if (len > 0) {
                        //接收数据
                        String data = new String(byteBuffer.array());
                        //执行处理数据的业务逻辑
                    // 如果客户端断开连接,关闭Socket
                    } else if (len == -1) { 
                        //客户端断开连接,关闭socket
                        socketChannel.close();
                    }
                }
                //从事件集合里删除本次处理的key,防止下次select重复处理
                iterator.remove();
            }
        }
    }
}

2、总结

NIO整个调用流程就是

  • Java调用了操作系统的内核函数来创建Socket,获取到Socket的文件描述符。
  • Java再创建一个Selector对象,对应操作系统的Epoll描述符,将获取到的Socket连接的文件描述符的事件绑定到Selector对应的Epoll文件描述符上。

事件的异步通知

  • 实现了使用一条线程
  • 不需要太多的无效的遍历
  • 将事件处理交给了操作系统内核(操作系统中断程序实现)
  • 大大提高了效率


转载来源:https://juejin.cn/post/7107260376181391390

相关文章
|
7天前
|
存储 网络协议 安全
Java网络编程,多线程,IO流综合小项目一一ChatBoxes
**项目介绍**:本项目实现了一个基于TCP协议的C/S架构控制台聊天室,支持局域网内多客户端同时聊天。用户需注册并登录,用户名唯一,密码格式为字母开头加纯数字。登录后可实时聊天,服务端负责验证用户信息并转发消息。 **项目亮点**: - **C/S架构**:客户端与服务端通过TCP连接通信。 - **多线程**:采用多线程处理多个客户端的并发请求,确保实时交互。 - **IO流**:使用BufferedReader和BufferedWriter进行数据传输,确保高效稳定的通信。 - **线程安全**:通过同步代码块和锁机制保证共享数据的安全性。
58 23
|
3月前
|
监控 Java API
探索Java NIO:究竟在哪些领域能大显身手?揭秘原理、应用场景与官方示例代码
Java NIO(New IO)自Java SE 1.4引入,提供比传统IO更高效、灵活的操作,支持非阻塞IO和选择器特性,适用于高并发、高吞吐量场景。NIO的核心概念包括通道(Channel)、缓冲区(Buffer)和选择器(Selector),能实现多路复用和异步操作。其应用场景涵盖网络通信、文件操作、进程间通信及数据库操作等。NIO的优势在于提高并发性和性能,简化编程;但学习成本较高,且与传统IO存在不兼容性。尽管如此,NIO在构建高性能框架如Netty、Mina和Jetty中仍广泛应用。
74 3
|
3月前
|
存储 监控 Java
Java的NIO体系
通过本文的介绍,希望您能够深入理解Java NIO体系的核心组件、工作原理及其在高性能应用中的实际应用,并能够在实际开发中灵活运用这些知识,构建高效的Java应用程序。
78 5
|
4月前
|
消息中间件 监控 Java
BIO、NIO、AIO在不同场景下的应用对比
BIO(阻塞I/O)、NIO(非阻塞I/O)和AIO(异步I/O)是Java中处理I/O操作的三种模式。BIO适用于连接数少且稳定的场景;NIO通过非阻塞模式提高并发处理能力,适合高并发场景;AIO则完全异步,适合需要高效、低延迟的I/O操作场景。
459 4
|
4月前
|
Java
BIO、NIO、AIO 有什么区别
BIO(阻塞I/O)模型中,服务器实现模式为一个连接一个线程;NIO(非阻塞I/O)使用单线程或少量线程处理多个请求;AIO(异步I/O)则是在NIO基础上进一步优化,采用事件通知机制,提高并发处理能力。
156 6
|
7月前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
8月前
|
Java 大数据
解析Java中的NIO与传统IO的区别与应用
解析Java中的NIO与传统IO的区别与应用
|
6月前
|
Java 大数据 API
Java 流(Stream)、文件(File)和IO的区别
Java中的流(Stream)、文件(File)和输入/输出(I/O)是处理数据的关键概念。`File`类用于基本文件操作,如创建、删除和检查文件;流则提供了数据读写的抽象机制,适用于文件、内存和网络等多种数据源;I/O涵盖更广泛的输入输出操作,包括文件I/O、网络通信等,并支持异常处理和缓冲等功能。实际开发中,这三者常结合使用,以实现高效的数据处理。例如,`File`用于管理文件路径,`Stream`用于读写数据,I/O则处理复杂的输入输出需求。
364 12
|
7月前
|
Java 数据处理
Java IO 接口(Input)究竟隐藏着怎样的神秘用法?快来一探究竟,解锁高效编程新境界!
【8月更文挑战第22天】Java的输入输出(IO)操作至关重要,它支持从多种来源读取数据,如文件、网络等。常用输入流包括`FileInputStream`,适用于按字节读取文件;结合`BufferedInputStream`可提升读取效率。此外,通过`Socket`和相关输入流,还能实现网络数据读取。合理选用这些流能有效支持程序的数据处理需求。
92 2
|
7月前
|
XML 存储 JSON
【IO面试题 六】、 除了Java自带的序列化之外,你还了解哪些序列化工具?
除了Java自带的序列化,常见的序列化工具还包括JSON(如jackson、gson、fastjson)、Protobuf、Thrift和Avro,各具特点,适用于不同的应用场景和性能需求。