基于NIO建立长连接

引言

NIO非阻塞IO无论长连接和短连接都有很好的适配性,本篇文章将实战NIO实现长链接的能力

NIO是什么

NIO(New I/O)是 Java 中的一种非阻塞 I/O 模型,其底层实现主要涉及以下几个关键方面:

  • Selector(选择器)

    • 原理:Selector 是 NIO 实现非阻塞 I/O 的核心组件。它基于操作系统的底层 I/O 多路复用机制,如 Linux 中的 epoll、Windows 中的 IOCP 等。Selector 可以同时监控多个 Channel 的 I/O 事件,比如连接建立、数据可读、数据可写等。通过 Selector,线程可以在一个或多个 Channel 上等待 I/O 事件的发生,而不需要为每个 Channel 都创建一个单独的线程来阻塞等待 I/O 操作完成,从而大大提高了 I/O 的效率和系统的并发处理能力。
    • 实现方式:在 Java 中,Selector 的实现是通过操作系统提供的底层 I/O 多路复用接口来完成的。例如在 Linux 系统上,Java 的 Selector 底层会调用 epoll 相关的系统函数来注册和监听 Channel 上的 I/O 事件。当有 I/O 事件发生时,操作系统会将这些事件通知给 Selector,Selector 再将这些事件分发给相应的 Channel 进行处理。
  • Channel(通道)

    • 原理:Channel 是 NIO 中用于与 I/O 设备进行交互的通道,它类似于传统 I/O 中的流,但具有非阻塞和双向等特性。Channel 可以看作是对底层操作系统 I/O 通道的一种抽象封装,它提供了一种更高效、更灵活的方式来进行数据的读写操作。每个 Channel 都可以注册到一个 Selector 上,以便 Selector 对其 I/O 事件进行监控。
    • 实现方式:在底层,不同类型的 Channel 有不同的实现。例如,SocketChannel 用于网络 I/O,它的底层实现是基于操作系统的网络套接字接口。在 Java 中,通过 JNI(Java Native Interface)等技术调用操作系统的网络相关函数来实现 SocketChannel 的功能,如创建套接字、连接服务器、发送和接收数据等。而 FileChannel 用于文件 I/O,它的底层实现则是通过调用操作系统的文件操作相关系统函数来完成文件的读写、定位等操作。
  • Buffer(缓冲区)

    • 原理:Buffer 是 NIO 中用于存储数据的缓冲区,它是一个线性的字节数组或其他基本数据类型的数组。在 NIO 中,数据的读写操作都是通过 Buffer 来进行的。当从 Channel 读取数据时,数据会被读取到 Buffer 中;当向 Channel 写入数据时,数据也是从 Buffer 中获取的。Buffer 提供了一系列的方法来管理和操作缓冲区中的数据,如读取、写入、翻转、复位等操作,方便了数据的处理和传输。
    • 实现方式:在 Java 中,Buffer 是一个抽象类,具体的实现有 ByteBuffer、CharBuffer、IntBuffer 等多种类型,分别用于存储不同类型的数据。这些具体的 Buffer 实现类通常是基于 Java 的数组来实现的,通过维护一些指针和状态变量来管理缓冲区中的数据读写位置和操作状态。例如,ByteBuffer 可以通过调用操作系统的内存分配函数来分配一块连续的内存空间作为缓冲区,然后通过 Java 的直接内存访问技术来对这块内存进行读写操作,从而提高数据的读写效率。

长连接与短连接的概念

  • 短连接:客户端和服务器进行一次数据交互就断开连接,每次请求都需要重新建立连接,完成数据传输后关闭连接。
  • 长连接:客户端和服务器建立连接后,在一段时间内保持连接状态,多次数据交互可以复用这个连接,直到双方主动关闭或者因为网络异常等原因断开。

NIO 支持长连接的原因

  • 非阻塞特性 :NIO 的核心在于非阻塞 I/O 模型,利用 Selector 可以同时监控多个 Channel 的 I/O 事件。对于长连接场景,服务器可以使用一个线程通过 Selector 管理大量的长连接 Channel,当某个连接有数据可读或可写时才进行相应的处理,而不是像传统阻塞 I/O 那样为每个连接都分配一个线程并阻塞等待,这样大大提高了资源利用率,适合长连接这种长时间保持连接的场景。
  • 资源复用:在长连接中,建立连接是一个相对开销较大的操作。NIO 允许在已建立的连接上多次进行数据读写操作,复用已有的连接资源,避免了频繁建立和销毁连接带来的性能损耗。例如,在一个基于 NIO 的即时通讯服务器中,客户端和服务器建立长连接后,可以不断地通过这个连接发送和接收消息。

实战(Nio支持长连接服务器)

java 复制代码
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class NioLongConnectionServer {
    private static final int PORT = 8888;

    public static void main(String[] args) {
        try (Selector selector = Selector.open();
             ServerSocketChannel serverSocketChannel = ServerSocketChannel.open()) {

            serverSocketChannel.bind(new InetSocketAddress(PORT));
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            System.out.println("Server started, listening on port: " + PORT);

            while (true) {
                int readyChannels = selector.select();
                if (readyChannels == 0) continue;

                Set<SelectionKey> selectedKeys = selector.selectedKeys();
                Iterator<SelectionKey> keyIterator = selectedKeys.iterator();

                while (keyIterator.hasNext()) {
                    SelectionKey key = keyIterator.next();

                    if (key.isAcceptable()) {
                        ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
                        SocketChannel socketChannel = serverChannel.accept();
                        socketChannel.configureBlocking(false);
                        socketChannel.register(selector, SelectionKey.OP_READ);
                        System.out.println("New connection accepted: " + socketChannel.getRemoteAddress());
                    } else if (key.isReadable()) {
                        SocketChannel socketChannel = (SocketChannel) key.channel();
                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                        int bytesRead = socketChannel.read(buffer);
                        if (bytesRead > 0) {
                            buffer.flip();
                            byte[] data = new byte[buffer.remaining()];
                            buffer.get(data);
                            String message = new String(data);
                            System.out.println("Received message: " + message);
                        } else if (bytesRead == -1) {
                            // 客户端关闭连接
                            socketChannel.close();
                            System.out.println("Connection closed: " + socketChannel.getRemoteAddress());
                        }
                    }
                    keyIterator.remove();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
相关推荐
星如雨グッ!(๑•̀ㅂ•́)و✧2 天前
Java NIO全面详解
java·python·nio
熊文豪9 天前
【网络编程】Java高并发IO模型深度指南:BIO、NIO、AIO核心解析与实战选型
性能优化·高并发·nio·bio·aio·io模型·java网络编程
翻晒时光12 天前
探秘 Java IO 与 NIO:春招面试知识要点
java·面试·nio
我劝告了风*13 天前
NIO | 什么是Java中的NIO —— 结合业务场景理解 NIO (二)
java·nio
我劝告了风*13 天前
NIO | 什么是Java中的NIO —— 结合业务场景理解 NIO (一)
nio·i/o操作
李少兄14 天前
解决因JDK升级导致的`java.nio.file.NoSuchFileException`问题
java·python·nio
次元工程师!14 天前
JAVA-IO模型的理解(BIO、NIO)
java·笔记·学习·nio·bio·io模型
qq_3340602117 天前
IO模型与NIO基础二
nio
Yoyo25年秋招冲冲冲18 天前
【Java回顾】Day7 Java IO|分类(传输方式,数据操作)|零拷贝和NIO
java·开发语言·nio