关于Python:5. Python网络编程

一些基本概念:

概念说明
IP地址网络中唯一标识一台主机的地址(如 192.168.1.1)
端口号标识某台主机上的具体服务(如 80 是 HTTP 服务)
协议通信的规则和格式,常见如 TCP、UDP、HTTP、FTP
客户端/服务器客户端发起请求,服务器响应请求
DNS域名解析服务,将域名转为 IP 地址

一、网络基础知识

1. 计算机网络的分层结构

计算机网络通信是复杂的系统,为了简化设计,采用分层结构:

OSI 七层模型(理论模型)

层级名称作用简介
7应用层用户软件与网络交互,如HTTP、FTP
6表示层数据格式转换、加解密、压缩
5会话层建立、管理和终止会话
4传输层端到端传输控制,TCP/UDP
3网络层IP寻址与路由选择(IP、ICMP)
2数据链路层点到点传输,MAC地址,ARP协议
1物理层物理传输,比特流(如电信号、光信号)

TCP/IP 四层模型(实际使用)

层级名称OSI对应协议举例
4应用层7,6,5HTTP, HTTPS, FTP, DNS
3传输层4TCP, UDP
2网络层3IP, ICMP
1网络接口层2,1Ethernet, PPP

2. 常见协议讲解

HTTP / HTTPS

  • HTTP 是超文本传输协议,基于 TCP,明文传输。

  • HTTPS 是加密的 HTTP,使用 TLS/SSL。

HTTP 报文结构:

GET /index.html HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0
...
  • 请求方法: GET、POST、PUT、DELETE...

  • 状态码: 200 成功、404 未找到、500 服务器错误

TCP 与 UDP(传输层)

特点TCPUDP
是否连接面向连接(三次握手)无连接
可靠性可靠(重传、校验、确认)不可靠
传输速度
应用举例HTTP、FTP、SMTPDNS、视频、语音、游戏

三次握手流程:

  1. 客户端发送 SYN

  2. 服务器回复 SYN+ACK

  3. 客户端回复 ACK

四次挥手流程:

  1. 主动方发送 FIN

  2. 被动方回复 ACK

  3. 被动方发送 FIN

  4. 主动方回复 ACK

3. IP地址、子网与路由

 IP 地址分类(IPv4)

类别地址范围特点
A1.0.0.0 - 126.255.255.255大型网络
B128.0.0.0 - 191.255.255.255中型网络
C192.0.0.0 - 223.255.255.255小型网络

私有地址(常见于内网)

  • A类:10.0.0.0/8

  • B类:172.16.0.0/12

  • C类:192.168.0.0/16

子网掩码

用来划分子网,例如:

  • IP: 192.168.1.1

  • 子网掩码:255.255.255.0   表示前24位是网络位,后8位是主机位

4. DNS域名解析流程

  • 浏览器检查本地缓存

  • 向本地 DNS 查询

  • 本地 DNS 若无结果则向根服务器查找

  • 找到对应的权威 DNS 返回 IP 地址


二、Socket 编程

Socket 是“套接字”的意思,是操作系统提供的一种网络通信接口。它是网络编程中最底层、最关键的部分,用来建立客户端和服务端之间的连接,实现数据的发送与接收。

可以把 Socket 理解为一个通信端口,客户端和服务端都通过各自的 Socket 建立连接,就像电话的两端:

  • 客户端主动拨号(connect)

  • 服务器监听电话(listen),接听(accept)

  • 双方通话(send、recv)

  • 最后挂断电话(close)

1. Socket 通信步骤(以 TCP 为例)

服务端流程:

  1. socket() 创建 Socket 对象

  2. bind() 绑定 IP 和端口

  3. listen() 开始监听

  4. accept() 等待客户端连接

  5. recv() 接收数据

  6. send() 发送响应

  7. close() 关闭连接

客户端流程:

  1. socket() 创建 Socket 对象

  2. connect() 连接服务端

  3. send() 发送请求

  4. recv() 接收服务端返回

  5. close() 关闭连接

2. 详细案例:模拟聊天室(单连接版本)

服务端代码(server.py)

import socket

# 创建 socket 对象
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 绑定 IP 和端口
server.bind(('127.0.0.1', 8000))

# 开始监听,最多同时挂起 5 个连接
server.listen(5)
print("服务器启动,等待连接...")

# 等待客户端连接
conn, addr = server.accept()
print("连接来自:", addr)

while True:
    # 接收客户端数据
    data = conn.recv(1024)
    if not data:
        break  # 断开连接
    print("客户端说:", data.decode())

    # 发送响应
    reply = input("你回复:")
    conn.send(reply.encode())

# 关闭连接
conn.close()
server.close()

