13、Python网络编程

4 阅读31分钟

Python网络编程

1. 网络编程基础

作为Java开发者,您可能已经使用过Java的网络编程API,如Socket和ServerSocket。Python同样提供了强大的网络编程功能,允许您创建客户端和服务器应用程序,处理各种网络协议。

网络模型

在深入Python网络编程之前,让我们回顾一下网络通信的基本模型:

  1. OSI七层模型
    • 物理层:处理比特流的传输
    • 数据链路层:处理帧的传输
    • 网络层:处理数据包的路由(IP协议)
    • 传输层:处理端到端的连接(TCP/UDP协议)
    • 会话层:管理会话
    • 表示层:处理数据格式转换
    • 应用层:为应用程序提供网络服务(HTTP、FTP等)
graph TD
    A[应用层] -->|数据| B[表示层]
    B -->|数据| C[会话层]
    C -->|数据| D[传输层]
    D -->|数据包| E[网络层]
    E -->|数据包| F[数据链路层]
    F -->|帧| G[物理层]
    G -->|比特流| H[物理介质]
    H -->|比特流| I[物理层]
    I -->|帧| J[数据链路层]
    J -->|数据包| K[网络层]
    K -->|数据包| L[传输层]
    L -->|数据| M[会话层]
    M -->|数据| N[表示层]
    N -->|数据| O[应用层]
    
    style A fill:#f9f,stroke:#333,stroke-width:2px
    style B fill:#bbf,stroke:#333,stroke-width:2px
    style C fill:#dfd,stroke:#333,stroke-width:2px
    style D fill:#fdd,stroke:#333,stroke-width:2px
    style E fill:#ddf,stroke:#333,stroke-width:2px
    style F fill:#ffd,stroke:#333,stroke-width:2px
    style G fill:#dff,stroke:#333,stroke-width:2px
    style H fill:#ccc,stroke:#333,stroke-width:2px
    style I fill:#dff,stroke:#333,stroke-width:2px
    style J fill:#ffd,stroke:#333,stroke-width:2px
    style K fill:#ddf,stroke:#333,stroke-width:2px
    style L fill:#fdd,stroke:#333,stroke-width:2px
    style M fill:#dfd,stroke:#333,stroke-width:2px
    style N fill:#bbf,stroke:#333,stroke-width:2px
    style O fill:#f9f,stroke:#333,stroke-width:2px
  1. TCP/IP四层模型
    • 网络接口层:对应OSI的物理层和数据链路层
    • 网络层:对应OSI的网络层(IP协议)
    • 传输层:对应OSI的传输层(TCP/UDP协议)
    • 应用层:对应OSI的会话层、表示层和应用层

常见网络协议

  1. TCP (传输控制协议)

    • 面向连接的协议
    • 提供可靠的数据传输
    • 具有流量控制和拥塞控制
    • 适用于需要可靠传输的应用(如HTTP、FTP、SMTP)
  2. UDP (用户数据报协议)

    • 无连接的协议
    • 不保证数据传输的可靠性
    • 低开销,高效率
    • 适用于实时应用(如视频流、游戏、DNS)
  3. HTTP (超文本传输协议)

    • 应用层协议,基于TCP
    • 用于Web浏览器和服务器之间的通信
    • 无状态协议
    • HTTP/1.1、HTTP/2、HTTP/3版本
  4. WebSocket

    • 提供全双工通信通道
    • 基于TCP的应用层协议
    • 用于Web应用中的实时通信
  5. SMTP (简单邮件传输协议)

    • 用于发送电子邮件
    • 基于TCP的应用层协议
  6. FTP (文件传输协议)

    • 用于在客户端和服务器之间传输文件
    • 使用两个TCP连接:控制连接和数据连接

2. Python Socket编程

Socket是网络编程的基础,它提供了一种在网络上发送和接收数据的方法。Python的socket模块提供了对底层Socket API的访问。

创建Socket

import socket

# 创建TCP Socket
tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 创建UDP Socket
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

参数说明:

  • socket.AF_INET:使用IPv4地址族
  • socket.AF_INET6:使用IPv6地址族
  • socket.SOCK_STREAM:使用TCP协议
  • socket.SOCK_DGRAM:使用UDP协议

TCP客户端

import socket

def tcp_client():
    # 创建Socket
    client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    try:
        # 连接服务器
        server_address = ('localhost', 8000)
        print(f"连接到 {server_address[0]}:{server_address[1]}")
        client_socket.connect(server_address)
        
        # 发送数据
        message = "Hello, Server!"
        print(f"发送: {message}")
        client_socket.sendall(message.encode('utf-8'))
        
        # 接收响应
        data = client_socket.recv(1024)
        print(f"接收: {data.decode('utf-8')}")
        
    finally:
        # 关闭连接
        print("关闭连接")
        client_socket.close()

if __name__ == "__main__":
    tcp_client()

TCP服务器

import socket

def tcp_server():
    # 创建Socket
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    # 绑定地址和端口
    server_address = ('localhost', 8000)
    print(f"启动服务器 {server_address[0]}:{server_address[1]}")
    server_socket.bind(server_address)
    
    # 监听连接
    server_socket.listen(5)  # 最多5个排队连接
    
    try:
        while True:
            # 等待连接
            print("等待连接...")
            client_socket, client_address = server_socket.accept()
            print(f"接受来自 {client_address[0]}:{client_address[1]} 的连接")
            
            try:
                # 接收数据
                data = client_socket.recv(1024)
                print(f"接收: {data.decode('utf-8')}")
                
                if data:
                    # 发送响应
                    response = "Hello, Client!"
                    print(f"发送: {response}")
                    client_socket.sendall(response.encode('utf-8'))
                else:
                    print("没有数据")
                    break
                    
            finally:
                # 关闭客户端连接
                print("关闭客户端连接")
                client_socket.close()
                
    finally:
        # 关闭服务器Socket
        print("关闭服务器")
        server_socket.close()

if __name__ == "__main__":
    tcp_server()

UDP客户端

import socket

def udp_client():
    # 创建Socket
    client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    
    try:
        # 服务器地址
        server_address = ('localhost', 8000)
        
        # 发送数据
        message = "Hello, UDP Server!"
        print(f"发送: {message}")
        client_socket.sendto(message.encode('utf-8'), server_address)
        
        # 接收响应
        data, server = client_socket.recvfrom(1024)
        print(f"从 {server[0]}:{server[1]} 接收: {data.decode('utf-8')}")
        
    finally:
        # 关闭Socket
        print("关闭Socket")
        client_socket.close()

if __name__ == "__main__":
    udp_client()

UDP服务器

import socket

def udp_server():
    # 创建Socket
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    
    # 绑定地址和端口
    server_address = ('localhost', 8000)
    print(f"启动UDP服务器 {server_address[0]}:{server_address[1]}")
    server_socket.bind(server_address)
    
    try:
        while True:
            # 接收数据
            print("等待消息...")
            data, client_address = server_socket.recvfrom(1024)
            print(f"从 {client_address[0]}:{client_address[1]} 接收: {data.decode('utf-8')}")
            
            # 发送响应
            response = "Hello, UDP Client!"
            print(f"发送: {response}")
            server_socket.sendto(response.encode('utf-8'), client_address)
            
    finally:
        # 关闭服务器Socket
        print("关闭服务器")
        server_socket.close()

if __name__ == "__main__":
    udp_server()

Socket选项

可以使用setsockopt方法设置Socket选项:

import socket

# 创建Socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 设置地址重用选项
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

# 设置超时
client_socket.settimeout(5.0)  # 5秒超时

非阻塞Socket

默认情况下,Socket操作是阻塞的。可以设置为非阻塞模式:

import socket

# 创建Socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 设置为非阻塞模式
client_socket.setblocking(False)

try:
    client_socket.connect(('localhost', 8000))
except BlockingIOError:
    # 连接操作正在进行中
    pass

# 使用select模块处理非阻塞I/O
import select

readable, writable, exceptional = select.select([client_socket], [client_socket], [client_socket], 5.0)

if writable:
    print("连接就绪")
    client_socket.send(b"Hello")

3. 高级Socket编程

使用socketserver模块

Python的socketserver模块提供了更高级的服务器实现,简化了服务器编程:

import socketserver

class MyTCPHandler(socketserver.BaseRequestHandler):
    """
    处理TCP请求的类
    """
    def handle(self):
        # self.request是TCP套接字
        data = self.request.recv(1024).strip()
        print(f"{self.client_address[0]} 发送: {data.decode('utf-8')}")
        
        # 发送响应
        response = "已收到您的消息"
        self.request.sendall(response.encode('utf-8'))

