Get up to 80 % extra points for free! More info:

Lesson 19 - Java Chat - Client - Server Connection Part 3

In the previous lesson, Java Chat - Client - Server Connection Part 2, we focused on implementing a client communicator.

In today's Java tutorial, we're finally going to establish a connection with the server.

Modifying the Connect Controller

We'll start by modifying the ConnectController class. In the class, we'll create a new instance variable of the IClientCommunicationService type. We'll also create a setter for it to set the communicator:

public void setCommunicator(IClientCommunicationService communicator) {
    this.communicator = communicator;
    final BooleanBinding connected = Bindings.createBooleanBinding(() -> this.communicator.getConnectionState() == ConnectionState.CONNECTED, this.communicator.connectionStateProperty());
    btnConnect.disableProperty().bind(connected.or(txtServer.textProperty().isEmpty()));
    btnDisconnect.disableProperty().bind(connected.not());
    lblConnectedTo.textProperty().bind(this.communicator.connectedServerNameProperty());
}

We have to set the communicator like this because we don't have any dependency management implemented in this project, as we did with the server. In the setter we create a connected BooleanProperty to which we bind disableProperty of the Connect and Disconnect buttons. The disableProperty of the "connect" button has a bit more complicated logic, because we have to check that there's anything to connect to (the TextField with the server is filled in).

Next, we'll edit the initialize() method to which we'll add a response to the listView item selection:

lvServers.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
    if (newValue == null) {
        return;
    }

    txtServer.textProperty().set(String.format("%s:%d", newValue.getServerAddress().getHostAddress(), newValue.getPort()));
});

By calling the getSelectionModel().selectedItemProperty() methods we get an observable read-only property to which we add a listener. It's called whenever we change the selected item in the listView. If the new value is null, we don't do anything. Otherwise we fill the txtServer TextField with a value in the format: "server:port".

Now we'll create the most important method in the controller, connect(), to connect to the server:

private void connect() {
    final String hostPort = txtServer.textProperty().get();
    final String host = hostPort.substring(0, hostPort.indexOf(":"));
    final String portRaw = hostPort.substring(hostPort.indexOf(":") + 1);
    int port;
    try {
        port = Integer.parseInt(portRaw);
    } catch (Exception ex) {
        Alert alert = new Alert(AlertType.ERROR);
        alert.setHeaderText("Error");
        alert.setContentText("Unable to parse the server port.");
        alert.showAndWait();
        return;
    }

    this.communicator.connect(host, port)
    .exceptionally(throwable -> {
        Alert alert = new Alert(AlertType.ERROR);
        alert.setHeaderText("Error");
        alert.setContentText("Unable to connect to the server.");
        alert.showAndWait();

        throw new RuntimeException(throwable);
    })
   .thenAccept(ignored -> {
       Alert alert = new Alert(AlertType.INFORMATION);
       alert.setHeaderText("Information");
       alert.setContentText("Connection established successfully.");
       alert.showAndWait();
   });
}

In the first part of the method, we parse the server address and port which the server listens on from the TextField. If the server port cannot be parsed, we notify the user that the port is invalid. Then the connect() method is called on the communicator instance. The exceptionaly() method handles the case when the connection failed. Once we get to the exceptionaly() branch, we can stay there by throwing an exception again, or return a correct value to get to the default branch. The thenAccept() branch is evaluated if the connection was successfully established.

Finally, all we have to do is set the appropriate action for the buttons:

@FXML
private void handleConnect(ActionEvent actionEvent) {
    connect();
}

@FXML
private void handleDisconnect(ActionEvent actionEvent) {
    communicator.disconnect();
}

Customizing the Main Controller

Now we'll move to the main controller which will hold a single communicator instance. So, in the MainController class, we'll create an instance constant with the communicator:

private final IClientCommunicationService communicator = new ClientCommunicationService();

Next, we'll modify the handleConnect() method in which we'll show the window:

@FXML
private void handleConnect(ActionEvent actionEvent) {
    try {
        final ConnectController controller = showNewWindow("connect/connect", "Connect to server...");
        controller.setCommunicator(communicator);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

The showNewWindow() method returns a new window controller. We store this controller to a local variable and set the communicator using setCommunicator().

We'll also use the onClose() method, which is called when the window is closed. In this method we'll disconnect from the server just to be sure. This terminates the connection safely and all threads associated with it:

@Override
public void onClose() {
    communicator.disconnect()
}

Testing the Functionality

Finally, we can connect to the server. From the previous lesson we have searching for local servers available, so we'll use it now. Start the server and the client. Next, we'll display the connection window. When our running server appears in the list, we'll click this item. At this point, TextField should be filled in with the server address and port and the connection button should be active. After clicking the button, we should see a dialog that the connection was successful and the connection button shouldn't be disabled. On the contrary, the connect button should be activated. The best way to know that we're connected is that the number of connected users on the server changed:

Successful server connection - Server for Client Applications in Java

That would be all for today's lesson.

Next time, in the lesson Java Chat - Server - User Management, we'll create a simple server-side user management and log in to the server using a nickname.


 

Previous article
Java Chat - Client - Server Connection Part 2
All articles in this section
Server for Client Applications in Java
Skip article
(not recommended)
Java Chat - Server - User Management
Article has been written for you by Petr Štechmüller
Avatar
User rating:
No one has rated this quite yet, be the first one!
Activities