客户端代码(client.py)

import socket

# 创建 socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 连接服务器
client.connect(('127.0.0.1', 8000))
print("连接服务器成功")

while True:
    msg = input("你说:")
    if msg.lower() == 'exit':
        break
    client.send(msg.encode())

    # 接收服务端返回
    reply = client.recv(1024)
    print("服务器说:", reply.decode())

# 关闭连接
client.close()

使用说明:

  1. 先运行服务端:python server.py

  2. 再运行客户端:python client.py

  3. 客户端输入消息,服务器可接收并回复

  4. 双向通信,直到 exit

3. Socket 编程核心参数解释

socket.socket(family=AF_INET, type=SOCK_STREAM)
参数含义
AF_INET使用 IPv4 地址
SOCK_STREAM使用 TCP 协议(若用 UDP 则写 SOCK_DGRAM)

4. Socket 编程注意事项

  • 每个连接都需要一个 socket 对象处理:服务端 accept 之后要分线程处理

  • 传输的数据需是字节串(bytes),不是字符串,注意编码 encode()/decode()

  • recv() 是阻塞的,没有数据时会等待

  • 端口被占用或断线要处理异常

5. 基于 Python Socket + 多线程的群聊软件

软件结构

  • server.py:服务端(监听连接 + 广播消息)

  • client.py:客户端(发送 + 接收消息)

服务端 server.py

import socket
import threading

# 存储所有客户端连接
clients = []

# 广播消息给所有客户端
def broadcast(message, sender_socket):
    for client in clients:
        if client != sender_socket:
            try:
                client.send(message)
            except:
                clients.remove(client)

# 每个客户端的处理线程
def handle_client(client_socket, addr):
    print(f"[+] 新连接:{addr}")
    clients.append(client_socket)
    client_socket.send("欢迎加入群聊!\n".encode())

    while True:
        try:
            msg = client_socket.recv(1024)
            if not msg:
                break
            full_msg = f"[{addr[0]}:{addr[1]}] 说:{msg.decode()}"
            print(full_msg.strip())
            broadcast(full_msg.encode(), client_socket)
        except:
            break

    print(f"[-] 断开连接:{addr}")
    clients.remove(client_socket)
    client_socket.close()

def main():
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.bind(('0.0.0.0', 9999))
    server.listen(10)
    print("[*] 群聊服务器已启动,等待客户端连接...")

    while True:
        client_socket, addr = server.accept()
        thread = threading.Thread(target=handle_client, args=(client_socket, addr))
        thread.daemon = True
        thread.start()

if __name__ == '__main__':
    main()

客户端 client.py

import socket
import threading

def receive_messages(sock):
    while True:
        try:
            data = sock.recv(1024)
            if not data:
                break
            print(data.decode())
        except:
            break

def main():
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    client.connect(('127.0.0.1', 9999))  # 改成你服务端IP也可以
    print("已连接到群聊服务器。输入内容开始聊天,输入 exit 退出。\n")

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

    while True:
        msg = input()
        if msg.lower() == 'exit':
            break
        try:
            client.send(msg.encode())
        except:
            break

    client.close()
    print("已退出群聊。")

if __name__ == '__main__':
    main()

运行说明

打开终端运行服务端:

python server.py

输出:

[*] 群聊服务器已启动,等待客户端连接...

多个终端分别运行客户端(你可以多开几个):

python client.py

输入内容即可发消息,其他客户端会同步收到。输入 exit 退出群聊。

关键点解释

技术说明
socket.socket()创建 TCP 连接
threading.Thread多线程处理多个客户端
broadcast()把消息发给除自己以外的所有人
recv()send()接收和发送字节数据
daemon=True子线程随着主线程退出而退出

三、socket.makefile()

将 Socket 对象包装成类文件对象(类文件接口),从而可以使用 readline()read()write() 等操作,就像操作文件一样。

Python 的 socket 通常使用的是低级接口,如:

data = sock.recv(1024)
sock.send(b"hello")

这种方式只能收发固定长度的字节串,不太适合处理“按行读取”等文本协议(如 HTTP、SMTP、POP3 等)。

1. makefile() 的作用

sock_file = sock.makefile(mode='rwb')

你可以将一个 socket 包装成“文件流”,用 sock_file.read(), sock_file.readline(),更加灵活,特别适合“基于行”的通信协议。

2. 方法签名

socket.makefile(mode='r', buffering=None, encoding=None, errors=None, newline=None)
参数作用
mode模式,如 'r'(读)、'w'(写)、'rb'(二进制读)等
buffering缓冲策略(通常默认即可)
encoding文本编码,如 'utf-8'
newline设置换行符行为

3. 实际例子:模拟 HTTP 服务端

