Python 网络编程

1 阅读15分钟

Python 网络编程

目录


网络编程基础

网络模型

OSI 七层模型

  1. 物理层
  2. 数据链路层
  3. 网络层(IP)
  4. 传输层(TCP/UDP)
  5. 会话层
  6. 表示层
  7. 应用层(HTTP、FTP、SMTP 等)

IP 地址和端口

import socket

# 获取本机信息
hostname = socket.gethostname()
print(f"主机名: {hostname}")

ip_address = socket.gethostbyname(hostname)
print(f"IP 地址: {ip_address}")

# 常见端口
common_ports = {
    "HTTP": 80,
    "HTTPS": 443,
    "FTP": 21,
    "SSH": 22,
    "SMTP": 25,
    "DNS": 53,
    "MySQL": 3306,
    "Redis": 6379
}

for service, port in common_ports.items():
    print(f"{service}: {port}")

Socket 编程

Socket 基础

Socket 是网络通信的端点,提供进程间通信的能力。

import socket

# 创建 Socket
# AF_INET: IPv4, AF_INET6: IPv6
# SOCK_STREAM: TCP, SOCK_DGRAM: UDP
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# Socket 选项
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

# 设置超时
sock.settimeout(5)  # 5秒超时

# 关闭 Socket
sock.close()

地址格式

# IPv4 地址格式: (host, port)
address_v4 = ("127.0.0.1", 8080)

# IPv6 地址格式: (host, port, flowinfo, scopeid)
address_v6 = ("::1", 8080, 0, 0)

# 域名解析
import socket
ip = socket.gethostbyname("www.example.com")
print(f"IP: {ip}")

TCP 编程

TCP 服务器

import socket
import threading

def handle_client(client_socket, address):
    """处理客户端连接"""
    print(f"[新连接] {address}")

    try:
        while True:
            # 接收数据
            data = client_socket.recv(1024)
            if not data:
                break

            message = data.decode('utf-8')
            print(f"[{address}] 收到: {message}")

            # 发送响应
            response = f"服务器收到: {message}"
            client_socket.send(response.encode('utf-8'))

    except Exception as e:
        print(f"[{address}] 错误: {e}")
    finally:
        client_socket.close()
        print(f"[断开连接] {address}")

def start_tcp_server(host='127.0.0.1', port=8080):
    """启动 TCP 服务器"""
    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"[监听] {host}:{port}")

    try:
        while True:
            client_socket, address = server.accept()

            # 为每个客户端创建线程
            thread = threading.Thread(
                target=handle_client,
                args=(client_socket, address)
            )
            thread.daemon = True
            thread.start()

    except KeyboardInterrupt:
        print("\n[服务器关闭]")
    finally:
        server.close()

if __name__ == "__main__":
    start_tcp_server()

TCP 客户端

import socket

def tcp_client(host='127.0.0.1', port=8080):
    """TCP 客户端"""
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    try:
        # 连接服务器
        client.connect((host, port))
        print(f"[已连接] {host}:{port}")

        while True:
            # 发送消息
            message = input("输入消息 (quit 退出): ")
            if message.lower() == 'quit':
                break

            client.send(message.encode('utf-8'))

            # 接收响应
            response = client.recv(1024)
            print(f"服务器响应: {response.decode('utf-8')}")

    except Exception as e:
        print(f"错误: {e}")
    finally:
        client.close()
        print("[连接关闭]")

if __name__ == "__main__":
    tcp_client()

文件传输示例

import socket
import os

def send_file(client_socket, filepath):
    """发送文件"""
    filename = os.path.basename(filepath)
    filesize = os.path.getsize(filepath)

    # 发送文件名和大小
    header = f"{filename}|{filesize}"
    client_socket.send(header.encode('utf-8'))

    # 发送文件内容
    with open(filepath, 'rb') as f:
        while True:
            bytes_read = f.read(4096)
            if not bytes_read:
                break
            client_socket.sendall(bytes_read)

    print(f"文件发送完成: {filename}")

def receive_file(client_socket, save_dir="."):
    """接收文件"""
    # 接收文件头
    header = client_socket.recv(1024).decode('utf-8')
    filename, filesize = header.split('|')
    filesize = int(filesize)

    filepath = os.path.join(save_dir, filename)

    # 接收文件内容
    bytes_received = 0
    with open(filepath, 'wb') as f:
        while bytes_received < filesize:
            bytes_read = client_socket.recv(4096)
            if not bytes_read:
                break
            f.write(bytes_read)
            bytes_received += len(bytes_read)

    print(f"文件接收完成: {filename}")

