Con đường NIO 2: Phân tích mã nguồn NIO trong Java

  1. IO Multiplexing

Trong BIO, mỗi yêu cầu từ客户端 đều được xử lý bởi một thread riêng, dẫn đến việc tăng số lượng thread và gây ra các vấn đề về hiệu suất. Vì vậy,(IO multiplexing) đã ra đời để cho phép một thread xử lý nhiều kết nối同時に. Cơ chế này trong Java NIO được thực hiện thông qua các API multiplexing của hệ điều hành như select, poll, và epoll.

1.1 Cơ chế select

Select sử dụng fd_set để giám sát các file descriptor. Mỗi lần gọi select, hệ điều hành sẽ kiểm tra các descriptor trong fd_set và trả về các descriptor đã sẵn sàng. Tuy nhiên, select có một số nhược điểm:

  • Phải sao chép fd_set từ không gian người dùng sang không gian nhân mỗi lần gọi
  • Hiệu suất kém khi số descriptor lớn
  • Giới hạn số descriptor (1024 hoặc 2048)
Cơ chế select poll epoll
Cách giám sát Linear scan Linear scan Event-driven
Số descriptor tối đa Giới hạn Không giới hạn Không giới hạn

1.2 Cơ chế poll

Poll cải thiện hơn select bằng cách không giới hạn số descriptor và sử dụng cấu trúc链表. Tuy nhiên, nó vẫn gặp các vấn đề tương tự như select khi các descriptor không hoạt động.

1.3 Cơ chế epoll

Epoll là một cải tiến lớn hơn. Nó sử dụng các callback để thông báo khi một descriptor sẵn sàng, giảm đáng kể số lần kiểm tra. Epoll cũng hỗ trợ cả level-triggered và edge-triggered modes.

  1. Lý thuyết NIO

BIO sử dụng thread同步 và bị tắc khi chờ IO. NIO, ngược lại, cho phép một thread giám sát nhiều kết nối và chỉ xử lý khi có sự kiện xảy ra.

2.1 Three Core Modules

  • Channel: Quản lý giao tiếp giữa客户端 và server
  • Buffer: Lưu trữ dữ liệu trung gian
  • Selector: Giám sát các sự kiện trên các channel

2.2 Ví dụ ứng dụng NIO

Server NIO:

public class NioServer {
    private static ServerSocketChannel serverSocketChannel;
    private static Selector selector;

    public static void main(String[] args) {
        try {
            init();
            start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void init() throws IOException {
        serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.bind(new InetSocketAddress(8000));
        selector = Selector.open();
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    }

    private static void start() throws IOException {
        while (true) {
            selector.select();
            for (SelectionKey key : selector.selectedKeys()) {
                if (key.isAcceptable()) {
                    SocketChannel channel = serverSocketChannel.accept();
                    channel.configureBlocking(false);
                    channel.register(selector, SelectionKey.OP_READ);
                }
                selector.selectedKeys().remove(key);
            }
        }
    }
}

Client NIO:

public class NioClient {
    private SocketChannel channel;

    public static void main(String[] args) throws Exception {
        NioClient client = new NioClient();
        client.connect();
        client.send("Hello Server");
        client.receive();
    }

    private void connect() throws Exception {
        channel = SocketChannel.open(new InetSocketAddress("localhost",  theo dõi các sự kiện của các channel và chỉ xử lý khi có sự kiện xảy ra. Điều này giúp giảm thiểu việc tạo và hủy các thread, tăng hiệu suất xử lý.</p>
  1. Phân tích mã nguồn NIO

3.1 Khởi tạo server

Server được khởi tạo bằng cách tạo ServerSocketChannel và Selector. Mỗi channel được đăng ký với Selector để监听 các sự kiện.

3.2 selector.select() và các sự kiện

Phương thức select() gây tắc thread cho đến khi có sự kiện. Các SelectionKey được trả về và xử lý từng phần tử.

3.3 buffer và các phương thức

Buffer có các thuộc tính chính:

  • capacity: Kích thước tối đa
  • position: Vị trí hiện tại
  • limit: Giới hạn đọc hoặc viết
  • mark: Vị trí được đánh dấu

Các phương thức quan trọng:

  • put(): Thêm dữ liệu vào buffer
  • flip(): Chuyển sang chế độ đọc
  • compact(): Compress dữ liệu chưa đọc
  1. Buffer và Channel

Channel là các đối tượng thực hiện giao tiếp IO. Buffer đóng vai trò trung gian lưu trữ dữ liệu. Cả hai đều là thành phần cốt lõi của NIO.

Lưu ý: Phải xóa các SelectionKey đã xử lý để tránh việc xử lý lại trong lần select tiếp theo.

Thẻ: NIO Java selector buffer Channel

Đăng vào ngày 30 tháng 6 lúc 05:13