import java.io.*;
import java.net.*;
import java.util.*;
import java.util.concurrent.*;
/**
*
* This Java network programming example shows how to
* write a server that handles client connections on two
* different sockets: A and B. Clients that connect
* to socket A are called Group A clients, and those for
* socket B are Group B clients. Each line of text received
* from any A client is sent to all B clients, and each
* line received from a B client is copied to all A clients.
* The port numbers for the A and B sockets must be supplied
* on the command line.
*
* This example shows how to use java.net.ServerSocket,
* java.net.Socket, various java.io classes, java.util.Set,
* java.util.concurrent.ExecutorService, java.lang.Thread,
* and java.lang.Runnable.
*
* This code requires JDK 1.5 (J2SE 5.0) or later.
*/
public class TwoPortServer {
// output thread count
private final static int OUTPUT_THREADS = 5;
// members to support the two server sockets
private ServerSocket socketA;
private Runnable accepterA;
private ServerSocket socketB;
private Runnable accepterB;
// members to hold the two groups of clients
private Set groupA;
private Set groupB;
// members to support the output thread pool
private ExecutorService outputService;
/**
* Constructor that creates a TwoPortServer for
* two designated ports. If either port is
* not usable, or if any other errors occur,
* an IOException is thrown.
*/
public TwoPortServer(int portA, int portB)
throws IOException
{
if (portA == portB)
throw new IllegalArgumentException("Ports can't be equal");
groupA = Collections.synchronizedSet(new HashSet());
groupB = Collections.synchronizedSet(new HashSet());
socketA = new ServerSocket(portA);
socketB = new ServerSocket(portB);
accepterA = new ConnAccepter(socketA, groupA, groupB);
accepterB = new ConnAccepter(socketB, groupB, groupA);
Thread tA = new Thread(accepterA, "Group A");
Thread tB = new Thread(accepterB, "Group B");
outputService = Executors.newFixedThreadPool(OUTPUT_THREADS);
tA.start();
tB.start();
}
/**
* Each ConnAccepter object runs in a thread, and handles
* accepting connection for one ServerSocket. Each time
* it accepts a connection, it create a SocketInputReader
* object for the newly connected Socket, and kicks off
* a separate Thread for reading input from that Socket.
*/
class ConnAccepter implements Runnable {
private ServerSocket sock;
private Set mygroup, othergroup;
ConnAccepter(ServerSocket s, Set g1, Set g2) {
sock = s;
mygroup = g1; othergroup = g2;
}
public void run() {
Socket newsock;
Runnable newreader;
while(true) {
newsock = null;
try {
newsock = sock.accept();
System.err.println("Connection from " + newsock +
" for " +
Thread.currentThread().getName());
} catch (IOException e) {
System.err.println("Bad accept in " +
Thread.currentThread().getName());
}
if (newsock != null) {
try {
newreader = new SocketInputReader(newsock,
mygroup,othergroup);
mygroup.add(newsock);
Thread t = new Thread(newreader,newsock.toString());
t.start();
} catch (IOException e2) {
System.err.println("Error on reader startup in " +
Thread.currentThread().getName());
}
}
}
}
}
/**
* Each SocketInputReader object handles reading lines of
* text from a Socket, using a BufferedReader. For each
* line of text, an OutputAction object is created for
* each member of the outputgroup, and each OutputAction
* object is given to the outputService to be handled.
*/
class SocketInputReader implements Runnable {
private Socket mysock;
private Set mygroup, outputgroup;
private BufferedReader reader;
SocketInputReader(Socket s, Setmg, Set og)
throws IOException
{
InputStream is = s.getInputStream();
reader = new BufferedReader(new InputStreamReader(is));
mygroup = mg;
outputgroup = og;
}
public void run() {
String line;
Runnable opx;
Iterator it;
try {
while(true) {
line = reader.readLine();
if (line == null) break; // end of input
line = line + "\n";
for(it = outputgroup.iterator(); it.hasNext(); ) {
opx = new OutputAction(line.getBytes(),it.next());
outputService.submit(opx);
}
}
}
catch (IOException e) { }
finally {
System.err.println("Lost connection (" +
Thread.currentThread().getName() +
")");
mygroup.remove(mysock);
try { reader.close(); } catch (Exception e1) { }
try { mysock.close(); } catch (Exception e2) { }
}
}
}
/**
* An OutputAction object represents a single chunk of data
* to be written to a particular Socket. The socket and
* data are bundled up in the way so that the output action
* can be managed by a ExecutorService (thread pool).
*/
public class OutputAction implements Runnable {
private byte[] data;
private Socket sock;
public OutputAction(byte [] d, Socket s) {
data = d; sock = s;
}
public void run() {
try {
sock.getOutputStream().write(data);
}
catch(IOException e) {
groupA.remove(sock);
groupB.remove(sock);
}
}
}
/**
* Main method, for accepting command line arguments.
*
* To use this program, give it two command line arguments,
* two different integers between 1025 and 65535. It will
* open TCP sockets on the two ports and wait for connections.
*/
public static void main(String [] args) {
if (args.length != 2) {
System.err.println("Usage: java TwoPortServer port1 port2");
System.exit(0);
}
int portA = Integer.parseInt(args[0]);
int portB = Integer.parseInt(args[1]);
TwoPortServer server;
try {
server = new TwoPortServer(portA, portB);
} catch(IOException ie) {
System.err.println("Could not start server: " + ie);
}
}
}