import socket

def handle_client(conn):
    # 将 socket 包装为类文件对象,方便按行读取
    rfile = conn.makefile('r', encoding='utf-8')
    wfile = conn.makefile('w', encoding='utf-8')

    # 读取第一行(GET / HTTP/1.1)
    line = rfile.readline()
    print("请求行:", line.strip())

    # 忽略请求头,只读空行分隔
    while rfile.readline().strip() != "":
        continue

    # 写入 HTTP 响应
    wfile.write("HTTP/1.1 200 OK\r\n")
    wfile.write("Content-Type: text/plain\r\n")
    wfile.write("\r\n")  # 头部结束
    wfile.write("Hello from Python server!\n")
    wfile.flush()

    # 关闭文件流和 socket
    wfile.close()
    rfile.close()
    conn.close()

def main():
    server = socket.socket()
    server.bind(('0.0.0.0', 8888))
    server.listen(1)
    print("服务器已启动:http://127.0.0.1:8888")

    while True:
        conn, addr = server.accept()
        handle_client(conn)

if __name__ == '__main__':
    main()

功能:

  • 可以用浏览器访问 http://127.0.0.1:8888

  • 返回纯文本内容

  • makefile() 实现按行读取 HTTP 请求,便于分析请求行和头部

4. 常见用法场景

场景为什么适合用 makefile()
HTTP 协议请求头是按行组织的,需要 readline()
SMTP/POP3邮件协议以行分隔命令和响应
自定义协议若你设计了以\n结尾的消息格式
使用 json, text, base64 编码不需要手动切片二进制流

5. 与 recv()/send() 区别总结

方法特点
recv() / send()底层,二进制收发,需要自己控制协议边界
makefile() 文件流高层,支持按行读写,适合文本协议处理

四、udp

UDP 是一种无连接、不可靠但速度快的协议。

与 TCP 的区别:

特性TCPUDP
是否连接是(连接后通信)否(直接发消息)
是否可靠是(顺序、重传)否(可能丢失)
应用场景HTTP、FTP、SSH视频、语音、DNS、局域网群聊
编程模型服务端需 accept()直接 recvfrom()/sendto()

1. UDP 编程基础结构

服务端(接收消息):

import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(("0.0.0.0", 9999))

while True:
    data, addr = sock.recvfrom(1024)
    print(f"收到来自 {addr} 的消息:{data.decode()}")

客户端(发送消息):

import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

while True:
    msg = input("你说:")
    sock.sendto(msg.encode(), ("127.0.0.1", 9999))

注意:UDP 不需要 listen()accept(),而是直接收发。

2. UDP 群聊系统实现(支持多客户端)

功能

  • 所有人加入就可以发消息

  • 所有人能实时收到群消息

  • 基于局域网(不跨公网)

服务器端 udp_server.py

import socket

# 用于存储所有客户端的地址(set 自动去重)
clients = set()

def main():
    server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    server.bind(('0.0.0.0', 9999))

    print("[*] UDP 群聊服务器已启动,端口 9999")

    while True:
        data, addr = server.recvfrom(1024)
        message = data.decode()
        print(f"[{addr}]:{message}")

        # 加入客户端集合
        clients.add(addr)

        # 广播给其他所有客户端
        for client in clients:
            if client != addr:
                server.sendto(f"[{addr}] 说: {message}".encode(), client)

if __name__ == '__main__':
    main()

客户端 udp_client.py

import socket
import threading

SERVER_IP = '127.0.0.1'
SERVER_PORT = 9999

def receive(sock):
    while True:
        try:
            data, _ = sock.recvfrom(1024)
            print(data.decode())
        except:
            break

def main():
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

    # 启动接收线程
    threading.Thread(target=receive, args=(sock,), daemon=True).start()

    print("已加入群聊。输入内容发送消息,输入 exit 退出。")
    while True:
        msg = input()
        if msg.lower() == 'exit':
            break
        sock.sendto(msg.encode(), (SERVER_IP, SERVER_PORT))

    sock.close()

if __name__ == '__main__':
    main()

运行方式

启动服务器:

python udp_server.py

多个终端运行客户端(可复制运行):

python udp_client.py

聊天,输入内容自动广播给所有客户端。

关键点解释

关键词含义
recvfrom()接收数据,同时获取客户端地址
sendto()向指定地址发送数据
set()存储客户端地址集合,避免重复广播
多线程每个客户端用线程接收消息,防止阻塞
daemon=True守护线程,不阻止主线程退出

五、心跳机制

心跳机制(Heartbeat Mechanism)是网络通信中用于判断连接是否仍然有效的机制,常用于 TCP、WebSocket、UDP 等长连接场景,确保客户端/服务端还“活着”。