UDP 编程

UDP 服务器

import socket

def udp_server(host='127.0.0.1', port=9090):
    """UDP 服务器"""
    server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    server.bind((host, port))

    print(f"[UDP 监听] {host}:{port}")

    try:
        while True:
            # 接收数据
            data, address = server.recvfrom(1024)
            message = data.decode('utf-8')
            print(f"[{address}] 收到: {message}")

            # 发送响应
            response = f"服务器收到: {message}"
            server.sendto(response.encode('utf-8'), address)

    except KeyboardInterrupt:
        print("\n[服务器关闭]")
    finally:
        server.close()

if __name__ == "__main__":
    udp_server()

UDP 客户端

import socket

def udp_client(host='127.0.0.1', port=9090):
    """UDP 客户端"""
    client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

    try:
        while True:
            message = input("输入消息 (quit 退出): ")
            if message.lower() == 'quit':
                break

            # 发送数据
            client.sendto(message.encode('utf-8'), (host, port))

            # 接收响应
            response, server = client.recvfrom(1024)
            print(f"服务器响应: {response.decode('utf-8')}")

    except Exception as e:
        print(f"错误: {e}")
    finally:
        client.close()

if __name__ == "__main__":
    udp_client()

TCP vs UDP 对比

特性TCPUDP
连接面向连接无连接
可靠性可靠传输不可靠
顺序保证顺序不保证顺序
速度较慢较快
适用场景文件传输、网页视频流、游戏

HTTP 协议基础

HTTP 请求结构

GET /index.html HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0
Accept: text/html

HTTP 响应结构

HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1234

<html>...</html>

常用 HTTP 方法

  • GET: 获取资源
  • POST: 创建资源
  • PUT: 更新资源
  • DELETE: 删除资源
  • PATCH: 部分更新

urllib 模块

基本用法

from urllib.request import urlopen, Request
from urllib.parse import urlencode, quote
import json

# GET 请求
response = urlopen("https://httpbin.org/get")
print(response.status)  # 200
print(response.headers)
data = response.read().decode('utf-8')
print(data)

# POST 请求
post_data = urlencode({"key": "value", "name": "测试"}).encode('utf-8')
request = Request("https://httpbin.org/post", data=post_data)
response = urlopen(request)
print(response.read().decode('utf-8'))

# 添加请求头
headers = {
    "User-Agent": "Mozilla/5.0",
    "Accept": "application/json"
}
request = Request("https://httpbin.org/headers", headers=headers)
response = urlopen(request)

URL 编码

from urllib.parse import urlencode, quote, unquote, urlparse

# 编码
params = {"name": "张三", "age": 25}
encoded = urlencode(params)
print(encoded)  # name=%E5%BC%A0%E4%B8%89&age=25

# 解码
decoded = unquote(encoded)
print(decoded)  # name=张三&age=25

# 解析 URL
url = "https://www.example.com/path?name=test&page=1"
parsed = urlparse(url)
print(f"协议: {parsed.scheme}")
print(f"域名: {parsed.netloc}")
print(f"路径: {parsed.path}")
print(f"参数: {parsed.query}")

下载文件

from urllib.request import urlretrieve

# 下载文件
url = "https://httpbin.org/image/jpeg"
filename = "downloaded.jpg"

urlretrieve(url, filename)
print(f"文件已保存到: {filename}")

Requests 库

安装

pip install requests

基本用法

import requests

# GET 请求
response = requests.get("https://httpbin.org/get")
print(response.status_code)  # 200
print(response.text)
print(response.json())

# POST 请求
data = {"name": "张三", "age": 25}
response = requests.post("https://httpbin.org/post", json=data)
print(response.json())

# PUT 请求
response = requests.put("https://httpbin.org/put", json={"key": "value"})

# DELETE 请求
response = requests.delete("https://httpbin.org/delete")

请求参数

import requests

# URL 参数
params = {"page": 1, "limit": 10}
response = requests.get("https://httpbin.org/get", params=params)
print(response.url)  # https://httpbin.org/get?page=1&limit=10

