Packet Fragmentation and Consolidation

The TCP transport protocol guarantees that all bytes sent are delivered in the order sent. However, packets may be fragmented and/or consolidated at any point along the process. For example, if you send out several packets of 100 bytes in rapid succession, they may be consolidated and delivered as a single packet of several hundred bytes. Or conversely, a large packet of 4000 bytes may be broken up and delivered as two or more smaller packets. In fact, the very idea of a "packet" doesn’t really exist in TCP, which is a byte-stream protocol. "Packets" are used at the network and datalink layers to implement the TCP byte-stream, but are essentially invisible to us as the TCP level.

This is an important consideration when using TCPX (or TCPCLI / TCPSRV), since the fact that they can send or receive a logical packet of data at a time, may falsely give the impression of these logical packets being the same as physical packets, i.e. more like "messages" or "data records", when they are not. They are just groups of bytes, which may well be regrouped (but never reordered) during the transport process. It is up to the application to design a protocol that works with this paradigm. There are several possible approaches to doing this.

One is to build your own two-level protocol on top of TCPX (TCPCLI / TCPSRV), with the lower level simply being responsible for accumulating (or transmitting) bytes, and the upper level mapping the buffered bytes into logical "packets" or "records". This is a bit awkward to do in BASIC, although it is certainly possible using unformatted variables with substring addressing, or arrays, along with the ability to map overlay structures in BASIC.

Another is to make extensive use of Opcode 8 to always check that the number of bytes in your "packet" is available before reading, and to always specify the "packet" size in the FLAGS argument when reading, so that you don’t accidentally read more than one "packet" at a time. This, of course, works in conjunction with a protocol involving fixed length logical "packets".

As discussed in the preceding topic, TCPX added the ability to combine a time-out with a blocking read operation. This simplifies the task of reading complete packets, but doesn’t completely eliminate the possibility that fragmentation and network delays will result in the timer expiring after receiving only a part of the desired logical packet. (This, however, would be pretty rare, and gets rarer still as the time out value is increased and the packet size is decreased. In fact, with packets less than about 1000 bytes, the chance of them being fragmented is practically nil, and even if there is fragmentation, the chance of there being a significant delay in arrival of the packets is another order of magnitude smaller. So it wouldn’t be unreasonable to forego the effort of reassembling physical packets into logical ones and instead just treat such a condition as a general protocol error, and reset the connection.)

A third approach, which might be combined with either of the others, is to include mandatory "ACK/NAK" messages in your protocol. These allow you to synchronize the two processes, as well as providing a way to abort the connection at convenient points where errors might be anticipated. For example, consider a simple file transfer protocol. The client sends a (fixed length) "request packet" to the server asking to retrieve a certain file, and the server responds first with a "header packet" giving details about the file (most importantly its size) and then sends a series of "data packets" containing the file. If the sender immediately followed the "header packet" with "data packets", they might arrive merged, requiring the receiver to separate them. But if the sender waits for the client to "ACK" the "header packet" before proceeding, the client can avoid excess manipulation of the incoming data, and if necessary, it can abort the transmission right here before it starts by sending a "NAK". The following code excerpt illustrates such a client:

! Simple file transfer client

map1 CONTROL'PACKET                 ! arbitrary packet layout (must match server!)

     map1 CTL'OPCODE,B,1            ! 0=end, 1=request file, 2=ACK, 3=NAK

     map1 CTL'SIZE,B,4              ! # bytes in file (0=NA)

     map1 CTL'DATA,S,251            ! requested file name

map1 CTL'PAK'SIZ,F,6,256

 

map1 DATA'PACKET,X,1024

 

     FLAGS = 0                      ! (blocking connection)

     call CONNECT                   ! connect to server

     if STATUS <= 0 goto ERROR

     CTL'OPCODE = 1

     CTL'DATA = "MYDATA:SAMPLE.DAT"

     FLAGS = CTL'PAK'SIZ            ! send fixed length request packet

     call SEND'CONTROL'PACKET

     if STATUS < CTL'PAK'SIZ goto ERROR

     call READ'CONTROL'PACKET       ! wait for response

     if STATUS < 1 goto ERROR       ! premature close (0) or other error (<0)

     if STATUS < CTL'PAK'SIZ then   ! (partial packet received – loop and get more)

          < get rest of control packet >

     endif

     open #CH, CTL'DATA, OUTPUT     ! open file ch to write received file

                                    ! (if error during open, our error trap

                                    ! should send a NAK to server, possibly

                                    ! with a reason in CTL'DATA)

     CTL'OPCODE = 2 : FLAGS = CTL'PAK'SIZ

     call SEND'CONTROL'PACKET       ! send ACK to server

     if STATUS < CTL'PAK'SIZ goto ERROR

RCV'LOOP:

     FLAGS = 0                      ! receive up to size of DATA'PACKET

     call READ'DATA'PACKET

     if STATUS <= 0 goto ERROR      ! premature close (0) or error (<0)

     PRINT #CH, DATA'PACKET[1,STATUS];     ! write received bytes to file

     TOT'RCVD = TOT'RCVD + STATUS

     if (TOT'RCVD < CTL'SIZE) goto RCV'LOOP

     close #CH

     call CLOSE'CONNECTION