Network Programming với Python

Tìm hiểu socket programming trong Python - TCP/UDP clients, servers, và network communication.

Network Programming là gì?

Network Programming cho phép Python apps giao tiếp qua mạng. Module socket cung cấp API low-level cho network communication.

Socket Basics

import socket

# Tạo socket
sock = socket.socket(
    socket.AF_INET,      # Address family (IPv4)
    socket.SOCK_STREAM   # Socket type (TCP)
)

# Socket types
# SOCK_STREAM = TCP (reliable, connection-oriented)
# SOCK_DGRAM = UDP (fast, connectionless)

TCP Client

import socket

def tcp_client(host: str, port: int, message: str) -> str:
    """Send message to TCP server and get response."""
    
    # Create socket
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    try:
        # Connect to server
        sock.connect((host, port))
        
        # Send data
        sock.send(message.encode())
        
        # Receive response
        response = sock.recv(4096)
        return response.decode()
    
    finally:
        sock.close()

# Usage
response = tcp_client("example.com", 80, "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
print(response)

TCP Server

import socket
import threading

def handle_client(client_socket, address):
    """Handle client connection."""
    print(f"Connection from {address}")
    
    try:
        while True:
            # Receive data
            data = client_socket.recv(1024)
            if not data:
                break
            
            print(f"Received: {data.decode()}")
            
            # Send response
            response = f"Echo: {data.decode()}"
            client_socket.send(response.encode())
    
    finally:
        client_socket.close()
        print(f"Connection closed: {address}")

def tcp_server(host: str = "0.0.0.0", port: int = 9999):
    """Start TCP server."""
    
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    
    server.bind((host, port))
    server.listen(5)
    
    print(f"Server listening on {host}:{port}")
    
    while True:
        client_socket, address = server.accept()
        
        # Handle client in new thread
        thread = threading.Thread(
            target=handle_client,
            args=(client_socket, address)
        )
        thread.start()

# Run server
# tcp_server()

UDP Client

import socket

def udp_client(host: str, port: int, message: str) -> str:
    """Send UDP packet and receive response."""
    
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.settimeout(5.0)
    
    try:
        # Send data (no connection needed)
        sock.sendto(message.encode(), (host, port))
        
        # Receive response
        data, addr = sock.recvfrom(4096)
        return data.decode()
    
    finally:
        sock.close()

# Example: DNS query (simplified)
# response = udp_client("8.8.8.8", 53, dns_query_bytes)

UDP Server

import socket

def udp_server(host: str = "0.0.0.0", port: int = 9999):
    """Start UDP server."""
    
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.bind((host, port))
    
    print(f"UDP Server listening on {host}:{port}")
    
    while True:
        data, addr = sock.recvfrom(1024)
        print(f"Received from {addr}: {data.decode()}")
        
        # Send response
        response = f"ACK: {data.decode()}"
        sock.sendto(response.encode(), addr)

HTTP Client (Low-level)

import socket
import ssl

def http_get(host: str, path: str = "/", port: int = 80, https: bool = False) -> str:
    """Make HTTP GET request."""
    
    # Create socket
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    # Wrap with SSL for HTTPS
    if https:
        port = 443
        context = ssl.create_default_context()
        sock = context.wrap_socket(sock, server_hostname=host)
    
    try:
        sock.connect((host, port))
        
        # Build HTTP request
        request = f"GET {path} HTTP/1.1\r\n"
        request += f"Host: {host}\r\n"
        request += "Connection: close\r\n"
        request += "\r\n"
        
        sock.send(request.encode())
        
        # Receive response
        response = b""
        while True:
            chunk = sock.recv(4096)
            if not chunk:
                break
            response += chunk
        
        return response.decode()
    
    finally:
        sock.close()

# Usage
response = http_get("example.com", "/", https=True)
print(response[:500])
import socket

def grab_banner(host: str, port: int, timeout: float = 2.0) -> str | None:
    """Grab service banner from port."""
    
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.settimeout(timeout)
    
    try:
        sock.connect((host, port))
        
        # Some services send banner immediately
        banner = sock.recv(1024)
        
        if not banner:
            # Try sending something to trigger response
            sock.send(b"HEAD / HTTP/1.0\r\n\r\n")
            banner = sock.recv(1024)
        
        return banner.decode(errors='ignore').strip()
    
    except (socket.timeout, ConnectionRefusedError, OSError):
        return None
    
    finally:
        sock.close()

# Grab banners from common ports
ports = [21, 22, 25, 80, 443]
for port in ports:
    banner = grab_banner("scanme.nmap.org", port)
    if banner:
        print(f"Port {port}: {banner[:50]}")

DNS Resolution

import socket

def resolve_hostname(hostname: str) -> dict:
    """Resolve hostname to IP addresses."""
    
    result = {
        "hostname": hostname,
        "ipv4": [],
        "ipv6": []
    }
    
    try:
        # Get all address info
        infos = socket.getaddrinfo(hostname, None)
        
        for info in infos:
            family, _, _, _, addr = info
            ip = addr[0]
            
            if family == socket.AF_INET:
                if ip not in result["ipv4"]:
                    result["ipv4"].append(ip)
            elif family == socket.AF_INET6:
                if ip not in result["ipv6"]:
                    result["ipv6"].append(ip)
    
    except socket.gaierror as e:
        result["error"] = str(e)
    
    return result

# Usage
info = resolve_hostname("google.com")
print(f"IPv4: {info['ipv4']}")
print(f"IPv6: {info['ipv6']}")

# Reverse DNS
def reverse_dns(ip: str) -> str | None:
    """Reverse DNS lookup."""
    try:
        hostname, _, _ = socket.gethostbyaddr(ip)
        return hostname
    except socket.herror:
        return None

Network Scanner

import socket
from concurrent.futures import ThreadPoolExecutor

def check_port(host: str, port: int, timeout: float = 1.0) -> tuple:
    """Check if port is open."""
    
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.settimeout(timeout)
    
    try:
        result = sock.connect_ex((host, port))
        return (port, result == 0)
    finally:
        sock.close()

def scan_ports(host: str, ports: list, threads: int = 100) -> list:
    """Scan multiple ports with threading."""
    
    open_ports = []
    
    with ThreadPoolExecutor(max_workers=threads) as executor:
        futures = [executor.submit(check_port, host, port) for port in ports]
        
        for future in futures:
            port, is_open = future.result()
            if is_open:
                open_ports.append(port)
    
    return sorted(open_ports)

# Usage
if __name__ == "__main__":
    target = "scanme.nmap.org"
    ports = list(range(1, 1025))
    
    print(f"Scanning {target}...")
    open_ports = scan_ports(target, ports)
    
    print(f"Open ports: {open_ports}")

Proxy SOCKS Support

import socks
import socket

# pip install PySocks

def connect_via_proxy(
    host: str, 
    port: int, 
    proxy_host: str = "127.0.0.1",
    proxy_port: int = 9050  # Tor default
) -> socket.socket:
    """Connect through SOCKS proxy."""
    
    sock = socks.socksocket()
    sock.set_proxy(socks.SOCKS5, proxy_host, proxy_port)
    sock.connect((host, port))
    
    return sock

# Global proxy (affects all sockets)
# socks.set_default_proxy(socks.SOCKS5, "127.0.0.1", 9050)
# socket.socket = socks.socksocket

Bước tiếp theo

Tiếp theo trong khóa học:

  • Web Scraping: BeautifulSoup và requests
  • Port Scanner: Xây dựng tool quét port hoàn chỉnh

💡 Pro tip: Luôn set timeout để tránh blocking vô hạn!