# 请求头
headers = {
    "User-Agent": "MyApp/1.0",
    "Authorization": "Bearer token123"
}
response = requests.get("https://httpbin.org/headers", headers=headers)

# Cookie
cookies = {"session_id": "abc123"}
response = requests.get("https://httpbin.org/cookies", cookies=cookies)

# 超时设置
try:
    response = requests.get("https://httpbin.org/delay/5", timeout=3)
except requests.exceptions.Timeout:
    print("请求超时")

文件上传

import requests

# 上传文件
files = {
    "file": ("test.txt", open("test.txt", "rb"), "text/plain")
}
response = requests.post("https://httpbin.org/post", files=files)
print(response.json())

# 多个文件
files = [
    ("images", ("img1.jpg", open("img1.jpg", "rb"))),
    ("images", ("img2.jpg", open("img2.jpg", "rb")))
]
response = requests.post("https://httpbin.org/post", files=files)

Session 对象

import requests

# 创建会话
session = requests.Session()

# 设置默认请求头
session.headers.update({
    "User-Agent": "MyApp/1.0"
})

# 设置 Cookie
session.cookies.set("session_id", "abc123")

# 多次请求共享状态
response1 = session.get("https://httpbin.org/cookies")
response2 = session.get("https://httpbin.org/headers")

# 关闭会话
session.close()

异常处理

import requests
from requests.exceptions import (
    ConnectionError,
    Timeout,
    HTTPError,
    RequestException
)

try:
    response = requests.get("https://api.example.com/data", timeout=5)
    response.raise_for_status()  # 如果状态码不是 2xx,抛出异常

    data = response.json()
    print(data)

except ConnectionError:
    print("网络连接错误")

except Timeout:
    print("请求超时")

except HTTPError as e:
    print(f"HTTP 错误: {e}")

except RequestException as e:
    print(f"请求异常: {e}")

Web 服务器

简单 HTTP 服务器

from http.server import HTTPServer, SimpleHTTPRequestHandler
import webbrowser
import threading

def start_web_server(port=8000):
    """启动简单的 Web 服务器"""
    server = HTTPServer(("127.0.0.1", port), SimpleHTTPRequestHandler)
    print(f"服务器运行在 http://127.0.0.1:{port}")

    # 自动打开浏览器
    webbrowser.open(f"http://127.0.0.1:{port}")

    try:
        server.serve_forever()
    except KeyboardInterrupt:
        print("\n服务器关闭")
        server.server_close()

if __name__ == "__main__":
    start_web_server()

自定义 HTTP 服务器

from http.server import HTTPServer, BaseHTTPRequestHandler
import json

class CustomHandler(BaseHTTPRequestHandler):
    """自定义请求处理器"""

    def do_GET(self):
        """处理 GET 请求"""
        if self.path == "/":
            self.send_response(200)
            self.send_header("Content-Type", "text/html; charset=utf-8")
            self.end_headers()

            html = """
            <html>
            <head><title>Python Web Server</title></head>
            <body>
                <h1>欢迎使用 Python Web 服务器</h1>
                <p><a href="/api/data">查看 API 数据</a></p>
            </body>
            </html>
            """
            self.wfile.write(html.encode('utf-8'))

        elif self.path == "/api/data":
            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.end_headers()

            data = {
                "message": "Hello, World!",
                "status": "success"
            }
            self.wfile.write(json.dumps(data, ensure_ascii=False).encode('utf-8'))

        else:
            self.send_error(404, "Not Found")

    def do_POST(self):
        """处理 POST 请求"""
        content_length = int(self.headers.get('Content-Length', 0))
        post_data = self.rfile.read(content_length)

        self.send_response(200)
        self.send_header("Content-Type", "application/json")
        self.end_headers()

        response = {
            "received": post_data.decode('utf-8'),
            "status": "success"
        }
        self.wfile.write(json.dumps(response).encode('utf-8'))

    def log_message(self, format, *args):
        """自定义日志格式"""
        print(f"[{self.client_address[0]}] {format % args}")

def run_server(host='127.0.0.1', port=8080):
    """运行服务器"""
    server = HTTPServer((host, port), CustomHandler)
    print(f"服务器运行在 http://{host}:{port}")

    try:
        server.serve_forever()
    except KeyboardInterrupt:
        print("\n服务器关闭")
        server.server_close()