在长连接中,即使两端掉线,操作系统也可能不知道,这会造成资源浪费或服务异常。

例如:

  • 客户端死机、断网、宕机,但服务端还以为它在线

  • 服务端宕机,客户端还在等回复

所以需要“定时问候”一下彼此是否还活着,这就是心跳机制。

客户端 → 每隔5秒 → 发送 ping → 服务端
服务端 ← 立即回应 ← pong

服务端定时检查:超过10秒没收到 ping 的客户端 = 掉线

TCP 心跳机制实现

我们以 TCP 的 socket 连接为例,用两个线程:

  • 一个线程用来收消息

  • 一个线程用来定时发心跳

服务端:tcp_heartbeat_server.py

import socket
import threading
import time

clients = {}  # 存储客户端连接及最后心跳时间

def handle_client(conn, addr):
    clients[addr] = time.time()
    print(f"[{addr}] 连接")

    while True:
        try:
            data = conn.recv(1024)
            if not data:
                break

            msg = data.decode().strip()

            if msg == "ping":
                clients[addr] = time.time()
                conn.sendall(b"pong")
            else:
                print(f"[{addr}] 消息: {msg}")
                conn.sendall(b"ok")
        except:
            break

    print(f"[{addr}] 断开连接")
    conn.close()
    del clients[addr]

def check_heartbeat():
    while True:
        now = time.time()
        for addr, last in list(clients.items()):
            if now - last > 10:  # 超过10秒无响应
                print(f"[{addr}] 心跳超时,关闭连接")
                # 注意:这里只是标记断开,实际关闭应通过 conn.close()
                del clients[addr]
        time.sleep(5)

def main():
    server = socket.socket()
    server.bind(("0.0.0.0", 9001))
    server.listen(5)
    print("服务器启动,监听 9001")

    threading.Thread(target=check_heartbeat, daemon=True).start()

    while True:
        conn, addr = server.accept()
        threading.Thread(target=handle_client, args=(conn, addr), daemon=True).start()

if __name__ == "__main__":
    main()

客户端:tcp_heartbeat_client.py

import socket
import threading
import time

def receive(sock):
    while True:
        try:
            data = sock.recv(1024)
            if not data:
                break
            print("服务器回复:", data.decode())
        except:
            break

def heartbeat(sock):
    while True:
        try:
            sock.sendall(b"ping")
        except:
            break
        time.sleep(3)

def main():
    sock = socket.socket()
    sock.connect(("127.0.0.1", 9001))

    # 启动收消息线程
    threading.Thread(target=receive, args=(sock,), daemon=True).start()
    # 启动心跳线程
    threading.Thread(target=heartbeat, args=(sock,), daemon=True).start()

    # 输入消息
    while True:
        msg = input()
        if msg == "exit":
            break
        sock.sendall(msg.encode())

    sock.close()

if __name__ == "__main__":
    main()
技术点说明
ping/pong心跳包格式,可以是任何自定义内容
超时检测服务器定期检查客户端最后活跃时间
recv() 超时可配合 socket.settimeout() 判断
daemon=True心跳与接收线程不会阻塞主进程关闭
time.sleep()控制心跳频率(建议 5~30 秒一次)

六、socketserver

socketserver 是 Python 标准库中封装好的一个网络服务器框架,基于 socket 封装,自动处理:

  • 连接监听

  • 多客户端支持

  • 数据接收、响应

  • 多线程或多进程支持

简单说:用少量代码实现一个并发网络服务器!

手动 socket 写法(很烦):

while True:
    conn, addr = server.accept()
    # 手动写线程
    t = threading.Thread(target=handle, args=(conn,))
    t.start()

socketserver(超简单):

class MyHandler(socketserver.BaseRequestHandler):
    def handle(self):
        self.data = self.request.recv(1024)
        self.request.sendall(b'hi')

server = socketserver.ThreadingTCPServer(('0.0.0.0', 9001), MyHandler)
server.serve_forever()

它自动处理线程、连接管理,你只要管数据怎么处理。

1. 核心类介绍

类名描述
BaseRequestHandler所有请求处理类的基类
TCPServer单线程 TCP 服务器
ThreadingTCPServer多线程 TCP 服务器(常用)
ForkingTCPServer多进程 TCP 服务器(Unix)
UDPServer单线程 UDP 服务器
ThreadingUDPServer多线程 UDP 服务器

2. 最常用结构:多线程 TCP 服务

import socketserver

class MyHandler(socketserver.BaseRequestHandler):
    def handle(self):
        print(f"连接来自: {self.client_address}")
        while True:
            data = self.request.recv(1024)
            if not data:
                break
            print("收到:", data.decode())
            self.request.sendall(b"我收到了: " + data)

