Programação sobre IPX em MS-DOS

André Moreira (andre@dei.isep.ipp.pt)
Professor 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.C

Para 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 IPXheader

A 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 ECB

Trata-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 IPXsetUpSend

Esta 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 IPXsetUpReceive

Esta 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 IPXinit

Esta 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 IPXopenSocket

Esta 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 IPXcloseSocket

Mesmo 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 IPXdoSendPacket

A 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 IPXlistenForPacket

Esta 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 IPXcancelEvent

Quando 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 IPX

A 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 Datagramas

Os passos necessários para enviar datagramas são os seguintes:

  • Abrir uma porta.
  • Preparar o ECB e o cabeçalho IPX para o envio.
  • Enviar o datagrama.

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 Datagramas

A 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:

  • Abrir uma porta.
  • Preparar o ECB e o cabeçalho IPX para a recepção.
  • Colocar o ECB em escuta.
  • Consultar ciclicamente a inUseFlag do ECB para saber se chegou um datagrama.

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 Datagramas

No 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 Filtragem

O 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 IPX

Segundo este modelo existem duas aplicações (cliente e servidor) com papeis distintos cujo diálogo obdece a principios bem definidos:

  • O servidor é passivo e aguarda o contacto de clientes, para o efeito escuta numa porta previamente combinada.
  • O contacto consiste, na sua forma mais simples na transmissão de um "datagrama" contendo um pedido a executar. O servidor executa o pedido e geralmente devolve uma resposta ao cliente.
  • O cliente toma a iniciativa de contactar o servidor enviando um pedido, de seguida aguarda que este lhe responda.

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).

  • O servidor anuncia-se periodicamente em "broadcast". Ao receberem o "datagrama" de anuncio os clientes ficam a conhecer o endereço do servidor.
  • O cliente pede um servidor em "broadcast". O servidor responde e deste modo seu endereço fica a ser conhecido.
  • O cliente envia o pedido em "broadcast".

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 IPX

No 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:

  • Cada aplicação vai-se anunciando em "broadcast" e simultaneamente vai escutando "datagramas" de anuncio de potênciais parceiros.
  • Assim que recebe um "datagrama" de anúncio fixa-se a esse parceiro e passa a uma segunda fase em que deixa de se anunciar em "broadcast" a passa a emitir apenas para o parceiro.
  • Na segunda fase anuncia-se apenas para o parceiro e espera até receber um "datagrama" do parceiro que não tenha sido enviado em "broadcast". Mesmo após receber um "datagrama" nas condições indicadas deve ainda enviar mais alguns "datagramas" ao parceiro para se certificar que ele também recebe um "datagrama" nas condições necessárias.

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