if __name__ == "__main__":
    run_server()

WebSocket

安装

pip install websockets

WebSocket 服务器

import asyncio
import websockets
import json

connected_clients = set()

async def handler(websocket, path):
    """处理 WebSocket 连接"""
    # 注册客户端
    connected_clients.add(websocket)
    print(f"[连接] 当前在线: {len(connected_clients)}")

    try:
        async for message in websocket:
            print(f"[收到] {message}")

            # 广播消息给所有客户端
            broadcast_message = {
                "type": "message",
                "content": message,
                "clients": len(connected_clients)
            }

            await broadcast(json.dumps(broadcast_message))

    except websockets.exceptions.ConnectionClosed:
        pass
    finally:
        # 注销客户端
        connected_clients.discard(websocket)
        print(f"[断开] 当前在线: {len(connected_clients)}")

async def broadcast(message):
    """广播消息"""
    if connected_clients:
        await asyncio.wait([
            client.send(message)
            for client in connected_clients
        ])

async def main():
    """启动 WebSocket 服务器"""
    server = await websockets.serve(handler, "127.0.0.1", 8765)
    print("WebSocket 服务器运行在 ws://127.0.0.1:8765")
    await server.wait_closed()

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

WebSocket 客户端

import asyncio
import websockets

async def websocket_client():
    """WebSocket 客户端"""
    uri = "ws://127.0.0.1:8765"

    async with websockets.connect(uri) as websocket:
        print("[已连接]")

        # 启动接收消息的任务
        receive_task = asyncio.create_task(receive_messages(websocket))

        try:
            while True:
                message = input("输入消息: ")
                await websocket.send(message)
        except KeyboardInterrupt:
            pass
        finally:
            receive_task.cancel()

async def receive_messages(websocket):
    """接收消息"""
    try:
        async for message in websocket:
            print(f"[收到] {message}")
    except asyncio.CancelledError:
        pass

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

FTP 编程

FTP 客户端

from ftplib import FTP
import os

def ftp_example():
    """FTP 操作示例"""
    # 连接 FTP 服务器
    ftp = FTP("ftp.example.com")
    ftp.login(user="username", passwd="password")

    # 获取欢迎信息
    print(ftp.getwelcome())

    # 列出目录内容
    ftp.dir()

    # 切换目录
    ftp.cwd("/public")

    # 下载文件
    with open("local_file.txt", "wb") as f:
        ftp.retrbinary("RETR remote_file.txt", f.write)

    # 上传文件
    with open("local_file.txt", "rb") as f:
        ftp.storbinary("STOR uploaded_file.txt", f)

    # 创建目录
    ftp.mkd("new_directory")

    # 删除文件
    ftp.delete("file_to_delete.txt")

    # 删除目录
    ftp.rmd("directory_to_delete")

    # 关闭连接
    ftp.quit()

# 匿名 FTP
def anonymous_ftp():
    """匿名 FTP 连接"""
    ftp = FTP("ftp.debian.org")
    ftp.login()  # 匿名登录
    ftp.cwd("/debian")
    ftp.dir()
    ftp.quit()

电子邮件

发送邮件(SMTP)

import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email import encoders

def send_email():
    """发送电子邮件"""
    # 邮件配置
    smtp_server = "smtp.gmail.com"
    smtp_port = 587
    sender_email = "your_email@gmail.com"
    sender_password = "your_password"
    receiver_email = "receiver@example.com"

    # 创建邮件
    message = MIMEMultipart()
    message["From"] = sender_email
    message["To"] = receiver_email
    message["Subject"] = "测试邮件"

    # 邮件正文
    body = "这是一封测试邮件"
    message.attach(MIMEText(body, "plain"))

    # 添加附件
    filename = "document.pdf"
    with open(filename, "rb") as attachment:
        part = MIMEBase("application", "octet-stream")
        part.set_payload(attachment.read())
        encoders.encode_base64(part)
        part.add_header(
            "Content-Disposition",
            f"attachment; filename= {filename}"
        )
        message.attach(part)

    # 发送邮件
    try:
        server = smtplib.SMTP(smtp_server, smtp_port)
        server.ehlo()
        server.starttls()
        server.ehlo()
        server.login(sender_email, sender_password)
        server.sendmail(sender_email, receiver_email, message.as_string())
        print("邮件发送成功")
    except Exception as e:
        print(f"邮件发送失败: {e}")
    finally:
        server.quit()

