• Nenhum resultado encontrado

Clientes e servidores

No documento Android em Ação - 3°. Edição (páginas 194-200)

Visão geral do capítulo:  Princípios básicos de redes

CAMADAS E PROTOCOLOS

6.1.2 Clientes e servidores

Qualquer um que já tenha usado um navegador web está familiarizado como o modelo de computação cliente/servidor. Dados, em um formato ou outro, são ar- mazenados em um poderoso servidor centralizado. Clientes se conectam àquele servidor usando um protocolo designado, como HTTP, para recuperar os dados e trabalhar com eles.

Esse padrão é, claro, muito mais antigo que a web e tem sido aplicado em tudo, de terminais completamente “burros” que se conectam a mainframes até modernos aplicativos de desktop que se conectam a um servidor para somente parte do seu propósito. Um bom exemplo é o iTunes, que é principalmente um organizador e player de mídia, mas também tem uma loja onde os consumidores podem se co- nectar ao servidor para obter novo conteúdo. Em qualquer caso, o conceito é o mesmo: o cliente faz uma requisição ao servidor e o servidor responde. Esse modelo é o mesmo que a maioria dos aplicativos Android (ao menos aqueles que usam um servidor) geralmente segue. Os aplicativos Android geralmente são o cliente.

Para tratar as muitas requisições que são frequentemente para objetivos diferen- tes e que chegam quase simultaneamente a um único endereço IP, sistemas opera- cionais modernos usam o conceito de portas. As portas não são físicas, mas sim uma representação de uma área particular da memória do computador. Um servidor pode monitorar múltiplas portas designadas em um único endereço: por exemplo, uma porta para enviar emails, uma para tráfego da web, duas portas para transferên-

Verificando o estado da rede 167 cia de arquivos, etc. Cada computador com um endereço IP também suporta milha- res de portas para permitir que múltiplas conversações ocorram ao mesmo tempo.

As portas estão divididas em três faixas:

Portas bem conhecidas — 0 até 1023. Portas registradas — 1024 até 49151.

Portas dinâmicas e/ou privativas — 49152 até 65535.

As portas bem conhecidas são todas divulgadas e são somente isso — bem co- nhecidas. HTTP é a porta 80 (e HTTP Seguro, ou HTTPS, é a porta 443), FTP são as portas 20 (controle) e 21 (dados), SSH é a porta 22, SMTP é a porta 25, etc.

Além das portas bem conhecidas, as portas registradas são ainda controladas e divulgadas, mas para objetivos mais específicos. Frequentemente essas portas são usadas por um aplicativo ou companhia específicos. Por exemplo, MySQL é a porta 3306 (por padrão). Para uma lista completa de portas conhecidas e registradas, veja o registro de números de portas da Internet Corporation for Assigned Names and Numbers (ICANN): www.iana. org/assignments/port-numbers.

As portas dinâmicas ou privativas são intencionalmente não registradas, porque são usadas pela pilha TCP/IP para facilitar a comunicação. Essas portas são regis- tradas dinamicamente para cada computador e usadas na conversação. A porta di- nâmica 49500, por exemplo, pode ser usada para enviar uma requisição para um servidor web e lidar com a resposta. Quando a conversação está encerrada, a porta é reclamada e pode ser reutilizada localmente para outra transferência de dados.

Clientes e servidores se comunicam como nós com endereços, usando portas em uma rede que suporta diversos protocolos. Os protocolos que o Android usa são baseados na rede IP que a plataforma é projetada para suportar e envolvem a família

TCP/IP. Antes que você construa um aplicativo Android cliente/servidor usando a rede, você precisa realizar a tarefa de determinar o estado da conexão.

6.2

Verificando o estado da rede

O Android fornece diversas utilidades para determinar a configuração do disposi- tivo e o estado de diversos serviços, incluindo a rede. Você geralmente usa a classe

ConnectivityManager para determinar se existe conectividade de rede e obter notificações de mudanças na rede. A listagem a seguir, que é uma parte da Activ­ ity principal do aplicativo NetworkExplorer, demonstra o uso básico do Connec­ tivityManager.

lisTagem 6.1 O método onStart da Activity principal do NetworkExplorer

@Override

public void onStart() { super.onStart();

ConnectivityManager cMgr = (ConnectivityManager) this.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo netInfo = cMgr.getActiveNetworkInfo(); this.status.setText(netInfo.toString()); }

Esse pequeno exemplo mostra que você pode gerenciar o ConnectivityManager

por meio do método getSystemService() do contexto usando a constante CON­ NECTIVITY_SERVICE. Com o gerenciador, você pode obter informações da rede por meio do objeto NetworkInfo. O método toString() do objeto NetworkInfo

retorna a saída mostrada na Figura 6.2.

Claro, você normalmente só exibe a saída String de NetworkInfo, mas esse exemplo dá a você um vislumbre do que está disponível. Mas você usará frequen- temente o método isAvailable() ou isConnected() (que retorna um valor booleano) ou irá pesquisar diretamente NetworkInfo.State usando o método

