41bb726c

Dia 26

Cliente/Servidor que Transmite em rede em Java

Michael Morrison


CONTEÚDOS

As capacidades de ligação em rede de Java são possivelmente o componente mais potente de Java API porque a grande maioria de programas Java corre em um ambiente em rede. Usar a ampla variação de características de rede incorporou em Java, pode desenvolver facilmente applets Baseados na web que executam várias tarefas sobre uma rede. O suporte de rede em Java ajusta-se em particular bem a um acordo de cliente/servidor onde uns marechais de servidor a informação e serve-o a clientes que tratam os detalhes de expor a informação a um usuário.

Na lição de hoje aprenderá o que Java tem de oferecer com respeito à comunicação sobre uma conexão de rede de Internet usando um acordo de cliente/servidor. Começará a lição dando uma olhada em alguns conceitos fundamentais que rodeiam a estrutura da Internet como uma rede. Então mudará a que suporte específico se fornece por Java padrão que transmite API em rede e como se ajusta com o paradigma de cliente/servidor. Finalmente, concluirá a lição desenvolvendo um par de programas de mostra interessantes que demonstram os tipos diferentes de aproximações de cliente/servidor disponíveis em Java.

Os seguintes tópicos são cobertos na lição de hoje:

Até ao fim desta lição, estará pronto para construir os seus próprios programas de cliente/servidor de rede de Java do zero. Também terá uma melhor compreensão de uma das razões Java ficou tão popular - em virtude do seu suporte limpo e franco de uma área de outra maneira confusa e complexa da programação: programação de rede!

Fundamentos de rede de Internet

Antes que aprenda sobre os tipos do suporte de rede que Java fornece, é importante que entenda algum fundamentals sobre a estrutura da Internet como uma rede. Como não há dúvida já sabe, a Internet é uma rede global de muitos tipos diferentes de computadores unidos de vários modos. Com esta larga diversidade tanto de hardware como de software todos uniram-se em conjunto, é bastante assombroso que a Internet seja até funcional. Confie em mim, a funcionalidade da Internet não é nenhum acidente e não veio para nenhum pequeno preço quanto a planejamento.

O único modo de garantir a compatibilidade e a comunicação fiável através de uma ampla variação de sistemas de computador diferentes é definir padrões muito estritos que devem ajustar-se com rigorosamente. Isto é exatamente a aproximação tomada pelos planejadores da Internet na determinação dos seus protocolos de comunicações. Por favor entenda que não sou o tipo da pessoa que tipicamente prega a conformidade, mas a conformidade na vida pessoal de alguém é muito diferente da conformidade em redes de computador complexas.

O ponto é, o único modo de permitir a uma ampla variação de sistemas de computador coexistir e comunicar-se um com outro efetivamente é elaborar alguns padrões. Afortunadamente, a abundância de padrões abundam para a Internet, e compartilham o largo suporte através de muitos sistemas de computador diferentes. Confiamos que convenci-o da importância de padrões de comunicação na Internet - vamos dar uma olhada em alguns deles.

Endereços

Uma das primeiras áreas da padronização na Internet esteve no estabelecimento de um meio de identificar unicamente cada computador ligado. Não é surpreendente que uma técnica logicamente equivalente a endereços postais tradicionais seja aquela que se adotou; cada computador fisicamente unido à Internet destina-se um endereço que unicamente o identifica. Estes endereços, também endereços IP tratados como, vêm na forma de um número de 32 bits que parece a isto: 243.37.126.82. É provavelmente mais familiar com a forma simbólica de endereços IP, que parece a isto: sams.mcp.com.

Novo termo
Um endereço IP é um número de 32 bits que unicamente identifica cada computador fisicamente anexado à Internet.

Portanto os endereços fornecem cada computador unido à Internet com um identificador único. Cada computador de Internet tem um endereço pela mesma razão tem um endereço postal e um número de telefone na sua casa: facilitar a comunicação. Poderia parecer simples, e é porque conceptualmente é. Enquanto podemos garantir que cada computador é unicamente identificável, podemos comunicar-nos facilmente com qualquer computador sem preocupação. Bem, quase. A verdade é, os endereços são só uma pequena parte da equação de comunicação de Internet, mas uma parte importante no entanto. Sem endereços, não haveria modo de distinguir-se entre computadores diferentes.

Protocolos

A ideia de comunicar-se entre computadores diferentes na Internet não poderia parecer que grande um acordo agora que entende que usam endereços semelhantes a endereços postais. O problema consiste em que há muitos tipos diferentes da comunicação que pode realizar-se na Internet, significando que deve haver um número igual de mecanismos para tratá-los. Está neste ponto que a comparação de endereço postal com o endereçamento de Internet se avaria. A razão disto consiste em que cada tipo da comunicação que se realiza na Internet necessita um protocolo único. O seu endereço postal essencialmente gira em volta de um tipo da comunicação: a transportadora postal que vai de carro até a sua caixa do correio e coloca o correio no interior.

Um protocolo especifica o formato de dados que se enviam pela Internet, junto com como e quando se envia. Em outro fim da comunicação, o protocolo também define como os dados se recebem junto com a sua estrutura e o que significa. Ouviu provavelmente a menção da Internet que é logo um ramo de bits que voam para a frente e para trás no ciberespaço. Isto é uma afirmação muito verdadeira, e sem protocolos, aqueles bits não significariam nada.

Novo termo
Um protocolo é grupo de regras e padrões que definem certo tipo da comunicação de Internet.

O conceito de um protocolo não é groundbreaking ou até novo. Usamos protocolos todo o tempo em situações diárias; somente não os chamamos protocolos. Pense quantas vezes se implicou neste tipo do diálogo:

"Oi, posso tomar a sua ordem?"
"Sim, eu gostaria do salmão grelhado e margarita de morango congelada".
"Agradecimentos, porei a sua ordem em e lhe trarei a sua bebida".
"Obrigado, esfomeio-me".

Embora esta conversação não pudesse parecer a nada especial, é um protocolo social muito definido usado para colocar ordens da comida em um restaurante. O protocolo conversador é importante porque nos dá a familiaridade e a confiança no conhecimento que fazer em certas situações. Não esteve alguma vez nervoso entrando em uma nova situação social em que não sabia bastante como atuar? Nestes casos, realmente não teve confiança no protocolo, portanto provavelmente se incomodou com um problema de comunicação que pode ter resultado facilmente no embaraço. Para computadores e redes, o esgotamento de protocolo traduz para erros e fracasso de transferência de informação em vez de embaraço.

Agora que entende a importância de protocolos, vamos dar uma olhada em um par os mais importantes usados na Internet. Sem uma dúvida, o protocolo que adquire a maior parte de atenção nestes dias é HTTP, que significa o Protocolo de transferência de hipertexto. HTTP é o protocolo usado para transferir documentos HTML na Web. Outro protocolo importante é FTP, que significa o Protocolo de Transferência de arquivos. O FTP é um protocolo mais geral usado para transferir arquivos binários pela Internet. Cada um destes protocolos tem o seu próprio conjunto de regras único e definição de padrões como a informação se transfere, e Java fornece o suporte de ambos.

Novo termo
HTTP significa o Protocolo de transferência de hipertexto, que é o protocolo usado para transferir documentos HTML na Web.

Novo termo
O FTP significa o Protocolo de Transferência de arquivos, que é o protocolo usado para transferir arquivos através da Internet.

Portos

