Important: The information in this document is obsolete and should not be used for new development.
A Sample Socket Listener
There are many ways to implement a socket listener that follow the requirements described previously for using and preserving registers and reading packets. This section uses a sample socket listener that shows one way to implement the process within a DDP socket-client application that reads in the packet contents. The sample code also shows those segments of the sample client application that set up the socket listener and check to determine when a packet that the socket listener has read is available for processing by the client application.Some of the tasks that your socket listener can do that this sample socket listener does not illustrate are how to
The sample socket listener does, however, show you how to
- route packets to different sockets based on the socket number in register D0 when more than one socket uses your socket listener
- check the DDP protocol type field and ignore any packets that do not match the desired packet types that your socket listener is set up to receive
- check the source node ID and ignore any packets that don't come from a desired node
- implement a completion routine to be executed after a packet is processed
- buffer multiple packets
- retrieve the frame and DDP packet header information that DDP has already read into the RHA
- calculate and compare the packet checksum when a packet uses a long DDP header that includes the checksum value
Socket Listener Queues and Buffers
The sample socket listener uses two standard operating-system queues to manage the contents of the packets that it receives and makes available to the socket-client application. It calls these linked lists a free queue and a used queue. The use of two queues allows the socket listener to receive and process packets while the client application is reading the data from those packets that the socket listener has already processed.The free queue is used to manage available buffers that consist of data structures declared as
PacketBuffer
records. The sample socket listener uses the buffers in the free queue one at a time to hold the contents of an incoming packet as it processes
the packet header and data fields. The socket listener's initialization module,SL_InitSktListener
, shown in Listing 7-5 on page 7-24, releases the first element
or buffer of the free queue and points to it from the current queue element (current_qelem
) variable; it is this buffer that the socket listener uses when the .MPP driver calls the socket listener with a packet for it to process.After the socket listener fills in the fields of the record pointed to by
current_qelem
with the processed contents of the packet, it moves the buffer into the used queue, pointed to byused_queue
, for the client application to read. Then the socket listener releases the next record buffer from the free queue and points to it using thecurrent_qelem
variable. The sample code in Listing 7-7 on page 7-31 shows that when the client application has finished reading the contents of a used queue buffer element, it returns the buffer to the free queue pointed to byfree_queue
to make the buffer available again to the socket listener.The socket listener uses the variables declared in Listing 7-1 to point to
Listing 7-1 Declarations for pointers to the sample socket listener's queues and packet buffer
- the free queue's queue header
- the used queue's queue header
- the current buffer queue element
SL_Locals PROC ENTRY free_queue,used_queue,current_qelem free_queue DC.L 0 ;pointer to freeQ QHdr ; ; initialized by InitSktListener used_queue DC.L 0 ;pointer to usedQ QHdr ; ; initialized by InitSktListener current_qelem DC.L 0 ;pointer to current ; PacketBuffer record ENDP;Listing 7-4 on page 7-23 shows the Pascal-language client applicationSetUpSocketListener
procedure. This procedure calls theSL_InitSktListener
function to pass to the socket listener pointers to these two operating-system queues.When the .MPP driver calls the socket listener, if there is an available buffer, the socket listener processes the packet and returns in the fields of the packet buffer record the DDP type, the destination node ID, the source address in
AddrBlock
format, the hop count, the size of the packet, a flag to indicate whether a checksum error occurred, and the data delivered in the packet. If you use the sample record data structure as a model, you can extend it to include fields to hold additional values, such as the tick count at the time when the .MPP driver called your socket listener. Listing 7-2 shows the assembly-
language declaration for thePacketBuffer
record.Listing 7-2 Declaration for the sample socket listener's packet buffer record
PacketBuffer RECORD 0 qLink DS.L 1 qType DS.W 1 buffer_Type DS.W 1 ;DDP protocol type buffer_NodeID DS.W 1 ;destination node buffer_Address DS.L 1 ;source address in AddrBlock format buffer_Hops DS.W 1 ;hop count buffer_ActCount DS.W 1 ;length of DDP datagram buffer_CheckSum DS.W 1 ;chksum error returned here ; (cksumErr or noErr) buffer_Data DS.B ddpMaxData ;the DDP datagram ENDRListing 7-3 shows the socket listener's declaration for the queue header record, which is defined and used to make the code easier to read.Listing 7-3 Declaration for the sample socket listener's queue header record
QHdr RECORD 0 qFlags DS.W 1 qHead DS.L 1 qTail DS.L 1 ENDRSetting Up the Socket Listener
The client application that includes the sample socket listener uses a Pascal procedure,SetUpSocketListener
, to set up the socket listener's initialization routine.
TheSetUpSocketListener
procedure defines
If you base your own code on the sample code, you can add new fields to the record declaration, if you need them. If you do this, you must modify the packet buffer data structure defined in the socket listener code to match the high-level language record declaration.
- the free and used queue variables of type
QHdr
- a packet buffer record of type
PacketBuffer
to match the data structure defined
in the socket listener code (The sample Pascal code declares an array of 10 packet buffer records.)
Listing 7-4 shows the client-application's Pascal code that initializes the packet buffer records and then adds them to the free queue using the
_Enqueue
trap. The code calls theSL_InitSktListener
routine and passes to it pointers to the queue header for the free queue and the queue header for the used queue.Listing 7-4 Setting up the socket listener from the client application
CONST ddpMaxData = 586; TYPE PacketBuffer = RECORD qLink: QElemPtr; qType: Integer; buffer_Type: Integer; buffer_NodeID: Integer; buffer_Address: AddrBlock; buffer_Hops: Integer; buffer_ActCount: Integer; buffer_CheckSum: OSErr; buffer_Data: ARRAY[1..ddpMaxData] OF SignedByte; END; VAR freeQ, usedQ: QHdr; Buffers: ARRAY[1..10] OF PacketBuffer; PROCEDURE SL_TheListener; External; FUNCTION SL_InitSktListener (freeQ, usedQ: QHdrPtr): OSErr; External; PROCEDURE SetUpSocketListener; VAR err: OSErr; i: Integer; BEGIN freeQ.QHead := NIL; {initialize to nil to indicate empty queue} freeQ.QTail := NIL; {initialize to nil to indicate end of queue} usedQ.QHead := NIL; {initialize to nil to indicate empty queue} usedQ.QTail := NIL; {initialize to nil to indicate end of queue} FOR i := 1 TO 10 DO {add all buffers to the free queue} Enqueue(@Buffers[i], @freeQ); err := SL_InitSktListener(@freeQ, @usedQ); {initialize the socket listener code} IF err <> noErr THEN BEGIN {Perform error processing here} END; {You can now call POpenSkt because the socket listener is ready to } { process packets.} END;Initializing the Socket Listener
The sample socket-client application procedureSetUpSocketListener
(shown in the preceding listing) calls the socket listenerSL_InitSktListener
initialization routine provided in Listing 7-5 to pass it pointers to the two operating-system queues (used and free) that the socket listener uses after theSetUpSocketListener
procedure initializes these queues.The
SL_InitSktListener
routine sets up its local variablesused_queue
andfree_queue
to point to the queue headers for the two queues. Then the routine releases from the free queue the first buffer and sets thecurrent_qelem
variable to point to it. This is the buffer that the socket listener uses when it next reads a packet.Listing 7-5 Initializing the socket listener
;Function SL_InitSktListener(freeQ, usedQ: QHdrPtr): OSErr; ; SL_InitSktListener PROC EXPORT StackFrame RECORD {A6Link},DECR ;build a stack frame record Result1 DS.W 1 ;function's result returned to caller ParamBegin EQU * ;start parameters after this point freeQ DS.L 1 ;freeQ parameter usedQ DS.L 1 ;usedQ parameter ParamSize EQU ParamBegin-* ;size of all the passed parameters RetAddr DS.L 1 ;placeholder for return address A6Link DS.L 1 ;placeholder for A6 link LocalSize EQU * ;size of all the local variables ENDR WITH StackFrame,QHdr; ;use these record types LINK A6,#LocalSize ;allocate your local stack frame ;Copy the queue header pointers into our local storage for use in the ; listener LEA used_queue,A0 ;copy usedQ into used_queue MOVE.L usedQ(A6),(A0) LEA free_queue,A0 ;copy freeQ into free_queue MOVE.L freeQ(A6),(A0) ;Release the first buffer record from freeQ and set current_qelem to it MOVEA.L freeQ(A6),A1 ; A1 = ^freeQ LEA current_qelem,A0 ;copy freeQ.qHead into current_qelem MOVE.L qHead(A1),(A0) MOVEA.L qHead(A1),A0 ;A0 = freeQ.qHead _Dequeue MOVE.W D0,Result1(A6) ;return status @1 UNLK A6 ;destroy the link MOVEA.L (SP)+,A0 ;pull off the return address ADDA.L #ParamSize,SP ;strip all of the caller's parameters JMP (A0) ;return to the caller ENDP ENDProcessing a Packet
When the .MPP driver calls the sample socket listener, the socket listener's main module, theSL_TheListener
procedure, reads and processes a packet addressed to the socket-
client application. However, the socket listener can only process a packet if there is a packet buffer record available to hold the processed packet.The code shown in Listing 7-6 determines if the
current_qelem
variable isNIL
or not.If it is not
NIL
, the code gets a buffer, if one is available.
If the socket listener reads the packet successfully, it processes the header information that the hardware driver has stored in the .MPP driver's local variable space pointed to by the value in register A2. To do this, the socket listener
- If there is no buffer available, the code ignores the packet and calls the
ReadRest
routine with a buffer size value of 0. Before returning to the calling program, the code calls itsGetNextBuffer
routine to set up thecurrent_qelem
variable to point to the next available buffer, if there is one.- If there is a buffer available, the code reads in the packet data and processes it.
The socket listener then returns control to the calling program and waits until the .MPP driver calls it again when the .MPP driver next receives a packet addressed to a socket that is associated with the socket listener. Listing 7-6 shows the
- fills in a value for the hop count field of the packet buffer record and determines the packet length
- determines whether the DDP header is short or long and fills in the remaining fields of the packet buffer
- tests the checksum field of long DDP headers to determine if they are nonzero, indicating that the packet contains a checksum, and, if so, calculates the checksum
- adds the packet buffer to the used queue and then gets the next free buffer from the free queue and points to it with
current_qelem
SL_TheListener
procedure.Listing 7-6 Receiving and processing a DDP packet
;SL_TheListener ;Input: ; D0 (byte) = packet's destination socket number ; D1 (word) = number of bytes left to read in packet ; A0 points to the bytes to checksum ; A1 points to the bytes to checksum ; A2 points to MPP's local variables ; A3 points to next free byte in read-header area ; A4 points to ReadPacket and ReadRest jump table ; ;Return: ; D0 is modified ; D3 (word) = accumulated checksum SL_TheListener PROC EXPORT WITH PacketBuffer ;Get pointer to current PacketBuffer. GetBuffer: LEA current_qelem,A3 ;get the pointer to PacketBuffer MOVE.L (A3),A3 MOVE.L A3,D0 ;if no PacketBuffer BEQ.S NoBuffer ; then ignore packet ;Read rest of packet into PacketBuffer.datagramData. MOVE.L D1,D3 ;read rest of packet LEA buffer_data(A3),A3 ;A3 = ^bufferData JSR 2(A4) ;call ReadRest BEQ.S ProcessPacket ;if no error, continue BRA RcvRTS ;if error, ignore the packet ;No buffer; ignore the packet. NoBuffer CLR D3 ;set to ignore packet (buffer size = 0) JSR 2(A4) ;call ReadRest BRA GetNextBuffer ;no buffer available, so read next packet; ; maybe there will be a buffer ; for the next packet ;Process the packet you just read in. ; ReadRest has been called so registers A0-A3 and D0-D3 are free ; to use. Use registers this way: PktBuff EQU A0 ;current PacketBuffer MPPLocals EQU A2 ;pointer to MPP's local variables ; (still set up from entry to ; socket listener) HopCount EQU D0 ;gets the hop count DatagramLength EQU D1 ;determines the datagram length SourceNetAddr EQU D2 ;builds the source network address ProcessPacket: LEA current_qelem,PktBuff ;PktBuff = current_qelem MOVE.L (PktBuff),PktBuff ;Do everything that's common to both long and short DDP headers ; first, clear buffer_Type and buffer_NodeID to ensure their high ; bytes are 0. CLR.W buffer_Type(PktBuff) ;clear buffer_Type CLR.W buffer_NodeID(PktBuff) ;clear buffer_NodeID ;Clear SourceNetAddr to prepare to build network address. MOVEQ #0,SourceNetAddr ;build the network address in ; SourceNetAddr ;Get the hop count MOVE.W toRHA+lapHdSz+ddpLength(MPPLocals),HopCount ;get hop/length field ANDI.W #DDPHopsMask,HopCount ;mask off the hop count bits LSR.W #2,HopCount ;shift hop count into low bits ; of high byte LSR.W #8,HopCount ;shift hop count into low byte MOVE.W HopCount,buffer_Hops(PktBuff) ; and move it into the ; PacketBuffer ;Get the packet length (including the DDP header). MOVE.W toRHA+lapHdSz+ddpLength(MPPLocals),DatagramLength ;get length field ANDI.W #ddpLenMask,DatagramLength ;mask off the hop count bits ;Now, find out if the DDP header is long or short. MOVE.B toRHA+lapType(MPPLocals),D3 ;get LAP type CMPI.B #shortDDP,D3 ;is this a long or short DDP ; header? BEQ.S IsShortHdr ;skip if short DDP header ;It's a long DDP header. MOVE.B toRHA+lapHdSz+ddpType(MPPLocals),buffer_Type+1(PktBuff) ;get DDP type MOVE.B toRHA+lapHdSz+ddpDstNode(MPPLocals),buffer_NodeID+1(PktBuff) ;get destination node from frame header MOVE.L toRHA+lapHdSz+ddpSrcNet(MPPLocals),SourceNetAddr ;source network in high word, ; source node in low byte LSL.W #8,SourceNetAddr ;shift source node up to high byte ; of low word; get source socket ; from DDP header MOVE.B toRHA+lapHdSz+ddpSrcSkt(MPPLocals),SourceNetAddr SUB.W #ddpType+1,DatagramLength ;DatagramLength = number of ; bytes in datagram BRA.S MoveToBuffer ;Determine if there is a checksum. TST.W toRHA+lapHdSz+ddpChecksum(MPPLocals) ;does packet have checksum? BEQ.S noChecksum ;Calculate checksum for the DDP header. MOVE.L DatagramLength,-(SP);save DatagramLength (D1) CLR D3 ;set checksum to 0 MOVEQ #ddphSzLong-ddpDstNet,D1 ;D1 = length of header part to ; checksum pointer to destination ; network number in DDP header LEA toRHA+lapHdSz+ddpDstNet(MPPLocals),A1 JSR SL_DoChksum ;checksum of DDP header part ; (D3 holds accumulated ; checksum) ;Calculate checksum for the data portion of the packet (if any). MOVE.L buffer_Data(PktBuff),A1 ;pointer to datagram MOVE.L (SP)+,DatagramLength ;restore DatagramLength (D1) MOVE.L DatagramLength,-(SP) ;save DatagramLength (D1) ; before calling SL_DoChksum BEQ.S TestChecksum ;don't checksum datagram if ; its length = 0 JSR SL_DoChksum ;checksum of DDP datagram part ; (D3 holds accumulated checksum) TestChecksum: MOVE.L (SP)+,DatagramLength ;restore DatagramLength (D1) ;Now make sure the checksum is OK. TST.W D3 ;is the calculated value 0? BNE.S NotZero ;if nonzero, go and use it SUBQ.W #1,D3 ;if 0, make it -1 NotZero: CMP.W toRHA+lapHdSz+ddpChecksum(MPPLocals),D3 BNE.S ChecksumErr ;bad checksum MOVE.W #0,buffer_CheckSum(A0) ;no errors BRA.S noChecksum ChecksumErr: MOVE.W #ckSumErr,buffer_CheckSum(PktBuff) ;checksum error noChecksum: BRA.S MoveToBuffer ;It's a short DDP header. IsShortHdr: MOVE.B toRHA+lapHdSz+sddpType(MPPLocals),buffer_Type+1(PktBuff) ;get DDP type MOVE.B toRHA+lapDstAdr(MPPLocals),buffer_NodeID+1(PktBuff) ;get destination node from LAP header MOVE.B toRHA+lapSrcAdr(MPPLocals),SourceNetAddr ;get source node from LAP header LSL.W #8,SourceNetAddr ;shift src node up to high byte of low word MOVE.B toRHA+lapHdSz+sddpSrcSkt(MPPLocals),SourceNetAddr ;get source socket from short DDP header SUB.W #sddpType+1,DatagramLength ;DatagramLength = number of bytes in ; datagram MoveToBuffer: MOVE.L SourceNetAddr,buffer_Address(PktBuff) ;move source network address into ; PacketBufffer MOVE.W DatagramLength,buffer_ActCount(PktBuff) ;move datagram length into PacketBuffer ;Write the packet into the used queue and ; get another buffer from the free queue for the next packet. LEA used_queue,A1 ;A1 = ^used_queue MOVE.L (A1),A1 ;A1 = used_queue (pointer to usedQ) _Enqueue ;put the PacketBuffer in the used queue GetNextBuffer: LEA free_queue,A1 ;A1 = ^free_queue MOVE.L (A1),A1 ;A1 = free_queue (pointer to freeQ) LEA current_qelem,A0 ;copy freeQ.qHead into current_qelem MOVE.L qHead(A1),(A0) MOVEA.L qHead(A1),A0 ;A0 = freeQ.qHead _Dequeue RcvRTS: RTS ;return to caller ENDPTesting for Available Packets
Your client application must include a routine that determines if the socket listener has processed a packet for a socket associated with your client application. If it has, your client application routine must itself read and process the packet's contents, which are made available by the socket listener.If your client application includes several processes each with its own socket that use the same socket listener, your client application routine must include a mechanism to scan for packets addressed to specific sockets.
If you expect to receive multiple packets for a specific socket, you should anticipate the possibility that the client application might handle the first packet for a socket before
the socket listener processes the second packet for that socket. For example, to prepare for reception of multiple related packets addressed to the same socket, the sample client application's routine could check the socket listener's used queueQHead
field for addi-
tional packets periodically after it read the first packet.If you design your socket listener based on the sample one, your client's application should define a sufficient number of packet buffers so that as the client application releases a buffer from the used queue, processes its contents, and then moves that buffer back into the free queue for the socket listener to use, there are always buffers available in the free queue.
Listing 7-7 shows the code that the sample client application uses for this purpose. It periodically checks the
QHead
element of the socket listener's used queue. WhenQHead
is notNIL
, the client application knows that a packet is available for processing.Listing 7-7 Determining if the socket listener has processed a packet
TYPE PacketBuffer = RECORD qLink: QElemPtr; qType: Integer; buffer_Type: Integer; buffer_NodeID: Integer; buffer_Address: AddrBlock; buffer_Hops: Integer; buffer_ActCount: Integer; buffer_CheckSum: OSErr; buffer_Data: ARRAY[1..ddpMaxData] OF SignedByte; END; PacketPtr = ^PacketBuffer; VAR freeQ, usedQ: QHdr; bufPtr : PacketPtr; . . . WHILE (usedQ.QHead <> nil) DO BEGIN bufPtr := PacketPtr(usedQ.QHead); {get the packet ptr} IF (Dequeue(QElemPtr(bufPtr), @usedQ) <> noErr) THEN BEGIN {process the packet information} Enqueue(QElemPtr(bufPtr), @freeQ); {requeue the packet buffer for use} END ELSE BEGIN {Error occurred dequeueing packet - perform error } { processing here. However, because this is the only } { place in the code where buffers are dequeued, your error } { code should never be called. You can include a debugging } { statement here.} END; END;