Compartilhamento de tecnologia

Alfabetização completa no modelo de rede Java

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

Visão geral

Descreve o conhecimento básico do NIO no nível ava, para revisão básica

1. Visão geral do NIO

Em Java, NIO (E/S sem bloqueio ou Nova E/S) é um novo conjunto de API de operação de entrada/saída introduzido no Java SE 1.4 e versões subsequentes.

Em comparação com o modelo IO tradicional, oferece maior eficiência e melhores capacidades de processamento simultâneo. O principal recurso do NIO é seu recurso sem bloqueio, que permite que um único thread gerencie vários canais de E/S, melhorando bastante o desempenho do aplicativo em cenários de alta simultaneidade.

O cenário de uso do Java NIO é particularmente adequado para situações em que um grande número de conexões simultâneas precisa ser processado. Por exemplo, em um servidor de rede, um thread pode gerenciar milhares de conexões de clientes sem a necessidade de alocar um thread independente para cada conexão. . Pode reduzir significativamente o consumo de recursos do sistema e melhorar as capacidades de processamento.

2. Três componentes principais do NIO em Java

1. Canais

Canal é o meio de fluxo de dados em Java NIO. É bidirecional e pode ser usado para ler ou gravar dados. A principal vantagem dos canais é que eles não são bloqueadores, o que significa que um thread pode gerenciar vários canais. Quando nenhum evento ocorre em um canal, o thread não será bloqueado e poderá, em vez disso, lidar com outras tarefas. Os principais tipos de canais incluem:

  • FileChannel: Usado para operações de leitura e gravação de arquivo. Pode ser usado para gravar dados do buffer no arquivo ou para ler dados do arquivo no buffer.
  • SocketChannel: Usado para conexões TCP em comunicações de rede e pode ser usado para ler e gravar dados.
  • ServerSocketChannel: Usado para aceitar novas conexões SocketChannel, semelhantes ao ServerSocket tradicional.
  • DatagramChannel: Usado para comunicação UDP, que pode enviar e receber datagramas.

2. Tampões

Buffer é um objeto usado para armazenar dados em NIO. É uma matriz de bytes que pode gravar e ler dados dele. Um buffer tem uma capacidade específica e duas propriedades importantes: posição e limite.

  • Capacidade: o número máximo de elementos que o buffer pode armazenar.
  • Posição: O índice do elemento em operação no momento, que muda quando os dados são lidos ou gravados.
  • Limite: No modo leitura, o valor máximo que a posição pode atingir no modo gravação, a posição não pode ultrapassar o valor;

Os principais tipos de buffers sãoByteBufferCharBufferShortBufferIntBufferLongBufferFloatBuffereDoubleBuffer, cada tipo corresponde a um tipo de dados primitivo.

3. Seletores

Um seletor é um multiplexador em NIO que permite que um único thread monitore eventos de vários canais, como leitura, gravação, conexão e recebimento de eventos. O seletor notifica a aplicação quando um dos canais está pronto para operações de E/S. O uso de seletores melhora muito a capacidade de processamento simultâneo de aplicativos de rede porque não há necessidade de criar um thread para cada conexão.

Os principais métodos de seletores incluem:

  • select(): Bloqueia até que pelo menos um canal esteja pronto para operações de E/S.
  • selectedKeys(): Retorna um Conjunto contendo os objetos SelectionKey de todos os canais preparados.
  • wakeup(): Interrupção bloqueadaselect()transferir.

SelectionKeyÉ a associação entre seletores e canais. Representa o status de registro de um canal no seletor, incluindo canais, seletores, coleções de eventos interessados ​​e coleções de eventos prontos.

3. Programação de rede

1. Bloqueio de E/S

​ Este é o modelo de E/S mais tradicional em Java, as APIs Socket e ServerSocket tradicionais são baseadas no bloqueio de E/S. Neste modo, quando um thread inicia uma operação de leitura ou gravação, o thread é bloqueado até que a operação de E/S seja concluída. Se não houver dados para ler para a operação de leitura ou se a operação de gravação não puder ser concluída imediatamente, o thread aguardará até que a operação seja concluída.

Características:

  • Modelo de programação simples: Fácil de entender e implementar.
  • Ocupação de recursos: cada conexão requer um thread independente, resultando em um número limitado de threads e alto consumo de recursos.
  • Limitações de desempenho: em cenários de alta simultaneidade, a alternância de threads e de contexto são caras e podem facilmente se tornar gargalos.