Os protocolos de Internet só fazem sentido no contexto de um serviço. Por exemplo, o protocolo HTTP entra no jogo quando fornece o Conteúdo Web (páginas de HTML) por um serviço HTTP. Cada computador na Internet tem a capacidade de fornecer vários serviços por vários protocolos apoiados. Há um problema, contudo, nisto o tipo do serviço deve conhecer-se antes que a informação pode transferir-se. Isto é onde os portos entram. Um porto é uma abstração de software que fornece um meio de diferenciar-se entre serviços de rede. Mais especificamente, um porto é um número de 16 bits que identifica os serviços diferentes oferecidos por um servidor de rede.

Novo termo
Um porto é um número de 16 bits que identifica cada serviço oferecido por um servidor de rede.

Cada computador na Internet tem um ramo de portos que podem destinar-se serviços diferentes. Para usar um determinado serviço e por isso estabelecem uma linha da comunicação via um determinado protocolo, deve unir-se ao porto correto. Os portos numeram-se, e alguns números associam-se especificamente com um tipo do serviço. Os portos com nomeações de serviço específicas conhecem-se como portos padrão, significando que sempre pode contar com um determinado porto que corresponde a certo serviço. Por exemplo, o serviço de FTP localiza-se no porto 21, portanto qualquer outro computador que quer executar uma transferência de arquivos de FTP uniria ao porto 21 do computador de anfitrião. De mesmo modo, o serviço HTTP localiza-se no porto 80, portanto qualquer tempo acessa um Web site, realmente une ao porto 80 do anfitrião que usa o protocolo HTTP nos bastidores. A figura 26.1 ilustra como trabalham os portos e os protocolos.

A figura 26.1: A relação entre protocolos e portos.

Todas as nomeações de serviço padrão dão-se valores de porto abaixo 1024. Isto significa que os portos em cima 1024 se consideram disponíveis para comunicações alfandegárias, como os necessitados por um programa de cliente/servidor de Java implementando o seu próprio protocolo. Tenha em mente, contudo, que outros tipos da comunicação alfandegária também se realizam acima do porto 1024, portanto deveria tentar alguns portos diferentes para encontrar um não usado.

O Paradigma de Cliente/Servidor

Por enquanto consegui explicar um montante decente da Internet que transmite em rede fundamentals evitando uma principal questão: o paradigma de cliente/servidor. Não há dúvida ouviu de clientes e servidores antes, mas não poderia entender totalmente a sua importância com respeito à Internet. Bem, é tempo do remédio que a situação, porque não será capaz de tornar-se muito feito em Java sem entender como trabalham os clientes e os servidores. Em verdade, a rede de Java - a programação de armação é baseada em um acordo de cliente/servidor.

O paradigma de cliente/servidor implica o pensamento de computação quanto a um cliente, que precisa essencialmente de algum tipo da informação e um servidor, quem tem muita informação e somente espera para distribui-la. Tipicamente, um cliente vai se unir a um servidor e pergunta de certa informação. O servidor vai se ir e encontrará a informação e logo a devolverá ao cliente. Poderia soar como se esteja simplificando demais coisas aqui, mas em sua maioria não sou; conceptualmente, o cliente/servidor que calcula é tão simples como um cliente que pede informação e um servidor devolvendo-o.

No contexto da Internet, os clientes dirigem-se tipicamente em desktop ou computadores de computador portátil anexados à Internet procurando a informação, ao passo que os servidores se dirigem tipicamente em computadores maiores com certos tipos da informação disponível para os clientes para recuperar. A própria Web compõe-se de um ramo de computadores aquele ato como Servidores Web; têm montantes vastos de páginas de HTML e dados disponíveis relacionados de pessoas para recuperar e pesquisar. Os clientes de web usam-se por aqueles de nós que se unimos aos Servidores Web e pesquisamos pelas Páginas da Web. Deste modo, o Navegador de Netscape considera-se o software Web de cliente. Dê uma olhada na Figura 26.2 para adquirir uma melhor ideia do acordo de cliente/servidor.

A figura 26.2: Um Servidor Web com múltiplos clientes uniu-se.

Tomadas

Um de ternos fortes principais de Java como uma linguagem de programação é a sua ampla variação do suporte de rede. Java tem esta vantagem porque se desenvolveu com a Internet em mente. O resultado consiste em que tem muitas opções com respeito à programação de rede em Java. Embora haja muitas opções de rede, a maior parte de programação de rede de Java usa um determinado tipo da comunicação de rede conhecida como tomadas.

Novo termo
Uma tomada é uma abstração de software de um meio de produção ou entrada da comunicação.

Java executa toda da sua comunicação de rede de baixo nível por tomadas. Logicamente, as tomadas são um passo mais baixo do que portos; usa tomadas para comunicar-se por um determinado porto. Portanto uma tomada é um canal de comunicação que lhe permite transferir dados por certo porto. A Figura 26.3 de check out, que mostra a comunicação que se realiza por múltiplas tomadas em um porto.

A figura 26.3: múltiplas tomadas que se comunicam por um porto.

Este número sobe um ponto interessante sobre tomadas: os Dados podem transferir-se por múltiplas tomadas de um porto único. Isto faz sentido porque é característico para múltiplos usuários de Web recuperar Páginas da Web de um servidor via o porto 80 (HTTP) ao mesmo tempo. Java fornece classes de tomada básicas para fazer a programação com tomadas muito mais fácil. As tomadas de Java rompem-se em dois tipos: tomadas de datagrama e tomadas de corrente.

Tomadas de datagrama

Uma tomada de datagrama usa User Datagram Protocol (UDP) para facilitar o envio de datagramas (as partes reservadas da informação) em uma maneira insegura. Inseguro significa que a informação enviada via datagramas não se garante para fazê-lo ao seu destino. A troca aqui consiste em que as tomadas de datagrama necessitam relativamente poucos recursos diretamente por causa deste desenho inseguro. Os clientes e os servidores em um cenário de datagrama não necessitam uma conexão de rede "viva" ou dedicada, que é às vezes desejável. Deste modo, uma tomada de datagrama é um tanto equivalente a uma conexão de rede de disco, com a qual se une temporariamente a uma rede baseada nas suas necessidades de informação imediatas.

Os datagramas enviam-se como pacotes individualmente embrulhados que podem ou podem não fazê-lo ao seu destino em nenhuma determinada ordem ou por cima de nenhum tempo particular. No fim de recepção de um sistema de datagrama, os pacotes da informação podem receber-se em qualquer ordem e em qualquer momento. Por essa razão, os datagramas às vezes incluem um número de sequência que especifica a que parte da quebra-cabeça cada pacote corresponde. O receptor então pode esperar para receber a sequência inteira, em que caso os reporá em conjunto para formar a estrutura de informação original.

Novo termo
UDP (Protocolo de Datagrama de Usuário) é um protocolo de transmissão de rede que não garante o êxito de transferência. Em troca, UDP confia em poucos recursos de rede.

Novo termo
Um datagrama é uma parte independente, reservada da informação enviada sobre uma rede cuja chegada, hora de chegada e conteúdo não se garante.

Novo termo
Uma tomada de datagrama ou tomada "desconexa", é uma tomada sobre a qual os dados se embrulham em pacotes e se enviam sem necessitar uma conexão "viva" ao computador de destino.

O fato que as tomadas de datagrama são abertamente inseguras pode levá-lo a pensar que são algo para evitar na programação de rede. Contudo, há cenários muito práticos nos quais as tomadas de datagrama fazem soluções perfeitamente aceitáveis. Por exemplo, os servidores que constantemente transmitem a informação semelhante fazem grandes candidatos para a comunicação de datagrama. Um servidor de cotação de ações é um bom exemplo desde que as cotações de ações se estão constantemente cuspindo com relação à entrega bem sucedida. O fato que as cotações de ações são altamente dependentes do tempo fá-lo menos de uma questão se uma cotação de ações nunca o conseguir; somente pode esperar até que novo um se envie.

