Socket Programming
Learning Objectives
By the end of this reading, you will be able to:
- Understand the socket API and its role in network programming
- Explain the client-server model
- Create TCP socket applications in Python
- Create UDP socket applications in Python
- Handle common socket programming scenarios
- Debug and troubleshoot socket applications
- Build practical networked applications
Introduction
Socket programming is the foundation of network communication. A socket is an endpoint for sending or receiving data across a computer network. Whether you're building a web server, chat application, or multiplayer game, you'll use sockets.
In this reading, we'll explore socket programming using Python, building practical examples of both TCP and UDP applications.
What is a Socket?
A socket is a software endpoint that establishes bidirectional communication between a server program and one or more client programs.
Socket Analogy
Think of a socket like a telephone:
Phone Call:
1. You have a phone (socket)
2. You dial a number (IP address and port)
3. Someone answers (connection established)
4. You talk (send/receive data)
5. You hang up (close connection)
Socket Address
A socket address uniquely identifies a network endpoint:
Socket = IP Address + Port Number + Protocol
Examples:
192.168.1.100:8080 (TCP)
10.0.0.5:53 (UDP)
localhost:3000 (TCP)
Socket Types
Stream Sockets (SOCK_STREAM):
- Use TCP
- Reliable, connection-oriented
- Data arrives in order
- Error checking and retransmission
Datagram Sockets (SOCK_DGRAM):
- Use UDP
- Unreliable, connectionless
- No order guarantee
- Faster, less overhead
Raw Sockets (SOCK_RAW):
- Direct IP access
- Custom protocols
- Requires administrative privileges
The Client-Server Model
Most networked applications follow the client-server model:
┌──────────────────┐ ┌──────────────────┐
│ Client │ │ Server │
│ │ │ │
│ 1. Connect to │ │ 1. Listen on │
│ server │ │ port │
│ │ │ │
│ 2. Send request │─────────────>│ 2. Accept │
│ │ │ connection │
│ │ │ │
│ 3. Receive │<─────────────│ 3. Process │
│ response │ │ request │
│ │ │ │
│ 4. Close │ │ 4. Send response │
│ connection │ │ │
└──────────────────┘ └──────────────────┘
Server Responsibilities
- Create socket
- Bind to address (IP + port)
- Listen for connections
- Accept incoming connections
- Receive/send data
- Close connections
Client Responsibilities
- Create socket
- Connect to server
- Send/receive data
- Close connection
TCP Socket Programming in Python
Python Socket Module
Python's socket module provides access to the BSD socket interface.
import socket
# Common constants
socket.AF_INET # IPv4
socket.AF_INET6 # IPv6
socket.SOCK_STREAM # TCP
socket.SOCK_DGRAM # UDP
TCP Server: Step by Step
Complete TCP Echo Server
import socket
def tcp_echo_server(host='127.0.0.1', port=8080):
"""
Simple TCP echo server that echoes back received messages.
"""
# 1. Create socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. Set socket options (optional but recommended)
# Allow reuse of address (avoid "Address already in use" error)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 3. Bind socket to address
server_socket.bind((host, port))
print(f"Server bound to {host}:{port}")
# 4. Listen for connections (backlog queue size = 5)
server_socket.listen(5)
print("Server listening for connections...")
try:
while True:
# 5. Accept incoming connection (blocks until client connects)
client_socket, client_address = server_socket.accept()
print(f"Connection from {client_address}")
try:
while True:
# 6. Receive data (up to 1024 bytes)
data = client_socket.recv(1024)
# Empty data means client closed connection
if not data:
print(f"Client {client_address} disconnected")
break
print(f"Received from {client_address}: {data.decode()}")
# 7. Send response (echo back)
client_socket.sendall(data)
except Exception as e:
print(f"Error handling client {client_address}: {e}")
finally:
# 8. Close client connection
client_socket.close()
except KeyboardInterrupt:
print("\nServer shutting down...")
finally:
# 9. Close server socket
server_socket.close()
if __name__ == "__main__":
tcp_echo_server()
TCP Server Methods Explained
# Create socket
socket.socket(family, type, proto)
# family: AF_INET (IPv4), AF_INET6 (IPv6)
# type: SOCK_STREAM (TCP), SOCK_DGRAM (UDP)
# Bind to address
socket.bind((host, port))
# host: IP address or hostname
# port: Port number (1-65535)
# Listen for connections
socket.listen(backlog)
# backlog: Maximum queued connections
# Accept connection (blocking)
client_socket, address = socket.accept()
# Returns: new socket for client, client address tuple
# Receive data
data = socket.recv(buffer_size)
# buffer_size: Maximum bytes to receive
# Returns: bytes object
# Send data
socket.send(data) # May not send all data
socket.sendall(data) # Ensures all data sent
# Close socket
socket.close()
TCP Client: Step by Step
Complete TCP Echo Client
import socket
def tcp_echo_client(host='127.0.0.1', port=8080):
"""
Simple TCP echo client that sends messages and receives echoes.
"""
# 1. Create socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
# 2. Connect to server
client_socket.connect((host, port))
print(f"Connected to server {host}:{port}")
# 3. Send and receive messages
while True:
# Get user input
message = input("Enter message (or 'quit' to exit): ")
if message.lower() == 'quit':
break
# 4. Send data
client_socket.sendall(message.encode())
# 5. Receive response
response = client_socket.recv(1024)
print(f"Server echoed: {response.decode()}")
except ConnectionRefusedError:
print(f"Could not connect to {host}:{port}")
except Exception as e:
print(f"Error: {e}")
finally:
# 6. Close connection
client_socket.close()
print("Connection closed")
if __name__ == "__main__":
tcp_echo_client()
Multi-Client TCP Server (Threading)
Handle multiple clients simultaneously:
import socket
import threading
def handle_client(client_socket, client_address):
"""
Handle individual client connection.
"""
print(f"New thread for client {client_address}")
try:
while True:
data = client_socket.recv(1024)
if not data:
break
print(f"[{client_address}] {data.decode()}")
client_socket.sendall(data)
except Exception as e:
print(f"Error with client {client_address}: {e}")
finally:
client_socket.close()
print(f"Client {client_address} disconnected")
def tcp_multi_client_server(host='127.0.0.1', port=8080):
"""
TCP server that handles multiple clients using threads.
"""
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind((host, port))
server_socket.listen(5)
print(f"Multi-client server listening on {host}:{port}")
try:
while True:
client_socket, client_address = server_socket.accept()
print(f"Connection from {client_address}")
# Create new thread for each client
client_thread = threading.Thread(
target=handle_client,
args=(client_socket, client_address)
)
client_thread.daemon = True # Thread dies when main thread exits
client_thread.start()
except KeyboardInterrupt:
print("\nServer shutting down...")
finally:
server_socket.close()
if __name__ == "__main__":
tcp_multi_client_server()
HTTP Server Example
Simple HTTP server that serves a webpage:
import socket
def http_server(host='127.0.0.1', port=8080):
"""
Simple HTTP server that serves a basic webpage.
"""
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind((host, port))
server_socket.listen(5)
print(f"HTTP Server running on http://{host}:{port}")
# HTML content to serve
html_content = """<!DOCTYPE html>
<html>
<head>
<title>Simple HTTP Server</title>
</head>
<body>
<h1>Hello from Python Socket Server!</h1>
<p>This page is served by a simple TCP socket.</p>
</body>
</html>"""
# HTTP response
http_response = f"""HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: {len(html_content)}
Connection: close
{html_content}"""
try:
while True:
client_socket, client_address = server_socket.accept()
# Receive HTTP request
request = client_socket.recv(1024).decode()
print(f"Request from {client_address}:")
print(request[:200]) # Print first 200 chars
# Send HTTP response
client_socket.sendall(http_response.encode())
client_socket.close()
except KeyboardInterrupt:
print("\nServer stopped")
finally:
server_socket.close()
if __name__ == "__main__":
http_server()
Visit http://127.0.0.1:8080 in your browser!
UDP Socket Programming in Python
UDP is connectionless: no handshake, just send and receive datagrams.
UDP Server
import socket
def udp_echo_server(host='127.0.0.1', port=8080):
"""
Simple UDP echo server.
"""
# 1. Create UDP socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 2. Bind to address
server_socket.bind((host, port))
print(f"UDP Server listening on {host}:{port}")
try:
while True:
# 3. Receive data and client address (no accept() needed)
data, client_address = server_socket.recvfrom(1024)
print(f"Received from {client_address}: {data.decode()}")
# 4. Send response (no connection, specify destination)
server_socket.sendto(data, client_address)
except KeyboardInterrupt:
print("\nServer shutting down...")
finally:
server_socket.close()
if __name__ == "__main__":
udp_echo_server()
UDP Client
import socket
def udp_echo_client(host='127.0.0.1', port=8080):
"""
Simple UDP echo client.
"""
# 1. Create UDP socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# Optional: Set timeout
client_socket.settimeout(5.0)
try:
while True:
message = input("Enter message (or 'quit' to exit): ")
if message.lower() == 'quit':
break
# 2. Send data (no connection needed, specify destination)
client_socket.sendto(message.encode(), (host, port))
try:
# 3. Receive response
data, server_address = client_socket.recvfrom(1024)
print(f"Server echoed: {data.decode()}")
except socket.timeout:
print("Request timed out")
except Exception as e:
print(f"Error: {e}")
finally:
client_socket.close()
if __name__ == "__main__":
udp_echo_client()
TCP vs UDP Socket Comparison
# TCP (Connection-oriented)
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((host, port))
server.listen(5)
client_sock, addr = server.accept() # Accept connection
data = client_sock.recv(1024) # Receive from connected socket
client_sock.send(data) # Send to connected socket
client_sock.close() # Close client connection
# UDP (Connectionless)
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server.bind((host, port))
# No listen() or accept()
data, addr = server.recvfrom(1024) # Receive with sender address
server.sendto(data, addr) # Send to specific address
# No close() per client (no connection)
Practical Examples
1. Chat Server (TCP)
import socket
import threading
clients = [] # List of connected client sockets
lock = threading.Lock() # Thread synchronization
def broadcast(message, sender_socket):
"""Send message to all clients except sender."""
with lock:
for client in clients:
if client != sender_socket:
try:
client.sendall(message)
except:
clients.remove(client)
def handle_client(client_socket, client_address):
"""Handle individual chat client."""
print(f"{client_address} joined the chat")
# Ask for username
client_socket.sendall(b"Enter your name: ")
username = client_socket.recv(1024).decode().strip()
# Announce new user
join_msg = f"{username} joined the chat\n".encode()
broadcast(join_msg, client_socket)
try:
while True:
data = client_socket.recv(1024)
if not data:
break
# Broadcast message with username
message = f"{username}: {data.decode()}".encode()
print(message.decode().strip())
broadcast(message, client_socket)
except Exception as e:
print(f"Error with {username}: {e}")
finally:
with lock:
if client_socket in clients:
clients.remove(client_socket)
leave_msg = f"{username} left the chat\n".encode()
broadcast(leave_msg, client_socket)
client_socket.close()
print(f"{username} disconnected")
def chat_server(host='127.0.0.1', port=9999):
"""Multi-client chat server."""
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind((host, port))
server_socket.listen(5)
print(f"Chat server running on {host}:{port}")
try:
while True:
client_socket, client_address = server_socket.accept()
with lock:
clients.append(client_socket)
thread = threading.Thread(
target=handle_client,
args=(client_socket, client_address)
)
thread.daemon = True
thread.start()
except KeyboardInterrupt:
print("\nShutting down chat server...")
finally:
server_socket.close()
if __name__ == "__main__":
chat_server()
2. File Transfer Server (TCP)
import socket
import os
def file_server(host='127.0.0.1', port=9000, directory='./files'):
"""
Simple file transfer server.
Client sends filename, server sends file content.
"""
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind((host, port))
server_socket.listen(5)
print(f"File server running on {host}:{port}")
print(f"Serving files from: {os.path.abspath(directory)}")
try:
while True:
client_socket, client_address = server_socket.accept()
print(f"Connection from {client_address}")
try:
# Receive filename
filename = client_socket.recv(1024).decode().strip()
filepath = os.path.join(directory, filename)
print(f"Client requested: {filename}")
# Check if file exists
if os.path.isfile(filepath):
# Send file size first
file_size = os.path.getsize(filepath)
client_socket.sendall(f"{file_size}\n".encode())
# Send file content
with open(filepath, 'rb') as f:
while chunk := f.read(4096):
client_socket.sendall(chunk)
print(f"Sent {filename} ({file_size} bytes)")
else:
client_socket.sendall(b"ERROR: File not found\n")
print(f"File not found: {filename}")
except Exception as e:
print(f"Error: {e}")
finally:
client_socket.close()
except KeyboardInterrupt:
print("\nServer stopped")
finally:
server_socket.close()
def file_client(host='127.0.0.1', port=9000, filename='test.txt'):
"""
File transfer client.
"""
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
client_socket.connect((host, port))
print(f"Connected to {host}:{port}")
# Send filename
client_socket.sendall(filename.encode())
# Receive file size or error
response = client_socket.recv(1024).decode()
if response.startswith("ERROR"):
print(response)
return
file_size = int(response.strip())
print(f"Receiving {filename} ({file_size} bytes)...")
# Receive file content
received = 0
with open(f"received_{filename}", 'wb') as f:
while received < file_size:
chunk = client_socket.recv(min(4096, file_size - received))
if not chunk:
break
f.write(chunk)
received += len(chunk)
print(f"File saved as received_{filename}")
except Exception as e:
print(f"Error: {e}")
finally:
client_socket.close()
if __name__ == "__main__":
import sys
if len(sys.argv) > 1 and sys.argv[1] == 'client':
filename = sys.argv[2] if len(sys.argv) > 2 else 'test.txt'
file_client(filename=filename)
else:
file_server()
3. Port Scanner (TCP)
import socket
from concurrent.futures import ThreadPoolExecutor
def scan_port(host, port, timeout=1):
"""
Check if a port is open on a host.
Returns: (port, True/False)
"""
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
result = sock.connect_ex((host, port))
sock.close()
return (port, result == 0) # 0 means success (open)
except:
return (port, False)
def port_scanner(host, start_port=1, end_port=1024, threads=100):
"""
Scan range of ports on a host.
"""
print(f"Scanning {host} for open ports ({start_port}-{end_port})...")
print("-" * 50)
open_ports = []
# Use thread pool for concurrent scanning
with ThreadPoolExecutor(max_workers=threads) as executor:
futures = [
executor.submit(scan_port, host, port)
for port in range(start_port, end_port + 1)
]
for future in futures:
port, is_open = future.result()
if is_open:
# Try to get service name
try:
service = socket.getservbyport(port)
except:
service = "unknown"
print(f"Port {port:5d} - OPEN ({service})")
open_ports.append(port)
print("-" * 50)
print(f"Found {len(open_ports)} open ports")
return open_ports
if __name__ == "__main__":
# Scan common ports on localhost
port_scanner('127.0.0.1', 1, 1024)
4. DNS Lookup Utility (UDP)
import socket
def dns_lookup(domain):
"""
Perform DNS lookup for a domain.
"""
print(f"Looking up: {domain}")
print("-" * 50)
try:
# Get IP address
ip_address = socket.gethostbyname(domain)
print(f"IPv4 Address: {ip_address}")
# Get all addresses (including IPv6)
addr_info = socket.getaddrinfo(domain, None)
print("\nAll addresses:")
for info in addr_info:
family, socktype, proto, canonname, sockaddr = info
ip = sockaddr[0]
family_name = "IPv6" if family == socket.AF_INET6 else "IPv4"
socktype_name = "TCP" if socktype == socket.SOCK_STREAM else "UDP"
print(f" {family_name} ({socktype_name}): {ip}")
# Reverse lookup
try:
hostname = socket.gethostbyaddr(ip_address)
print(f"\nReverse lookup: {hostname[0]}")
except:
print("\nReverse lookup: Not available")
except socket.gaierror as e:
print(f"Error: {e}")
except Exception as e:
print(f"Error: {e}")
if __name__ == "__main__":
dns_lookup("www.google.com")
print("\n" + "=" * 50 + "\n")
dns_lookup("www.github.com")
Socket Options and Advanced Techniques
Setting Socket Options
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Reuse address (avoid "Address already in use")
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# Set timeout (seconds)
sock.settimeout(5.0)
# Set buffer sizes
sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 4096) # Receive buffer
sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 4096) # Send buffer
# TCP keep-alive (detect dead connections)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
# Disable Nagle's algorithm (send immediately, don't buffer)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
Non-Blocking Sockets
import socket
import select
# Create non-blocking socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setblocking(False)
# Use select() to wait for socket to be ready
readable, writable, exceptional = select.select([sock], [], [], timeout)
if readable:
data = sock.recv(1024)
Context Manager (with statement)
# Automatically closes socket
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.connect(('example.com', 80))
sock.sendall(b'GET / HTTP/1.1\r\nHost: example.com\r\n\r\n')
response = sock.recv(4096)
# Socket automatically closed here
Common Issues and Debugging
1. Address Already in Use
# Error: [Errno 48] Address already in use
# Solution: Use SO_REUSEADDR
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
2. Connection Refused
# Error: [Errno 61] Connection refused
# Causes:
# - Server not running
# - Wrong host/port
# - Firewall blocking connection
# Debugging:
# 1. Check if server is running
# 2. Verify host and port
# 3. Test with telnet: telnet localhost 8080
3. Broken Pipe
# Error: [Errno 32] Broken pipe
# Cause: Writing to socket after remote end closed
# Solution: Check if connection is alive before writing
try:
sock.sendall(data)
except BrokenPipeError:
print("Connection closed by remote host")
4. Incomplete Data Reception
# Problem: recv() may not receive all data at once
# Wrong:
data = sock.recv(1024) # May only receive partial data
# Right:
def recv_all(sock, length):
"""Receive exactly 'length' bytes."""
data = b''
while len(data) < length:
chunk = sock.recv(length - len(data))
if not chunk:
raise ConnectionError("Connection closed")
data += chunk
return data
# Or for unknown length, receive until delimiter:
def recv_until(sock, delimiter=b'\n'):
"""Receive until delimiter found."""
data = b''
while True:
chunk = sock.recv(1)
if not chunk:
break
data += chunk
if chunk == delimiter:
break
return data
5. Socket Timeout
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(5.0) # 5 second timeout
try:
sock.connect(('example.com', 80))
data = sock.recv(1024)
except socket.timeout:
print("Operation timed out")
Best Practices
- Always Close Sockets: Use try/finally or context managers
- Handle Exceptions: Network operations can fail
- Set Timeouts: Prevent indefinite blocking
- Use SO_REUSEADDR: For servers
- Validate Input: Check data before sending
- Buffer Management: Handle partial sends/receives
- Thread Safety: Use locks for shared resources
- Security: Validate client input, use encryption for sensitive data
- Logging: Log connections, errors, and important events
- Graceful Shutdown: Close connections properly
Exercises
Basic Exercises
Echo Server: Modify the echo server to:
- a) Convert received text to uppercase before echoing
- b) Log all messages to a file
- c) Send a welcome message when client connects
Time Server: Create a TCP server that:
- Returns current time when client connects
- Format: "Current time: HH:MM:SS"
- Closes connection after sending time
UDP vs TCP: Create both UDP and TCP versions of a simple calculator server:
- Client sends: "5 + 3"
- Server responds: "8"
- Compare the implementations
Intermediate Exercises
HTTP Client: Write a simple HTTP client that:
- Connects to a web server
- Sends GET request
- Parses and displays response headers
- Saves response body to file
Chat Client: Write a client for the chat server example:
- Send messages to server
- Receive messages from other clients
- Use threading to send and receive simultaneously
File Transfer: Enhance the file transfer server:
- Support uploading files (not just downloading)
- Add progress bar for large files
- Handle multiple clients simultaneously
Port Scanner Enhancement: Improve the port scanner:
- Add banner grabbing (identify service version)
- Save results to JSON file
- Add verbose mode
Advanced Exercises
HTTP Server: Build a more complete HTTP server:
- Serve files from directory
- Support GET and POST methods
- Parse query parameters
- Return proper HTTP status codes (200, 404, 500)
- Add logging
Multiplayer Game: Create a simple multiplayer game:
- Server maintains game state
- Multiple clients can connect
- Real-time updates (use UDP for game state, TCP for chat)
- Handle player disconnections
Proxy Server: Implement a simple HTTP proxy:
- Client connects to proxy
- Proxy forwards request to actual server
- Proxy returns response to client
- Add caching for repeated requests
Secure Chat: Add encryption to the chat server:
- Use SSL/TLS for encrypted communication
- Implement user authentication
- Add private messages between users
Distributed System: Create a simple distributed key-value store:
- Multiple server nodes
- Clients can set/get values
- Data replication across nodes
- Handle node failures
Summary
In this reading, we explored socket programming fundamentals:
- Sockets: Endpoints for network communication (IP + Port + Protocol)
- Client-Server Model: Server listens, client connects
- TCP Sockets: Reliable, connection-oriented (socket, bind, listen, accept, send/recv, close)
- UDP Sockets: Unreliable, connectionless (socket, bind, sendto/recvfrom)
- Python socket module: Provides complete socket API
- Practical Applications: Chat servers, file transfer, HTTP servers, port scanners
- Best Practices: Error handling, timeouts, proper cleanup, thread safety
Socket programming is the foundation for building networked applications, from simple clients to complex distributed systems.
Key Takeaways
- TCP provides reliability; UDP provides speed
- Always handle exceptions in network code
- recv() may not receive all data at once; loop until complete
- Use threading or async I/O for multiple clients
- Set socket options (SO_REUSEADDR, timeouts) appropriately
- Close sockets properly to free resources
- Security is critical: validate input, use encryption
Next Steps
Congratulations! You've completed Module 8: Networking. You now understand:
- Network fundamentals and the OSI model
- TCP/IP protocol suite and addressing
- Application layer protocols (HTTP, DNS, email)
- Network security basics
- Socket programming in Python
Continue your learning:
- Build real-world networked applications
- Explore asynchronous I/O (asyncio, select, epoll)
- Study network protocols in depth (Wireshark)
- Learn about distributed systems and microservices
- Investigate network security testing and penetration testing
Additional Resources
- Python socket module documentation
- Beej's Guide to Network Programming
- RFC 1180: TCP/IP Tutorial
- "Computer Networking: A Top-Down Approach" by Kurose and Ross
- Wireshark for packet analysis
- asyncio for asynchronous socket programming
- Scapy for packet manipulation
This reading is part of Module 8: Networking
Module 8 Complete!
Return to Module Index or start with 01-fundamentals.md