# 创建多线程 TCP 服务器
server = socketserver.ThreadingTCPServer(("0.0.0.0", 9001), MyHandler)
print("服务器已启动")
server.serve_forever()

客户端(可用原始 socket):

import socket

sock = socket.socket()
sock.connect(("127.0.0.1", 9001))

while True:
    msg = input("你说:")
    sock.send(msg.encode())
    data = sock.recv(1024)
    print("服务器说:", data.decode())

3. 关键点解释

概念解释
BaseRequestHandler自定义处理逻辑,重写 handle() 方法
self.request表示客户端连接对象(一个 socket)
self.client_address表示客户端 IP 和端口元组
ThreadingTCPServer每个客户端连接自动起一个线程
serve_forever()启动服务器并阻塞运行

总结:socketserver 是 Python 提供的一个高层网络服务器封装模块,让你专注写逻辑,不用再操心 socket 和多线程细节


七、同步和阻塞

同步 vs 异步

  • 同步(Synchronous):你做事必须一步一步等结果

                比喻:你排队打饭,前面人不动你就不能动。

  • 异步(Asynchronous):你发出请求就继续干别的,结果回来再处理。

                比喻:你点了外卖就继续学习,饭到了再吃。

阻塞 vs 非阻塞

  • 阻塞(Blocking):程序卡在那等结果,不返回。

                比喻:你打电话,电话没人接,你就只能一直等。

  • 非阻塞(Non-blocking):程序立刻返回,即使没结果也继续执行。

                比喻:你打电话没人接,立刻挂断去做别的事。