if __name__ == "__main__":
    HOST, PORT = "localhost", 8000
    
    # 创建服务器
    server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)
    
    # 启动服务器
    print(f"服务器启动在 {HOST}:{PORT}")
    server.serve_forever()

多线程服务器

使用ThreadingMixIn创建多线程服务器:

import socketserver

class ThreadedTCPHandler(socketserver.BaseRequestHandler):
    def handle(self):
        data = self.request.recv(1024).strip()
        print(f"线程 {threading.current_thread().name} 处理来自 {self.client_address[0]} 的请求")
        print(f"接收: {data.decode('utf-8')}")
        
        response = f"线程 {threading.current_thread().name} 已处理您的请求"
        self.request.sendall(response.encode('utf-8'))

class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
    pass

if __name__ == "__main__":
    import threading
    
    HOST, PORT = "localhost", 8000
    server = ThreadedTCPServer((HOST, PORT), ThreadedTCPHandler)
    
    # 启动线程处理请求
    server_thread = threading.Thread(target=server.serve_forever)
    server_thread.daemon = True
    server_thread.start()
    print(f"服务器启动在 {HOST}:{PORT}")
    print(f"服务器线程名: {server_thread.name}")
    
    # 保持主线程运行
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        server.shutdown()

异步I/O

使用asyncio模块实现异步网络编程:

import asyncio

async def handle_client(reader, writer):
    # 获取客户端地址
    addr = writer.get_extra_info('peername')
    print(f"连接来自 {addr}")
    
    # 读取数据
    data = await reader.read(100)
    message = data.decode('utf-8')
    print(f"接收: {message}")
    
    # 发送响应
    response = f"已收到: {message}"
    print(f"发送: {response}")
    writer.write(response.encode('utf-8'))
    await writer.drain()
    
    # 关闭连接
    print("关闭连接")
    writer.close()
    await writer.wait_closed()

async def main():
    # 启动服务器
    server = await asyncio.start_server(
        handle_client, '127.0.0.1', 8000)
    
    addr = server.sockets[0].getsockname()
    print(f'服务器启动在 {addr}')
    
    async with server:
        await server.serve_forever()

if __name__ == "__main__":
    asyncio.run(main())

4. HTTP客户端编程

Python提供了多种方式来发送HTTP请求。

使用urllib

urllib是Python标准库中的HTTP客户端:

from urllib import request, parse
import json

def fetch_data():
    # GET请求
    with request.urlopen('https://api.example.com/data') as response:
        data = response.read().decode('utf-8')
        return json.loads(data)

def post_data():
    # POST请求数据
    data = {
        'name': 'John Doe',
        'email': 'john@example.com'
    }
    data = parse.urlencode(data).encode('utf-8')
    
    # 创建请求
    req = request.Request('https://api.example.com/submit', data=data, method='POST')
    req.add_header('Content-Type', 'application/x-www-form-urlencoded')
    
    # 发送请求
    with request.urlopen(req) as response:
        result = response.read().decode('utf-8')
        return json.loads(result)

使用requests库

requests是一个更简单、更强大的HTTP客户端库:

import requests

def fetch_data():
    # GET请求
    response = requests.get('https://api.example.com/data')
    response.raise_for_status()  # 如果请求失败则抛出异常
    return response.json()

def post_data():
    # POST请求
    data = {
        'name': 'John Doe',
        'email': 'john@example.com'
    }
    response = requests.post('https://api.example.com/submit', data=data)
    response.raise_for_status()
    return response.json()

def upload_file():
    # 上传文件
    files = {'file': open('document.pdf', 'rb')}
    response = requests.post('https://api.example.com/upload', files=files)
    response.raise_for_status()
    return response.json()

def custom_headers():
    # 自定义请求头
    headers = {
        'User-Agent': 'My Python Client',
        'Authorization': 'Bearer token123'
    }
    response = requests.get('https://api.example.com/protected', headers=headers)
    response.raise_for_status()
    return response.json()

def with_cookies():
    # 使用会话保持Cookie
    session = requests.Session()
    
    # 登录
    login_data = {'username': 'user', 'password': 'pass'}
    session.post('https://example.com/login', data=login_data)
    
    # 访问需要认证的页面
    response = session.get('https://example.com/dashboard')
    return response.text

def with_timeout():
    # 设置超时
    try:
        response = requests.get('https://api.example.com/data', timeout=5)
        response.raise_for_status()
        return response.json()
    except requests.Timeout:
        print("请求超时")
        return None

异步HTTP请求

使用aiohttp库进行异步HTTP请求:

import aiohttp
import asyncio

async def fetch(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            return await response.text()

async def fetch_multiple(urls):
    tasks = [fetch(url) for url in urls]
    return await asyncio.gather(*tasks)

async def main():
    urls = [
        'https://api.example.com/data1',
        'https://api.example.com/data2',
        'https://api.example.com/data3'
    ]
    results = await fetch_multiple(urls)
    for url, result in zip(urls, results):
        print(f"URL: {url}, 长度: {len(result)}")

if __name__ == "__main__":
    asyncio.run(main())

5. HTTP服务器编程

使用http.server模块

Python的http.server模块提供了简单的HTTP服务器实现:

from http.server import HTTPServer, BaseHTTPRequestHandler
import json

class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
    def _set_headers(self, content_type='text/html'):
        self.send_response(200)
        self.send_header('Content-type', content_type)
        self.end_headers()
    
    def do_GET(self):
        if self.path == '/':
            self._set_headers()
            self.wfile.write(b"<html><body><h1>Welcome to my server!</h1></body></html>")
        elif self.path == '/api/data':
            self._set_headers('application/json')
            data = {'message': 'Hello, World!', 'status': 'success'}
            self.wfile.write(json.dumps(data).encode('utf-8'))
        else:
            self.send_response(404)
            self.end_headers()
            self.wfile.write(b"404 Not Found")
    
    def do_POST(self):
        if self.path == '/api/submit':
            content_length = int(self.headers['Content-Length'])
            post_data = self.rfile.read(content_length)
            
            # 解析JSON数据
            try:
                data = json.loads(post_data.decode('utf-8'))
                
                # 处理数据
                response = {'status': 'success', 'received': data}
                
                # 发送响应
                self._set_headers('application/json')
                self.wfile.write(json.dumps(response).encode('utf-8'))
            except json.JSONDecodeError:
                self.send_response(400)
                self.end_headers()
                self.wfile.write(b"Invalid JSON")
        else:
            self.send_response(404)
            self.end_headers()
            self.wfile.write(b"404 Not Found")

def run_server(server_class=HTTPServer, handler_class=SimpleHTTPRequestHandler, port=8000):
    server_address = ('', port)
    httpd = server_class(server_address, handler_class)
    print(f"Starting server on port {port}...")
    httpd.serve_forever()

if __name__ == "__main__":
    run_server()

使用Flask框架

Flask是一个轻量级的Web框架,适合构建API和Web应用:

from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/')
def home():
    return "<h1>Welcome to my API</h1>"

@app.route('/api/data')
def get_data():
    data = {'message': 'Hello, World!', 'status': 'success'}
    return jsonify(data)

@app.route('/api/submit', methods=['POST'])
def submit_data():
    if request.is_json:
        data = request.get_json()
        # 处理数据
        response = {'status': 'success', 'received': data}
        return jsonify(response)
    else:
        return jsonify({'error': 'Invalid JSON'}), 400

if __name__ == '__main__':
    app.run(debug=True, port=8000)

使用FastAPI框架

FastAPI是一个现代、高性能的Web框架,支持异步请求处理和自动API文档生成:

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import uvicorn

app = FastAPI()

class Item(BaseModel):
    name: str
    description: str = None
    price: float
    tax: float = None

@app.get("/")
def read_root():
    return {"Hello": "World"}

@app.get("/items/{item_id}")
def read_item(item_id: int, q: str = None):
    return {"item_id": item_id, "q": q}

@app.post("/items/")
def create_item(item: Item):
    return item

@app.put("/items/{item_id}")
def update_item(item_id: int, item: Item):
    if item_id < 0:
        raise HTTPException(status_code=400, detail="Item ID must be positive")
    return {"item_id": item_id, **item.dict()}

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

6. WebSocket编程

WebSocket提供了全双工通信通道,适用于需要实时通信的Web应用。

使用websockets库

import asyncio
import websockets

# WebSocket服务器
async def echo(websocket, path):
    async for message in websocket:
        print(f"接收: {message}")
        await websocket.send(f"Echo: {message}")

async def main():
    async with websockets.serve(echo, "localhost", 8765):
        await asyncio.Future()  # 运行直到被取消

if __name__ == "__main__":
    asyncio.run(main())

WebSocket客户端

import asyncio
import websockets

async def hello():
    uri = "ws://localhost:8765"
    async with websockets.connect(uri) as websocket:
        message = "Hello WebSocket!"
        await websocket.send(message)
        print(f"发送: {message}")
        
        response = await websocket.recv()
        print(f"接收: {response}")

if __name__ == "__main__":
    asyncio.run(hello())

使用Flask-SocketIO

Flask-SocketIO是Flask的WebSocket扩展:

from flask import Flask, render_template
from flask_socketio import SocketIO, emit

app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app)