接收邮件(POP3/IMAP)

import poplib
import imaplib
import email

def receive_email_pop3():
    """使用 POP3 接收邮件"""
    pop3_server = "pop.gmail.com"
    username = "your_email@gmail.com"
    password = "your_password"

    # 连接服务器
    server = poplib.POP3_SSL(pop3_server)
    server.user(username)
    server.pass_(password)

    # 获取邮件数量
    num_messages = len(server.list()[1])
    print(f"共有 {num_messages} 封邮件")

    # 获取最新邮件
    if num_messages > 0:
        response, lines, octets = server.retr(num_messages)
        msg_content = b"\r\n".join(lines).decode("utf-8")
        msg = email.message_from_string(msg_content)

        print(f"主题: {msg['Subject']}")
        print(f"发件人: {msg['From']}")

        # 获取邮件正文
        if msg.is_multipart():
            for part in msg.walk():
                if part.get_content_type() == "text/plain":
                    print(part.get_payload(decode=True).decode("utf-8"))
        else:
            print(msg.get_payload(decode=True).decode("utf-8"))

    server.quit()

def receive_email_imap():
    """使用 IMAP 接收邮件"""
    imap_server = "imap.gmail.com"
    username = "your_email@gmail.com"
    password = "your_password"

    # 连接服务器
    mail = imaplib.IMAP4_SSL(imap_server)
    mail.login(username, password)
    mail.select("inbox")

    # 搜索邮件
    status, messages = mail.search(None, "ALL")
    email_ids = messages[0].split()

    print(f"共有 {len(email_ids)} 封邮件")

    # 获取最新邮件
    if email_ids:
        latest_email_id = email_ids[-1]
        status, msg_data = mail.fetch(latest_email_id, "(RFC822)")

        msg = email.message_from_bytes(msg_data[0][1])
        print(f"主题: {msg['Subject']}")
        print(f"发件人: {msg['From']}")

    mail.close()
    mail.logout()

SSH 连接

安装

pip install paramiko

SSH 客户端

import paramiko

def ssh_execute_command(hostname, username, password, command):
    """执行远程命令"""
    # 创建 SSH 客户端
    ssh = paramiko.SSHClient()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())

    try:
        # 连接服务器
        ssh.connect(hostname, username=username, password=password)

        # 执行命令
        stdin, stdout, stderr = ssh.exec_command(command)

        # 获取输出
        output = stdout.read().decode('utf-8')
        error = stderr.read().decode('utf-8')

        if output:
            print(f"输出:\n{output}")
        if error:
            print(f"错误:\n{error}")

    except Exception as e:
        print(f"SSH 错误: {e}")
    finally:
        ssh.close()

def ssh_transfer_file(hostname, username, password, local_path, remote_path):
    """传输文件"""
    transport = paramiko.Transport((hostname, 22))
    transport.connect(username=username, password=password)

    sftp = paramiko.SFTPClient.from_transport(transport)

    try:
        # 上传文件
        sftp.put(local_path, remote_path)
        print(f"文件已上传: {remote_path}")

        # 下载文件
        # sftp.get(remote_path, local_path)

    except Exception as e:
        print(f"SFTP 错误: {e}")
    finally:
        sftp.close()
        transport.close()

# 使用密钥认证
def ssh_with_key(hostname, username, key_path, command):
    """使用密钥认证"""
    ssh = paramiko.SSHClient()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())

    private_key = paramiko.RSAKey.from_private_key_file(key_path)
    ssh.connect(hostname, username=username, pkey=private_key)

    stdin, stdout, stderr = ssh.exec_command(command)
    print(stdout.read().decode('utf-8'))

    ssh.close()

综合实战

实战1: RESTful API 客户端

"""
RESTful API 客户端
展示完整的 HTTP API 调用封装
"""

import requests
from typing import Dict, List, Optional, Any
import json

