
com.threerings.presents.client.BlockingCommunicator Maven / Gradle / Ivy
//
// $Id: BlockingCommunicator.java 6530 2011-03-10 23:18:22Z andrzej $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2011 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.presents.client;
import java.io.EOFException;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousCloseException;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import com.samskivert.util.LoopingThread;
import com.samskivert.util.Queue;
import com.samskivert.util.StringUtil;
import com.samskivert.util.Throttle;
import com.threerings.io.ByteBufferInputStream;
import com.threerings.io.ByteBufferOutputStream;
import com.threerings.io.FramedInputStream;
import com.threerings.io.FramingOutputStream;
import com.threerings.io.ObjectInputStream;
import com.threerings.io.ObjectOutputStream;
import com.threerings.io.UnreliableObjectInputStream;
import com.threerings.io.UnreliableObjectOutputStream;
import com.threerings.presents.net.AESAuthRequest;
import com.threerings.presents.net.AuthResponse;
import com.threerings.presents.net.AuthResponseData;
import com.threerings.presents.net.AuthRequest;
import com.threerings.presents.net.DownstreamMessage;
import com.threerings.presents.net.LogoffRequest;
import com.threerings.presents.net.PingRequest;
import com.threerings.presents.net.PublicKeyCredentials;
import com.threerings.presents.net.SecureRequest;
import com.threerings.presents.net.SecureResponse;
import com.threerings.presents.net.TransmitDatagramsRequest;
import com.threerings.presents.net.Transport;
import com.threerings.presents.net.UpstreamMessage;
import com.threerings.presents.util.DatagramSequencer;
import static com.threerings.presents.Log.log;
/**
* The client performs all network I/O on separate threads (one for reading and one for
* writing). The communicator class encapsulates that functionality.
*
*
* Logon synopsis:
*
* Client.logon():
* - Calls Communicator.start()
* Communicator.start():
* - spawn Reader thread
* Reader.run():
* { - connect
* - authenticate
* } if either fail, notify observers of failed logon
* - start writer thread
* - notify observers that we're logged on
* - read loop
* Writer.run():
* - write loop
*
*/
public class BlockingCommunicator extends Communicator
{
/**
* Creates a new communicator instance which is associated with the supplied client.
*/
public BlockingCommunicator (Client client)
{
super(client);
}
@Override // from Communicator
public void logon ()
{
// make sure things are copacetic
if (_reader != null) {
throw new RuntimeException("Communicator already started.");
}
// start up the reader thread. it will connect to the server and start up the writer thread
// if everything went successfully
_reader = new Reader();
_reader.start();
}
@Override // from Communicator
public synchronized void logoff ()
{
// if our socket is already closed, we've already taken care of this business
if (_channel == null) {
return;
}
// post a logoff message
postMessage(new LogoffRequest());
// let our readers and writers know that it's time to go
if (_reader != null) {
// if logoff() is being called by the client as part of a normal shutdown, this will
// cause the reader thread to be interrupted and shutdown gracefully. if logoff is
// being called by the reader thread as a result of a failed socket, it won't interrupt
// itself as it is already shutting down gracefully. if the JVM is buggy and calling
// interrupt() on a thread that is blocked on a socket doesn't wake it up, then when we
// close() the socket a bit further down, we have another chance that the reader thread
// will wake up; this time slightly less gracefully because it will think there's a
// network error when in fact we're just shutting down, but at least it will cleanly
// exit
_reader.shutdown();
}
if (_writer != null) {
// shutting down the writer thread is simpler because we can post a termination message
// on the queue and be sure that it will receive it. when the writer thread has
// delivered our logoff request and exited, we will complete the logoff process by
// closing our socket and invoking the clientDidLogoff callback
_writer.shutdown();
}
if (_datagramWriter != null) {
_datagramWriter.shutdown();
}
if (_datagramReader != null) {
_datagramReader.shutdown();
}
}
@Override // from Communicator
public void gotBootstrap ()
{
// start the datagram writer thread, if applicable
if (_client.getDatagramPorts().length > 0) {
_datagramReader = new DatagramReader();
_datagramReader.start();
}
}
@Override // from Communicator
public void postMessage (UpstreamMessage msg)
{
// post as datagram if hinted and possible
if (!msg.getTransport().isReliable() && _datagramWriter != null) {
msg.noteActualTransport(Transport.UNRELIABLE_UNORDERED);
_dataq.append(msg);
} else {
msg.noteActualTransport(Transport.RELIABLE_ORDERED);
_msgq.append(msg);
}
}
@Override // from Communicator
public void setClassLoader (ClassLoader loader)
{
_loader = loader;
if (_oin != null) {
_oin.setClassLoader(loader);
}
}
@Override // from Communicator
public synchronized long getLastWrite ()
{
return _lastWrite;
}
@Override // from Communicator
public boolean getTransmitDatagrams ()
{
return _datagramWriter != null;
}
@Override // from Communicator
protected synchronized void logonSucceeded (AuthResponseData data)
{
super.logonSucceeded(data);
// create a new writer thread and start it up
if (_writer != null) {
throw new RuntimeException("Writer already started!?");
}
_writer = new Writer();
_writer.start();
}
/**
* Callback called by the reader or writer thread when something goes awry with our socket
* connection to the server.
*/
protected synchronized void connectionFailed (final IOException ioe)
{
// make sure the socket isn't already closed down (meaning we've already dealt with the
// failed connection)
if (_channel == null) {
return;
}
log.info("Connection failed", ioe);
// let the client know that things went south
notifyClientObservers(new ObserverOps.Client(_client) {
@Override protected void notify (ClientObserver obs) {
obs.clientConnectionFailed(_client, ioe);
}
});
// and request that we go through the motions of logging off
logoff();
}
/**
* Callback called by the reader if the server closes the other end of the connection.
*/
protected synchronized void connectionClosed ()
{
// make sure the socket isn't already closed down (meaning we've already dealt with the
// closed connection)
if (_channel == null) {
return;
}
log.debug("Connection closed.");
// now do the whole logoff thing
logoff();
}
/**
* Callback called by the reader thread when it goes away.
*/
protected synchronized void readerDidExit ()
{
// clear out our reader reference
_reader = null;
if (_writer == null) {
// there's no writer during authentication, so we may be responsible for closing the
// socket channel
closeChannel();
// let the client know when we finally go away
clientCleanup(_logonError);
}
log.debug("Reader thread exited.");
}
/**
* Callback called by the writer thread when it goes away.
*/
protected synchronized void writerDidExit ()
{
// clear out our writer reference
_writer = null;
log.debug("Writer thread exited.");
// let the client observers know that we're logged off
notifyClientObservers(new ObserverOps.Session(_client) {
@Override protected void notify (SessionObserver obs) {
obs.clientDidLogoff(_client);
}
});
// now that the writer thread has gone away, we can safely close our socket and let the
// client know that the logoff process has completed
closeChannel();
// let the client know when we finally go away
if (_reader == null) {
clientCleanup(_logonError);
}
}
/**
* Closes the socket channel that we have open to the server. Called by either {@link
* #readerDidExit} or {@link #writerDidExit} whichever is called last.
*/
protected void closeChannel ()
{
if (_channel != null) {
log.debug("Closing socket channel.");
try {
_channel.close();
} catch (IOException ioe) {
log.warning("Error closing failed socket: " + ioe);
}
_channel = null;
// clear these out because they are probably large and in charge
_oin = null;
_oout = null;
}
}
/**
* Callback called by the datagram reader thread when it goes away.
*/
protected synchronized void datagramReaderDidExit ()
{
// clear out our reader reference
_datagramReader = null;
if (_datagramWriter == null) {
closeDatagramChannel();
}
log.debug("Datagram reader thread exited.");
}
/**
* Callback called by the datagram writer thread when it goes away.
*/
protected synchronized void datagramWriterDidExit ()
{
// clear out our writer reference
_datagramWriter = null;
if (_datagramReader == null) {
closeDatagramChannel();
}
log.debug("Datagram writer thread exited.");
}
/**
* Closes the datagram channel.
*/
protected void closeDatagramChannel ()
{
if (_selector != null) {
try {
_selector.close();
} catch (IOException ioe) {
log.warning("Error closing selector: " + ioe);
}
_selector = null;
}
if (_datagramChannel != null) {
log.debug("Closing datagram socket channel.");
try {
_datagramChannel.close();
} catch (IOException ioe) {
log.warning("Error closing datagram socket: " + ioe);
}
_datagramChannel = null;
// clear these out because they are probably large and in charge
_uout = null;
_sequencer = null;
}
}
/**
* Writes the supplied message to the socket.
*/
protected void sendMessage (UpstreamMessage msg)
throws IOException
{
if (debugLogMessages()) {
log.info("SEND " + msg);
}
// first we write the message so that we can measure it's length
_oout.writeObject(msg);
_oout.flush();
// then write the framed message to actual output stream
try {
ByteBuffer buffer = _fout.frameAndReturnBuffer();
if (buffer.limit() > 4096) {
String txt = StringUtil.truncate(String.valueOf(msg), 80, "...");
log.info("Whoa, writin' a big one", "msg", txt, "size", buffer.limit());
}
int wrote = writeMessage(buffer);
if (wrote != buffer.limit()) {
log.warning("Aiya! Couldn't write entire message", "msg", msg,
"size", buffer.limit(), "wrote", wrote);
} else {
// Log.info("Wrote " + wrote + " bytes.");
_client.getMessageTracker().messageSent(false, wrote, msg);
}
} finally {
_fout.resetFrame();
}
// make a note of our most recent write time
updateWriteStamp();
}
/**
* Writes the message contained in the supplied buffer.
*
* @return the number of bytes written.
*/
protected int writeMessage (ByteBuffer buf)
throws IOException
{
return _channel.write(buf);
}
/**
* Sends a datagram over the datagram socket.
*/
protected void sendDatagram (UpstreamMessage msg)
throws IOException
{
// reset the stream and write our connection id and hash placeholder
_bout.reset();
_uout.writeInt(_client.getConnectionId());
_uout.writeLong(0L);
// write the datagram through the sequencer
_sequencer.writeDatagram(msg);
// flip the buffer and make sure it's not too long
ByteBuffer buf = _bout.flip();
int size = buf.remaining();
if (size > Client.MAX_DATAGRAM_SIZE) {
log.warning("Dropping oversized datagram", "size", size, "msg", msg);
return;
}
// compute the hash
buf.position(12);
_digest.update(buf);
byte[] hash = _digest.digest(_secret);
// insert the first 64 bits of the hash
buf.position(4);
buf.put(hash, 0, 8).rewind();
// send the datagram
writeDatagram(buf);
// notify the tracker
_client.getMessageTracker().messageSent(true, size, msg);
}
/**
* Writes the datagram contained in the supplied buffer.
*
* @return the number of bytes written.
*/
protected int writeDatagram (ByteBuffer buf)
throws IOException
{
return _datagramChannel.write(buf);
}
/**
* Reads a new message from the socket (blocking until a message has arrived).
*/
protected DownstreamMessage receiveMessage ()
throws IOException
{
// read in the next message frame (readFrame() can return false meaning it only read part
// of the frame from the network, in which case we simply call it again because we can't do
// anything until it has a whole frame; it will throw an exception if it hits EOF or if
// something goes awry)
while (!readFrame()) {
// noop!
}
if (_oin == null) {
// Our object input stream was taken away from us before we could do anything with the
// frame, this is equivalent to being interrupted mid-frame, so indicate that.
throw new InterruptedIOException();
}
try {
int size = _fin.available();
DownstreamMessage msg = (DownstreamMessage)_oin.readObject();
if (debugLogMessages()) {
log.info("RECEIVE " + msg);
}
_client.getMessageTracker().messageReceived(false, size, msg, 0);
return msg;
} catch (ClassNotFoundException cnfe) {
throw (IOException) new IOException(
"Unable to decode incoming message.").initCause(cnfe);
}
}
/**
* Reads a frame from the socket.
*
* @return true if a complete frame is available, false if more needs to be read.
*/
protected boolean readFrame ()
throws IOException
{
return _fin.readFrame(_channel);
}
/**
* Reads a datagram from the socket (blocking until a datagram has arrived).
*/
protected DownstreamMessage receiveDatagram ()
throws IOException
{
// clear the buffer and read a datagram
_buf.clear();
int size = readDatagram(_buf);
if (size <= 0) {
throw new IOException("No datagram available to read.");
}
_buf.flip();
// decode through the sequencer
try {
DownstreamMessage msg = (DownstreamMessage)_sequencer.readDatagram();
if (_client != null) {
_client.getMessageTracker().messageReceived(
true, size, msg, (msg == null) ? 0 : _sequencer.getMissedCount());
}
if (msg == null) {
return null; // received out of order
}
if (debugLogMessages()) {
log.info("DATAGRAM " + msg);
}
return msg;
} catch (ClassNotFoundException cnfe) {
throw (IOException) new IOException(
"Unable to decode incoming datagram.").initCause(cnfe);
}
}
/**
* Reads a datagram into the supplied buffer.
*
* @return the number of bytes read.
*/
protected int readDatagram (ByteBuffer buf)
throws IOException
{
return _datagramChannel.read(buf);
}
protected void openChannel (InetAddress host)
throws IOException
{
// the default implementation just connects to the first port and does no cycling
int port = _client.getPorts()[0];
log.info("Connecting", "host", host, "port", port);
synchronized (BlockingCommunicator.this) {
_channel = SocketChannel.open(new InetSocketAddress(host, port));
}
}
protected boolean debugLogMessages ()
{
return false;
}
/**
* Throttles an outgoing message operation in a thread-safe manner.
*/
protected void throttleOutgoingMessage ()
{
Throttle throttle = _client.getOutgoingMessageThrottle();
synchronized(throttle) {
while (throttle.throttleOp()) {
try {
Thread.sleep(2);
} catch (InterruptedException ie) {
// no problem
}
}
}
}
/**
* The reader encapsulates the authentication and message reading process. It calls back to the
* {@link Communicator} class to do things, but the general flow of the reader thread is
* encapsulated in this class.
*/
protected class Reader extends LoopingThread
{
public Reader () {
super("BlockingCommunicator_Reader");
}
@Override
protected void willStart () {
try {
// connect to the server
connect();
// If a public key is specified, we'll attempt to establish a secure authentication
// channel
PublicKey key = _client.getPublicKey();
AuthResponse response = null;
if (key != null) {
PublicKeyCredentials pkcreds = new PublicKeyCredentials(key);
sendMessage(new SecureRequest(pkcreds, _client.getVersion()));
// now wait for the handshake
log.debug("Waiting for secure response.");
response = (AuthResponse)receiveMessage();
// If we've received a secure response, proceed with authentication
if (response instanceof SecureResponse) {
AuthRequest areq = AESAuthRequest.createAuthRequest(
_client.getCredentials(), _client.getVersion(),
_client.getBootGroups(), _client.requireSecureAuth(),
pkcreds, (SecureResponse)response);
sendMessage(areq);
_client.setSecret(areq.getSecret());
// now wait for the auth response
log.debug("Waiting for auth response.");
response = (AuthResponse)receiveMessage();
}
} else {
// construct an auth request and send it
sendMessage(AESAuthRequest.createAuthRequest(
_client.getCredentials(), _client.getVersion(),
_client.getBootGroups(), _client.requireSecureAuth()));
// now wait for the auth response
log.debug("Waiting for auth response.");
response = (AuthResponse)receiveMessage();
}
gotAuthResponse(response);
} catch (Exception e) {
log.debug("Logon failed: " + e);
// once we're shutdown we'll report this error
_logonError = e;
// terminate our communicator thread
shutdown();
}
}
protected void connect ()
throws IOException
{
// if we're already connected, we freak out
if (_channel != null) {
throw new IOException("Already connected.");
}
// look up the address of the target server
InetAddress host = InetAddress.getByName(_client.getHostname());
openChannel(host);
_channel.configureBlocking(true);
// our messages are framed (preceded by their length), so we use these helper streams
// to manage the framing
_fin = new FramedInputStream();
_fout = new FramingOutputStream();
// create our object input and output streams
_oin = new ClientObjectInputStream(_client, _fin);
_oin.setClassLoader(_loader);
_oout = new ObjectOutputStream(_fout);
}
// now that we're authenticated, we manage the reading half of things by continuously
// reading messages from the socket and processing them
@Override
protected void iterate () {
DownstreamMessage msg = null;
try {
// read the next message from the socket
msg = receiveMessage();
// process the message
processMessage(msg);
} catch (InterruptedIOException iioe) {
// somebody set up us the bomb! we've been interrupted which means that we're being
// shut down, so we just report it and return from iterate() like a good monkey
log.debug("Reader thread woken up in time to die.");
} catch (EOFException eofe) {
// let the communicator know that our connection was closed
connectionClosed();
// and shut ourselves down
shutdown();
} catch (IOException ioe) {
// let the communicator know that our connection failed
connectionFailed(ioe);
// and shut ourselves down
shutdown();
} catch (Exception e) {
log.warning("Error processing message", "msg", msg, e);
}
}
@Override
protected void handleIterateFailure (Exception e) {
log.warning("Uncaught exception it reader thread.", e);
}
@Override
protected void didShutdown () {
// let the communicator know when we finally go away
readerDidExit();
}
@Override
protected void kick () {
// we want to interrupt the reader thread as it may be blocked listening to the socket;
// this is only called if the reader thread doesn't shut itself down
// While it would be nice to be able to handle wacky cases requiring reader-side
// shutdown, doing so causes consternation on the other end's writer which suddenly
// loses its connection. So, we rely on the writer side to take us down.
// interrupt();
}
}
/**
* The writer encapsulates the message writing process. It calls back to the {@link
* Communicator} class to do things, but the general flow of the writer thread is encapsulated
* in this class.
*/
protected class Writer extends LoopingThread
{
public Writer () {
super("BlockingCommunicator_Writer");
}
@Override
public synchronized void shutdown () {
// we want to finish off what's in our queue before we actually shutdown
postMessage(new TerminationMessage());
}
@Override
protected void iterate () {
// fetch the next message from the queue
UpstreamMessage msg = _msgq.get();
// if this is a termination message, we're being requested to exit, so we call
// super.shutdown() to mark ourselves as not running and then return
if (msg instanceof TerminationMessage) {
super.shutdown();
return;
}
// make sure we're not exceeding our outgoing throttle rate
throttleOutgoingMessage();
try {
// write the message out the socket
sendMessage(msg);
} catch (IOException ioe) {
connectionFailed(ioe); // let the communicator know
super.shutdown(); // and bail immediately (which is why we call super)
}
}
@Override
protected void handleIterateFailure (Exception e) {
log.warning("Uncaught exception it writer thread.", e);
}
@Override
protected void didShutdown () {
writerDidExit();
}
}
/**
* Handles the general flow of reading datagrams.
*/
protected class DatagramReader extends LoopingThread
{
public DatagramReader () {
super("BlockingCommunicator_DatagramReader");
}
@Override
protected void willStart () {
try {
connect();
} catch (IOException ioe) {
log.warning("Failed to open datagram channel", "error", ioe);
shutdown();
}
}
protected void connect ()
throws IOException
{
// create a selector to be used only for the initial connection
_selector = Selector.open();
// create and register the channel
_datagramChannel = DatagramChannel.open();
_datagramChannel.socket().setTrafficClass(0x10); // IPTOS_LOWDELAY
_datagramChannel.configureBlocking(false);
_datagramChannel.register(_selector, SelectionKey.OP_READ, null);
// create the message digest
try {
_digest = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException nsae) {
log.warning("Missing MD5 algorithm.");
shutdown();
return;
}
_secret = _client.getCredentials().getDatagramSecret().getBytes("UTF-8");
// create our various streams
_bout = new ByteBufferOutputStream();
_uout = new UnreliableObjectOutputStream(_bout);
ByteBufferInputStream bin = new ByteBufferInputStream(_buf);
UnreliableObjectInputStream uin = new UnreliableObjectInputStream(bin);
uin.setClassLoader(_loader);
// create the datagram sequencer
_sequencer = new DatagramSequencer(uin, _uout);
// try each port in turn
int cport = -1;
for (int port : _client.getDatagramPorts()) {
boolean connected = connect(port);
if (!isRunning()) {
return; // cancelled
} else if (connected) {
cport = port;
break;
}
}
// close the selector and return the channel to blocking mode
_selector.close();
_selector = null;
_datagramChannel.configureBlocking(true);
// check if we managed to establish a connection
if (cport > 0) {
log.info("Datagram connection established", "port", cport);
// notify the server
postMessage(new TransmitDatagramsRequest());
// start up the writer thread
_datagramWriter = new DatagramWriter();
_datagramWriter.start();
} else {
log.info("Failed to establish datagram connection.");
shutdown();
}
}
protected boolean connect (int port)
throws IOException
{
_datagramChannel.connect(new InetSocketAddress(_client.getHostname(), port));
for (int ii = 0; ii < DATAGRAM_ATTEMPTS_PER_PORT; ii++) {
// send a ping datagram
sendDatagram(new PingRequest(Transport.UNRELIABLE_UNORDERED));
// wait for a response
int resp = _selector.select(DATAGRAM_RESPONSE_WAIT);
if (!isRunning()) {
return false; // cancelled
} else if (resp > 0) {
receiveDatagram();
return true;
}
}
_datagramChannel.disconnect();
return false;
}
@Override
protected void iterate () {
DownstreamMessage msg = null;
try {
// read the next message from the socket
msg = receiveDatagram();
// process the message if it wasn't dropped
if (msg != null) {
processMessage(msg);
}
} catch (AsynchronousCloseException ace) {
// somebody set up us the bomb! we've been interrupted which means that we're being
// shut down, so we just report it and return from iterate() like a good monkey
log.debug("Datagram reader thread woken up in time to die.");
} catch (IOException ioe) {
log.warning("Error receiving datagram", ioe);
} catch (Exception e) {
log.warning("Error processing message", "msg", msg, e);
}
}
@Override
protected void handleIterateFailure (Exception e) {
log.warning("Uncaught exception in datagram reader thread.", e);
}
@Override
protected void didShutdown () {
datagramReaderDidExit();
}
@Override
protected void kick () {
// if we have a selector, wake it up
if (_selector != null) {
_selector.wakeup();
}
}
}
/**
* Handles the general flow of writing datagrams.
*/
protected class DatagramWriter extends LoopingThread
{
public DatagramWriter () {
super("BlockingCommunicator_DatagramWriter");
}
@Override
protected void iterate () {
// fetch the next message from the queue
UpstreamMessage msg = _dataq.get();
// if this is a termination message, we're being requested to exit, so we want to bail
// now rather than continuing
if (msg instanceof TerminationMessage) {
return;
}
// if we're exceeding our outgoing throttle rate, drop the packet
Throttle throttle = _client.getOutgoingMessageThrottle();
synchronized(throttle) {
if (throttle.throttleOp()) {
return;
}
}
try {
// write the message out the socket
sendDatagram(msg);
} catch (IOException ioe) {
log.warning("Error sending datagram", "error", ioe);
}
}
@Override
protected void handleIterateFailure (Exception e) {
log.warning("Uncaught exception in datagram writer thread.", e);
}
@Override
protected void didShutdown () {
datagramWriterDidExit();
}
@Override
protected void kick () {
// post a bogus message to the outgoing queue to ensure that the writer thread notices
// that it's time to go
_dataq.append(new TerminationMessage());
}
}
/** This is used to terminate the writer threads. */
protected static class TerminationMessage extends UpstreamMessage
{
}
protected Reader _reader;
protected Writer _writer;
protected DatagramReader _datagramReader;
protected DatagramWriter _datagramWriter;
protected SocketChannel _channel;
protected Queue _msgq = new Queue();
protected Selector _selector;
protected DatagramChannel _datagramChannel;
protected Queue _dataq = new Queue();
protected Exception _logonError;
/** We use this to frame our upstream messages. */
protected FramingOutputStream _fout;
protected ObjectOutputStream _oout;
/** We use this to frame our downstream messages. */
protected FramedInputStream _fin;
protected ObjectInputStream _oin;
/** We use these to write our upstream datagrams. */
protected ByteBufferOutputStream _bout;
protected UnreliableObjectOutputStream _uout;
protected MessageDigest _digest;
protected byte[] _secret;
/** We use these to read our downstream datagrams. */
protected ByteBuffer _buf = ByteBuffer.allocateDirect(Client.MAX_DATAGRAM_SIZE);
protected DatagramSequencer _sequencer;
protected ClassLoader _loader;
/** The number of times per port to try to establish a datagram "connection". */
protected static final int DATAGRAM_ATTEMPTS_PER_PORT = 10;
/** The number of milliseconds to wait for a response datagram. */
protected static final long DATAGRAM_RESPONSE_WAIT = 1000L;
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy