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); } |