[go: up one dir, main page]

0% found this document useful (0 votes)
4 views9 pages

Advance Python Program Unit IV

This document covers network and multithreaded programming in Python, focusing on sockets, TCP/UDP protocols, and HTTP client/server implementation. It also discusses multithreading and multiprocessing, including thread synchronization, shared memory, and priority queues. Examples of code for TCP/UDP clients and servers, as well as HTTP servers and clients, are provided to illustrate these concepts.

Uploaded by

Abhi Bunny
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
4 views9 pages

Advance Python Program Unit IV

This document covers network and multithreaded programming in Python, focusing on sockets, TCP/UDP protocols, and HTTP client/server implementation. It also discusses multithreading and multiprocessing, including thread synchronization, shared memory, and priority queues. Examples of code for TCP/UDP clients and servers, as well as HTTP servers and clients, are provided to illustrate these concepts.

Uploaded by

Abhi Bunny
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 9

UNIT - IV

Network and Multithreaded Programming: Introduction to sockets and networking protocols


(TCP, UDP), Building basic TCP/UDP clients and servers, HTTP client/server implementation,
Multithreading and multiprocessing, Thread synchronization, shared memory, priority queues.

Python, sockets are a way to enable communication between devices over a network. They
allow data to be sent and received between two endpoints, such as a client and a server.

Networking Protocols:

• TCP (Transmission Control Protocol): Reliable, connection-oriented protocol. Ensures


data is received in order and without errors.

• UDP (User Datagram Protocol): Fast, connectionless protocol. Does not guarantee
delivery or order, making it ideal for real-time applications like gaming or streaming.

Using Sockets in Python

Python provides the socket module to work with networking protocols. Here's a simple
example:

TCP Server

import socket

server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # TCP socket

server_socket.bind(("127.0.0.1", 12345)) # Bind to localhost and port 12345

server_socket.listen(1) # Listen for connections

print("Server is listening...")

conn, addr = server_socket.accept() # Accept a connection

print(f"Connected to {addr}")

data = conn.recv(1024) # Receive data

print(f"Received: {data.decode()}")

conn.sendall(b"Hello from server!") # Send response


conn.close()

server_socket.close()

TCP Client

import socket

client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

client_socket.connect(("127.0.0.1", 12345)) # Connect to server

client_socket.sendall(b"Hello, Server!") # Send data

response = client_socket.recv(1024) # Receive response

print(f"Received: {response.decode()}")

client_socket.close()

UDP (User Datagram Protocol) is a connectionless protocol, meaning it doesn't require a


handshake before sending data. It's faster but doesn't guarantee delivery or order.

UDP Server

import socket

server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # UDP socket

server_socket.bind(("127.0.0.1", 12345)) # Bind to localhost and port 12345

print("UDP server is listening...")

while True:

data, addr = server_socket.recvfrom(1024) # Receive data

print(f"Received from {addr}: {data.decode()}")


server_socket.sendto(b"Hello from server!", addr) # Send response

UDP Client

import socket

client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # UDP socket

server_address = ("127.0.0.1", 12345)

client_socket.sendto(b"Hello, Server!", server_address) # Send data

response, _ = client_socket.recvfrom(1024) # Receive response

print(f"Received: {response.decode()}")

client_socket.close()

With UDP, since there's no connection, the client can send data anytime, and the server
listens indefinitely.

HTTP client and server


Implementing an HTTP client and server in Python can be done using the built-in http.server
module for the server and requests or http.client for the client.

HTTP Server using http.server

from http.server import SimpleHTTPRequestHandler, HTTPServer class


MyHandler(SimpleHTTPRequestHandler): de

from http.server import SimpleHTTPRequestHandler, HTTPServer

class MyHandler(SimpleHTTPRequestHandler):

def do_GET(self):

self.send_response(200)

self.send_header("Content-type", "text/html")

self.end_headers()

self.wfile.write(b"<html><body><h1>Hello, HTTP Client!</h1></body></html>")

server_address = ("127.0.0.1", 8080)


httpd = HTTPServer(server_address, MyHandler)

print("Serving HTTP on port 8080...")

httpd.serve_forever()

HTTP Client using requests

