0

I've been trying to implement a simple multithreaded server-client program in Java. It's a project stage in Hyperskill (find it here).

Stage objectives are as follows:

The server should keep the database on the hard drive in a db.json file which should be stored as the JSON file in the /server/data folder. Use executors at the server in order to simultaneously handle multiple requests. Writing to the database file should be protected by a lock as described in the description. Implement the ability to read a request from a file. If the -in argument is followed by the file name provided, read a request from that file. The file will be stored in /client/data.

I have 2 packages, that are client and server, and 4 different classes in each package. Please find my full source code below. Also you may find the automated test results after the source code.

// client.Main

package client;

public class Main {

    public static void main(String[] args) {
        ClientInitializer clientInitializer = new ClientInitializer(args);
        clientInitializer.start();
    }
}

// client.clientInitializer
package client;

import com.google.gson.Gson;

import java.io.IOException;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Scanner;

public class ClientInitializer {

    private final List<String> args;

    public ClientInitializer(String[] args) {
        this.args = Arrays.asList(args);
    }

    @SuppressWarnings("CallToPrintStackTrace")
    public void start() {
        String input = ".\\client\\data\\" + getInput();
        if (!input.equals(".\\client\\data\\")) {
            try (Scanner fileScanner = new Scanner(Paths.get(input))) {
                StringBuilder jsonBuilder = new StringBuilder();
                while (fileScanner.hasNextLine()) {
                    jsonBuilder.append(fileScanner.nextLine());
                }
                String readRequest = jsonBuilder.toString();
                Request request = new Request(new Gson().fromJson(readRequest, Request.class));
                Client client = new Client(request);
                client.run();
                return;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        String type = getType();
        String key = getKey();
        String value = getValue();
        Request request = new Request(type, key, value);
        Client client = new Client(request);
        client.run();
    }

    private String getInput() {
        try {
            int indexOfInput = args.indexOf("-in");
            return indexOfInput == -1 ? "" : args.get(indexOfInput + 1);
        } catch (NullPointerException | NoSuchElementException e) {
            return "";
        }
    }

    private String getType() {
        int indexOfType = args.indexOf("-t") + 1;
        return args.get(indexOfType);
    }

    private String getKey() {
        try {
            int indexOfKey = args.indexOf("-k");
            return indexOfKey == -1 ? null : args.get(indexOfKey + 1);
        } catch (NullPointerException | NoSuchElementException e) {
            return null;
        }
    }

    private String getValue() {
        try {
            int indexOfCellIndex = args.indexOf("-v");
            return indexOfCellIndex == -1 ? null : args.get(indexOfCellIndex + 1);
        } catch (NullPointerException | NoSuchElementException e) {
            return null;
        }
    }
}
// client.Request

package client;

public class Request {

    private final String type;
    private final String key;
    private final String value;

    public Request(String type, String key, String value) {
        this.type = type;
        this.key = key;
        this.value = value;
    }

    public Request(Request request) {
        this.type = request.getType();
        this.key = request.getKey();
        this.value = request.getValue();
    }

    public String getType() {
        return type;
    }

    public String getKey() {
        return key;
    }

    public String getValue() {
        return value;
    }
}
// client.Client

package client;

import com.google.gson.Gson;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.net.Socket;

public class Client {

    private static final String ADDRESS = "127.0.0.1";
    private static final int PORT = 23456;
    private final Request request;

    public Client(Request request) {
        this.request = request;
    }

    @SuppressWarnings("CallToPrintStackTrace")
    public void run() {
        System.out.println("Client started!");
        try (Socket socket = new Socket(ADDRESS, PORT);
             DataInputStream input = new DataInputStream(socket.getInputStream());
             DataOutputStream output = new DataOutputStream(socket.getOutputStream())
        ) {
            String sentJson = new Gson().toJson(request);
            output.writeUTF(sentJson);
            System.out.println("Sent: " + sentJson);
            if (socket.isOutputShutdown()) {
                System.out.println("Server closed the connection.");
            } else {
                try {
                    String receivedJson = input.readUTF();
                    System.out.println("Received: " + receivedJson);
                } catch (EOFException e) {
                    System.out.println("Server closed the connection unexpectedly.");
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

// server.Main

package server;

public class Main {

    public static void main(String[] args) {
        Server server = new Server();
        server.run();
    }
}

// server.Server

package server;

import client.Request;
import com.google.gson.Gson;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Server {

    private static final int PORT = 23456;
    private final Gson gson;
    private final ReadWriteLock lock;
    private final ExecutorService executor = Executors.newFixedThreadPool(4);
    private volatile boolean isRunning = true;

    public Server() {
        writeDbToFile();
        this.gson = new Gson();
        this.lock = new ReentrantReadWriteLock();
    }

    @SuppressWarnings("CallToPrintStackTrace")
    public void run() {
        System.out.println("Server started!");
        while (isRunning) {
            try (ServerSocket serverSocket = new ServerSocket(PORT)) {
                try (Socket acceptSocket = serverSocket.accept();
                     DataInputStream input = new DataInputStream(acceptSocket.getInputStream());
                     DataOutputStream output = new DataOutputStream(acceptSocket.getOutputStream())
                ) {
                    String receivedJson = input.readUTF();
                    if (receivedJson.contains("exit")) {
                        executor.submit(() -> {
                           handleExitRequest(output);
                        });
                        isRunning = false;
                    } else {
                        executor.submit(() -> {
                            handleRequest(receivedJson, output);
                        });
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void handleExitRequest(DataOutputStream output) {
        String sentJson = gson.toJson(new Response("OK"));
        try {
            output.writeUTF(sentJson);
        } catch (IOException e) {
            throw new RuntimeException("Server side IOException");
        }
    }

    private void handleRequest(String receivedJson, DataOutputStream output) {
        Request request = gson.fromJson(receivedJson, Request.class);
        if (receivedJson.contains("get")) {
            Database db = new Database();
            Lock readLock = lock.readLock();
            String key = request.getKey();
            try {
                output.writeUTF(gson.toJson(db.getInformation(key)));
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            readLock.unlock();
        } else if (receivedJson.contains("set")) {
            Database db = new Database();
            Lock writeLock = lock.writeLock();
            String key = request.getKey();
            String value = request.getValue();
            try {
                output.writeUTF(gson.toJson(db.setInformation(key, value)));
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            writeDbToFile();
            writeLock.unlock();
        } else if (receivedJson.contains("delete")) {
            Database db = new Database();
            Lock writeLock = lock.writeLock();
            String key = request.getKey();
            try {
                output.writeUTF(gson.toJson(db.deleteInformation(key)));
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            writeDbToFile();
            writeLock.unlock();
        }
    }

    @SuppressWarnings("CallToPrintStackTrace")
    private void writeDbToFile() {
        Database db = new Database();
        try (FileWriter fileWriter = new FileWriter(System.getProperty("user.dir") + "/src/server/data/db.json", true)) {
            for (var entry : db.getDb().entrySet()) {
                fileWriter.write(entry.getKey() + ":" + entry.getValue() + "\n");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
// server.Database

package server;

import java.util.HashMap;
import java.util.Map;

public class Database {

    private final Map<String, String> db;
    private static final String successMessage = "OK";
    private static final String errorMessage = "ERROR";
    private static final String noSuchKeyMessage = "No such key";

    public Database() {
        this.db = new HashMap<>(1000);
    }

    public Map<String, String> getDb() {
        return db;
    }

    public Response getInformation(String key) {
        return db.containsKey(key) ? new Response(successMessage, db.get(key), false) :
                new Response(errorMessage, noSuchKeyMessage);
    }

    public Response setInformation(String key, String value) {
        db.put(key, value);
        return new Response(successMessage);
    }

    public Response deleteInformation(String key) {
        if (db.containsKey(key)) {
            db.remove(key);
            return new Response(successMessage);
        } else {
            return new Response(errorMessage, noSuchKeyMessage);
        }
    }
}
// server.Response

package server;

public class Response {

    private final String response;
    private final String reason;
    private final String value;

    public Response(String response) {
        this.response = response;
        this.reason = null;
        this.value = null;
    }

    public Response(String response, String reason) {
        this.response = response;
        this.reason = reason;
        this.value = null;
    }

    public Response(String response, String value, boolean isReason) {
        this.response = response;
        this.value = value;
        this.reason = null;
    }
}

The test results:

Start test 1
Server started!
Client started!
Sent: {"type":"exit"}
Server closed the connection unexpectedly.

Start test 2

Start test 3
Server started!
Client started!
Sent: {"type":"get","key":"1"}
Server closed the connection unexpectedly.

Based on the automated tests, I've put some print statements in the catch blocks to see what exactly the issue is, and saw that Client can't read the response that was sent from the Server. I thought that it might be caused by one of the threads closing the server before the response was sent in the first place, but as I have little to zero experience with multithreading, I'm not so sure.

Program doesn't seem to have any issues with reading from and writing to files. To be honest, I'm not so sure that I have structured the multithreading behavior correct at this point. I'm also not so sure about the use of Locks here.

I also tried to find similar problems on the Internet and alter my source code a bit depending on the suggestions here and there to no avail.

Pardon any potential design and programming problems as I've just started learning programming this summer. Any help is very much appreciated.

0

1 Answer 1

0

I was able to solve the problem by removing the try-with-resources when accepting clients. I've read it here: EOFException / SocketException when working with sockets and threads (1337joe's answer).

I removed try-with-resources and my code got immediately past the tests. I also moved the while loop after creating a ServerSocket object as to make sure that one server accepts multiple clients rather than trying to create multiple servers (which also created problems probably). Below are the changes for the future comers:

// I used try-with-resources here in the first implementation
while (isRunning) {
    try (ServerSocket serverSocket = new ServerSocket(PORT)) {
        try (Socket acceptSocket = serverSocket.accept();
             DataInputStream input = new DataInputStream(acceptSocket.getInputStream());
             DataOutputStream output = new DataOutputStream(acceptSocket.getOutputStream()))
             // ... rest of the code

// I removed try-with-resources and changed it as follows
    try (ServerSocket serverSocket = new ServerSocket(PORT)) {
        while (isRunning) {
            try {
                Socket acceptSocket = serverSocket.accept();
                DataInputStream input = new DataInputStream(acceptSocket.getInputStream());
                DataOutputStream output = new DataOutputStream(acceptSocket.getOutputStream());
                // ... rest of the code
Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.