Java apoia a programação de tomada de datagrama por duas classes: DatagramSocket e DatagramPacket. A classe de DatagramSocket fornece uma implementação para uma tomada de datagrama básica. A classe de DatagramPacket fornece a funcionalidade necessitada de um pacote da informação que é capaz de enviar-se por uma tomada de datagrama. Estas duas classes são tudo que tem de adquirir a escrita ocupada dos seus próprios programas Java de cliente/servidor de datagrama.

O seguinte é uma lista de alguns métodos mais importantes implementados na classe de DatagramSocket:

DatagramSocket ()
DatagramSocket (porto interno)
o vazio envia (DatagramPacket p)
o vazio sincronizado recebe (DatagramPacket p)
vazio sincronizado perto ()

Dois primeiros métodos enumerados são de fato construtores da classe de DatagramSocket. O primeiro construtor é o construtor à revelia e não toma nenhum parâmetro, e o segundo construtor cria uma tomada de datagrama unida ao porto especificado. O send e os métodos de receive são muito francos e fornecem um meio de enviar e receber pacotes de datagrama. O método de close simplesmente fecha a tomada de datagrama. Não se torna muito mais simples do que isto!

Note que a classe de DatagramSocket não se distingue entre a tomada que é tomada de servidor ou cliente. A razão disto é a maneira na qual a comunicação de datagrama se realiza, que não precisa que a tomada atue especificamente como um cliente ou servidor. Melhor os clientes de Java e os servidores distinguem-se por como usam a classe de DatagramSocket para transmitir/receber datagramas.

Outra metade da solução de datagrama é a classe de DatagramPacket, que modela um pacote da informação enviada por uma tomada de datagrama. O seguinte é alguns métodos mais úteis na classe de DatagramPacket:

DatagramPacket (byte ibuf [], número interno ilength)
DatagramPacket (byte ibuf [], número interno ilength, InetAddress iaddr, número interno iport)
byte [] getData ()
número interno getLength ()

Dois primeiros métodos são os construtores de DatagramPacket. Como provavelmente adivinhou dos parâmetros, constrói pacotes de datagrama de tabelas de byte de dados. O primeiro construtor usa-se para receber datagramas, como é evidente pela ausência do número de porto ou um endereço. O segundo construtor usa-se para enviar datagramas, que é porque tem de especificar um endereço de destino e número de porto do datagrama a enviar-se. Outros dois métodos devolvem os dados de datagrama crus e o comprimento dos dados, respectivamente.

Outro do que os construtores, todos os métodos em DatagramPacket são passivos, significando que simplesmente devolvem a informação sobre o pacote de datagrama e não modificam de fato nada. Isto é evidência que a classe de DatagramPacket se usa principalmente como um container para dados que se enviam sobre uma tomada de datagrama. Em outras palavras, criará tipicamente um objeto de DatagramPacket como um empacotador de dados que se enviam ou recebido e nunca chamará nenhum método nele.

Tomadas de corrente

Diferentemente de tomadas de datagrama, nas quais a comunicação é rudemente parecida a isto em uma rede de disco, uma tomada de corrente é mais parecida a uma rede viva, na qual a conexão de comunicação é continuamente ativa. Uma tomada de corrente é uma tomada "ligada" pela qual os dados se transferem continuamente. Por continuamente, não necessariamente subentendo que os dados se estão enviando todo o tempo, mas que a própria tomada é ativa e pronta para a comunicação todo o tempo.

Novo termo
Uma tomada de corrente ou tomada unida, é uma tomada pela qual os dados podem transmitir-se continuamente.

O benefício de usar uma tomada de corrente é que a informação pode enviar-se com menos preocupação sobre quando chegará ao seu destino. Como a conexão de comunicação sempre é viva, os dados transmitem-se geralmente imediatamente depois que o envia. Naturalmente, esta conexão de comunicação dedicada traz com ele a de cima da consumação de mais recursos. Contudo, a maior parte de programas de rede beneficiam-se muito da coerência e confiança de uma tomada de corrente.

Observar
Um uso prático de um mecanismo que corre é RealAudio, que é uma tecnologia que fornece um modo de escutar o áudio na Web como se está transmitindo em tempo real.

Java apoia a tomada de corrente que programa principalmente por duas classes: Socket e ServerSocket. A classe de Socket fornece o necessário em cima para facilitar um cliente de tomada de corrente, e a classe de ServerSocket fornece a funcionalidade principal para um servidor.

O seguinte é uma lista de alguns métodos mais importantes implementados na classe de Socket:

Tomada (Anfitrião de cadeia, porto interno)
Tomada (endereço de InetAddress, porto interno)
vazio sincronizado perto ()
InputStream getInputStream ()
OutputStream getOutputStream ()

Dois primeiros métodos enumerados são construtores da classe de Socket. O computador de anfitrião ao qual une a tomada especifica-se no primeiro parâmetro de cada construtor; a diferença entre os dois construtores é se especifica o anfitrião que usa um nome de cadeia ou um objeto de InetAddress. O segundo parâmetro é um número inteiro que especifica o porto ao qual quer unir-se. O método de close usa-se para fechar uma tomada. O getInputStream e os métodos de getOutputStream usam-se para recuperar as correntes de entrada e saída associadas com a tomada.

A classe de ServerSocket trata outro fim da comunicação de tomada em um cenário de cliente/servidor. O seguinte é alguns dos métodos mais úteis definidos na classe de ServerSocket:

ServerSocket (porto interno)
ServerSocket (porto interno, conta interna)
A tomada aceita ()
vazio perto ()

Dois primeiros métodos são os construtores de ServerSocket, que ambos tomam um número de porto como o primeiro parâmetro. O parâmetro de count no segundo construtor especifica um período de intervalo do servidor para deixar automaticamente "de escutar" para uma conexão de cliente. Isto é o fator que se distingue entre os dois construtores; a primeira versão não escuta para uma conexão de cliente, ao passo que a segunda versão faz. Se usar o primeiro construtor, deve dizer especificamente ao servidor esperar por uma conexão de cliente. Faz isto chamando o método de accept, que bloqueia o fluxo de programa até que uma conexão se faça. O método de close simplesmente fecha a tomada de servidor.

Como com as classes de tomada de datagrama, poderia estar pensando que as classes de tomada de corrente parecem terrivelmente simples. De fato, são simples, que é uma boa coisa. A maioria do código real que facilita comunicação via tomadas de corrente trata-se pelas correntes de entrada e saída unidas a uma tomada. Deste modo, a própria comunicação trata-se independentemente da conexão de tomada de rede. Isto não poderia parecer uma grande coisa no início, mas é crucial para o desenho das classes de tomada; depois que criou uma tomada, une-lhe uma corrente de produção ou entrada e logo esquece-se da tomada.

Fortuna: um cliente de datagrama e servidor

Cobriu agora os fundamentos de tomadas e como trabalham em Java, mas não viu uma tomada na ação. Bem, é tempo do remédio que a situação com um programa de cliente/servidor completamente desabrochado que usa tomadas de datagrama. Também trabalhará por um exemplo de tomada de corrente depois hoje, mas primeiras coisas primeiro!

O exemplo de cliente/servidor de datagrama chama-se Fortune e compõe-se de um servidor que transmite citações interessantes chamadas "fortunas" e um cliente que recebe e expõe as fortunas. O exemplo de Fortune também pode usar-se para implementar um chiste do servidor de dia, onde os usuários podem unir e adquirir o último chiste tem de oferecer. Desde que tinha citações mais interessantes do que chistes engraçados, decidi picar com um servidor de citação!

O exemplo de Fortuna trabalha como isto: há um programa de servidor que corre em um Servidor Web e espera pacientemente por clientes para unir e pedir uma fortuna. Em outro fim, há um cliente applet introduzido em uma Página da Web que uns acessos de usuário com um Navegador da Web permitido por Java. Quando o usuário carrega a Página da Web e acende o applet, o applet une-se ao servidor e pede-lhe uma fortuna. O servidor à sua vez escolhe uma fortuna à toa e retorna-a ao applet. O applet no regresso expõe a fortuna do usuário para ver. É isto simples!

Desenho de fortuna

Antes de pular no código de Java necessitado implementar o exemplo de Fortune, vamos dar uma olhada resumidamente no que se necessita do desenho em cada lado da cerca de cliente/servidor. No lado de servidor, precisa de um programa que controla um determinado porto na máquina de anfitrião de conexões de cliente. Quando um cliente se descobre, o servidor escolhe uma fortuna casual, que é uma cadeia de texto simples e a envia ao cliente sobre o porto especificado. O servidor então é livre de quebrar a conexão e deixar o cliente ir ao seu caminho alegre. Os regressos de servidor ao seu original esperam o estado, onde procura outros clientes para unir-se. Portanto o servidor deve executar as seguintes tarefas:

  1. Espere por um cliente para unir-se.
  2. Aceite a conexão de cliente.
  3. Envie uma fortuna casual ao cliente.
  4. Volte ao passo 1.

Agora, no cliente. O lado de cliente do exemplo de Fortune é um applet que vive em uma Página da Web e tem o suporte cheio da produção gráfica. O cliente applet é responsável por unir-se ao servidor e esperar a resposta do servidor. A resposta do servidor é a transmissão da cadeia de fortuna, que o cliente deve receber e expor. Quando o cliente com sucesso recebe a fortuna, pode quebrar a conexão com o servidor. Como um bônus acrescentado, o cliente applet também é capaz de agarrar outra fortuna se clicar no botão de rato. As tarefas primárias do cliente seguem:

  1. Una-se ao servidor.
  2. Espere por uma fortuna a enviar-se.
  3. Exponha a fortuna.
  4. Volte ao passo 1 se o usuário clicar no botão de rato.

Implementar o servidor de fortuna

Não há dúvida coça para ver algum verdadeiro código que executa todas estas ideias que tem aprendido. Bem, a hora chegou! Desde que o exemplo de Fortuna enfim começa e termina no servidor, vamos começar olhando para o código do servidor. O texto fonte completo da classe de FortuneServer localiza-se no CD-ROM acompanhante no arquivo FortuneServer.java. O seguinte é as variáveis de membro definidas na classe de FortuneServer:

private static final int  PORTNUM = 1234;
private String[]          fortunes;
private DatagramSocket    serverSocket;
private Random            rand = new Random(System.currentTimeMillis());

O membro de PORTNUM representa o número do porto usado por Fortune. O valor de PORTNUM-1234-is arbitralmente escolhido; a coisa importante consiste em que é maior do que 1.024. A variável de membro de fortunes é uma tabela de cadeias que mantêm o texto das fortunas reais. A variável de membro de serverSocket representa a tomada de datagrama usada para a comunicação com o cliente. A variável de membro de rand é um objeto de Random que se usa na determinação da fortuna casual a enviar-se ao cliente.

Aviso
Sem falta faça sempre os seus números de porto maiores do que 1.024 para que não estejam em conflito com nomeações de porto de servidor padrão.

O construtor de maçanetas de FortuneServer que criam a tomada de servidor:

public FortuneServer() {
  super("FortuneServer");
  try {
    serverSocket = new DatagramSocket(PORTNUM);
    System.out.println("FortuneServer up and running...");
  }
  catch (SocketException e) {
    System.err.println("Exception: couldn't create datagram socket");
    System.exit(1);
  }
}

Como pode ver, o construtor cria uma tomada de datagrama usando o número de porto especificado por PORTNUM. Se a tomada não puder criar-se, uma exceção lança-se, e as saídas de servidor. As saídas de servidor porque é bastante bem sem valor sem uma tomada para comunicar-se por.

O método que faz a maioria do trabalho na classe de FortuneServer é o método de run, que se mostra na Listagem 26.1.


A listagem 26.1. O método de run.
 1: public void run() {
 2:   if (serverSocket == null)
 3:     return;
 4:
 5:   // Initialize the array of fortunes
 6:   if (!initFortunes()) {
 7:     System.err.println("Error: couldn't initialize fortunes");
 8:     return;
 9:   }
10:
11:   // Look for clients and serve up the fortunes
12:   while (true) {
13:     try {
14:       InetAddress     address;
15:       int             port;
16:       DatagramPacket  packet;
17:       byte[]          data = new byte[256];
18:       int             num = Math.abs(rand.nextInt()) % fortunes.length;
19:
20:       // Wait for a client connection
21:       packet = new DatagramPacket(data, data.length);
22:       serverSocket.receive(packet);
23:
24:       // Send a fortune
25:       address = packet.getAddress();
26:       port = packet.getPort();
27:       fortunes[num].getBytes(0, fortunes[num].length(), data, 0);
28:       packet = new DatagramPacket(data, data.length, address, port);
29:       serverSocket.send(packet);
30:     }
31:     catch (Exception e) {
32:       System.err.println("Exception: " + e);
33:       e.printStackTrace();
34:     }
35:   }
36: }

Análise
A primeira coisa que o método de run faz é verificar para assegurar-se que a tomada é válida. Se a tomada for okey, run chama initFortunes para inicializar a tabela de cadeias de fortuna. Aprenderá sobre o método de initFortunes durante somente um momento. Uma vez que as fortunas inicializam-se, run entra em um laço de while infinito que espera por uma conexão de cliente. Quando uma conexão de cliente se descobre, um pacote de datagrama cria-se usando uma cadeia de fortuna casual. Este pacote então envia-se ao cliente pela tomada.

Desde que não quereria ter de recompilar a aplicação de servidor cada vez quando quis modificar as fortunas, as fortunas leem-se em um arquivo de texto. Cada fortuna guarda-se como uma linha única do texto no arquivo Fortunes.txt. O seguinte é uma listagem do arquivo de Fortunes.txt:

You can no more win a war than you can win an earthquake.
The highest result of education is tolerance.
The right to be let alone is indeed the beginning of all freedom.
When we lose the right to be different, we lost the right to be free.
The only vice that cannot be forgiven is hypocrisy.
We learn from history that we do not learn from history.
That which we call sin in others is experiment for us.
Few men have virtue to withstand the highest bidder.

O método de initFortunes é responsável por ler as fortunas neste arquivo e
guardá-los em uma tabela que é mais prontamente acessível. A listagem 26.2 contém o texto fonte do método de initFortunes.