@app.route('/')
def index():
    return render_template('index.html')

@socketio.on('connect')
def test_connect():
    emit('response', {'data': 'Connected'})

@socketio.on('message')
def handle_message(data):
    print('received message: ' + data)
    emit('response', {'data': f'Server received: {data}'})

@socketio.on('disconnect')
def test_disconnect():
    print('Client disconnected')

if __name__ == '__main__':
    socketio.run(app, debug=True)

HTML客户端:

<!DOCTYPE html>
<html>
<head>
    <title>Flask-SocketIO Test</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script>
    <script>
        document.addEventListener('DOMContentLoaded', () => {
            const socket = io();
            
            socket.on('connect', () => {
                console.log('Connected to server');
            });
            
            socket.on('response', (data) => {
                console.log('Server response:', data);
                document.getElementById('messages').innerHTML += '<p>' + data.data + '</p>';
            });
            
            document.getElementById('send').addEventListener('click', () => {
                const message = document.getElementById('message').value;
                socket.emit('message', message);
                document.getElementById('message').value = '';
            });
        });
    </script>
</head>
<body>
    <h1>Flask-SocketIO Test</h1>
    <div id="messages"></div>
    <input type="text" id="message" placeholder="Enter message">
    <button id="send">Send</button>
</body>
</html>

7. 电子邮件编程

Python提供了发送和接收电子邮件的功能。

使用smtplib发送邮件

import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

def send_email():
    # 邮件服务器配置
    smtp_server = "smtp.gmail.com"
    port = 587
    sender_email = "your_email@gmail.com"
    password = "your_password"
    receiver_email = "recipient@example.com"
    
    # 创建多部分消息
    message = MIMEMultipart()
    message["Subject"] = "Python邮件测试"
    message["From"] = sender_email
    message["To"] = receiver_email
    
    # 添加正文
    body = "这是一封由Python发送的测试邮件。"
    message.attach(MIMEText(body, "plain"))
    
    try:
        # 创建SMTP会话
        server = smtplib.SMTP(smtp_server, port)
        server.ehlo()  # 向SMTP服务器标识自己
        server.starttls()  # 启用TLS加密
        server.ehlo()  # 重新标识
        server.login(sender_email, password)
        
        # 发送邮件
        server.sendmail(sender_email, receiver_email, message.as_string())
        print("邮件发送成功")
    except Exception as e:
        print(f"邮件发送失败: {e}")
    finally:
        server.quit()

if __name__ == "__main__":
    send_email()

发送HTML邮件和附件

import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication
import os

def send_email_with_attachment():
    # 邮件服务器配置
    smtp_server = "smtp.gmail.com"
    port = 587
    sender_email = "your_email@gmail.com"
    password = "your_password"
    receiver_email = "recipient@example.com"
    
    # 创建多部分消息
    message = MIMEMultipart()
    message["Subject"] = "Python邮件测试 - 带附件"
    message["From"] = sender_email
    message["To"] = receiver_email
    
    # 添加HTML正文
    html = """
    <html>
      <body>
        <h1>Python邮件测试</h1>
        <p>这是一封由<b>Python</b>发送的测试邮件,包含<i>HTML</i>格式和附件。</p>
      </body>
    </html>
    """
    message.attach(MIMEText(html, "html"))
    
    # 添加附件
    filename = "document.pdf"  # 替换为实际文件路径
    with open(filename, "rb") as f:
        attachment = MIMEApplication(f.read(), _subtype="pdf")
        attachment.add_header('Content-Disposition', 'attachment', filename=os.path.basename(filename))
        message.attach(attachment)
    
    try:
        # 创建SMTP会话
        server = smtplib.SMTP(smtp_server, port)
        server.ehlo()
        server.starttls()
        server.ehlo()
        server.login(sender_email, password)
        
        # 发送邮件
        server.sendmail(sender_email, receiver_email, message.as_string())
        print("邮件发送成功")
    except Exception as e:
        print(f"邮件发送失败: {e}")
    finally:
        server.quit()

使用imaplib接收邮件

import imaplib
import email
from email.header import decode_header

def fetch_emails():
    # 邮件服务器配置
    imap_server = "imap.gmail.com"
    email_address = "your_email@gmail.com"
    password = "your_password"
    
    try:
        # 连接到IMAP服务器
        mail = imaplib.IMAP4_SSL(imap_server)
        mail.login(email_address, password)
        
        # 选择收件箱
        mail.select("INBOX")
        
        # 搜索邮件
        status, messages = mail.search(None, "UNSEEN")  # 未读邮件
        
        if status == "OK":
            # 获取邮件ID列表
            email_ids = messages[0].split()
            
            for email_id in email_ids:
                # 获取邮件内容
                status, msg_data = mail.fetch(email_id, "(RFC822)")
                
                if status == "OK":
                    # 解析邮件
                    msg = email.message_from_bytes(msg_data[0][1])
                    
                    # 解码主题
                    subject, encoding = decode_header(msg["Subject"])[0]
                    if isinstance(subject, bytes):
                        subject = subject.decode(encoding or "utf-8")
                    
                    # 获取发件人
                    from_addr = msg.get("From")
                    print(f"From: {from_addr}")
                    print(f"Subject: {subject}")
                    
                    # 获取邮件内容
                    if msg.is_multipart():
                        # 处理多部分邮件
                        for part in msg.walk():
                            content_type = part.get_content_type()
                            content_disposition = str(part.get("Content-Disposition"))
                            
                            # 跳过附件
                            if "attachment" in content_disposition:
                                filename = part.get_filename()
                                if filename:
                                    print(f"附件: {filename}")
                            
                            # 获取文本内容
                            if content_type == "text/plain" and "attachment" not in content_disposition:
                                body = part.get_payload(decode=True).decode()
                                print(f"Body: {body}")
                    else:
                        # 处理纯文本邮件
                        body = msg.get_payload(decode=True).decode()
                        print(f"Body: {body}")
                    
                    # 标记为已读
                    mail.store(email_id, "+FLAGS", "\\Seen")
        
        # 关闭连接
        mail.close()
        mail.logout()
    
    except Exception as e:
        print(f"接收邮件失败: {e}")

if __name__ == "__main__":
    fetch_emails()

8. FTP编程

Python的ftplib模块提供了FTP客户端功能。

FTP客户端

from ftplib import FTP

def ftp_operations():
    # 连接到FTP服务器
    ftp = FTP('ftp.example.com')
    ftp.login(user='username', passwd='password')
    
    # 显示当前目录
    print(f"当前目录: {ftp.pwd()}")
    
    # 列出目录内容
    files = ftp.nlst()
    print("目录内容:")
    for file in files:
        print(f"  {file}")
    
    # 切换目录
    ftp.cwd('/public')
    print(f"切换到目录: {ftp.pwd()}")
    
    # 下载文件
    with open('downloaded_file.txt', 'wb') as local_file:
        ftp.retrbinary('RETR example.txt', local_file.write)
    
    # 上传文件
    with open('local_file.txt', 'rb') as local_file:
        ftp.storbinary('STOR uploaded_file.txt', local_file)
    
    # 关闭连接
    ftp.quit()

if __name__ == "__main__":
    ftp_operations()

使用ftplib创建FTP服务器

Python标准库没有直接提供FTP服务器实现,但可以使用第三方库如pyftpdlib

from pyftpdlib.authorizers import DummyAuthorizer
from pyftpdlib.handlers import FTPHandler
from pyftpdlib.servers import FTPServer

def run_ftp_server():
    # 创建用户认证器
    authorizer = DummyAuthorizer()
    
    # 添加用户,设置权限
    # 参数: 用户名, 密码, 主目录, 权限
    authorizer.add_user("user", "password", "./ftp_home", perm="elradfmwMT")
    
    # 添加匿名用户
    authorizer.add_anonymous("./ftp_home", perm="elr")
    
    # 创建处理器
    handler = FTPHandler
    handler.authorizer = authorizer
    
    # 设置欢迎消息
    handler.banner = "欢迎使用Python FTP服务器"
    
    # 创建服务器
    address = ('', 2121)  # 空字符串表示所有可用地址
    server = FTPServer(address, handler)
    
    # 设置最大连接数
    server.max_cons = 256
    server.max_cons_per_ip = 5
    
    # 启动服务器
    print(f"FTP服务器启动在端口 {address[1]}")
    server.serve_forever()

