public final class AsyncSocket extends AbstractAsyncSocket
AsyncSocket
encapsulates a
SocketChannel
instance, passing I/O events back to SocketListener
.
Opening a TCP connection to a remote TCP service follows these steps:
socket listener interface
.
An instance of this class handles the async socket
callbacks.
AsyncSocket
calls back to the listener in four
situations:
AsyncSocket.open
returns
false
- meaning that the socket connection did not
immediately complete. If true
is returned then the
connection synchronously completed and is ready to
transmit and receive data.
Note that these callbacks are made on the socket's associated selector thread. So it is good if the listener method does its work as quickly as possible so the selector thread can get back to monitoring sockets.
Having defined and instantiated a async socket listener, an
async socket may now be built. The code is simple. Assuming
that the socket listener instance is stored in a variable
named socketListener
, the rest is:
final AsyncSocket.SocketBuilder builder = AsyncSocket.builder();
final AsyncSocket socket = builder.inputBufferSize(1_024) // Optional. Defaults to ENetConfigure.DEFAULT_BUFFER_SIZE.
.outputBufferSize(1_024) // Optional. Defaults to ENetConfigure.DEFAULT_BUFFER_SIZE.
.byteOrder(ByteOrder.BIG_ENDIAN) // Optional. Defaults to ByteOrder.LITTLE_ENDIAN
.listener(socketListener) // Required.
.selector("fastSelector") // Optional. Defaults to AsyncChannel.defaultSelector().
.build();
There are two methods for initiating a TCP connection to a service:
In both cases AsyncSocket.open
returns true
if
the connection was synchronously established (unlikely) and
false
if the connection process successfully started
and is expected to complete asynchronously later (likely). If
the connection process failed to successfully start, then an
appropriate exception is thrown.
If the connection asynchronously successfully completes, then
SocketListener.handleOpen(AbstractAsyncSocket)
is called. At this point the application may expect to receive
bytes and is free to start transmitting bytes.
There are two way to send data on an async socket. The first
is the most natural way: by passing one or more bytes to the
methods
AsyncSocket.send(int)
,
AsyncSocket.send(byte[])
,
and
AsyncSocket.send(byte[], int, int)
.
These methods will either attempt to send all the bytes or, if
the socket's associated ByteBuffer
overflows, then none of the data is sent. See
below for a detailed
discussion of how to respond to a buffer overflow.
A more interesting transmit option is implementing the
BufferWriter
interface. The idea here
is that the bytes you want to send are actually an encoded
object. The straight-forward solution is to encode the object
to an application ByteBuffer
, flip the buffer over,
and pass the extracted bytes to AsyncSocket.send
.
Would it not be better to encode the object directly into the
socket's buffer? Yes and that is how
AsyncSocket.send(BufferWriter)
works.
The application will generally associate a single
BufferWriter
with each open async socket. When
transmitting an object, that object is passed to the buffer
writer and that buffer writer is passed to the async socket.
The async socket then calls back to
BufferWriter.fill(ByteBuffer)
passing in the async socket's output buffer. The
fill(ByteBuffer)
method encodes the store object
directly to that buffer. This technique uses far less effort
than the encode-extract-send method.
Like the byte send methods, if BufferWriter.fill
overflows the given buffer, then
AsyncSocket.send(BufferWriter)
catches that exception,
discards all encoded bytes, and re-throws the buffer overflow.
It is now time to discuss how an application responds to a
buffer overflow.
When catching a
AsyncSocket.send
thrown
BufferOverflowException
what should be done with the
un-transmitted bytes? The simplest solution is to throw the
bytes away if it makes no sense to send them later. Either it
is send them now or never.
But it is more likely that not sending them will break the
communication protocol with the connection far-end. That means
the placing the unsent bytes on a FIFO queue and waiting for
the SocketListener.handleOutputAvailable
callback.
Between the buffer overflow and the "output available"
callback all further outbound bytes are placed on the queue.
handleOutputAvailable
method will iterate over the
queue sending each pending transmit in turn. But there is a
catch: what if another buffer overflow is caught while sending
the next queue entry? If the bytes were removed from the queue
(Queue.poll()
, then those
unsent bytes will need to be put back on the queue's head. It
is better to Queue.peek()
the
next bytes and, only after successfully sending those bytes,
remove them from the queue.
But now there is another issue: bounded or unbounded queue? If unbounded, there is a possibility of the JVM running out of memory if the too many bytes are being transmitted too quickly while the buffer is overflowed. But if the TCP connection is short lived and the transmits are slow and small, this may be an acceptable solution.
But using a bounded queue, that raises another question: what to do when the queue overflows? The common solution is to close the TCP connection, raise an alarm, asking for human intervention. This solution is needed for applications which cannot stop running and use long-term TCP connections.
SocketListener
,
AsyncServerSocket
,
AsyncDatagramSocket
Modifier and Type | Class and Description |
---|---|
static class |
AsyncSocket.SocketBuilder
Constructs an
AsyncSocket instance based on the
parameters set via this builder's API. |
AsyncChannel.BufferMode, AsyncChannel.ChannelBuilder<B extends AsyncChannel.ChannelBuilder<?>>, AsyncChannel.ConnectState
mListener, mOutputPending, mOverflowFlag, mState
LOCALHOST, mChannel, mInputBuffer, mLocalAddress, mOutputBuffer, mOutputLock, mRemoteAddress, mSelectorThread
Modifier and Type | Method and Description |
---|---|
static AsyncSocket.SocketBuilder |
builder()
Returns the builder used to create new
AsyncSocket
instance. |
protected void |
close(boolean nowFlag)
Does the actual work to close this async connection.
|
protected void |
handleRead()
Passes
AsyncChannel.mInputBuffer to the listener and the
number of valid bytes. |
void |
open(java.nio.channels.SelectableChannel channel)
"Opens" the socket connection by storing an already
open socket channel.
|
boolean |
open(java.net.SocketAddress address)
Opens a TCP socket connection to the specified socket
address and binding the local port any available ephemeral
port.
|
boolean |
open(java.net.SocketAddress address,
int bindPort)
Opens a TCP socket connection to the specified socket
address after binding the socket to the specified port.
|
boolean |
open(java.net.SocketAddress address,
java.net.SocketAddress bindAddress)
Opens a socket connection to the specified socket address.
|
void |
send(BufferWriter writer)
Synchronizes the output buffer and then calls
BufferWriter.fill(java.nio.ByteBuffer) to write the output to the
buffer. |
void |
send(byte[] b)
Writes
b.length bytes starting at offset zero from
byte array b to the socket. |
void |
send(byte[] b,
int off,
int len)
Writes
len bytes from byte array b
starting at offset off . |
void |
send(int b)
Writes a single byte to the socket.
|
java.net.Socket |
socket()
Returns the open TCP socket or
null if closed. |
close, closeNow, closeSocket, connectSocket, isOpen, openSocket, write
createSelector, defaultSelectorName, getOption, inputBufferSize, isSelector, localSocketAddress, outputBufferSize, remoteSocketAddress, selectorNames, setOption, supportedOptions
protected void handleRead()
AsyncChannel.mInputBuffer
to the listener and the
number of valid bytes.handleRead
in class AbstractAsyncSocket
public boolean open(java.net.SocketAddress address) throws java.io.IOException
true
if the connection successfully
completed and false
if the connection process will
asynchronously complete later. If false
is returned, then either
SocketListener.handleOpen
or
SocketListener.handleClose
is called depending on whether the connection was
successfully or unsuccessfully completed.open
in class AbstractAsyncSocket
address
- Connect to this remote address.true
is the socket synchronously connected
or false
if the connection is continuing.java.lang.IllegalArgumentException
- if address
is null
.java.lang.IllegalStateException
- if the selector thread is not running or if the socket is
not disconnected.java.io.IOException
- if an I/O exception occurs when connecting or if this
socket is not closed.open(SocketAddress, int)
,
open(SelectableChannel)
public boolean open(java.net.SocketAddress address, int bindPort) throws java.io.IOException
true
if the connection successfully
completed and false
if the connection process will
asynchronously complete later. If false
is
returned, then either
SocketListener.handleOpen
or
SocketListener.handleClose
is called depending on whether the connection was
successfully or unsuccessfully completed.open
in class AbstractAsyncSocket
address
- Connect to this remote address.bindPort
- Bind the local port to this value if
> zero; if ≤ zero then bind to any available port.true
is the socket synchronously connected
or false
if the connection is continuing.java.lang.IllegalArgumentException
- if address
is null
.java.lang.IllegalStateException
- if the selector thread is not running or if the socket is
not disconnected.java.io.IOException
- if an I/O exception occurs when connecting or if this
socket is not closed.open(SocketAddress)
,
open(SelectableChannel)
public boolean open(java.net.SocketAddress address, @Nullable java.net.SocketAddress bindAddress) throws java.io.IOException
true
if the connection completes
synchronously; false
if the connection process
will asynchronously complete later. If false
is
returned, then either
SocketListener.handleOpen
or
SocketListener.handleClose
is called depending on whether the connection
successfully or unsuccessfully completes.
Binds channel's local address to the given
bindAddress
. This binding remains in place until
channel is closed. If bind address is null
then
channel is bound to an automatically assigned address.
open
in class AbstractAsyncSocket
address
- connect to this remote address.bindAddress
- bind local address to this value if not
null
. May be null
which means channel's
local address and port are automatically assigned.true
if the socket synchronously connected
or false
if the connection process continues.java.lang.IllegalArgumentException
- if address
is null
.java.lang.IllegalStateException
- if the selector thread is not running or if the socket is
not disconnected.java.io.IOException
- if an I/O exception occurs when connecting or if this
socket is not closed.public void open(java.nio.channels.SelectableChannel channel) throws java.io.IOException
open
in class AbstractAsyncSocket
channel
- server-accepted client connection.java.lang.NullPointerException
- if socket
is null
.java.io.IOException
- if socket
is closed or this AsyncSocket
is
already open.open(SocketAddress)
,
open(SocketAddress, int)
,
close(boolean)
,
AbstractAsyncSocket.closeNow()
protected void close(boolean nowFlag)
close
in class AbstractAsyncSocket
nowFlag
- if true
, close the connection
immediately.public void send(int b) throws java.io.IOException
b
.
b
's 24 high-order bits are ignored.send
in class AbstractAsyncSocket
b
- the byte to be sent.java.io.IOException
- if the socket connection is closed.java.nio.BufferOverflowException
- if the output buffer size has been exceeded.send(byte[])
,
send(byte[], int, int)
,
send(BufferWriter)
public void send(byte[] b) throws java.io.IOException
b.length
bytes starting at offset zero from
byte array b
to the socket.send
in class AbstractAsyncSocket
b
- the data to be sent.java.lang.IllegalArgumentException
- if b
is null
.java.io.IOException
- if the socket connection is closed.java.nio.BufferOverflowException
- if the output buffer size has been exceeded.send(int)
,
send(byte[], int, int)
,
send(BufferWriter)
public void send(byte[] b, int off, int len) throws java.io.IOException
len
bytes from byte array b
starting at offset off
. The b
's bytes are
written to the output stream in order: byte b[off]
is written first and byte b[off+len-1]
is written
last.
If b
is null
, an
IllegalArgumentException
is thrown. If off
is negative, or len
is negative, or
off+len
is greater than b
's length, then
an IndexOutOfBoundsException
is thrown.
If the output buffer is overflowed, then none of
b
's bytes are added to the output buffer. This
allows the caller to catch the exception and implement an
error recovery strategy.
SocketListener.handleOutputAvailable(AbstractAsyncSocket)
is called when the output buffer is no longer full.
send
in class AbstractAsyncSocket
b
- data to be sent.off
- offset in b
.len
- number of bytes to be sent.java.lang.IllegalArgumentException
- if b
is null
.java.lang.IndexOutOfBoundsException
- if off
or len
is <
zero, or
off+len
is > b.length
.java.io.IOException
- if the socket is closed.java.nio.BufferOverflowException
- if the output buffer size has been exceeded.send(int)
,
send(byte[])
,
send(BufferWriter)
public void send(BufferWriter writer) throws java.io.IOException
BufferWriter.fill(java.nio.ByteBuffer)
to write the output to the
buffer.
If the output buffer is overflowed, then none
of writer
's output are added to the output buffer.
This allows the caller to catch the exception and
implement an error recovery strategy.
SocketListener.handleOutputAvailable(AbstractAsyncSocket)
is called when the output buffer is no longer full.
send
in class AbstractAsyncSocket
writer
- writes to the output buffer.java.lang.IllegalArgumentException
- if writer
is null
.java.io.IOException
- if the socket is closed.java.nio.BufferOverflowException
- if the output buffer size has been exceeded.send(int)
,
send(byte[])
,
send(byte[], int, int)
public java.net.Socket socket()
null
if closed.
Note: this method is provided to allow the TCP
socket to be configured. Do not close the socket.
Use either close(boolean)
or AbstractAsyncSocket.closeNow()
instead.
null
if closed.public static AsyncSocket.SocketBuilder builder()
AsyncSocket
instance.AsyncSocket
builder.Copyright © 2001 - 2024. Charles W. Rapp. All rights reserved.