Day 14:网络编程

🎯 学习目标:理解网络通信原理,掌握 UDP/TCP/HTTP 编程

1. 加锁解决线程安全问题(续 Day 13)

 
import threading
 
# 除了 Lock,还有 RLock(可重入锁)
 
rlock = threading.RLock()
 
def recursive_function(n):
 
    with rlock:       # 同一线程可以多次获取 RLock
 
        if n > 0:
 
            recursive_function(n - 1)  # 递归时不会死锁
 
# 死锁演示(两个锁互相等待)
 
lock_a = threading.Lock()
 
lock_b = threading.Lock()
 
def thread1():
 
    with lock_a:
 
        time.sleep(0.1)
 
        with lock_b:    # 等待 lock_b(被 thread2 持有)
 
            pass
 
def thread2():
 
    with lock_b:
 
        time.sleep(0.1)
 
        with lock_a:    # 等待 lock_a(被 thread1 持有)
 
            pass
 
# thread1 和 thread2 互相等待 → 死锁!
 

2. 进程和线程对比

维度进程线程
资源独立内存空间共享进程内存
创建开销大(复制整个进程)小(共享内存)
通信IPC(Queue、Pipe)共享变量(需加锁)
GIL 影响无(各自有独立 GIL)IO 型不受影响
适用场景CPU 密集型IO 密集型
稳定性一个崩了不影响其他一个崩了全崩

3. 网络相关概念

3.1 IP 与端口

 
# 49152-65535:动态/私有端口
 

3.2 协议


graph TB

    APP[应用层:HTTP FTP DNS SMTP] --> TCP_UDP[传输层:TCP UDP]

    TCP_UDP --> IP[网络层:IP 地址与路由]

    IP --> LINK[链路层:网卡 网线 WiFi]

3.3 TCP vs UDP

特性TCPUDP
连接需要建立连接(三次握手)无连接
可靠性可靠,有确认重传机制不可靠,可能丢包
顺序保证有序不保证
速度相对慢
适用网页、文件、邮件视频、直播、游戏
类比打电话(确认对方收到)寄信(不知道是否收到)

4. UDP 通信

4.1 服务端

 
import socket
 
# 创建 UDP socket
 
udp_server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
 
udp_server.bind(("127.0.0.1", 8888))
 
print("UDP 服务器启动,等待消息...")
 
while True:
 
    data, addr = udp_server.recvfrom(1024)  # 接收(阻塞)
 
    text = data.decode("utf-8")
 
    print(f"收到来自 {addr} 的消息:{text}")
 
    if text == "quit":
 
        break
 
    # 回复
 
    reply = f"收到:{text}"
 
    udp_server.sendto(reply.encode("utf-8"), addr)
 
udp_server.close()
 

4.2 客户端

 
import socket
 
# 创建 UDP socket
 
udp_client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
 
while True:
 
    msg = input("输入消息(quit 退出):")
 
    udp_client.sendto(msg.encode("utf-8"), ("127.0.0.1", 8888))
 
    if msg == "quit":
 
        break
 
    data, addr = udp_client.recvfrom(1024)
 
    print(f"服务器回复:{data.decode('utf-8')}")
 
udp_client.close()
 

5. TCP 通信

5.1 服务端

 
import socket
 
tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 
tcp_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)  # 端口复用
 
tcp_server.bind(("127.0.0.1", 9999))
 
tcp_server.listen(5)    # 最大等待连接数
 
print("TCP 服务器启动,等待连接...")
 
while True:
 
    client_socket, client_addr = tcp_server.accept()
 
    print(f"客户端 {client_addr} 已连接")
 
    # 接收数据
 
    data = client_socket.recv(1024)
 
    print(f"收到:{data.decode('utf-8')}")
 
    # 回复
 
    client_socket.send(f"服务器收到:{data.decode('utf-8')}".encode("utf-8"))
 
    client_socket.close()
 
tcp_server.close()
 

5.2 客户端

 
import socket
 
tcp_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 
tcp_client.connect(("127.0.0.1", 9999))
 
tcp_client.send("Hello TCP!".encode("utf-8"))
 
response = tcp_client.recv(1024)
 
print(f"服务器回复:{response.decode('utf-8')}")
 
tcp_client.close()
 

5.3 多线程 TCP 服务端(优化)

 
import socket
 
import threading
 
def handle_client(client_socket, client_addr):
 
    """处理单个客户端(运行在线程中)"""
 
    print(f"新连接:{client_addr}")
 
    try:
 
        while True:
 
            data = client_socket.recv(1024)
 
            if not data:
 
                break
 
            print(f"{client_addr}{data.decode('utf-8')}")
 
            client_socket.send(f"Echo:{data.decode('utf-8')}".encode())
 
    except ConnectionResetError:
 
        print(f"{client_addr} 断开连接")
 
    finally:
 
        client_socket.close()
 
# 主循环
 
tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 
tcp_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
 
tcp_server.bind(("127.0.0.1", 9999))
 
tcp_server.listen(10)
 
