0

I'm trying to set up a peer to peer connection in java.

I'm trying to set up my program to listen for an incoming connection while outwardly being able to connect to a different client.

How can I instantiate my socket connection: socketConnection as whatever is connected to the program. Ideally like so:

if(socketConnection.isConnectedToExternalPeer()){
//do stuff
} else if (socketConnection.hasAnIncomingConnection()){
//do stuff
}

After consulting @L.Spillner 's solution I've put together the following code below, this only issue is that I can't quite grasp how to go about accepting a connection, this is evident from the fact that when I try to set up streams the program ends up in a loop while waiting for the peer's reply:

public class Client implements AutoCloseable {

    // Any other ThreadPool can be used as well
    private ExecutorService cachedExecutor = null;
    private ExecutorService singleThreadExecutor = null;
    // port this client shall listen on
    private int port = 0;

    // Name of the client
    private String name = null;

    // indicates that a connection is ongoing
    private boolean isConnected = false;

    // the socket the Client is currently connected with
    private Socket activeConenctionSocket = null;

    // The ServerSocket which will be listening for any incoming connection
    private ServerSocket listener = null;

    // The socket which has been accepted by the ServerSocket
    private Future<Socket> acceptedSocket;


    private ObjectInputStream inputStream = null;


    private ObjectOutputStream outputStream = null;

    private BloomChain bloomChain = null;


    /**
     * @param port Port number by which this client shall be accessed.
     * @param name The name of this Client.
     */
    public Client( int port, String name )
    {
        this.port = port;
        this.name = name;
        this.bloomChain = new BloomChain();

        this.cachedExecutor = Executors.newCachedThreadPool();
        this.singleThreadExecutor = Executors.newSingleThreadExecutor();

        this.listener = createListeningSocket();
        startListening();
    }

    private ServerSocket createListeningSocket()
    {

        ServerSocket temp = null;
        try
        {
            temp = new ServerSocket( this.port );

        }
        catch ( IOException e )
        {
            e.printStackTrace();
        }

        return temp;
    }

    private void startListening()
    {
        if ( !this.isConnected )
        {
            this.listener = createListeningSocket();
            this.acceptedSocket = this.cachedExecutor.submit( new ServAccept( this.listener ) );

        }
    }



    /**
     * Attempts to connect to any other socket specified by the hostname and the targetport.
     *
     * @param host The hostname of the target to connect.
     * @param targetport The port of the target.
     */
    public void connect( String host, int targetport )
    {

        try
        {   System.out.println(host);
            System.out.println(targetport);
            this.activeConenctionSocket = new Socket( InetAddress.getByName( host ), targetport );

            setUpStreams(this.activeConenctionSocket);

            this.isConnected = true;

            System.out.println(InetAddress.getAllByName(host));
        }
        catch ( IOException e )
        {
            e.printStackTrace();
        }

        try
        {
            this.listener.close();
        }
        catch ( IOException e )
        {
            // this will almost certainly throw an exception but it is intended.
        }
    }

    public void setUpStreams(Socket socket) throws IOException {
        this.outputStream = new ObjectOutputStream(socket.getOutputStream());
        this.outputStream.flush();
        this.inputStream = new ObjectInputStream(socket.getInputStream());
    }

    @Override
    public void close() throws Exception
    {
        // close logic (can be rather nasty)
    }

