759 lines
33 KiB
Plaintext
759 lines
33 KiB
Plaintext
ARTICLES: IPX/SPX for NetBIOS Developers
|
|
|
|
Original article: (c) Copyright Novell, 1994
|
|
Novell Professional Developer BULLETS
|
|
January 1994 (Volume 6, Number 1)
|
|
NwTP additions : (between angular brackets/ minus signs [- ... -])
|
|
|
|
NetBIOS is a popular peer-to-peer communication method that it is
|
|
supported under NetWare through a NetBIOS emulator. However, even though
|
|
NetBIOS is supported, there are definite advantages to using Novell's
|
|
"native tongue" protocols, IPX (Internet Packet eXchange) and SPX
|
|
(Sequenced Packet eXchange), when doing peer-to-peer communication.
|
|
|
|
This article discusses the advantages of using IPX/SPX and provides an
|
|
introduction to Novell's IPX and SPX protocols for developers who have a
|
|
working familiarity with NetBIOS.
|
|
|
|
Why Use IPX/SPX?
|
|
|
|
The most obvious reason to use IPX and SPX is to improve performance;
|
|
since NetWare emulates NetBIOS, processing NetBIOS commands involves more
|
|
overhead than processing IPX/SPX commands. NetWare encapsulates emulated
|
|
NetBIOS packets within IPX packets before they go out on the wire, so
|
|
moving to IPX/SPX allows you to "cut out the middleman."
|
|
|
|
You lose no connectivity by switching protocols either, since the
|
|
emulated NetBIOS layer cannot communicate with hardware NetBIOS systems.
|
|
In fact, moving to IPX/SPX gives you a net gain in connectivity; NetWare
|
|
has a 70% share of the network operating system market.
|
|
|
|
Also, since the NetBIOS emulator adds an additional layer of complexity
|
|
to packets being sent out, it is more difficult to troubleshoot problems.
|
|
Emulating NetBIOS involves an extra driver and an extra set of potential
|
|
incompatibilities. Generally speaking, since IPX and SPX are not
|
|
dramatically different from NetBIOS, it makes your job easier to work
|
|
with the protocols that NetWare is designed to support.
|
|
|
|
Datagram Services
|
|
|
|
Novell's IPX protocol provides almost the same functionality as NetBIOS
|
|
datagrams. Both specifications deliver packets on a best-effort basis,
|
|
but with no guarantee of delivery or sequencing. Both IPX and NetBIOS
|
|
also provide the capability to send packets either to a single node or to
|
|
multiple nodes. NetBIOS supports the multicast, or the sending of a
|
|
datagram to a selected group of nodes with the same group name. Since IPX
|
|
is address-based instead of name-based, this capability is not directly
|
|
supported; instead IPX must send an individual packet to each node.
|
|
|
|
NetBIOS also supports the broadcast datagram, a datagram that is
|
|
broadcast to the entire internetwork. IPX supports broadcasts, but only
|
|
to one subnet at a time. Usually, this restriction poses no problem,
|
|
since mechanisms such as the NetWare Service Advertising Protocol (SAP)
|
|
overcome this limitation.
|
|
|
|
The data portion of a NetBIOS datagram is limited in length to 512 bytes,
|
|
whereas IPX packets allow 546 bytes of data on all networks and can
|
|
sometimes be substantially larger than that depending on the maximum
|
|
packet size supported by network routers. Some networks can handle packet
|
|
sizes of 4096 bytes or more.
|
|
|
|
Session Services
|
|
|
|
As in the relationship between IPX and NetBIOS datagrams, Novell's SPX
|
|
protocol serves much the same function as the NetBIOS session. Both SPX
|
|
and NetBIOS sessions provide guaranteed delivery and sequencing of
|
|
packets, but at the cost of increased overhead.
|
|
|
|
The primary difference between the two is the supported packet size.
|
|
NetBIOS sessions support 64K packet sizes (128K with Chain Sends). SPX
|
|
has the same 546-byte packet size limitation as IPX and, in fact, SPX
|
|
allows slightly less data in a packet than IPX, since the SPX header
|
|
requires an additional 12 bytes. SPX therefore supports 534 bytes of data
|
|
on all networks with the potential for much larger packets if supported
|
|
by the routers, although attaining a 64K packet size is unlikely.
|
|
|
|
Probably the most noticeable difference between IPX/SPX and NetBIOS is
|
|
how each addresses packets. IPX/SPX addresses packets using network,
|
|
node, and socket numbers. NetBIOS uses unique names to address packets.
|
|
Each workstation can be uniquely addressed using the network and node
|
|
numbers.
|
|
|
|
A workstation can then have as many open sockets as desired for receiving
|
|
peer-to-peer data packets. Many methods exist for determining a
|
|
workstation's network, node, and destination socket number, but for
|
|
simplicity the example code in this article uses SAP to obtain this
|
|
information.
|
|
|
|
The Waiting Game
|
|
|
|
With NetBIOS, you can choose to allow most NetBIOS commands to complete
|
|
before returning control to the application, but most IPX/SPX commands
|
|
return control immediately. In other words, most IPX/SPX commands are
|
|
"no-wait" commands; there is no IPX/SPX "wait" counterpart.
|
|
|
|
Since most NetBIOS developers use the "no-wait" variants, this difference
|
|
should not pose a problem, but if you need to use a "wait," you can code
|
|
it very simply by issuing the command and then looping on the in use
|
|
field.
|
|
|
|
Asynchronous Events
|
|
|
|
IPX/SPX also has a feature that is not used with NetBIOS: the
|
|
asynchronous event. An asynchronous event can be initiated at any time
|
|
and, as the name implies, can be set to occur independent of an
|
|
application's execution path. An event could be set up, for example, to
|
|
automatically broadcast an IPX packet every 45 seconds. The application
|
|
initiating this event could then continue processing and leave the timing
|
|
and broadcasting of packets to the IPX event handler.
|
|
|
|
The Network Control Block & the Event Control Block
|
|
|
|
From a developer's perspective, the "core" of NetBIOS is the Network
|
|
Control Block (NCB). IPX and SPX are based on an Event Control Block
|
|
(ECB) and an IPX/SPX header. Figure 1 describes the fields in the ECB.
|
|
|
|
*********************************************************
|
|
Figure 1: The IPX/SPX Event Control Block [- C structure -]
|
|
|
|
void far *linkAddress Set by IPX
|
|
void (far *ESRAddress)() Equivalent to NetBIOS POST routine
|
|
BYTE inUseFlag Set when the ECB is in use, zero
|
|
when it is available
|
|
BYTE completionCode Equivalent to NetBIOS Command
|
|
Completion
|
|
WORD socketNumber Socket number associated with ECB
|
|
BYTE IPXWorkspace[4] Set by IPX
|
|
BYTE driverWorkspace[12] Set by IPX
|
|
BYTE immediateAddress[6] Node address of next "hop"
|
|
WORD fragmentCount Number of buffer fragments in packet
|
|
ECBFragment fragmentDescriptor[2] Address and size of fragment(s)
|
|
|
|
END of FIGURE 1
|
|
*********************************************************
|
|
|
|
[- *********************************************************
|
|
Figure 1a: The IPX/SPX Event Control Block (Pascal syntax)
|
|
|
|
linkAddress :Pointer Set by IPX
|
|
ESRAddress :Pointer Equivalent to NetBIOS
|
|
POST routine
|
|
InUseFlag :Byte; Set when the ECB is in use,
|
|
zero when it is available
|
|
CompletionCode :Byte; Equivalent to NetBIOS Command
|
|
Completion
|
|
SocketNumber :Word; Socket number associated
|
|
with ECB
|
|
IPXWorkspace :array[1..4] of byte; Set by IPX
|
|
DriverWorkspace :array[1..12] of byte; Set by IPX
|
|
ImmediateAddress:array[1..6] of byte; (Tnodeaddress)
|
|
Node address of next "hop"
|
|
FragmentCount :word; Number of buffer fragments
|
|
in packet
|
|
Fragment :array[1.. ] of Tfragment Address and size of
|
|
fragment(s)
|
|
|
|
(Note: this structure is declared as the Tecb type in the nwIPX unit)
|
|
|
|
END of FIGURE 1a
|
|
********************************************************* -]
|
|
|
|
Note that the ECB contains a field that has no equivalent in the NCB
|
|
called the immediate address field. This field should be populated with
|
|
the node address of the first "hop" on the way to the packet's ultimate
|
|
destination. Novell provides an API call to populate this field, the
|
|
IPXGetLocalTarget() API available in the NetWare Client SDK.
|
|
|
|
IPX Send Example
|
|
|
|
The sample code in this article includes simple examples written under
|
|
DOS with the NetWare Client SDK. Figure 2 shows a routine sending an IPX
|
|
packet.
|
|
|
|
*********************************************************
|
|
Figure 2: IPX Send [- C example -]
|
|
|
|
/* Send "Hello!" to the station at network 0x11111111, node
|
|
0x222222222222, socket 0x3333 using IPX */
|
|
|
|
void IPXSayHello()
|
|
{
|
|
char buffer[] = "Hello!";
|
|
ECB ecb;
|
|
IPXHeader header;
|
|
int transTime;
|
|
header.packetType = 4;
|
|
memset(header.destination.network, 0x11, 4);
|
|
memset(header.destination.node, 0x22, 6);
|
|
memset(header.destination.socket, 0x33, 2);
|
|
|
|
ecb.ESRAddress = NULL;
|
|
ecb.socketNumber = 0x4444;
|
|
IPXGetLocalTarget(header.destination,
|
|
ecb.immediateAddress, &transTime);
|
|
ecb.fragmentCount = 2;
|
|
ecb.fragmentDescriptor[0].address = &header;
|
|
ecb.fragmentDescriptor[0].size = sizeof(IPXHeader);
|
|
ecb.fragmentDescriptor[1].address = buffer;
|
|
ecb.fragmentDescriptor[1].size = strlen(buffer) + 1;
|
|
IPXSendPacket(&ecb);
|
|
}
|
|
|
|
END of FIGURE 2
|
|
*********************************************************
|
|
|
|
[- *********************************************************
|
|
Figure 2a: IPX Send (Pascal example)
|
|
|
|
{ Send "Hello!" to the station at network $11111111, node
|
|
$222222222222, socket $3333 using IPX }
|
|
|
|
Procedure IPXSayHello;
|
|
Var buffer:string;
|
|
ecb:Tecb;
|
|
header:TipxHeader;
|
|
transTime:Word;
|
|
begin
|
|
Buffer:="Hello!";
|
|
header.packetType := 4;
|
|
FillChar(header.destination.network,4,$11);
|
|
FillChar(header.destination.node, 6,$22);
|
|
FillChar(header.destination.socket, 2,$33);
|
|
|
|
ecb.ESRAddress:=NIL;
|
|
ecb.socketNumber:=$4444;
|
|
IPXGetLocalTarget(header.destination,
|
|
ecb.immediateAddress, transTime);
|
|
ecb.fragmentCount:=2;
|
|
ecb.fragment[1].address:= @header;
|
|
ecb.fragment[1].size := SizeOf(TIPXHeader);
|
|
ecb.fragment[2].address:= @buffer[1];
|
|
ecb.fragment[2].size:= ord(buffer[0]);
|
|
IPXSendPacket(ecb);
|
|
end;
|
|
|
|
END of FIGURE 2a
|
|
********************************************************* -]
|
|
|
|
|
|
The first apparent difference between IPX and NetBIOS is that IPX uses
|
|
two buffers where NetBIOS would use one. The first buffer is the IPX
|
|
Header containing the source and destination addresses, the packet type,
|
|
and several "housekeeping" fields. Refer to Figure 3 for a description of
|
|
the IPX header.
|
|
|
|
*********************************************************
|
|
Figure 3: IPX Header
|
|
|
|
WORD checkSum Included to conform to Xerox IDP standard
|
|
Set to FFFF by IPX
|
|
WORD length Length of entire IPX packet including
|
|
header
|
|
Set by IPX
|
|
BYTE transportControl Hop count - Set to zero by IPX
|
|
BYTE packetType IPX packet type is 4
|
|
IPXAddress destination Address the packet is sent to
|
|
[- Pascal: of type TInternetworkAddress -]
|
|
IPXAddress source Address of node sending packet set by IPX
|
|
[- Pascal: of type TinternetworkAddress -]
|
|
|
|
END of FIGURE 3
|
|
*********************************************************
|
|
|
|
The second buffer is the data to be sent. Two fields in the IPX header
|
|
must be set for an IPX send: the packet type and the destination address.
|
|
IPX packets are type 4, SPX packets are type 5. [- Note that according
|
|
to the original xerox definitions this statement is not correct. Type 4
|
|
packets are reserved for the PEP protocol. Use type 0 (undefined) when
|
|
transmitting standard IPX packets-] The destination address consists
|
|
of a four-byte network number, a six-byte node number, and a two-byte
|
|
socket number.
|
|
|
|
If these examples used an Event Service Routine (ESR), the ESR address
|
|
would be filled with the address of a procedure to be run when the send
|
|
completes, but since NULL is specified, this routine will not be run. The
|
|
ESR is equivalent to the NetBIOS POST routine. When the IPX send
|
|
executes, the rest of the fields in the IPX header are filled in
|
|
automatically, including the source address. You must specify the socket
|
|
number to be included in the source address, but the socket need not be
|
|
open to send a packet. For this example, socket number 0x4444 was
|
|
arbitrarily chosen.
|
|
|
|
The immediate address field described above must be filled in as well,
|
|
and the IPXGetLocalTarget() API call fills in this field with the
|
|
appropriate value. It is passed the final destination of the packet and
|
|
it calculates the address of the "first hop" on the way to the final
|
|
destination. Note that if the target workstation is on the same subnet as
|
|
the sending workstation the immediate address will be the same as the
|
|
final destination. Otherwise, it will be a bridge or router on the
|
|
subnet.
|
|
|
|
Each of the buffers sent in the IPX packet is considered to be a
|
|
fragment. Since there are two buffers (the IPX header and the data), the
|
|
fragment count is equal to two. The address and size of the fragments are
|
|
then entered, starting with the IPX header. As soon as all of the
|
|
relevant fields are filled, the example calls IPXSendPacket() and passes
|
|
it the address of the ECB.
|
|
|
|
Receiving an IPX packet is much like sending one from a programming
|
|
standpoint, except that you do not need to set the IPX header fields. In
|
|
the ECB, you should set the ESR address, socket number, immediate
|
|
address, and fragment descriptors.
|
|
|
|
Note about socket numbers: the socket number specified for an IPX send
|
|
does not need to be open, but for an IPX receive the socket must be open.
|
|
The API call to receive an IPX packet is IPXListenForPacket().
|
|
|
|
SPX Connection Example
|
|
|
|
Figure 4 contains a code sample that establishes an SPX connection.
|
|
Before the request for an SPX connection is submitted, several ECBs are
|
|
already listening for data (this is important). SPX temporarily "steals"
|
|
two ECBs from the available and waiting ones for connection maintenance,
|
|
and then it puts the stolen ECBs back in the pool when finished. If there
|
|
are no pending ECBs for SPX to use, it cannot send an acknowledgement to
|
|
the remote site and the connection will stall and time out.
|
|
|
|
*********************************************************
|
|
Figure 4: Establishing an SPX Connection [- C code example -]
|
|
|
|
/* Start an SPX connection with the station at network
|
|
0x11111111, node 0x222222222222, socket 0x3333, use
|
|
local socket 0x4444 */
|
|
|
|
#define NUM_BUFFS 5
|
|
|
|
void call()
|
|
{
|
|
ECB send, receive[NUM_BUFFS], connect, term;
|
|
SPXHeader sendHdr, rcvHdr[NUM_BUFFS], connHdr;
|
|
char buffer[NUM_BUFFS][80], sendbuf[] = "Hello!";
|
|
int i, ccode, packetsReceived;
|
|
WORD spxConnectionID;
|
|
|
|
for (i = 0; i < NUM_BUFFS; i++) {
|
|
receive[i].ESRAddress = NULL;
|
|
receive[i].socketNumber = 0x4444;
|
|
receive[i].fragmentCount = 2;
|
|
receive[i].fragmentDescriptor[0].address
|
|
= &(rcvHdr[i]);
|
|
receive[i].fragmentDescriptor[0].size
|
|
= sizeof(SPXHeader);
|
|
receive[i].fragmentDescriptor[1].address
|
|
= &(buffer[i]);
|
|
receive[i].fragmentDescriptor[1].size = 80;
|
|
SPXListenForSequencedPacket(receive[i]);
|
|
}
|
|
|
|
connect.ESRAddress = NULL;
|
|
connect.socketNumber = 0x4444;
|
|
connect.fragmentCount = 1;
|
|
connect.fragmentDescriptor[0].address = &connHdr;
|
|
connect.fragmentDescriptor[0].size
|
|
= sizeof(SPXHeader);
|
|
|
|
memset(connHdr.destination.network, 0x11, 4);
|
|
memset(connHdr.destination.node, 0x22, 6);
|
|
memset(connHdr.destination.socket, 0x33, 2);
|
|
|
|
ccode = SPXEstablishConnection(0, 0,
|
|
&spxConnectionID,
|
|
&connect);
|
|
printf("SPXEstablishConnection return code
|
|
= 0x%x\n", ccode);
|
|
if (ccode != 0)
|
|
return;
|
|
while (connect.inUseFlag != 0)
|
|
IPXRelinquishControl();
|
|
if (connect.completionCode != 0)
|
|
return;
|
|
send.ESRAddress = NULL;
|
|
send.fragmentCount = 2;
|
|
send.fragmentDescriptor[0].address = &sendHdr;
|
|
send.fragmentDescriptor[0].size = sizeof(SPXHeader);
|
|
send.fragmentDescriptor[1].address = sendbuf;
|
|
send.fragmentDescriptor[1].size = 7;
|
|
SPXSendSequencedPacket(spxConnectionID, &send);
|
|
|
|
packetsReceived = 0;
|
|
while (packetsReceived < 10) {
|
|
for (i = 0; i < NUM_BUFFS; i++) {
|
|
if (receive[i].inUseFlag != 0) {
|
|
if (receive[i].completionCode != 0) {
|
|
packetsReceived = 10;
|
|
/* If we get an error, terminate */
|
|
break;
|
|
}
|
|
printf("Received: %s\n", buffer[i]);
|
|
packetsReceived++;
|
|
}
|
|
SPXListenForSequencedPacket(receive[i]);
|
|
}
|
|
IPXRelinquishControl();
|
|
}
|
|
|
|
term.ESRAddress = NULL;
|
|
term.fragmentCount = 1;
|
|
term.fragmentDescriptor[0].address = &connHdr;
|
|
term.fragmentDescriptor[0].size = sizeof(SPXHeader);
|
|
SPXTerminateConnection(spxConnectionID, &term);
|
|
while (term.inUseFlag != 0)
|
|
IPXRelinquishControl();
|
|
for (i = 0; i < NUM_BUFFS; i++)
|
|
IPXCancelEvent(receive[i]);
|
|
}
|
|
END of FIGURE 4
|
|
*********************************************************
|
|
|
|
[- *********************************************************
|
|
Figure 4a: Establishing an SPX Connection (Pascal example)
|
|
|
|
{ Start an SPX connection with the station at network
|
|
$11111111, node $222222222222, socket $3333, use
|
|
local socket $4444 }
|
|
|
|
CONST NUM_BUFFS=5;
|
|
|
|
Procedure call;
|
|
Var send,connect,term:Tecb;
|
|
receive :array[1..NUM_BUFFS] of Tecb;
|
|
sendHdr, connHdr : TspxHeader;
|
|
rcvHdr :array[1..NUM_BUFFS] of TspxHeader;
|
|
buffer :array[1..NUM_BUFFS] of string[80];
|
|
sendBuf :string;
|
|
i,packetsReceived:Integer;
|
|
spxConnectionId :word;
|
|
begin;
|
|
sendbuf:="Hello!";
|
|
|
|
for i:= 1 to NUM_BUFFS
|
|
do begin
|
|
receive[i].ESRAddress := NIL;
|
|
receive[i].socketNumber := $4444;
|
|
receive[i].fragmentCount = 2;
|
|
receive[i].fragment[1].address := @rcvHdr[i];
|
|
receive[i].fragment[1].size := sizeof(TSPXHeader);
|
|
receive[i].fragment[2].address := @buffer[i];
|
|
receive[i].fragment[2].size := 80;
|
|
SPXListenForSequencedPacket(receive[i]);
|
|
end;
|
|
|
|
connect.ESRAddress := NIL;
|
|
connect.socketNumber := $4444;
|
|
connect.fragmentCount := 1;
|
|
connect.fragment[1].address := @connHdr;
|
|
connect.fragment[1].size := sizeof(TSPXHeader);
|
|
|
|
FillChar(connHdr.destination.network, 4, $11);
|
|
FillChar(connHdr.destination.node, 6, $22);
|
|
FillChar(connHdr.destination.socket, 2, $33);
|
|
|
|
IF NOT SPXEstablishConnection(0, 0,
|
|
spxConnectionID,
|
|
connect)
|
|
then begin
|
|
writeln('SPXEstablishConnection return code',
|
|
HexStr(nwSpx.result,2));
|
|
exit;
|
|
end;
|
|
|
|
while (connect.inUseFlag <> 0)
|
|
do IPXRelinquishControl();
|
|
if (connect.completionCode <> 0)
|
|
then exit;
|
|
|
|
send.ESRAddress := NIL;
|
|
send.fragmentCount := 2;
|
|
send.fragment[1].address = @sendHdr;
|
|
send.fragment[1].size := sizeof(TSPXHeader);
|
|
send.fragment[2].address := @sendbuf[0];
|
|
send.fragment[2].size := ord(sendBuf[0])+1;
|
|
SPXSendSequencedPacket(spxConnectionID, send);
|
|
|
|
packetsReceived := 0;
|
|
while (packetsReceived < 10)
|
|
do begin
|
|
for i :=1 to NUM_BUFFS
|
|
do begin
|
|
if (receive[i].inUseFlag <> 0)
|
|
and (receive[i].completionCode <> 0)
|
|
then begin
|
|
packetsReceived := 10;
|
|
exit;
|
|
{ If we get an error, terminate }
|
|
end;
|
|
writeln('Received: ", buffer[i]);
|
|
inc(packetsReceived);
|
|
SPXListenForSequencedPacket(receive[i]);
|
|
end;
|
|
IPXRelinquishControl;
|
|
end;
|
|
|
|
term.ESRAddress := NIL;
|
|
term.fragmentCount := 1;
|
|
term.fragment[1].address := @connHdr;
|
|
term.fragment[1].size := sizeof(TSPXHeader);
|
|
SPXTerminateConnection(spxConnectionID, term);
|
|
while (term.inUseFlag <> 0)
|
|
do IPXRelinquishControl;
|
|
for i:=1 to NUM_BUFFS
|
|
do IPXCancelEvent(receive[i]);
|
|
end;
|
|
|
|
END of FIGURE 4a
|
|
********************************************************* -]
|
|
|
|
This process may sound complicated, but everything happens transparently.
|
|
As long as there are extra ECBs available, the application never knows
|
|
they have been borrowed, since SPX puts them back in the exact same state
|
|
they were in when they were pressed into service.
|
|
|
|
If the connection is established with the SPX watchdog enabled, the
|
|
watchdog monitors the connection and notifies the application if the
|
|
connection fails, even if the application is not currently sending data
|
|
over the connection. This feature is useful for applications that start
|
|
SPX connections, but use them infrequently. For simplicity, however, the
|
|
example does not use the SPX watchdog.
|
|
|
|
After the listen ECBs have been posted, the connection ECB is then set up
|
|
in much the same way the IPX send ECB was, except that this ECB has only
|
|
one fragment: the SPX header. The destination network, node, and socket
|
|
also are set the same way they were in the previous example.
|
|
|
|
SPXEstablishConnection() is passed a retry count of zero, indicating that
|
|
you should use the default value for number of retries. This value is set
|
|
in the workstation's NET.CFG file using the IPX RETRY COUNT parameter,
|
|
which defaults to 20. The last zero passed in SPXEstablishConnection()
|
|
indicates not to use the SPX watchdog. The SPX connection ID is returned
|
|
as the third parameter. The SPX connection ID can be considered
|
|
equivalent to the NetBIOS local session number.
|
|
|
|
Next, the sample code attempts to establish a connection. It polls the
|
|
ECB's in use flag waiting for the event to complete. The
|
|
IPXRelinquishControl() call is very important at this stage. If the code
|
|
did nothing but sit in a tight loop, IPX and SPX would never get the
|
|
chance to do any processing. IPXRelinquishControl() allows the IPX/SPX
|
|
layer to get some work done.
|
|
|
|
Once the in use flag is set to zero, the example checks the return code
|
|
to see if the attempt to establish a connection was successful. The code
|
|
does not illustrate how to handle the various failure cases, but the most
|
|
likely cause of a failure would be that the other side is not yet
|
|
listening for a connection, just like in NetBIOS. After establishing the
|
|
connection, packets can be sent to the remote station.
|
|
|
|
The SPXSendSequencedPacket() call requires much less information than its
|
|
IPX counterpart. Since the connection is already established, all
|
|
SPXSendSequencedPacket() needs is the SPX connection ID, an ESR address,
|
|
and the fragment information.
|
|
|
|
After sending a packet, the example program waits for ten packets to
|
|
arrive. When an ECB comes back, the example displays the data and then
|
|
re-submits the ECB so that it can be used to receive a packet again.
|
|
After receiving ten packets, it issues an SPXTerminateConnection() call
|
|
to notify the other side that it is done.
|
|
|
|
The call to terminate the connection takes almost the same parameters
|
|
that the establish connection call does, except that there is no need to
|
|
fill out any information in the SPX header. Once the connection has been
|
|
terminated, the pending listen ECBs must be cancelled. To do so, the
|
|
example calls IPXCancelEvent(). Unlike most other ECB-related calls,
|
|
IPXCancelEvent() does not return until the ECB has been cancelled so
|
|
there is no need to poll the in use flag.
|
|
|
|
Event Service Routines
|
|
|
|
Event Service Routines (ESRs) serve the same purpose as the NetBIOS POST
|
|
routines, but require a little more setup than the standard POST routine.
|
|
Most ESRs are written in Assembly, although some call C functions.
|
|
|
|
Figure 5 shows a generic ESR that calls a C function after allocating its
|
|
own stack. This is very important since the amount of free stack space
|
|
(if any) at interrupt time is unknown, and any attempt by a C function to
|
|
use the stack could result in memory corruption if the stack is
|
|
overflowed. The only way to guarantee that this will not occur is to
|
|
allocate sufficient stack space in the ESR.
|
|
|
|
*********************************************************
|
|
Figure 5: Example Event Service Routine (ESR) [- C/ASM code -]
|
|
|
|
.MODEL LARGE
|
|
|
|
public _ReceiveESRHandler
|
|
extrn _ProcessReceiveData:PROC
|
|
|
|
.DATA
|
|
|
|
; The stack segment and pointer must be saved so that you can set up
|
|
; your own stack.
|
|
|
|
stk_seg dw 0 ; variable to store old stack segment
|
|
stk_ptr dw 0 ; variable to store old stack pointer
|
|
stk_stk dw 512 dup (0) ; new stack of 1024 bytes in length
|
|
stk_end dw 0 ; the end of the stack
|
|
|
|
.CODE
|
|
|
|
; @datasize is TRUE if the model is MEDIUM or LARGE and FALSE if the
|
|
; model is SMALL or COMPACT. Just modify the .MODEL ???? above for the
|
|
; model you want. ES/SI holds the seg/offset of the currently used ECB
|
|
; that ProcessReceivedData needs to process.
|
|
|
|
_ReceiveESRHandler PROC far
|
|
mov ax,DGroup
|
|
mov ds,ax
|
|
mov stk_seg,ss ; Save the stack segment
|
|
mov stk_ptr,sp ; Save the stack pointer
|
|
mov ss,ax ; move the segment of new_stk into ss
|
|
mov sp,offset stk_end ; move offset of new_stk to sp
|
|
IF @datasize
|
|
push es ; push es if mem. model medium/large
|
|
ENDIF
|
|
push si
|
|
call _ProcessReceivedData
|
|
mov ss,stk_seg ; Restore old stack segment
|
|
mov sp,stk_ptr ; Restore old stack pointer
|
|
retf
|
|
_ReceiveESRHandler ENDP
|
|
|
|
END END of FIGURE 5
|
|
*********************************************************
|
|
|
|
|
|
[- *********************************************************
|
|
Figure 5a: Example Event Service Routine (ESR) (BASM/Pascal)
|
|
|
|
{ The stack segment and pointer must be saved so that you can set up
|
|
your own stack. }
|
|
|
|
Var stk_stk:array[1..512] of word; { new stack of 1024 bytes in length }
|
|
stk_end:word; { the end of the stack }
|
|
|
|
{$F+}
|
|
Procedure ESRhandler(Var p:Tpecb); { * Type TPecb=^Tecb }
|
|
begin
|
|
.
|
|
.
|
|
end;
|
|
{$F-}
|
|
|
|
{$F+}
|
|
Procedure ListenESR; assembler;
|
|
asm { ES:SI are the only valid registers when entering this procedure ! }
|
|
mov dx, seg stk_stk { = seg @DATA }
|
|
mov ds, dx
|
|
|
|
mov dx,ss { setup of a new local stack }
|
|
mov bx,sp { ss:sp copied to dx:bx}
|
|
mov ax,ds
|
|
mov ss,ax
|
|
mov sp,offset stk_end
|
|
push dx { push old ss:sp on new stack }
|
|
push bx
|
|
push es { * push es:si on stack as local vars }
|
|
push si { * }
|
|
mov di,sp { * }
|
|
push ss { * push address of local ptr on stack }
|
|
push di { * }
|
|
|
|
CALL EsrHandler
|
|
|
|
add sp,4 { skip stack ptr-copy }
|
|
pop bx { restore ss:sp from new stack }
|
|
pop dx
|
|
mov sp,bx
|
|
mov ss,dx
|
|
end;
|
|
{$F-}
|
|
|
|
Note that a local stack of 1024 bytes (512 words) may not be large
|
|
enough for some applications calling other functions within the
|
|
ESRhandler. Increase the stacksize by 1024 bytes at a time to
|
|
determine the stack requirement.
|
|
|
|
|
|
END END of FIGURE 5a
|
|
********************************************************* -]
|
|
|
|
Figure 6 contains a code fragment demonstrating the use of an ESR. It
|
|
receives ten SPX packets just like the example in Figure 3 does, but it
|
|
uses an ESR instead of polling the in use flag. The assembly language
|
|
routine from Figure 4 is declared as the ESR, and it in turn calls the
|
|
C [-/Pascal-] function ProcessReceivedData().
|
|
|
|
*********************************************************
|
|
Figure 6: Using an Event Service Routine (ESR) [- C Code -]
|
|
|
|
int packetCount = 0;
|
|
|
|
void ProcessReceivedData(ECB *ecb)
|
|
{
|
|
packetCount++;
|
|
printf("%s\n", ecb->fragmentDescriptor[1].address);
|
|
SPXListenForSequencedPacket(ecb); /* Re-issue the listen */
|
|
}
|
|
|
|
main()
|
|
{
|
|
.
|
|
. /* This code is identical to SPX setup code in Fig. 4, except */
|
|
. /* for receive[i].ESRAddress line, which will be as follows: */
|
|
|
|
receive[i].ESRAddress = (void (far *) () ) ReceiveESRHandler;
|
|
|
|
.
|
|
. /* The send ECB does not normally use an ESR. */
|
|
.
|
|
|
|
while (packetCount < 10)
|
|
IPXRelinquishControl();
|
|
.
|
|
. /* Shut down connection, cancel ECBs */
|
|
.
|
|
}
|
|
|
|
END of FIGURE 6
|
|
*********************************************************
|
|
|
|
[- *********************************************************
|
|
Figure 6a: Using an Event Service Routine (ESR) (Pascal)
|
|
|
|
Var PacketCount;
|
|
|
|
Procedure ProcessReceivedData(Var ECB:Tecb)
|
|
begin
|
|
inc(packetCount);
|
|
writeln(string(ecb^.fragment[2].address^));
|
|
SPXListenForSequencedPacket(ecb); { Re-issue the listen }
|
|
end;
|
|
|
|
begin { main body }
|
|
PacketCount:=0;
|
|
|
|
.
|
|
. { This code is identical to SPX setup code in Fig. 4a, except }
|
|
. { for receive[i].ESRAddress line, which will be as follows: }
|
|
|
|
receive[i].ESRAddress := @ReceiveESRHandler;
|
|
.
|
|
. { The send ECB does not normally use an ESR. }
|
|
.
|
|
|
|
while (packetCount < 10)
|
|
do IPXRelinquishControl;
|
|
.
|
|
. { Shut down connection, cancel ECBs }
|
|
.
|
|
end;
|
|
|
|
END of FIGURE 6a
|
|
********************************************************* -]
|
|
|
|
IPX and SPX may look a little more complicated than NetBIOS at first, but
|
|
as soon as you begin using these protocols, you see how similar they
|
|
really are. Using IPX/SPX requires slightly more effort, but the
|
|
performance and compatibility gains when running under NetWare more than
|
|
compensate. If you are thinking about becoming more familiar with IPX and
|
|
SPX development, feel free to contact Novell's Developer Support group at
|
|
1-800-NETWARE (1-800-638-9273) or 1-801-429-5588.
|