public void close() {
        // LOG.log(Level.WARNING, "close()");
        synchronized(this) {
            if(closed)
                return;
            
            // Mark the stream closed immediately.  We're going to release the
            // lock below; this ensures that no new callers to read() or write()
            // will do anything.
            closed = true;

            // Wake any threads waiting in read().
            this.notifyAll();
        }

        // Closing the socket will cancel the thread if it's connecting or writing to
        // the socket.  Interrupt will stop the thread if it's waiting on queuedPackets.take.
        // LOG.log(Level.WARNING, "interrupting()");
        thread.interrupt();
        try {
            socket.close();
        } catch(IOException e) {
            // When can close() fail?  This doesn't seem to be documented.  We must be
            // able to close the socket in order to shut down the thread.
            throw new RuntimeException("Error closing thread", e); 
        }

        // Join the thread, deferring interrupts until it completes. 
        boolean interrupted = false;
        while(true) {
            try {
                // LOG.log(Level.WARNING, "joining");
                thread.join();
            } catch(InterruptedException e) {
                interrupted = true;
                continue;
            }
            break;
        }

        // LOG.log(Level.WARNING, "closed");
        if(interrupted)
            Thread.currentThread().interrupt();

        // The thread is closed.  If the socket is open, close it. 
        if(inputStream != null) {
            try { inputStream.close(); } catch(IOException e) { }
        }
        inputStream = null;

        // socket is already closed.
        socket = null;
        
        // Mark the thread closed.
        thread = null;
    }