2. E/S sem bloqueio

A E/S sem bloqueio faz parte da estrutura Java NIO (New I/O), que permite que threads iniciem operações de leitura e gravação sem serem bloqueadas. Se não houver dados para ler para uma operação de leitura ou se a operação de gravação não puder ser concluída imediatamente, o thread não será suspenso e poderá continuar a executar outras tarefas.

Características:

  • flexibilidade: Threads podem lidar com mais conexões porque não ficam bloqueadas aguardando operações de E/S.
  • Maior complexidade: O programador precisa verificar explicitamente se a operação foi concluída, o que aumenta a dificuldade de programação.
  • Melhorias de desempenho: em cenários de alta simultaneidade, a sobrecarga causada pela alternância de threads pode ser significativamente reduzida.

3. Multiplexação

Multiplexação é monitorar vários descritores de arquivo ao mesmo tempo por meio de um thread e operar apenas em um descritor quando ele estiver pronto (geralmente significa que os dados são legíveis ou o buffer de gravação é gravável). Seletores são usados ​​em Java para implementar multiplexação.

Características:

  • Alta simultaneidade: Um thread pode lidar com milhares de conexões, melhorando muito a taxa de transferência do servidor.
  • Eficiente: Somente quando uma operação de E/S estiver pronta, o thread será despertado para evitar trocas desnecessárias de thread.
  • Economia de recursos: Em comparação com o bloqueio de E/S, os servidores no modelo de multiplexação podem utilizar os recursos do sistema com mais eficiência.

Perceber:

Em aplicações práticas, a E/S sem bloqueio é frequentemente usada em conjunto com a multiplexação. Por exemplo, um servidor pode usar um Seletor para monitorar vários SocketChannels. Quando um SocketChannel possui dados que podem ser lidos ou gravados, o Seletor notificará o servidor e o servidor processará esse SocketChannel específico de maneira sem bloqueio.

4.NIO VS BIO

4.1 fluxo vs canal

Em Java, Stream e Channel são duas maneiras diferentes de processar fluxos de dados. Eles pertencem à biblioteca IO padrão e à biblioteca NIO do Java, respectivamente. Aqui está uma comparação detalhada dos dois conceitos:

Fluxo

Stream faz parte do modelo IO (IO de bloqueio) padrão do Java, que fornece uma maneira de ler e gravar dados sequencialmente. Stream é dividido em dois tipos: InputStream e OutputStream, que são usados ​​para ler e gravar dados respectivamente.Esses fluxos podem ser fluxos de bytes (comoInputStream, OutputStream) ou um fluxo de caracteres (comoReader, Writer)。

Características:

  • Obstrutivo: Por padrão, as operações de Stream são bloqueadas, ou seja, se não houver dados para leitura na operação de leitura ou se a operação de gravação não puder ser concluída imediatamente, o thread de chamada será bloqueado até que a operação seja concluída.
  • Unidirecionalidade: Cada Stream tem uma direção, seja somente leitura ou somente gravação.
  • desligamento automático: Depois de usar o Stream, geralmente você precisa chamá-loclose() método para liberar recursos.A partir do Java 7, a instrução try-with-resources pode fechar automaticamente a implementaçãoAutoCloseableRecursos de interface, incluindo Stream.

Canal

Channel faz parte do modelo Java NIO (New IO), que fornece um nível de abstração superior ao Stream, permitindo um processamento de dados mais eficiente. Os canais podem ser bidirecionais, o que significa que podem ser usados ​​para ler e gravar dados.Os principais tipos de canais incluemFileChannelSocketChannelServerSocketChanneleDatagramChannel

Características:

  • sem bloqueio :O canal pode ser configurado em modo bloqueado ou não bloqueado. No modo sem bloqueio, se uma operação de leitura não tiver dados para ler ou se uma operação de gravação não puder ser concluída imediatamente, a operação retornará imediatamente em vez de bloquear o thread.
  • Operações de buffer:As operações do canal são sempre executadas através do buffer (os dados do buffer são lidos do canal para o buffer ou gravados do buffer para o canal).
  • Multiplexação: O canal pode ser usado junto com o seletor para obter multiplexação, permitindo que um thread monitore as operações de E/S de vários canais e melhore os recursos de processamento simultâneo.