if __name__ == "__main__":
    run_ftp_server()

9. 网络安全编程

SSL/TLS加密通信

使用ssl模块实现加密通信:

import socket
import ssl

def ssl_client():
    # 创建普通Socket
    client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    # 包装为SSL Socket
    context = ssl.create_default_context()
    ssl_socket = context.wrap_socket(client_socket, server_hostname='example.com')
    
    try:
        # 连接服务器
        ssl_socket.connect(('example.com', 443))
        
        # 获取证书信息
        cert = ssl_socket.getpeercert()
        print(f"服务器证书: {cert}")
        
        # 发送HTTP请求
        request = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
        ssl_socket.send(request.encode('utf-8'))
        
        # 接收响应
        response = ssl_socket.recv(4096)
        print(response.decode('utf-8'))
        
    finally:
        # 关闭连接
        ssl_socket.close()

def ssl_server():
    # 创建SSL上下文
    context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
    
    # 加载证书和私钥
    context.load_cert_chain(certfile='server.crt', keyfile='server.key')
    
    # 创建Socket
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.bind(('localhost', 8443))
    server_socket.listen(5)
    
    # 包装为SSL Socket
    ssl_socket = context.wrap_socket(server_socket, server_side=True)
    
    try:
        print("等待连接...")
        client_socket, addr = ssl_socket.accept()
        print(f"连接来自: {addr}")
        
        # 接收数据
        data = client_socket.recv(1024)
        print(f"接收: {data.decode('utf-8')}")
        
        # 发送响应
        response = "Hello, secure world!"
        client_socket.send(response.encode('utf-8'))
        
        # 关闭客户端连接
        client_socket.close()
        
    finally:
        # 关闭服务器
        ssl_socket.close()

使用cryptography库进行加密

from cryptography.fernet import Fernet

def encrypt_decrypt_data():
    # 生成密钥
    key = Fernet.generate_key()
    cipher = Fernet(key)
    
    # 加密数据
    message = "敏感信息"
    encrypted = cipher.encrypt(message.encode('utf-8'))
    print(f"加密后: {encrypted}")
    
    # 解密数据
    decrypted = cipher.decrypt(encrypted).decode('utf-8')
    print(f"解密后: {decrypted}")

def secure_network_communication():
    # 在实际应用中,发送方和接收方需要安全地共享密钥
    key = Fernet.generate_key()
    cipher = Fernet(key)
    
    # 发送方加密数据
    message = "这是一条安全消息"
    encrypted = cipher.encrypt(message.encode('utf-8'))
    
    # 通过网络发送加密数据
    # network.send(encrypted)
    
    # 接收方解密数据
    # received_data = network.receive()
    received_data = encrypted  # 模拟接收
    decrypted = cipher.decrypt(received_data).decode('utf-8')
    print(f"接收到的消息: {decrypted}")

哈希和数字签名

import hashlib
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding, rsa

def calculate_hash():
    # 使用hashlib计算哈希
    data = "重要数据".encode('utf-8')
    
    # MD5 (不推荐用于安全目的)
    md5_hash = hashlib.md5(data).hexdigest()
    print(f"MD5: {md5_hash}")
    
    # SHA-256
    sha256_hash = hashlib.sha256(data).hexdigest()
    print(f"SHA-256: {sha256_hash}")
    
    # SHA-512
    sha512_hash = hashlib.sha512(data).hexdigest()
    print(f"SHA-512: {sha512_hash}")

def digital_signature():
    # 生成RSA密钥对
    private_key = rsa.generate_private_key(
        public_exponent=65537,
        key_size=2048
    )
    public_key = private_key.public_key()
    
    # 要签名的消息
    message = b"Sign this message"
    
    # 创建签名
    signature = private_key.sign(
        message,
        padding.PSS(
            mgf=padding.MGF1(hashes.SHA256()),
            salt_length=padding.PSS.MAX_LENGTH
        ),
        hashes.SHA256()
    )
    print(f"签名: {signature.hex()}")
    
    # 验证签名
    try:
        public_key.verify(
            signature,
            message,
            padding.PSS(
                mgf=padding.MGF1(hashes.SHA256()),
                salt_length=padding.PSS.MAX_LENGTH
            ),
            hashes.SHA256()
        )
        print("签名验证成功")
    except Exception as e:
        print(f"签名验证失败: {e}")

10. 网络编程最佳实践

错误处理

在网络编程中,错误处理至关重要:

import socket
import time

