Threads, sockets, and communication – writing our own server

Author Author:
Innokrea Team
Date of publication: 2024-06-06
Caterogies: Administration Programming

Hey, today as Innokrea we want to talk to you about interprocess communication, sockets, and threads. If you’re curious about how to implement your own server based on threads, we invite you to read on!

 

Client-server architecture

In today’s IT world, the most popular architecture among people (though not dominant) in systems is the client-server, where client stations send a request for a specific resource to a server that holds the information. This model is also called the request-response model, and an example of its use is the popular REST API. Other architectures that may be used even more frequently are often decentralized architectures (without a central server with resources). The internet, as a network composed of billions of devices, is an example of such a solution. Other architectures that can be mentioned in this context include peer-to-peer (Bittorrent), microservices, or the public-subscribe model used in event-driven architecture. Complex systems often utilize multiple paradigms and associated architectures.

 

Client-server architecture

Figure 1 – Client-Server Architecture [1]

 

Sockets

What are sockets? It is one of the most basic ways of communication in computer technologies. The process of establishing communication involves creating a bidirectional connection through a computer network. Each of the endpoints is called a socket. The entire process requires reserving a port and an IP address, which means that communication utilizes both the transport layer and the network layer of the OSI model.

 

Socket API

Drawing 2 – Socket API, source [2]

 

In the above drawing, we can observe the successive methods that each socket must go through to change the state of its object and transmit data. For example, the bind method is responsible for assigning the appropriate port, and connect is for making a connection request to the second machine using sockets.

 

Example of a simple server

Let’s try to write a simple server using the Java language and the IntelliJ environment. The server will operate based on sockets and will use a single thread. This means that it will only process a single request from a single user at a time. Such a server is called iterative. To create such a server, we will use the ServerSocket class from the java.net library. Our program will be tasked with accepting two integers and then returning their sum.

package singleThreaded.Server;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class TCPServer {
   Integer portNumber;
   ServerSocket serverSocket;

   public  TCPServer(Integer portNumber) throws IOException {
       this.portNumber = portNumber;
       serverSocket = new ServerSocket(portNumber);
   }

   public void start() throws IOException {
       System.out.println("SERVER HAS BEEN STARTED \n");
       while (true){
           Socket clientSocket = serverSocket.accept();
           DataInputStream inputClientStream = new DataInputStream(clientSocket.getInputStream());
         
           int firstNumber = inputClientStream.readInt();
           int secondNumber = inputClientStream.readInt();
           int sum = firstNumber + secondNumber;

           System.out.println(firstNumber);
           System.out.println(secondNumber);

           // Send sum back to client
           DataOutputStream outputClientStream = new DataOutputStream(clientSocket.getOutputStream());
           outputClientStream.writeInt(sum);

           // Close client socket
           clientSocket.close();
       }
   }
}

 

package singleThreaded.Server;
import java.util.Scanner;

public class Program {
   public static void main(String[] args) {
       System.out.println("SINGLE-THREADER SERVER PROGRAM");

       Scanner scanner = new Scanner(System.in);
       System.out.println("Enter server port");
       Integer portNumber = Integer.valueOf(scanner.nextLine());
       try{
           TCPServer server = new TCPServer(portNumber);
           server.start();
       }
       catch (Exception e){
           System.out.println(e.getMessage());
       }
   }
}

 

To create a client, we use the Socket class, also from the java.net library, and we will ask the user which two numbers they want to send for the server’s operation.

package singleThreaded.Client;
import singleThreaded.Server.TCPServer;
import java.io.*;
import java.net.Socket;

public class TCPClient {
   String ipAddress;
   Integer portNumber;
   Socket clientSocket;

   public TCPClient(String ipAddress, Integer portNumber) throws IOException {
       this.ipAddress = ipAddress;
       this.portNumber = portNumber;
       this.clientSocket = new Socket(ipAddress, portNumber);
   }

   public void computeSum(Integer firstNumber, Integer secondNumber) throws IOException {
       System.out.println("CLIENT HAS BEEN STARTED\n");
       DataOutputStream outputStreamToServer = new DataOutputStream(clientSocket.getOutputStream());

       outputStreamToServer.writeInt(firstNumber);
       outputStreamToServer.writeInt(secondNumber);
       outputStreamToServer.flush();
       DataInputStream inputStreamFromServer = new DataInputStream(clientSocket.getInputStream());

       int sumFromServerString = inputStreamFromServer.readInt();
       System.out.println("SUM FROM SERVER : " + sumFromServerString);

       clientSocket.close();
   }
}

 

package singleThreaded.Client;
import java.util.Arrays;
import java.util.Scanner;