print("多线程 TCP 服务器启动...")
 
while True:
 
    client, addr = tcp_server.accept()
 
    thread = threading.Thread(target=handle_client, args=(client, addr), daemon=True)
 
    thread.start()
 

6. HTTP 请求

6.1 requests 库

 
import requests
 
# GET 请求
 
resp = requests.get("https://api.github.com")
 
print(f"状态码:{resp.status_code}")
 
print(f"Content-Type:{resp.headers['Content-Type']}")
 
data = resp.json()
 
# POST 请求
 
resp = requests.post(
 
    "https://httpbin.org/post",
 
    json={"name": "张三", "age": 20},
 
    headers={"X-Custom": "value"}
 
)
 
print(resp.json())
 
# 带参数的 GET
 
resp = requests.get(
 
    "https://api.example.com/search",
 
    params={"q": "python", "page": 1}
 
)
 
# 超时和异常处理
 
try:
 
    resp = requests.get("https://api.example.com", timeout=5)
 
    resp.raise_for_status()  # 非 200 状态码抛出异常
 
except requests.Timeout:
 
    print("请求超时")
 
except requests.HTTPError as e:
 
    print(f"HTTP 错误:{e}")
 
except requests.ConnectionError:
 
    print("连接失败")
 

6.2 一言网案例

 
import requests
 
# 获取随机句子
 
def get_hitokoto():
 
    """从一言网获取一句随机名言"""
 
    try:
 
        resp = requests.get("https://v1.hitokoto.cn/", timeout=5)
 
        data = resp.json()
 
        return f"{data['hitokoto']}\n—— {data['from']}"
 
    except Exception as e:
 
        return f"获取失败:{e}"
 
print(get_hitokoto())
 

7. starlette 部署 Web 服务

 
from starlette.applications import Starlette
 
from starlette.responses import JSONResponse, PlainTextResponse
 
from starlette.routing import Route
 
import uvicorn
 
# 路由处理函数
 
async def hello(request):
 
    return JSONResponse({"message": "Hello World!"})
 
async def greet(request):
 
    name = request.path_params.get("name", "World")
 
    return PlainTextResponse(f"Hello, {name}!")
 
# 创建应用
 
app = Starlette(debug=True, routes=[
 
    Route("/", hello),
 
    Route("/greet/{name}", greet),
 
])
 
# 启动:uvicorn app:app --reload
 
if __name__ == "__main__":
 
    uvicorn.run(app, host="127.0.0.1", port=8000)
 

后续学习

在大模型项目中,你会使用 FastAPI(基于 starlette)构建 API,用 asyncio 实现异步高并发。

速记卡(面试闪卡)

Q1:一句话讲清「Day 14:网络编程」到底是什么?

A:网络编程这块讲清三件事:进程线程怎么选、TCP/UDP 有啥区别、以及用 socket 和 requests 写通信。

Q2:进程和线程怎么对比? —— 怎么理解?

A:像公司和部门——进程是独立公司(独立内存、开销大、一个崩不影响其他、适合 CPU 密集);线程是部门(共享内存、开销小、一个崩全崩、适合 IO 密集)。进程间靠 IPC 通信,线程间共享变量要加锁。

Q3:TCP 和 UDP 有啥区别? —— 怎么理解?

A:TCP 像打电话——先三次握手建连接、可靠有序、慢,适合网页文件邮件;UDP 像寄信——无连接、可能丢包不保序、快,适合视频直播游戏。Python 里 socket.SOCK_STREAM 是 TCP,SOCK_DGRAM 是 UDP。

Q4:怎么写 UDP 和 TCP 通信? —— 怎么理解?

A:像寄信 vs 打电话的前台——UDP 用 socket + SOCK_DGRAM,bind 后 recvfrom/sendto 不建连直接收发;TCP 用 SOCK_STREAM,服务端 bind+listen+accept、客户端 connect,再 send/recv 可靠传,多线程服务端每连接开一线程。

Q5:requests 和 starlette 怎么用? —— 怎么理解?

A:requests 像派快递员——get/post 一行发 HTTP 请求,带 params/timeout/异常处理;starlette 像搭服务台——定义 Route 路由和 async 处理函数返回 JSONResponse,用 uvicorn 启动,是 FastAPI 的底层。

Q6:核心速记主线有哪些?

  • 进程线程对比:进程独立内存开销大、线程共享内存开销小;进程适合CPU密集、线程适合IO密集

  • TCP vs UDP:TCP 可靠有序慢(打电话)、UDP 无连接快可能丢(寄信)

  • UDP 用 SOCK_DGRAM recvfrom/sendto;TCP 用 SOCK_STREAM 需 connect/accept

  • 应用层:requests 发 HTTP 请求;starlette 搭异步 Web 服务(FastAPI 的底层)

口诀

A:进程独立线程共享,CPU密集进程扛。

TCP打电话UDP寄信,可靠快选你要的。

UDP不连直接发,TCP握手再传话。

requests派件starlette台,Web服务异步开。

相关链接


技术学习路线图 > 进阶特性