Reputation: 79
I want to use same port for both inbound and outbound connections in java
The purpose is to make a node in distributed environment. But in Tcp I need to use two different ports for accepting and initiating connections.
// accept incoming connection on one port
ServerSocket.accept()
// connect to remote, the port used will be different from the one used for accepting
Socket.connect()
Now the problem is:
Socket.connect()
), A & B will keep the socket open for future message passing.Socket()
instance which doesn't have a accept()
methodOf course, A can inform B about the port it is listening, but isn't there a direct way?
How can I make this test to pass?
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.DatagramSocket;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class DualSocketTest {
ExecutorService service= Executors.newFixedThreadPool(10);
int echoServerport=8080;
int localServerport=8090;
@Test
public void testConnectivity() throws IOException {
// create a echo server on port 8080
startEcho();
// create a local Server instance
ServerSocket localServer=new ServerSocket();
// set the reuseAddress to true
localServer.setReuseAddress(true);
// bind the serverSocket
localServer.bind(new InetSocketAddress(localServerport));
// create a socket to connect the echo server using the same port used by localServer
Socket socket = new Socket();
socket.setReuseAddress(true);
// but this will throw SocketBindException
socket.bind(new InetSocketAddress(localServerport));
socket.connect(new InetSocketAddress(echoServerport));
// write hello
socket.getOutputStream().write("Hello !".getBytes());
byte[] result=new byte[100];
// receive hello
String ans=new String(result,0,socket.getInputStream().read(result));
System.out.println("Server replied with : "+ans);
// what was written and what was received must be same.
assert(ans.equals("Hello !"));
}
// start a echo server listening on the specified port
private void startEcho() throws IOException {
ServerSocket echoServer=new ServerSocket(echoServerport);
service.submit(()->{
try {
while(!echoServer.isClosed()) {
Socket socket = echoServer.accept();
System.out.println("connected with :" + socket.getInetAddress().toString() + ":" + socket.getPort());
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
service.submit(() -> {
while (socket.isConnected()) {
try {
outputStream.write(inputStream.read());
} catch (IOException e) {
break;
}
}
System.out.println("The Client has closed connection.");
});
}
} catch (IOException e) {
e.printStackTrace();
}
});
Thread.yield();
}
// Write something to the socket.
}
There is no such problem in when I previously used udp. The same socket supports receive()
and send()
method. For udp, sharing address is easy.
socketAddress
of A,Upvotes: 6
Views: 981
Reputation: 2278
I've fixed your test - hope that it's the way you wanted it to be. Take a look at the code here:
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.junit.Test;
public class DualSocketTest {
ExecutorService service = Executors.newFixedThreadPool(10);
int echoServerport = 8080;
int localServerport = 8080;
@Test
public void testConnectivity() throws IOException {
// create a echo server on port 8080
startEcho();
// create a socket to connect the echo server using the same port used by localServer
Socket socket = new Socket();
// but this will throw SocketBindException
socket.connect(new InetSocketAddress(echoServerport));
// write hello
socket.getOutputStream().write("Hello !".getBytes());
socket.getOutputStream().flush();
byte[] result = new byte[100];
// receive hello
String ans = new String(result, 0, socket.getInputStream().read(result));
System.out.println("Server replied with : " + ans);
// what was written and what was received must be same.
assert (ans.equals("Hello !"));
}
// start a echo server listening on the specified port
private void startEcho() throws IOException {
ServerSocket echoServer = new ServerSocket(echoServerport);
service.submit(() -> {
try {
while (!echoServer.isClosed()) {
Socket socket = echoServer.accept();
System.out.println("connected with :" + socket.getInetAddress().toString() + ":" + socket.getPort());
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
service.submit(() -> {
while (socket.isConnected()) {
try {
byte[] buffer = new byte[1024];
int read = -1;
while ((read = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, read);
}
} catch (IOException e) {
break;
}
}
System.out.println("The Client has closed connection.");
});
}
} catch (IOException e) {
e.printStackTrace();
}
});
Thread.yield();
}
// Write something to the socket.
}
Upvotes: 0
Reputation: 149155
SO_REUSEADDR option shall be set on a socket before binding. I initially did my tests in Python (no access to a Java environment) and following script worked without errors on a Windows 10 system:
import socket
serv = socket.socket() # set a listening socket
serv.bind(('0.0.0.0',8080))
serv.listen(5)
s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('0.0.0.0',8080))
s.connect(('127.0.0.1', 8090))
with another process listening on port 8090
Unfortunately, in Java setReuseAddr
javadoc says explicitely (emphasize mine):
Enabling SO_REUSEADDR prior to binding the socket using bind(SocketAddress) allows the socket to be bound even though a previous connection is in a timeout state.
For reasons I cannot guess, Java is more restrictive here. What looks even more weird, is that according to this other question it used to be allowed on older JRE versions (up to JRE 7U5)
Original (and wrong) post follows:
The trick is to set the SO_REUSEADDR option before binding. That means that you will need to use a parameterless constructor for both ServerSocket
et Socket
. More or less:
ServerSocket localServer = new ServerSocket();
localServer.setReuseAddress(true);
localServer.bind(InetSocketAddress(localServerport));
... // Ok listening...
Socket socket = new Socket();
socket.setReuseAddress(true);
socket.bind(InetSocketAddress(localServerport));
socket.connect(...);
That way you can connect from your local listening port so that the peer will know how to reconnect after the connection will be closed.
Beware: untested...
Upvotes: 0