import requests response = requests.get("http://127.0.0.1:8080") print(f"Response Status:


{response.status_code}") print

import requests

response = requests.get("http://127.0.0.1:8080")

print(f"Response Status: {response.status_code}")

print(f"Response Body: {response.text}")

This example sets up a simple HTTP server responding with an HTML message, and a client
fetching that page.

Multithreading and multiprocessing


Python help improve performance by running tasks concurrently.

1. Multithreading in Python

• Uses the threading module.

• Best for I/O-bound tasks (e.g., network requests, file operations).

• Threads share the same memory space, avoiding unnecessary duplication.

Example: Using threading

import threading import time def print_numbers(): for i in range(5): print(f"Number {i}")
time.sleep(1) thread = threading.Thread(target=print_numb

import threading

import time

def print_numbers():

for i in range(5):
print(f"Number {i}")

time.sleep(1)

thread = threading.Thread(target=print_numbers)

thread.start()

thread.join() # Wait for the thread to finish

print("Thread execution completed.")

2. Multiprocessing in Python

• Uses the multiprocessing module.

• Best for CPU-bound tasks (e.g., heavy computations).

• Processes have separate memory spaces, avoiding global interpreter lock (GIL)
issues.

Example: Using multiprocessing

import multiprocessing

def worker_function(num):

print(f"Processing {num}")

if __name__ == "__main__":import multiprocessing

def worker_function(num):

print(f"Processing {num}")

if __name__ == "__main__":

process = multiprocessing.Process(target=worker_function, args=(5,))

process.start()

process.join() # Wait for the process to finish

print("Process execution completed.") process =


multiprocessing.Process(target=worker_function, args=(5,))

process.start()

process.join() # Wait for the process to finish


print("Process execution completed.")

Thread synchronization
Python is crucial when multiple threads need to share resources safely. Since Python
threads run in the same memory space, improper synchronization can lead to race
conditions, where threads modify shared data unpredictably.

Key Synchronization Methods:

1. Locks (threading.Lock) – Used to prevent multiple threads from accessing a shared


resource at the same time.

2. RLocks (threading.RLock) – Allows a thread to acquire the same lock multiple times.

3. Semaphores (threading.Semaphore) – Limits the number of threads accessing a


resource.

4. Events (threading.Event) – Used for signaling between threads.

5. Condition Variables (threading.Condition) – Used when one thread waits for another
to meet certain conditions.

Example: Using threading.Lock to Prevent Race Conditions

import threading

counter = 0

lock = threading.Lock()

def increment():

global counter

for _ in range(1000000):

with lock: # Lock ensures only one thread modifies 'counter' at a time

counter += 1

thread1 = threading.Thread(target=increment)

thread2 = threading.Thread(target=increment)

thread1.start()
thread2.start()

thread1.join()

thread2.join()

print(f"Final Counter Value: {counter}")

Without the lock, counter may not reach the expected value due to race conditions.

Shared memory
Python allows multiple processes to access and modify the same data without needing
expensive inter-process communication (IPC). The multiprocessing module provides tools to
facilitate shared memory.

1. Using multiprocessing.Value and multiprocessing.Array

The Value and Array classes create shared variables that multiple processes can modify.

Example: Shared Memory with multiprocessing.Value

import multiprocessing

shared_counter = multiprocessing.Value("i", 0) # 'i' means integer type

def increment(shared_counter):

for _ in range(1000000):

shared_counter.value += 1

process1 = multiprocessing.Process(target=increment, args=(shared_counter,))

process2 = multiprocessing.Process(target=increment, args=(shared_counter,))

process1.start()

process2.start()

process1.join()

process2.join()

print(f"Final Counter Value: {shared_counter.value}")


However, this can lead to race conditions, so a Lock should be used.

2. Using multiprocessing.shared_memory for Fast Data Sharing

Python 3.8 introduced shared_memory, which allows direct access to memory.

Example: Shared Memory with multiprocessing.shared_memory

import multiprocessing.shared_memory

import numpy as np

# Create shared memory

shm = multiprocessing.shared_memory.SharedMemory(create=True, size=1024)

array = np.ndarray((256,), dtype=np.int32, buffer=shm.buf)

# Modify the shared array

array[:] = np.arange(256)

print("Shared memory created!")

shm.close()

shm.unlink() # Cleanup after use

This method is faster and efficient compared to Value and Array.

Priority queues
Python allow elements to be processed based on priority rather than order of arrival. They
are especially useful in tasks like scheduling, pathfinding algorithms (like Dijkstra’s), and task
management.

1. Using heapq for Priority Queues

Python’s heapq module provides an efficient way to implement a priority queue using a
min-heap.

Example: Simple Priority Queue

import heapq

queue = []

heapq.heappush(queue, (3, "Task 3")) # Priority 3

heapq.heappush(queue, (1, "Task 1")) # Priority 1 (highest priority)

heapq.heappush(queue, (2, "Task 2")) # Priority 2

while queue:
priority, task = heapq.heappop(queue)

print(f"Processing {task} with priority {priority}")

Output:

Processing Task 1 with priority 1

Processing Task 2 with priority 2

Processing Task 3 with priority 3

Since heapq uses a min-heap, lower numbers have higher priority.

2. Using queue.PriorityQueue

The queue.PriorityQueue class provides a thread-safe implementation of a priority queue.

Example: Thread-Safe Priority Queue

import queue

pq = queue.PriorityQueue()

pq.put((3, "Task 3"))

pq.put((1, "Task 1"))

pq.put((2, "Task 2"))

while not pq.empty():

priority, task = pq.get()

print(f"Processing {task} with priority {priority}")

This behaves similarly to heapq, but with built-in synchronization for multithreading.

You might also like