Distributed Computing Manual
Distributed Computing Manual
Aim- To implement Inter-Process Communication between client and server using Python
socket programming.
Sockets Mechanism
The Sockets are the End Points of Communication between two machines. They provide a way
for processes to communicate with each other, either on the same on machine or over the
Internet also possible. The Sockets enable the communication connection between Server and
the client to transfer data in a bidirectional way.
Program:
Client-
import socket
# Server details
host = '[Link]'
port = 12345
# Connect to server
client_socket.connect((host, port))
print("Connected to server!")
while True:
# Send message to server
message = input("Client: ")
client_socket.send([Link]())
if [Link]() == 'exit':
print("Connection closed.")
break
if [Link]() == 'exit':
print("Server ended the connection.")
break
# Close socket
client_socket.close()
Server-
import socket
# Create a socket object
server_socket = [Link](socket.AF_INET, socket.SOCK_STREAM)
# Define host and port
host = '[Link]' # Localhost
port = 12345
# Bind the socket
server_socket.bind((host, port))
# Listen for connections
server_socket.listen(5)
print("Server is waiting for connection...")
# Accept connection
conn, addr = server_socket.accept()
print(f"Connected to client: {addr}")
while True:
# Receive data from client
data = [Link](1024).decode()
if not data:
break
print(f"Client: {data}")
# Exit condition
if [Link]() == 'exit':
print("Connection closed by client.")
break
# Send response to client
message = input("Server: ")
[Link]([Link]())
if [Link]() == 'exit':
print("Connection closed by server.")
break
# Close connection
[Link]()
server_socket.close()
Result:
Experiment No - 2
Aim: To implement Remote Procedure / RMI Call between client and server.
Theory: Remote Procedure Call (RPC) is a protocol that allows a program to execute a
procedure on another machine without knowing network details. Python provides an XMLRPC
library that allows functions to be called remotely over [Link] simplifies distributed
programming by making remote calls appear like local function calls.
Program:
Server-
import socket
import json
# Define functions that can be called remotely
def add(a, b):
return a + b
def subtract(a, b):
return a - b
def multiply(a, b):
return a * b
def divide(a, b):
if b == 0:
return "Error: Division by zero"
return a / b
# Function mapping
functions = {
"add": add,
"subtract": subtract,
"multiply": multiply,
"divide": divide
}
# Create socket
server = [Link](socket.AF_INET, socket.SOCK_STREAM)
[Link](('[Link]', 5000))
[Link](5)
print("RPC Server is running...")
while True:
conn, addr = [Link]()
print(f"Connected to {addr}")
data = [Link](1024).decode()
request = [Link](data)
func_name = request["function"]
args = request["args"]
[Link]()
Client-
import socket
import json
def call_remote_function(function_name, args):
client = [Link](socket.AF_INET, socket.SOCK_STREAM)
[Link](('[Link]', 5000))
# Create request
request = {
"function": function_name,
"args": args
}
[Link]([Link](request).encode())
# Receive response
response = [Link](1024).decode()
result = [Link](response)
[Link]()
return result["result"]
# User interaction
while True:
print("\nAvailable functions: add, subtract, multiply, divide")
func = input("Enter function name (or 'exit'): ")
if [Link]() == 'exit':
break
a = float(input("Enter first number: "))
b = float(input("Enter second number: "))
result = call_remote_function(func, [a, b])
print("Result from server:", result)
Result:
Working of RMI
The communication between client and server is handled by using two intermediate objects:
Stub object (on client side) and Skeleton object (on server-side) as also can be depicted
from below media as follows:
These are the steps to be followed sequentially to implement Interface as defined below
as follows:
/* =======================
Remote Interface (RPC)
======================= */
interface HelloService extends Remote {
String sayHello(String name) throws RemoteException;
}
/* =======================
Remote Implementation
======================= */
class HelloServiceImpl extends UnicastRemoteObject
implements HelloService {
@Override
public String sayHello(String name) throws RemoteException {
return "Hello " + name + ", this message is from RMI Server!";
}
}
/* =======================
Main Class (Server + Client)
====================== */
public class RMIRPC {
if (args[0].equalsIgnoreCase("server")) {
startServer();
} else if (args[0].equalsIgnoreCase("client")) {
startClient();
}
}
}
Result:
Experiment No -3
Aim: To implement group communication using multicast.
Theory: Group communication allows one sender to transmit messages to multiple receivers
simultaneously. Multicast networking enables efficient communication with multiple nodes
without sending individual messages.
Types of Group Communication in a Distributed System
3. Broadcast Communication
• Broadcast is when a sender sends a message to every node in the network without
targeting specific recipients.
• Messages are delivered to all nodes at once using a special address designed for this
purpose.
• It’s often used for network management tasks, like sending status updates, or for
emergency alerts that need to reach everyone quickly.
• Broadcast ensures that every node receives the message without needing to specify
who the recipients are, making it efficient for sharing information widely.
• It can cause network congestion in larger networks and raises security concerns since
anyone on the network can access the broadcast message, which might lead to
unauthorized access.
Algorithm:
1. Create a sender program.
2. Define multicast IP address and port.
3. Send a message to the multicast group.
4. Create a receiver program.
5. Join a multicast group.
6. Receive and display messages.
Program:
Sender-
import socket
import time
MCAST_GRP = '[Link]'
MCAST_PORT = 5007
# Create UDP socket
sock = [Link](socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
while True:
message = input("Enter message to send (or 'exit'): ")
if [Link]() == 'exit':
break
Result:
Experiment 4
Clock Synchronization (Cristian’s Algorithm Simulation)
Aim: To simulate clock synchronization in distributed systems.
Theory: In distributed systems, each node has its own clock which may differ from others.
Clock synchronization ensures all systems maintain consistent time. Cristian’s algorithm
synchronizes a client clock with a time server.
Server-
import socket
import time
HOST = '[Link]'
PORT = 5000
server_socket = [Link](socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind((HOST, PORT))
server_socket.listen(5)
print("Time Server is running...")
while True:
conn, addr = server_socket.accept()
print(f"Connected by {addr}")
# Receive request
data = [Link](1024)
if not data:
break
# Get server time
server_time = [Link]()
# Send server time
[Link](str(server_time).encode())
[Link]()
Client-
import socket
import time
HOST = '[Link]'
PORT = 5000
client_socket = [Link](socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect((HOST, PORT))
client_socket.send(b"Request Time")
Ts = float([Link]())
client_socket.close()
Result:
Experiment 5
Aim: To simulate leader election using Bully Algorithm.
Theory: Election algorithms are used to choose a coordinator among distributed processes
when the existing coordinator fails. In the Bully algorithm, the process with the highest ID
becomes the new coordinator.
In distributed systems, leader election algorithms are used to select a coordinator among
multiple processes when the current coordinator fails. The Ring Algorithm is a distributed
election algorithm where processes are arranged in a logical ring structure.
In this algorithm, each process has a unique ID and knows the ID of its neighbor in the ring.
When a process detects that the coordinator has failed, it initiates an election message
containing its own ID and passes it to the next process in the ring.
Each process that receives the election message compares its own ID with the ID in the
message. If its ID is higher, it replaces the ID in the message with its own and forwards it to
the next process. This process continues until the message returns to the initiator.
The process with the highest ID becomes the new coordinator and announces itself to all
other processes in the ring.
Algorithm:
1. Define a list of processes with unique IDs.
2. Arrange the processes logically in a ring structure.
3. Select a process to initiate the election.
4. Pass the election message to the next process in the ring.
5. Each process compares its ID with the received ID.
6. The highest ID is selected as the coordinator.
7. Display the elected coordinator.
Program:
import time
class Process:
def __init__(self, pid):
[Link] = pid
[Link] = True
class BullyAlgorithm:
def __init__(self, num_processes):
[Link] = [Process(i) for i in range(1, num_processes + 1)]
[Link] = max([Link], key=lambda p: [Link])
def display_processes(self):
print("\nProcess Status:")
for p in [Link]:
status = "Alive" if [Link] else "Down"
print(f"Process {[Link]}: {status}")
print(f"Current Coordinator: P{[Link]}")
def crash_process(self, pid):
for p in [Link]:
if [Link] == pid:
[Link] = False
print(f"\n Process {pid} crashed!")
if [Link] == pid:
print("Coordinator crashed! Starting election...")
self.start_election(self.get_highest_alive())
return
def get_highest_alive(self):
alive = [p for p in [Link] if [Link]]
return max(alive, key=lambda p: [Link])
def start_election(self, initiator):
print(f"\n Process {[Link]} starts election")
higher_processes = [
p for p in [Link] if [Link] > [Link] and [Link]
]
if not higher_processes:
[Link] = initiator
print(f"\n Process {[Link]} becomes the NEW COORDINATOR")
return
for p in higher_processes:
print(f"Process {[Link]} → ELECTION → Process {[Link]}")
[Link](1)
# Higher process responds and takes over
next_initiator = max(higher_processes, key=lambda p: [Link])
print(f"\nProcess {next_initiator.pid} responds OK")
self.start_election(next_initiator)
def recover_process(self, pid):
for p in [Link]:
if [Link] == pid:
[Link] = True
print(f"\n Process {pid} recovered!")
print("Recovered process starts election...")
self.start_election(p)
return
# ---- Simulation ----
if __name__ == "__main__":
system = BullyAlgorithm(5)
system.display_processes()
# Crash current leader
system.crash_process(5)
[Link](2)
system.display_processes()
# Recover highest process
system.recover_process(5)
[Link](2)
system.display_processes()
Result:
Experiment 6
Aim: To implement mutual exclusion in distributed systems.
Theory: Mutual exclusion ensures that only one process can access a shared resource at a
time to prevent conflicts.
Mutual exclusion is a concurrency control property which is introduced to prevent race
conditions. It is the requirement that a process can’t enter its critical section while another
concurrent process is currently present or executing in its critical section i.e only one process
is allowed to execute the critical section at any given instance of time.
Mutual exclusion in single computer system Vs. distributed system: In single computer
system, memory and other resources are shared between different processes. The status of
shared resources and the status of users is easily available in the shared memory so with the
help of shared variable (For example: Semaphores) mutual exclusion problem can be easily
solved. In Distributed systems, we neither have shared memory nor a common physical clock
and therefore we can’t solve mutual exclusion problem using shared variables. To eliminate
the mutual exclusion problem in distributed system approach based on message passing is
used. A site in distributed system do not have complete information of state of the system due
to lack of shared memory and a common physical clock.
Some points are need to be taken in consideration to understand mutual exclusion fully:
1) It is an issue/problem which frequently arises when concurrent access to shared
resources by several sites is involved. For example, directory management where
updates and reads to a directory must be done atomically to ensure correctness.
2) It is a fundamental issue in the design of distributed systems.
3) Mutual exclusion for a single computer is not applicable for the shared resources since
it involves resource distribution, transmission delays, and lack of global information.
Program:
import threading
import time
class Process:
def __init__(self, pid, total):
[Link] = pid
[Link] = 0
[Link] = False
[Link] = 0
[Link] = 0
[Link] = total
[Link] = []
def request_cs(self, processes):
[Link] += 1
[Link] = [Link]
[Link] = True
[Link] = 0
print(f"\nProcess {[Link]} requesting CS")
for p in processes:
if [Link] != [Link]:
p.receive_request(self, [Link])
Result:
Experiment 7
Aim: To detect deadlock using a wait-for graph.
Theory: Deadlock occurs when processes wait indefinitely for resources held by other
processes. A wait-for graph helps identify circular dependencies.
Deadlocks are a fundamental problem in distributed systems. A process may request
resources in any order and a process can request resources while holding others. A Deadlock
is a situation where a set of processes are blocked as each process in a Distributed system is
holding some resources and that acquired resources are needed by some other processes.
Example: If there are three processes p1,p2 and p1 are acquiring r1 resource and that r1 is
needed by p2 which is acquiring another resource r2 and that is needed by p1. Here cycle
occurs. It is called a deadlock.
Here in the above graph, we found a cycle from P1 to P2 and again to P1. So we can say that
the system is in a deadlock state. The Problem of deadlocks has been generally studied in
distributed systems under the following models:
• The system has only reusable resources.
• Processes are allowed only exclusive access to resources.
• There is only one copy of each resource.
Deadlock Detection
The deadlock Detection Algorithm is of two types:
• Wait-for-Graph Algorithm (Single Instance)
• Banker's Algorithm (Multiple Instance)
Wait-for-Graph Algorithm
It is a variant of the Resource Allocation graph. In this algorithm, we only have processes as
vertices in the graph. If the Wait-for-Graph contains a cycle then we can say the system is in
a Deadlock state. We need to remove resources while converting from Resource Allocation
Graph to Wait-for-Graph.
Algorithm:
Below are the steps to follow:
• Step 1: Take the first process (Pi) from the resource allocation graph and check the
path in which it is acquiring resource (Ri), and start a
• wait-for-graph with that particular process.
• Step 2: Make a path for the Wait-for-Graph in which there will be no Resource
included from the current process (Pi) to next process (Pj), from that next process (Pj)
find a resource (Rj) that will be acquired by next Process (Pk) which is released from
Process (Pj).
• Step 3: Repeat Step 2 for all the processes.
• Step 4: After completion of all processes, if we find a closed-loop cycle then the
system is in a deadlock state, and deadlock is detected.
Example 1:
Consider a Resource Allocation Graph with 4 Processes P1, P2, P3, P4, and 4 Resources R1,
R2, R3, R4. Find if there is a deadlock in the Graph using the Wait for Graph-based deadlock
detection algorithm.
Step 1
First take Process P1 which is waiting for Resource R1, resource R1 is acquired by Process
P2, Start a Wait-for-Graph for the above Resource Allocation Graph.
Step 2
Now we can observe that there is a path from P1 to P2 as P1 is waiting for R1 which is been
acquired by P2. Now the Graph would be after removing resource R1 looks like.
Step 3
From P2 we can observe a path from P2 to P3 as P2 is waiting for R4 which is acquired by
P3. So make a path from P2 to P3 after removing resource R4 looks like.
Step 4
From P3 we find a path to P4 as it is waiting for P3 which is acquired by P4. After removing
R3 the graph looks like this.
Step 5
Here we can find Process P4 is waiting for R2 which is acquired by P1. So finally, the Wait-
for-Graph is as follows:
Step 6
Note: Finally In this Graph, we found a cycle as the Process P4 again came back to the
Process P1 which is the starting point (i.e., it's a closed-loop). So, According to the
Algorithm if we found a closed loop, then the system is in a deadlock state. So here we can
say the system is in a deadlock state.
Example 2:
Now consider another Resource Allocation Graph with 4 Processes P1, P2, P3, P4, and 3
Resources R1, R2, R3. Find if there is a deadlock in the Graph using the Wait for Graph-
based deadlock detection algorithm.
Step 1
First take Process P1 which is waiting for Resource R1, resource R1 is acquired by Process
P2, Start a Wait-for-Graph for the above Resource Allocation Graph.
Step 2
Now we can observe that there is a path from P1 to P2 and also from P1 to P4 as P1 is
waiting for R1 which is been acquired by P2 and P1 is also waiting for R2 which is acquired
by P4. Now the Graph would be after removing resources R1 and R2 looks like.
Step 3
From P2 we can observe a path from P2 to P3 as P2 is waiting for R3 which is acquired by
P3. So make a path from P2 to P3 after removing resource R3 looks like.
Step 4
Here we can find Process P4 is waiting for R3 which is acquired by P3. So finally the Wait-
for-Graph looks like after removing Resource R3 looks like.
Step 5
In this Graph, we don't find a cycle as no process came back to the starting point (i.e., there is
no closed loop). So, According to the Algorithm if we found a closed loop, then the system is
in a deadlock state. But here we didn't find any closed loop so the system is not in a deadlock
state. The system is in a safe state.
Note: In Example 2, even though it looks like a loop but there is no process that has reached
the first process, or starting point again. So there is no closed loop.
Program:
class WaitForGraph:
def __init__(self, processes):
[Link] = {p: [] for p in processes}
def add_edge(self, p1, p2):
# p1 is waiting for p2
[Link][p1].append(p2)
def detect_deadlock(self):
visited = set()
rec_stack = set()
def dfs(node):
[Link](node)
rec_stack.add(node)
for neighbor in [Link][node]:
if neighbor not in visited:
if dfs(neighbor):
return True
elif neighbor in rec_stack:
return True
rec_stack.remove(node)
return False
for process in [Link]:
if process not in visited:
if dfs(process):
return True
return False
# ---- Simulation ----
if __name__ == "__main__":
processes = ["P1", "P2", "P3", "P4"]
wfg = WaitForGraph(processes)
print("\nWait-For Graph:")
for p in [Link]:
print(f"{p} → {[Link][p]}")
if wfg.detect_deadlock():
print("\n🔴 Deadlock Detected!")
else:
print("\n🟢 No Deadlock.")
Result:
Experiment 8
Aim: To simulate load balancing in distributed systems.
Theory:
Load balancing distributes workloads across multiple processors to improve system
performance and avoid overloading.
A load balancer is a device that acts as a reverse proxy and distributes network or application
traffic across a number of servers. Load adjusting is the approach to conveying load units
(i.e., occupations/assignments) across the organization which is associated with the
distributed system. Load adjusting should be possible by the load balancer. The load balancer
is a framework that can deal with the load and is utilized to disperse the assignments to the
servers. The load balancers allocates the primary undertaking to the main server and the
second assignment to the second server.
• Security: A load balancer provide safety to your site with practically no progressions
to your application.
• Protect applications from emerging threats: The Web Application Firewall (WAF)
in the load balancer shields your site.
• Authenticate User Access: The load balancer can demand a username and secret key
prior to conceding admittance to your site to safeguard against unapproved access.
• Protect against DDoS attacks: The load balancer can distinguish and drop conveyed
refusal of administration (DDoS) traffic before it gets to your site.
• Performance: Load balancers can decrease the load on your web servers and advance
traffic for a superior client experience.
• SSL Offload: Protecting traffic with SSL (Secure Sockets Layer) on the load balancer
eliminates the upward from web servers bringing about additional assets being
accessible for your web application.
• Traffic Compression: A load balancer can pack site traffic giving your clients a
vastly improved encounter with your site.
Load Balancing Approaches:
• Round Robin
• Least Connections
• Least Time
• Hash
• IP Hash
Following are a portion of the various classes of the load adjusting calculations.
• Static: In this model assuming any hub/node is found with a heavy load, an
assignment can be taken arbitrarily and move the undertaking to some other arbitrary
system.
• Dynamic: It involves the present status data for load adjusting. These are better
calculations than static calculations.
• Deterministic: These calculations utilize processor and cycle attributes to apportion
cycles to the hubs.
• Centralized: The framework states data is gathered by a single hub.
Migration:
Another important policy to be used by a distributed operating system that supports process
migration is to decide about the total number of times a process should be allowed to migrate.
Migration Models:
• Code section
• Resource section
• Execution section
def finish_task(self):
if [Link] > 0:
[Link] -= 1
class LoadBalancer:
def __init__(self, num_servers):
[Link] = [Server(i) for i in range(num_servers)]
[Link] = 0 # for round robin
# Round Robin
def round_robin(self, task):
server = [Link][[Link]]
server.assign_task(task)
[Link] = ([Link] + 1) % len([Link])
# Least Loaded
def least_loaded(self, task):
server = min([Link], key=lambda s: [Link])
server.assign_task(task)
# Random
def random_assign(self, task):
server = [Link]([Link])
server.assign_task(task)
def show_load(self):
print("\nCurrent Server Loads:")
for s in [Link]:
print(f"Server {[Link]}: {[Link]}")
# ---- Simulation ----
if __name__ == "__main__":
lb = LoadBalancer(3)
Result:
Experiment 9
Aim: To simulate distributed shared memory.
Theory: Distributed Shared Memory (DSM) allows multiple systems to access shared data as
if they share the same memory.
Distributed shared memory can be achieved via both software and hardware. Hardware
examples include cache coherence circuits and network interface controllers. In contrast,
software DSM systems implemented at the library or language level are not transparent and
developers usually have to program them differently.
2. Bus-Based Multiprocessors
• A set of parallel wires called a bus acts as a connection between CPU and memory.
• Accessing of same memory simultaneously by multiple CPUs is prevented by using
some algorithms.
• Cache memory is used to reduce network traffic.
3. Ring-Based Multiprocessors
• There is no global centralized memory present in Ring-based DSM.
• All nodes are connected via a token passing ring.
• In ring-bases DSM a single address line is divided into the shared area.
Program:
import threading
import time
class DSM:
def __init__(self, num_nodes):
[Link] = {"x": 0} # shared variable
[Link] = [Node(i, self) for i in range(num_nodes)]
def broadcast_update(self, key, value, source_id):
print(f"\n Broadcasting update: {key} = {value} from Node
{source_id}")
for node in [Link]:
if node.node_id != source_id:
node.update_local_copy(key, value)
class Node:
def __init__(self, node_id, dsm):
self.node_id = node_id
[Link] = dsm
self.local_memory = dict([Link])
[Link]("x")
new_value = node.node_id * 10
[Link]("x", new_value)
[Link](1)
[Link]("x")
if __name__ == "__main__":
dsm = DSM(3)
threads = []
for node in [Link]:
t = [Link](target=node_work, args=(node,))
[Link](t)
for t in threads:
[Link]()
for t in threads:
[Link]()
Result:
Experiment 10
Aim: Study of Distributed File System (AFS/CODA)
Theory: A distributed file system (DFS) is a networked architecture that allows multiple
users and applications to access and manage files across various machines as if they were on a
local storage device. Instead of storing data on a single server, a DFS spreads files across
multiple locations, enhancing redundancy and reliability.
• This setup not only improves performance by enabling parallel access but also
simplifies data sharing and collaboration among users.
• By abstracting the complexities of the underlying hardware, a distributed file system
provides a seamless experience for file operations, making it easier to manage large
volumes of data in a scalable manner.
Components of DFS
• Location Transparency: Location Transparency achieves through the namespace
component.
• Redundancy: Redundancy is done through a file replication component.
In the case of failure and heavy load, these components together improve data availability by
allowing the sharing of data in different locations to be logically grouped under one folder,
which is known as the "DFS root". It is not necessary to use both the two components of DFS
together, it is possible to use the namespace component without using the file replication
component and it is perfectly possible to use the file replication component without using the
namespace component between servers.
History of DFS
The server component of the Distributed File System was initially introduced as an add-on
feature. It was added to Windows NT 4.0 Server and was known as "DFS
4.1". Then later on it was included as a standard component for all editions of Windows 2000
Server. Client-side support has been included in Windows NT 4.0 and also in later on version
of Windows. Linux kernels 2.6.14 and versions after it come with an SMB client VFS
known as "cifs" which supports DFS. Mac OS X 10.7 (lion) and onwards supports Mac OS
X DFS.
Working of DFS
There are two ways in which DFS can be implemented:
• Standalone DFS namespace allows only for those DFS roots that exist on the local
computer and are not using Active Directory. A Standalone DFS can only be acquired
on those computers on which it is created. It does not provide any fault liberation and
cannot be linked to any other DFS. Standalone DFS roots are rarely come across
because of their limited advantage.
• Domain-based DFS namespace stores the configuration of DFS in Active Directory,
creating the DFS namespace root accessible at \\<domainname>\<dfsroot> or
\\<FQDN>\<dfsroot>
Fig: Distributed File System (DFS)
Applications of DFS:
• NFS: NFS stands for Network File System. It is a client-server architecture that
allows a computer user to view, store, and update files remotely. The protocol of NFS
is one of the several distributed file system standards for Network-Attached Storage
(NAS).
• CIFS stands for Common Internet File System. CIFS is an accent of SMB. That is,
CIFS is an application of SIMB protocol, designed by Microsoft.
• SMB stands for Server Message Block. It is a protocol for sharing a file and was
invented by IBM. The SMB protocol was created to allow computers to perform read
and write operations on files to a remote host over a Local Area Network (LAN). The
directories present in the remote host can be accessed via SMB and are called as
"shares".
• Hadoop is a group of open-source software services. It gives a software framework
for distributed storage and operating of big data using the MapReduce programming
model. The core of Hadoop contains a storage part, known as Hadoop Distributed File
System (HDFS), and an operating part which is a MapReduce programming model.
• NetWare is an abandon computer network operating system developed by Novell, Inc.
It primarily used combined multitasking to run different services on a personal
computer, using the IPX network protocol.
Features of AFS
• Client-side caching improves performance.
• Location transparency, users access files without knowing their physical location.
• Scalability, supports thousands of clients.
• Security using authentication mechanisms.
• Volume-based file management.
Working of AFS
1. Clients request files from the server.
2. Files are cached on the client machine.
3. Updates are synchronized with the server.
4. Multiple clients can access files efficiently.
Features of CODA
• Disconnected operation, clients can work even when the network is unavailable.
• Server replication for high availability.
• Fault tolerance in case of server failures.
• Automatic conflict resolution.
Working of CODA
1. Files are replicated on multiple servers.
2. Clients access cached copies of files.
3. If the network fails, the client can continue working locally.
4. Once the connection is restored, changes are synchronized with servers.
The Common Object Request Broker Architecture (CORBA), standardized by the Object
Management Group, was developed to address one of the fundamental challenges in distributed
computing: enabling seamless communication between heterogeneous systems. In large-scale
distributed environments, applications are often built using different programming languages,
run on diverse operating systems, and are deployed across geographically dispersed networks.
CORBA solves this problem by introducing a middleware layer that abstracts communication
complexities and provides a uniform interface for interaction. This allows developers to build
scalable and interoperable systems without worrying about underlying implementation details
such as network protocols or data representation formats.
The central component of CORBA is the Object Request Broker (ORB), which acts as a
mediator between client requests and server responses. When a client invokes a method on a
remote object, the ORB intercepts the request, identifies the object’s location, and forwards the
request to the appropriate server. The response is then sent back to the client in a transparent
manner. This mechanism ensures location transparency, meaning that the client does not need
to know whether the object is local or remote. The ORB also handles essential tasks such as
parameter marshalling and unmarshalling, connection management, and error handling,
thereby simplifying the development of distributed applications.
Another key aspect of CORBA is the Interface Definition Language (IDL), which serves as a
contract between clients and servers. IDL allows developers to define object interfaces in a
language-neutral way, specifying the methods, parameters, and data types that can be used.
These definitions are then compiled into language-specific stubs and skeletons, enabling
communication between components written in different programming languages. For
example, a server implemented in C++ can provide services to a client written in Java without
requiring any changes to either application’s core logic. This language independence is one of
CORBA’s most powerful features and has made it suitable for enterprise environments with
diverse technology stacks.
CORBA also includes a wide range of services that enhance the functionality of distributed
systems. These include naming services, which allow objects to be registered and discovered
dynamically; transaction services, which ensure data consistency across multiple operations;
and security services, which provide authentication, authorization, and encryption
mechanisms. Additionally, CORBA supports event and notification services, enabling
asynchronous communication between distributed components. This is particularly useful in
real-time systems such as stock trading platforms or telecommunications networks, where
timely data updates are critical.
Despite its strengths, CORBA has certain limitations that have affected its adoption in modern
systems. One of the primary challenges is its complexity, as setting up and maintaining
CORBA-based systems requires significant expertise. The use of IDL, along with the need for
ORB configuration and management, can make development time-consuming. Additionally,
CORBA systems tend to be heavyweight, requiring substantial computational resources
compared to newer lightweight communication frameworks. As a result, many organizations
have transitioned to alternative technologies such as RESTful APIs and microservices, which
offer simpler and more flexible solutions for distributed communication.
However, CORBA still holds significant academic and historical value in the field of
distributed computing. It introduced several important concepts such as middleware
abstraction, object-based communication, and interface standardization, which have influenced
modern distributed system designs. Many of the ideas pioneered by CORBA are still relevant
today and can be seen in technologies like web services, gRPC, and distributed object
frameworks. Understanding CORBA provides valuable insights into the evolution of
distributed computing and helps students grasp the fundamental principles that underpin
modern systems.
Conclusion:
In conclusion, CORBA represents a foundational technology in distributed computing that has
played a crucial role in enabling interoperability across heterogeneous systems. While it has
largely been replaced by more lightweight and flexible solutions, its contributions to the field
remain significant. By studying CORBA, one can gain a deeper understanding of distributed
system design, including challenges such as scalability, reliability, and cross-platform
communication. As such, CORBA continues to be an important case study for both academic
learning and historical reference in the evolution of distributed computing technologies.
Case Study: Android Stack
The Android Stack refers to the layered architecture of the Android Operating System,
designed to support the development and execution of mobile applications efficiently. This
architecture follows a modular approach, where each layer provides specific functionality and
interacts with the layers above and below it. The Android Stack is typically divided into five
main layers: Linux Kernel, Hardware Abstraction Layer (HAL), Native Libraries, Android
Runtime, Application Framework, and Applications. This structured design ensures scalability,
flexibility, and efficient resource management across a wide range of devices.
At the base of the Android Stack lies the Linux Kernel, which acts as the core of the system. It
is responsible for managing hardware resources such as memory, process scheduling, power
management, and device drivers. The Linux Kernel provides a secure and stable environment
by enforcing permissions and isolating processes. It also includes drivers for hardware
components like display, camera, Bluetooth, and Wi-Fi, enabling communication between the
hardware and software layers. This layer is crucial because it ensures that Android can run on
diverse hardware platforms while maintaining consistency.
Above the kernel is the Hardware Abstraction Layer (HAL), which acts as an interface between
hardware components and higher-level software. HAL allows developers to interact with
hardware devices without needing to understand the low-level implementation details. For
example, whether a device uses a specific camera chip or sensor, the HAL provides a consistent
interface to access its functionality. This abstraction improves portability and simplifies the
development of applications across different devices and manufacturers.
The next layer consists of Native Libraries and the Android Runtime. Native libraries are
written in C and C++ and provide core functionalities such as graphics rendering, database
management, and media playback. Examples include OpenGL for graphics, SQLite for
databases, and WebKit for web browsing. The Android Runtime includes the Android Runtime
(ART), which replaced the older Dalvik Virtual Machine. ART uses ahead-of-time (AOT)
compilation to improve application performance and reduce startup time. It also includes core
Java libraries that developers use to build Android applications. Together, these components
form the backbone of application execution and performance optimization.
At the top of the Android Stack is the Applications layer, which includes all user-facing
applications such as contacts, messaging, browser, and third-party apps installed from app
stores. These applications are built using the APIs provided by the Application Framework and
run within their own sandboxed environment for security. This isolation ensures that one
application cannot interfere with another, thereby enhancing system stability and user privacy.
From a distributed computing perspective, the Android Stack supports communication and data
exchange through various mechanisms such as web services, REST APIs, and inter-process
communication (IPC). Android applications can interact with remote servers, cloud services,
and other devices, making it a powerful platform for distributed applications. Features such as
background services, broadcast receivers, and content providers enable efficient data sharing
and synchronization across distributed systems.
Figure [Link] Stack
The Android Operating System follows a layered architecture known as the Android Stack,
which is designed to manage hardware resources and provide a platform for application
development. This architecture is divided into multiple layers, each responsible for specific
functionalities. The layered approach ensures that developers can build applications
efficiently without dealing directly with hardware complexities, making the system scalable,
flexible, and easy to maintain.
1. Applications Layer
The Applications layer is the topmost layer of the Android architecture and consists of all
user-facing applications. These include both pre-installed system applications and third-party
applications downloaded by users. Each application runs in its own isolated environment
(sandbox), which enhances security and prevents interference between apps. This layer
directly interacts with the user and provides the actual functionality of the device.
Key features:
• Includes apps like Phone, Browser, Games
• Developed using Java or Kotlin
• Runs in isolated processes for security
2. Application Framework
Below the Applications layer is the Application Framework, which provides a set of APIs
and system services that developers use to create applications. This layer acts as a bridge
between applications and the lower-level system components. It simplifies development by
offering reusable components and managing essential functions like UI design and
application lifecycle.
Important services provided by this layer include Activity Manager (controls app lifecycle),
Window Manager (manages screen layout), Content Providers (enable data sharing), and
Notification Manager (handles alerts). These services allow applications to function smoothly
without directly interacting with the system hardware.
Main components:
• Activity Manager
• Window Manager
• Content Providers
• Notification Manager
5. Linux Kernel
The Linux Kernel forms the foundation of the Android Stack and is responsible for low-level
system operations. It manages hardware resources, including memory, processes, and device
drivers. The kernel also ensures system security by enforcing access control and isolating
processes.
It plays a critical role in maintaining system stability and performance, acting as a bridge
between hardware and software components.
Functions:
• Memory management
• Process scheduling
• Device drivers (camera, Wi-Fi, display)
• Power management
Conclusion:
The Android Stack is a well-structured architecture that ensures smooth interaction between
applications and hardware. Its layered design simplifies development, improves performance,
and enhances security. By understanding this architecture, developers can create efficient and
scalable mobile applications while leveraging the full capabilities of the Android system.