public class Program {
   public static void main(String[] args) {
       System.out.println("CLIENT PROGRAM");
       Scanner scanner = new Scanner(System.in);  // Create a Scanner object
       System.out.println("Enter server port");
       Integer portNumber = Integer.valueOf(scanner.nextLine().trim());
       System.out.println("Enter ip address");
       String ipAddress = scanner.nextLine().trim();
       System.out.println("Enter first number");
       Integer firstNumber = Integer.valueOf(scanner.nextLine().trim());
       System.out.println("Enter second number");
       Integer secondNumber = Integer.valueOf(scanner.nextLine().trim());
       try{
           TCPClient client = new TCPClient(ipAddress,portNumber);
           client.computeSum(firstNumber,secondNumber);
       }
       catch (Exception e){
           System.out.println(Arrays.toString(e.getStackTrace()));
           System.out.println(e.toString());
       }
   }
}

 

However, there is a significant problem with the above server code. Every time a client connects to the server, it occupies its only main thread, which is busy performing operations, meaning that another client cannot connect. To prevent this, it is necessary to handle each client’s request on a separate thread when they connect.

 

What is a thread?

A thread is a part of code in our program executed concurrently. If we want code to be executed at the same time, and the task it needs to perform can be handled simultaneously, we can implement this programmatically in Java using the Runnable interface or the Thread class. It’s also worth mentioning that threads can easily cooperate through shared memory space, unlike processes. The preferred way to implement multithreading is by using the Runnable interface. You can read more about basic examples of multithreading in Java here:

https://www.geeksforgeeks.org/runnable-interface-in-java/

 

Multithreaded solution – socket server

To implement the functionality of handling clients on multiple threads within the server, we will create a class called ClientHandler.

package multiThreaded.Server;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
class ClientHandler implements Runnable {
   private final Socket clientSocket;
   public ClientHandler(Socket clientSocket) {
       this.clientSocket = clientSocket;
   }

   @Override
   public void run() {
       try {
           DataInputStream inFromClient = new DataInputStream(clientSocket.getInputStream());

           // Read numbers sent from client
           int firstNumber = inFromClient.readInt();
           int secondNumber = inFromClient.readInt();
           int sum = firstNumber + secondNumber;

           // Send response back to client
           DataOutputStream outToClient = new DataOutputStream(clientSocket.getOutputStream());
           outToClient.writeInt(sum);

           // Close client socket
           clientSocket.close();
           System.out.println("Closing the connection for clientSocket port " + clientSocket.getPort());
       } catch (IOException e) {
           System.out.println("Error handling client connection: " + e.getMessage());
       }
   }
}

 

Meanwhile, we will modify the TCPServer class from the previous example as follows.

package multiThreaded.Server;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class TCPServer {
   Integer portNumber;
   ServerSocket serverSocket;

   public  TCPServer(Integer portNumber) throws IOException {
       this.portNumber = portNumber;
       serverSocket = new ServerSocket(portNumber);
   }

   public void start() throws IOException {
       System.out.println("Server Started \n");
       while (true){
           Socket clientSocket = serverSocket.accept();
           System.out.println("Starting new Thread for " + clientSocket.getPort());
           Thread thread = new Thread(new ClientHandler(clientSocket));
           thread.start();
       }
   }
}

 

Let’s pay attention to creating a new thread every time a connection is established (the accept method corresponds to the method in diagram 2) and passing a new ClientHandler object along with the accepted client connection (clientSocket) to it.

Thanks to this approach, we are able to handle multiple clients and process many of their requests simultaneously. To run the programs, remember to change the package name accordingly, and note that when running the server and client, you still need the code from the Program classes in the single-threaded solution.

 

Summary

Today, we learned about threads, sockets, and how to create a simple system operating in a client-server architecture. If you found this article interesting, we also recommend checking out the rest of the articles on our blog, where we discuss topics ranging from programming to cybersecurity and networking.

 

Sources:

[1] https://darvishdarab.github.io/cs421_f20/docs/readings/client_server/

[2] https://www.javatpoint.com/socket-programming

See more on our blog:

Design Patterns

Design Patterns

Programmer, this article is for you! Grab a handful of useful information about design patterns.

Programming

Blockchain – Payments in the World of Cryptocurrencies

Blockchain – Payments in the World of Cryptocurrencies

Blockchain - discover the world of transactions, cryptocurrencies, and electronic payments.

FinancialSecurity

FastAPI – How to Build a Simple REST API in Python? – Part 3

FastAPI – How to Build a Simple REST API in Python? – Part 3

REST API using FastAPI framework. The last part of articles about API in Python. Start your FastAPI adventure with us today!

Programming