A listagem 26.2. O método de initFortunes.
 1: private boolean initFortunes() {
 2:   try {
 3:     File            inFile = new File("Fortunes.txt");
 4:     FileInputStream inStream = new FileInputStream(inFile);
 5:     byte[]          data = new byte[(int)inFile.length()];
 6:
 7:     // Read the fortunes into a byte array
 8:     if (inStream.read(data) <= 0) {
 9:       System.err.println("Error: couldn't read fortunes");
10:       return false;
11:     }
12:
13:     // See how many fortunes there are
14:     int numFortunes = 0;
15:     for (int i = 0; i < data.length; i++)
16:       if (data[i] == (byte)'\n')
17:         numFortunes++;
18:     fortunes = new String[numFortunes];
19:
20:     // Parse the fortunes into an array of strings
21:     int start = 0, index = 0;
22:     for (int i = 0; i < data.length; i++)
23:       if (data[i] == (byte)'\n') {
24:         fortunes[index++] = new String(data, 0, start, i - start - 1);
25:         start = i + 1;
26:       }
27:   }
28:   catch (FileNotFoundException e) {
29:     System.err.println("Exception: couldn't find the fortune file");
30:     return false;
31:   }
32:   catch (IOException e) {
33:     System.err.println("Exception: I/O error trying to read fortunes");
34:     return false;
35:   }
36:
37:   return true;
38: }

Análise
O método de initFortunes primeiro cria um objeto de File baseado no arquivo de Fortunes.txt, que se usa para inicializar uma corrente de entrada de arquivo. O objeto de File também se usa para determinar o comprimento do arquivo de fortunas. O comprimento do arquivo é importante porque se usa para criar uma tabela de byte bastante grande para manter todas as fortunas que se leem.

initFortunes lê as fortunas no arquivo de texto com uma chamada simples ao método de read da corrente de entrada. O número de fortunas então determina-se contando o número de carateres newline ('\n') no texto de fortuna. Isto trabalha porque cada fortuna se separa por um caráter newline no arquivo. Quando o número de fortunas se estabeleceu, uma tabela de cadeia cria-se que é bastante grande para manter as fortunas. Usando newline carateres como separadores, o texto de fortuna então analisa-se e cada fortuna guarda-se na tabela de cadeias. O resultado de fim é uma tabela de cadeias que é muito mais conveniente para o acesso do que a tentativa ler um arquivo cada vez quando um cliente quer uma fortuna.

O método último na classe de FortuneServer é main, que é o ponto de entrada da aplicação de servidor:

public static void main(String[] args) {
  FortuneServer server = new FortuneServer();
  server.start();
}

Como pode ver, o método de main é muito simples; cria um objeto de FortuneServer e diz-lhe começar a correr. É isso para o lado de servidor de Fortune!

Implementar o cliente de fortuna Applet

Poderia ter-se surpreendido pela simplicidade do código de servidor de Fortune. Nesse caso então até mais vai se surpreender provavelmente pelo lado de cliente de Fortune. A classe de cliente de Fortune simplesmente chama-se Fortune e localiza-se no CD-ROM no arquivo Fortune.java. O seguinte é as variáveis de membro definidas na classe de Fortune:

private static final int  PORTNUM = 1234;
private String            fortune;

O membro de PORTNUM deve ser muito familiar para você. Note que se estabelece no mesmo valor que a variável de PORTNUM definida em FortuneServer. Isto é crítico porque o número de porto é o que ata os dois programas em conjunto. A variável de membro de fortune simplesmente mantém a fortuna atual que se expõe.

Aviso
É muito importante que os números de porto do seu cliente e servidor combinem exatamente, porque o número de porto é como o cliente e o servidor se ligam um a outro.

A Fortuna applet tenta agarrar uma fortuna do servidor logo que corra. Isto realiza-se no método de init, cujo código segue:

public void init() {
  fortune = getFortune();
  if (fortune == null)
    fortune = "Error: No fortunes found!";
}

O método de init chama getFortune para adquirir uma fortuna do servidor. Se a fortuna for inválida, uma mensagem de erro expõe-se em vez disso. O método de getFortune trata o trabalho de unir-se de fato a e adquirir uma fortuna do servidor. O código de getFortune mostra-se na Listagem 26.3.


A listagem 26.3. O método de getFortune.
 1: private String getFortune() {
 2:   try {
 3:     DatagramSocket  socket;
 4:     DatagramPacket  packet;
 5:     byte[]          data = new byte[256];
 6:
 7:     // Send a fortune request to the server
 8:     socket = new DatagramSocket();
 9:     packet = new DatagramPacket(data, data.length,
10:       InetAddress.getByName(getCodeBase().getHost()), PORTNUM);
11:     socket.send(packet);
12:
13:     // Receive a fortune
14:     packet = new DatagramPacket(data, data.length);
15:     socket.receive(packet);
16:     fortune = new String(packet.getData(), 0);
17:     socket.close();
18:   }
19:   catch (UnknownHostException e) {
20:     System.err.println("Exception: host could not be found");
21:     return null;
22:   }
23:   catch (Exception e) {
24:     System.err.println("Exception: " + e);
25:     e.printStackTrace();
26:     return null;
27:   }
28:   return fortune;
29: }

Análise
O método de getFortune primeiro cria um pacote de pedido e envia-o ao servidor. Os conteúdos deste pacote são sem importância; o ponto somente deve entrar em contato com o servidor. Depois de enviar o pacote de pedido, getFortune cria um novo pacote e usa-o para receber uma fortuna do servidor.

Como Fortune é um applet, as fortunas expõem-se graficamente via o método de paint. A listagem 26.4 contém o método de paint definido na classe de Fortune.


A listagem 26.4. O método de paint.
 1: public void paint(Graphics g) {
 2:   // Draw the title and fortune text
 3:   Font        f1 = new Font("TimesRoman", Font.BOLD, 28),
 4:               f2 = new Font("Helvetica", Font.PLAIN, 16);
 5:   FontMetrics fm1 = g.getFontMetrics(f1),
 6:               fm2 = g.getFontMetrics(f2);
 7:   String      title = new String("Today's Fortune:");
 8:   g.setFont(f1);
 9:   g.drawString(title, (size().width - fm1.stringWidth(title)) / 2,
10:     ((size().height - fm1.getHeight()) / 2) + fm1.getAscent());
11:   g.setFont(f2);
12:   g.drawString(fortune, (size().width - fm2.stringWidth(fortune)) / 2,
13:     size().height - fm2.getHeight() - fm2.getAscent());
14: }

Análise
O método de paint pode parecer um pouco complicado, mas tudo que faz executa algum de fantasia centrar e alinhamento para que o posicionamento da fortuna pareça bem. O método de paint também expõe o texto Today's Fortune: somente acima da fortuna.

O aspecto final da classe de Fortune que não cobriu é como adquirir uma nova fortuna quando o usuário clica no botão de rato. Isto trata-se no método de mouseDown, cujo código segue:

public boolean mouseDown(Event evt, int x, int y) {
  // Display a new fortune
  getFortune();
  repaint();
  return true;
}

Desde que getFortune já cuida dos detalhes implicados na obtenção de uma nova fortuna do servidor, todo o método de mouseDown tem de fazer é chamar getFortune e atualizar a tela com uma chamada a repaint. Isto sumaria o lado de cliente de Fortune, que significa que está provavelmente pronto para tomá-lo para uma volta!

Gerência de fortuna

Como já sabe, o exemplo de Fortune compõe-se de duas partes: um cliente e um servidor. O servidor de Fortune deve estar correndo para o cliente para trabalhar. Assim para começar coisas, deve dirigir primeiro o servidor usando o intérprete de Java (java); faz isto de uma linha de comando, como isto:

java FortuneServer

Outra metade de Fortune é o cliente, que é um applet que corre de dentro de um Navegador da Web compatível com Java, como Navegador de Netscape ou Microsoft Internet Explorer. Depois que convida o servidor e gerência, acende um browser e carrega um documento HTML inclusive o cliente de Fortune applet. No CD-ROM, este documento HTML chama-se Example1.html, de acordo com a demonstração de JDK padrão applets. Depois de dirigir o cliente de Fortune applet, deve ver algo semelhante ao que se mostra na Figura 26.4. Pode clicar na janela applet para recuperar novas fortunas.

