DATE:
ROLL NO. B814
Experiment No: 1
Aim: Inter-Process Communication.
Theory:
Interprocess communication in a distributed system is a process of exchanging data between two or
more independent processes in a distributed environment. It is called interprocess communication.
Interprocess communication on the internet provides both Datagram and stream communication.
Characteristics of Inter-process Communication in Distributed Systems
There are mainly five characteristics of inter-process communication in a distributed
environment/system.
➢ Synchronous System Calls: In synchronous system calls both sender and receiver use blocking
system calls to transmit the data which means the sender will wait until the acknowledgment is
received from the receiver and the receiver waits until the message arrives.
➢ Asynchronous System Calls: In asynchronous system calls, both sender and receiver use non-
blocking system calls to transmit the data which means the sender doesn’t wait from the receiver
acknowledgment.
➢ Message Destination: A local port is a message destination within a computer, specified as an
integer. Aport has exactly one receiver but many senders. Processes may use multiple ports from
which to receive messages. Any process that knows the number of a port can send the message
to it.
➢ Reliability: It is defined as validity and integrity.
➢ Integrity: Messages must arrive without corruption and duplication to the destination.
Types of Interprocess Communication in Distributed Systems
Below are the types of interprocess communication (IPC) commonly used in distributed systems:
➢ Message Passing:
➔ Definition: Message passing involves processes communicating by sending and receiving
messages. Messages can be structured data packets containing information or commands.
➔ Characteristics: It is a versatile method suitable for both synchronous and asynchronous
communication. Message passing can be implemented using various protocols such as
TCP/IP, UDP, or higher-level messaging protocols like AMQP (Advanced Message Queuing
Protocol) or MQTT (Message Queuing Telemetry Transport).
➢ Remote Procedure Calls (RPC):
➔ Definition: RPC allows one process to invoke a procedure (or function) in another process,
typically located on a different machine over a network.
➔ Characteristics: It abstracts the communication between processes by making it appear as if a
local procedure call is being made. RPC frameworks handle details like parameter
marshalling, network communication, and error handling.
➢ Sockets:
➔ Definition: Sockets provide a low-level interface for network communication between
processes running on different computers.
➔ Characteristics: They allow processes to establish connections, send data streams (TCP) or
datagrams (UDP), and receive responses. Sockets are fundamental for implementing higher-
level communication protocols.
➢ Message Queuing Systems:
➔ Description: Message queuing systems facilitate asynchronous communication by allowing
processes to send messages to and receive messages from queues.
➔ Characteristics: They decouple producers (senders) and consumers (receivers) of messages,
providing fault tolerance, scalability, and persistence of messages. Examples include Apache
Kafka, RabbitMQ, and AWS SQS.
➢ Publish-Subscribe Systems:
➔ Description: Publish-subscribe (pub-sub) systems enable communication between
components without requiring them to directly know each other.
➔ Characteristics: Publishers publish messages to topics, and subscribers receive messages
based on their interest in specific topics. This model supports one-to-many communication
and is scalable for large-scale distributed systems. Examples include MQTT and Apache
Pulsar.
Benefits of Interprocess Communication in Distributed Systems
Below are the benefits of IPC in Distributed Systems:
1. Facilitates Communication:
IPC enables processes or components distributed across different nodes to communicate
seamlessly. This allows for building complex distributed applications where different parts of
the system can exchange information and coordinate their activities.
2. Integration of Heterogeneous Systems:
IPC mechanisms provide a standardized way for integrating heterogeneous systems and
platforms.
Processes written in different programming languages or running on different operating
systems can communicate using common IPC protocols and interfaces.
3. Scalability:
Distributed systems often need to scale horizontally by adding more nodes or instances.
IPC mechanisms, especially those designed for distributed environments, can facilitate scalable
communication patterns such as publish-subscribe or message queuing, enabling efficient
scaling without compromising performance.
4. Fault Tolerance and Resilience:
IPC techniques in distributed systems often include mechanisms for handling failures and
ensuring resilience.
For example, message queues can buffer messages during network interruptions, and RPC
frameworks can retry failed calls or implement failover strategies.
5. Performance Optimization:
Effective IPC can optimize performance by minimizing latency and overhead associated with
communication between distributed components.
Techniques like shared memory or efficient message passing protocols help in achieving low-
latency communication.
Implementation:
Part 1] Calling a function from one file in another file
Code:
# client.py
import tempfile
import os
import time
TEMP_DIR = tempfile.gettempdir()
REQUEST_FILE = os.path.join(TEMP_DIR, 'request.txt')
RESPONSE_FILE = os.path.join(TEMP_DIR, 'response.txt')
def main():
while True:
try:
print("\nEnter two numbers (separated by space) or 'q' to quit:")
user_input = input().strip()
if user_input.lower() == 'q':
break
try:
num1, num2 = map(float, user_input.split())
except ValueError:
print("Invalid input! Please enter two numbers separated by space.")
continue
with open(REQUEST_FILE, 'w') as f:
f.write(f"{num1} {num2}\n")
for _ in range(50): # Wait up to 5 seconds
time.sleep(0.1)
try:
with open(RESPONSE_FILE, 'r') as f:
result = f.read().strip()
if result:
if result == "ERROR":
print("Server encountered an error")
else:
print(f"Result from server: {result}")
break
except:
continue
else:
print("Timeout waiting for server response")
except KeyboardInterrupt:
break
except Exception as e:
print(f"Error occurred: {e}")
break
if __name__ == '__main__':
main()
# server.py
import tempfile
import os
import sys
import time
TEMP_DIR = tempfile.gettempdir()
REQUEST_FILE = os.path.join(TEMP_DIR, 'request.txt')
RESPONSE_FILE = os.path.join(TEMP_DIR, 'response.txt')
def main():
print("Server started. Waiting for client requests...")
open(REQUEST_FILE, 'w').close()
open(RESPONSE_FILE, 'w').close()
while True:
try:
with open(REQUEST_FILE, 'r') as f:
data = f.read().strip()
if data:
try:
num1, num2 = map(float, data.split())
result = num1 + num2
print(f"Processed: {num1} + {num2} = {result}")
with open(RESPONSE_FILE, 'w') as f:
f.write(f"{result}\n")
open(REQUEST_FILE, 'w').close()
except ValueError:
with open(RESPONSE_FILE, 'w') as f:
f.write("ERROR\n")
open(REQUEST_FILE, 'w').close()
time.sleep(0.1)
except KeyboardInterrupt:
break
except Exception as e:
print(f"Error: {e}")
break
try:
os.remove(REQUEST_FILE)
os.remove(RESPONSE_FILE)
except:
pass
if __name__ == '__main__':
main()
Output:
Server- Client:
Part 2] Create a banking transaction system using file-based IPC between client and server
Code:
# client.py
import json
import time
import uuid
import os
from datetime import datetime
REQUEST_FILE = 'request.txt'
RESPONSE_FILE = 'response.txt'
class TransactionClient:
def __init__(self, client_id):
self.client_id = client_id
self.transaction_count = 0
def generate_transaction_id(self):
self.transaction_count += 1
return f"{self.client_id}-{self.transaction_count}-{str(uuid.uuid4())[:8]}"
def send_transaction(self, amount, force_proceed=False):
transaction_id = self.generate_transaction_id()
request = {
'client_id': self.client_id,
'transaction_id': transaction_id,
'amount': amount,
'force_proceed': force_proceed
}
return self.send_request(request, transaction_id)
def get_transaction_history(self):
request = {
'client_id': self.client_id,
'type': 'history'
}
return self.send_request(request)
def send_request(self, request, transaction_id=None):
with open(REQUEST_FILE, 'w') as f:
json.dump(request, f)
for _ in range(50):
time.sleep(0.1)
try:
with open(RESPONSE_FILE, 'r') as f:
response = json.load(f)
if transaction_id is None or response.get('transaction_id') == transaction_id:
return response
except:
continue
return {'success': False, 'message': 'Timeout waiting for server response'}
def display_history(self, history):
if not history:
print("\nNo transaction history found.")
return
print("\n=== Transaction History ===")
print("Time | Type | Amount | Balance After | Notes")
print("-" * 70)
for trans in sorted(history, key=lambda x: x['timestamp']):
notes = "Duplicate confirmed" if trans.get('duplicate_confirmed') else ""
print(f"{trans['timestamp']} | "
f"{trans['type']:<5} | "
f"${trans['amount']:<6.2f} | "
f"${trans['balance_after']:<11.2f} | {notes}")
def process_transaction(client, amount):
response = client.send_transaction(amount)
if response.get('status') == 'DUPLICATE_WARNING':
print(f"\n{response['message']}")
while True:
choice = input("Do you want to proceed with this transaction? (yes/no): ").lower()
if choice in ['yes', 'no']:
break
print("Please enter 'yes' or 'no'")
if choice == 'yes':
response = client.send_transaction(amount, force_proceed=True)
return response
def main():
client_id = f"CLIENT_{str(uuid.uuid4())[:8]}"
client = TransactionClient(client_id)
print(f"Client ID: {client_id}")
print("Initial balance: 1000")
while True:
try:
print("\nOptions:")
print("1. Make a transaction")
print("2. View transaction history")
print("3. Exit")
choice = input("Enter your choice (1-3): ").strip()
if choice == '1':
print("\nEnter amount to debit:")
amount = float(input().strip())
if amount <= 0:
print("Please enter a positive amount")
continue
response = process_transaction(client, amount)
if response['success']:
print(f"Transaction successful!")
print(f"New balance: ${response['balance']:.2f}")
else:
print(f"Transaction failed: {response['message']}")
elif choice == '2':
response = client.get_transaction_history()
if response['success']:
client.display_history(response['history'])
print(f"\nCurrent balance: ${response['balance']:.2f}")
else:
print("Failed to retrieve transaction history")
elif choice == '3':
print("Thank you for using our service!")
break
else:
print("Invalid choice. Please try again.")
except ValueError:
print("Invalid input! Please enter a valid number.")
except KeyboardInterrupt:
break
except Exception as e:
print(f"Error occurred: {e}")
break
if __name__ == '__main__':
main()
# server.py
import json
import os
import time
from datetime import datetime, timedelta
TRANSACTION_FILE = 'transactions.json'
REQUEST_FILE = 'request.txt'
RESPONSE_FILE = 'response.txt'
class TransactionServer:
def __init__(self):
self.transactions = self.load_transactions()
open(REQUEST_FILE, 'w').close()
open(RESPONSE_FILE, 'w').close()
def load_transactions(self):
if os.path.exists(TRANSACTION_FILE):
try:
with open(TRANSACTION_FILE, 'r') as f:
return json.load(f)
except:
return {'transactions': [], 'balances': {}}
return {'transactions': [], 'balances': {}}
def save_transactions(self):
with open(TRANSACTION_FILE, 'w') as f:
json.dump(self.transactions, f, indent=4)
def check_similar_transaction(self, client_id, amount, current_time):
current_dt = datetime.strptime(current_time, '%Y-%m-%d %H:%M:%S')
time_window = timedelta(minutes=1)
client_transactions = [t for t in self.transactions['transactions']
if t['client_id'] == client_id]
for transaction in client_transactions:
trans_time = datetime.strptime(transaction['timestamp'], '%Y-%m-%d %H:%M:%S')
time_diff = abs(current_dt - trans_time)
if (transaction['amount'] == amount and time_diff <= time_window):
return True, {
'original_time': transaction['timestamp'],
'amount': transaction['amount'],
'transaction_id': transaction['transaction_id']
}
return False, None
def process_transaction(self, client_id, transaction_id, amount, force_proceed=False):
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
is_similar, similar_info = self.check_similar_transaction(client_id, amount, current_time)
if is_similar and not force_proceed:
warning_msg = (f"WARNING: Similar transaction detected! "
f"Transaction of ${similar_info['amount']} "
f"was made at {similar_info['original_time']}. "
f"Do you want to proceed?")
return "DUPLICATE_WARNING", warning_msg
if client_id not in self.transactions['balances']:
self.transactions['balances'][client_id] = 1000
current_balance = self.transactions['balances'][client_id]
if current_balance < amount:
return False, "Insufficient balance"
new_balance = current_balance - amount
self.transactions['balances'][client_id] = new_balance
transaction = {
'transaction_id': transaction_id,
'client_id': client_id,
'amount': amount,
'type': 'debit',
'timestamp': current_time,
'balance_after': new_balance,
'duplicate_confirmed': force_proceed
}
self.transactions['transactions'].append(transaction)
self.save_transactions()
return True, f"Transaction successful. New balance: {new_balance}"
def run(self):
print("Transaction Server started. Waiting for requests...")
while True:
try:
with open(REQUEST_FILE, 'r') as f:
data = f.read().strip()
if data:
try:
request_data = json.loads(data)
client_id = request_data['client_id']
if request_data.get('type') == 'history':
history = [t for t in self.transactions['transactions']
if t['client_id'] == client_id]
response = {
'success': True,
'type': 'history',
'history': history,
'balance': self.transactions['balances'].get(client_id, 0)
}
else:
transaction_id = request_data['transaction_id']
amount = float(request_data['amount'])
force_proceed = request_data.get('force_proceed', False)
status, message = self.process_transaction(
client_id, transaction_id, amount, force_proceed
)
response = {
'success': status == True,
'status': status,
'message': message,
'transaction_id': transaction_id,
'balance': self.transactions['balances'].get(client_id, 0)
}
with open(RESPONSE_FILE, 'w') as f:
json.dump(response, f)
open(REQUEST_FILE, 'w').close()
except json.JSONDecodeError:
print("Invalid JSON request")
except KeyError as e:
print(f"Missing required field: {e}")
except ValueError as e:
print(f"Invalid value: {e}")
time.sleep(0.1)
except KeyboardInterrupt:
print("\nServer shutting down...")
break
except Exception as e:
print(f"Error: {e}")
break
if __name__ == '__main__':
server = TransactionServer()
server.run()
Output: Simple transaction:, duplicate transaction detection & transaction history
Conclusion: Inter-Process Communication (IPC) enables secure, efficient data exchange between
processes, providing a robust mechanism for distributed computing by allowing controlled
communication, synchronization, and resource sharing across different program contexts.