def robust_client():
    max_retries = 3
    retry_delay = 2
    retries = 0
    
    while retries < max_retries:
        try:
            # 创建Socket
            client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            client_socket.settimeout(5)  # 设置超时
            
            # 连接服务器
            client_socket.connect(('example.com', 80))
            
            # 发送数据
            client_socket.sendall(b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
            
            # 接收响应
            response = b""
            while True:
                try:
                    data = client_socket.recv(4096)
                    if not data:
                        break
                    response += data
                except socket.timeout:
                    print("接收超时")
                    break
            
            print(f"接收到 {len(response)} 字节")
            return response
            
        except socket.timeout:
            print(f"连接超时,重试 {retries + 1}/{max_retries}")
        except ConnectionRefusedError:
            print(f"连接被拒绝,重试 {retries + 1}/{max_retries}")
        except socket.error as e:
            print(f"Socket错误: {e}, 重试 {retries + 1}/{max_retries}")
        finally:
            client_socket.close()
        
        retries += 1
        time.sleep(retry_delay)
    
    print("达到最大重试次数,放弃")
    return None

资源管理

使用上下文管理器确保资源正确关闭:

import socket
from contextlib import contextmanager

@contextmanager
def tcp_connection(host, port):
    """Socket连接的上下文管理器"""
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    try:
        sock.connect((host, port))
        yield sock
    finally:
        sock.close()

# 使用上下文管理器
def safe_client():
    try:
        with tcp_connection('example.com', 80) as sock:
            sock.sendall(b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
            response = sock.recv(4096)
            print(response.decode('utf-8'))
    except Exception as e:
        print(f"错误: {e}")

并发处理

使用多线程或异步I/O处理多个连接:

import threading
import socket

def handle_client(client_socket, address):
    """处理客户端连接的线程函数"""
    try:
        print(f"处理来自 {address} 的连接")
        
        # 接收数据
        data = client_socket.recv(1024)
        print(f"接收: {data.decode('utf-8')}")
        
        # 发送响应
        response = "Hello from server!"
        client_socket.sendall(response.encode('utf-8'))
        
    except Exception as e:
        print(f"处理客户端时出错: {e}")
    finally:
        client_socket.close()

def threaded_server():
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    
    server_address = ('localhost', 8000)
    server_socket.bind(server_address)
    server_socket.listen(5)
    print(f"服务器启动在 {server_address}")
    
    try:
        while True:
            client_socket, client_address = server_socket.accept()
            
            # 为每个客户端创建新线程
            client_thread = threading.Thread(
                target=handle_client,
                args=(client_socket, client_address)
            )
            client_thread.daemon = True
            client_thread.start()
            
    except KeyboardInterrupt:
        print("服务器关闭")
    finally:
        server_socket.close()

性能优化

提高网络应用性能的技巧:

  1. 使用连接池:重用连接而不是每次创建新连接
  2. 批量处理:合并多个小请求为一个大请求
  3. 压缩数据:减少传输的数据量
  4. 异步I/O:避免阻塞等待I/O操作
  5. 负载均衡:分散请求到多个服务器
# 使用连接池示例
import urllib3

def connection_pool_example():
    # 创建连接池
    http = urllib3.PoolManager(maxsize=10)
    
    # 发送多个请求
    urls = [
        "http://example.com",
        "http://example.org",
        "http://example.net"
    ]
    
    for url in urls:
        response = http.request('GET', url)
        print(f"URL: {url}, 状态: {response.status}")

安全最佳实践

  1. 始终使用TLS/SSL:加密敏感数据传输
  2. 验证证书:防止中间人攻击
  3. 输入验证:防止注入攻击
  4. 使用最新的加密算法:避免使用已知有漏洞的算法
  5. 定期更新依赖库:修复安全漏洞
import requests

def secure_request():
    # 验证证书
    response = requests.get('https://example.com', verify=True)
    
    # 设置超时
    response = requests.get('https://example.com', timeout=5)
    
    # 使用会话保持连接
    session = requests.Session()
    session.verify = True  # 验证证书
    response = session.get('https://example.com')

11. 实际应用案例

简单的聊天服务器和客户端

服务器端

import socket
import threading
import time

class ChatServer:
    def __init__(self, host='localhost', port=9090):
        self.host = host
        self.port = port
        self.server_socket = None
        self.clients = []
        self.nicknames = []
        
    def start(self):
        self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.server_socket.bind((self.host, self.port))
        self.server_socket.listen(5)
        
        print(f"聊天服务器启动在 {self.host}:{self.port}")
        
        try:
            while True:
                client_socket, address = self.server_socket.accept()
                print(f"连接来自 {address}")
                
                # 请求昵称
                client_socket.send("NICK".encode('utf-8'))
                nickname = client_socket.recv(1024).decode('utf-8')
                
                self.nicknames.append(nickname)
                self.clients.append(client_socket)
                
                # 广播新用户加入
                self.broadcast(f"{nickname} 加入了聊天室!".encode('utf-8'))
                client_socket.send("已连接到服务器!".encode('utf-8'))
                
                # 为客户端创建处理线程
                thread = threading.Thread(target=self.handle_client, args=(client_socket, nickname))
                thread.daemon = True
                thread.start()
                
        except KeyboardInterrupt:
            self.broadcast("服务器关闭".encode('utf-8'))
            self.stop()
    
    def handle_client(self, client_socket, nickname):
        try:
            while True:
                message = client_socket.recv(1024)
                if message:
                    self.broadcast(f"{nickname}: {message.decode('utf-8')}".encode('utf-8'))
                else:
                    # 客户端断开连接
                    index = self.clients.index(client_socket)
                    self.clients.remove(client_socket)
                    client_socket.close()
                    nickname = self.nicknames[index]
                    self.nicknames.remove(nickname)
                    self.broadcast(f"{nickname} 离开了聊天室!".encode('utf-8'))
                    break
        except Exception as e:
            print(f"处理客户端时出错: {e}")
            if client_socket in self.clients:
                index = self.clients.index(client_socket)
                self.clients.remove(client_socket)
                client_socket.close()
                nickname = self.nicknames[index]
                self.nicknames.remove(nickname)
                self.broadcast(f"{nickname} 离开了聊天室!".encode('utf-8'))
    
    def broadcast(self, message):
        for client in self.clients:
            try:
                client.send(message)
            except:
                # 如果发送失败,移除客户端
                index = self.clients.index(client)
                self.clients.remove(client)
                client.close()
                nickname = self.nicknames[index]
                self.nicknames.remove(nickname)
                self.broadcast(f"{nickname} 离开了聊天室!".encode('utf-8'))
    
    def stop(self):
        if self.server_socket:
            for client in self.clients:
                client.close()
            self.server_socket.close()
            print("服务器已关闭")

if __name__ == "__main__":
    server = ChatServer()
    server.start()

客户端

import socket
import threading
import tkinter as tk
from tkinter import scrolledtext, Entry, Button, END

class ChatClient:
    def __init__(self, host='localhost', port=9090):
        self.host = host
        self.port = port
        self.client_socket = None
        self.nickname = None
        
        # 创建GUI
        self.root = tk.Tk()
        self.root.title("Python聊天客户端")
        self.root.geometry("600x400")
        
        # 聊天显示区域
        self.chat_area = scrolledtext.ScrolledText(self.root, state='disabled')
        self.chat_area.pack(padx=10, pady=10, fill=tk.BOTH, expand=True)
        
        # 输入框和发送按钮
        input_frame = tk.Frame(self.root)
        input_frame.pack(padx=10, pady=5, fill=tk.X)
        
        self.message_input = Entry(input_frame)
        self.message_input.pack(side=tk.LEFT, fill=tk.X, expand=True)
        self.message_input.bind("<Return>", self.send_message)
        
        send_button = Button(input_frame, text="发送", command=self.send_message)
        send_button.pack(side=tk.RIGHT, padx=5)
        
        # 设置关闭事件
        self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
        
    def connect(self, nickname):
        self.nickname = nickname
        
        try:
            self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.client_socket.connect((self.host, self.port))
            
            # 启动接收线程
            receive_thread = threading.Thread(target=self.receive_messages)
            receive_thread.daemon = True
            receive_thread.start()
            
            return True
        except Exception as e:
            self.update_chat(f"连接错误: {e}")
            return False
    
    def receive_messages(self):
        while True:
            try:
                message = self.client_socket.recv(1024).decode('utf-8')
                
                if message == "NICK":
                    self.client_socket.send(self.nickname.encode('utf-8'))
                else:
                    self.update_chat(message)
            except Exception as e:
                self.update_chat(f"错误: {e}")
                self.client_socket.close()
                break
    
    def send_message(self, event=None):
        message = self.message_input.get()
        if message:
            try:
                self.client_socket.send(message.encode('utf-8'))
                self.message_input.delete(0, END)
            except Exception as e:
                self.update_chat(f"发送错误: {e}")
    
    def update_chat(self, message):
        self.chat_area.config(state='normal')
        self.chat_area.insert(END, message + "\n")
        self.chat_area.config(state='disabled')
        self.chat_area.see(END)
    
    def on_closing(self):
        if self.client_socket:
            try:
                self.client_socket.close()
            except:
                pass
        self.root.destroy()
    
    def run(self):
        # 先获取昵称
        nickname_window = tk.Toplevel(self.root)
        nickname_window.title("输入昵称")
        nickname_window.geometry("300x100")
        nickname_window.transient(self.root)
        nickname_window.grab_set()
        
        tk.Label(nickname_window, text="请输入您的昵称:").pack(pady=5)
        nickname_entry = Entry(nickname_window)
        nickname_entry.pack(pady=5, padx=10, fill=tk.X)
        nickname_entry.focus()
        
        def submit_nickname():
            nickname = nickname_entry.get()
            if nickname:
                if self.connect(nickname):
                    nickname_window.destroy()
                    self.root.deiconify()  # 显示主窗口
                else:
                    tk.Label(nickname_window, text="连接失败,请重试", fg="red").pack()
        
        Button(nickname_window, text="连接", command=submit_nickname).pack(pady=5)
        nickname_entry.bind("<Return>", lambda e: submit_nickname())
        
        self.root.withdraw()  # 隐藏主窗口
        self.root.mainloop()

if __name__ == "__main__":
    client = ChatClient()
    client.run()

简单的Web爬虫

import requests
from bs4 import BeautifulSoup
import time
import random
import csv

class WebCrawler:
    def __init__(self, base_url):
        self.base_url = base_url
        self.visited_urls = set()
        self.data = []
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
        }
    
    def crawl(self, url, max_pages=10):
        """爬取指定URL及其链接的页面"""
        page_count = 0
        urls_to_visit = [url]
        
        while urls_to_visit and page_count < max_pages:
            current_url = urls_to_visit.pop(0)
            
            if current_url in self.visited_urls:
                continue
            
            print(f"爬取: {current_url}")
            
            try:
                response = requests.get(current_url, headers=self.headers)
                response.raise_for_status()
                
                self.visited_urls.add(current_url)
                page_count += 1
                
                # 解析页面
                soup = BeautifulSoup(response.text, 'html.parser')
                
                # 提取数据
                self.extract_data(soup, current_url)
                
                # 提取链接
                links = soup.find_all('a', href=True)
                for link in links:
                    href = link['href']
                    
                    # 处理相对URL
                    if href.startswith('/'):
                        href = self.base_url + href
                    
                    # 只处理同一域名下的URL
                    if href.startswith(self.base_url) and href not in self.visited_urls:
                        urls_to_visit.append(href)
                
                # 礼貌爬取,避免请求过于频繁
                time.sleep(random.uniform(1, 3))
                
            except Exception as e:
                print(f"爬取 {current_url} 时出错: {e}")
        
        print(f"爬取完成,共访问 {len(self.visited_urls)} 个页面")
    
    def extract_data(self, soup, url):
        """从页面提取数据,需要根据具体网站定制"""
        # 示例:提取所有标题和段落
        title = soup.title.string if soup.title else "无标题"
        
        paragraphs = []
        for p in soup.find_all('p'):
            if p.text.strip():
                paragraphs.append(p.text.strip())
        
        self.data.append({
            'url': url,
            'title': title,
            'paragraphs': paragraphs,
            'timestamp': time.strftime('%Y-%m-%d %H:%M:%S')
        })
    
    def save_to_csv(self, filename):
        """将爬取的数据保存到CSV文件"""
        with open(filename, 'w', newline='', encoding='utf-8') as csvfile:
            fieldnames = ['url', 'title', 'timestamp']
            writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
            
            writer.writeheader()
            for item in self.data:
                # 只保存URL、标题和时间戳
                writer.writerow({
                    'url': item['url'],
                    'title': item['title'],
                    'timestamp': item['timestamp']
                })
        
        print(f"数据已保存到 {filename}")

if __name__ == "__main__":
    crawler = WebCrawler('https://example.com')
    crawler.crawl('https://example.com', max_pages=5)
    crawler.save_to_csv('crawled_data.csv')

实时股票价格监控器

import requests
import time
import json
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

class StockMonitor:
    def __init__(self, symbols):
        self.symbols = symbols
        self.data = {symbol: {'prices': [], 'times': []} for symbol in symbols}
        self.api_key = "YOUR_API_KEY"  # 替换为实际的API密钥
    
    def fetch_stock_data(self):
        """获取股票数据"""
        for symbol in self.symbols:
            try:
                # 使用Alpha Vantage API获取股票数据
                url = f"https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol={symbol}&apikey={self.api_key}"
                response = requests.get(url)
                response.raise_for_status()
                
                data = response.json()
                if "Global Quote" in data:
                    quote = data["Global Quote"]
                    price = float(quote.get("05. price", 0))
                    
                    current_time = time.strftime('%H:%M:%S')
                    
                    self.data[symbol]['prices'].append(price)
                    self.data[symbol]['times'].append(current_time)
                    
                    # 只保留最近30个数据点
                    if len(self.data[symbol]['prices']) > 30:
                        self.data[symbol]['prices'] = self.data[symbol]['prices'][-30:]
                        self.data[symbol]['times'] = self.data[symbol]['times'][-30:]
                    
                    print(f"{symbol}: ${price} at {current_time}")
                else:
                    print(f"无法获取 {symbol} 的数据")
            
            except Exception as e:
                print(f"获取 {symbol} 数据时出错: {e}")
    
    def plot_data(self):
        """绘制股票价格图表"""
        plt.figure(figsize=(12, 6))
        
        for symbol in self.symbols:
            plt.plot(self.data[symbol]['times'], self.data[symbol]['prices'], label=symbol)
        
        plt.title('实时股票价格')
        plt.xlabel('时间')
        plt.ylabel('价格 ($)')
        plt.legend()
        plt.xticks(rotation=45)
        plt.tight_layout()
        
        plt.show()
    
    def start_monitoring(self, interval=60):
        """开始实时监控股票价格"""
        fig, ax = plt.subplots(figsize=(12, 6))
        
        def update(frame):
            self.fetch_stock_data()
            ax.clear()
            
            for symbol in self.symbols:
                ax.plot(self.data[symbol]['times'], self.data[symbol]['prices'], label=symbol)
            
            ax.set_title('实时股票价格')
            ax.set_xlabel('时间')
            ax.set_ylabel('价格 ($)')
            ax.legend()
            plt.xticks(rotation=45)
            plt.tight_layout()
        
        ani = FuncAnimation(fig, update, interval=interval*1000)
        plt.show()

if __name__ == "__main__":
    # 示例股票代码
    symbols = ['AAPL', 'MSFT', 'GOOGL']
    
    monitor = StockMonitor(symbols)
    monitor.fetch_stock_data()  # 获取初始数据
    monitor.start_monitoring(interval=60)  # 每60秒更新一次

12. 总结

在本章中,我们学习了Python网络编程的各个方面,从基础的Socket编程到高级的Web服务器和客户端开发。以下是主要内容的总结:

  1. 网络基础:了解了网络模型和常见协议(TCP、UDP、HTTP等)
  2. Socket编程:学习了如何使用Python的socket模块创建客户端和服务器
  3. 高级Socket编程:探索了socketserver模块和异步I/O
  4. HTTP客户端编程:使用urllib和requests库发送HTTP请求
  5. HTTP服务器编程:使用http.server模块和Flask框架创建Web服务器
  6. WebSocket编程:实现了实时双向通信
  7. 电子邮件编程:学习了如何发送和接收电子邮件
  8. FTP编程:使用ftplib模块进行文件传输
  9. 网络安全编程:了解了SSL/TLS加密和数字签名
  10. 网络编程最佳实践:错误处理、资源管理和性能优化
  11. 实际应用案例:开发了聊天应用、Web爬虫和股票监控器

Python丰富的网络编程库和工具使其成为开发网络应用的理想选择。无论是简单的客户端脚本还是复杂的分布式系统,Python都能提供所需的功能和灵活性。

随着互联网和网络技术的不断发展,网络编程技能变得越来越重要。掌握Python网络编程不仅可以帮助您开发各种网络应用,还能为您在云计算、物联网和微服务架构等领域打下坚实的基础。

Python网络编程

1. 网络编程基础

作为Java开发者,您可能已经使用过Java的网络编程API,如Socket和ServerSocket。Python同样提供了强大的网络编程功能,允许您创建客户端和服务器应用程序,处理各种网络协议。

网络模型

在深入Python网络编程之前,让我们回顾一下网络通信的基本模型:

  1. OSI七层模型

    • 物理层:处理比特流的传输
    • 数据链路层:处理帧的传输
    • 网络层:处理数据包的路由(IP协议)
    • 传输层:处理端到端的连接(TCP/UDP协议)
    • 会话层:管理会话
    • 表示层:处理数据格式转换
    • 应用层:为应用程序提供网络服务(HTTP、FTP等)
  2. TCP/IP四层模型

    • 网络接口层:对应OSI的物理层和数据链路层
    • 网络层:对应OSI的网络层(IP协议)
    • 传输层:对应OSI的传输层(TCP/UDP协议)
    • 应用层:对应OSI的会话层、表示层和应用层

常见网络协议

  1. TCP (传输控制协议)

    • 面向连接的协议
    • 提供可靠的数据传输
    • 具有流量控制和拥塞控制
    • 适用于需要可靠传输的应用(如HTTP、FTP、SMTP)
  2. UDP (用户数据报协议)

    • 无连接的协议
    • 不保证数据传输的可靠性
    • 低开销,高效率
    • 适用于实时应用(如视频流、游戏、DNS)
  3. HTTP (超文本传输协议)

    • 应用层协议,基于TCP
    • 用于Web浏览器和服务器之间的通信
    • 无状态协议
    • HTTP/1.1、HTTP/2、HTTP/3版本
  4. WebSocket

    • 提供全双工通信通道
    • 基于TCP的应用层协议
    • 用于Web应用中的实时通信
  5. SMTP (简单邮件传输协议)

    • 用于发送电子邮件
    • 基于TCP的应用层协议
  6. FTP (文件传输协议)

    • 用于在客户端和服务器之间传输文件
    • 使用两个TCP连接:控制连接和数据连接

2. Python Socket编程

Socket是网络编程的基础,它提供了一种在网络上发送和接收数据的方法。Python的socket模块提供了对底层Socket API的访问。

创建Socket

import socket

# 创建TCP Socket
tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 创建UDP Socket
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

参数说明:

  • socket.AF_INET:使用IPv4地址族
  • socket.AF_INET6:使用IPv6地址族
  • socket.SOCK_STREAM:使用TCP协议
  • socket.SOCK_DGRAM:使用UDP协议

TCP客户端

import socket

def tcp_client():
    # 创建Socket
    client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    try:
        # 连接服务器
        server_address = ('localhost', 8000)
        print(f"连接到 {server_address[0]}:{server_address[1]}")
        client_socket.connect(server_address)
        
        # 发送数据
        message = "Hello, Server!"
        print(f"发送: {message}")
        client_socket.sendall(message.encode('utf-8'))
        
        # 接收响应
        data = client_socket.recv(1024)
        print(f"接收: {data.decode('utf-8')}")
        
    finally:
        # 关闭连接
        print("关闭连接")
        client_socket.close()

if __name__ == "__main__":
    tcp_client()

TCP服务器

import socket

def tcp_server():
    # 创建Socket
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    # 绑定地址和端口
    server_address = ('localhost', 8000)
    print(f"启动服务器 {server_address[0]}:{server_address[1]}")
    server_socket.bind(server_address)
    
    # 监听连接
    server_socket.listen(5)  # 最多5个排队连接
    
    try:
        while True:
            # 等待连接
            print("等待连接...")
            client_socket, client_address = server_socket.accept()
            print(f"接受来自 {client_address[0]}:{client_address[1]} 的连接")
            
            try:
                # 接收数据
                data = client_socket.recv(1024)
                print(f"接收: {data.decode('utf-8')}")
                
                if data:
                    # 发送响应
                    response = "Hello, Client!"
                    print(f"发送: {response}")
                    client_socket.sendall(response.encode('utf-8'))
                else:
                    print("没有数据")
                    break
                    
            finally:
                # 关闭客户端连接
                print("关闭客户端连接")
                client_socket.close()
                
    finally:
        # 关闭服务器Socket
        print("关闭服务器")
        server_socket.close()

if __name__ == "__main__":
    tcp_server()

UDP客户端

import socket

def udp_client():
    # 创建Socket
    client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    
    try:
        # 服务器地址
        server_address = ('localhost', 8000)
        
        # 发送数据
        message = "Hello, UDP Server!"
        print(f"发送: {message}")
        client_socket.sendto(message.encode('utf-8'), server_address)
        
        # 接收响应
        data, server = client_socket.recvfrom(1024)
        print(f"从 {server[0]}:{server[1]} 接收: {data.decode('utf-8')}")
        
    finally:
        # 关闭Socket
        print("关闭Socket")
        client_socket.close()

if __name__ == "__main__":
    udp_client()

UDP服务器

import socket

def udp_server():
    # 创建Socket
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    
    # 绑定地址和端口
    server_address = ('localhost', 8000)
    print(f"启动UDP服务器 {server_address[0]}:{server_address[1]}")
    server_socket.bind(server_address)
    
    try:
        while True:
            # 接收数据
            print("等待消息...")
            data, client_address = server_socket.recvfrom(1024)
            print(f"从 {client_address[0]}:{client_address[1]} 接收: {data.decode('utf-8')}")
            
            # 发送响应
            response = "Hello, UDP Client!"
            print(f"发送: {response}")
            server_socket.sendto(response.encode('utf-8'), client_address)
            
    finally:
        # 关闭服务器Socket
        print("关闭服务器")
        server_socket.close()

if __name__ == "__main__":
    udp_server()

Socket选项

可以使用setsockopt方法设置Socket选项:

import socket

# 创建Socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 设置地址重用选项
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

# 设置超时
client_socket.settimeout(5.0)  # 5秒超时

非阻塞Socket

默认情况下,Socket操作是阻塞的。可以设置为非阻塞模式:

import socket

# 创建Socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 设置为非阻塞模式
client_socket.setblocking(False)

try:
    client_socket.connect(('localhost', 8000))
except BlockingIOError:
    # 连接操作正在进行中
    pass

# 使用select模块处理非阻塞I/O
import select

readable, writable, exceptional = select.select([client_socket], [client_socket], [client_socket], 5.0)

if writable:
    print("连接就绪")
    client_socket.send(b"Hello")

3. 高级Socket编程

使用socketserver模块

Python的socketserver模块提供了更高级的服务器实现,简化了服务器编程:

import socketserver

class MyTCPHandler(socketserver.BaseRequestHandler):
    """
    处理TCP请求的类
    """
    def handle(self):
        # self.request是TCP套接字
        data = self.request.recv(1024).strip()
        print(f"{self.client_address[0]} 发送: {data.decode('utf-8')}")
        
        # 发送响应
        response = "已收到您的消息"
        self.request.sendall(response.encode('utf-8'))

if __name__ == "__main__":
    HOST, PORT = "localhost", 8000
    
    # 创建服务器
    server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)
    
    # 启动服务器
    print(f"服务器启动在 {HOST}:{PORT}")
    server.serve_forever()