A figura 26.4: O cliente de Fortuna applet.

Observar
Esta discussão sobre a gerência do exemplo de Fortune supõe que tenha o acesso a um Servidor Web ou pode simular uma conexão de rede na sua máquina local. Desde que o meu sistema do Windows 95 local não é parte de uma rede física, testei os programas simulando uma conexão de rede. Fiz isto modificando a configuração TCP/IP no meu sistema para que usasse um endereço IP específico (somente compus um endereço). Se fizer esta modificação da sua configuração de rede, não será capaz de acessar uma verdadeira rede usando TCP/IP até que o atrase, então não esqueça de restaurar coisas quando se termina.

Trivialidades: um cliente de corrente e servidor

Os programas Fortune são um bom exemplo de como usar facilidades de ligação em rede de datagrama de Java. Encontrará provavelmente, contudo, que mais problemas de ligação em rede necessitam uma aproximação de corrente. Desde que não quereria deixá-lo sentindo-me como meio programador de rede de Java, vamos olhar para um exemplo que necessita uma aproximação de tomada de corrente.

O exemplo de cliente/servidor de corrente chama-se Trivialidades e compõe-se de um servidor que faz perguntas de trivialidades e um cliente que interage com o servidor permitindo ao usuário responder às perguntas. O exemplo de Trivialidades diferencia-se do exemplo de Fortune em que há uma comunicação de duas vias contínua entre o cliente e o servidor.

O exemplo de Trivialidades trabalha como isto: O programa de servidor espera pacientemente por um cliente para unir-se. Quando um cliente se une, o servidor envia uma pergunta e espera por uma resposta. Em outro fim, o cliente recebe a pergunta e incita o usuário de uma resposta. O usuário datilografa em uma resposta que se retorna ao servidor. O servidor então verifica para ver se a resposta é correta e notifica o usuário. O servidor segue isto perguntando o cliente se quiser outra pergunta. Nesse caso as repetições de processo.

Desenho de trivialidades

É importante executar sempre um breve desenho preliminar antes que comece a produzir rapidamente o código. Com isto em mente, vamos dar uma olhada no que se necessita do servidor de Trivialidades e cliente. No lado de servidor, precisa de um programa que controla um determinado porto na máquina de anfitrião de conexões de cliente, como fez em Fortune. Quando um cliente se descobre, o servidor escolhe uma pergunta casual e envia-a ao cliente sobre o porto especificado. O servidor então entra em um estado esperar até que ouça atrás do cliente. Quando recobra uma resposta do cliente, o servidor verifica-o e notifica o cliente se é correto ou incorreto. O servidor então pergunta o cliente se quiser outra pergunta, na qual entra o outro espera o estado até que o cliente responda. Finalmente, o servidor repete o processo fazendo outra pergunta, ou termina a conexão com o cliente. No sumário, o servidor executa as seguintes tarefas:

  1. Espere por um cliente para unir-se.
  2. Aceite a conexão de cliente.
  3. Envie uma pergunta casual ao cliente.
  4. Espere por uma resposta do cliente.
  5. Verifique a resposta e notifique o cliente.
  6. Pergunte o cliente se quiser outra pergunta.
  7. Espere por uma resposta do cliente.
  8. Volte ao passo 3 se necessário.

Diferentemente da Fortuna, o lado de cliente do exemplo de Trivialidades é uma aplicação que corre de uma linha de comando. O cliente é responsável por unir-se ao servidor e esperar por uma pergunta. Quando recebe uma pergunta do servidor, o cliente expõe-no ao usuário e permite ao usuário datilografar em uma resposta. Esta resposta retorna-se ao servidor, e o cliente novamente espera pela resposta do servidor. O cliente expõe a resposta do servidor ao usuário e permite ao usuário confirmar-se se quer outra pergunta. O cliente então envia a resposta do usuário ao servidor e saídas se o usuário recusou mais perguntas. As tarefas primárias do cliente seguem:

  1. Una-se ao servidor.
  2. Espere por uma pergunta a enviar-se.
  3. Exponha a pergunta e introduza a resposta do usuário.
  4. Envie a resposta ao servidor.
  5. Espere por uma resposta do servidor.
  6. Exponha a resposta do servidor e incite o usuário a confirmar outra pergunta.
  7. Envie a resposta do usuário ao servidor.
  8. Volte ao passo 2 se necessário.

Implementar o servidor de trivialidades

Como Fortuna, o coração do exemplo de Trivialidades está no servidor. O programa de servidor de Trivialidades chama-se TriviaServer e localiza-se no CD-ROM no arquivo TriviaServer.java. O seguinte é as variáveis de membro definidas na classe de TriviaServer:

PORTNUM interno final estático privado = 1234;
WAITFORCLIENT interno final estático privado = 0;
WAITFORANSWER interno final estático privado = 1;
WAITFORCONFIRM interno final estático privado = 2;
Cadeia privada [] perguntas;
Cadeia privada [] respostas;
ServerSocket serverSocket privado;
número interno privado numQuestions;
número interno privado num = 0;
estado interno privado = WAITFORCLIENT;
rand Casual privado = novo Casual (System.currentTimeMillis ());

O WAITFORCLIENT, WAITFORANSWER e os membros de WAITFORCONFIRM são todas as constantes estatais que definem estados diferentes nos quais o servidor pode estar. Verá estas constantes na ação durante um momento. O questions e as variáveis de membro de answers são tabelas de cadeia usadas para guardar as perguntas e respostas correspondentes. A variável de membro de serverSocket acompanha a conexão de tomada de servidor. numQuestions usa-se para guardar o número total de perguntas, enquanto num é o número da pergunta atual que se pergunta. A variável de membro de state mantém o estado atual do servidor, como definido por três constantes estatais (WAITFORCLIENT, WAITFORANSWER e WAITFORCONFIRM). Finalmente, a variável de membro de rand usa-se para escolher perguntas à toa.

O construtor de TriviaServer é muito semelhante ao construtor de FortuneServer, exceto que cria um ServerSocket em vez de um DatagramSocket. Verifique-o:

public TriviaServer() {
  super("TriviaServer");
  try {
    serverSocket = new ServerSocket(PORTNUM);
    System.out.println("TriviaServer up and running...");
  }
  catch (IOException e) {
    System.err.println("Exception: couldn't create socket");
    System.exit(1);
  }
}

Também como Fortuna, o método de run em TriviaServer é onde a maioria da ação é. O texto fonte do método de run mostra-se na Listagem 26.5.


A listagem 26.5. O método de run.
 1: public void run() {
 2:   Socket  clientSocket;
 3:
 4:   // Initialize the arrays of questions and answers
 5:   if (!initQnA()) {
 6:     System.err.println("Error: couldn't initialize questions and answers");
 7:     return;
 8:   }
 9:
10:   // Look for clients and ask trivia questions
11:   while (true) {
12:     // Wait for a client
13:     if (serverSocket == null)
14:       return;
15:     try {
16:       clientSocket = serverSocket.accept();
17:     }
18:     catch (IOException e) {
19:       System.err.println("Exception: couldn't connect to client socket");
20:       System.exit(1);
21:     }
22:
23:     // Perform the question/answer processing
24:     try {
25:       DataInputStream is = new DataInputStream(new
26:         BufferedInputStream(clientSocket.getInputStream()));
27:       PrintStream os = new PrintStream(new
28:         BufferedOutputStream(clientSocket.getOutputStream()), false);
29:       String inLine, outLine;
30:
31:       // Output server request
32:       outLine = processInput(null);
33:       os.println(outLine);
34:       os.flush();
35:
36:       // Process and output user input
37:       while ((inLine = is.readLine()) != null) {
38:         outLine = processInput(inLine);
39:         os.println(outLine);
40:         os.flush();
41:         if (outLine.equals("Bye."))
42:           break;
43:       }
44:
45:       // Cleanup
46:       os.close();
47:       is.close();
48:       clientSocket.close();
49:     }
50:     catch (Exception e) {
51:       System.err.println("Exception: " + e);
52:       e.printStackTrace();
53:     }
54:   }
55: }

