1 Activity: Threads

Background

For this activity you will convert a simple single-threaded server to a multi-threaded server. You will create 2 versions, one that allows threads to grow unbounded, and one that sets the number of allowed clients at a time to a fixed number. This part is to practice threads on a smaller scale than activity 2.

You are given starting code of a single-threaded server. There is a Client provided, which you should use for your implementation. It also specifies a protocol which you need to implement as is.

Given Code

You are given starter code for this assignment:

  • Server.java - a main program that accepts incoming client connections
  • Performer.java - a program that does the "business logic" of what the client requests
  • StringList.java - a simple wrapper class for a list of strings
  • Client.java - a client which has the main functions and user interaction part which you should implement on the Server/Performer side
  • A build.gradle file which can run the base Client and Server

You can have any package structure you want and add new classes etc. Important, you should only have 1 Readme and 1 build.gradle file for all 3 tasks. Each task (server) can be started separately and work only with the features described. You can make any changes in the given files you like, BUT the protocol should work as described in the code.

Task 1: Make Performer more interesting

Presently, all the Performer does is add strings to an array. Make it more interesting by implementing the following protocol:

  • add < string > - adds a string to the list (presently what it does by default now) and displays the list (strings will be added to the end) - already implemented
  • remove < int > - removes an element at the given index and shows that element
  • display - displays the current list
  • count - returns the number of elements in your list and displays the number
  • reverse < int > - reverses the String at the given index and displays the new list

Task 2: Make the server multi-threaded

You can add new classes here of course. Task 1 should still run as is!

  • Name this server class "ThreadedServer"
  • Allow unbounded incoming connections to the server.
  • No client should block.
  • The shared state of the string list should be properly managed.

Task 3: Make the multi-threaded server bounded

You can add new classes here of course. Task 1 and 2 should still run as is.

  • Name this server class "ThreadPoolServer"
  • Only allow a set number of incoming connections at any given time. How many should be specified when calling the program through Gradle.
  • Name this server class "ThreadPoolServer".

Starter Codes

Client.java

package taskone;

import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Base64;
import java.util.Scanner;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import org.json.JSONObject;