多线程服务器

使用ThreadingMixIn创建多线程服务器:

import socketserver

class ThreadedTCPHandler(socketserver.BaseRequestHandler):
    def handle(self):
        data = self.request.recv(1024).strip()
        print(f"线程 {threading.current_thread().name} 处理来自 {self.client_address[0]} 的请求")
        print(f"接收: {data.decode('utf-8')}")
        
        response = f"线程 {threading.current_thread().name} 已处理您的请求"
        self.request.sendall(response.encode('utf-8'))

class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
    pass

if __name__ == "__main__":
    import threading
    
    HOST, PORT = "localhost", 8000
    server = ThreadedTCPServer((HOST, PORT), ThreadedTCPHandler)
    
    # 启动线程处理请求
    server_thread = threading.Thread(target=server.serve_forever)
    server_thread.daemon = True
    server_thread.start()
    print(f"服务器启动在 {HOST}:{PORT}")
    print(f"服务器线程名: {server_thread.name}")
    
    # 保持主线程运行
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        server.shutdown()

异步I/O

使用asyncio模块实现异步网络编程:

import asyncio

async def handle_client(reader, writer):
    # 获取客户端地址
    addr = writer.get_extra_info('peername')
    print(f"连接来自 {addr}")
    
    # 读取数据
    data = await reader.read(100)
    message = data.decode('utf-8')
    print(f"接收: {message}")
    
    # 发送响应
    response = f"已收到: {message}"
    print(f"发送: {response}")
    writer.write(response.encode('utf-8'))
    await writer.drain()
    
    # 关闭连接
    print("关闭连接")
    writer.close()
    await writer.wait_closed()

