Programação sobre IPX em MS-DOSProfessor Adjunto do Departamento de Engenharia Informática do ISEP O protocolo IPX é meio base de transferencia de informação na arquitectura cliente servidor das redes NetWare. Nesta arquitectura (cliente-servidor), todas as comunicações ocorrem entre as máquinas clientes e o servidor NetWare. As estações clientes utilizam uma grande variedade de sistemas operativos, tais como: DOS/Windows; UNIX; OS/2; System 7 (Mac); Windows/95 ou Windows/NT. O servidor utiliza um sistema operativo multi-processo, especificamente desenvolvido para funcionar como servidor e gestor de comunicações. Para que as máquinas clientes possam usar o servidor necessitam do "software" cliente NetWare, que por sua vez necessita do protocolo IPX para comunicar com este. O protocolo IPX pode no entanto ser usado para outras finalidades, tais como a comunicação directa entre postos de trabalho (peer-to-peer) ou como suporte para o protocolo SMB dos servidores Microsoft. Em ambiente DOS, o acesso ao driver IPX é conseguido através da interrupção 7A, geralmente, na entrada, o registo BX contém um número que identifica a função a realizar, e na saída o registo AL contém o resultado da operação, sendo usado o valor zero para as operações com sucesso e outros valores para representar erros ocorridos. ECB ("Event Control Block")Para enviar ou receber datagramas IPX são necessárias duas estruturas, o cabeçalho do datagrama e um ECB, o ECB é uma estrutura que fica associada ao evento de envio ou recepção do datagrama. A estrutura do ECB é composta por vários campos, que são de preenchimento obrigatório, assim para enviar um datagrama é necessário definir os campos ESR Address, Socket Number, Immediat Address e o buffer de dados a enviar. Para receber um datagrama é necessário preencher os campos ESR Address, Socket Number e o buffer onde serão colocados os dados recebidos. O campo "ESR Address" contém o endereço de uma rotina de interrupção que será invocada quando o evento ocorre (Event Service Routine). Este método de lidar com os eventos não será aqui utilizado pelo que este campo terá o valor zero. O campo "Socket Number" contém o número da porta que é usada para o envio ou recepção do datagrama. O campo "Immediat Address" contém o endereço físico da máquina para onde o datagrama deve ser enviado ou de onde foi enviado, conforma se trate de uma emissão ou recepção. Quando as comunicações envolvem routers, isto é não se processam na mesma rede, para proceder ao envio de um datagrama, deverá ser definido no campo "Immediat Address", o endereço físico do router por onde o datagrama deve ser encaminhado. Após a recepção de um datagrama, este campo contém o endereço físico do router de onde ele veio. Definições em IPXLIB.CPara facilitar o desenvolvimento de aplicações sugere-se a utilização das funções contidas no ficheiro IPXLIB.C, que se passam a descrever. Estruturas IPXaddress e IPXheaderA estrutura IPXaddress define o formato de um endereço IPX completo.
typedef struct
{
BYTE net[4], node[6], socket[2];
}
IPXaddress;
A estrutura IPXheader define o formato do cabeçalho de um datagrama IPX, antes de se proceder ao envio de um datagrama, é necessário preencher os campos "destination" e "type".
typedef struct
{
WORD checkSum, length;
BYTE tranportControl, type;
IPXaddress destination, source;
}
IPXheader;
Após a recepção de um datagrama, esta estrutura pode ser consultada, por exemplo para verificar de onde provém. Estrutura ECBTrata-se da estrutura que está associada aos eventos de envio e recepção de datagramas, antes das quais será necessário inicializar esta estrutura com os valores adequados.
typedef struct
{
WORD link[2];
BYTE far *ESRaddress;
BYTE inUseFlag, compCode;
BYTE ECBsocket[2];
BYTE IPXworkSpace[4], driverWorkSpace[12], immediateAddress[6];
WORD fragCount;
ECBfragment fragDesc[2];
}
ECB;
A seguir vão ser abordadas funções que realizam estas operações de inicialização, libertando o programador desses detalhes. Os campos "inUseFlag" e "compCode" são importantes para controlar a ocorrência dos eventos, assim após a ocorrência do evento o campo "inUseFlag" passa a ter o valor zero, por exemplo após a recepção ou envio de um datagrama. Depois da ocorrência do evento é possível saber foi bem sucedido consultando o campo "compCode" que deverá ter o valor zero. Função IPXsetUpSendEsta função prepara um ECB e um cabeçalho IPX para o envio de um datagrama. void IPXsetUpSend( ECB *ecb, IPXheader *ipx, BYTE *toNet, BYTE *toNode, WORD toSocket, WORD socket, void *buffer, WORD buffSize, void (*esr)() ) O parâmetro "toNet" é um apontador para um buffer de 4 bytes contendo o endereço da rede de destino, "toNode" é um apontador para um buffer de 6 bytes contendo o endereço de nó do destino e "toSocket" é um inteiro sem sinal que contém o número da porta de destino. O parâmetro "socket" contém o número da porta a usar para o envio, que deverá ter sido previamente aberta com a função IPXopenSocket, "buffer" é um apontador para os dados a enviar e "buffSize" o número de bytes a enviar. Como já foi referido, o parâmetro "esr" deverá ter o valor zero. Função IPXsetUpReceiveEsta função prepara um conjunto ECB e cabeçalho IPX para a recepção de um datagrama. void IPXsetUpReceive( ECB *ecb, IPXheader *ipx, WORD socket, void *buffer, WORD buffSize, void (*esr)() ) Os parâmetros são semelhantes aos da função anterior, não sendo no entanto definido qualquer endereço, já que o datagrama a receber pode ter qualquer proveniência, desde que sejam enviados para a porta indicada em "socket". Função IPXinitEsta função tem o objectivo de verificar se o driver do protocolo IPX está instalado, devendo por isso ser invocado no inicio da aplicação:
BYTE IPXinit(BYTE exitFlag)
{
union REGS out,in;
in.x.ax=0x7A00;
int86(0x2F,&in,&out);
if(exitFlag && !out.h.al) {printf("\nIPX not Installed -> exiting...\n");exit(1);}
return(out.h.al);
}
Se o driver está instalado devolve FF, caso contrário devolve zero, sendo possível neste caso finalizar imediatamente a aplicação. Função IPXopenSocketEsta função abre uma porta para uso da aplicação (envio e recepção de datagramas). Esta é uma operação que deve ser a primeira operação a realizar após se verificar que o driver IPX está disponível. #define TEMP_SOCKET_TYPE 0x00 #define PERM_SOCKET_TYPE 0xFF #define DYNAMIC_SOCKET 0x0000 BYTE IPXopenSocket(void *socket, BYTE type) O parâmetro "socket" é um apontador para um inteiro sem sinal que contém o número da porta a abrir. Pode ser utilizado o valor DYNAMIC_SOCKET para que o driver defina automaticamente um número de porta adequado. Existem duas modalidades de porta: permanentes ou temporárias. Geralmente devemos usar portas temporárias, neste caso quando a aplicação termina a porta é automaticamente fechada. As portas permanentes são importantes quando está a ser criado um programa TSR, assim quando o TSR se instala em memória e termina, a porta permanece aberta. As portas funcionam como endereços de aplicações numa dada máquina, graças aos números de porta é possivel a coexistencia de diversas aplicações IPX numa mesma máquina que comunicam sem interferências entre sí. O "driver" assegura que numa mesma máquina nunca existem duas aplicações a usar a mesma porta. Após a invocação desta função os "datagramas" recebidos pela máquina, destinados à porta em questão começam a ser guardados em fila. Note-se que todos os "datagramas" recebidos numa máquina e que se destinam a uma porta que não está aberta por nenhuma aplicação são simplesmente ignorados. Função IPXcloseSocketMesmo que as portas sejam temporárias, é conveniente fechar as mesmas antes de terminar a aplicação, para o efeito deve ser usada esta função. void IPXcloseSocket(WORD socket) Função IPXsendPacket e IPXdoSendPacketA função IPXsendPacket permite o envio de um datagrama, nas condições definidas previamente com a função IPXsetUpSend. void IPXsendPacket(ECB *ecb) Esta função pode não originar o envio imediato do datagrama, por exemplo devido ao facto de o driver IPX estar no momento ocupado com outra tarefa, assim é necessário verificar se o campo "inUseFlag" já tem o valor zero, se não for esse o caso o controlo deverá ser passado ao driver para este continuar a tarefa de envio, invocando IPXdoSendPacket em lugar de IPXsendPacket, obtém-se esse efeito automaticamente: #define IPXdoSendPacket(ecb) IPXsendPacket(&ecb); while(ecb.inUseFlag) IPXrelinquishControl() Note-se que o parâmetro é o ECB e não o seu endereço. Função IPXlistenForPacketEsta função coloca o ECB indicado em escuta, este deverá ter sido previamente preparado com a função IPXsetUpReceive. void IPXlistenForPacket(ECB *ecb) Supondo que não são utilizadas ESRs, para saber se chegou um datagrama, é necessário consultar periodicamente o campo "inUseFlag", quando tiver o valor zero significa que chegou um datagrama. Quando um datagrama é recebido, o ECB deixa de estar em escuta, por este motivo, para não perder datagramas, é aconselhável ter mais do que um ECB em escuta em cada porta, assim quando um datagrama é recebido, um dos ECB deixa de estar em escuta, mas os outros continuam. Função IPXcancelEventQuando se pretende terminar a aplicação, é necessário ter o máximo cuidado com a existência de eventos pendentes, ou seja a existência de ECBs com inUseFlag diferente de zero. Tome-se o exemplo da recepção de datagramas, foi usada a função IPXsetUpReceive para indicador ao driver, entre outros parâmetros, a localização do buffer onde deverá colocar os dados. Quando a aplicação termina, a zona de memória usada pela aplicação é liberta, e com ela os locais onde estavam armazenadas todas as estruturas tais como o ECB, o cabeçalho IPX e o buffer. Contudo o driver IPX continua à espera da ocorrência do evento e quando o datagrama chega tentará usar essas zonas de memória. Para evitar esta situação que em regra geral leva a um bloqueio da máquina, todos os eventos pendentes devem ser cancelados antes determinar a aplicação, para esse efeito deve ser usada esta função. BYTE IPXcancelEvent(ECB *ecb) Se o evento foi cancelado a função devolve o valor zero. Emissão e Recepção de Datagramas IPXA emissão e recepção de datagramas obedece a procedimentos bem definidos baseados nas funções anteriormente descritas, vai-se agora apresentar esses procedimentos base, com alguns exemplos. Emissão de DatagramasOs passos necessários para enviar datagramas são os seguintes:
A seguir apresenta-se um exemplo muito simples que se limita a enviar um "datagrama" em "broadcast" (FF:FF:FF:FF:FF:FF), na rede corrente (00:00:00:00), para a porta 8383 (Hex.):
#include "ipxlib.c"
ECB sendECB;
IPXheader IPX;
char *buff="Texto enviado como teste";
WORD myPort=0x0808;
/* Endereço de Destino do Datagrama */
BYTE toNet[4]={0,0,0,0};
BYTE toNode[6]={0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
WORD toPort=0x8383;
void main(void) {
IPXinit(1);
IPXopenSocket(&myPort,TEMP_SOCKET_TYPE);
IPXsetUpSend(&sendECB, &IPX, toNet, toNode, toPort, myPort, buff, 1+strlen(buff),0);
IPXdoSendPacket(sendECB);
IPXcloseSocket(myPort);
}
Recepção de DatagramasA recepção de datagramas é ligeiramente mais complexa pois trata-se de eventos assíncronos, não controlados pela aplicação, os passos a seguir são os seguintes:
O exemplo a seguir apresentado limita-se a receber um "datagrama" e sair, utiliza apenas um ECB:
#include "ipxlib.c"
ECB rcvECB;
IPXheader IPX;
char buff[80];
WORD myPort=0x8383;
void main(void) {
IPXinit(1);
IPXopenSocket(&myPort,TEMP_SOCKET_TYPE);
IPXsetUpReceive(&rcvECB, &IPX, myPort, buff, 79,0);
IPXlistenForPacket(&rcvECB);
while(rcvECB.inUseFlag);
if(!rcvECB.compCode) puts(buff);
IPXcloseSocket(myPort);
}
Recepção de Vários DatagramasNo exemplo anterior apenas se pretendia receber um datagrama, na prática, o mais comum é a recepção de vários datagramas, o exemplo anterior poderia ser facilmente modificado para atingir este objectivo:
...
void main(void) {
IPXinit(1);
IPXopenSocket(&myPort,TEMP_SOCKET_TYPE);
IPXsetUpReceive(&rcvECB, &IPX, myPort, buff, 79,0);
do {
IPXlistenForPacket(&rcvECB);
while(rcvECB.inUseFlag); if(!rcvECB.compCode) puts(buff);}
while(strcmp(buff, "sair"));
IPXcloseSocket(myPort);
}
Recepção com FiltragemO driver IPX não proporciona qualquer mecanismo de filtragem dos datagramas recebidos, isto é uma vez executada a função IPXlistenForSocket, todos os datagramas que são enviados para a porta usada serão aceites. Contudo, em muitos casos será conveniente atender apenas aos "datagramas" com uma determinada origem, o exemplo seguinte mostra a implementação deste mecanismo:
#include "ipxlib.c"
ECB rECB; IPXheader rIPX;
char buff[80];
WORD myPort=0x8383;
BYTE fromNode[6]={0x34,0xE3,0x12,0xAB,0x38,0xAC};
void main(void) {
char sair=0;
IPXinit(1);
IPXopenSocket(&myPort,TEMP_SOCKET_TYPE);
IPXsetUpReceive(&rECB, &IPX, myPort, buff, 80,0);
IPXlistenForPacket(&rECB);}
do
{
if(!rECB.inUseFlag)
{
if(!memcmp(IPX.source.node, fromNode,6))
{
puts(buff);
if(!strcmp(buff,"sair")) sair=1;
}
IPXlistenForPacket(&rECB);
}
} while(!sair);
IPXcloseSocket(myPort);
}
Como se pode verificar, antes de o conteúdo do datagrama ser considerado, verifica-se se o endereço de nó de origem do mesmo coincide com o pretendido. Modelo Cliente-Servidor em IPXSegundo este modelo existem duas aplicações (cliente e servidor) com papeis distintos cujo diálogo obdece a principios bem definidos:
Numa rede IPX, o principal problema consiste no cliente conhecer a localização (endereço) do servidor. Existem várias abordagens, numa rede local as mais comuns utilizam "broadcast". Devemos recordar que o "broadcast" apenas tem efeito numa dada rede, se existem diversas redes interligadas por "routers" e pretendemos o contacto entre redes distintas, seria necessário efectuar o "broadcast" em cada uma das redes e não apenas na rede corrente (00:00:00:00).
Destas soluções uma das mais simples de implementar é a última, note-se que se tratar de vários pedidos isolados apenas o primeiro necessita de ser enviado em "broadcast". No extracto seguinte apresenta-se um exemplo de cliente que envia um "string" em "broadcast" para a porta 8383 (Hexadecimal):
#include "ipxlib.c"
ECB rECB, sECB;
IPXheader rIPX, sIPX;
char rBuff[80], sBuff[80];
WORD myPort=0x0808, servPort=0x8383;
BYTE net[4]={0,0,0,0};
BYTE node[6]={0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
void main(void) {
IPXinit(1);
IPXopenSocket(&myPort,TEMP_SOCKET_TYPE);
IPXsetUpReceive(&rECB, &rIPX, myPort, rBuff, 80,0);
IPXlistenForPacket(&rECB);
do
{
gets(sBuff);
IPXsetUpSend(&sECB, &sIPX, net, node, servPort, myPort, sBuff,
1+strlen(sBuff),0);
IPXdoSendPacket(sECB);
while(rECB.inUseFlag);
puts(rBuff);
}
while(strcmp(sBuff,"sair"));
IPXcloseSocket(myPort);
}
A seguir apresenta-se a implementação de um servidor IPX para funcionar em conjunto com o cliente anterior e que para efeitos de demonstração inverte o "string" enviado. Note-se que a resposta não é enviada em "broadcast", o endereço de proveniência do pedido é usado para o efeito.
#include "ipxlib.c"
ECB rECB, sECB;
IPXheader rIPX, sIPX;
char rBuff[80], sBuff[80], i, j;
WORD myPort=0x8383, cliPort;
BYTE net[4];
BYTE node[6];
void main(void) {
IPXinit(1);
IPXopenSocket(&myPort,TEMP_SOCKET_TYPE);
IPXsetUpReceive(&rECB, &rIPX, myPort, rBuff, 80,0);
do
{
IPXlistenForPacket(&rECB);
while(rECB.inUseFlag);
i=strlen(rBuff);j=0;
while(i) {i--; sBuff[j]=rBuff[i]; j++;}
sBuff[j]=0;
memcpy(net,rIPX.source.net,4);
memcpy(node,rIPX.source.node,6);
memcpy(&cliPort,rIPX.source.socket,2);
IPXsetUpSend(&sECB, &sIPX, net, node, cliPort, myPort, sBuff,
1+strlen(sBuff),0);
IPXdoSendPacket(sECB);
}
while(strcmp(rBuff,"sair"));
IPXcloseSocket(myPort);
}
Pelo facto de os pedidos serem enviados em "broadcast", quando existe mais do que um servidor vão ser obtidas respostas múltiplas o receptor não está a ter em consideração este aspecto, a consequência é que vão ser obtidas respostas referentes a pedidos anteriores. O problema referido deve-se ao facto de as respostas múltiplas recebidas ficarem armazenadas na fila de recepção do "driver", a melhor solução é limpar a fila e considerar apenas a última resposta.
#include "ipxlib.c"
ECB rECB, sECB;
IPXheader rIPX, sIPX;
char rBuff[80], sBuff[80];
WORD myPort=0x0808, servPort=0x8383;
BYTE net[4]={0,0,0,0};
BYTE node[6]={0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
void main(void) {
IPXinit(1);
IPXopenSocket(&myPort,TEMP_SOCKET_TYPE);
IPXsetUpReceive(&rECB, &rIPX, myPort, rBuff, 80,0);
IPXlistenForPacket(&rECB);
do
{
gets(sBuff);
IPXsetUpSend(&sECB, &sIPX, net, node, servPort, myPort, sBuff,
1+strlen(sBuff),0);
IPXdoSendPacket(sECB);
while(rECB.inUseFlag);
/* receber todos os datagramas que estao na fila */
while(!rECB.inUseFlag) IPXlistenForPacket(&rECB);
puts(rBuff);
}
while(strcmp(sBuff,"sair"));
IPXcloseSocket(myPort);
}
No caso o servidor termina quando recebe o "string" "sair", na prática os servidores mantêm-se em funcionamento. Como foi referido quando existem vários pedidos apenas o primeiro necessita de ser enviado em "broadcast". Após a recepção da primeira resposta o cliente fica a conhecer a localização do servidor. O cliente pode ser facilmente modificado neste sentido:
#include "ipxlib.c"
ECB rECB, sECB;
IPXheader rIPX, sIPX;
char rBuff[80], sBuff[80], fst=1;
WORD myPort=0x0808, servPort=0x8383;
BYTE net[4]={0,0,0,0};
BYTE node[6]={0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
void main(void) {
IPXinit(1);
IPXopenSocket(&myPort,TEMP_SOCKET_TYPE);
IPXsetUpReceive(&rECB, &rIPX, myPort, rBuff, 80,0);
IPXlistenForPacket(&rECB);
do
{
gets(sBuff);
IPXsetUpSend(&sECB, &sIPX, net, node, servPort, myPort, sBuff,
1+strlen(sBuff),0);
IPXdoSendPacket(sECB);
while(rECB.inUseFlag);
puts(rBuff);
if(fst)
{
memcpy(net,rIPX.source.net,4);
memcpy(node,rIPX.source.node,6);
memcpy(&servPort,rIPX.source.socket,2);
/* receber todos os datagramas que estao na fila */
while(!rECB.inUseFlag) IPXlistenForPacket(&rECB);
fst=0;
}
else IPXlistenForPacket(&rECB);
}
while(strcmp(sBuff,"sair"));
IPXcloseSocket(myPort);
}
Note-se que como apenas o primeiro pedido é enviado em "broadcast", apenas temos de tratar respostas múltiplas nesse caso. O cliente, tal como está, bloqueia quando nenhum servidor responde, uma melhoria adicional consiste em desbloquear essa situação:
#include "ipxlib.c"
ECB rECB, sECB;
IPXheader rIPX, sIPX;
char rBuff[80], sBuff[80], tout;
WORD myPort=0x0808, servPort=0x8383;
BYTE net[4]={0,0,0,0};
BYTE node[6]={0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
void main(void) {
IPXinit(1);
IPXopenSocket(&myPort,TEMP_SOCKET_TYPE);
IPXsetUpReceive(&rECB, &rIPX, myPort, rBuff, 80,0);
IPXlistenForPacket(&rECB);
do
{
gets(sBuff);
IPXsetUpSend(&sECB, &sIPX, net, node, servPort, myPort, sBuff,
1+strlen(sBuff),0);
IPXdoSendPacket(sECB);
tout=50; /* Timeout = 5 seg. */
while(rECB.inUseFlag && tout) {tout--;delay(100);}
if(rECB.inUseFlag) puts("Timeout...");
else
{
puts(rBuff);
while(!rECB.inUseFlag) IPXlistenForPacket(&rECB);
}
}
while(strcmp(sBuff,"sair"));
IPXcloseSocket(myPort);
}
Modelo "peer-to-peer" em IPXNo modelo "peer-to-peer" não existem duas aplicações distintas com papeis bem definidos com no modelo cliente-servidor. Existe um única aplicação com duas instâncias em execução em máquinas distintas e que comunicam entre sí. O objectivo é conseguir establecer a ligação entre as duas instâncias, isto é, conseguir ter cada uma esteja a trocar informação com a outra, ignorando intervenções de terceiros e não interferindo em terceiros. Como é habitual nas redes IPX a primeira fase envolve a utilização de "broadcast" para que cada uma das instâncias fiquem a conhecer a localização (endereço) da outra. Os passos para o processo de ligação podem ser os seguintes:
No exemplo seguinte apresenta-se uma implementação deste tipo que utiliza a porta 7171, com anuncios de um em um segundo.
#include "ipxlib.c"
ECB rECB, sECB;
IPXheader rIPX, sIPX;
char *anuncio="PEER-TO-PEER EX1";
char rBuff[80], fase, try;
WORD appPort=0x7171;
BYTE net[4]={0,0,0,0};
BYTE node[6], broad[6]={0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
BYTE myAddress[12];
void main(void) {
IPXinit(1);
if(IPXopenSocket(&appPort,TEMP_SOCKET_TYPE)) {puts("A porta esta ocupada ...");exit(1);}
IPXgetInternetworkAddress(myAddress);
IPXsetUpReceive(&rECB, &rIPX, appPort, rBuff, 80,0);
IPXlistenForPacket(&rECB);
do
{
fase=1;
puts("Envio de anuncios ...");
IPXsetUpSend(&sECB, &sIPX, net, broad, appPort, appPort, anuncio,
1+strlen(anuncio),0);
do
{
IPXdoSendPacket(sECB);
while(!rECB.inUseFlag) IPXlistenForPacket(&rECB);
delay(1000);
if(!rECB.inUseFlag)
{
if(!strcmp(anuncio,rBuff))
if(memcmp(rIPX.source.node,&myAddress[4],6))
fase=2;
}
}
while(fase!=2);
puts("Anuncio recebido, tentativa de ligação ...");
/* FASE 2 */
memcpy(node,rIPX.source.node,6);
IPXsetUpSend(&sECB, &sIPX, net, node, appPort, appPort, anuncio,
1+strlen(anuncio),0);
try=20
do
{
try--;
if(fase>2) fase++;
IPXdoSendPacket(sECB);
while(!rECB.inUseFlag) IPXlistenForPacket(&rECB);
delay(1000);
if(!rECB.inUseFlag)
{
if(!strcmp(anuncio,rBuff))
if(!memcmp(node,rIPX.source.node,6))
if(memcmp(broad, rIPX.destination.node,6))
if(fase==2) fase=3;
}
}
while(fase!=10 && try);
if(!try) puts("A ligação falhou ...");
}
while(fase!=10);
puts("Ligado ...");
delay(10000);
IPXcloseSocket(appPort);
}
|