So What Is SO_KEEPALIVE?
Joshua is a senior software engineer at Surf & Call Network Services. He can be contacted at [email protected].
The client and server are connected by sockets. The client and server occasionally send each other messages. Now the client suddenly crashes. To your surprise, the socket on the server remains forever, taking up memory and other resources, until the server attempts to send something. Only then, the failure is detected and the resources cleaned up.
For memory in the Java Virtual Machine, the garbage collector collects memory locations that aren't needed. In distributed systems, cleaning up resources is more difficult. Yet when a remote host or application crashes, or when the network is partitioned, resources devoted to servicing an unused connection need to be released.
Resources to be released include the socket (which takes at least 8 KB of nonVM memory), entries for a remote client in local Hashtable, or database connections helping the application service the remote side, as well as any per-session data structures, such as a database connection looking up data on behalf of the client. There may be one or two threads servicing each client. Other resources, found in higher level protocols such as RMI, Jini, and DCOM include remote leases and client call-back proxies. If these resources aren't cleaned up, the leak causes a slow accumulation over time and the server eventually crashes. In the client, data structures referring to the remote side also need to be cleaned up. All of these clean ups, not just plain old release of memory, can be included in the category of "distributed garbage collection."
The term "garbage collection" is used in a different sense here from the term "memory garbage collection" we are used to in Java. Garbage collection of memory cleans up memory slots known definitely to be unused. On the other hand, the challenge of remote garbage collection is to detect when the remote connection has unexpectedly failed. In most cases, connections are closed in an orderly fashion, and then cleaned up.
It is not difficult to clean up the resources -- once you know the connection is broken. The difficulty is knowing when the connection has failed unexpectedly.
The problem is that silence can't be distinguished from a crash. Many communication systems work on a simple full-duplex asynchronous scheme: The client initiates the connection, and either side can send a message to the other side at any time. The connection may be quiet for a long time, until one of the sides has something to send. When the two applications are silent, the connection is completely silent -- nothing is transmitted behind the scenes on the lower levels. IP protocols are packet-switched, not circuit-switched, so the network need bear no load from silent applications.
The reliable protocol TCP -- in contrast to the unreliable UDP -- helps provide a solution to this problem. All successful transmissions receive acknowledgments (ACKs) from the remote side. Failure to receive the ACK in a certain time period triggers an IOException in Java. A four-way acknowledgment protocol allows an orderly closing of the connection (on the other hand, a protocol isn't possible in the case of a sudden crash). The connection-close protocol takes care of cleaning up resources on both sides.
Thus, there is a way to distinguish silence from failure. Just send some data, even dummy data. This either succeeds or produces an IOException, indicating that communication is no longer possible. At this point, you should close the socket, even though the four-way protocol may not succeed.
Still, the reliability of TCP is one way: It tells you if messages you send don't get through, but it doesn't tell you if any messages the remote side might be sending would get lost along the way. As long as the connection is not used, you can't know that it has failed. Thus, there is no way to tell the difference between a silent remote side and a crash on the Internet, unless you probe the connection.
TCP sockets provide a built-in solution. The Keep-Alive socket option SO_KEEPALIVE (familiar to C network programmers) makes the socket probe the connection to be probed to see if it is still alive. A dummy packet is sent and, if no ACK is forthcoming after several tries, the socket resources can be released.
java.net.Socket prior to JDK 1.3 didn't provide access to SO_KEEPALIVE? David Brown, one of the authors of Sun's java.net package, says in the JDK Guide, "the conventional wisdom on [Keep-Alive] is that this functionality is best handled at the application level" (http://java.sun.com/ products/jdk/1.3/docs/guide/net/socketOpt.html). He adds that there are serious limits on using the built-in Keep-Alive (even with the 1.3 API). In his classic TCP/IP Illustrated Volume 1: The Protocols (Addison-Wesley, 1994), W. Richard Stevens says that "The keepalive timer is a controversial feature. Many feel that this polling of the other end has no place in TCP and should be done by the application, if desired. This is one of the religious issues, because of the fervor expressed by some on the topic."
So you're supposed to implement Keep-Alive on the application level -- but how do you do it? In this article, I'll show a few approaches, which you can adapt to your needs. (To present the algorithms in their simplest form, the listings here are in pseudocode. The full Java implementations are available electronically; see "Resource Center," page 5.) I will also compare Keep-Alive to leases, which perform the corresponding function in RMI and Jini, and mention the corresponding mechanisms for CORBA and DCOM.
Although this article is oriented toward Java, the techniques can (and should) be implemented in C++, Perl, or any other language.
What Is SO_KEEPALIVE?
Socket options are a way of configuring sockets. One such option is Keep-Alive. Despite its name, Keep-Alive is actually responsible for killing the socket when the remote side is not available. A socket with SO_KEEPALIVE probes the connection automatically when the remote side has been quiet for too long. It sends an empty TCP packet and waits for a TCP ACK. If the ACK arrives, the socket stays alive; hence, the source of the term Keep-Alive. (The term Keep-Alive is used in a quite different way in a nonstandard extension to the HTTP/1.0 protocol. In that case, Keep-Alive refers to the practice of not closing a socket after each HTTP request/response, but rather reusing the socket by streaming repeated request/response pairs over it. This practice is the default for HTTP/1.1, where the term "persistent connection" is used.) On the other hand, after several failed probes, the socket is closed.
Sounds great, but unfortunately it is not practical. Many operating systems do not implement SO_KEEPALIVE, and those that do set the default timeout period for silence at two hours, far too long for most applications. These limitations are intentional, on the theory that an unused connection should consume zero bandwidth. This two-hour timeout period can in fact be reduced in some operating systems, but even where that is allowed, the timeout period is global for the entire machine, not per process or per socket. Because of these problems, and because each application has different needs for cleaning up sockets, each application must achieve the goal of socket option Keep-Alive by itself.
Because of the cross-platform differences and the difficulties with SO_KEEPALIVE even where it is implemented, the Java development team left out access to this socket option in previous JDKs. In JDK 1.3, SO_KEEPALIVE is accessible, but, for the reasons just pointed out, not too useful.
Given the limitations, then, what is SO_KEEPALIVE good for? If you know that your operating system supports this socket option (which is not possible in cross-platform Java), then SO_KEEPALIVE is a good safety measure, making sure that bugs in your code or in imported libraries don't tie up system resources forever. Still, this can only serve as a secondary measure; the primary responsibility for clean-up must lie within your application.
Keep-Alive Strategies
The way that you implement your distributed garbage collection, cleaning up unneeded socket resources such as memory and ports, depends on your application.
The Scylla and Charybdis of distributed systems are partial failure and nondeterministic latency. You design your Keep-Alive strategy to deal with them as effectively as possible.
Partial failure means that remote components or the connection to them may fail at any time, and you must design your application to deal with that, not crash. Partial failure is actually worse for you, the developer, than total failure: You can't do anything about total failure, so there is no use worrying about it. Keep-Alive strategies allow one side to detect and respond to the failure of the remote application or to a network partition. (There is no way whatsoever to distinguish between these two types of failure, so they have to be considered together.)
Nondeterministic latency means that you can't predict how long it takes messages to pass from one side to the other. This makes implementing a Keep-Alive much harder. If you assume that a certain period of silence implies a connection failure, so that you close the connection, you might be wrong. Perhaps there is just a blip in the network, so that for a short time there is an unusually large latency, and soon the latency comes back to normal, so that closing the connection turns out to be a mistake. Not only your Keep-Alive code, but also the TCP ACK mechanism itself may encounter this problem; there is no perfect way to get around it. When your mechanisms detect what they interpret as a connection failure, they must completely close and clean up the socket and related resources, even if there is a chance that they are doing this needlessly. Remote resources cannot be left in an indeterminate state, even if your interpretation of the current state of the connection is not fully certain.
All you can do is choose an approximate timeout period and hope that no stray messages take longer to arrive than the timeout period allows. You can also adjust the timeout in response to network conditions that you measure. When an application measures round-trip time and detects that it is longer than usual, it could increase the timeout. Still, nothing can deal with sudden, unexpected increases in network latency.
You have to make some difficult choices. If you make the application hypersensitive, with a short timeout, then connection failures are detected quickly; but on the other hand, your Keep-Alive probe packets burden the network, and short-term latency spikes are interpreted as connection failure. However, if you make the application very patient, with long timeouts, then it is flexible in the face of latency changes and imposes little burden on the network, but it cannot detect failures quickly.
Only the needs of your application can determine the strategy to choose. If users require a visual indication of failure or mission-critical applications depend on a live connection, the Keep-Alive timeouts must be more sensitive, perhaps on the order of seconds. On the other hand, if the purpose is to clean up dead resources on the server, the time period can be somewhat longer, on the order of minutes, on the assumption that you can tolerate accumulated garbage for a while. If your purpose is clean up on the client, then the time period can be even longer, since clients generally have less stress on them than multiclient servers. The longest timeouts, perhaps on the order of hours, may be devoted to identifying frozen remote applications, because freezes occur less frequently than crashes or network partitions.
Many communications applications don't need a Keep-Alive at all. Keep-Alive has its costs: Not only does it impose a burden on the CPU and the network, but it may cut off live connections when there is a temporary network failure or latency spike.
Some socket-based protocols are not asynchronous but rather request/response oriented. For example, in HTTP, the client sends a request and the server responds to that request. The server can then close the socket connection (although HTTP/1.1 allows persistent connections). Little more is needed for proper clean-up than a read timeout on the socket: A call to setSoTimeout(int timeout) on your Socket ensures that an exception is thrown when the InputStream's read() method has received no incoming data for timeout milliseconds. This approach is not possible in full- duplex asynchronous communications in which either side may be silent for an indefinite time, yet you still want the socket connection to stay open. Where Keep-Alive is needed, the response to a dead connection depends on the application.
In a server, the socket and data structures should simply be cleaned up, although the server might store persistent data about the client, in the hope that the client will reconnect. This persistent data should not be cleaned up until a longer timeout period indicates that the client is not likely to return. (Compare servlet Session data, which is stored for approximately 30 minutes if the client does not reconnect.) Even if persistent data is stored, any resources associated with the connection itself, such as sockets, need to be released.
On the other hand, a client, after cleaning up resources, might try to reconnect. If the network or the server has truly failed, reconnection is impossible, so a thread should periodically try to reconnect. In other cases, the client-side software might leave the decision to reconnect to the human user.
While SO_KEEPALIVE is of limited use, other socket options will come in handy in improving your application-level Keep-Alive. TCP_NODELAY disables Nagle's algorithm, which causes the socket to save up small amounts of data and send them out together, rather than wastefully sending lots of small packets (this mechanism is distinct from the ordinary socket buffer emptied by flush()). To keep your probe messages from being saved up, disable Nagle's algorithm by calling socket.setTcpNoDelay(true). Another socket option, SO_LINGER, allows the socket to remain in the TIME_WAIT state after you call close(); this allows any remaining data to arrive, but delays the actual closing of the socket. To cause an immediate close, call socket.setSoLinger(true,0). The potential downside is that messages from old and new sockets could be confused if the connection is immediately renewed.
Keep-Alive Examples
The Keep-Alive examples (available electronically) presented here have their performance and functionality trade-offs, so careful planning is needed to choose the right protocol for your application. You will probably have to adjust the algorithm to fit the needs of your own application.
- You can periodically probe the connection to see if you get an IOException, often caused by a TCP/IP acknowledgment failure.
- As an optimization, you can just wait and listen. Then, only probe the connection if it has been silent for a certain timeout period.
- You can create your own "Are-You-There/Here-I-Am" protocol; this is useful for detecting a frozen or otherwise malfunctioning remote application.
- You may want to follow the model of Jini's lease mechanism for your garbage collection. A somewhat different leasing mechanism is used in RMI's Distributed Garbage Collection.
Java's multithreading capabilities make it easy to set up a separate thread to deal with the Keep-Alive process. By placing Keep-Alive in a separate thread from the main send/receive threads, you can make the encapsulation of your Keep-Alive with the Socket almost as tight as the encapsulation of socket options.
Keep-Alive objects function as part of a two-way protocol, interacting with the remote side. Some require no cooperation from the remote side, other than filtering out dummy messages. Most communication protocols, on lower or higher levels, already define an ignorable dummy message. Other Keep-Alives require a specific response from the remote side to indicate viability of the connection. In some cases, different combinations of Keep-Alives are possible on the two sides. Moreover, you might want to combine several Keep-Alive algorithms in a single application.
Efficiency can often be improved with a piggybacking optimization. This involves using ordinary traffic as evidence of life on the other side. For example, in an algorithm that relies on outgoing probe messages, you can set a timer each time any outgoing message is sent. Only if the timer expires without being reset do you send the probe message. If there is extensive outgoing traffic, the timer will never expire. Likewise, an algorithm that gets suspicious at silence from the remote side can reset its timer each time any message is received. A similar optimization is to combine the Keep-Alive transmissions of all client objects within a given application or host.
The Keep-Alives in the online examples are encapsulated in classes derived from KeepAlive, an abstract class derived from Thread. Certain features in these classes have been simplified to avoid obscuring the essentials: The transmission units are simple strings, Keep-Alives are per socket rather than across the entire application, and timeout periods are kept very short to show quickly how Keep-Alive works. Adjust these assumptions to match your needs.
ActiveKeepAlive
The simplest Keep-Alive is to send short dummy messages periodically. You get an IOException if something goes wrong. Catch the exception, call close() on the socket, and other threads that are blocked on the socket are released. This simple protocol, called ActiveKeepAlive (Listing One) is one sided in that it does not require any application-level intervention from the remote side, other than filtering out the dummy messages. You may want to run ActiveKeepAlive on the server side alone, since server resources are often more precious than client-side resources; alternately, you can run it on both the server and client sides simultaneously.
In this Keep-Alive, as in others, you immediately take IOException as an indication of socket failure, and close the socket. In TCP socket option Keep-Alive, on the other hand, the remote side is queried several times to see if an ACK can be elicited. Here you rely on the Java implementation of sockets to tell us accurately when the socket has failed. In fact, in Java, there is no choice but to assume that IOException indicates connection failure. Even if something else -- say, a temporary spike in the latency -- caused the IOException, and the connection has not really failed, you close the socket and all associated resources.
As you send probes in this and other Keep-Alives, remember that there are outgoing buffers on the socket layer and in your buffered Java output streams. You must flush() the output stream or fill the buffer to make sure that the transmission actually goes out.
For consistency and encapsulation, all Keep-Alives described here devote one thread to each socket. When you have hundreds of clients, this can be an inefficient use of resources. A variation on our Keep-Alive theme is to devote a single thread to probing all clients; the thread tries clients sequentially. In design terms, this disrupts the encapsulation of the Keep-Alive with the socket, but in a multithreaded system, it reduces the number of Keep-Alive Threads from O(number of clients) to O(1). Simple load leveling can be gained by having the Keep-Alive thread wait a constant time between each client. With this method, adding clients does not increase the overall resources devoted to the Keep-Alive; on the other hand, as clients are added, each client is probed less often.
Despite its simplicity, a disadvantage of ActiveKeepAlive is that it generates traffic continually -- even if there is no suspicious silence in the socket. This puts a load not only on the network, but also on the processing capacities of the application, which must filter out the incoming messages. Piggybacking optimizations can help here.
PassiveKeepAlive
Instead of having two ActiveKeepAlives fire probes at each other, you can save resources by implementing an algorithm that resembles the socket option. This Keep-Alive, called PassiveKeepAlive (Listing Two), probes the connection with a dummy message only when it gets suspicious--when it notices that no incoming data has been received for a long time.
You can measure the period of silence by updating a timestamp whenever data is received, and having a thread periodically check the timestamp. Alternately, you can use the wait/notify mechanism in the Object class. Place a wait(timeout) in the Keep-Alive thread; when data arrives, restart the wait(timeout). If the wait(timeout) expires, the timeout period has passed with no incoming data. The Keep-Alive should then become suspicious and probe the connection.
Because PassiveKeepAlive only sends a dummy message when it gets suspicious, it is more conservative with network and CPU resources than ActiveKeepAlive.
ActiveKeepAlive on the server and PassiveKeepAlive on the client make a good combination. If you set the ActiveKeepAlive's send interval to less than the PassiveKeepAlive's timeout period, then the timeout should never trigger. As long as communications are uninterrupted, the server need not waste CPU filtering out incoming dummy messages from the client (often the load on the CPU is more of a problem than the load on the network), yet the server can clean up dead sockets as fast as possible. The client, on the other hand, can make its own use of the server's dummy messages as confirmation that the connection is still alive.
A variant on PassiveKeepAlive is to update the timestamp both when a message is received and when a message is successfully sent, rather than just when a message is received. Because of TCP's reliability, a successful send is an indication that the connection is alive. However, it is not an indication that the remote side's application is functioning correctly, and so it may be better to update timestamps on incoming messages alone.
AreYouThere/IHeardYou
The aforementioned Keep-Alives all depend on TCP's reliability, as implemented with built-in acknowledgments, to verify that the connection is alive. Often, however, you may encounter situations in which the client application, though running and connected, has frozen. This can occur, for example, when a bug in a browser causes it to start consuming 100 percent CPU (yes, it happens), even if your applet is bug free. In these situations, the TCP/IP stack still sends ACKs as needed, but the client application has, for your purposes, ceased functioning. When this happens, you want to clean up the server-side socket.
You can do this by replicating part of the TCP acknowledgment protocol in your code. When one side, usually the server, has heard silence for too long, it sends out a special "Are-You-There" message (see AreYouThere in Listing Three). The protocol requires the other side to respond with I-Hear-You (see IHearYou in Listing Four). Before sending I-Hear-You, the receiving side may wish to check that all its threads are functioning properly. The I-Hear-You can be a dummy message, since its only purpose is to restart the server's timestamp for incoming messages. Because you are duplicating the existing TCP functionality, you want to save bandwidth by making timeouts for this protocol quite long.
Unlike the previous Keep-Alives, implementing this requires the Keep-Alive on the receiving side to pay attention to received data and respond to specific messages. Are-You-There messages need to be filtered out from other incoming data. Of course, you want to be able to do this without mixing Keep-Alive code into the remainder of the application. The example in the online code implements the IHearYou Keep-Alive as a callback filter. It receives each incoming message and checks to see if it is an Are-You-There or an application message. If it is an Are-You-There, a dummy message is sent in response.
HeartbeatClient/HeartbeatServer
A similar application-level simulation of the socket option is the heartbeat protocol. (See UNIX Network Programming Vol. 1, second edition, by W. Richard Stevens, Prentice Hall 1998, for a heartbeat client/ server system using out-of-band data, implemented in C. The special degree of urgency of out-of-band data is appropriate for Are-You-There messages, which are intended to identify frozen remote applications. The java.net implementation, however, does not allow access to the out-of-band data socket option.) HeartbeatClient (Listing Five) sends Are-You-Theres at regular intervals, and the HeartbeatServer (Listing Six) responds with I-Heard-You. If either side hears silence for a suspicious amount of time, it closes the connection. The time that arouses suspicion can be some multiple of the heartbeat-transmission interval. Like ActiveKeepAlive, this protocol has the disadvantage of network load, because it sends periodic messages unconditionally.
However, the heartbeat protocol has the advantage of being useable in UDP. Whereas some other KeepAlives rely on TCP's built-in acknowledgments, the heartbeat protocol generates its own two-way messages. The protocol can function without reliance on IOExceptions resulting from TCP ACK failure. Likewise, AreYouThere/ IHearYou works in UDP. In contrast, other protocols are not useable with UDP. The probes of ActiveKeepAlive and PassiveKeepAlive, for example, rely on IOException.
ActiveKeepAliveWithAYT/ PassiveKeepAliveWithIHY
You often have to combine several Keep-Alive algorithms in a single application. For example, ActiveKeepAlive/PassiveKeepAlive is usefully combined with AreYouThere/IHeardYou. The first pair checks on the viability of the connection, and the second on the viability of the client application. On each loop, ActiveKeepAliveWithAYT (Listing Seven) performs the tasks of ActiveKeepAlive and AreYouThere, sending probes and checking to see if there has been silence from the remote side for a suspiciously long time. In addition, ActiveKeepAliveWithAYT sends an Are-You-There if it gets suspicious. On the other side is PassiveKeepAliveWithIHY (Listing Eight). It acts as PassiveKeepAlive, probing the connection after a suspicious silence, and also as IHearYou, responding to any Are-You-Theres with I-Hear-You.
Jini, RMI, DCOM, and CORBA
Object-oriented distributed technologies such as Jini, JavaSpaces, RMI, CORBA, and DCOM also need to achieve the goals of Keep-Alive.
The solution in Jini and JavaSpaces (which is a Jini service) as well as RMI (which reflects the conceptual underpinnings of Jini) are "leases" -- which are a type of reified Keep-Alive. Embodying the algorithm in an object gives all the usual advantages of object-oriented design: flexible interchange of algorithms by polymorphism, delegation of responsibility for lease management to other objects, and encapsulation of the Keep-Alive mechanism from the rest of the code.
A lease is a permission to use a remote resource. In RMI, the resources are remote references and associated data structures. In Jini, the resources are services, such as JavaSpaces, storage in a lookup server, or access to hardware such as a printer.
In leasing, responsibility for viability of the connection is with the client rather than the server. The client synchronously requests a lease with a desired time period. The server may reject the lease, for example, if there are insufficient resources; normally the server grants a lease, informing the client of the time period (which may be less than the requested period). When the client receives the lease, it can interact with the remote resources, and it must request the lease again before the time period runs out. If the client does not send another request for the lease before its expiration, the server cleans up resources dedicated to the client. In RMI, the expiration of the lease decrements the reference count for the servant (remotely accessible object), just as explicitly releasing a reference decrements the count. When the remote reference count reaches zero, the servant object is locally garbage collected.
Leases in RMI and Jini tend to be granted for 6 to 10 minutes -- this time period has been found appropriate given the reliability of networks and the requirements of the system.
With leases, as in some of the Keep-Alive algorithms, only the special-purpose lease-renewal messages serve to renew the viability of the connection. Any other traffic from the client to the server does not prevent the server from closing the connection after the timeout period, even though in principle this is evidence that the connection is alive.
CORBA includes no built-in distributed garbage collection. This is because CORBA must support multiple languages with their different memory-management models. When writing a CORBA application, you should consider implementing a garbage-collection mechanism based on one of the techniques described in this article. You will have to translate concepts from the streams described earlier to the method calls of CORBA, but the principles remain the same.
Microsoft's DCOM does have a type of Keep-Alive. A DCOM component periodically pings any clients that are attached to it. Every 6 minutes it sends a ping, and if it does not hear from the client within three pings, it closes the connection and decrements the reference count. The implementation includes various optimizations such as sharing pings for a number of components on a host with clients on another host, or piggybacking pings on other traffic.
Conclusion
Detection and response to connection failure is essential in distributed systems. Since the needs of applications vary, lower level mechanisms often do not provide the solution: You have to write it into your application. Carefully planned implementation of the techniques presented here ensures the robustness of your distributed system in the face of partial failure and nondeterministic latency.
Acknowledgment
My thanks to David Korman for important discussions that helped shape this article. Responsibility for errors remains with me.
DDJ
Listing One
public void run() { try { for(;;) { output_stream.send(dummy_message); } } catch (IOException ioe){ socket_and_remote_resources.close(); } }
Listing Two
private boolean message_just_received = false; public void run() { for(;;) { try { // This wait/notify technique is // preferable to a loop that repeatedly checks the situation. synchronized (this) { wait(TIME_BETWEEN_ACTIVE_PROBES * 2); } if (!message_just_received) output_stream.send(dummy_message); } message_just_received = false; } catch (InterruptedException ie) { break; } catch (IOException ioe) { socket_and_remote_resources.close(); break; } } } // callback, called when any message is received synchronized void on_message_received(String s) { message_just_received = true; notify(); }
Listing Three
public void run() { while (!endRequested()) { if (remote_side_has_been_silent_for_too_long()) { output_stream.send(are_you_there); } if (remote_side_been_silent_for_WAY_too_long ()) { socket_and_remote_resources.close(); } sleep(1000L); } }
Listing Four
// callback, called when "Are you there" is received void on_are_you_there_received() { // callback try { // I-Hear-You does not require a special message type output_stream.send(dummy_message); } catch (IOException ioe){ socket_and_remote_resources.close(); } }
Listing Five
public void run() { for(;;) { try { output_stream.send(are_you_there_message); } catch (IOException ioe) { socket_and_remote_resources.close(); break; } sleep(TIME_BETWEEN_ACTIVE_PROBES); if (remote_side_has_been_silent_for_too_long()) { socket_and_remote_resources.close(); } } }
Listing Six
public void run() { for(;;) { // Can be implemented alternately using wait/notify. // See PassiveKeepAlive (Listing Two). sleep(TIME_BETWEEN_ACTIVE_PROBES); if (remote_side_has_been_silent_for_too_long()) { socket_and_remote_resources.close(); } } } // callback, called when "Are you there" is received void on_are_you_there_received() { try { output_stream.send(dummy_message); } catch (IOException ioe){ socket_and_remote_resources.close(); } }
Listing Seven
public void run() { for(;;){ try { output_stream.send(dummy_message); } catch (IOException ioe) { socket_and_remote_resources.close(); break; } if (remote_side_has_been_silent_for_too_long()) { output_stream.send(are_you_there); } if (remote_side_has_been_silent_for_WAY_too_long ()) { socket_and_remote_resources.close(); } sleep(TIME_BETWEEN_ACTIVE_PROBES); } }
Listing Eight
public void run() { for(;;) { try { if(remote_side_has_been_silent_for_too_long() ){ output_stream.send(dummy_message); } } catch (IOException ioe) { getSender().close(); } sleep(TIME_BETWEEN_ACTIVE_PROBES/2); } } // callback, called when "Are you there" is received void on_are_you_there_received () { try { // I-Hear-You does not require a special message type output_stream.send(dummy_message); } catch (IOException ioe){ socket_and_remote_resources.close(); } }