O sistema a seguir fornece uma introdução aprofundada ao mecanismo de função e uso de Java Nio por meio de princípios, processos etc., vamos aprender.
Prefácio
Este artigo explica principalmente o mecanismo de IO em Java
Dividido em dois pedaços:
A primeira parte explica o mecanismo de IO em multithreading. A segunda parte explica como otimizar o desperdício de recursos da CPU sob o mecanismo de IO (novo IO)
Servidor de eco
Não preciso apresentar o mecanismo de soquete em um único thread. Se você não souber, pode verificar as informações em tantos tópicos. E se você usar soquetes?
Usamos o servidor Echo mais simples para ajudar todos a entender
Primeiro, vamos dar uma olhada no diagrama de fluxo de trabalho do servidor e do cliente em multi-threading:
Você pode ver que vários clientes enviam solicitações ao servidor ao mesmo tempo
O servidor fez uma medida para ativar vários threads para corresponder ao cliente correspondente.
E cada tópico completa seus pedidos de cliente sozinho
Depois que o princípio terminar, vamos ver como ele é implementado
Aqui escrevi um servidor simples
Use a tecnologia do pool de threads para criar threads (eu comentei sobre a função de código específica):
public class MyServer {private Static ExecorService ExecorService = Executores.NewCachedThreadpool (); // Crie um pool de thread Pool Private estático handlemsg implementa Runnable {// Depois que houver uma nova solicitação do cliente, crie este thread para processar o cliente de soquete; // Crie um cliente public handlemsg (cliente de soquete) {// Construa uma ligação de parâmetro this.client = client; } @Override public void run () {BufferReader bufferReader = null; // Crie um fluxo de cache de cache de caracteres printwriter printWriter = null; // Crie Stream de escrita de caracteres Try {bufferredReader = new BufferredReader (new InputStreamReader (client.getInputStream ())); // Obtenha o fluxo de entrada do cliente PrintWriter = new PrintWriter (client.getOutputStream (), true); // Obtenha o fluxo de saída do cliente, true é para atualizar a string inputline = null; longo a = System.currenttimemillis (); while ((inputLine = buffarredReader.readline ())! = null) {printWriter.println (inputline); } long b = System.currenttimemillis (); System.out.println ("Este tópico pegou:"+(ba)+"segundos!"); } catch (ioexception e) {e.printStackTrace (); } finalmente {tente {bufferredreader.close (); printWriter.close (); client.close (); } catch (ioexception e) {e.printStackTrace (); }}}} public static void main (string [] args) lança ioexception {// O encadeamento principal do servidor é usado para ouvir as solicitações do cliente em um loop serversocket server = new ServerSocket (8686); // Crie um servidor com porta 8686 Socket Client = NULL; while (true) {// loop listen client = server.accept (); // O servidor ouve um sistema de solicitação de cliente.out.println (client.getRemoteSocketAddress ()+"A conexão do cliente é bem -sucedida!"); executorService.submit (new Handlemsg (cliente)); // Coloque a solicitação do cliente no thread handlmsg através do pool de threads para processamento}}}No código acima, usamos uma classe para escrever um servidor de eco simples para ativar a escuta da porta no thread principal usando um loop morto.
Cliente simples
Com um servidor, podemos acessá -lo e enviar alguns dados da string. A função do servidor é retornar essas seqüências e imprimir o thread que leva tempo.
Vamos escrever um cliente simples para responder ao servidor:
public class MyClient {public static void main (string [] args) lança ioexception {socket client = null; PrintWriter printWriter = null; BufferReader buffaredReader = null; tente {client = new Socket (); client.connect (new inetSocketAddress ("localhost", 8686)); printWriter = new PrintWriter (client.getOutputStream (), true); printwriter.println ("hello"); printWriter.flush (); BufferredReader = new BufferredReader (new InputStreamReader (client.getInputStream ()); // Leia as informações retornadas pelo servidor e pelo sistema de saída.out.println ("As informações do servidor são:"+bufferreader.readline ()); } catch (ioexception e) {e.printStackTrace (); } finalmente {printWriter.close (); BufferredReader.Close (); client.close (); }}}No código, usamos o fluxo de caracteres para enviar uma string hello. Se o código estiver bom, o servidor retornará um Hello Data e imprimirá as informações de log que definimos.
Exibição de resultados do servidor de eco
Vamos correr:
1. Abra o servidor e habilite a escuta do loop:
2. Abra um cliente:
Você pode ver que o cliente imprime o resultado de retorno
3. Verifique o log do servidor:
Muito bom, uma programação de soquete multithread simples é implementada
Mas pense nisso:
Se um cliente solicitar, adicione o sono durante a escrita de IO ao servidor,
Faça cada solicitação ocupar o tópico do servidor por 10 segundos
Depois, há muitas solicitações de clientes, cada um levando tanto tempo
Então os recursos de unificação do servidor serão bastante reduzidos
Isso não ocorre porque o servidor tem muitas tarefas pesadas, mas apenas porque o tópico de serviço está esperando por IO (porque aceite, leia e escreva está bloqueando)
Não é muito pago deixar as CPUs de alta velocidade esperar por sua rede ineficiente io
O que devo fazer neste momento?
NIO
O novo IO resolveu com sucesso o problema acima. Como isso resolveu isso?
A unidade mínima para o IO para processar solicitações de clientes é thread
Nio usa uma unidade que é um nível menor que um thread: canal (canal)
Pode -se dizer que apenas um tópico é necessário no NIO para concluir toda a recepção, leitura, escrita e outras operações.
Para aprender Nio, você deve primeiro entender seus três pontos principais
Seletor, seletor
Buffer, buffer
Canal, canal
O blogueiro não é talentoso, então ele desenhou uma imagem feia para aprofundar sua impressão. ^
Dê -me outro diagrama de fluxo de trabalho do NIO no TCP (muito difícil de desenhar linhas ...)
Apenas entenda aproximadamente, vamos passo a passo
Buffer
Primeiro de tudo, você precisa saber o que é um buffer
A interação de dados não usa mais fluxos como mecanismos de IO
Em vez disso, use um buffer (buffer)
O blogueiro acha que as fotos são as mais fáceis de entender
então...
Você pode ver onde o buffer está em todo o fluxo de trabalho
Vamos dar uma olhada nos reais. O código específico na figura acima é o seguinte:
1. Primeiro aloca espaço para o buffer, em bytes
Bytebuffer bytebuffer = bytebuffer.allocate (1024);
Crie um objeto ByteBuffer e especifique o tamanho da memória
2. Escreva dados para o buffer:
1). Dados de canal para buffer: canal.read (bytebuffer); 2). Dados do cliente para buffer: bytebuffer.put (...);
3. Leia os dados do buffer:
1). Dados de buffer para canal: canal.write (bytebuffer); 2). Dados de buffer para servidor: bytebuffer.get (...);
Seletor
O seletor é o núcleo de Nio, é o administrador do canal
Ao executar o método de bloqueio select (), ouça se o canal está pronto
Depois que os dados são legíveis, o valor de retorno deste método é o número de teclas de seleção
Portanto, o servidor geralmente executa o método select () Method Loop até que o Chanl esteja pronto e depois comece a funcionar
Cada canal vinculará um evento ao seletor e, em seguida, gerará um objeto SelectionKey.
O que deve ser observado é:
Quando o canal e o seletor estão ligados, o canal deve estar no modo de não bloqueio.
O FileChannel não pode mudar para o modo de não bloqueio porque não é um canal de soquete, portanto, o FileChannel não pode vincular eventos com seletor
Existem quatro tipos de eventos no NIO:
1.SelectionKey.OP_Connect: Evento de conexão
2.SelectionKey.OP_ACECT: Receba eventos
3.SelectionKey.op_read: Leia eventos
4.SelectionKey.OP_WRITE: Escreva eventos
Canal
Existem quatro canais no total:
FileChannel: Atos no fluxo de arquivos IO
DataGramChannel: funciona no protocolo UDP
Socketchannel: Funciona no protocolo TCP
ServerSocketchAnnel: ele atua no protocolo TCP
Este artigo explica o NIO através do protocolo TCP comumente usado
Vamos tomar o ServerSocketchannel como exemplo:
Abra um canal ServerSocketchAnnel
ServerSocketchAnnel ServerSocketchannel = ServerSocketchAnnel.open ();
Feche o canal ServerSocketchAnnel:
Serversocketchannel.close ();
Socketchannel em loop:
while (true) {socketchannel socketchannel = serversocketchannel.accept (); clientChannel.configureblocking (false);} clientChannel.configureBlocking(false); A declaração é definir esse canal para não bloquear, ou seja, o controle livre assíncrono de bloqueio ou não bloqueio é uma das características do NIO
SelectionKey
SelectionKey é o componente principal da interação entre canais e seletores
Por exemplo, vincule um seletor no Socketchannel e registre -o como um evento de conexão:
Socketchannel clientChannel = socketchannel.open (); clientChannel.configureblocking (false); clientChannel.connect (new inetSocketAddress (porta)); clientChannel.register (seletor, seleçãokeyke.op_connect);
O núcleo está no método Register (), que retorna um objeto SelectionKey
Para detectar eventos de canal é que tipo de evento você pode usar o seguinte método:
SelectionKey.isAceptable (); SelectionKey.isconnectable (); SelectionKey.isReadable (); SelectionKey.isWritable ();
O servidor executa operações correspondentes nas pesquisas através desses métodos
Obviamente, as chaves ligadas ao canal e seletor também podem ser obtidas por sua vez.
Canal canal = SelectionKey.Channel (); Selector Selector = SelectionKey.Selector ();
Ao registrar eventos no canal, também podemos vincular um buffer:
clientChannel.register (key.selector (), SelectionKey.op_read, bytebuffer.alocatedirect (1024));
Ou vincular um objeto:
SelectionKey.attach (objeto); objeto anthorobj = SelectionKey.attachment ();
Servidor TCP da NIO
Depois de falar tanto, daremos uma olhada no código mais simples e mais importante (não é elegante adicionar tantos comentários, mas é conveniente para que todos entendam):
pacote cn.blog.test.niotest; importar java.io.ioException; importar java.net.inetsocketaddress; importar java.nio.bytebuffer; import java.nio.channels. // Crie um seletor privado final estático intt = 8686; private final estático int buf_size = 10240; private void initServer () lança IoException {// Crie seletor de objetos do Channel Manager this.selector = Selector.open (); // Crie um canal de canal de canal servidorsocketchannel canal = serversocketchannel.open (); canal.configureblocking (false); // Defina o canal para canal não-bloqueador.socket (). Bind (novo inetSocketAddress (porta)); // Ligue o canal na porta 8686 // vincula o gerenciador e o canal acima do canal e registre o evento OP_ACECT para o canal // Após o registro do evento, selettor.select () retornará (uma chave). Se o evento não chegar a seletor.Select (), ele bloqueará a seleção seleção selectionKey = canal.register (seletor, selectionKey.op_accept); while (true) {// Poll Selector.Select (); // Este é um método de bloqueio, aguarde até que os dados sejam legíveis, e o valor de retorno é o número de chaves (pode haver várias chaves definidas) = selettor.SelectedKeys (); // Se o canal tiver dados, acesse as teclas geradas no iterador de coleta de chaves iterator = keys.iterator (); // Obtenha o iterador desta coleção de chaves enquanto (iterator.hasnext ()) {// use o iterador para atravessar a coleção seleçãoKey Key = (SelectionKey) iterator.Next (); // Obtenha uma instância -chave no iterator.remove (); // Lembre -se de excluir esse elemento no iterador depois de obter a instância de chave atual, que é muito importante, caso contrário, ocorrerá um erro se (key.isAcceptable ()) {// julgar se o canal representado pela chave atual estiver no estado aceitável e, nesse caso, DoAcept (chave); } else if (key.isReadable ()) {Doread (key); } else if (key.isWritable () && key.isValid ()) {dowrite (key); } else if (key.isconnectable ()) {System.out.println ("conectável!"); }}}} public void DoAcept (chave de seleção) lança IoException {ServerSocketchAnnel ServerChannel = (ServerSocketchAnel) key.Channel (); System.out.println ("ServerSocketchannel está ouvindo em um loop"); Socketchannel clientChannel = serverChannel.accept (); clientChannel.configureblocking (false); clientChannel.register (key.selector (), SelectionKey.op_read); } public void Doread (seleção Key) lança IoException {SocketchAnnel ClientChannel = (Socketchannel) key.Channel (); Bytebuffer bytebuffer = bytebuffer.allocate (buf_size); bytesread long bytes = clientChannel.read (bytebuffer); while (bytesread> 0) {bytebuffer.flip (); byte [] dados = bytebuffer.array (); String info = new String (dados) .Trim (); System.out.println ("A mensagem enviada do cliente é:"+info); byteBuffer.clear (); bytesread = clientChannel.read (bytebuffer); } if (bytesRead ==-1) {clientChannel.close (); }} public void Dowrite (seleção Key) lança ioexception {bytebuffer bytebuffer = bytebuffer.allocate (buf_size); byteBuffer.flip (); Socketchannel ClientChannel = (Socketchannel) key.Channel (); while (bytebuffer.hasReNaining ()) {clientchannel.write (bytebuffer); } byteBuffer.compact (); } public static void main (string [] args) lança ioexception {mynioserver mynioserver = new mynioserver (); mynioserver.initServer (); }} Imprimi o canal do monitor e disse quando o ServerSocketchannel começou a executar
Se você cooperar com a depuração do cliente da NIO, poderá encontrá -lo claramente. Antes de inserir a pesquisa select ()
Embora já exista uma chave para o evento de aceitação, o select () não será chamado por padrão
Em vez disso, você deve esperar até que outros eventos interessantes sejam capturados por select () antes de chamar a seleção da aceitação
Somente então o ServerSocketchannel começou a executar o monitoramento circular
Em outras palavras, em um seletor, o ServerSocketchannel é sempre mantido.
E serverChannel.accept(); é verdadeiramente assíncrono (canal.configureblocking (false); no método initserver)
Se a conexão não for aceita, um nulo será devolvido
Se um Socketchannel estiver conectado com sucesso, este Socketchannel registra um evento de gravação (read)
E definir assíncrono
Cliente TCP da NIO
Deve haver um cliente se houver um servidor
De fato, se você pode entender completamente o servidor
O código do cliente é semelhante
pacote cn.blog.test.niotest; importar java.io.ioException; importar java.net.inetsocketaddress; importar java.nio.bytebuffer; import java.nio.kannels.selectionKey; import java.nio.channels.selector; Mynioclient {seletor de seletor privado; // Crie um seletor privado final estático intt = 8686; private final estático int buf_size = 10240; bytebuffer bytebuffer privado = bytebuffer.allocate (buf_size); private void initclient () lança ioexception {this.selector = Selector.open (); Socketchannel clientChannel = socketchannel.open (); clientChannel.configureblocking (false); clientChannel.connect (new inetSocketAddress (porta)); clientChannel.register (seletor, SelectionKey.op_Connect); while (true) {Selector.Select (); Iterator <sectionKey> iterator = selector.SelectedKeys (). Iterator (); while (iterator.hasNext ()) {seleçãoKey Key = iterator.Next (); iterator.remove (); if (key.isconnectable ()) {doconnect (key); } else if (key.isReadable ()) {Doread (key); }}} public void doconnect (selectionKey Key) lança IoException {SocketchAnnel ClientChannel = (Socketchannel) key.Channel (); if (clientChannel.isconnectionPending ()) {clientChannel.finishconnect (); } clientChannel.configureblocking (false); String info = "Hello Server !!!"; byteBuffer.clear (); bytebuffer.put (info.getbytes ("utf-8")); byteBuffer.flip (); clientChannel.Write (ByTeBuffer); //clientChannel.register(Key.Selector( ),selectionKey.op_read); clientChannel.close (); } public void Doread (seleção Key) lança IoException {SocketchAnnel ClientChannel = (Socketchannel) key.Channel (); clientChannel.read (bytebuffer); byte [] dados = bytebuffer.array (); String msg = new String (dados) .Trim (); System.out.println ("Servidor envia mensagem:"+msg); clientChannel.close (); key.Selector (). Close (); } public static void main (string [] args) lança ioexception {mynioclient mynioclient = new mynioclient (); mynioclient.initclient (); }}Resultado de saída
Aqui eu abro um servidor e dois clientes:
Em seguida, você pode tentar abrir mil clientes ao mesmo tempo. Enquanto sua CPU for poderosa o suficiente, o servidor não poderá reduzir o desempenho devido ao bloqueio.
O exposto acima é uma explicação detalhada do Java Nio. Se você tiver alguma dúvida, poderá discuti -lo na área de mensagem abaixo.