    public void sendMessage(String message){

        if(bloomChain.size()<1){
            bloomChain.addBlock(new Block(message, "0"));
        } else {
            bloomChain.addBlock(new Block(message, bloomChain.get(bloomChain.size()-1).getPreviousHash()));
        }
        try {

            this.outputStream.writeObject(bloomChain);
            this.outputStream.flush();

        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    public String mineMessage(){
        final String[] receivedMessage = {null};
        final Block tempBlock = this.bloomChain.get(this.bloomChain.size()-1);

        this.singleThreadExecutor.submit(()->{
            tempBlock.mineBlock(bloomChain.getDifficulty());
            receivedMessage[0] = tempBlock.getData();

        });

        return receivedMessage[0];
    }

    public String dataListener(){
        if(isConnected) {
            try {
                BloomChain tempChain = (BloomChain) this.inputStream.readObject();
                if (tempChain.isChainValid()) {
                    this.bloomChain = tempChain;
                    return mineMessage();
                }

            } catch (IOException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }

        return null;
    }

    public ServerSocket getListener() {
        return this.listener;
    }

    public boolean isConnected(){
        return isConnected;
    }

    public ObjectOutputStream getOutputStream(){
        return this.outputStream;
    }

    public ObjectInputStream getInputStream(){
        return this.inputStream;
    }



}

EDIT 2: I tried to await for acceptedSocket.get() to return a socket in a separate thread as follows:

new Thread(()->{
    setupStreams(this.acceptedSocket.get());
    //try-catch blocks omitted 
}).start();

This successfully wait for acceptedSocket to return a connected socket however when I try to connect to another locally running client i get the following error: java.net.SocketException: socket closed

Adrian Coutsoftides
  • 1,203
  • 1
  • 16
  • 38

1 Answers1

2

Okay after some tinkering I finally figured out a neat little solution:

We want to be able to listen and connect at the same time so we need a ServerSocket and issue an ServerSocket#accept call to accept incoming cnnections.
However this method is blocking the thread so in order to being able to proceed with our programm we have to outsource this call into another thread and luckly the default Java API does provide a simple way to do so.

The following codesample is not finished but provides the core functionality:

Client.java:

public class Client
    implements AutoCloseable
{
  // Any other ThreadPool can be used as well
  private ExecutorService es = Executors.newCachedThreadPool();

  // port this client shall listen on
  private int port;

  // Name of the client
  private String name;

  // indicates that a connection is ongoing
  private boolean isConnected = false;

  // the socket the Client is currently connected with
  private Socket activeConenctionSocket;

  // The ServerSocket which will be listening for any incoming connection
  private ServerSocket listener;

  // The socket which has been accepted by the ServerSocket
  private Future<Socket> acceptedSocket;

  /**
   * @param port Port number by which this client shall be accessed.
   * @param name The name of this Client.
   */
  public Client( int port, String name )
  {
    this.port = port;
    this.name = name;
    this.listener = createListeningSocket();
    startListening();
  }

  private ServerSocket createListeningSocket()
  {

    ServerSocket temp = null;
    try
    {
      temp = new ServerSocket( port );
    }
    catch ( IOException e )
    {
      e.printStackTrace();
    }

    return temp;
  }

  private void startListening()
  {
    if ( !isConnected )
    {
      listener = createListeningSocket();
      acceptedSocket = es.submit( new ServAccept( listener ) );
    }
  }

  /**
   * Attempts to connect to any other socket specified by the hostname and the targetport.
   * 
   * @param host The hostname of the target to connect.
   * @param targetport The port of the target.
   */
  public void connect( String host, int targetport )
  {
    isConnected = true;
    try
    {
      activeConenctionSocket = new Socket( InetAddress.getByName( host ), targetport );
    }
    catch ( IOException e )
    {
      e.printStackTrace();
    }

    try
    {
      listener.close();
    }
    catch ( IOException e )
    {
      // this will almost certainly throw an exception but it is intended.
    }
  }

  @Override
  public void close() throws Exception
  {
    // close logic (can be rather nasty)
  }
}

Let's walk through there step by step on how we instantiate a new Client object:

  1. When we instantiate our object we create a new ServerSocket
  2. We start listenting by creating a new Thread of a Callable<V> Object which I've named ServAccept for example purposes.
  3. Now we have a Future<T> object which will contain a socket if any connection gets accepted.

A positive side effect of the startListening() method is, that you can make it public and call it once more if the connection has dropped.

The conenct(...) method almost works the same way as your setupConnection() method but with a small twist. The ServerSocket, which is still listening in another thread, will be close. The reason for this is, that there is no other way to exit the accept() method the other thread is stuck in.

The last thing (which you have to figure out) is when to check if the Future object is already done.

ServAccept.java

public class ServAccept
    implements Callable<Socket>
{
  ServerSocket serv;

  public ServAccept( ServerSocket sock )
  {
    this.serv = sock;
  }

  @Override
  public Socket call() throws Exception
  {
    return serv.accept();
  }

}

EDIT:

As a matter of fact I have to admit that my approach might not be a very well rounded approach for the task so I decided to change tweak some things. This time instead of using a Future Object I decided to go with Events / a custom EventListener which is just sitting there and listening for a connection to receive. I tested the connection functionality and it works just fine but I haven't implemented a solution to determine if a Client really conncted to a peer. I just made sure that a client can only hold one connection at a time.

The changes:

ServerAccept.java

import java.io.IOException;
import java.net.ServerSocket;

public class ServAccept implements Runnable
{
    private ServerSocket serv;
    private ConnectionReceivedListener listener;

    public ServAccept( ServerSocket sock,ConnectionReceivedListener con )
    {
        this.serv = sock;
        this.listener = con;
    }

    @Override
    public void run()
    {
        try
        {
            listener.onConnectionReceived( new ConnectionReceivedEvent( serv.accept() ) );
        } catch (IOException e)
        {
            // planned exception here.
        }
    }
}

Does no longer implement Callable<V> but Runnable the only reason for that change is that we do not longer await any return since we will work with a listener and some juicy events. Anyway in order to do so we need to create and pass a listener to this object. But first we should take a look at the listener / event structure:

ConnectionReceivedListener.java

import java.util.EventListener;

@FunctionalInterface
public interface ConnectionReceivedListener extends EventListener
{
    public void onConnectionReceived(ConnectionReceivedEvent event);
}

Just a simple interface from what we build some anonymous classes or lambda expressions. Nothing to fancy. It doen't even need to extend the EventListener interface but I love to do that to remind me what the purpose of the class is.

ConnectionReceivedEvent.java

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class ConnectionReceivedEvent
{
    private Socket accepted;

    public ConnectionReceivedEvent( Socket sock )
    {
        this.accepted = sock;
    }

    public Socket getSocket()
    {
        return accepted;
    }

    public OutputStream getOutput() throws IOException
    {
        return accepted.getOutputStream();
    }

    public InputStream getInput() throws IOException
    {
        return accepted.getInputStream();
    }

    public int getPort()
    {
        return accepted.getPort();
    }
}

Nothing to fancy as well, just passing a Socket as a constructor parameter and defining some getters from which most will not be used in this example.

But how to we use it now?

private void startListening()
{
    if (!isConnected)
    {
        closeIfNotNull();
        listener = createListeningSocket();
        es.execute( new ServAccept( listener, event -> setAccepted( event.getSocket() ) ) );
    }
}

private void setAccepted( Socket socket )
{
    if (!isConnected)
    {
        this.activeConenctionSocket = socket;
        setUpStreams( socket );
    } else
    {
        sendError( socket );
    }
}

We still make use of our ExecutorService and creating a new Thread with the ServAccept class. However since we do not expect any return I changed from ExecutorService#submit to ExecutorService#execute (just a matter of opinion and taste).
But ServAccept needs two arguments now. The ServerSocket and the Listener to use. Fortunately we can use annonymous classes and since our Listener does only feature one method we can even use a lambda expression. event -> setAccepted(event.getSocket()).

As an answer to your 2nd edit: I did a logical mistake. Not the ServerSocket#close method does throw the exception whe interrupting a ServerSocket#accept call but rather the accept() call itself throws the exception. In other words the exception you got was intended and i suppressed another one by mistake.

L.Spillner
  • 1,772
  • 10
  • 19
  • If blocking is an issue, why not switch to NIO? See [this answer](https://stackoverflow.com/questions/24616774/non-socket-based-java-server/24617983#24617983) – Vince May 11 '18 at 13:26
  • @VinceEmigh I myself have never worked with the nio package yet and therefore am unable to provide example using it. On the other hand I've used a solution similar to this one in a decent project and it worked fine for me (for up to 1000 connections tested). – L.Spillner May 11 '18 at 13:29
  • I edited my comment to include an answer I wrote quite a while ago, which shows an easy example of using NIO for a non-blocking connection. It's actually quite simple, and allows you to avoid excess threads (which means avoided excess context switching). Not saying your answer is wrong, just saying NIO is an easy way to achieve non-blocking socket management. – Vince May 11 '18 at 13:30
  • @L.Spillner sorry for the late reply but I had to go away and brush up on `Callables` and `Futures` before I fully understood your answer. But having done so I quite like the way you have gone about this implementation! – Adrian Coutsoftides May 12 '18 at 14:38
  • After implementing your solution it appears as though I keep getting the same error whenever I try to run my program: `java.net.BindException: Address already in use` which occurs when I assign `temp = new ServerSocket( this.port );` in `createListeningSocket()`, any thoughts? I've changed the port number a few times to ports I know are free – Adrian Coutsoftides May 13 '18 at 00:41
  • @AdrianCoutsoftides Yes, I've not implemented a check logic in that method if there is already a socket opened. If there is you must close it before creating a new socket, otherwise you get that exception. But beware of `null`. – L.Spillner May 13 '18 at 07:42
  • @L.Spillner ah ok, i've passed `0` as the only `ServerSocket` parameter. How do I go about using `call()` in `ServAccept` in order to setup my input and output streams – Adrian Coutsoftides May 13 '18 at 14:04
  • @AdrianCoutsoftides That is up to you, if the accept call returns a socket ( a connection came in) `acceptedSocket.isDone()` will return true and `acceptedSocket.get()` retrives the concrete Socket which yields the streams you need. However its upt o you when to check for `acceptedSocket.isDone()` – L.Spillner May 13 '18 at 14:15
  • @L.Spillner could you please explain exactly how and when `call()` in `ServAccept` is called? – Adrian Coutsoftides May 13 '18 at 19:07
  • @AdrianCoutsoftides I updated my post and explained that the Callable / Future combination might not be the best approach. However `call()` will always be immediately called after the `ExecutorService` finished the construction of the thread (so to say 'instant'). – L.Spillner May 14 '18 at 07:37