Rahner is an independent consultant living near Sacramento, Calif. He can be reached by phone at 916-722-1939 or through CompuServe at 71450,757.
When I first began using networks, the most obvious means of communication between nodes was to open a file and have all the nodes access that file. Records would be written to that open file, flushed, and read by another node. With only a couple of nodes on a network communicating sparingly in this fashion, the method appeared to work.
With a moderate load, however, the network seemed to lose pep. Sometimes the nodes would fail to flush their buffers properly and data would never pass into the open file. Often the entire system would bog down in the message pool, inhibiting normal data-access functions.
I knew there must be a better way, and happily, Novell provided an apparent answer to my need for speed--Internetwork Packet Exchange, or lPX. IPX is an example of the third layer of what the International Standards Organization (ISO) proposed standard calls the "Open System Interconnection" (OSI) model, as it relates to Novell's Netware. IPX lets programmers perform high-speed, peer-to-peer communication on Novell's Netware. IPX is the lowest level of communication that can be performed on a network without resorting to direct access of the hardware. Novell refers to the functions that enable this type of communication as "IPX/SPX Communication Services." (SPX stands for Sequenced Packet Exchange.) Here, I'll refer to IPX/SPX functions as "XPX."
In this article, I'll discuss IPX/SPX and present a library of IPX functions--implemented as a stream--that significantly improves IPX throughput without additional server time. I've also written a working program called TEST1.C that uses the major XPX functions. TEST1.C is run on multiple network nodes and continually transfers packets to any other nodes from which it receives broadcasts. The library and test sample program are available electronically; see page 3 in this issue.
An XPX Backgrounder
Normal Novell operating-system accesses are through the MS-DOS INT 21h window. XPX, however, uses a slightly different mechanism. Before any function calls can be made, the application must call the MS-DOS multiplex and get the vector to the XPX entry point. The assembly code segment for this is shown in Example 1. All XPX routines are accessed by making far calls using the IPX_Vector.XPX uses register BX to hold an XPX command number. An XPX call is not kind to unused registers and, in general, sensitive registers should be saved (especially BP). By using a far call (rather than MS-DOS's INT 21h), most IPX functions can be called from a background process without the programmer worrying about trashing the system. All XPX functions return the status of their result in AL.
Example 1: Assembly code calls MS-DOS multiplex and gets vector to the XPX entry point.
mov ax, 7a00h ; Function 7Ah, AL = 0 int 2fh ; MS-DOS Multiplex interrupt ; Returns with AL == 0FFh if xPX ; exists and ES:DI == xPX vector inc al ; Set ZERO if AL == -1 jnz outta_here ; Quit if xPX isn't around mov IPX_Vector, di ; Save the xPX entry factor mov IPX_Vector+2, es
In my mind, the XPX functions fall into two major categories: initialization/information and communication. The initialization/information functions start up the XPX internals, open communication pathways, and give the application information about how things have been setup. The communication functions are responsible for sending/receiving packets of information to/from peers on the network.
All network interfaces (Ethernet, Arc-net, and so on) require a unique 6-byte node ID to differentiate between them. The node ID only differentiates a network interface within a single network. Therefore, two networks can have interfaces with identical node IDs. Because a single computer can have multiple network interfaces or a network interface can be placed in another computer, a node ID does not necessarily specify a single, distinct computer system.
Every Novell network has a 4-byte ID number that uniquely differentiates it from any other Novell networks to which it is connected. If no other networks are connected, any number is unique.
Every node connected to a network can open several different channels of communication, called "sockets." Sockets allow applications to differentiate types of communication performed by a node. One socket can be used to broadcast one message to all nodes while another socket can be used to send and/or receive messages from specific nodes or groups of nodes. By default, XPX can support up to 20 sockets on a single node. Through configuration, the number of sockets that may be open can be increased to 150. A 2-byte number is used as a socket ID.
Numerically, the network, node, and socket IDs are in Motorola format. The format is actually of no consequence because the magnitude, order, and content are not relevant to the application or XPX. There are special cases of each type of ID, but all are palindromes, so byte order is still not important. The 4-byte network ID can be viewed as a 32-bit long integer with no significance associated with its magnitude. The 6-byte node ID can be viewed as a non-ASCII string, such as a filename, with no naming convention. The 2-byte socket ID can be a short integer that also has no significance associated with its magnitude.
XPX Structures
The basic control structure used with both IPX and SPX is the Event Control Block (ECB); see Table 1. The ECB is passed to XPX to describe its associated channel and buffers. The first 34 bytes of the ECB contain control and addressing information. A list of "associated fragments" immediately follows this 34-byte header.
Table 1: Event Control Block format
Region Description 0-3 Link to next ECB, filled and used by XPX. While XPX is not using this ECB, the application can use this field for its own ECB management. 4-7 Far pointer to the Event Service Routine (ESR) associated with this ECB. This can be NULL, if asynchronous processing is not desired. 8 In Use Flag. Used by XPX to show the current state of ECB processing. Set to 0 when XPX is done with the ECB. 9 Completion Code. Set by XPX when the ECB In Use Flag is set to 0. Valid only when XPX has finished with the ECB. A 0 indicates that the ECB task was completed successfully. Any other value indicates an error condition. 10-11 Socket ID. Set by the application to tell XPX with which socket the ECB is to communicate. 12-27 Used internally by XPX. 28-33 Immediate Address. This is the local node ID with which this ECB is to communicate. If ECB is being sent (a talker) using IPX (rather than SPX), this field should be filled in by the application. If ECB is a listener or uses SPX, this field is filled by XPX. 34-35 Fragment Count. Filled in by application to tell XPX how many fragment descriptors are to follow. All ECBs must have at least one fragment descriptor to point to an XPX packet header. The cumulative size of the fragments associated with an ECB can not exceed 576 bytes. 36-39 Fragment Pointer 1. Far pointer to the first fragment associated with the ECB. The application must have at least one fragment that contains a complete IPX or SPX header. Any additional data buffers can be contiguous extensions of the XPX header or segmented into unconnected memory locations. Noncontiguous memory fragments require more than one fragment descriptor. 40-41 Fragment Size 1. The number of bytes in the first fragment. If the size of the first fragment is not at least the size of either an IPX or SPX header, XPX returns an error. Fragments that follow must contain at least 1 byte. 42-45 Fragment Pointer 2. This fragment descriptor and those that follow are optional and need not be used or declared if the first fragment descriptor describes the packet buffer in its entirety. 46-47 Fragment Size 2. Optional size of the second fragment. ... ... nn-nn Fragment Pointer n mm-mm Fragment Size m
The Event Service Routine (ESR) is a function provided by the application and called by XPX when the ECB has been processed. The ESR is called in the following conditions:
- ES:SI points to the ECB that was processed.
- All registers except SS and SP have been saved on the stack.
- Interrupts are disabled.
- AL is 0FFh if called by IPX, or 0 if called by the Asynchronous Event Scheduler.
- All segment registers are in unknown states.
An application could conceivably have a separate ESR for every ECB; however, that approach would be excessive. The ESR should be fairly general purpose and should be approached with the same mind-set as an Interrupt Service Routine.
The In Use Flag field can reflect a variety of states as the ECB nears completion. These state values are defined within my header file, NETWORK.H. All definitions associated with the In Use Flag are prefixed with IU_. The application can poll In Use to check on the current status of a particular ECB or wait until XPX calls the ESR.
The Completion Code field is filled by XPX after the ECB has been processed. The contents of this field have no meaning until the In Use Flag equals 0, so the application should have no expectations of Completion Code until that time. Many of the Novell-documented completion codes are defined within NETWORK.H. All definitions associated with Completion Code are prefixed with CC_.
The first fragment descriptor must point to either a header structure for an IPX or SPX packet before the ECB can be passed to any XPX function. Both packet types start with the same structure. The format of the IPX header is shown in Table 2. The application needs to fill the IPX structure only if the packet is being transmitted. If the packet is being used as a listener, XPX fills in all the fields.
Table 2: Format for IPX header
Region Description 0-1 Checksum. Set by XPX to -1. 2-3 Length of the entire XPX packet, including all other fragments associated with the ECB. Filled by XPX. 4 Transport Control. Set to 0 by XPX. 5 Packet Type. Set by application to 4 for an IPX packet, or 5 for an SPX packet if the packet is being sent (talker). 6-9 Destination Network ID. Set by application if XPX packet is being sent (talker). If set to 0, the current network is used regardless of its true ID. 10-15 Destination Node ID. Set by application if packet is being sent (talker). If set to all 0FFh's, the IPX packet will be sent to all IPX listeners on the network with an equal socket ID, including active listeners on transmitting node. 16-17 Destination Socket ID. Set by application if packet is being sent (talker). Socket ID must be opened before socket ID can be used. 18-21 Source Network ID. Set by XPX to the network ID of packet's source. 22-27 Source Node ID. Set by XPX to node ID of packet's source. 28-29 Source Socket ID. Set by XPX to socket ID of packet's source.
The SPX packet header is a superset of the IPX packet header; see Table 3. None of the SPX-specific fields need to be filled in by the application for either transmissions or receptions. These fields must be available for SPX packets.
Table 3: The SPX packet header structure
Region Description 0-29 IPX header previously defined. 30 Connection Control. Used by SPX to control flow of data. 31 Data-stream Type. Information byte that can be used by application for any purpose. SPX reserves values 0FEh and 0FFh for its own use. 32-33 Source Connection ID. Connection number of source node for this SPX packet. Created by SPX for use by application. 34-35 Destination Connection ID. Connection number of destination node for this SPX packet. Created by SPX for use by application. 36-37 Sequence Number. Used by SPX to keep sequence of received and transmitted packets straight. 38-39 Acknowledge Number. Used by SPX to acknowledge receipt of a packet. 40-41 Allocation Number. Used by SPX to keep track of packets sent but not acknowledged.
The size of an SPX packet header is 12 bytes larger than the header for IPX packets. Because the headers must be part of every XPX packet, the maximum size of data that can be sent with an IPX packet is 546 bytes, and the maximum for SPX is 534 bytes.
Simplicity Breeds Attempts
When I first began coding access functions, IPX demonstrated itself superior to file- or pipe-access methods. Eventually, however, IPX began to lose packets. I discovered that under a moderate transmission load, my application spent too much time minding its business, while IPX-filled listening packets weren't being serviced. Polling was not able to keep up with the data-transfer demand. Novell's documentation confirmed this, stating that IPX yields about a 95 percent delivery rate (although I was never able to achieve this rate).
I first decided that if more than a few packets per second are being transferred, an IPX communication needs ESR support to function effectively. I then wrote a library of ESR-powered IPX functions. My experimentation yielded a 100 percent delivery rate for over 100,000 packet transmissions. To find a reasonable upper limit, I ran a program on four 80386 PCs on my Ethernet network. Each computer sent and received 512-byte IPX packets continuously. I didn't notice any packet loss until they reached about 70 packets/second/node (280 packets/second, overall). At that point, the 16-MHz 386SX started to go deaf.
After I implemented ESR support, I decided that the functions and data structures were cumbersome. At an application level, I didn't want to have to deal with the asynchronous approach. Because I was already using a file-based approach, I figured that the entire IPX access would be best implemented as a streams type of interface. With a streams implementation, analogous structures became apparent. The full network-address structure (network ID, node ID, and socket ID) became the "filename." Access flags could be used to define the type of packet (listener or talker) and communication method (IPX or SPX). I merged the ECB and a superset structure of the IPX/SPX header.
To keep track of the stream's operation, I created a structure called XPX_STREAM_T, which is documented in the file NETWORK.H. XPX_STREAM_T allows the programmer to open a channel to a node (or nodes, if it is a broadcast channel) and perform reads, writes, and queries on that channel. Multiple XPX packets are automatically allocated and provided for the stream's I/O.
Descriptions of Major Functions
The major functions written for the stream approach are XPX_INIT(), XPX_OPEN(), XPX_READ(), XPX_WRITE(), XPX_CLOSE(), and IPX_READ_ORPHAN(). Except for XPX_INIT() and IPX_READ_ORPHAN(), the functions are used in a fashion analogous to their file counterparts. This function library, a text file that details the internals of IPX functions, my NETWORK.H, and the TEST1.C sample program are available electronically; see page 3.
XPX_INIT() (see Listing Two) initializes the SPX internals and gets the entry vector for accessing IPX. This function queries IPX for the application's node ID and network address, which are placed in the global structure Our_Address. After XPX is initialized, a single-socket ID is opened and dynamically generated by IPX. This socket ID is placed in the Our_Address structure, and it can be used as an application's private channel. The open socket is not needed for the operation of any functions, and is provided only for the sake of convenience.
XPX_OPEN() allows the application to open a communication channel to another node on the network. The channel can be read only, write only, or read/write. The node ID can be specific or a broadcast channel. A specific open will receive packets only from the defined target-node ID. A broadcast channel will accept packets from any node ID at that socket. There is no such thing as a broadcast socket, so socket IDs are important. Any packet received by a node from itself will be ignored. Note that an IPX stream cannot share the same socket as an SPX stream, but mutiple IPX streams can share the same socket.
XPX_READ() allows the application to read the next received packet data partially or completely. The read will only be made up to the end of the first available packet. Data from multiple packets can only be read by successive reads. Packets may only be read in the order they were received from the source node.
XPX_WRITE() allows the application to write data to a stream. The data written does not have to be of a particular size. XPX_WRITE() will packetize the data and send the packets to XPX. XPX_WRITE() returns the number of bytes written. If a done pointer is provided for IPX_WRITE(), IPX_WRITE() will set the flag to a nonzero value, and the ESR will reset the flag to 0 when a packet has been sent.
XPX_CLOSE() closes the communication channel and frees all data buffers allocated by XPX_OPEN().
IPX_READ_ORPHAN() allows the application to read any packets sent to a socket ID that do not match any open stream addresses. (Broadcast streams with read ability match all nodes.) This function should be called periodically, so that all the listening packets do not get used up by spurious receptions. If orphan packets are not an issue, the application can set the global variable _Ignore_Nomatch to a nonzero value.
Four macros have been defined within NETWORK.H to provide status information for the application. XPX_ERROR_STATUS() returns the number of packet errors that have occurred since the last call to IPX_INIT(). XPX_ORPHAN_STATUS() returns the current number of unprocessed orphan packets. XPX_READ_STATUS() returns the number of packets associated with a stream that have not been read. XPX_WRITE_STATUS() returns the number of packets that are available to a stream for transmission.
Conclusion
IPX is a high-performance protocol available on Novell Netware networks. IPX adds a level of complexity and uncertainty to peer-to-peer communications, but it can be fashioned into a useful resource. SPX is slightly slower, but guarantees data delivery. Given the scope of this article, I have dwelled upon IPX and only touched upon SPX. I leave these functions as a foundation on which you can build.
DDJ
_IPX: THE GREAT COMMUNICATOR_ by Rahner James[LISTING ONE]
<a name="0113_000e"> ; ************************************************************************** ; * Title: ESRS.ASM -- by Rahner James ; * Copyright (c) January 1991, Ryu Consulting, 916/722-1939 ; * File contains default Event Service Routine for listening & talking packets ; ************************************************************************** _ESRS_ASM_ equ 1 ifdef LARGEMODEL .model large,c else .model small,c endif ADDRESS_S struct network dw ?,? ; Network number node dw 3 dup(?); Node address on that network socket dw ? ; Socket number on that node ADDRESS_S ends IPX_PACKET_S struct next dd ? ; Used by IPX/SPX when the ECB is active function dd ? ; Called after packet sent/recd, called ESR in_use db ? ; Set to !0 by IPX/SPX when packet is in use completion_code db ? ; Set by XPX after packet task is complete socket dw ? ; Socket to use for this ECB IPX_work dd ? ; Workspace used internally by IPX driver_work dd ?,?,? ; Workspace used internally by IPX driver dest_address db 6 dup(?); Destination address for packet fragment_count dw ? ; Fragments descriptors that follow hdr dd ? ; -> IPX/SPX packet descriptor to use size_hdr dw ? ; Size of the IPX(30) or SPX(42) descriptor buffer_ptr dd ? ; -> data buffer to use for transmission/reception buffer_size dw ? ; Number of bytes in that buffer next_allocated dd ? ; -> next allocated packet structure next_sibling dd ? ; -> next packet for stream and condition parent dd ? ; -> parent stream definition packet default_buffer dd ? ; -> default buffer to use for IPX or SPX default_size dw ? ; Size of the default buffer done_flag dd ? ; Set by the ESR with the completion code checksum dw ? ; Dummy checksum of 30-byte packet header packet_length dw ? ; Length of complete IPX packet control db ? ; Transport control byte for internet bridges packet_type db ? ; Packet type: IPX(4)/SPX(5) dest_network dd ? ; Destination network address dest_node db 6 dup(?); Destination node address dest_socket dw ? ; Destination socket src_network dd ? ; Source network address src_node db 6 dup(?); Source node address src_socket dw ? ; Source socket IPX_PACKET_S ends XPX_STREAM_S struct next dd ? ; -> next stream structure opened first_allocated dd ? ; -> first allocated packet for handle last_allocated dd ? ; -> last allocated packet for handle first_unread dd ? ; -> first unread packet last_unread dd ? ; -> last unread packet in the list first_free dd ? ; -> first packet available for talking first_error dd ? ; -> first packet encountering an error last_error dd ? ; -> last packet encountering an error dest_network dd ? ; Destination network address dest_node db 6 dup(?); Destination node address dest_socket dw ? ; Destination socket local_target db 6 dup(?); Node address of local target for dest connection_ID dw ? ; Connection ID used for SPX total_talkers dw ? ; Number of talkers for this stream total_listeners dw ? ; Number of listeners for this stream unread_count dw ? ; Number of packets unread by app free_count dw ? ; Number of packets ready for talking maximum_unread dw ? ; Maximum number of unread packets error_count dw ? ; Number of unprocessed error packets total_transmissions dd ? ; Number of transmissions performed total_receptions dd ? ; Number of receptions performed total_errors dd ? ; Number of errors encountered XPX_STREAM_S ends .data extern _Ignore_Nomatch:byte, _Our_Address:word, IPX_Vector:dword, _First_Stream:dword extern _First_Nomatch:dword, _Last_Nomatch:dword, _Total_Nomatchs:word .code Last_Broad_Ptr dw 0,0 ; -> last checked broadcast stream ; ************************************************************************** ; * void far TALK_ESR( void ) -- Event Service Routine (ESR) for IPX ; * functions and their talking packets ; * Given: AL = 0 if AES called this ESR, 0xff if this is a normal event ; * ES:SI -> ECB that just finished talking ; * Returns: Packet either glued onto the free list or the error list ; * Note: Interrupts are enabled at this point and should stay that way ; ************************************************************************** talk_esr proc far ; * See if we need to set the done flag lds bx, es:[si].IPX_PACKET_S.done_flag ; DS:BX -> process done flag mov cl, es:[si].IPX_PACKET_S.completion_code mov ax, ds or ax, bx jz @F ; If DS:BX -> NULL, just skip it mov [bx], cl ; Set the flag with our completion code mov word ptr es:[si].IPX_PACKET_S.done_flag, 0 ; Make it NULL mov word ptr es:[si].IPX_PACKET_S.done_flag+2, 0 ; * Check whether the packet goes in the error list or the free list @@: lds bx, es:[si].IPX_PACKET_S.parent ; DS:BX -> parent structure or cl, cl ; See if we got a transmission error jnz talk20_esr ; Jump if we got one ; * Here's where we process the good transmissions add word ptr [bx].XPX_STREAM_S.total_transmissions, 1 adc word ptr [bx].XPX_STREAM_S.total_transmissions+2, 0 inc [bx].XPX_STREAM_S.free_count; mov cx, word ptr [bx].XPX_STREAM_S.first_free ; DX:CX -> first free mov dx, word ptr [bx].XPX_STREAM_S.first_free+2 mov word ptr [bx].XPX_STREAM_S.first_free, si mov word ptr [bx].XPX_STREAM_S.first_free+2, es mov word ptr es:[si].IPX_PACKET_S.next_sibling, cx mov word ptr es:[si].IPX_PACKET_S.next_sibling+2, dx talk10_esr: ret ; * Here's where we take care of our challenged packets talk20_esr: add word ptr [bx].XPX_STREAM_S.total_errors, 1 adc word ptr [bx].XPX_STREAM_S.total_errors+2, 0 inc [bx].XPX_STREAM_S.error_count mov cx, word ptr [bx].XPX_STREAM_S.last_error ; DX:CX ->last error mov dx, word ptr [bx].XPX_STREAM_S.last_error+2 mov word ptr [bx].XPX_STREAM_S.last_error, si ; Set new last error mov word ptr [bx].XPX_STREAM_S.last_error+2, es mov word ptr es:[si].IPX_PACKET_S.next_sibling, 0 mov word ptr es:[si].IPX_PACKET_S.next_sibling+2, 0 mov ax, cx ; See if we need to do the first as well or ax, dx jnz @F mov word ptr [bx].XPX_STREAM_S.first_error, si ;Set new first error mov word ptr [bx].XPX_STREAM_S.first_error+2, es ret @@: mov ds, dx ; DS:BX -> the first born mov bx, cx mov word ptr [bx].IPX_PACKET_S.next_sibling, si ; Point old end mov word ptr [bx].IPX_PACKET_S.next_sibling+2, es ret talk_esr endp ; ************************************************************************** ; * void far LISTEN_ESR( void ) -- Event Service Routine (ESR) for IPX ; * functions and their listening packets ; * Given: AL = 0 if AES called this ESR, 0xff if this is a normal event ; * ES:SI -> ECB that just got something ; * Returns: Packet put at the end of the unread packet list of the stream it ; * was intended for. ; * Note: Interrupts are enabled at this point and should stay that way. This ; * packet may not be put with its parent if there are multiple parents ; * associated with one socket ; ************************************************************************** listen_esr proc far mov ax, @Data ; DS = our data segment mov ds, ax ; * First see if we sent it as a broadcast and it got back to us mov ax, _Our_Address.ADDRESS_S.node+4 cmp word ptr es:[si].IPX_PACKET_S.src_node+4, ax jne listen10_esr mov ax, _Our_Address.ADDRESS_S.node+2 cmp word ptr es:[si].IPX_PACKET_S.src_node+2, ax jne listen10_esr mov ax, _Our_Address.ADDRESS_S.node cmp word ptr es:[si].IPX_PACKET_S.src_node, ax jne listen10_esr mov ax, _Our_Address.ADDRESS_S.network+2 cmp word ptr es:[si].IPX_PACKET_S.src_network+2, ax jne listen10_esr mov ax, _Our_Address.ADDRESS_S.network cmp word ptr es:[si].IPX_PACKET_S.src_network, ax jne listen10_esr listen_again_buckwheat: mov bx, 4 ; BX = IPX Listen For Packet command call dword ptr IPX_Vector ; Call the IPX function ret ; * See if we need to set the done flag listen10_esr: mov ax, es:[si].IPX_PACKET_S.packet_length ; Change format xchg ah, al sub ax, es:[si].IPX_PACKET_S.size_hdr mov es:[si].IPX_PACKET_S.packet_length, ax lds bx, es:[si].IPX_PACKET_S.done_flag ; DS:BX -> process done flag mov cl, es:[si].IPX_PACKET_S.completion_code mov ax, ds or ax, bx jz listen20_esr mov [bx], cl mov word ptr es:[si].IPX_PACKET_S.done_flag, 0 mov word ptr es:[si].IPX_PACKET_S.done_flag+2, 0 ; * Check whether the packet goes in the error list or the free list listen20_esr: lds bx, es:[si].IPX_PACKET_S.parent ; DS:BX -> parent structure or cl, cl ; See if we got a reception error jz listen40_esr ; Jump if we have an unimpaired reception ; * Here's where we take care of our datistically challenged packets add word ptr [bx].XPX_STREAM_S.total_errors, 1 adc word ptr [bx].XPX_STREAM_S.total_errors+2, 0 inc [bx].XPX_STREAM_S.error_count mov cx, word ptr [bx].XPX_STREAM_S.last_error ; DX:CX->last error mov dx, word ptr [bx].XPX_STREAM_S.last_error+2 mov word ptr [bx].XPX_STREAM_S.last_error, si ; Set new last error mov word ptr [bx].XPX_STREAM_S.last_error+2, es mov word ptr es:[si].IPX_PACKET_S.next_sibling, 0 mov word ptr es:[si].IPX_PACKET_S.next_sibling+2, 0 mov ax, dx ; See if we are the only packet here or ax, cx jnz @F ; Skip out of this ESR if we are not alone mov word ptr [bx].XPX_STREAM_S.first_error, si ;Set new first error mov word ptr [bx].XPX_STREAM_S.first_error+2, es ret @@: mov ds, dx ; DS:BX -> the first born mov bx, cx mov word ptr [bx].IPX_PACKET_S.next_sibling, si mov word ptr [bx].IPX_PACKET_S.next_sibling+2, es ret ; * Here's where we process the good transmissions listen40_esr: push ds ; DX:CX -> the first stream structure mov ax, @Data mov ds, ax mov cx, word ptr _First_Stream mov dx, word ptr _First_Stream+2 pop ds mov Last_Broad_Ptr, 0 mov Last_Broad_Ptr+2, 0 listen50_esr: cmp [bx].XPX_STREAM_S.total_listeners, 0 ; See if READ ONLY stream jz not_parent ; Skip this one if it is READ ONLY mov ax, word ptr [bx].XPX_STREAM_S.dest_node and ax, word ptr [bx].XPX_STREAM_S.dest_node+2 and ax, word ptr [bx].XPX_STREAM_S.dest_node+4 inc ax jnz @F ; Skip if it is not a broadcast type mov Last_Broad_Ptr, bx ; Save this for later mov Last_Broad_Ptr+2, ds jmp short not_parent ; Still not necessarily the right one @@: mov ax, word ptr [bx].XPX_STREAM_S.local_target+4 ; Match parent cmp word ptr es:[si].IPX_PACKET_S.src_node+4, ax jne not_parent mov ax, word ptr [bx].XPX_STREAM_S.local_target+2 cmp word ptr es:[si].IPX_PACKET_S.src_node+2, ax jne not_parent mov ax, word ptr [bx].XPX_STREAM_S.local_target cmp word ptr es:[si].IPX_PACKET_S.src_node, ax jne not_parent mov ax, word ptr es:[si].IPX_PACKET_S.src_network+2 cmp word ptr es:[si].IPX_PACKET_S.dest_network+2, ax jne not_parent mov ax, word ptr es:[si].IPX_PACKET_S.src_network cmp word ptr es:[si].IPX_PACKET_S.dest_network, ax je found_listener ; * At this point, the current structure has been determined not to be suitable not_parent: mov ax, cx ; See if we are at the end of our rope or ax, dx jz no_listener ; No stream match found mov ds, dx ; DS:BX -> next stream definition mov bx, cx mov cx, word ptr [bx].XPX_STREAM_S.next ; DX:CX -> next stream mov dx, word ptr [bx].XPX_STREAM_S.next+2 jmp listen50_esr ; Loop until we poop ; * Stream ID matches up with destination address, so add packet to stream list found_listener: add word ptr [bx].XPX_STREAM_S.total_receptions, 1 adc word ptr [bx].XPX_STREAM_S.total_receptions+2, 0 inc [bx].XPX_STREAM_S.unread_count mov ax, [bx].XPX_STREAM_S.unread_count ; Update our statistics cmp [bx].XPX_STREAM_S.maximum_unread, ax jnc found10_listener ; Skip if no need to update mov [bx].XPX_STREAM_S.maximum_unread, ax found10_listener: mov cx, word ptr [bx].XPX_STREAM_S.last_unread mov dx, word ptr [bx].XPX_STREAM_S.last_unread+2 mov word ptr [bx].XPX_STREAM_S.last_unread, si mov word ptr [bx].XPX_STREAM_S.last_unread+2, es mov word ptr es:[si].IPX_PACKET_S.next_sibling, 0 mov word ptr es:[si].IPX_PACKET_S.next_sibling+2, 0 mov ax, cx ; See if we need to do the first as well or ax, dx jnz @F ; All done if there are others mov word ptr [bx].XPX_STREAM_S.first_unread, si mov word ptr [bx].XPX_STREAM_S.first_unread+2, es ret @@: mov ds, dx ; DS:BX -> the first born mov bx, cx mov word ptr [bx].IPX_PACKET_S.next_sibling, si mov word ptr [bx].IPX_PACKET_S.next_sibling+2, es ret ; * At this point, packet is an orphan and must be sent off to farm or be glue no_listener: lds bx, dword ptr Last_Broad_Ptr ; DS:BX->last broadcast stream mov ax, ds or ax, bx jnz found_listener ; If one was found, use as last resort cmp _Ignore_Nomatch, al ; Ignore orphans or adopt jmp listen_again_buckwheat ; Put back on the mountaintop no10_listener: mov ax, @Data ; DS = our most lovable data segment mov ds, ax inc _Total_Nomatchs mov cx, word ptr _Last_Nomatch ; DX:CX -> last error packet mov dx, word ptr _Last_Nomatch+2 mov word ptr _Last_Nomatch, si ; Make it point to us mov word ptr _Last_Nomatch+2, es mov word ptr es:[si].IPX_PACKET_S.next_sibling, 0 mov word ptr es:[si].IPX_PACKET_S.next_sibling+2, 0 mov ax, cx ; See if we need to do the first as well or ax, dx jnz @F ; All done if there are others mov word ptr _First_Nomatch, si ; Set us as the new first error mov word ptr _First_Nomatch+2, es ret @@: mov ds, dx ; DS:BX -> the first born mov bx, cx mov word ptr [bx].IPX_PACKET_S.next_sibling, si mov word ptr [bx].IPX_PACKET_S.next_sibling+2, es ret listen_esr endp end <a name="0113_000f"> <a name="0113_0010">[LISTING TWO]
<a name="0113_0010"> ; ************************************************************************** ; * Title: XPX_INIT.ASM -- Rahner James ; * Copyright (c) December 1991, Ryu Consulting, 916/722-1939 ; * File contains all the functions to support initializing IPX engine ; ************************************************************************** _XPX_INIT_ASM_ equ 1 include network.inc .data public IPX_Vector, _Socket_Life, _SPX_Version, _SPX_Max_Connections, _Our_Address public _SPX_Available_Connections, _SPX_Retry_Count IPX_Vector dw offset dummy_IPX_function,@Code ; -> IPX support function _Socket_Life db 0 ; 0=socket closed at app termination ; 0ffh= socket closed when requested _SPX_Version dw 0 ; SPX version #: MSByte=major, LSByte=minor _SPX_Max_Connections dw 0 ; Max number of SPX connections _SPX_Available_Connections dw 0 ; # of SPX connections available to app _SPX_Retry_Count db 0 ; Retry count for SPX establish connection _SPX_Bowser_Flag db 1 ; Watchdog flag, 0=disable, 1=enable _Our_Address label dword ; Global access for this structure network dd 0 ; Network address node db 6 dup(0) ; Node address socket dw 0 ; Socket number .code ; ************************************************************************** ; * int XPX_INIT( us SOCKET_NUMBER ) -- Initializes all IPX/SPX internals ; * Given: SOCKET_NUMBER = socket number to open for listening, 0 opens ; * the next available ; * Returns: 0 if IPX was initialized successfully ; * -1 = socket already open (!) ; * -2 = socket table full ; * -3 = IPX or SPX is not installed ; * Note: Initializes IPX vector, opens a listening socket for IPX driver. ; * Internal vectors, counter, & pointers are brought to initial conditions ; ************************************************************************** xpx_init proc uses di si, socket_number:word mov ax, 7a00h ; Get the IPX vector int 2fh ; Query the DOS multiplexer inc al ; AL = 0ffh if IPX is there jnz derr_xpx_init ; Quit in disgrace if it's not there mov IPX_Vector, di ; IPX function vector returned in ES:DI mov IPX_Vector+2, es ; * See if we need to close the old stuff down mov dx, socket or dx, dx ; See if we opened a socket jz xpx10_init ; Skip if we didn't cmp socket_number, 0 jz xpx20_init ; Skip if so cmp socket_number, dx ; See if it's the same as before je xpx20_init ; Skip if it is IPX 1 ; IPX Close Socket command ; * Now, open the socket xpx10_init: mov dx, socket_number mov al, _Socket_Life IPX_CHECK 0 ; IPX Open Socket command jnz done_xpx_init ; Quit if an error xpx20_init: mov socket, dx ; * Get our internetwork address mov ax, ds ; ES = DS mov es, ax mov si, offset network mov di, si IPX 9 ; IPX Get Internetwork Address command ; * Last, we have to initialize the SPX interface xor ax, ax IPX_CHECK 10h ; SPX Initialize command jz derr_xpx_init ; Quit if it's not there mov _SPX_Version, bx ; Save the information returned mov _SPX_Max_Connections, cx mov _SPX_Available_Connections, dx xor ax, ax ; Good return jmp short done_xpx_init derr_xpx_init: mov al, -3 ; It's gone McCreedy! done_xpx_init: cbw ret xpx_init endp ; ************************************************************************** ; * int DUMMY_IPX_FUNCTION( void ) -- Dummy function that returns error ; * code -10 so that system will not hang if not initialized ; * Given: nothing ; * Returns: -10 always ; ************************************************************************** dummy_IPX_function proc far mov ax, -10 ret dummy_IPX_function endp end Example 1: mov ax, 7a00h ; Function 7Ah, AL = 0 int 2fh ; MS-DOS Multiplex interrupt ; Returns with AL == 0FFh if xPX ; exists and ES:DI == xPX vector inc al ; Set ZERO if AL == -1 jnz outta_here ; Quit if xPX isn't around mov IPX_Vector, di ; Save the xPX entry vector mov IPX_Vector+2, es
Copyright © 1992, Dr. Dobb's Journal