async def main():
    # 启动服务器
    server = await asyncio.start_server(
        handle_client, '127.0.0.1', 8000)
    
    addr = server.sockets[0].getsockname()
    print(f'服务器启动在 {addr}')
    
    async with server:
        await server.serve_forever()

if __name__ == "__main__":
    asyncio.run(main())

4. HTTP客户端编程

Python提供了多种方式来发送HTTP请求。

使用urllib

urllib是Python标准库中的HTTP客户端:

from urllib import request, parse
import json

def fetch_data():
    # GET请求
    with request.urlopen('https://api.example.com/data') as response:
        data = response.read().decode('utf-8')
        return json.loads(data)

def post_data():
    # POST请求数据
    data = {
        'name': 'John Doe',
        'email': 'john@example.com'
    }
    data = parse.urlencode(data).encode('utf-8')
    
    # 创建请求
    req = request.Request('https://api.example.com/submit', data=data, method='POST')
    req.add_header('Content-Type', 'application/x-www-form-urlencoded')
    
    # 发送请求
    with request.urlopen(req) as response:
        result = response.read().decode('utf-8')
        return json.loads(result)

使用requests库

requests是一个更简单、更强大的HTTP客户端库:

import requests

def fetch_data():
    # GET请求
    response = requests.get('https://api.example.com/data')
    response.raise_for_status()  # 如果请求失败则抛出异常
    return response.json()

def post_data():
    # POST请求
    data = {
        'name': 'John Doe',
        'email': 'john@example.com'
    }
    response = requests.post('https://api.example.com/submit', data=data)
    response.raise_for_status()
    return response.json()

def upload_file():
    # 上传文件
    files = {'file': open('document.pdf', 'rb')}
    response = requests.post('https://api.example.com/upload', files=files)
    response.raise_for_status()
    return response.json()

def custom_headers():
    # 自定义请求头
    headers = {
        'User-Agent': 'My Python Client',
        'Authorization': 'Bearer token123'
    }
    response = requests.get('https://api.example.com/protected', headers=headers)
    response.raise_for_status()
    return response.json()

def with_cookies():
    # 使用会话保持Cookie
    session = requests.Session()
    
    # 登录
    login_data = {'username': 'user', 'password': 'pass'}
    session.post('https://example.com/login', data=login_data)
    
    # 访问需要认证的页面
    response = session.get('https://example.com/dashboard')
    return response.text

def with_timeout():
    # 设置超时
    try:
        response = requests.get('https://api.example.com/data', timeout=5)
        response.raise_for_status()
        return response.json()
    except requests.Timeout:
        print("请求超时")
        return None

异步HTTP请求

使用aiohttp库进行异步HTTP请求:

import aiohttp
import asyncio