class APIClient:
    """RESTful API 客户端"""

    def __init__(self, base_url: str, api_key: Optional[str] = None):
        self.base_url = base_url.rstrip('/')
        self.session = requests.Session()

        # 设置默认请求头
        self.session.headers.update({
            "Content-Type": "application/json",
            "Accept": "application/json"
        })

        # 添加 API Key
        if api_key:
            self.session.headers.update({
                "Authorization": f"Bearer {api_key}"
            })

    def _build_url(self, endpoint: str) -> str:
        """构建完整 URL"""
        return f"{self.base_url}/{endpoint.lstrip('/')}"

    def _handle_response(self, response: requests.Response) -> Dict[str, Any]:
        """处理响应"""
        try:
            response.raise_for_status()
            return {
                "success": True,
                "data": response.json() if response.content else None,
                "status_code": response.status_code
            }
        except requests.exceptions.HTTPError as e:
            return {
                "success": False,
                "error": str(e),
                "status_code": response.status_code,
                "data": response.text
            }

    def get(self, endpoint: str, params: Optional[Dict] = None) -> Dict[str, Any]:
        """GET 请求"""
        url = self._build_url(endpoint)
        response = self.session.get(url, params=params)
        return self._handle_response(response)

    def post(self, endpoint: str, data: Optional[Dict] = None) -> Dict[str, Any]:
        """POST 请求"""
        url = self._build_url(endpoint)
        response = self.session.post(url, json=data)
        return self._handle_response(response)

    def put(self, endpoint: str, data: Optional[Dict] = None) -> Dict[str, Any]:
        """PUT 请求"""
        url = self._build_url(endpoint)
        response = self.session.put(url, json=data)
        return self._handle_response(response)

    def delete(self, endpoint: str) -> Dict[str, Any]:
        """DELETE 请求"""
        url = self._build_url(endpoint)
        response = self.session.delete(url)
        return self._handle_response(response)

    def close(self):
        """关闭会话"""
        self.session.close()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()

# 使用示例
def main():
    # 使用 JSONPlaceholder 测试 API
    with APIClient("https://jsonplaceholder.typicode.com") as client:

        # 获取帖子列表
        result = client.get("/posts", params={"_limit": 5})
        if result["success"]:
            print(f"获取到 {len(result['data'])} 个帖子")
            for post in result["data"][:2]:
                print(f"  - {post['title'][:50]}...")

        # 获取单个帖子
        result = client.get("/posts/1")
        if result["success"]:
            print(f"\n帖子标题: {result['data']['title']}")

        # 创建新帖子
        new_post = {
            "title": "Test Post",
            "body": "This is a test",
            "userId": 1
        }
        result = client.post("/posts", data=new_post)
        if result["success"]:
            print(f"\n创建帖子 ID: {result['data']['id']}")

        # 更新帖子
        update_data = {"title": "Updated Title"}
        result = client.put("/posts/1", data=update_data)
        if result["success"]:
            print(f"更新成功")

        # 删除帖子
        result = client.delete("/posts/1")
        if result["success"]:
            print(f"删除成功")

if __name__ == "__main__":
    main()

实战2: 聊天室服务器

"""
聊天室服务器
展示 Socket 和线程的综合应用
"""

import socket
import threading
import json
from datetime import datetime
from typing import Set, Dict

class ChatRoom:
    """聊天室"""

    def __init__(self):
        self.clients: Dict[str, socket.socket] = {}
        self.usernames: Dict[socket.socket, str] = {}
        self.lock = threading.Lock()

    def add_client(self, client_socket: socket.socket, username: str):
        """添加客户端"""
        with self.lock:
            self.clients[username] = client_socket
            self.usernames[client_socket] = username

    def remove_client(self, client_socket: socket.socket):
        """移除客户端"""
        with self.lock:
            if client_socket in self.usernames:
                username = self.usernames.pop(client_socket)
                self.clients.pop(username, None)
                return username
        return None

    def broadcast(self, message: str, exclude: socket.socket = None):
        """广播消息"""
        with self.lock:
            for username, client_socket in self.clients.items():
                if client_socket != exclude:
                    try:
                        client_socket.send(message.encode('utf-8'))
                    except:
                        pass

    def get_online_users(self) -> list:
        """获取在线用户列表"""
        with self.lock:
            return list(self.clients.keys())

