Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Tools

Improving Kermit Performance


FEB96: Improving Kermit Performance

Improving Kermit Performance

A windowing strategy makes all the difference

Tim Kientzle

Tim, a technical editor with DDJ, is the author of The Working Programmer's Guide to Serial Protocols (Coriolis, 1995) and Internet File Formats (Coriolis, 1995). He can be contacted at [email protected].


Many people associate Kermit with ponderously slow file transfers. Conservative encoding, small packets, and "stop-and-go" half-duplex transfers make speed a common complaint about the original 1981 protocol. (For a discussion of the original Kermit protocol, see "Kermit Meets Modula-2," by Brian Anderson, DDJ, May 1989.)

A lot can change in 15 years, however. Developers at The Source Telecomputing began to experiment with "windowing Kermit," also known as "SuperKermit," in 1985. Windowing allows the Kermit sender to send multiple packets before expecting a reply. The combination of windowing, longer packets, and relaxed encoding constraints makes today's Kermit protocol one of the most flexible and best-performing file-transfer protocols available.

Kermit's windowing approach is clearly faster than protocols such as XModem and YModem, which must wait after each packet. What many people don't realize is that under less-than-ideal conditions, Kermit's windowing approach is significantly faster than ZModem, a protocol with a well-deserved reputation for fast transfers over good-quality connections. The difference lies in how these two protocols respond to errors.

In this article, I'll compare the error-handling strategies of a variety of popular protocols. I'll then give a brief overview of Kermit and describe some simple heuristics I've developed to improve the performance of Kermit's windowing strategy.

Error-Handling Strategies

Most serial and networking protocols use a general error-correction strategy called "retransmission." The sender computes an error-detecting signature value, such as a checksum or cyclic redundancy check (CRC), for each block of data and sends that signature value with the data. The receiver recomputes the signature and compares it with the value received from the sender. If the two values don't match, the receiver asks the sender to retransmit the block.

XModem and YModem use this strategy in a simple way. The sender sends a single packet at a time and waits for a response. This works well if there is no delay; that is, if each packet arrives immediately at its destination. In that case, the sender sees an instantaneous response to each packet, allowing it to maintain an almost continuous flow of data, even when there are errors.

In practice, most connections have delays. Each sent packet must navigate through system I/O buffers, modems, networks, and other obstacles before being received. XModem can be slow when there are delays; the sender must wait after each packet for a complete round-trip from the sender to the receiver and back. When there are both delays and errors, there will be several round-trip delays for some packets.

Because of these delays, many protocols send several packets before expecting a response. This allows the sender to continue sending while awaiting the receiver's response, with corresponding speed gains. However, the price of these performance gains is more-complex error handling.

Protocols such as ZModem and UUCP's G protocol allow multiple outstanding packets, but attempt to reduce the complexity by only transferring packets in order. At any point in time, the receiver only accepts the next consecutive packet. If the receiver sees the wrong packet (for example, if the packet the receiver expected was lost or damaged and it's now receiving a later packet), it will ignore it and send an error notification to the sender. This simplifies both the receiver and sender. However, when an error occurs, the two sides must wait for a complete round-trip, as the receiver's error message travels back to the sender and the sender resends the correct packet. Intervening packets are simply discarded. As a result, much of the speed advantage of these protocols is lost when errors occur.

The most complex approach is "packet reassembly," used by TCP (Transmission Control Protocol) and Kermit. In this approach, the receiver stores each correct packet, even if it arrives out of order. The name "packet reassembly" comes from the process of reassembling the packets into the correct order before passing the received data to its destination. If a packet is damaged, only that single packet will be resent; in ZModem, a number of subsequent packets may also have to be repeated. When properly implemented, a protocol using packet reassembly need never wait for a full round-trip, even when there are errors.

Kermit Basics

Kermit was developed at Columbia University in 1981 by Frank da Cruz and Bill Catchings to provide a way of transferring data between a variety of different computers. Since then, Kermit has been extended to support packet reassembly ("windowing"), a variety of file attributes, crash recovery, character-set translation, and other features. Modern Kermit is efficient and flexible, providing fast transfers under less-than-ideal conditions.

In a Kermit transfer, the sender and receiver exchange packets of data. Figure 1 is the original Kermit packet format. An important design feature of Kermit is that all of the packet overhead--including the length, sequence number, and error check--is in ASCII form. This allows Kermit, with the optional eighth-bit encoding, to function in situations where only the printable ASCII characters can be safely transferred. Two additional formats--"long" and "extra-long"--support longer packets and a separate error check for the header; see Figure 2.