类型行为
同步阻塞一步一步 + 等结果,最常见的方式(比如 recv()
同步非阻塞一步一步 + 不等结果,很少用
异步阻塞程序逻辑异步,但中间有阻塞等待(比如异步里 await
异步非阻塞(理想)真正并发,不等待结果,比如回调、协程、事件驱动

1. 同步 + 阻塞(最常见)

客户端连接服务器后,recv() 会阻塞,直到有数据来。

import socket

sock = socket.socket()
sock.connect(('127.0.0.1', 9001))

sock.send(b'hello')

# 同步阻塞:这里会一直等,直到服务器发数据
data = sock.recv(1024)
print("收到:", data.decode())

运行这个,如果服务器不回复,程序会卡在 recv() 那一行。

2. 非阻塞 socket 示例

将 socket 设置为非阻塞模式,立刻返回,不管有没有数据。

import socket

sock = socket.socket()
sock.connect(('127.0.0.1', 9001))
sock.setblocking(False)  # 设置为非阻塞

sock.send(b'hello')

try:
    data = sock.recv(1024)  # 如果没数据会直接报错
    print("收到:", data.decode())
except BlockingIOError:
    print("当前没有数据,继续干别的事")

输出:

当前没有数据,继续干别的事

3. 异步 + 非阻塞(使用 asyncio)

import asyncio

async def client():
    reader, writer = await asyncio.open_connection('127.0.0.1', 9001)
    writer.write(b'hello\n')
    await writer.drain()

    # 等待数据(不阻塞整个程序,其他任务仍能运行)
    data = await reader.read(100)
    print("收到:", data.decode())

asyncio.run(client())

这个异步程序在 await 期间不会卡住主线程,可以同时执行多个任务。

总结

模式阻塞?异步?说明
sock.recv()阻塞同步一直等数据,主线程被卡住
sock.setblocking(False) + recv()非阻塞同步立刻返回,没有数据就报错
asyncio + await reader.read()非阻塞异步数据没来不会卡死主线程,可并发处理

八、IO多路复用

I/O 多路复用 是一种机制,允许单个线程/进程同时监听多个 socket 连接的读写状态,从而高效处理多个客户端连接。

原始 socket 写法(同步 + 阻塞)的问题:

  • 每个客户端都要一个线程,线程太多浪费资源。

  • 有些连接很久才发一次数据,占着资源没事干。

多路复用的优势是:

一个线程监听多个 socket,只在真正有数据时才处理,节省资源,效率高。

模型说明Python对应模块
select最基础,跨平台,但效率低(有最大连接限制)select.select()
poll没有连接数量限制,效率高select.poll()
epollLinux专用,效率最高(推荐)select.epoll()
主线程:
 ┌────────────────────────────┐
 │ select/poll/epoll 监听多个 socket │
 └────┬───────────────┬─────┘
      │               │
  客户端1           客户端2
(有数据)       (没数据)

=> 只处理有数据的 socket,节省 CPU 和线程资源

Python 示例:select 多路复用

这是一个使用 select 同时监听多个客户端的服务器示例。

import socket
import select

# 创建 TCP socket
server = socket.socket()
server.bind(('0.0.0.0', 9001))
server.listen()

server.setblocking(False)  # 设置非阻塞

inputs = [server]  # 要监听的 socket 列表

print("服务器启动")

while True:
    readable, _, _ = select.select(inputs, [], [])
    
    for sock in readable:
        if sock is server:
            conn, addr = server.accept()
            print("新连接:", addr)
            conn.setblocking(False)
            inputs.append(conn)  # 把新连接加入监听列表
        else:
            try:
                data = sock.recv(1024)
                if data:
                    print("收到:", data.decode())
                    sock.send(b"我收到了")
                else:
                    print("客户端断开")
                    inputs.remove(sock)
                    sock.close()
            except:
                inputs.remove(sock)
                sock.close()
模型特点建议用途
多线程/进程简单但资源开销大客户端少、简单场景
select跨平台,但连接数有限学习、简单并发场景
epoll高性能,适合高并发推荐用于生产环境
asyncio基于多路复用封装的协程模型写异步代码更优雅

I/O 多路复用就是一个线程高效监听多个 socket,只处理“有事的连接”,提高并发性能,是写高性能网络服务的基础。


九、事件驱动

事件驱动是一种编程模型,它的核心思想是:

        程序不主动一步步执行,而是等待事件发生,再执行对应的事件处理函数

就像生活中打电话、收快递:

  • 电话响(事件) → 你接电话(事件处理)

  • 快递到了(事件) → 你签收(事件处理)

事件驱动模型的流程

整体流程:

事件源(如 socket)→ 等待事件(注册)→ 检测事件发生 → 回调处理函数

举个实际例子(聊天服务器):

  1. 等待客户端连接

  2. 客户端发来消息(事件发生)

  3. 触发“on_message”处理函数,服务器回应

事件驱动 vs 常见模型

模型描述
同步阻塞模型一步步执行,哪个阻塞就卡哪
多线程模型每个连接用一个线程,同时处理多个
事件驱动模型一个线程 + 事件循环 + 回调函数,高效处理并发

事件驱动的关键组件

组件说明
事件源比如:socket、用户操作、定时器等
事件循环不断检测“是否有事件发生”
回调函数事件发生后自动调用的处理函数
事件注册表记录哪个事件应该调用哪个函数

Python 中事件驱动的常用方式

方式 1:asyncio(推荐)

import asyncio

async def handle_client(reader, writer):
    data = await reader.read(100)
    print("收到:", data.decode())
    
    # 将字符串转换为字节串
    writer.write("我收到了".encode('utf-8'))  
    await writer.drain()
    writer.close()

async def main():
    server = await asyncio.start_server(handle_client, '0.0.0.0', 9001)
    print("服务器启动")
    async with server:
        await server.serve_forever()

asyncio.run(main())

核心思想:事件发生时(socket 可读)→ 调用 handler

方式 2:自己实现 select + 回调(底层事件驱动)

import socket, select

def on_recv(sock):
    data = sock.recv(1024)
    print("收到:", data.decode())
    sock.send(b'收到')

sock = socket.socket()
sock.bind(('0.0.0.0', 9001))
sock.listen()
sock.setblocking(False)

inputs = [sock]
handlers = {}

while True:
    readable, _, _ = select.select(inputs, [], [])
    for s in readable:
        if s is sock:
            conn, addr = sock.accept()
            conn.setblocking(False)
            inputs.append(conn)
            handlers[conn] = on_recv
        else:
            handlers[s](s)

事件驱动的优点

  • 高性能:一个线程可管理上千个连接(如 nginx、Node.js)

  •  资源低:无需频繁创建线程或进程

  •  响应快:只处理“有事件”的部分

总结:事件驱动模型让程序“不主动等”,而是“监听 + 回调”,是高并发、高性能服务器的核心思想(如:asyncio、Node.js、nginx、epoll 等)。


十、WebSocket 通信

WebSocket 是一种用于在客户端与服务器之间建立持久化、双向通信的协议。它基于 TCP 协议,通过 HTTP 协议进行握手,连接建立后,客户端和服务器可以在不重复握手的情况下,随时交换数据,极大地提高了实时性和效率。

1. WebSocket 基本原理

WebSocket 协议在初次建立连接时,使用 HTTP 协议进行握手,之后转换为 WebSocket 协议进行双向通信,并保持该连接。

  1. HTTP 握手阶段

    • 客户端向服务器发送 WebSocket 握手请求(通过 HTTP 协议),并指定将要升级到 WebSocket 协议。

    • 服务器收到请求后,返回一个响应,表示同意升级协议。

    • 握手完成后,通信双方就建立了 WebSocket 连接。

  2. 数据传输阶段

    • 一旦连接建立,客户端和服务器可以随时向对方发送数据。

    • 数据传输基于 WebSocket 协议,是二进制数据和文本数据的流,并且在两端都可以异步发送和接收数据。

  3. 连接关闭阶段

    • 连接的关闭是由客户端或服务器发起的,双方进行“关闭握手”后,连接将被关闭。

2. WebSocket 的特点

  • 全双工:客户端和服务器可以同时双向传输数据,而不需要等待对方完成操作。

  • 长连接:WebSocket 协议会保持连接,在连接期间可以频繁交换数据,避免了每次请求都需要重新建立连接。

  • 低延迟:避免了传统的轮询或长轮询方式中多次请求的延迟,提高了实时性。

  • 节省带宽:相比 HTTP 协议每次请求都要包含完整的头信息,WebSocket 数据包更加轻量。

3. WebSocket 客户端和服务器实现

客户端代码(Python 使用 websockets 库)

首先,安装 websockets 库:

pip install websockets

客户端代码:

import asyncio
import websockets

async def hello():
    uri = "ws://localhost:8765"
    async with websockets.connect(uri) as websocket:
        await websocket.send("Hello Server!")
        response = await websocket.recv()
        print(f"服务器回应: {response}")

asyncio.get_event_loop().run_until_complete(hello())

服务器代码(Python 使用 websockets 库)

import asyncio
import websockets

async def echo(websocket, path):
    message = await websocket.recv()  # 接收客户端消息
    print(f"收到客户端消息: {message}")
    await websocket.send(f"服务器回应: {message}")  # 向客户端发送回应

start_server = websockets.serve(echo, "localhost", 8765)

# 启动 WebSocket 服务器
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()

解释:

  • websockets.connect(uri):客户端使用此函数连接到服务器,uri 表示服务器的 WebSocket 地址。

  • websocket.send():向服务器发送数据。

  • websocket.recv():接收服务器发送的数据。

  • websockets.serve():服务器端用于启动 WebSocket 服务,指定了服务器地址和端口。

4. WebSocket 协议握手过程

WebSocket 协议的握手阶段,实际上是一个 HTTP 升级请求,它使用 HTTP 协议发送一个特定的请求头来告知服务器,它希望升级为 WebSocket 连接。

客户端请求示例:

GET / HTTP/1.1
Host: localhost:8765
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
  • Upgrade: 表示客户端希望升级到 WebSocket 协议。

  • Connection: 必须是 Upgrade,表示连接要被升级。

  • Sec-WebSocket-Key: 随机生成的键,服务器需要使用这个键生成一个响应。

  • Sec-WebSocket-Version: 客户端支持的 WebSocket 协议版本。

服务器回应示例:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: dGhlIHNhbXBsZSBub25jZQ==
  • Sec-WebSocket-Accept: 服务器通过 Sec-WebSocket-Key 和一个固定的 GUID 生成的响应密钥,用来验证客户端的请求。

握手完成后,客户端和服务器就切换到 WebSocket 协议进行数据传输。

5. WebSocket 关闭连接

WebSocket 连接的关闭是由客户端或服务器发起的关闭握手

客户端发起关闭:

await websocket.close()

服务器发起关闭:

await websocket.close()

关闭后,连接会被切断,任何后续数据都无法再传输。

总结:WebSocket 提供了一个高效的双向通信协议,广泛用于需要实时通信的应用场景(如即时聊天、游戏、金融实时数据等)。相比传统的 HTTP,WebSocket 在性能和实时性上具有显著优势。


十一、requests 库

requests 是一个非常流行且简洁易用的 Python 庑库,主要用于发送 HTTP 请求,获取服务器的响应。它比 Python 标准库中的 urllib 更加简单和直观,常用于与 Web 服务进行交互(例如 REST API 调用、网页爬取等)。

1. 安装 requests

首先,如果你的环境中没有安装 requests,可以通过以下命令安装:

pip install requests

2. 基本概念

requests 库主要用于发起 HTTP 请求,并能够处理 HTTP 的请求头、请求体、响应头、响应体等内容。它支持各种 HTTP 方法(如 GET、POST、PUT、DELETE、HEAD 等)和一些高级特性(如会话维持、身份验证、代理等)。

常见 HTTP 请求方法

  1. GET:用于从服务器获取资源。请求数据通过 URL 传递。

  2. POST:用于向服务器提交数据(例如表单数据或 JSON 数据)。

  3. PUT:用于向服务器更新现有资源。

  4. DELETE:用于删除服务器上的资源。

  5. HEAD:与 GET 方法类似,但只返回响应头,不返回响应体。

3. 常见功能及用法

GET 请求

GET 请求用于获取资源(如网页内容、API 返回数据等)。通过 requests.get() 发起请求。

import requests

# 发起 GET 请求
response = requests.get("https://jsonplaceholder.typicode.com/posts")

# 获取响应内容
print(response.text)  # 返回响应体文本内容
print(response.status_code)  # 返回响应的状态码
print(response.json())  # 如果响应内容是 JSON 格式,使用 .json() 转换为 Python 字典
  • response.text:返回响应体的文本内容。

  • response.status_code:返回 HTTP 响应的状态码。

  • response.json():如果服务器返回的是 JSON 格式数据,可以使用此方法将其转换为 Python 字典。

POST 请求

POST 请求用于向服务器发送数据(如表单数据)。通过 requests.post() 发起请求。

import requests

url = "https://jsonplaceholder.typicode.com/posts"
data = {"title": "foo", "body": "bar", "userId": 1}

# 发起 POST 请求
response = requests.post(url, json=data)  # 使用 json 参数自动将数据转换为 JSON 格式

# 获取响应内容
print(response.text)

json=data:将 Python 字典自动转化为 JSON 格式的请求体并发送。

发送表单数据(POST)

import requests

url = "https://httpbin.org/post"
data = {"username": "admin", "password": "123456"}

# 使用 form 表单提交数据
response = requests.post(url, data=data)

print(response.json())  # 获取响应的 JSON 格式数据

data=data:将数据以表单格式发送。

发送文件(POST)

import requests

url = "https://httpbin.org/post"
files = {'file': open('example.txt', 'rb')}  # 以二进制方式打开文件

# 发送文件
response = requests.post(url, files=files)

print(response.json())

files=files:用于上传文件。可以上传一个或多个文件。

添加请求头

import requests

url = "https://jsonplaceholder.typicode.com/posts"
headers = {
    "User-Agent": "my-app",
    "Authorization": "Bearer <your_token>"
}

response = requests.get(url, headers=headers)

print(response.text)

headers:可以通过传递字典来指定请求头。

URL 参数(Query Parameters)

import requests

url = "https://jsonplaceholder.typicode.com/posts"
params = {'userId': 1}

# 发起 GET 请求,带 URL 参数
response = requests.get(url, params=params)

print(response.text)

params=params:URL 参数会被自动拼接到 URL 后面。

处理 JSON 响应

import requests

url = "https://jsonplaceholder.typicode.com/posts/1"
response = requests.get(url)

# 获取 JSON 数据并处理
data = response.json()
print(data)

使用 response.json() 直接获取 JSON 响应并转换成 Python 字典。

异常处理

请求可能会因为多种原因失败(如网络问题、服务器不可达等)。因此,在使用 requests 时,可以通过异常处理来捕获错误。

import requests
from requests.exceptions import RequestException

try:
    response = requests.get("https://jsonplaceholder.typicode.com/posts")
    response.raise_for_status()  # 如果响应状态码不是 2xx,会抛出异常
except RequestException as e:
    print(f"请求错误:{e}")
else:
    print(response.text)

response.raise_for_status():检查响应的状态码,如果是 4xx 或 5xx 错误,会抛出异常。

4. 进阶用法

会话管理(Session)

requests 库的 Session 对象可以帮助我们保持某些参数(如 cookies)跨请求持续存在。适用于需要登录的场景。

import requests

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

# 使用会话对象发起请求
session.post("https://httpbin.org/post", data={"username": "admin", "password": "123456"})

# 发送后续请求时,session 会自动保持之前的 cookies
response = session.get("https://httpbin.org/cookies")
print(response.text)

超时设置

可以通过设置 timeout 来限制请求等待的最大时间,防止程序一直挂起。

import requests

try:
    response = requests.get("https://jsonplaceholder.typicode.com/posts", timeout=3)  # 设置超时为 3 秒
    print(response.text)
except requests.Timeout:
    print("请求超时!")

代理设置

通过代理服务器发送请求:

import requests

proxies = {
    "http": "http://10.10.1.10:3128",
    "https": "https://10.10.1.10:1080"
}

response = requests.get("https://jsonplaceholder.typicode.com/posts", proxies=proxies)
print(response.text)

proxies:字典中包含了代理服务器的地址,httphttps 的代理分别设置。

5. 总结

requests 是 Python 中非常方便和常用的网络请求库,它封装了 HTTP 请求的常见操作,提供了简单易用的 API,可以非常方便地进行 GET、POST 请求、表单提交、文件上传、JSON 数据处理等操作。同时,requests 还支持会话管理、异常处理、请求头和代理设置等高级功能。

对于网络编程和数据爬取任务,requests 是一个非常实用的工具,能够更高效地与 Web 服务进行交互。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值