Desenvolvendo aplicações PALM com socket usando a biblioteca NetLib

 

Neste artigo será apresentado como escrever um aplicativo para Palm OS utilizando sockets fornecidos pela biblioteca de sistema NetLib presente no Palm OS. Através dos métodos descritos, será possível criar um aplicativo semelhante ao wget existente no Linux. O wget é um programa que faz download de arquivos da internet, usando o protocolo HTTP. Assumimos neste artigo que o leitor é familiar com os princípios de programação para a plataforma Palm OS.

 

A programação de socket dentro do Palm OS difere levemente da programação de sockets nos sistemas Unix. Conseqüentemente, todos que já tenham feito isto anteriormente, em conformidade com o Unix, não devem ter nenhum problema em aplicar sua solução no Palm OS.

 

Inicializando o NetLib

Com o objetivo de aproveitar os benefícios fornecidos por uma biblioteca de sistema, em primeiro lugar, é preciso determinar seu número de referência. Ele é necessário para que se possa utilizar qualquer função pertencente a essa biblioteca em particular. Podemos obter o número de referência chamando o método SysLibFind(), fornecendo um apontador à nossa variável (AppNetRefnum) como seu parâmetro (ver Listagem 1). Naturalmente, a biblioteca que nós estamos procurando deve estar presente no sistema, e já deve ter sido carregada. Neste ponto, é interessante fazer o tratamento de erros que podem retornar do método SysLibFind().

 

Com o número de referência de nossa biblioteca, podemos abrir e inicializá-la através da função NetLibOpen(); naturalmente, nós temos que passar o número de referência como um de seus argumentos. A função tentará levantar todas as interfaces de rede que já tenham sido configuradas. Caso não exista nenhuma interface disponível, será retornado um erro. Se o NetLib já tiver sido aberto, o erro retornado pela função será netErrAlreadyOpen - que pode, com segurança, ser ignorado já que ele simplesmente nos informa que abrir a biblioteca não era necessário. Vale à pena recordar-se disto a fim de não abortar a operação inteira devido a este erro.

 

Outra coisa que se deve fazer antes de começar a manipular sockets é testar as conexões de rede. Para tanto, deve-se usar a função NetLibConnectionRefresh(), que verifica e, opcionalmente, abre todas as conexões (se nós passarmos o valor true como seu segundo argumento).

 

Uma vez que tenhamos concluído o uso de todas as operações estabelecidas no socket, devemos “limpar o sistema” fechando a NetLib com a função NetLibClose(). Isto protegerá o programa de erros mais tarde, como netErrAlreadyOpen (este erro indica que existem conexões abertas no sistema).

 

Endereçamento

Os endereços de um socket no Palm OS são armazenados em uma estrutura apropriada, assim como no Unix. Como em nosso caso usamos os endereços dos sockets de internet, podemos usar a estrutura de NetSocketAddrINType, na qual armazenamos três valores. A família do endereço determina a sintaxe de um endereço. Nós estaremos nos conectando à internet, então utilizaremos endereços IP. O que resta são os números da porta e o endereço de IP do servidor remoto. Se o usuário fornecer o nome do servidor ao invés de um endereço IP, isto terá que ser considerado de alguma forma. Felizmente, o NetLibGetHostByName() pode nos ajudar com isto. Para que possamos utilizá-lo, necessitaremos de outra estrutura que armazene a informação do servidor - NetHostInfoBufType. Os argumentos da função NetLibGetHostByName() serão a cadeia contendo o nome do servidor e um apontador para a estrutura de saída. Depois que a função tiver sido executada, NetHostInfoBufType conterá um array chamado address, na qual todos os endereços IP do servidor em questão estão armazenados. O tamanho deste array depende da constante netDNSMaxAddresses, de forma que ela possa conter mais de um endereço IP. Nós, por outro lado, estamos interessados em apenas um, e faremos referência apenas ao valor armazenado no primeiro índice (address[0]). Obtido o endereço de IP do servidor, podemos escrevê-lo na estrutura de endereço anteriormente mencionada, NetSocketAddrINType.

 

Utilizaremos então esta estrutura com o objetivo de vincularmos o nosso socket a um servidor remoto.

 

Listagem 1. Inicialização da biblioteca do NetLib.

Err err, errcode;

UInt8 allup;

UInt16 AppNetRefnum = 0;

 

// Procura pela biblioteca do sistema

err = SysLibFind("Net.lib", &AppNetRefnum);

if (err || !AppNetRefnum) return err;

 

// Abre a biblioteca NetLib

NetLibOpen(AppNetRefnum, &errcode);

if (errcode && errcode != netErrAlreadyOpen)

return errcode;

 

// Atualiza as conexões de rede

NetLibConnectionRefresh(AppNetRefnum,

true, &allup, &errcode);

if (errcode) {

 NetLibClose(AppNetRefnum, true);

 return errcode;

}

(...)

NetLibClose(AppNetRefnum, true);

 

Sockets

Está mais do que na hora de realmente abrir um socket de rede. Para fazermos isso, necessitaremos de algumas informações. Para começar, necessitamos do domínio – o valor que passamos para o NetSocketAddrINType como sendo a família do endereço (netSocketAddrINET) (ver Listagem 2). A seguir, definiremos o tipo de socket, por exemplo, se a conexão utilizará datagramas ou streams. No nosso caso, temos de especificar netSocketTypeStream. Teremos também que escolher um protocolo adequado, dependendo do tipo do socket. Para um soquete seqüencial (streaming), o protocolo correto será TCP (netSocketProtoIPTCP). Passaremos todas estas informações como argumentos para a função NetLibSocketOpen(), que, por sua vez, retorna um descritor de socket que armazenaremos em uma variável do tipo NetSocketRef. Utilizaremos este descritor de socket em todas as operações neste socket.

 