getState(). NetworkInfo.State é um enum que define o estado geral da conexão. Os valores possí- veis são CONNECTED, CONNECTING, DISCONNECTED e

DISCONNECTING. O objeto NetworkInfo também fornece acesso a informações detalhadas, mas você normalmente não vai precisar de mais do que o estado básico.

Quando você souber que está conectado, pela rede móvel ou por Wi-Fi, poderá usar a rede IP. Para os fins do nosso aplicativo NetworkExplorer, vamos começar com a conexão IP mais rudimentar, um soquete, e prosseguir para os serviços web e HTTP.

6.3

Comunicação com um soquete de servidor

Um soquete de servidor é um stream no qual você pode escrever ou ler dados brutos, em um endereço

IP e porta especificados. Você pode lidar com dados e não se preocupar sobre tipos de mídia, tamanho

de pacotes, etc. Um soquete de servidor é outra abstração de rede com intuito de tornar a vida do programador um pouco mais fácil. A filosofia dos soquetes — de que tudo deve se parecer com arquivos de entrada/saída (I/O) para o desenvolve- dor — vem da família de padrões Portable Operating System Interface for UNIX

(POSIX) e foi adotada pelos principais sistemas em uso hoje.

Vamos nos mover para níveis mais altos de comunicação em rede daqui a pouco, mas vamos começar com um soquete bruto. Para isso, precisamos de um servidor monitorando uma determinada porta. O código EchoServer mostrado na listagem a seguir faz isso. Esse exemplo não é uma classe específica do Android. Em vez disso, é um servidor muito simplificado que pode rodar em qualquer máquina com Java. Vamos conectá-lo mais tarde a um cliente Android.

lisTagem 6.2 Um echo server simples para demonstrar o uso de soquete

public final class EchoServer extends Thread { private static final int PORT = 8889; private EchoServer() {}

public static void main(String args[]) { EchoServer echoServer = new EchoServer();

q

Usa java.net. ServerSocket Figura 6.2 O resultado do mé-

Comunicação com um soquete de servidor 169

if (echoServer != null) { echoServer.start(); }

}

public void run() { try {

ServerSocket server = new ServerSocket(PORT, 1); while (true) {

Socket client = server.accept(); System.out.println(“Client connected”); while (true) {

BufferedReader reader =

new BufferedReader(new InputStreamReader( client.getInputStream()));

System.out.println(“Read from client”); String textLine = reader.readLine() + “\n”; if (textLine.equalsIgnoreCase(“EXIT\n”)) {

System.out.println(“EXIT invoked, closing client”); break;

}

BufferedWriter writer = new BufferedWriter( new OutputStreamWriter(

client.getOutputStream()));

System.out.println(“Echo input to client”); writer.write(“ECHO from server: “

+ textLine, 0, textLine.length() + 18); writer.flush(); } client.close(); } } catch (IOException e) { System.err.println(e); } } }

A classe EchoServer que estamos usando é I/O Java bem simples. Ela estende

Thread e implementa run, para que cada cliente que se conecta possa ser maneja- do no próprio contexto. Usamos então um ServerSocket 

q

para monitorar uma porta definida. Cada cliente é então uma implementação de um Socket. A entrada do cliente é alimentada em um BufferedReader de onde cada linha é lida 

w

. A única consideração especial que esse servidor simples possui é que se a entrada for

EXIT, ela interrompe o loop e sai 

e

. Se a entrada não solicitar uma saída, o servidor ecoa a entrada de volta para o OuputStream do cliente com um BufferedWriter.

Esse exemplo é uma boa, embora intencionalmente básica, representação do que um servidor faz. Ele gerencia entradas, usualmente em um thread separado, então responde ao cliente, baseado na entrada. Para testar esse servidor antes de usar o Android, você pode se comunicar via telnet à porta especificada (depois que o servidor estiver rodando, é claro) e digitar alguma entrada. Se tudo estiver funcio- nando, ele irá ecoar a saída.

Para rodar o servidor, você precisa invocá-lo localmente com Java. O servidor tem um método principal, então ele irá rodar por conta própria. Inicie-o na linha de co- mandos ou no seu IDE. Saiba que quando você se conecta a um servidor a partir do

Lê entrada com BufferedReader

w

Sai e inter- rompe o loop

e

emulador (esse ou qualquer outro), você precisa se conectar ao endereço IP do host onde você executa o processo do servidor, e não ao loopback (não ao 127.0.0.1). O emulador pensa em si mesmo como 127.0.0.1, então use o endereço não loopback do host do servidor quando tentar se conectar com o Android. (Você pode desco- brir o endereço IP da sua máquina na linha de comandos, digitando ifconfig no Linux ou Mac e ipconfig no Windows).

