Table of Contents
The Multiplex subsystem of the JBoss Remoting Project
(referred to herein on occasion simply as “Muliplex”) supports the
multiplexing of multiple data streams over a single network
connection, based on a reimplementation of the following classes from
java.net
:
Socket
ServerSocket
SocketInputStream
SocketOutputStream
and the following classes from javax.net:
SocketFactory
ServerSocketFactory
It is motivated by circumstances in which the number of available ports on a system is restricted by a firewall or other considerations. Since the Remoting project is the principal client of Multiplex, we illustrate multiplexing primarily in the context of a Remoting application. Remoting supports two modes of client-server communication: (1) method calls from client to server, with a synchronous response, and (2) client requests for an asynchronous callback from the server. The usual need for separate ports to support both synchronous and asynchronous modes is obviated by the Multiplexing subsystem.
The typical application of multiplexing in the Remoting context is illustrated by the Prime Scenario, in which a client requiring both synchronous and asynchronous responses from a server is behind a firewall and has only a single port at its disposal. Without the restriction to a single port, we would have the situation in Figure 1, which requires no multiplexing. With the restriction, we have the Prime Scenario, as in Figure 2.
Multiplexing is supported primarily by the concept of the
virtual socket, implemented by the
VirtualSocket
class.
VirtualSocket
is a subclass of
java.io.Socket
, and supports the full socket
API. As is the case with actual sockets, virtual sockets are
created in one of two ways:
a constructor (or factory) call on a client, or
a call to the accept()
method of a server
socket on a server.
Accordingly, the other principal Multiplex concept is the virtual server socket, implemented by two classes:
MultiPortVirtualServerSocket
,
and
SinglePortVirtualServerSocket
.
These are both subclasses of
java.io.ServerSocket
, and both implement the
full server socket API. Since virtual sockets are implemented on
the foundation of actual sockets, and the creation of actual
sockets requires a server socket, we need the support of actual
server sockets in the creation of virtual sockets. It is the role
of MultiPortVirtualServerSocket
to provide
that support. The accept()
method of
MultiPortVirtualServerSocket
calls
super.accept()
to create an actual socket which is
then wrapped in a mechanism which supports one or more virtual
sockets. Every Muliplex application requires at least one
MultiPortVirtualServerSocket
, and the Prime
Scenario requires exactly one. Figure 3 illustrates the process in
which a virtual socket v1 connects to a
MultiPortVirtualServerSocket
, which creates
and returns a reference to a new virtual socket
v2.
In Figure 3 we have a connection between v1 and
v2, which can support synchronous communication but
which offers nothing not provided by actual sockets. The support of
multiplexed callbacks, however, requires the use
of the other virtual server socket class,
SinglePortVirtualServerSocket
. Unlike
MultiPortVirtualServerSocket
,
SinglePortVirtualServerSocket
does not
depend on superclass facilities, but rather it uses an ordinary client socket,
with which implements its own
version of the accept()
method, able to create any
number of virtual sockets, all of which share a single port with
the SinglePortVirtualServerSocket
. It is important to understand
how its use of an actual socket determines the nature of a
SinglePortVirtualServerSocket
. Unlike a server socket, a client
socket must be connected to another socket to function, and a
SinglePortVirtualServerSocket
has the same property. It follows
that a SinglePortVirtualServerSocket
can process requests
from just one host, the host to which its actual socket is connected.
The role of the SinglePortVirtualServerSocket
is
illustrated in Figure 4. A constructor (or factory method, which
calls a constructor) is called on the server to create virtual socket
v3 to support callbacks. The constructor sends a
connection request to the
SinglePortVirtualServerSocket
on the client,
which creates new virtual socket v4 and sends back to
v3 a reference to v4. At this point the
Prime Scenario is set up.
Figure 4. Adding an asynchronous connection to Figure 3.
In order to understand the creation of structures like the
Prime Scenario and others described below, it is important to
understand the concept of a virtual socket
group. A virtual socket group is a set of virtual
sockets, and zero or one
SinglePortVirtualServerSocket
s, sharing a
single actual socket. We say that the socket group is based
on its actual socket. Depending on the state of its
underlying actual socket and the nature of its peer socket group, if any,
a socket group may be in one of three states.
Let G be a socket group based on actual socket
S. Then G may be
bound: S is bound but not connected, or
connected: S is
connected to socket S' and the socket group based on
S' does not contain a SinglePortVirtualServerSocket
, or
joinable: S is
connected to socket S' and the socket group based on
S' does contain a SinglePortVirtualServerSocket
.
Although it is possible for a socket to be neither bound nor connected, we do not consider a socket group to exist until its underlying socket is at least bound to a local address. A connected or joinable socket group is said to be visible, and a bound socket group is invisible. A socket group is characterized by the pair of addresses
(localAddress, remoteAddress)
where these are the local and remote addresses of the actual socket underlying the socket group. localAddress may take the special form (*, port), where the wildcard value “*” denotes any hostname by which the local host is known. Depending on the state of the socket group, remoteAddress may have the special value undefined, indicating that a connection has not yet been established.
There are two ways of creating a new virtual socket group or of joining an existing socket group: through a binding action or a connecting action. A binding action is either
a call to any of the
SinglePortVirtualServerSocket
constructors other than the default constructor (i.e., those with a
port parameter), or
a call to a bind()
method in
VirtualSocket
or
SinglePortVirtualServerSocket
.
A connecting action belongs to one of five categories:
a call to any VirtualSocket
or
SinglePortVirtualServerSocket
constructor that requires a remote address (note that
unlike java.net.ServerSocket
,
SinglePortVirtualServerSocket
has a
such a constructor),
a call to a connect()
method (again,
SinglePortVirtualServerSocket
has a nonstandard
connect()
method),
a call to
SinglePortVirtualServerSocket.accept()
,
a call to
MultiPortVirtualServerSocket.accept()
, or
a call to
MultiPortVirtualServerSocket.acceptServerSocketConnection()
.
Each binding action has an associated local address, and each
connecting action has an associated remote address and an optional
local address. For binding actions, and connecting actions in the
first two categories, the addresses are given explicitly in the
method call. For a a call to
SinglePortVirtualServerSocket.accept()
, the addresses
are those of the socket group to which the server socket belongs, and
for the two MultiPortVirtualServerSocket
methods, the addresses are those of the actual socket they
create.
Depending on their associated local and remote addresses and on the socket groups that exist at the time of the action, a binding or connecting action may have the effect of creating a new socket group or adding a new member to an existing socket group. The rules are straightforward, but there is one source of possible confusion, the accidental connection problem discussed below, that must be guarded against. Let V be a virtual socket or virtual server socket undergoing either a binding or connecting action.
binding action rule: If there are visible socket groups whose local address matches the action's local address, then V joins one of them chosen at random. Otherwise, a new bound socket group is created and V joins it.
connecting action rule:
For actions in the first two categories, where V
is a VirtualSocket
(respectively, a
SinglePortVirtualServerSocket
):
If the action has a remote address but no local address:
If there are any joinable (resp., connected) socket groups with a matching remote address, then V joins one of them chosen at random.
If there are no such socket groups, an attempt is made to connect to a MultiPortVirtualServerSocket at the remote address, and if the attempt succeeds, a new socket group is created and V joins it.
If the action has both a local address and a remote address:
If there is a joinable (resp., connected) socket group with matching addresses, then V joins it
Otherwise, if the local address (in particular, its port) is currently
in use, the action results in a IOException
.
Otherwise, a new socket group G is created and bound to the local address. Then an attempt is made to connect to a MultiPortVirtualServerSocket at the remote address, and if the attempt succeeds, V joins G.
For SinglePortVirtualServerSocket.accept()
calls,
the new virtual socket joins the socket group to which the
server socket belongs.
For MultiPortVirtualServerSocket.accept()
calls, a
new socket group is created with the new virtual socket as its
first member.
For
MultiPortVirtualServerSocket.acceptServerSocketConnection()
calls, a new socket group with zero members is created.
NOTES:
A bound socket group is inaccessible to the connect action rules (which is why it is called "invisible"). The reason is to avoid a situation in which one virtual socket "highjacks" another virtual socket's group. Suppose that virtual socket v1 binds itself to ("localhost", 5555), but before it gets a chance to connect to ("www.jboss.com", 6666), virtual socket v2 binds to ("localhost", 5555) and then connects to ("www.ibm.com", 7777). Then when v1 tries to connect to ("www.jboss.com", 6666), the attempt fails. This situation cannot occur because at the moment when v2 does its bind, v1's socket group is invisible and v2 is forced to create it own socket group.
The connecting action rules are different for VirtualSocket
and
SinglePortVirtualServerSocket
(specifically, the former can join
only joinable socket groups, while the later can join connected socket groups) because
VirtualSocket
needs a SinglePortVirtualServerSocket
to create a peer virtual socket for it to connect to, and a
SinglePortVirtualServerSocket
does not need such a peer.
N.B. It is important to understand a
possible side effect of a binding action. When V joins a
socket group through a binding action, it is possible that the group is already
connected. In this case, a subsequent connecting action (in particular, a call to
connect()
) to any address other than the
socket group's remote address is invalid, leading to an
IOException
with the message "socket is already
connected.". This is called the accidental connection
problem, and it is avoidable. Both VirtualSocket
and SinglePortVirtualServerSocket
have constructors and
nonstandard versions of the connect()
which accept both
local and remote addresses. These treat binding and connecting as a single
atomic process.
The socket group rules are illustrated in the following two sections.
In order to set up the Prime Scenario, the following steps are necessary (the socket names conform to Figure 4):
On the server, create a
MultiPortVirtualServerSocket
and bind it
to port P.
On the client, create a virtual socket v1 and connect it to port P.
Let Q be the port on the client to which
v1 is bound. Create a
SinglePortVirtualServerSocket
on the
client, bind it to Q, and connect it to
P.
On the server, create a virtual socket v3 and connect it to port Q.
The Prime Scenario provides an example of creating socket groups.
In step 2, a socket group G1 is created on the client
through the construction of v1. It enters the
connected state, bound to an arbitrary port Q on the
client and connected to port P on the server. In step
3 a SinglePortVirtualServerSocket
joins
G1 by way of binding to Q on the client
and connecting to P on the server. In fact, the
socket group rules imply that it is enough to bind the server socket to
port Q. Connecting it to P on the server
occurs as a side effect of the binding action. Finally, step 4 adds
virtual socket v4 to G1. While
G1 is being built on the client, a socket group
G2 is being built on the server. Step 2 results in the
creation of G2, along with its first member, a new
virtual socket, v2, returned by the
accept()
method of the
MultiPortVirtualServerSocket
. Step 4 adds a
second member, v3, to G2.
See Listing 1 and Listing 2 for a simple example of coding these steps. Variants of these samples may be found in the directory /org/jboss/remoting/samples/multiplex.
Although Multiplex was motivated by the Prime Scenario, it can also support other connection structures. We describe two alternatives in this section.
The N-socket
scenario demonstrates that a socket group is not restricted
to just two virtual sockets. It also demonstrates that a
SinglePortVirtualServerSocket
does not
depend on the prior existence of a connected virtual socket. As
long as it has access to a
MultiPortVirtualServerSocket
ready to
accept a connection, it can get started. In fact, the
MultiPortVirtualServerSocket.accept()
method will
silently accept a connection from a
SinglePortVirtualServerSocket
while it is
waiting for a connection request from a virtual socket, but the
acceptServerSocketConnection()
method is designed
specifically to accept a connection request from a
SinglePortVirtualServerSocket
.
The connection structure of the N-socket scenario is depicted in Figure 5 (for N = 3), and the code for a simple client and server is given in Listing 3 and Listing 4. In the example a socket group with 3 elements is constructed on the server. It is created with the call
serverSocket.acceptServerSocketConnection()
which creates an actual socket and a socket group which, though
it has no members, is connected to a
SinglePortVirtualServerSocket
on the
client. The next three lines,
Socket socket1 = new VirtualSocket(“localhost”, 5555); Socket socket2 = new VirtualSocket(“localhost”, 5555); Socket socket3 = new VirtualSocket(“localhost”, 5555);
populate the socket group with three virtual sockets. On the client there is a socket group with four members, first created with the call
serverSocket.connect(connectAddress);
and then further populated by the three subsequent lines
Socket socket1 = serverSocket.accept(); Socket socket2 = serverSocket.accept(); Socket socket3 = serverSocket.accept();
Variants of the N-Socket Scenario client and server may be found in the directory /org/jboss/remoting/samples/multiplex.
The connection structure in the
Symmetric Scenario consists of socket groups on two hosts,
each of which contains a
SinglePortVirtualServerSocket
and some
number of virtual sockets. The scenario is not truly symmetric,
since each connection structure has to begin with a connection
request to a MultiPortVirtualServerSocket
,
but once that happens the “client” and “server” are identical, as
depicted in Figure 6d. Once the line
serverSocket.connect(address);
on the client (see Listing 5) and the line
int port = mpvss.acceptServerSocketConnection();
on the server (see Listing 6) are executed, the client has a socket group characterized by the address pair
((*, 5555), (“localhost“, 7777))
and consisting of a
SinglePortVirtualServerSocket
, and the
server has a socket group with zero members characterized by the
address pair
((“localhost“, 7777), (“localhost”, 5555)).
(See Figure 6a.) And once the line
spvss.connect(address);
is executed on the server, the new
SinglePortVirtualServerSocket
joins the
server's socket group, as shown in Figure 6b. After the
lines
Socket virtualSocket1 = new VirtualSocket(“localhost”, port);
and
Socket virtualSocket1 = spvss.accept();
are executed on the client and server, respectively, each socket group has a new virtual socket (see Figure 6c), and finally, after the lines
Socket virtualSocket2 = new VirtualSocket(“localhost”, 5555);
and
Socket virtualSocket2 = serverSocket.accept();
are executed on the server and client, respectively, each socket group has a second virtual socket (see Figure 6d).
In addition to virtual sockets and virtual server sockets,
Multiplex also implements the two factories associated with
sockets: the socket factory and the server socket factory.
VirtualSocketFactory
extends
javax.net.SocketFactory
and reimplements all
of its methods. VirtualServerSocketFactory
extends javax.net.ServerSocketFactory
and
reimplements all of its methods.[1] These two classes make it possible for a
section of code to be completely unaware that it is using virtual
sockets instead of actual sockets. The only configuration involved
in the use of these factories is the need to tell
VirtualServerSocketFactory
whether it is
running on a client or a server, which tells it whether to create
SinglePortVirtualServerSocket
s or
MultiPortVirtualServerSocket
s, respectively.
That notification is performed by the methods
setOnClient()
and setOnServer()
. See
Listing 7 for an illustration of the idiomatic use of these
classes, where the method useFactories()
refers only
to the parent classes SocketFactory
and
ServerSocketFactory
.
It should come as no surprise that the classes in Muliplex
perform more slowly than their non-virtual counterparts, since the
multiplexing of data streams requires extra work. Multiplex uses
two classes to perform input and output multiplexing:
MultiplexingInputStream
and
MultiplexingOutputStream
, which are returned
by the VirtualSocket
methods
getInputStream()
and getOutputStream()
,
respectively. These classes subclass
java.io.InputStream
and
java.io.OutputStream
and reimplement all of
their methods. Tests show that input/output by these classes is
roughly four to five times slower than input/output by their
counterpart classes used by actual sockets,
java.net.SocketInputStream
and
java.net.SocketOutputStream
. This
information is gathered from multiple runs of three tests:
bare input: |
compares the transmission of bytes from a
|
bare output: | compares the
transmission of bytes from a
|
socket input/output: | compares
the transmission of bytes from a
|
Each of these tests was run 10 times, transmitting 100,000 bytes each time. Table 1 gives the factor by which the virtual socket version of each test was slower than the actual socket version.
One of the design goals of Multiplex is to make virtual sockets and their related classes as indistinguishable as possible from their real counterparts. There are two areas in which Multiplex is detectibly different.
The use of the two types of virtual server sockets entails an extra degree of complexity in setting up a multiplexed connection.
There are performance differences.
On the other hand, the virtual classes implement complete APIs, so that once a connection is
established, a VirtualSocket
, for example, can be passed to a method in place of a
Socket
and will demonstrate the same behavior. Similarly,
MultiplexingInputStream
s and MultiplexingOutputStream
s
are functionally indistinguishable from SocketInputStream
s and
SocketOutputStream
s.
It may be useful, however, to be aware of some implementational differences between the two sets of classes. The public methods in the virtual classes can be placed in five categories.
methods implemented directly by the class
methods inherited from the real superclass
methods implemented by delegation to the underlying real socket
methods whose behavior is essentially null (though they may throw an
IOException
if called on a closed virtual socket)
methods which have no counterpart in the real class
Categories 3, 4, and 5 are particularly informative. Methods in category 3 can be used to
fine tune a multiplexed connection by, for example, adjusting buffer sizes. Note that a method
such as setReceiveBufferSize()
may be called on any virtual socket
in a socket group with the same effect as calling it on any other virtual socket in the same group.
Methods in category 4
represent behavior that is not relevant to virtual sockets, and methods in category 5 represent
behavior that is specific to the special nature of multiplexed connections. The category 5
version of VirtualSocket.connect()
,
connect(SocketAddress remoteAddress, SocketAddress localAddress, int timeout)
exists to effect an atomic binding/connecting action to avoid the
accidental connection problem
discussed in the section on virtual socket groups. The notion of connection is irrelevant to ordinary
server sockets, but SinglePortVirtualServerSocket
has methods
connect(SocketAddress remoteAddress, SocketAddress localAddress, int timeout)
and isConnected()
because a
connection must be established before accept()
can function.
We also include in category 5 one of SinglePortVirtualServerSocket
's
nonstandard constructors, with the signature
SinglePortVirtualServerSocket(InetSocketAddress remoteAddress, InetSocketAddress localAddress, int timeout)
which calls the two-address form of connect()
.
The public methods of the main Multiplex classes are categorized in Table 2.
and Table 3. The only inherited methods among the
classes listed in Table 2
are found in MultiPortVirtualServerSocket
, and
we omit an explicit listing of them.
Table 2. Categories of public methods in the primary public Multiplex classes
|
|
| |
---|---|---|---|
category 1 |
|
|
|
|
|
| |
|
|
| |
|
|
| |
|
|
| |
|
|
| |
|
|
| |
|
|
| |
|
|
| |
|
|
| |
|
|
| |
|
|
| |
|
|
| |
|
|
| |
|
|
|
|
category 3 |
|
|
|
|
|
| |
|
|
| |
|
|
| |
|
|
| |
|
|
| |
|
|
| |
|
|
| |
|
|
| |
|
|
| |
|
|
| |
|
|
| |
|
|
| |
|
|
|
|
category 4 |
|
|
|
|
|
| |
|
|
| |
|
|
|
|
category 5 |
|
|
|
|
| ||
| |||
[a] This version of [b] This constructor is nonstandard in that it has both a local and remote address. It binds to a local address and connects to a remote address in a single atomic action. |
Table 3. Categories of public methods in the other public Multiplex classes
|
|
|
| |
---|---|---|---|---|
category 1 |
|
|
|
|
|
|
|
| |
| ||||
|
| |||
|
|
|
| |
category 2 |
| |||
| ||||
| ||||
|
|
|
| |
category 4 |
| |||
|
|
|
| |
category 5 |
| |||
| ||||
| ||||
|
Listing 1. Client for Prime Scenario example.
public class PrimeScenarioExampleClient { public void runPrimeScenario() { try { // create a VirtualSocket and connect it to MultiPortVirtualServerSocket Socket v1 = new VirtualSocket("localhost", 5555); // do some asynchronous input in a separate thread new AsynchronousThread(v1).start(); // do some synchronous communication ObjectOutputStream oos = new ObjectOutputStream(v1.getOutputStream()); ObjectInputStream ois = new ObjectInputStream(v1.getInputStream()); oos.writeObject(new Integer(3)); Integer i1 = (Integer) ois.readObject(); v1.close(); } catch (Exception e) {} } class AsynchronousThread extends Thread { private Socket virtualSocket; AsynchronousThread(Socket virtualSocket) { this.virtualSocket = virtualSocket; } public void run() { try { // create a SinglePortVirtualServerSocket that shares a port // with virtualSocket (Note that it will be connected by virtue // of joining a connected socket group.) ServerSocket serverSocket = new SinglePortVirtualServerSocket(virtualSocket.getLocalPort()); // create a VirtualSocket that shares a port with virtualSocket serverSocket.setSoTimeout(10000); Socket v4 = serverSocket.accept(); // get an object from the server v4.setSoTimeout(10000); ObjectInputStream ois = new ObjectInputStream(v4.getInputStream()); Object o = ois.readObject(); serverSocket.close(); v4.close(); } catch (Exception e) {} } } public static void main(String[] args) { new PrimeScenarioExampleClient().runPrimeScenario(); } }
Listing 2. Server for Prime Scenario example.
public class PrimeScenarioExampleServer { public void runPrimeScenario() { try { // create a MultiPortVirtualServerSocket and get a VirtualSocket ServerSocket serverSocket = new MultiPortVirtualServerSocket(5555); serverSocket.setSoTimeout(10000); Socket v2 = serverSocket.accept(); // do some asynchronous communication in a separate thread Thread asynchronousThread = new AsynchronousThread(v2); asynchronousThread.start(); // do some synchronous communication ObjectInputStream ois = new ObjectInputStream(v2.getInputStream()); ObjectOutputStream oos = new ObjectOutputStream(v2.getOutputStream()); v2.setSoTimeout(10000); Object o = ois.readObject(); oos.writeObject(o); serverSocket.close(); v2.close(); } catch (Exception e) { } } class AsynchronousThread extends Thread { private Socket virtualSocket; public AsynchronousThread(Socket socket) throws IOException {this.virtualSocket = socket;} public void run() { try { // connect to SinglePortVirtualServerSocket String hostName = virtualSocket.getInetAddress().getHostName(); int port = virtualSocket.getPort(); Socket v3 = new VirtualSocket(hostName, port); // send an object to the client ObjectOutputStream oos = new ObjectOutputStream(v3.getOutputStream()); oos.writeObject(new Integer(7)); oos.flush(); v3.close(); } catch (Exception e) {} } } public static void main(String[] args) { new PrimeScenarioExampleServer().runPrimeScenario(); } }
Listing 3. Sample client for N-socket scenario.
public class N_SocketScenarioClient { public void runN_SocketScenario() { try { // Create a SinglePortVirtualServerSocket and // connect it to the server. SinglePortVirtualServerSocket serverSocket = new SinglePortVirtualServerSocket(5555); InetSocketAddress connectAddress = new InetSocketAddress(“localhost”, 6666); serverSocket.setSoTimeout(10000); serverSocket.connect(connectAddress); // Accept connection requests for 3 virtual sockets. Socket socket1 = serverSocket.accept(); Socket socket2 = serverSocket.accept(); Socket socket3 = serverSocket.accept(); // Do some i/o. InputStream is1 = socket1.getInputStream(); OutputStream os1 = socket1.getOutputStream(); InputStream is2 = socket2.getInputStream(); OutputStream os2 = socket2.getOutputStream(); InputStream is3 = socket3.getInputStream(); OutputStream os3 = socket3.getOutputStream(); os1.write(3); os2.write(7); os3.write(11); System.out.println(is1.read()); System.out.println(is2.read()); System.out.println(is3.read()); socket1.close(); socket2.close(); socket3.close(); serverSocket.close(); } catch (Exception e) {} } public static void main(String[] args) { new N_SocketScenarioClient().runN_SocketScenario(); } }
Listing 4. Sample server for N-socket scenario.
public class N_SocketScenarioServer { public void runN_SocketScenario() { try { // Create and bind a MultiPortVirtualServerSocket. MultiPortVirtualServerSocket serverSocket = new MultiPortVirtualServerSocket(6666); // Accept connection request from // SinglePortVirtualServerSocket. serverSocket.setSoTimeout(10000); serverSocket.acceptServerSocketConnection(); // Create 3 virtual sockets Socket socket1 = new VirtualSocket("localhost", 5555); Socket socket2 = new VirtualSocket("localhost", 5555); Socket socket3 = new VirtualSocket("localhost", 5555); // Do some i/o. InputStream is1 = socket1.getInputStream(); OutputStream os1 = socket1.getOutputStream(); InputStream is2 = socket2.getInputStream(); OutputStream os2 = socket2.getOutputStream(); InputStream is3 = socket3.getInputStream(); OutputStream os3 = socket3.getOutputStream(); os1.write(is1.read()); os2.write(is2.read()); os3.write(is3.read()); socket1.close(); socket2.close(); socket3.close(); serverSocket.close(); } catch (Exception e) {} } public static void main(String[] args) { new N_SocketScenarioServer().runN_SocketScenario(); } }
Listing 5. Symmetric Scenario client.
public class SymmetricScenarioClient { public void runSymmetricScenario() { try { // Get a virtual socket to use for synchronizing client and server. Socket syncSocket = new Socket("localhost", 6666); InputStream is_sync = syncSocket.getInputStream(); OutputStream os_sync = syncSocket.getOutputStream(); // Create a SinglePortVirtualServerSocket and connect // it to MultiPortVirtualServerSocket running on the server. SinglePortVirtualServerSocket serverSocket = new SinglePortVirtualServerSocket(5555); InetSocketAddress address = new InetSocketAddress("localhost", 7777); is_sync.read(); serverSocket.setSoTimeout(10000); serverSocket.connect(address); // Call constructor to create a virtual socket and make a connection // request to the port on the server to which the local // SinglePortVirtualServerSocket is connected, // i.e., to the remote SinglePortVirtualServerSocket. os_sync.write(5); is_sync.read(); int port = serverSocket.getRemotePort(); Socket virtualSocket1 = new VirtualSocket("localhost", port); InputStream is1 = virtualSocket1.getInputStream(); OutputStream os1 = virtualSocket1.getOutputStream(); // Create a virtual socket with SinglePortVirtualServerSocket.accept(). Socket virtualSocket2 = serverSocket.accept(); InputStream is2 = virtualSocket2.getInputStream(); OutputStream os2 = virtualSocket2.getOutputStream(); // Do some i/o and close sockets. os1.write(9); System.out.println(is1.read()); os2.write(11); System.out.println(is2.read()); virtualSocket1.close(); virtualSocket2.close(); syncSocket.close(); serverSocket.close(); } catch (Exception e) {} } public static void main(String[] args) { new SymmetricScenarioClient().runSymmetricScenario(); } }
Listing 6. Symmetric Scenario server.
public class SymmetricScenarioServer { public void runSymmetricScenario() { try { // Create ServerSocket and get synchronizing socket. ServerSocket ss = new ServerSocket(6666); Socket syncSocket = ss.accept(); ss.close(); InputStream is_sync = syncSocket.getInputStream(); OutputStream os_sync = syncSocket.getOutputStream(); // Create MultiPortVirtualServerSocket, accept connection request from remote // SinglePortVirtualServerSocket, and get the bind port of the local actual // socket to which the SinglePortVirtualServerSocket is connected. MultiPortVirtualServerSocket mpvss = new MultiPortVirtualServerSocket(7777); os_sync.write(3); mpvss.setSoTimeout(10000); int port = mpvss.acceptServerSocketConnection(); mpvss.close(); // Wait until remote SinglePortVirtualServerSocket is running, then create local // SinglePortVirtualServerSocket, bind it to the local port to which the remote // SinglePortVirtualServerSocket is connected, and connect it to the remote // SinglePortVirtualServerSocket. is_sync.read(); SinglePortVirtualServerSocket spvss = new SinglePortVirtualServerSocket(port); InetSocketAddress address = new InetSocketAddress("localhost", 5555); spvss.setSoTimeout(5000); spvss.connect(address); // Indicate that the local SinglePortVirtualServerSocket is running. os_sync.write(7); // Create a virtual socket by way of SinglePortVirtualServerSocket.accept(); serverSocket.setSoTimeout(10000); Socket virtualSocket1 = spvss.accept(); InputStream is1 = virtualSocket1.getInputStream(); OutputStream os1 = virtualSocket1.getOutputStream(); // Call constructor to create a virtual socket and make a connection // request to the remote SinglePortVirtualServerSocket. Socket virtualSocket2 = new VirtualSocket("localhost", 5555); InputStream is2 = virtualSocket2.getInputStream(); OutputStream os2 = virtualSocket2.getOutputStream(); // Do some i/o and close sockets. os1.write(is1.read()); os2.write(is2.read()); virtualSocket1.close(); virtualSocket2.close(); syncSocket.close(); spvss.close(); } catch (Exception e) {} } public static void main(String[] args) { new SymmetricScenarioServer().runSymmetricScenario(); } }
Listing 7. Sample use of VirtualServerSocketFactory and VirtualSocketFactory.
public class FactoryExample { public void runFactoryExample() { ServerSocketFactory ssf = VirtualServerSocketFactory.getDefault(); ((VirtualServerSocketFactory) ssf).setOnServer(); SocketFactory sf = VirtualSocketFactory.getDefault(); useFactories(ssf, sf); } public void useFactories(ServerSocketFactory ssf, SocketFactory sf) { try { ServerSocket ss = ssf.createServerSocket(5555); Socket s = sf.createSocket("localhost", 6666); } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) { new FactoryExample().runFactoryExample(); } }