class ChatServer:
    """聊天服务器"""

    def __init__(self, host='127.0.0.1', port=8888):
        self.host = host
        self.port = port
        self.chat_room = ChatRoom()
        self.server_socket = None

    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}")

                # 为每个客户端创建线程
                thread = threading.Thread(
                    target=self.handle_client,
                    args=(client_socket, address)
                )
                thread.daemon = True
                thread.start()

        except KeyboardInterrupt:
            print("\n[服务器关闭]")
        finally:
            self.server_socket.close()

    def handle_client(self, client_socket: socket.socket, address):
        """处理客户端"""
        username = None

        try:
            # 获取用户名
            client_socket.send("请输入用户名: ".encode('utf-8'))
            username = client_socket.recv(1024).decode('utf-8').strip()

            if not username:
                username = f"用户{address[1]}"

            # 注册客户端
            self.chat_room.add_client(client_socket, username)

            # 通知所有人
            welcome_msg = self._format_message("系统", f"{username} 加入了聊天室")
            self.chat_room.broadcast(welcome_msg)

            # 发送在线用户列表
            online_users = self.chat_room.get_online_users()
            user_list_msg = self._format_message("系统", f"在线用户: {', '.join(online_users)}")
            client_socket.send(user_list_msg.encode('utf-8'))

            # 处理消息
            while True:
                data = client_socket.recv(1024)
                if not data:
                    break

                message = data.decode('utf-8').strip()

                if message.lower() == '/quit':
                    break

                # 处理命令
                if message.startswith('/'):
                    self._handle_command(client_socket, message)
                else:
                    # 广播消息
                    formatted_msg = self._format_message(username, message)
                    self.chat_room.broadcast(formatted_msg)

        except Exception as e:
            print(f"[错误] {e}")
        finally:
            # 清理
            if username:
                removed_user = self.chat_room.remove_client(client_socket)
                if removed_user:
                    leave_msg = self._format_message("系统", f"{removed_user} 离开了聊天室")
                    self.chat_room.broadcast(leave_msg)

            client_socket.close()
            print(f"[断开连接] {address}")

    def _handle_command(self, client_socket: socket.socket, command: str):
        """处理命令"""
        parts = command.split(' ', 1)
        cmd = parts[0].lower()

        if cmd == '/users':
            users = self.chat_room.get_online_users()
            msg = self._format_message("系统", f"在线用户: {', '.join(users)}")
            client_socket.send(msg.encode('utf-8'))

        elif cmd == '/help':
            help_text = """
可用命令:
  /users - 查看在线用户
  /help  - 显示帮助
  /quit  - 退出聊天室
            """
            client_socket.send(help_text.encode('utf-8'))

        else:
            msg = self._format_message("系统", f"未知命令: {cmd}")
            client_socket.send(msg.encode('utf-8'))

    def _format_message(self, username: str, message: str) -> str:
        """格式化消息"""
        timestamp = datetime.now().strftime("%H:%M:%S")
        return json.dumps({
            "timestamp": timestamp,
            "username": username,
            "message": message
        }, ensure_ascii=False) + "\n"

# 聊天客户端
def chat_client(host='127.0.0.1', port=8888):
    """聊天客户端"""
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    try:
        client.connect((host, port))
        print("[已连接到聊天服务器]")

        # 启动接收消息的线程
        receive_thread = threading.Thread(target=receive_messages, args=(client,))
        receive_thread.daemon = True
        receive_thread.start()

        # 发送消息
        while True:
            message = input()
            if message:
                client.send(message.encode('utf-8'))
                if message.lower() == '/quit':
                    break

    except Exception as e:
        print(f"[错误] {e}")
    finally:
        client.close()
        print("[连接关闭]")

def receive_messages(client_socket: socket.socket):
    """接收消息"""
    try:
        while True:
            data = client_socket.recv(1024)
            if not data:
                break

            msg = json.loads(data.decode('utf-8'))
            print(f"\n[{msg['timestamp']}] {msg['username']}: {msg['message']}")
            print("> ", end='', flush=True)

    except Exception:
        pass

if __name__ == "__main__":
    import sys

    if len(sys.argv) > 1 and sys.argv[1] == "server":
        server = ChatServer()
        server.start()
    else:
        chat_client()

实战3: 网络扫描器

"""
网络扫描器
展示 Socket 编程的实际应用
"""

import socket
import threading
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
from typing import List, Dict