Generally, the Kermit sender sends a packet, possibly containing some data, and the receiver responds with an Acknowledge packet whose data field contains the response. For example, when a file transfer begins, the sender sends a Send-Init packet with an encoded string specifying the sender's capabilities; the receiver's acknowledgment specifies the receiver's capabilities. A packet and its response have the same sequence number. You can think of a Negative Acknowledge packet as a request by the receiver for a particular packet; an idle receiver waiting to see a Send-Init will periodically emit Negative Acknowledgments with sequence number zero. I'll use the term "exchange" to refer to a single packet sent by the sender and the receiver's corresponding Acknowledge packet.

A complete transaction starts with a Send-Init exchange to establish the common capabilities of the sender and receiver. Each file transferred requires a File exchange to establish the filename, the transfer of the actual file data (which I'll explain later) and an End-of-File exchange. After all files are transferred, a Break exchange terminates the transaction.

Kermit's original strategy for transferring file data used this exchange mechanism in a simple fashion. The sender would send a Data packet containing some file data and wait for the corresponding Acknowledge packet. The receiver would respond to the correct packet with an Acknowledge; a timeout or an out-of-sequence packet would prompt a Negative Acknowledge requesting the correct packet.

If you are familiar with XModem, Kermit's original transfer strategy is similar, but with one important difference. Every response from the receiver specifies the particular packet being acknowledged or requested. This feature, lacking in XModem, allowed Kermit to easily add support for full-duplex packet reassembly.

Kermit's Packet-Reassembly Strategy

Modern Kermit retains the original strategy for everything except the actual data transfer. The initial handshake, start-of-file, and end-of-file negotiations would be more complex if the sender were allowed to send multiple packets. While transferring file data, however, Kermit allows the sender to send multiple packets before receiving an acknowledgment. The range of packets that a Kermit sender or receiver is still actively managing is called the "window."

Like most protocols, Kermit limits the size of the window. The window size is negotiated by the sender and receiver at the beginning of the transfer. If the negotiated window size is 1, the sender will wait after each packet for a response. This is completely compatible with the original protocol. If the negotiated window size is 20, then the sender can send a string of 20 packets before requiring an acknowledgment for the first one.

The window size is not a limit on the number of packets that have not been acknowledged. If the window size is 20, the sender cannot send packet number 207 until it receives an acknowledgment for packet number 187, even if every intervening packet has been acknowledged. In addition, the window size cannot exceed 32 packets. Both of these restrictions are necessary to avoid ambiguity. When the receiver sees a packet from the sender, it must determine the full packet sequence number from the six bits stored in the packet header. The only additional information the receiver has for determining the full packet-sequence number is the window size; the full packet number of the packet just received cannot differ from the packet expected

by the receiver by more than the window size.

Window Management

In my Kermit implementation, the details of packet reassembly are handled by the "reliability" layer. Lower layers handle the transmission and reception of single packets. Higher layers handle the transfer of entire files. The reliability layer ensures that each packet arrives at its destination; this is where errors are handled.

For simpler protocols such as XModem and YModem, the receiver's reliability functions wait for the correct packet, sending negative acknowledgments until the expected packet is successfully received. The sender's reliability layer repeatedly sends a packet until it is acknowledged.

For Kermit, this simple picture isn't sufficient. To the higher layers, the reliability layer looks the same as the simple case previously described. The reliability layer functions are still called once to send or receive each packet. Internally, however, there are significant differences. The reliability layer must cache data so it can juggle out-of-order packets.

Under good conditions, the sender's reliability function can always return immediately. Every time the function is called, it has already received an acknowledgment for the first packet in the window, so the function can return as soon as it sends the new packet, stores the packet in the cache, and processes any pending packets.

Under less ideal conditions, the sender must exercise some care to keep the transfer moving smoothly. For example, consider the situation in Figure 3. Each D is a Data packet sent; the Ys are the corresponding Acknowledge packets. As you can see, packet 32 was just sent; the most recent Acknowledge was for packet 25. However, no Acknowledge has been received for packet 13. If the window size is 20, then no further packets can be sent until the acknowledgment for packet 13 is received; the window is said to be "blocked." In my implementation, the sender's reliability function will not return until the window becomes unblocked.

The challenge for the sender is to keep the window from blocking. It can do this by correctly determining when to resend a packet. In Figure 3, if the sender had resent packet 13 earlier, it might have received an acknowledgment by now and avoided having the window block.

Just keeping the window from blocking isn't enough, however. One naive strategy is to resend a packet whenever a later packet is acknowledged. With that strategy, the sender would have sent a total of 13 copies of packet 13 by the time the situation in Figure 3 arose, and would likely have received a response from one of those repeated packets in time to avoid having the window block. But that approach would slow the rest of the transfer; time spent needlessly repeating packet 13 could have been spent sending additional data. The trick is to repeat packets soon enough that a response can be obtained before the window blocks, but infrequently enough to avoid needless duplication.

Keeping the Books

As you might expect, the way to break this apparent impasse is to track the correct data about each packet. There are two basic sources of information: sequence and time.

Unlike some networking protocols, Kermit operates over serial connections in which packet order is preserved. If you get an acknowledgment for packet 14 without seeing one for packet 13, then you can reasonably assume that packet 13 should be repeated; see Figure 4.

Using packet sequence to detect lost packets does not mean using the packet-sequence numbers. In Figure 4, packets 21 and 22 were just sent; the next packets sent will be 13 and 23. If at some point you receive an acknowledgment for packet 13, you can use that to check if packets 21 or 22 should be repeated. You can track the actual packet sequence by storing, for each packet sent, the number of the previously sent packet. That way, each time you receive an acknowledgment, you can chain back to check the previously sent packets.

This chaining must be handled carefully to avoid degenerate situations. For example, if a packet is sent twice in a row, you don't want it to indicate itself as the previously sent packet. I maintain this list as a doubly linked list and remove each sent packet from the list and reinsert it at the end. Example 1 is an outline of the code that's executed every time a packet is sent.

Of course, even with this sequencing heuristic, the window will sometimes block. For example, at the end of the file-data transfer, the window size is reduced to one, which will almost always result in a blocked window as the final packets of file data are handled.

As before, a blocked window is no reason to needlessly repeat the first packet in the window. Instead, repeat a packet only when you're reasonably certain that enough time has elapsed for a response to have been received. Most protocol implementations rely on fixed or user-specified timeouts. My Kermit implementation instead measures the round-trip delay and adjusts the timeout based on the observed behavior. This only requires timestamping each sent packet and measuring the delay when an acknowledgment is received. To avoid ambiguity, I only use round-trip measurements for packets that were sent once.

Knowing the average round-trip delay isn't quite sufficient. Some connections have a wide variation in the delay. One packet might require only four seconds for a round-trip; the next might require ten. This kind of variation is common on some packet-switched networks and when transferring files with heavily loaded hosts. Again, rather than hard-wiring assumptions about the connection, I measure the standard deviation of the round-trip delay. Example 2 is the method I use for tracking the average round-trip time and the standard deviation. To avoid overflow, I use the definition of standard deviation (root-mean-square of the differences from the mean) rather than one of the many standard formulas. My time routines return time values in milliseconds and the standard formulas require keeping the sum of the squares of the delays. These squares will be quite large, and overflowing a 32-bit integer is a real possibility. By keeping a decaying average of the squares of the differences, I can more easily bound the intermediate values in my standard-deviation computation. To compute the square root, I use one iteration of Newton's method on each iteration. From these statistics, I use a timeout interval of the round-trip time plus three times the standard deviation plus two seconds.

Implementation

Listing One (beginning on page 91) presents KSendPacketReliable, the primary reliability function from my implementation of Kermit. This function is called with a packet of data to be sent. It tracks information about the currently outstanding packets and processes responses from the receiver. The KERMIT_PRIVATE structure maintains information about the transfer; a pointer to it is passed to each function. In particular, the KERMIT_PRIVATE structure maintains a cache of 64 EXCHANGE structures, one for each possible packet-sequence number. Each EXCHANGE structure maintains information about a particular packet exchange, including the type of the packets sent and received, the time the packet was sent, the number of times the packet was sent, and pointers to the actual packet data.

The full implementation for my program, which provides basic Kermit file-transfer support, is available electronically (see "Availability," page 3). The UNIX version has been compiled using GCC 2.6 on FreeBSD 2.0. The serial routines in ftserial.c may require tweaking for other UNIX-like platforms.

Conclusion

My earlier Kermit implementations used a variety of different heuristics for deciding when to repeat packets. One simply resent the first packet when the window blocked; another resent it when certain later acknowledgments were received. Replacing those approaches with the heuristics described in this article substantially improved performance and answered questions that had arisen from the earlier attempts. (For example, it was clear early on that retransmission should be considered when the window is blocked and when an Acknowledge is received. The two different heuristics described in this article neatly answered the requirements of those two different situations.)

I've timed file transfers with two Kermit implementations and two ZModem implementations over a local modem connection that lacks proper flow control. In this situation, my Kermit implementation achieved more than twice the transfer speed of any of the three alternatives, and was nearly five times as fast as one of the ZModem implementations. Although inconclusive, these results suggest that these heuristics can allow even imperfect connections to achieve good throughput.

Acknowledgment

Tom Lippincott originally suggested the sequencing heuristic described in this article.

References

Frank da Cruz's Kermit: A File Transfer Protocol (Digital Press, 1987) is the definitive reference on the Kermit protocol. Also see http://www.columbia.edu/kermit.

The source code described here is more fully explained in my book The Working Programmer's Guide to Serial Protocols (Coriolis Books, 1995), which also gives in-depth explanations of ZModem and Kermit. The sequencing and timeout heuristics presented here are new, however.

The complete source code from the book is available from ftp.coriolis.com in the /pub/bookdisk/serial directory. It's periodically updated with bug fixes and other improvements.

Figure 1: Kermit short packet format.

Figure 2: Kermit long and extra-long packet format.

Figure 3: Window blocked (window size of 20).

             1    1   2    2    3   3    4    4    5    5    6
   0....5....0....5....0....5....0....5....0....5....0....5....0...
Tx:             DDDDDDDDDDDDDDDDDDDD                              :
Rx:             .YYYYYYYYYYYY.......                              :

Figure 4: Acknowledgement for Packet 13 lost.

             1    1    2    2    3    3    4    4    5    5    6
   0....5....0....5....0....5....0....5....0....5....0....5....0...
Tx:   DDDDDDDDDDDDDDDDDDDD                                        :
Rx:   YYYYYYYYYY.Y........                                        :

Example 1: Maintaining the list of sent packets.

/* Step 1: Unlink this packet from doubly-linked list */
  slot = sequence number of this packet
  prev = exchange[slot].previousPacket;
  next = exchange[slot].nextPacket;
  if (slot == lastPacket) lastPacket=prev;
  if (prev >= 0) exchange[prev].nextPacket = next;
  if (next >= 0) exchange[next].previousPacket = prev;
/* Step 2: Link at end of list */
  exchange[slot].previousPacket = lastPacket;
  exchange[slot].nextPacket = -1;
  exchange[lastPacket].nextPacket = slot;

Example 2: Estimating the average and standard deviation.

thisDelay = now - <time packet was sent>;
if (roundTripSamples++ == 0) {
   /* Handle first sample differently */
   roundTripDelay = thisDelay;
   roundTripDelayDifference = 0;
}else{
   /* Use real average for first 30 samples, then decaying average */
   if (roundTripSamples > 30)
      roundTripSamples=30;
   /* Compute the square of the difference */
   differenceSquared = (thisDelay-roundTripDelay)*(thisDelay-roundTripDelay);
   /* Update the average */
   roundTripDelay += (thisDelay-roundTripDelay)/roundTripSamples;
   /* Update the mean square */
   roundTripDelayVariance += (differenceSquared-roundTripDelayVariance)
/roundTripSamples;
   /* Update root-mean-square using Newton's method */
   roundTripDelaySD = (roundTripDelaySD +
              roundTripDelayVariance/roundTripDelaySD) / 2;
}

Listing One

STATIC int KSendPacketFromCache(KERMIT_PRIVATE *pK,long sequence,int addToList)
{
   int     slot = sequence & 63;
   long    prev, next;
   if ((pK->exchange[slot].myPacket.type == 0) 
                                || (pK->exchange[slot].myPacket.data == NULL))
      return kOK;
   prev = pK->exchange[slot].previousPacket; /* Unlink from list */
   next = pK->exchange[slot].nextPacket;
   if ((pK->lastPacket & 63) == slot) pK->lastPacket = prev;
   if (prev >= 0) pK->exchange[prev & 63].nextPacket = next;
   if (next >= 0) pK->exchange[next & 63].previousPacket = prev;
   pK->exchange[slot].nextPacket = -1;
   pK->exchange[slot].previousPacket = addToList ? pK->lastPacket : -1;
   if (addToList) {
      if (pK->lastPacket >= 0) /* Add to end of list */
          pK->exchange[pK->lastPacket & 63].nextPacket = 
                                                  pK->exchange[slot].sequence;
      pK->lastPacket = pK->exchange[slot].sequence;
   }
   pK->exchange[slot].tries++; /* Count number of sends */
   pK->exchange[slot].sendTime = SerialTime (pK->initTime); 
                                                       /* Stamp time of send */
   return StsWarn (KSendPacket (pK, slot, pK->exchange[slot].myPacket.type, 
                                                                  /* Send it */
                                pK->exchange[slot].myPacket.data,
                                pK->exchange[slot].myPacket.length));
}
STATIC int KSendPacketReliable(KERMIT_PRIVATE *pK, BYTE type,
                  const BYTE *pSendData, unsigned long sendDataLength, 
                                                  unsigned long rawDataLength)
{
   int     blocked = FALSE;
   int     err;
   int     slot = pK->sequence & 63;
   int     timeout = pK->my.timeout;
   { /* Put packet into cache */
      EXCHANGE *pThisExchange = &(pK->exchange[slot]);
      if (pThisExchange->myPacket.data == NULL) {
         if (pK->minCache < pK->minUsed) {
            SwapSlots (pK->minCache, slot); 
                                      /* Move free exchange to end of window */
            pK->minCache++;
            pThisExchange->yourPacket.type = 0;
         } else return StsWarn (kFail); /* Internal consistency failure */
      }
      if (pSendData == pK->spareExchange.myPacket.data) { 
                                                 /* In the reserved slot ? */
         BYTE   *pTmp = pThisExchange->myPacket.data;   /* Just swap it in */
         pThisExchange->myPacket.data = pK->spareExchange.myPacket.data;
         pK->spareExchange.myPacket.data = pTmp;
      } else /* copy it */
         memcpy (pThisExchange->myPacket.data, pSendData, sendDataLength);
      if (pK->sequence > pK->maxUsed) pK->maxUsed = pK->sequence; 
                                                    /* Update end of window */
      pThisExchange->sequence = pK->sequence;  
                                       /* Finish initializing this exchange */
      pThisExchange->myPacket.length = sendDataLength;
      pThisExchange->myPacket.type = type;
      pThisExchange->rawLength = rawDataLength;
      pThisExchange->tries = 0;
      pK->txPacket.data = pK->spareExchange.myPacket.data;
      pK->txPacket.length = 0;
   }
   StsRet (KSendPacketFromCache (pK, pK->sequence, TRUE)); /* Send packet */
   if (pK->minUsed <= pK->minCache)  blocked = 1; /* Are we blocked? */
   if (pK->maxUsed - pK->minUsed + 1 >= pK->currentWindowSize)
                                                      /* How blocked are we? */
      blocked = (pK->maxUsed - pK->minUsed + 1) - pK->currentWindowSize + 1;
   err = KReceivePacketCache (pK, 0); /* Get a packet if one's ready */
   do { /* Until we're not blocked and there are no more packets pending */
      switch (err) {
      case kBadPacket: /* Didn't get a packet */
      case kTimeout:
         break;
      default:  /* Unrecognized error, pass up to caller */
         return StsWarn (err);
      case kOK: /* Got one! */
        {
          EXCHANGE *pThisExchange = &(pK->exchange[pK->rxPacketSequence & 63]);
          switch (pK->rxPacket.type) {
          case 'N': /* Got a NAK */
             if (pThisExchange->myPacket.type != 0) /* Resend packet */
               StsRet (KSendPacketFromCache (pK, pK->rxPacketSequence, FALSE));
             if ((pK->currentWindowSize > 1) || (pK->maxUsed > pK->minUsed))
               break; /* Don't generate implicit ACKs for large windows */
             pThisExchange = &(pK->exchange[(pK->rxPacketSequence - 1) & 63]);
             pThisExchange->yourPacket.type = 'Y';
          case 'Y': /* Got an ACK */
             if (pThisExchange->rawLength > 0) { /* ACKed before?*/
                if (pThisExchange->tries == 1) { /* Update round-trip stats */
                    long    now = SerialTime (pK->initTime);
                    long    thisDelay = now - pThisExchange->sendTime;
                    if (pK->roundTripSamples++ == 0) { /* First sample? */
                        pK->roundTripDelay = thisDelay;
                        pK->roundTripDelayVariance = 0;
                 } else {
                        long    oldAverage = pK->roundTripDelay;
                        long    diffSquared;
                        if (pK->roundTripSamples > 30) /* Average first 30 */
                           pK->roundTripSamples = 30;
                                                    /* Then decaying average */
                        pK->roundTripDelay += (thisDelay - pK->roundTripDelay) 
                                  / pK->roundTripSamples;
                        diffSquared = (thisDelay - oldAverage) * 
                                                      (thisDelay - oldAverage);
                        pK->roundTripDelayVariance += (diffSquared - 
                                         pK->roundTripDelayVariance) / 
                                         pK->roundTripSamples;
                        pK->roundTripDelaySD = (pK->roundTripDelaySD +
                                         pK->roundTripDelayVariance / 
                                         pK->roundTripDelaySD) / 2;
                     }
                  }
                  if (pK->sending) /* Update file progress on receipt of ACK */
                     pK->filePosition += pThisExchange->rawLength;
                  pThisExchange->rawLength = 0; /* Don't count packet again */
               }
           {
                  long    j, i = pThisExchange->previousPacket;
                  while (i >= 0) { /* Resend packets based on send order*/
                     j = pK->exchange[i & 63].previousPacket;
                     if (pK->exchange[i & 63].yourPacket.type != 'Y')
                        StsRet (KSendPacketFromCache (pK, i, TRUE));
                     i = j;
                  }
               }
               while ((pK->exchange[pK->minUsed & 63].yourPacket.type == 'Y')
               && (pK->exchange[pK->minUsed & 63].sequence == pK->minUsed)) {
                { /* Free up slots that have been acknowledged */
                  long    prev, next;
                  prev = pK->exchange[pK->minUsed & 63].previousPacket; 
                                                                   /* Unlink */
                  next = pK->exchange[pK->minUsed & 63].nextPacket;
                  if ((pK->lastPacket & 63) == (pK->minUsed & 63))  
                                                         pK->lastPacket = prev;
                  if (prev >= 0) pK->exchange[prev & 63].nextPacket = next;
                  if (next >= 0) pK->exchange[next & 63].previousPacket = prev;
                  pK->exchange[pK->minUsed & 63].previousPacket = -1;
                  pK->exchange[pK->minUsed & 63].nextPacket = -1;
                  }
                  pK->minUsed++; /* Mark this exchange as free */
                  if (blocked > 0) blocked--; /* Reduce count to unblock window*/
               }
               break;
            case 'E': /* Received error packet, terminate transfer */
               return StsWarn (kFailed);
            default: /* Received unrecognized packet type, terminate */
               return StsWarn (kFail);
            }
         }
         break;
      }
      { /* Resend timed-out packets */
         long    i;
         unsigned long now = SerialTime (pK->initTime);
         unsigned long packetTimeout = pK->roundTripDelay + 
                        3 * pK->roundTripDelaySD + 2 * SERIAL_TIME_SCALE; 
                       /* Avg + 3 standard deviations + 2 seconds */
         long    oldest = -1;
         unsigned long oldestTime = ULONG_MAX;
         long    firstOld = -1;
         for (i = pK->minUsed; i <= pK->maxUsed; i++) {
            if (pK->exchange[i & 63].yourPacket.type != 'Y') {
               if ((firstOld == -1) && (pK->exchange[i & 63].sendTime + 
                                                          packetTimeout < now))
                  firstOld = i; /* Find first timed-out packet */
               else if (pK->exchange[i & 63].sendTime < oldestTime) { 
                                                      /* Find oldest packet */
                  oldestTime = pK->exchange[i & 63].sendTime;
                  oldest = i;
               }
            }
         }
         if (firstOld != -1) /* Resend first timed-out packet */
            StsRet (KSendPacketFromCache (pK, firstOld, FALSE));
         if (oldestTime == ULONG_MAX) timeout = pK->my.timeout;
         else if (packetTimeout + oldestTime < now) /* Next oldest? */
            timeout = 0;
         else /* Compute interval until next timeout */
            timeout = (packetTimeout - (now - oldestTime)) / SERIAL_TIME_SCALE;
         if (timeout < 1) timeout = 1; /* Minimum is one second */
         if (timeout > pK->my.timeout) timeout = pK->my.timeout; 
                                                 /* Max is negotiated value */
      }
      err = KReceivePacketCache (pK, blocked ? timeout : 0); 
                                                  /* Try to receive a packet */
   } while (blocked || (err == kOK));
   if (pK->exchange[pK->sequence & 63].yourPacket.type &&
       (pK->exchange[pK->sequence & 63].sequence == pK->sequence))
      pK->rxPacket = pK->exchange[pK->sequence & 63].yourPacket;
   pK->sequence++;
   if (err == kTimeout)  return kOK;
   if (err == kBadPacket) return kOK;
   return StsWarn (err);
}


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.