Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions Multithreaded/Client.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,43 @@

public class Client {

//the client is desinged to test the concurrent capabilities of the serer by
//simulating manny clinet connecctions at once.
//it creates a new thread for each client and sends a message to the server.
//it then reads the response from the server and prints it to the console.
//it does this for 100 clients.
//the server is designed to handle multiple clients concurrently.

public Runnable getRunnable() throws UnknownHostException, IOException {
//runnable is a functional interface that takes no arguments and returns nothing
return new Runnable() {
@Override
//override is used to override the run method of the runnable interface
public void run() {
int port = 8010;
//port is the port number of the server
try {
//InetAddress is used to get the IP address of the server locally
InetAddress address = InetAddress.getByName("localhost");
//Socket is used to connect to the server
Socket socket = new Socket(address, port);
//PrintWriter is used to send data to the server
//true is used to flush the output stream
//socket.getOutputStream() returns the output stream of the server
try (
//print writer in this try block is used to send data to the server
PrintWriter toSocket = new PrintWriter(socket.getOutputStream(), true);
//buffered reader here is used to read data from the server
//new InputStreamReader(socket.getInputStream()) returns the input stream of the server
BufferedReader fromSocket = new BufferedReader(new InputStreamReader(socket.getInputStream()))
) {

toSocket.println("Hello from Client " + socket.getLocalSocketAddress());
String line = fromSocket.readLine();
//readLine is used to read data from the server
//fromSocket.readLine() returns the data from the server
System.out.println("Response from Server " + line);
//println is used to print the response from the server
} catch (IOException e) {
e.printStackTrace();
}
Expand All @@ -38,8 +60,14 @@ public void run() {

public static void main(String[] args){
Client client = new Client();
//client is the object of the client class
//for loop is used to create 100 threads
for(int i=0; i<100; i++){
try{
//thread is the object of the thread class
//new Thread(client.getRunnable()) is used to create a new thread
//client.getRunnable() is used to get the runnable object of the client class

Thread thread = new Thread(client.getRunnable());
thread.start();
}catch(Exception ex){
Expand Down
29 changes: 27 additions & 2 deletions Multithreaded/Server.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,18 @@
import java.util.function.Consumer;

public class Server {

//we will handle each client in a separate thread
public Consumer<Socket> getConsumer() {
//Consumer is a functional interface that takes a single argument and returns nothing
//clientSocket is the socket of the client
//PrintWriter is used to send data to the client
//true is used to flush the output stream
//clientSocket.getOutputStream() returns the output stream of the client
return (clientSocket) -> {
//lambda expression is used to create a new thread for each client
try (PrintWriter toSocket = new PrintWriter(clientSocket.getOutputStream(), true)) {
//clientSocket.getInetAddress() returns the IP address of the client
toSocket.println("Hello from server " + clientSocket.getInetAddress());
} catch (IOException ex) {
ex.printStackTrace();
Expand All @@ -23,12 +32,28 @@ public static void main(String[] args) {
ServerSocket serverSocket = new ServerSocket(port);
serverSocket.setSoTimeout(70000);
System.out.println("Server is listening on port " + port);
//infinite loop to accept connections
while (true) {
//accepting a connection from the client
Socket clientSocket = serverSocket.accept();

// Create and start a new thread for each client
//serverSocket.accept() is a blocking call.
//the programs execution pauses here until a client connects.
//when a connection is made, it returns a socket objeect.
//this socket object is then passed to the getConsumer() method.


//creating a new thread for each client
//getConsumer() is a method that returns a Consumer<Socket>
//accept(clientSocket) is a method that accepts a Socket
//server.getConsumer().accept(clientSocket) is a method that accepts a Socket
//server.getConsumer() is a method that returns a Consumer<Socket>
//server.getConsumer().accept(clientSocket) is a method that accepts a Socket
Thread thread = new Thread(() -> server.getConsumer().accept(clientSocket));
thread.start();

//every time a client connects, this upper code will start a new thread.
//the actual contet of the code that is to be run is ddefined in the getConsumer() method.
//where it handles the client handling logic.
}
} catch (IOException ex) {
ex.printStackTrace();
Expand Down
20 changes: 19 additions & 1 deletion SingleThreaded/Client.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,34 @@
public class Client {

public void run() throws UnknownHostException, IOException{
int port = 8090;
int port = 8010;
InetAddress address = InetAddress.getByName("localhost");
//InetAddress is used to get the IP address of the server locally
//getByName("localhost") returns the IP address of the server locally
//port is the port number of the server
Socket socket = new Socket(address, port);
//Socket is used to connect to the server
//address is the IP address of the server
PrintWriter toSocket = new PrintWriter(socket.getOutputStream(), true);
//print writer is used to send data to the server
//true is used to flush the output stream
//by flushing the output stream, the data is sent to the server
//socket.getOutputStream() returns the output stream of the server
BufferedReader fromSocket = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//buffered reader is used to read data from the server
//new InputStreamReader(socket.getInputStream()) returns the input stream of the server
toSocket.println("Hello World from socket "+socket.getLocalSocketAddress());
//println is used to send data to the server
//socket.getLocalSocketAddress() returns the local socket address of the client
String line = fromSocket.readLine();
//readLine is used to read data from the server
//fromSocket.readLine() returns the data from the server
toSocket.close();
//close is used to close the output stream
fromSocket.close();
//close is used to close the input stream
socket.close();
//close is used to close the socket
}

public static void main(String[] args) {
Expand Down
43 changes: 43 additions & 0 deletions SingleThreaded/Server.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,60 @@

public class Server {

//This model is simple to understand but impractical for real-world use.
//If one client connects and the server is doing some time-consuming work, all other clients have to wait.
//This is because the server is single-threaded and can only handle one client at a time.
//This is not efficient and is not scalable.
//This is why we need to use a multi-threaded server.
//A multi-threaded server can handle multiple clients at the same time.
//This is more efficient and is scalable.
//This is why we need to use a multi-threaded server.

public void run() throws IOException, UnknownHostException{
//we are throwing an exception because we are not handling it
//we are not handling the exception because we are not using a try-with-resources block

//IOE exception is thrown when there is an error in the input/output stream
//UnknownHostException is thrown when the host is not found

//listening on port 8010
int port = 8010;

//creating a server socket
ServerSocket socket = new ServerSocket(port);

//setting a timeout of 20 seconds
socket.setSoTimeout(20000);

//infinite loop to accept connections
while(true){
//printing the port number
System.out.println("Server is listening on port: "+port);

//accepting a connection from the client
Socket acceptedConnection = socket.accept();

//printing the remote socket address
System.out.println("Connected to "+acceptedConnection.getRemoteSocketAddress());
//getRemoteSocketAddress() returns the remote socket address of the client
//getInetAddress() returns the local socket address of the server
//getPort() returns the port number of the client
//getAddress() returns the IP address of the client
//getPort() returns the port number of the server
//getAddress() returns the IP address of the server
//getInetAddress() returns the local socket address of the server

//creating a print writer to send data to the client
PrintWriter toClient = new PrintWriter(acceptedConnection.getOutputStream(), true);
//print writer is used to send data to the client
//true is used to flush the output stream
//acceptedConnection.getOutputStream() returns the output stream of the client
//acceptedConnection.getInputStream() returns the input stream of the client
BufferedReader fromClient = new BufferedReader(new InputStreamReader(acceptedConnection.getInputStream()));
//buffered reader is used to read data from the client
//new InputStreamReader(acceptedConnection.getInputStream()) returns the input stream of the client
toClient.println("Hello World from the server");
//print writer is used to send data to the client
}
}

Expand Down
32 changes: 31 additions & 1 deletion ThreadPool/Server.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,37 @@
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


//the thread pool method is a significant improvement over the
//client-server model, yes even the multithreaded model.
//creating a new thread for each client is expensive in computational terms.

//even tho, thread pool is single threaded it provides better efficiency.
// it maintains a fixed number of worker threads. These threads are reused to handle incoming client requests.
//this reduces the overhead of creating and destroying threads.
//this also reduces the overhead of context switching.
//this also reduces the overhead of synchronization.
//this also reduces the overhead of memory allocation.
//this also reduces the overhead of memory deallocation.
//this also reduces the overhead of memory allocation.

public class Server {
private final ExecutorService threadPool;

//The ExecutorService is a high-level API from Java's concurrency framework for managing threads.
//it provides a pool of threads that can be reused to handle incoming client requests.
public Server(int poolSize) {
this.threadPool = Executors.newFixedThreadPool(poolSize);

// initializes the pool. Executors.newFixedThreadPool(10) creates a pool with exactly 10 worker threads.
// These 10 threads will be created once and then reused for the lifetime of the server.
}

public void handleClient(Socket clientSocket) {


//This method contains the logic for how to interact with a connected client. In this case, it's very simple
// it opens a PrintWriter, sends a "Hello" message, and the try-with-resources block ensures the writer (and underlying socket stream) is closed automatically.
//Crucially, this method does not create a thread. It's just a plain method that will be executed by a thread from the pool.
try (PrintWriter toSocket = new PrintWriter(clientSocket.getOutputStream(), true)) {
toSocket.println("Hello from server " + clientSocket.getInetAddress());
} catch (IOException ex) {
Expand All @@ -30,6 +53,13 @@ public static void main(String[] args) {
serverSocket.setSoTimeout(70000);
System.out.println("Server is listening on port " + port);


// This is the most important part. When a client connects (serverSocket.accept()), the server executes this line:
// server.threadPool.execute(() -> server.handleClient(clientSocket));
// execute() is a method of the ExecutorService. It takes a Runnable task and assigns it to one of the available threads in the pool.
// If all 10 threads are busy handling other clients, the new task is placed in a queue. As soon as a thread finishes its current task, it will pick up the next task from the queue.
// The main server loop does not wait. It immediately loops back to accept() to listen for the next client. This ensures the server remains highly responsive.

while (true) {
Socket clientSocket = serverSocket.accept();

Expand Down