Let\'s say Java has two kind of sockets:
Imagine the situation of two
This is the code where I connected two Socket
without any ServerSocket
:
package primary;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class Main {
private static Object locker;
public static void main(String[] args) {
locker = new Object();
final int[][] a = new int[6][];
final int[][] b = new int[6][];
final int[][] c;
a[0] = new int[] {12340, 12341};
a[1] = new int[] {12342, 12344};
a[2] = new int[] {12342, 12343};
a[3] = new int[] {12340, 12345};
a[4] = new int[] {12344, 12345};
a[5] = new int[] {12341, 12343};
b[0] = new int[] {22340, 22341};
b[1] = new int[] {22342, 22344};
b[2] = new int[] {22342, 22343};
b[3] = new int[] {22340, 22345};
b[4] = new int[] {22344, 22345};
b[5] = new int[] {22341, 22343};
c = a;
SwingUtilities.invokeLater(
new Runnable() {
@Override
public void run() {
Client client1 = new Client("client1", c[0], c[1]);
client1.exe();
client1.setLocation(0, 200);
client1.setVisible(true);
client1.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
});
SwingUtilities.invokeLater(
new Runnable() {
@Override
public void run() {
Client client2 = new Client("client2", c[2], c[3]);
client2.exe();
client2.setLocation(400, 200);
client2.setVisible(true);
client2.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
});
SwingUtilities.invokeLater(
new Runnable() {
@Override
public void run() {
Client client3 = new Client("client3", c[4], c[5]);
client3.exe();
client3.setLocation(800, 200);
client3.setVisible(true);
client3.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
});
}
}
package primary;
import java.io.EOFException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.concurrent.*;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class Client extends JFrame implements Runnable {
private final String myName;
private ServerSocket listener;
private Socket connection1;
private Socket connection2;
private ObjectOutputStream output1;
private ObjectOutputStream output2;
private ObjectInputStream input1;
private ObjectInputStream input2;
private Object receiveObject;
private Object1 sendObject1;
private Object2 sendObject2;
private final int[] myLocalPort;
private final int[] connectionPort;
private ExecutorService service;
private Future<Boolean> future1;
private Future<Boolean> future2;
public Client(final String myName, int[] myLocalPort, int[] connectionPort) {
super(myName);
this.myName = myName;
this.myLocalPort = myLocalPort;
this.connectionPort = connectionPort;
sendObject1 = new Object1("string1", "string2", myName);
sendObject2 = new Object2("string1", 2.5, 2, true, myName);
initComponents();
}
public void exe() {
ExecutorService eService = Executors.newCachedThreadPool();
eService.execute(this);
}
@Override
public void run() {
try {
displayMessage("Attempting connection\n");
try {
connection1 = new Socket(InetAddress.getByName("localhost"), connectionPort[0], InetAddress.getByName("localhost"), myLocalPort[0]);
displayMessage(myName + " connection1\n");
} catch (Exception e) {
displayMessage("failed1\n");
System.err.println("1" + myName + e.getMessage() + "\n");
}
try {
connection2 = new Socket(InetAddress.getByName("localhost"), connectionPort[1], InetAddress.getByName("localhost"), myLocalPort[1]);
displayMessage(myName + " connection2\n");
} catch (Exception e) {
displayMessage("failed2\n");
System.err.println("2" + myName + e.getMessage() + "\n");
}
displayMessage("Connected to: " + connection1.getInetAddress().getHostName() + "\n\tport: "
+ connection1.getPort() + "\n\tlocal port: " + connection1.getLocalPort() + "\n"
+ connection2.getInetAddress().getHostName() + "\n\tport: " + connection2.getPort()
+ "\n\tlocal port: " + connection2.getLocalPort() + "\n\n");
output1 = new ObjectOutputStream(connection1.getOutputStream());
output1.flush();
output2 = new ObjectOutputStream(connection2.getOutputStream());
output2.flush();
input1 = new ObjectInputStream(connection1.getInputStream());
input2 = new ObjectInputStream(connection2.getInputStream());
displayMessage("Got I/O stream\n");
setTextFieldEditable(true);
service = Executors.newFixedThreadPool(2);
future1 = service.submit(
new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
try {
processConnection(input1);
displayMessage("input1 finished");
} catch (IOException e) {
displayMessage("blah");
}
return true;
}
});
future2 = service.submit(
new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
try {
processConnection(input2);
displayMessage("input2 finished");
} catch (IOException e) {
displayMessage("foo");
}
return true;
}
});
} catch (UnknownHostException e) {
displayMessage("UnknownHostException\n");
e.printStackTrace();
} catch (EOFException e) {
displayMessage("EOFException\n");
e.printStackTrace();
} catch (IOException e) {
displayMessage("IOException\n");
e.printStackTrace();
} catch(NullPointerException e) {
System.err.println("asdf " + e.getMessage());
} finally {
try {
displayMessage("i'm here\n");
if((future1 != null && future1.get()) && (future2 != null && future2.get())) {
displayMessage(future1.get() + " " + future2.get() + "\n");
displayMessage("Closing Connection\n");
setTextFieldEditable(false);
if(!connection1.isClosed()) {
output1.close();
input1.close();
connection1.close();
}
if(!connection2.isClosed()) {
output2.close();
input2.close();
connection2.close();
}
displayMessage("connection closed\n");
}
} catch (IOException e) {
displayMessage("IOException on closing");
} catch (InterruptedException e) {
displayMessage("InterruptedException on closing");
} catch (ExecutionException e) {
displayMessage("ExecutionException on closing");
}
}
}//method run ends
private void processConnection(ObjectInputStream input) throws IOException {
String message = "";
do {
try {
receiveObject = input.readObject();
if(receiveObject instanceof String) {
message = (String) receiveObject;
displayMessage(message + "\n");
} else if (receiveObject instanceof Object1) {
Object1 receiveObject1 = (Object1) receiveObject;
displayMessage(receiveObject1.getString1() + " " + receiveObject1.getString2()
+ " " + receiveObject1.toString() + "\n");
} else if (receiveObject instanceof Object2) {
Object2 receiveObject2 = (Object2) receiveObject;
displayMessage(receiveObject2.getString1() + " " + receiveObject2.getD()
+ " " + receiveObject2.getI() + " " + receiveObject2.toString() + "\n");
}
} catch (ClassNotFoundException e) {
displayMessage("Unknown object type received.\n");
}
displayMessage(Boolean.toString(message.equals("terminate\n")));
} while(!message.equals("terminate"));
displayMessage("finished\n");
input = null;
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">
private void initComponents() {
dataField = new javax.swing.JTextField();
sendButton1 = new javax.swing.JButton();
sendButton2 = new javax.swing.JButton();
jScrollPane1 = new javax.swing.JScrollPane();
resultArea = new javax.swing.JTextArea();
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
dataField.setEditable(false);
dataField.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
dataFieldActionPerformed(evt);
}
});
sendButton1.setText("Send Object 1");
sendButton1.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
sendButton1ActionPerformed(evt);
}
});
sendButton2.setText("Send Object 2");
sendButton2.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
sendButton2ActionPerformed(evt);
}
});
resultArea.setColumns(20);
resultArea.setEditable(false);
resultArea.setRows(5);
jScrollPane1.setViewportView(resultArea);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
getContentPane().setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
.addComponent(jScrollPane1)
.addComponent(dataField, javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
.addComponent(sendButton1)
.addGap(18, 18, 18)
.addComponent(sendButton2)
.addGap(0, 115, Short.MAX_VALUE)))
.addContainerGap())
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addComponent(dataField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(18, 18, 18)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(sendButton1)
.addComponent(sendButton2))
.addGap(18, 18, 18)
.addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 144, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
pack();
}// </editor-fold>
private void dataFieldActionPerformed(java.awt.event.ActionEvent evt) {
// TODO add your handling code here:
sendData(evt.getActionCommand());
dataField.setText("");
}
private void sendButton1ActionPerformed(java.awt.event.ActionEvent evt) {
// TODO add your handling code here:
sendData(sendObject1);
}
private void sendButton2ActionPerformed(java.awt.event.ActionEvent evt) {
// TODO add your handling code here:
sendData(sendObject2);
}
/**
* @param args the command line arguments
*/
private void displayMessage(final String messageToDisplay) {
SwingUtilities.invokeLater(
new Runnable() {
@Override
public void run() {
resultArea.append(messageToDisplay);
}
});
}
private void setTextFieldEditable(final boolean editable) {
SwingUtilities.invokeLater(
new Runnable() {
@Override
public void run() {
dataField.setEditable(editable);
}
});
}
private void sendData(final Object object) {
try {
output1.writeObject(object);
output1.flush();
output2.writeObject(object);
output2.flush();
displayMessage(myName + ": " + object.toString() + "\n");
} catch (IOException e) {
displayMessage("Error writing object\n");
}
}
// Variables declaration - do not modify
private javax.swing.JTextField dataField;
private javax.swing.JScrollPane jScrollPane1;
private javax.swing.JTextArea resultArea;
private javax.swing.JButton sendButton1;
private javax.swing.JButton sendButton2;
// End of variables declaration
}
Here Object1
and Object2
are just two Serializable
Objects.
All the sockets connect perfectly, it seems. If I System.exit() without calling the close()
methods for the sockets and their input, output streams and re-run, it works fine still. But if I System.exit() by making sure that the close()
methods are called, and I re-run again, I get this:
1client2Address already in use: connect
1client3Address already in use: connect
2client3Address already in use: connect
asdf null
1client1Connection refused: connect
2client2Connection refused: connect
asdf null
2client1Connection refused: connect
asdf null
I re-run again and again, I keep getting this, unless, I wait a certain amount of time and re-run again, it works just fine as the first time.
I understand what you are after - I have had to solve the same problem in situations where the server was behind a masquerading firewall with a dynamic IP. I used a small freely available program, javaProxy to provide a solution. It makes the server appear as a client socket - internally, it is still a server, but javaProxy provides forwarding program - My App in the example - that creates client connections "from" the server. It also provides the proxy in the middle (Middle Server, in the example) to join the two client ends together - the client socket forwarded from the server, and the client socket from the actual client trying to connect to the server.
The Middle Server is hosted outside the firewall on a known IP. (Even though we can pretend to do this without server sockets, each connection must involve a client and a server end and so we make sure the Middle Server is on an IP that the clients can reach.) In my case I just used a simple hosting provider that let me run a java from the shell.
With this setup, I could provide access to remote desktop and other services running behind a NAT firewall with dynamic IP, with access from my home machine which also was behind a NAT with dynamic IP. The only IP address I needed to know was the IP of the Middle Server.
As to threading, the javaproxy library is almost certainly implemented using threads to pump data between the client sockets, but these do not consume any CPU resources (or power) while they are blocking waiting for I/O. When java 7 is released with support for asynchronous I/O then one thread per client socket pair will not be necessary, but this is more about performance and avoiding limits on the maximum number of threads (stack space) rather than power consumption.
As to implementing this yourself with two client sockets in the same process requires the use of threads so long as java is dependent upon blocking I/O. The model is pull from the read end and push to the write end, so a thread is needed to pull from the read end. (If we had push from the read end, i.e asynchornous I/O then a dedicated thread per socket pair would not be needed.)
The classic Java approach to connection based socket communication is to set up a ServerSocket on a known IP and port and block on it's accept call, which (following a successful connection attempt) returns a new Socket with an implementation determined port (different from the ServerSocket's port). Typically the returned socket is passed to a handler implementing Runnable. Handlers are temporarily associated with a particular connection. Handlers can be reused and are associated with a particular thread usually for the lifetime of the connection. The blocking nature of classic Java socket IO makes connecting two sockets served by the same thread very difficult.
However it is possible, and not unusual, to process both input and output streams of a socket on the same thread and supporting a single connection at a time allows the Runnable requirement to be dropped, i.e. no additional thread is needed for the handler and the ServerSocket accept call is postponed until the current connection is closed.
In fact, if you use NIO you can easily handle many connections simultaniously on the same thread using the Selector mechanism. This is one of the most important features of NIO, non-blocking I/O to decouple threads from connections (allowing very high numbers of connections to be handled by small thread pools).
As far as the topology of your system, I'm sorry I'm not yet clear what you are after but it sounds like a job for either a NAT service or some sort of proxy bridging the public IP to the private IP.
Why do we need a middle server? If you just want to expose VNCServer. Why not try an architecture like following
VNCServer(S) <-> (C)MyApp(S) <-> (C) User
(S) represents a server socket
(C) represents a client socket
In this case MyApp acts both as a client (for VNCServer) and as a server(for User). So you will have to implement both client and server sockets in MyApp and then relay the data.
Edit: To communicate with VNCServer, MyApp needs to know the IP of the VNCServer. User will only be communicating with MyApp and only needs to know MyApp's IP Address. User doesn't need VNCServer's ip address.
First of all, don't call an accepted client (server-side) its socket a Client Socket
. That is very confusing.
Let's say how to have two client socket get interconnected without using an intermediate ServerSocket?
That is impossible. You always have to make a server-side, which can accept clients. Now the question is: which side of the connection should be the server-side?
Things you have to think about by this decision:
Middle server
Why don't make immediately a connection to the VNCServer?
But if you really want it, you can make a situation like this:
/ VNCServer (Server Running) <---. | | LAN -| Connects to VNCServer | | \ MyApp (Server Running --> Accepts from Middle Server) <------. | (Through a router) | Middle server (Server Running --> Accepts client) ---> Connects to Your App ^ | (Through a router) | Client --> Connects to Middle Server --°
And this is how it looks without the third server (What I recommend you):
/ VNCServer (Server Running) <---. | | LAN -| Connects to VNCServer | | \ MyApp (Server Running --> Accepts Clients) <------. | (Through a router) | Client --> Connects to MyApp --------------------------°
EDIT:
I think I got it now:
We have to visualize your situation like this:
Your Main Server (What you called middle server) (1) | | (2) /⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻/ \⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻⁻\ | | Your VNCServer <----------------------------> The client (5) (3)
(1)
The VNCServer connects to the main server. So, then the main server got the VNCServer its IP.
(2)
The client connects to the main server.
(3)
Now the main server knows where server and client are. Then he sends to the client where the server is. Then the client will connect to the IP he received from the main server. That is of course the IP from the VNCServer.
(5)
The VNCServer is running is server to accept the client.
Now desktop sharing can start.
I think this is the most recommend situation you can have.
Of course writing it in Java is to you.
Why would you need to do that?
If you want to have a "peer-to-peer" type system, then you just have each client run both a client and a server socket - the server socket for accepting connections from other clients and the client socket for establishing connections to others.
ETA: It wasn't entirely clear what you were asking in the original question, but since your edit, it seems like you are looking to create a sort of proxy server.
In your example, your app would create two client sockets, one connecting to the VNCServer and the other connecting to the "middle server". The "middle server" would then have two server sockets (one for your app to connect to and one for the user to connect to. Internally it would then need to know how to match those sockets up and shuttle data between the two.