A parte do cliente nesse exemplo é onde o NetworkExplorer começa, com o método callSocket() da ActivitySimpleSocket, mostrado na listagem a seguir.

lisTagem 6.3 Um cliente Android invocando um recurso de soquete do servidor, o echo server

public class SimpleSocket extends Activity {

. . . declarações de variável View omitidas por brevidade

@Override

public void onCreate(final Bundle icicle) { super.onCreate(icicle);

this.setContentView(R.layout.simple_socket); ... View inflation omitido por brevidade

this.socketButton.setOnClickListener(new OnClickListener() { public void onClick(final View v) {

socketOutput.setText(“”); String output = callSocket( ipAddress.getText().toString(), port.getText().toString(), socketInput.getText().toString()); socketOutput.setText(output); } }); }

private String callSocket(String ip, String port, String socketData) { Socket socket = null;

BufferedWriter writer = null; BufferedReader reader = null; String output = null; try {

socket = new Socket(ip, Integer.parseInt(port)); writer = new BufferedWriter(

new OutputStreamWriter( socket.getOutputStream())); reader = new BufferedReader( new InputStreamReader( socket.getInputStream())); String input = socketData;

writer.write(input + “\n”, 0, input.length() + 1); writer.flush();

output = reader.readLine(); this.socketOutput.setText(output); //envia EXIT e encerra

writer.write(“EXIT\n”, 0, 5); writer.flush();

. . . catches e reader, writer e socket closes omitidos por brevidade . . . onCreate omitido por brevidade

return output; }

q

Usa o método callSocket

w

Cria soquete de cliente

e

Escreve no soquete

Trabalhando com HTTP 171 Nessa listagem, usamos o método onCreate() para chamar um método auxiliar privado callSocket() 

q

e configurar a saída para TextView. Dentro do método

callSocket(), criamos um soquete para representar o lado do cliente da nossa conexão 

w

e estabelecemos um gravador para a entrada e um leitor para a saída. Agora que a limpeza da casa está feita, escrevemos no soquete 

e

, que se comunica com o servidor e obtém o valor de saída a retornar 

r

.

Um soquete é provavelmente o uso de rede de nível mais baixo que você vai encontrar no Android. Usar um soquete bruto, embora tenha abstraído bastante, ainda deixa muitos dos detalhes para você, especialmente os detalhes de ramais e filas do lado do servidor. Embora você possa encontrar situações nas quais terá que usar um soquete bruto (o lado do servidor já está construído) ou escolher usar um por um motivo ou outro, soluções de alto nível como alavancagem HTTP geralmen- te são mais vantajosas.

6.4

Trabalhando com HTTP

Como discutimos na seção anterior, você pode usar um soquete bruto para transfe- rir dados de IP para e de um servidor com o Android. É importante conhecer essa abordagem para que você saiba que tem essa opção e entenda um pouco sobre os detalhes envolvidos. Apesar disso, você pode querer evitar essa técnica quando pos- sível e, em vez disso, tirar vantagem dos produtos do servidor existente para enviar seus dados. O modo mais comum para fazer isso é usar um servidor web e HTTP.

Agora vejamos o modo de fazer requisições HTTP de um cliente Android e enviá- -los para um servidor HTTP. Vamos deixar o servidor HTTP gerenciar todos os deta- lhes de soquete e vamos nos concentrar no nosso aplicativo cliente Android.

O próprio protocolo HTTP é bem complexo. Se você não estiver familiarizado com ele ou quiser os detalhes completos, há informações prontamente disponíveis por meio de Requests for Comments (RFCs) (com na versão 1.1: www.w3.org/Pro- tocols/rfc2616/rfc2616.html). Em resumo, o protocolo é sem estado (stateless) e envolve diversos métodos diferentes que permitem aos usuários fazer requisições a servidores e a esses servidores retornar respostas. Toda a web é, claro, baseada em

HTTP. Além dos conceitos mais básicos, existem modos de trocar dados com requi- sições e respostas e de autenticar servidores. Aqui vamos usar alguns dos métodos e conceitos mais comuns para comunicação com recursos de rede a partir de aplica- tivos Android.

Para começar, vamos recuperar dados usando requisições HTTPGET para uma simples página HTML, usando a API padrão java.net. A partir daí, vamos exami- nar o uso da API Apache HttpClient incluída no Android. Depois vamos usar di- retamente o HttpClient para conhecê-lo. Vamos também criar uma classe auxiliar,

HttpRequestHelper, que você vai usar para simplificar o processo e encapsular os detalhes. Essa classe — e a API de rede Apache em geral — tem algumas vantagens sobre realizar a comunicação de rede com java.net, como você verá. Quando a classe auxiliar estiver no lugar, vamos usá-la para fazer requisições HTTP e HTTPS, tanto

GET quanto POST, e examinar autenticação básica.

Nossa primeira requisição HTTP será uma chamada HTTPGET usando HttpUrl­ Connection.

No documento Android em Ação - 3°. Edição (páginas 194-200)