class PortScanner:
    """端口扫描器"""

    def __init__(self, target: str, timeout: float = 1.0):
        self.target = target
        self.timeout = timeout
        self.open_ports: List[int] = []
        self.lock = threading.Lock()

    def scan_port(self, port: int) -> bool:
        """扫描单个端口"""
        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.settimeout(self.timeout)

            result = sock.connect_ex((self.target, port))
            sock.close()

            if result == 0:
                with self.lock:
                    self.open_ports.append(port)
                return True
            return False

        except Exception:
            return False

    def scan_range(self, start_port: int, end_port: int, max_workers: int = 100) -> List[int]:
        """扫描端口范围"""
        print(f"开始扫描 {self.target} 的端口 {start_port}-{end_port}...")
        start_time = time.time()

        with ThreadPoolExecutor(max_workers=max_workers) as executor:
            futures = {
                executor.submit(self.scan_port, port): port
                for port in range(start_port, end_port + 1)
            }

            for future in as_completed(futures):
                port = futures[future]
                if future.result():
                    print(f"  ✓ 端口 {port} 开放")

        elapsed = time.time() - start_time
        print(f"\n扫描完成! 耗时: {elapsed:.2f}秒")
        print(f"开放的端口: {sorted(self.open_ports)}")

        return sorted(self.open_ports)

    def get_service_name(self, port: int) -> str:
        """获取服务名称"""
        common_services = {
            21: "FTP",
            22: "SSH",
            23: "Telnet",
            25: "SMTP",
            53: "DNS",
            80: "HTTP",
            110: "POP3",
            143: "IMAP",
            443: "HTTPS",
            993: "IMAPS",
            995: "POP3S",
            3306: "MySQL",
            5432: "PostgreSQL",
            6379: "Redis",
            8080: "HTTP-Proxy",
            8443: "HTTPS-Alt"
        }
        return common_services.get(port, "Unknown")

    def scan_with_services(self, start_port: int = 1, end_port: int = 1024) -> Dict[int, str]:
        """扫描端口并识别服务"""
        open_ports = self.scan_range(start_port, end_port)

        services = {}
        for port in open_ports:
            service = self.get_service_name(port)
            services[port] = service
            print(f"  端口 {port}: {service}")

        return services

class NetworkInfo:
    """网络信息查询"""

    @staticmethod
    def get_local_ip() -> str:
        """获取本地 IP"""
        try:
            s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            s.connect(("8.8.8.8", 80))
            ip = s.getsockname()[0]
            s.close()
            return ip
        except Exception:
            return "127.0.0.1"

    @staticmethod
    def get_hostname() -> str:
        """获取主机名"""
        return socket.gethostname()

    @staticmethod
    def resolve_hostname(hostname: str) -> str:
        """解析主机名"""
        try:
            return socket.gethostbyname(hostname)
        except socket.gaierror:
            return None

    @staticmethod
    def reverse_lookup(ip: str) -> str:
        """反向查找主机名"""
        try:
            return socket.gethostbyaddr(ip)[0]
        except socket.herror:
            return None

# 使用示例
def main():
    # 显示本地网络信息
    info = NetworkInfo()
    print("="*60)
    print("网络信息")
    print("="*60)
    print(f"主机名: {info.get_hostname()}")
    print(f"本地 IP: {info.get_local_ip()}")

    # 扫描本地常见端口
    target = "127.0.0.1"
    print(f"\n{'='*60}")
    print(f"扫描目标: {target}")
    print(f"{'='*60}")

    scanner = PortScanner(target, timeout=0.5)
    services = scanner.scan_with_services(1, 1024)

    if services:
        print(f"\n发现 {len(services)} 个开放端口:")
        for port, service in sorted(services.items()):
            print(f"  {port}/tcp - {service}")
    else:
        print("未发现开放端口")

if __name__ == "__main__":
    main()

小结

协议/技术说明使用场景
Socket底层网络通信自定义协议
TCP可靠传输文件传输、聊天
UDP快速传输视频流、游戏
HTTPWeb 协议API 调用、网页
RequestsHTTP 库RESTful API
WebSocket双向通信实时聊天、推送
FTP文件传输文件上传下载
SMTP邮件发送发送邮件
SSH安全远程访问远程管理

核心要点

  • 理解 TCP 和 UDP 的区别
  • 掌握 Socket 编程基础
  • 熟练使用 Requests 库
  • 注意异常处理和超时设置
  • 合理使用线程提高并发
  • 遵循网络安全最佳实践
  • 选择合适的协议和工具

掌握网络编程将帮助你构建各种网络应用!