Neste momento já temos um socket de rede aberto e uma estrutura de endereço. Está mais do que na hora de estabelecer uma conexão com o servidor remoto, a não ser que se utilizem os sockets de datagrama; situação na qual, nenhuma conexão tem que ser estabelecida. Neste caso, apenas fornecemos as informações de endereço de um servidor adequado em todas as operações de envio e recebimento do socket. Para conectar um socket ao servidor remoto, utilizamos a função NetLibSocketConnect(), tomando como parâmetros, o descritor do socket e um apontador à estrutura do endereço. Agora, podemos finalmente nos comunicar com o servidor remoto.

 

Tendo terminado a nossa comunicação, fechamos o socket de rede chamando a função NetLibSocketClose().

 

Comunicação com o servidor remoto

Depois que a conexão foi estabelecida, a conversação propriamente dita com o servidor é apenas uma questão de envio de consultas e do recebimento de respostas. Existem duas funções que nós usamos no envio e recebimento de informações - NetLibSend() e NetLibReceive(). Os argumentos que ambos tomam são o descritor de socket que nós obtivemos anteriormente, um caractere string servindo de buffer das informações a serem enviadas ou para as informações a serem recebidas, e um valor definindo o comprimento do buffer. Ambas as funções retornam o número de bytes que foram transmitidos - uma característica muito útil para verificar a exatidão da comunicação. Quando uma função retorna zero, ela implica que o servidor fechou o socket, significando que todas as informações já foram enviadas.

 

No caso de sockets de datagrama, nós também temos que fornecer um apontador para a estrutura de endereço, assim como o seu tamanho.

 

A esta altura de nosso aplicativo, é hora de começar a considerar o protocolo de comunicação com o servidor. Nós temos que saber o que enviar ao usuário e quais respostas deve se esperar. No exemplo de wget, o protocolo usado é HTTP, daí nós também estaremos enviando e recebendo cabeçalhos de HTTP. As respostas de um servidor de HTTP consistem no cabeçalho e nas informações. O cabeçalho pode nos dizer quais são o nome e o tamanho do arquivo transmitido, nos fornecendo informações sobre quantos bytes nós devemos escrever em um arquivo e o nome deste. Os cabeçalhos também podem conter as mensagens de erro adequadas, que devem ser levadas em conta na função de manipulação de erro de nosso aplicativo (ver Listagem 2).

 

Listagem 2. Endereçamento e comunicação com um anfitrião remoto.

char host[] = "www.palmtop.pl";

char buffer[40]; // buffer para enviar/receber dados

int bytes = 1;

NetSocketRef sock;

NetSocketAddrINType addr;

NetHostInfoBufType hostinfo;

 

NetLibGetHostByName(

AppNetRefnum, host, &hostinfo, -1, &errcode);

 

if (errcode) return errcode;

addr.family = netSocketAddrINET;

addr.port = 80;

addr.addr = hostinfo.address[0];

 

// Inicia um socket de rede

sock = NetLibSocketOpen(

AppNetRefnum, netSocketAddrINET, netSocketTypeStream,

netSocketProtoIPTCP, -1, &errcode);

 

// Conecta o socket a uma específica porta e endereço do servidor

NetLibSocketConnect(AppNetRefnum, sock, &addr,

sizeof(addr), -1, &errcode);

if (errcode) {

  NetLibSocketClose (AppNetRefnum, sock, -1, &err);

  return errcode;

}

 

// Transmite o conteúdo do buffer através do soquete para o host

NetLibSend(AppNetRefnum, sock, buffer,

StrLen(buffer), 0, 0, 0, -1, &errcode);

 

// Recupera os dados do soquete para o buffer

while(bytes > 0)

bytes = NetLibReceive(AppNetRefnum, sock,

buffer, 40, 0, 0, 0, -1, &errcode);

 

// Fecha o socket de rede

NetLibSocketClose(AppNetRefnum, sock, -1, &errcode)

 

Comunicação com o usuário

Enquanto a transmissão de informações está em progresso, é interessante manter o usuário informado a respeito do progresso do fluxo de dados. Com este propósito, os criadores do Palm OS criaram uma interface especial chamada Progress Manager.

 

Usando o Progress Manager, podemos exibir e controlar o conteúdo de uma janela de progresso. O conteúdo a ser exibido nesta janela é determinado por uma função, de controle do programador, chamada PrgCallbackFunc. Um número de diferentes estados de progresso pode ser declarado, por exemplo, Conectando, Recuperando ou Desconectando. Logo antes de conectar ao servidor remoto, nós abrimos a janela de progresso, chamando a função PrgStartDialog, dando-lhe o título da janela e um apontador à função rediscagem. Todas as operações adicionais no conteúdo da janela devem ser executadas com a função PrgUpdateDialog. Esta função torna possível que se comute entre diferentes estados de progresso, fornecendo uma função de retorno com o envio de um texto adicional ou apresentando uma mensagem de erro. Outra coisa que vale mencionar aqui é o macro PrgUserCancel, que verifica se o usuário não cancelou a operação enquanto esta estava em andamento.

 

Conclusão

Muito pode ser aperfeiçoado no nosso programa, proporcionando grandes oportunidades para um programador. Primeiro de tudo, deve-se lembrar de realçar a verificação de erros. Cada uma das funções possui uma variedade de erros que podem ser retornados; alguns dos quais devem ser levados em consideração quando da exibição de mensagens. Uma descrição detalhada de cada um deles pode ser encontrada na documentação da API do Palm OS. Além disso, a maioria das funções realizadas com sockets possui o atributo de timeout, que merece ser observado e adequadamente ajustado às nossas necessidades. Esse atributo tem a finalidade de poupar o usuário de ter que esperar pela resposta do servidor para sempre.