Análise
O método de run primeiro inicializa as perguntas e respostas chamando initQnA. Aprenderá sobre o método de initQnA durante um momento. Um laço de while infinito então introduz-se que espera por uma conexão de cliente. Quando um cliente se une, as correntes de entrada-saída apropriadas criam-se, e a comunicação trata-se via o método de processInput. Aprenderá sobre processInp ut depois. processInput constantemente processa respostas de cliente e maçanetas fazendo novas perguntas até que o cliente decida não receber mais perguntas. Isto evidencia-se pelo servidor enviando à cadeia "Bye.". O método de run então limpa a tomada de cliente e as correntes.

O método de processInput acompanha o estado de servidor e dirige a lógica do processo de pergunta/resposta inteiro. O texto fonte de processInput mostra-se na Listagem 26.6.


A listagem 26.6. O método de processInput .
 1: String processInput(String inStr) {
 2:   String outStr;
 3:
 4:   switch (state) {
 5:   case WAITFORCLIENT:
 6:     // Ask a question
 7:     outStr = questions[num];
 8:     state = WAITFORANSWER;
 9:     break;
10:
11:   case WAITFORANSWER:
12:     // Check the answer
13:     if (inStr.equalsIgnoreCase(answers[num]))
14:       outStr = "That's correct! Want another? (y/n)";
15:     else
16:       outStr = "Wrong, the correct answer is " + answers[num] +
17:         ". Want another? (y/n)";
18:     state = WAITFORCONFIRM;
19:     break;
20:
21:   case WAITFORCONFIRM:
22:     // See if they want another question
23:     if (inStr.equalsIgnoreCase("y")) {
24:       num = Math.abs(rand.nextInt()) % questions.length;
25:       outStr = questions[num];
26:       state = WAITFORANSWER;
27:     }
28:     else {
29:       outStr = "Bye.";
30:       state = WAITFORCLIENT;
31:     }
32:     break;
33:   }
34:   return outStr;
35: }

Análise
A primeira coisa a observar sobre o método de processInput é o outStr variável local. O valor desta cadeia retorna-se ao cliente no método de run quando processInput volta. Por isso vigie como processInput usa outStr para transmitir a informação atrás ao cliente.

Em FortuneServer, o estado WAITFORCLIENT representa o servidor quando é ocioso e espera por uma conexão de cliente. Entenda que cada afirmação de case em processInput() representa o servidor deixando o estado dado. Por exemplo, o WAITFORCLIENT a afirmação de case introduz-se quando o servidor acaba de deixar o estado de WAITFORCLIENT. Em outras palavras, um cliente acaba de unir-se ao servidor. Quando isto ocorre, o servidor estabelece a cadeia de produção na pergunta atual e estabelece o estado em WAITFORANSWER.

Se o servidor estiver deixando o estado de WAITFORANSWER, significa que o cliente respondeu com uma resposta. processInput verifica a resposta do cliente contra a resposta correta e estabelece a cadeia de produção consequentemente. Então estabelece o estado em WAITFORCONFIRM.

O estado de WAITFORCONFIRM representa o servidor que espera por uma resposta de confirmação do cliente. Em processInput, o WAITFORCONFIRM a afirmação de case indica que o servidor deixa o estado porque o cliente devolveu uma confirmação (sim ou não). Se o cliente respondeu sim com um y, processInput escolhe uma nova pergunta e atrasa o estado a WAITFORANSWER. De outra maneira, o servidor diz ao cliente Bye. e devolve o estado a WAITFORCLIENT para esperar uma nova conexão de cliente.

Semelhante à Fortuna, as perguntas e as respostas em Trivialidades guardam-se em um arquivo de texto. Este arquivo chama-se QnA.txt e organiza-se com perguntas e respostas em linhas alternas. Alternando, subentendo que cada pergunta se segue da sua resposta na seguinte linha, que se segue à sua vez da seguinte pergunta. O seguinte é uma listagem parcial do arquivo de QnA.txt:

What caused the craters on the moon?
meteorites
How far away is the moon (in miles)?
239000
How far away is the sun (in millions of miles)?
93
Is the Earth a perfect spere?
no
What is the internal temperature of the Earth (in degrees F)?
9000

O método de initQnA trata o trabalho de ler as perguntas e respostas do arquivo de texto e guardá-los em tabelas de cadeia separadas. A listagem 26.7 contém o texto fonte do método de initQnA.


A listagem 26.7. O método de initQnA .
 1: private boolean initQnA() {
 2:   try {
 3:     File            inFile = new File("QnA.txt");
 4:     FileInputStream inStream = new FileInputStream(inFile);
 5:     byte[]          data = new byte[(int)inFile.length()];
 6:
 7:     // Read the questions and answers into a byte array
 8:     if (inStream.read(data) <= 0) {
 9:       System.err.println("Error: couldn't read questions and answers");
10:       return false;
11:     }
12:
13:     // See how many question/answer pairs there are
14:     for (int i = 0; i < data.length; i++)
15:       if (data[i] == (byte)'\n')
16:         numQuestions++;
17:     numQuestions /= 2;
18:     questions = new String[numQuestions];
19:     answers = new String[numQuestions];
20:
21:     // Parse the questions and answers into arrays of strings
22:     int start = 0, index = 0;
23:     boolean isQ = true;
24:     for (int i = 0; i < data.length; i++)
25:       if (data[i] == (byte)'\n') {
26:         if (isQ) {
27:           questions[index] = new String(data, 0, start, i - start - 1);
28:           isQ = false;
29:         }
30:         else {
31:           answers[index] = new String(data, 0, start, i - start - 1);
32:           isQ = true;
33:           index++;
34:         }
35:         start = i + 1;
36:       }
37:   }
38:   catch (FileNotFoundException e) {
39:     System.err.println("Exception: couldn't find the fortune file");
40:     return false;
41:   }
42:   catch (IOException e) {
43:     System.err.println("Exception: I/O error trying to read questions");
44:     return false;
45:   }
46:
47:   return true;
48: }

Análise
O método de initQnA é semelhante ao método de initFortunes em FortuneServer, exceto que neste caso duas tabelas se estão enchendo da alternação de cadeias. As duas tabelas são a pergunta e tabelas de cadeia de resposta. Em vez de repetir a explicação mais adiantada para initFortunes, o deixarei até você para comparar-me e contrastar as diferenças entre initFortunes e initQnA. Encontrará que as diferenças são muito pequenas e têm a ver com o fato que enche agora duas tabelas da alternação de cadeias.

O único método restante em TriviaServer é main, que segue:

public static void main(String[] args) {
  TriviaServer server = new TriviaServer();
  server.start();
}

Como o método de main em FortuneServer, todo este método de main faz é criam o servidor objetam e começam-no com uma chamada ao método de start.

Implementar o cliente de trivialidades Applet

