Connect two client sockets
Solution 1
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:
- A server should have a static public IP.
- A server, which is after a router connected, has to do "port forwarding". (See UPnP)
- A client has to know which host it has to connect to (public IP)
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.
Solution 2
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.
Solution 3
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.
Solution 4
The ServerSocket allows you to listen for connections on a particular port. When a server socket accepts a connection, it spawns another thread, and moves the connection to another port, so the original port can still listen for additional connections.
The client initiates the connection on the known port. Then, typically, the client will send some request, and the server will respond. This will repeat until the communication is complete. This is the simple client/server approach that the web uses.
If you don't need this mechanism, and requests may come from either socket at any time, then implementing the reader and writer threads the way you have seems appropriate.
Internally, they still use wait mechanisms, so you shouldn't see much CPU usage while they wait for data to arrive.
I think you still need one end to be a server socket because I don't think it's possible to have a client socket accept a connection. ClientSocket implies TCP, which requires a connection. If you used DatagramSocket, which implies UDP, you could have client to client communication, without a connection.
Solution 5
Are you trying to created a mocked socket ? If so, mocking both sides of the pipe may be a bit more complicated than necessary.
On the other hand, if you just want to create a data pipe between two threads, you could use PipedInputStream and PipedOutputStream.
However, without more information about what your trying to accomplish, I cannot tell you if either of these choices is a good fit or if something else would be better.
Comments
-
Hernán Eche almost 4 years
Let's say Java has two kind of sockets:
- server sockets "ServerSocket"
- client sockets or just "Socket"
Imagine the situation of two processes:
X = Client
Y = ServerThe server process Y : has a "ServerSocket", that is listening to a TCP port
The client process X : sends a connection request through a "Socket" to Y.Y: Then the
accept()
method returns a new client type "Socket",
when it occurs, the two Sockets get "interconnected",So: the socket in client process, is connected with the socket in the server process.
Then: reading/writing through socket X is like reading/writing through socket Y.
Now, two Client Sockets get interconnected!!But...
What if I create the two Client sockets in same process, and I want to get them "interconnected" ?... even possible?
Let's say how to have two client socket get interconnected without using an intermediate ServerSocket?
I've solved it by creating two Threads for continuously reading A and writing B, and other for reading B and writng A...
But I think could be a better way... (Those world-energy-consuming threads are not necessary with the client-server approach)Any help or advice would be appreciated!! Thanks
Edit:
Example of application: "An existent server application could be converted to a client one", For example VNC server, one client socket connects to the VNC server, and other client socket is created (to connect to a middle server), then the application interconnects the two client resulting the VNC server is a client application! And then, no public IP is needed.
VNCServer---MyApp---> |middle server| <---User