Manages a Java Socket connection. Written originally for a network game where both client and server programs were massively multi-threaded. Given an active Socket, the program keeps writes from multiple threads organized and makes read data available on a dedicated thread. Thoroughly Javadoc'ed.
Code deals with the threads needed to use a socket as well as the threads that may want to write to the socket simultaneously. Provides a thread to read from the socket. Gracefully handles the many problems and failures than can beset socket connections. Useful as is and can serve as a good example of programming with sockets.
A test program is provided that establishes a connection and feeds data both ways.
The following is a simplified version of the sample usage code included with the source code:
package rrc12;
import java.awt.event.*;
import java.io.*;
import java.net.*;
import javax.swing.*;
import rrc12.util.*;
import rrc12.util.socketconnection.*;
/** An example of using SocketConnection.
* This class is a blank window. While
* socket operations have nothing necessarily to do with windows, this
* class can produce an array of error messages, most of which must come
* to the attention of the user/developer immediately, and the best way
* to do that is with message boxes. Hence a window to serve as a parent
* to those message boxes.
* Starts listening at port 6212 and echoes back everything
* sent to the port.
* Then fires up a client thread to send info to the port.
* Sends arbitrary data to the server and writes it to the Standard Output
* when the server returns it.
*/
public class Sample extends JFrame {
// *** Class Fields ***
private static final int PORT = 6212;
private static Sample mainWindow;
// *** Class Methods ***
/** The main program. */
public static void main( String[] args ) {
mainWindow = new Sample();
mainWindow.setVisible( true );
// Start up the client.
mainWindow.runClient();
}
// *** Instance Fields ***
private ServerSocket serverSocket = null;
/** A ProcessData implementation to handle both ends of a socket.
* As they both, necessarily, use the same format, the read and write
* methods can be the same. */
private abstract class AbstractProcessData implements ProcessData {
public Object read( DataInput in ) throws IOException {
for (;;) {
int type = in.readByte();
switch (type) {
case 2:
return in.readUTF();
case 3:
int i = in.readInt();
return new Integer( i );
case 4:
long lng = in.readLong();
return new Long( lng );
}
}
}
public void write( Object o, DataOutput out )
throws IOException {
if (o instanceof String) {
out.writeByte( 2 );
out.writeUTF( (String) o );
}
else if (o instanceof Integer) {
out.writeByte( 3 );
out.writeInt( ((Integer) o).intValue() );
}
else if (o instanceof Long) {
out.writeByte( 4 );
out.writeLong( ((Long) o).longValue() );
}
}
}
/** A ProcessData implementation to handle the server end of a socket.
* In this case, it echoes back whatever comes in from the client.
* (Note that once a socket is established, which end is the client
* and which end is the server is somewhat arbitrary and even
* meaningless.) */
private class ServerConnect extends AbstractProcessData {
private volatile SocketConnection sConnect;
private ServerConnect( SocketConnection sc ) { sConnect = sc; }
public void respond( Object o ) {
SocketConnection conn = sConnect;
if (conn == null)
return;
if (o instanceof Integer &&
((Integer) o).intValue() == 99) {
conn.willShutDown();
System.out.println(
"Normal end. Close main window whenever.");
}
conn.getSocketOutput().write( o ); // Echo back to client.
}
public void terminate( Exception e ) {
Utilities.terminateProgram( Sample.this, e,
"Server ProcessData" );
}
}
/** A ProcessData implementation to handle the server end of a socket.
* In this case, it writes everything sent from the server to the
* Standard Output. */
private class ClientConnect extends AbstractProcessData {
private volatile SocketConnection sConnect;
private ClientConnect( SocketConnection sc ) { sConnect = sc; }
public void respond( Object o ) {
if (o instanceof Integer &&
((Integer) o).intValue() == 99) {
SocketConnection conn = sConnect;
if (conn == null) return;
if (!conn.isActive()) return;
conn.willShutDown();
conn.setSocket( null, true );
conn.dispose();
System.out.println( "Client has shut down.");
// Program is now done. Could call System.exit( 0 ) here.
return;
}
System.out.println( o.toString() );
}
public void terminate( Exception e ) {
Utilities.terminateProgram( Sample.this, e,
"Client ProcessData" );
}
}
// *** Instance Methods ***
/** Creates a new SocketSample. */
private Sample() {
setBounds( 100, 100, 150, 100 );
setDefaultCloseOperation( DISPOSE_ON_CLOSE );
addWindowListener( new WindowAdapter() {
public void windowClosed( WindowEvent e ) {
System.exit( 0 );
}
} );
// Create a ServerSocket to be used by portListening method.
try { serverSocket = new ServerSocket( PORT, 50, null ); }
catch (Exception e) {
// Warn user of major runtime exception.
quitPort();
return;
}
Thread t = new Thread() {
public void run() { portListening(); }
};
t.start();
try { Thread.sleep( 2000 ); }
catch (InterruptedException ie) {}
}
/** Establishes and starts running a client connection. */
private void runClient() {
// Set up SocketConnection.
SocketConnection client = new SocketConnection();
SocketOutput sout = client.getSocketOutput();
client.setMessageWindow( this );
client.setName( "Test Client" );
client.addListener( new SocketConnection.Listener() {
public void unexpectedDisconnect( SocketConnection sc ) {
System.out.println( "Client disconnected unexpectedly.");
}
} );
// The data handler handles all reads on the socket and, in this
// case, finally takes control of the client and shuts it down.
client.setDataHandler( new ClientConnect( client ) );
// Get a Socket.
Socket socket;
try { socket = new Socket( "127.0.0.1", PORT ); }
catch (Exception e) {
// Warn user of major runtime exception.
socket = null;
}
if (socket != null) {
// Demonstrate that we can write to SocketOutput before
// it has been given a connection.
sout.write( "First Line" );
// Apply the socket to the existing socket connection.
client.setSocket( socket, true );
// Write to the connected socket.
sout.write( "Second Line" );
sout.write( new Integer( 99 ));
}
}
/** Listens at a port (server connection). */
private void portListening() {
for (;;) {
if (serverSocket == null) break;
// Wait for a connection.
Socket s;
try { s = serverSocket.accept(); }
catch (Exception e) {
if (serverSocket == null) break;
continue;
}
if (serverSocket == null) break;
if (s == null) continue;
// Made a good connection.
// Put a new SocketConnection in place.
final SocketConnection connection = new SocketConnection();
connection.setMessageWindow( this );
connection.setName( "Test Server" );
connection.addListener( new SocketConnection.Listener() {
public void unexpectedDisconnect( SocketConnection sc ) {
System.out.println(
"Client has finished abnormally." );
connection.willShutDown();
connection.setSocket( null, true );
connection.dispose();
quitPort();
}
} );
ProcessData connect = new ServerConnect( connection );
connection.setDataHandler( connect );
connection.setSocket( s, true );
} // End serverLoop
quitPort();
}
/** Disconnects the server socket from the port and stops
* listening for possible connections. */
private void quitPort() {
if (serverSocket != null) {
try { serverSocket.close(); }
catch (IOException e) {}
serverSocket = null;
}
}
}
Questions & Comments