/**
* Class: Client
* Description: Client tasks.
*/
public class Client {
private static BufferedReader stdin;

/**
* Function JSONObject add().
*/
public static JSONObject add() {
String strToSend = null;
JSONObject request = new JSONObject();
request.put("selected", 1);
try {
System.out.print("Please input the string: ");
strToSend = stdin.readLine();
} catch (IOException e) {
e.printStackTrace();
}
request.put("data", strToSend);
return request;
}

/**
* Function JSONObject remove().
*/
public static JSONObject pop() {
String strToSend = null;
int inNum;
JSONObject request = new JSONObject();
request.put("selected", 2);
return request;
}

/**
* Function JSONObject display().
*/
public static JSONObject display() {
JSONObject request = new JSONObject();
request.put("selected", 3);
request.put("data", "");
return request;
}

/**
* Function JSONObject count().
*/
public static JSONObject count() {
JSONObject request = new JSONObject();
request.put("selected", 4);
request.put("data", "");
return request;
}

/**
* Function JSONObject reverse().
*/
public static JSONObject switching() {
String strToSend = null;
int inNum;
JSONObject request = new JSONObject();
request.put("selected", 5);
int numInput = 0;
int first = 0;
int last = 0;
while (true) {
System.out.print("Please input the index: ");
try {
strToSend = stdin.readLine();
} catch (IOException e) {
e.printStackTrace();
}
try {
inNum = Integer.parseInt(strToSend);
if (numInput == 0) {
first = inNum;
numInput = 1;
} else {
last = inNum;
break;
}

} catch (NumberFormatException ne) {
System.out.println("Input is not number, continue");
}
}
request.put("data", first+ " " + last);
return request;
}

/**
* Function JSONObject quit().
*/
public static JSONObject quit() {
JSONObject request = new JSONObject();
request.put("selected", 0);
request.put("data", ".");
return request;
}

/**
* Function main().
*/
public static void main(String[] args) throws IOException {
String host;
int port;
Socket sock;
stdin = new BufferedReader(new InputStreamReader(System.in));
try {
if (args.length != 2) {
// gradle runClient -Phost=localhost -Pport=9099 -q --console=plain
System.out.println("Usage: gradle runClient -Phost=localhost -Pport=9099");
System.exit(0);
}

host = args[0];
port = -1;
try {
port = Integer.parseInt(args[1]);
} catch (NumberFormatException nfe) {
System.out.println("[Port] must be an integer");
System.exit(2);
}

sock = new Socket(host, port);
OutputStream out = sock.getOutputStream();
InputStream in = sock.getInputStream();
Scanner input = new Scanner(System.in);
int choice;
do {
System.out.println();
// TODO: you will need to change the menu based on the tasks for this assignment, see Readme!
System.out.println("Client Menu");
System.out.println("Please select a valid option (1-5). 0 to diconnect the client");
System.out.println("1. add < string > - adds a string to the list and display it");
System.out.println("2. pop - remove the top elemnt");
System.out.println("3. display - display the list");
System.out.println("4. count - returns the elements in the list");
System.out.println("5. switch < int > < int > - switch two string");
System.out.println("0. quit");
System.out.println();
choice = input.nextInt(); // what if not int.. should error handle this
JSONObject request = null;
switch (choice) {
case (1):
request = add();
break;
case (2):
request = pop();
break;
case (3):
request = display();
break;
case (4):
request = count();
break;
case (5):
request = switching();
break;
case (0):
request = quit();
break;
default:
System.out.println("Please select a valid option (0-6).");
break;
}
if (request != null) {
System.out.println(request);
NetworkUtils.send(out, JsonUtils.toByteArray(request));
byte[] responseBytes = NetworkUtils.receive(in);
JSONObject response = JsonUtils.fromByteArray(responseBytes);

if (response.has("error")) {
System.out.println(response.getString("error"));
} else {
System.out.println();
System.out.println("The response from the server: ");
System.out.println("datatype: " + response.getString("type"));
System.out.println("data: " + response.getString("data"));
System.out.println();
String typeStr = (String) response.getString("type");
if (typeStr.equals("quit")) {
sock.close();
out.close();
in.close();
System.exit(0);
}
}
}
} while (true);
} catch (IOException e) {
e.printStackTrace();
}
}
}

JsonUtils.java

package taskone;

import org.json.JSONObject;

/**
* Class: JsonUtils
* Description: Json Utilities.
*/
public class JsonUtils {
public static JSONObject fromByteArray(byte[] bytes) {
String jsonString = new String(bytes);
System.out.println(jsonString);
return new JSONObject(jsonString);
}

public static byte[] toByteArray(JSONObject object) {
return object.toString().getBytes();
}
}

NetworkUtils.java

package taskone;

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

/**
* Class: NetworkUtils
* Description: NetworkUtils for send/read/receive.
*/
public class NetworkUtils {
// https://mkyong.com/java/java-convert-byte-to-int-and-vice-versa/
public static byte[] intToBytes(final int data) {
return new byte[] { (byte) ((data >> 24) & 0xff), (byte) ((data >> 16) & 0xff),
(byte) ((data >> 8) & 0xff), (byte) ((data >> 0) & 0xff), };
}

// https://mkyong.com/java/java-convert-byte-to-int-and-vice-versa/
public static int bytesToInt(byte[] bytes) {
return ((bytes[0] & 0xFF) << 24) | ((bytes[1] & 0xFF) << 16) | ((bytes[2] & 0xFF) << 8)
| ((bytes[3] & 0xFF) << 0);
}

/**
* send the bytes on the stream.
*/
public static void send(OutputStream out, byte... bytes) throws IOException {
out.write(intToBytes(bytes.length));
out.write(bytes);
out.flush();
}

// read the bytes on the stream
private static byte[] read(InputStream in, int length) throws IOException {
byte[] bytes = new byte[length];
int bytesRead = 0;
try {
bytesRead = in.read(bytes, 0, length);
} catch (IOException e1) {
e1.printStackTrace();
}

if (bytesRead != length) {
return null;
}

return bytes;
}

// first 4 bytes we read give us the length of the message we are about to
// receive
// next we call read again with the length of the actual bytes in the data we
// are interested in
/**
* Receive the bytes on the stream.
*/
public static byte[] receive(InputStream in) throws IOException {
byte[] lengthBytes = read(in, 4);
if (lengthBytes == null) {
return new byte[0];
}
int length = NetworkUtils.bytesToInt(lengthBytes);
byte[] message = read(in, length);
if (message == null) {
return new byte[0];
}
return message;
}
}