async def fetch(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            return await response.text()

async def fetch_multiple(urls):
    tasks = [fetch(url) for url in urls]
    return await asyncio.gather(*tasks)

async def main():
    urls = [
        'https://api.example.com/data1',
        'https://api.example.com/data2',
        'https://api.example.com/data3'
    ]
    results = await fetch_multiple(urls)
    for url, result in zip(urls, results):
        print(f"URL: {url}, 长度: {len(result)}")

if __name__ == "__main__":
    asyncio.run(main())

5. HTTP服务器编程

使用http.server模块

Python的http.server模块提供了简单的HTTP服务器实现:

from http.server import HTTPServer, BaseHTTPRequestHandler
import json

class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
    def _set_headers(self, content_type='text/html'):
        self.send_response(200)
        self.send_header('Content-type', content_type)
        self.end_headers()
    
    def do_GET(self):
        if self.path == '/':
            self._set_headers()
            self.wfile.write(b"<html><body><h1>Welcome to my server!</h1></body></html>")
        elif self.path == '/api/data':
            self._set_headers('application/json')
            data = {'message': 'Hello, World!', 'status': 'success'}
            self.wfile.write(json.dumps(data).encode('utf-8'))
        else:
            self.send_response(404)
            self.end_headers()
            self.wfile.write(b"404 Not Found")
    
    def do_POST(self):
        if self.path == '/api/submit':
            content_length = int(self.headers['Content-Length'])
            post_data = self.rfile.read(content_length)
            
            # 解析JSON数据
            try:
                data = json.loads(post_data.decode('utf-8'))
                
                # 处理数据
                response = {'status': 'success', 'received': data}
                
                # 发送响应
                self._set_headers('application/json')
                self.wfile.write(json.dumps(response).encode('utf-8'))
            except json.JSONDecodeError:
                self.send_response(400)
                self.end_headers()
                self.wfile.write(b"Invalid JSON")
        else:
            self.send_response(404)
            self.end_headers()
            self.wfile.write(b"404 Not Found")

def run_server(server_class=HTTPServer, handler_class=SimpleHTTPRequestHandler, port=8000):
    server_address = ('', port)
    httpd = server_class(server_address, handler_class)
    print(f"Starting server on port {port}...")
    httpd.serve_forever()

if __name__ == "__main__":
    run_server()

使用Flask框架

Flask是一个轻量级的Web框架,适合构建API和Web应用:

from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/')
def home():
    return "<h1>Welcome to my API</h1>"

@app.route('/api/data')
def get_data():
    data = {'message': 'Hello, World!', 'status': 'success'}
    return jsonify(data)

@app.route('/api/submit', methods=['POST'])
def submit_data():
    if request.is_json:
        data = request.get_json()
        # 处理数据
        response = {'status': 'success', 'received': data}
        return jsonify(response)
    else:
        return jsonify({'error': 'Invalid JSON'}), 400

if __name__ == '__main__':
    app.run(debug=True, port=8000)

使用FastAPI框架

FastAPI是一个现代、高性能的Web框架,支持异步请求处理和自动API文档生成:

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import uvicorn

app = FastAPI()

class Item(BaseModel):
    name: str
    description: str = None
    price: float
    tax: float = None

@app.get("/")
def read_root():
    return {"Hello": "World"}

@app.get("/items/{item_id}")
def read_item(item_id: int, q: str = None):
    return {"item_id": item_id, "q": q}

@app.post("/items/")
def create_item(item: Item):
    return item

@app.put("/items/{item_id}")
def update_item(item_id: int, item: Item):
    if item_id < 0:
        raise HTTPException(status_code=400, detail="Item ID must be positive")
    return {"item_id": item_id, **item.dict()}

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

6. WebSocket编程

WebSocket提供了全双工通信通道,适用于需要实时通信的Web应用。

使用websockets库

import asyncio
import websockets

# WebSocket服务器
async def echo(websocket, path):
    async for message in websocket:
        print(f"接收: {message}")
        await websocket.send(f"Echo: {message}")

async def main():
    async with websockets.serve(echo, "localhost", 8765):
        await asyncio.Future()  # 运行直到被取消

if __name__ == "__main__":
    asyncio.run(main())

WebSocket客户端

import asyncio
import websockets

async def hello():
    uri = "ws://localhost:8765"
    async with websockets.connect(uri) as websocket:
        message = "Hello WebSocket!"
        await websocket.send(message)
        print(f"发送: {message}")
        
        response = await websocket.recv()
        print(f"接收: {response}")

if __name__ == "__main__":
    asyncio.run(hello())

使用Flask-SocketIO

Flask-SocketIO是Flask的WebSocket扩展:

from flask import Flask, render_template
from flask_socketio import SocketIO, emit

app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app)

@app.route('/')
def index():
    return render_template('index.html')

@socketio.on('connect')
def test_connect():
    emit('response', {'data': 'Connected'})

@socketio.on('message')
def handle_message(data):
    print('received message: ' + data)
    emit('response', {'data': f'Server received: {data}'})

@socketio.on('disconnect')
def test_disconnect():
    print('Client disconnected')

if __name__ == '__main__':
    socketio.run(app, debug=True)

HTML客户端:

<!DOCTYPE html>
<html>
<head>
    <title>Flask-SocketIO Test</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script>
    <script>
        document.addEventListener('DOMContentLoaded', () => {
            const socket = io();
            
            socket.on('connect', () => {
                console.log('Connected to server');
            });
            
            socket.on('response', (data) => {
                console.log('Server response:', data);
                document.getElementById('messages').innerHTML += '<p>' + data.data + '</p>';
            });
            
            document.getElementById('send').addEventListener('click', () => {
                const message = document.getElementById('message').value;
                socket.emit('message', message);
                document.getElementById('message').value = '';
            });
        });
    </script>
</head>
<body>
    <h1>Flask-SocketIO Test</h1>
    <div id="messages"></div>
    <input type="text" id="message" placeholder="Enter message">
    <button id="send">Send</button>
</body>
</html>

7. 电子邮件编程

Python提供了发送和接收电子邮件的功能。

使用smtplib发送邮件

import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

def send_email():
    # 邮件服务器配置
    smtp_server = "smtp.gmail.com"
    port = 587
    sender_email = "your_email@gmail.com"
    password = "your_password"
    receiver_email = "recipient@example.com"
    
    # 创建多部分消息
    message = MIMEMultipart()
    message["Subject"] = "Python邮件测试"
    message["From"] = sender_email
    message["To"] = receiver_email
    
    # 添加正文
    body = "这是一封由Python发送的测试邮件。"
    message.attach(MIMEText(body, "plain"))
    
    try:
        # 创建SMTP会话
        server = smtplib.SMTP(smtp_server, port)
        server.ehlo()  # 向SMTP服务器标识自己
        server.starttls()  # 启用TLS加密
        server.ehlo()  # 重新标识
        server.login(sender_email, password)
        
        # 发送邮件
        server.sendmail(sender_email, receiver_email, message.as_string())
        print("邮件发送成功")
    except Exception as e:
        print(f"邮件发送失败: {e}")
    finally:
        server.quit()

if __name__ == "__main__":
    send_email()

发送HTML邮件和附件

import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication
import os

def send_email_with_attachment():
    # 邮件服务器配置
    smtp_server = "smtp.gmail.com"
    port = 587
    sender_email = "your_email@gmail.com"
    password = "your_password"
    receiver_email = "recipient@example.com"
    
    # 创建多部分消息
    message = MIMEMultipart()
    message["Subject"] = "Python邮件测试 - 带附件"
    message["From"] = sender_email
    message["To"] = receiver_email
    
    # 添加HTML正文
    html = """
    <html>
      <body>
        <h1>Python邮件测试</h1>
        <p>这是一封由<b>Python</b>发送的测试邮件,包含<i>HTML</i>格式和附件。</p>
      </body>
    </html>
    """
    message.attach(MIMEText(html, "html"))
    
    # 添加附件
    filename = "document.pdf"  # 替换为实际文件路径
    with open(filename, "rb") as f:
        attachment = MIMEApplication(f.read(), _subtype="pdf")
        attachment.add_header('Content-Disposition', 'attachment', filename=os.path.basename(filename))
        message.attach(attachment)
    
    try:
        # 创建SMTP会话
        server = smtplib.SMTP(smtp_server, port)
        server.ehlo()
        server.starttls()
        server.ehlo()
        server.login(sender_email, password)
        
        # 发送邮件
        server.sendmail(sender_email, receiver_email, message.as_string())
        print("邮件发送成功")
    except Exception as e:
        print(f"邮件发送失败: {e}")
    finally:
        server.quit()

使用imaplib接收邮件

import imaplib
import email
from email.header import decode_header

def fetch_emails():
    # 邮件服务器配置
    imap_server = "imap.gmail.com"
    email_address = "your_email@gmail.com"
    password = "your_password"
    
    try:
        # 连接到IMAP服务器
        mail = imaplib.IMAP4_SSL(imap_server)
        mail.login(email_address, password)
        
        # 选择收件箱
        mail.select("INBOX")
        
        # 搜索邮件
        status, messages = mail.search(None, "UNSEEN")  # 未读邮件
        
        if status == "OK":
            # 获取邮件ID列表
            email_ids = messages[0].split()
            
            for email_id in email_ids:
                # 获取邮件内容
                status, msg_data = mail.fetch(email_id, "(RFC822)")
                
                if status == "OK":
                    # 解析邮件
                    msg = email.message_from_bytes(msg_data[0][1])
                    
                    # 解码主题
                    subject, encoding = decode_header(msg["Subject"])[0]
                    if isinstance(subject, bytes):
                        subject = subject.decode(encoding or "utf-8")
                    
                    # 获