Resumir

  • Cena aplicável: Stream é mais adequado para processar conjuntos de dados menores ou operações simples de arquivos, enquanto Channel é mais adequado para processar grandes quantidades de dados, especialmente comunicação de rede e operações de arquivos grandes, porque fornece maior desempenho e melhores recursos de simultaneidade.
  • Complexidade de programação: A API do Stream é relativamente simples e fácil de usar, enquanto a API do Channel e NIO são mais complexas, mas fornecem funções mais poderosas e maior eficiência;
  • Gestão de recursos: Seja um Stream ou um Canal, os recursos devem ser gerenciados adequadamente e garantir que sejam fechados quando não forem mais necessários para evitar vazamentos de recursos.

4.2. Modelo IO

Em Java, os modelos de operação de E/S podem ser classificados com base nas duas dimensões de síncrono/assíncrono e bloqueador/não bloqueador.

4.2.1 E/S de bloqueio síncrono

definição : O bloqueio síncrono de E/S é o modelo de E/S mais tradicional. Quando um thread chama uma operação de E/S (como leitura ou gravação), o thread será bloqueado até que a operação seja concluída. Isso significa que o thread não pode executar nenhuma outra tarefa até que a operação seja concluída.

Características

  • Simples e fácil de usar, a lógica do código é clara.
  • Cada operação de E/S requer um thread exclusivo, o que não é adequado para cenários de alta simultaneidade e pode levar ao esgotamento dos recursos do thread.
  • Comumente usado emInputStreamOutputStreamSocketeServerSocketcena.

Exemplo:

Usar tradicionalInputStreameOutputStreamPara ler e gravar arquivos:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class SyncBlockingIOExample {
    public static void main(String[] args) {
        try (FileInputStream fis = new FileInputStream("input.txt");
             FileOutputStream fos = new FileOutputStream("output.txt")) {
            byte[] buffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = fis.read(buffer)) != -1) {
                fos.write(buffer, 0, bytesRead);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

4.2.2 E/S síncrona sem bloqueio

definição : Na E/S síncrona sem bloqueio, o thread não será bloqueado após chamar a operação de E/S. Se a operação não puder ser concluída imediatamente, o método retornará imediatamente, geralmente retornando um valor especial (como -1 ou 0) ou lançando uma exceção para indicar que a operação não foi concluída.

Características

  • O status da operação precisa ser pesquisado até que a operação seja concluída.
  • Maior utilização de threads porque os threads podem executar outras tarefas enquanto aguardam E/S.
  • A implementação é mais complexa e precisa lidar com pesquisas e verificações de status.
  • Baseado em JavaNIOChannelseBuffers, ligandoconfigureBlocking(false)Defina o canal para o modo sem bloqueio.

Exemplo:

Usando NIOFileChannelPara leitura e escrita sem bloqueio:

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.ByteBuffer;

public class SyncNonBlockingIOExample {
    public static void main(String[] args) {
        try (FileInputStream fis = new FileInputStream("input.txt");
             FileOutputStream fos = new FileOutputStream("output.txt");
             FileChannel inChannel = fis.getChannel();
             FileChannel outChannel = fos.getChannel()) {
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            inChannel.configureBlocking(false);
            while (inChannel.read(buffer) > 0) {
                buffer.flip();
                outChannel.write(buffer);
                buffer.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

4.2.3 E/S de multiplexação síncrona

definição: Uso do modelo de E/S multiplexado síncronoSelector(seletor) para monitorar múltiplosChannel evento.Quando o thread chamaSelector.select(), ele será bloqueado até que haja pelo menos umChannelOcorrem eventos (como legível, gravável, solicitação de conexão, etc.).

Características

  • Permite que um thread gerencie váriosChannel, melhora a simultaneidade.
  • passarSelectoreSelectionKeyMecanismo que pode lidar com eficiência com um grande número de conexões simultâneas.
  • Adequado para cenários de servidor de rede, como servidores web e servidores de chat.
  • Baseado na estrutura Java NIO.

Exemplo:

Usando NIOFileChannelPara leitura e escrita sem bloqueio:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Set;

public class SyncMultiplexingIOExample {
    public static void main(String[] args) throws IOException {
        try (Selector selector = Selector.open();
             ServerSocketChannel serverSocketChannel = ServerSocketChannel.open()) {
            serverSocketChannel.socket().bind(new InetSocketAddress(8080));
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            while (true) {
                if (selector.select() > 0) {
                    Set<SelectionKey> keys = selector.selectedKeys();
                    for (SelectionKey key : keys) {
                        if (key.isAcceptable()) {
                            ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                            SocketChannel clientChannel = ssc.accept();
                            clientChannel.configureBlocking(false);
                            clientChannel.register(selector, SelectionKey.OP_READ);
                        }
                    }
                    keys.clear();
                }
            }
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

4.2.4 E/S assíncrona sem bloqueio

definição : No modelo de E/S assíncrono sem bloqueio, o thread retorna imediatamente após iniciar a operação de E/S sem esperar que a operação seja concluída. Quando a operação for concluída, o thread será notificado de forma assíncrona por meio de uma função de retorno de chamada ou notificação de evento.

Características

  • O modelo de E/S mais eficiente, os threads não são bloqueados e podem executar outras tarefas imediatamente.
  • passarAsynchronousChannelInterface e sua implementação de subclasse, comoAsynchronousFileChanneleAsynchronousSocketChannel
  • Adequado para cenários de alta simultaneidade e alto desempenho, como processamento de big data e servidores de rede de alta carga.
  • Java 7 introduziu o modelo de E/S assíncrona sem bloqueio.

Exemplo:

Usando NIOAsynchronousFileChannelExecute leitura e gravação assíncrona de arquivos:

import java.io.IOException;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.ByteBuffer;
import java.util.concurrent.CountDownLatch;

public class AsyncNonBlockingIOExample {
    public static void main(String[] args) throws IOException, InterruptedException {
        AsynchronousFileChannel inChannel = AsynchronousFileChannel.open(Paths.get("input.txt"), StandardOpenOption.READ);
        AsynchronousFileChannel outChannel = AsynchronousFileChannel.open(Paths.get("output.txt"), StandardOpenOption.WRITE);
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        CountDownLatch latch = new CountDownLatch(1);
        inChannel.read(buffer, 0, buffer, 0, (channel, result) -> {
            buffer.flip();
            outChannel.write(buffer, 0, buffer, 0, null);
            latch.countDown();
        });
        latch.await();
        inChannel.close();
        outChannel.close();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

Nota: O modelo assíncrono requer suporte do sistema operacional subjacente (Kernel)

  • Os sistemas Windows implementam IO assíncrono verdadeiro por meio de IOCP
  • A E/S assíncrona no sistema Linux foi introduzida na versão 2.6, mas sua implementação subjacente ainda usa multiplexação para simular E/S assíncrona e não há vantagem de desempenho.

4.2.5 Bloqueio Assíncrono de E/S

definição : Em teoria, esse modelo não existe, pois "assíncrono" significa que a operação é executada em segundo plano e o thread não é bloqueado. Portanto, o bloqueio assíncrono de E/S é um conceito contraditório e não aparecerá na prática.

4.2.6 Resumo

A escolha do modelo de E/S apropriado é fundamental para o desempenho e o gerenciamento de recursos. Em cenários de alta simultaneidade, modelos de E/S síncrona sem bloqueio ou de E/S síncrona multiplexada são escolhas comuns. Em cenários que exigem desempenho e velocidade de resposta extremos, o modelo de E/S assíncrona sem bloqueio é a primeira escolha.

4.3 Cópia zero

Conceito de cópia zero

A tecnologia de cópia zero significa que durante o processo de transmissão de dados de um lugar para outro, os dados não precisam ser copiados entre o espaço do usuário e o espaço do kernel, ou pelo menos reduz o número dessas cópias, melhorando assim a eficiência do sistema. Nas operações tradicionais de E/S, quando os dados são lidos da rede ou do disco, eles são primeiro copiados para o buffer no espaço do kernel e depois copiados do espaço do kernel para o buffer no espaço do usuário. Pelo contrário, na cópia zero, os dados podem ser processados ​​diretamente no espaço do kernel ou transferidos diretamente do espaço do kernel para o dispositivo de rede, reduzindo assim a operação de cópia da CPU, reduzindo a sobrecarga do sistema e melhorando a eficiência da transmissão de dados.

Fonte de cópia zero

O conceito de cópia zero apareceu pela primeira vez no projeto de sistemas operacionais, com o objetivo de resolver o gargalo de desempenho causado pela cópia de dados em operações tradicionais de E/S. Nos primeiros sistemas de computador, todas as operações de E/S exigiam múltiplas cópias de dados no espaço do usuário e no espaço do kernel. Isso gradualmente se tornou um gargalo de desempenho depois que redes de alta velocidade e discos de grande capacidade se tornaram comuns.

Principais pontos técnicos

  • E/S direta: permite que os aplicativos acessem dispositivos de disco diretamente, ignorando o mecanismo de cache do sistema de arquivos.
  • Mapeamento de memória (MMAP): Mapeie arquivos para a memória para que os dados do arquivo possam ser acessados ​​como memória, reduzindo a cópia.
  • Enviar arquivo: chamada de sistema Linux que pode transferir dados diretamente de um descritor de arquivo para outro, evitando cópias intermediárias.
  • DMA (Acesso Direto à Memória): Tecnologia em nível de hardware que permite a transferência de dados diretamente entre o dispositivo e a memória sem envolvimento da CPU.

Implementação em Java:

Java oferece suporte à tecnologia de cópia zero por meio da estrutura NIO (New I/O). NIO introduzidoFileChanneleSocketChannel e outras classes, que fornecem operações de E/S mais eficientes. Especificamente,FileChannel.transferTo()eFileChannel.transferFrom()Os métodos podem transferir dados diretamente de um canal de arquivo para um canal de soquete ou vice-versa, sem carregar os dados em um buffer, obtendo assim zero cópia.

Por exemplo, suponha que você precise enviar o conteúdo de um arquivo grande para a rede. A abordagem tradicional é primeiro ler o conteúdo do arquivo em um buffer e depois gravá-lo na rede. Isto envolve duas operações de cópia: uma do disco para o buffer e outra do buffer para a rede.Enquanto estiver usandotransferTo()Ao utilizar este método, os dados podem ser transferidos diretamente do disco para a rede sem a necessidade de um buffer intermediário, o que reduz o número de cópias e atinge zero cópia.

Um exemplo usando ByteBuffer:

ByteBuffere outroBufferclasse (comoCharBufferShortBufferetc.) fornecem buffers que podem ser preenchidos ou esvaziados sem envolver diretamente uma cópia entre o espaço do usuário e o espaço do kernel.ByteBufferPode ser usado direta ou indiretamente na tecnologia de cópia zero:

  1. Buffer diretoByteBuffer.allocateDirect(size)CriadaByteBuffer As instâncias são mapeadas diretamente para a memória física, ignorando o heap Java.Quando tal buffer é comparado comFileChannelouSocketChannel Quando usados ​​em conjunto, os dados podem ser transferidos diretamente entre a memória física e os dispositivos de hardware sem a necessidade de cópias adicionais por meio do heap Java. Isso permite cópia zero verdadeira em algumas plataformas.
  2. usarFileChanneldetransferTo()etransferFrom(): Esses métodos permitem que os dados sejam armazenados diretamente emFileChanneleSocketChanneltransmitido entreByteBuffer como intermediário. Isso significa que os dados podem ser transferidos diretamente do disco para a rede ou vice-versa, sem a necessidade de serem copiados entre o espaço do usuário e o espaço do kernel.
  3. usarByteBufferdewrap()métodowrap()método permite agrupar uma matriz de bytes existente ou outro buffer em umByteBuffer , portanto não há necessidade de copiar os dados para um novo buffer. Isso é útil para evitar cópias desnecessárias de dados.
  4. usarByteBufferdeslice()métodoslice() O método cria uma visualização do buffer sem copiar os dados subjacentes.Isto significa que um grandeByteBufferDivida em vários buffers menores sem copiar dados.

Use ByteBuffer com FileChannel:

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;

public class ZeroCopyExample {
    public static void main(String[] args) {
        Path path = Path.of("example.txt");
        try (FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.READ)) {
            ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
            long transferred = fileChannel.transferTo(0, fileChannel.size(), System.out);
            System.out.println("Transferred bytes: " + transferred);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

Perceber:

Devemos observar que, na verdade, não existe uma cópia zero completa. A cópia zero mencionada aqui é apenas para o nosso aplicativo em si, que não possui uma cópia no nível do usuário. Mas mesmo no nível do usuário, é impossível não ter nenhuma operação de cópia, mas sim reduzir ao máximo as cópias. Portanto, podemos entender que o termo cópia zero na verdade se refere à tecnologia de redução do número de cópias de dados. e isso não significa que não haja operação de cópia verdadeira.