Performer.java

package taskone;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import org.json.JSONObject;

/**
* Class: Performer
* Description: Threaded Performer for server tasks.
*/
class Performer {

private StringList state;
private Socket conn;

public Performer(Socket sock, StringList strings) {
this.conn = sock;
this.state = strings;
}

public JSONObject add(String str) {
JSONObject json = new JSONObject();
json.put("datatype", 1);
json.put("type", "add");
state.add(str);
json.put("data", state.toString());
return json;
}

public static JSONObject error(String err) {
JSONObject json = new JSONObject();
json.put("error", err);
return json;
}

public void doPerform() {
boolean quit = false;
OutputStream out = null;
InputStream in = null;
try {
out = conn.getOutputStream();
in = conn.getInputStream();
System.out.println("Server connected to client:");
while (!quit) {
byte[] messageBytes = NetworkUtils.receive(in);
JSONObject message = JsonUtils.fromByteArray(messageBytes);
JSONObject returnMessage = new JSONObject();

int choice = message.getInt("selected");
switch (choice) {
case (1):
String inStr = (String) message.get("data");
returnMessage = add(inStr);
break;
default:
returnMessage = error("Invalid selection: " + choice
+ " is not an option");
break;
}
// we are converting the JSON object we have to a byte[]
byte[] output = JsonUtils.toByteArray(returnMessage);
NetworkUtils.send(out, output);
}
// close the resource
System.out.println("close the resources of client ");
out.close();
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

Server.java

package taskone;

import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import org.json.JSONObject;

/**
* Class: Server
* Description: Server tasks.
*/
class Server {

public static void main(String[] args) throws Exception {
int port;
StringList strings = new StringList();

if (args.length != 1) {
// gradle runServer -Pport=9099 -q --console=plain
System.out.println("Usage: gradle runServer -Pport=9099 -q --console=plain");
System.exit(1);
}
port = -1;
try {
port = Integer.parseInt(args[0]);
} catch (NumberFormatException nfe) {
System.out.println("[Port] must be an integer");
System.exit(2);
}
ServerSocket server = new ServerSocket(port);
System.out.println("Server Started...");
while (true) {
System.out.println("Accepting a Request...");
Socket sock = server.accept();

Performer performer = new Performer(sock, strings);
performer.doPerform();
try {
System.out.println("close socket of client ");
sock.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}

StringList.java

package taskone;

import java.util.List;
import java.util.ArrayList;

class StringList {

List strings = new ArrayList();

public void add(String str) {
int pos = strings.indexOf(str);
if (pos < 0) {
strings.add(str);
}
}

public boolean contains(String str) {
return strings.indexOf(str) >= 0;
}

public int size() {
return strings.size();
}

public String toString() {
return strings.toString();
}
}
Academic Honesty!
It is not our intention to break the school's academic policy. Posted solutions are meant to be used as a reference and should not be submitted as is. We are not held liable for any misuse of the solutions. Please see the frequently asked questions page for further questions and inquiries.
Kindly complete the form. Please provide a valid email address and we will get back to you within 24 hours. Payment is through PayPal, Buy me a Coffee or Cryptocurrency. We are a nonprofit organization however we need funds to keep this organization operating and to be able to complete our research and development projects.