Desde que o lado de cliente do exemplo de Trivialidades necessita que o usuário datilografe em respostas e receba respostas atrás do servidor, é mais franco para implementar como uma aplicação de linha de comando. Com certeza isto pode não ser tão atraente como um applet gráfico, mas faz muito fácil ver os eventos de comunicação como se abrem. A aplicação cliente chama-se Trivia e localiza-se no CD-ROM no arquivo Trivia.java.

O único membro definido na classe de Trivia é PORTNUM, que define o número de porto usado tanto pelo cliente como por servidor. Também há só um método definido na classe de Trivia: main. O texto fonte do método de main mostra-se na Listagem 26.8.


A listagem 26.8. O método de main.
 1: public static void main(String[] args) {
 2:   Socket          socket;
 3:   DataInputStream in;
 4:   PrintStream     out;
 5:   String          address;
 6:
 7:   // Check the command-line args for the host address
 8:   if (args.length != 1) {
 9:     System.out.println("Usage: java Trivia <address>");
10:     return;
11:   }
12:   else
13:     address = args[0];
14:
15:   // Initialize the socket and streams
16:   try {
17:     socket = new Socket(address, PORTNUM);
18:     in = new DataInputStream(socket.getInputStream());
19:     out = new PrintStream(socket.getOutputStream());
20:   }
21:   catch (IOException e) {
22:     System.err.println("Exception: couldn't create stream socket");
23:     System.exit(1);
24:   }
25:
26:   // Process user input and server responses
27:   try {
28:     StringBuffer  str = new StringBuffer(128);
29:     String        inStr;
30:     int           c;
31:
32:     while ((inStr = in.readLine()) != null) {
33:       System.out.println("Server: " + inStr);
34:       if (inStr.equals("Bye."))
35:         break;
36:       while ((c = System.in.read()) != '\n')
37:         str.append((char)c);
38:       System.out.println("Client: " + str);
39:       out.println(str.toString());
40:       out.flush();
41:       str.setl1ength(0);
42:     }
43:
44:     // Cleanup
45:     out.close();
46:     in.close();
47:     socket.close();
48:   }
49:   catch (IOException e) {
50:     System.err.println("Exception: I/O error trying to talk to server");
51:   }
52: }

Análise
A primeira coisa interessante que poderia notar sobre o método de main consiste em que procura um argumento de linha de comando. O argumento de linha de comando necessitado do cliente de Trivialidades é o endereço do servidor, como thetribe.com. Pode estar admirando-se porque o cliente de Fortune não necessitou que você especificasse um endereço de servidor. A razão consiste em que Java applets se acessa via Páginas da Web, que sempre se associam com um determinado servidor. Assim Java os applets atam-se inerentemente a um servidor e por isso podem questionar o servidor do seu endereço. Isto realizou-se no cliente de Fortune chamando o método de getHost.

Com aplicações de Java, não tem esta opção porque não há servidor inerente associado com a aplicação. Portanto tem ao código difícil o endereço de servidor ou lhe pede como um argumento de linha de comando. Muito não codifico muito porque necessita que você recompile qualquer tempo quer modificar algo. Daqui o argumento de linha de comando!

Se o argumento de linha de comando de endereço de servidor for válido (não nulo), o método de main cria a tomada necessária e correntes de entrada-saída. Então entra em um laço de while, onde processa a informação do servidor e transmite pedidos de usuário atrás ao servidor. Quando o servidor deixa de enviar a informação, o laço de while fracassa, e o método de main limpa a tomada e correntes. E isto é tudo que há ao cliente de Trivialidades!

Gerência de trivialidades

Como Fortuna, o servidor de Trivialidades deve estar correndo para o cliente para trabalhar. Para começar coisas, deve dirigir primeiro o servidor usando o intérprete de Java; isto faz-se de uma linha de comando, como isto:

java TriviaServer

O cliente de Trivialidades também se dirige de uma linha de comando, mas deve especificar um endereço de servidor como o único argumento. O seguinte é um exemplo de dirigir o cliente de Trivialidades e unir ao servidor thetribe.com:

java Trivia "thetribe.com"

Depois de dirigir o cliente de Trivialidades e responder a algumas perguntas, deve ver a produção semelhante a isto:

Server: Is the Galaxy rotating?
yes
Client: yes
Server: That's correct! Want another? (y/n)
y
Client: y
Server: Is the Earth a perfect sphere?
no
Client: no
Server: That's correct! Want another? (y/n)
y
Client: y
Server: What caused the craters on the moon?
asteroids
Client: asteroids
Server: Wrong, the correct answer is meteorites. Want another? (y/n)
n
Client: n
Server: Bye.

Sumário

Hoje aprendeu uma prosperidade da informação sobre a programação de rede de cliente/servidor em Java. Começou a lição aprendendo alguns conceitos fundamentais sobre a Internet e como se organiza como uma rede. Mais especificamente, aprendeu sobre endereços, protocolos e portos, que todos desempenham um papel crítico em comunicações de Internet. Daí, mudou à aprendizagem sobre o cliente/servidor que calcula e como Java apoia o modelo de cliente/servidor por dois tipos diferentes de tomadas: tomadas de datagrama e tomadas de corrente.

A metade última da lição de hoje conduziu-o por meio da criação de dois programas de cliente/servidor completos. Estes dois exemplos demonstram as aproximações se diferenciam da programação de rede de cliente/servidor permitida pelo datagrama de Java e classes de tomada de corrente. Ambos destes exemplos devem servir de uma base sólida dos seus próprios projetos de cliente/servidor.

Se toda a codificação durante os poucos últimos dias tenha tomado o seu pedágio em você, descanse - a lição de amanhã não implica absolutamente nenhuma programação. A lição de amanhã cobre a extensão de padrão de Java APIs, que são um novo jogo de extensões API que prometem acrescentar todas as espécies de características arrumadas a Java. Não são excitou?

Perguntas e Respostas

Q:
Porque o paradigma de cliente/servidor é tão importante na programação de rede de Java?
A:
O modelo de cliente/servidor integrou-se em Java porque resultou repetidas vezes ser superior a outras aproximações de ligação em rede. Dividindo o processo de servir dados do processo de observação e trabalho com dados, a aproximação de cliente/servidor provê desenvolvedores de rede da liberdade de implementar uma ampla variação de soluções para problemas de rede comuns.
Q:
Porque as tomadas de datagrama são menos convenientes para comunicações de rede do que tomadas de corrente?
A:
A razão primária é velocidade, porque não tem modo de saber quando a informação transferida por uma tomada de datagrama conseguirá o seu destino. Admitidamente, realmente não sabe com certeza quando os dados de tomada de corrente virão ao seu destino também, mas pode permanecer seguro será mais rápido do que com a tomada de datagrama. Também, as transferências de tomada de datagrama têm a complexidade adicional do que tem necessidade de reorganizar os dados de entrada, que são um aborrecimento desnecessário e demorado exceto em circunstâncias muito raras.
Q:
Como incorporo a Fortuna em um Web site?
A:
Além simplesmente inclusive o cliente applet em um documento HTML que se serve pelo seu Servidor Web, também deve assegurar-se que o servidor de Fortune (FortuneServer) corre na máquina de Servidor Web. Sem o servidor de fortuna, os clientes são sem valor.
Q:
Como modifico as perguntas de trivialidades e responde por Trivialidades?
A:
Simplesmente edita o arquivo de texto de QnA.txt e acrescenta tantas perguntas e respostas como quer. Somente assegure-se que cada pergunta e a resposta aparecem na sua própria linha, e que cada resposta imediatamente segue a sua pergunta correspondente.