第五章 网络基础 —— 让程序互联
5.1 OSI 七层模型与 TCP/IP 四层模型
TCP/IP 四层模型(实际使用):
应用层(HTTP、DNS、SSH)
传输层(TCP、UDP)
网络层(IP)
网络接口层(以太网)
5.2 IP 地址与端口
IPv4 地址: 32 位,点分十进制,如 192.168.1.1。分为 A/B/C/D/E 类,私有地址范围:
10.0.0.0 ~ 10.255.255.255
172.16.0.0 ~ 172.31.255.255
192.168.0.0 ~ 192.168.255.255
端口: 0~65535,0-1023 为系统保留(如 80 HTTP, 443 HTTPS, 22 SSH, 3306 MySQL)。
# Linux 查看端口监听
netstat -tuln
ss -tuln
# 测试端口连通
telnet google.com 80
nc -zv google.com 80
5.3 TCP 三次握手与四次挥手
三次握手建立连接:
Client -> SYN (seq=x) -> Server
Server -> SYN+ACK (seq=y, ack=x+1) -> Client
Client -> ACK (seq=x+1, ack=y+1) -> Server
# 使用 socket 模拟 TCP 客户端
import socket
# 创建 socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 连接服务器(发起三次握手)
client.connect(('www.baidu.com', 80))
# 发送 HTTP 请求
request = b'GET / HTTP/1.1\r\nHost: www.baidu.com\r\n\r\n'
client.send(request)
# 接收响应
response = client.recv(4096)
print(response[:100]) # 打印前100字节
# 关闭连接(四次挥手)
client.close()
四次挥手断开连接:
Client -> FIN -> Server
Server -> ACK -> Client (半关闭)
Server -> FIN -> Client
Client -> ACK -> Server
TCP 与 UDP 对比:
# UDP 客户端示例
import socket
udp_client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udp_client.sendto(b'Hello UDP', ('8.8.8.8', 53)) # DNS 服务器
data, addr = udp_client.recvfrom(1024)
print(data)
5.4 HTTP 协议基础
HTTP(超文本传输协议)是 Web 的基石。请求/响应模型,无状态。
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>
常见状态码:
用 Python 启动一个简单的 HTTP 服务器:
# 极简 HTTP 服务器(Python 内置)
# 在终端执行: python -m http.server 8000
# 自定义 HTTP 服务器
from http.server import HTTPServer, BaseHTTPRequestHandler
class MyHandler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write(b'<h1>Hello, World!</h1>')
self.wfile.write(f'<p>Path: {self.path}</p>'.encode())
def do_POST(self):
content_length = int(self.headers['Content-Length'])
post_data = self.rfile.read(content_length)
self.send_response(200)
self.end_headers()
self.wfile.write(b'Received: ' + post_data)
server = HTTPServer(('localhost', 8080), MyHandler)
print('Server running on http://localhost:8080')
server.serve_forever()
5.5 DNS 域名解析
将域名(如 www.google.com)转换为 IP 地址。
# 使用 nslookup 查询
nslookup www.google.com
# 使用 dig 获取详细信息
dig www.google.com
# 查看本地 hosts 文件
cat /etc/hosts # Linux/macOS
type C:\Windows\System32\drivers\etc\hosts # Windows
# Python DNS 解析
import socket
# 域名解析
ip = socket.gethostbyname('www.github.com')
print(f"github.com -> {ip}")
# 获取完整信息
info = socket.gethostbyname_ex('www.github.com')
print(info) # (hostname, alias_list, ip_list)
# 反向解析
hostname = socket.gethostbyaddr('8.8.8.8')
print(hostname) # ('dns.google', [], ['8.8.8.8'])
第六章 数据库基础 —— 持久化与查询
6.1 关系型数据库与 SQL
关系型数据库基于表和关系,使用 SQL(结构化查询语言)。
SQL 分类:
DDL(数据定义语言):CREATE, ALTER, DROP
DML(数据操作语言):SELECT, INSERT, UPDATE, DELETE
DCL(数据控制语言):GRANT, REVOKE
建表与约束:
-- 创建学生表
CREATE TABLE students (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
age INT CHECK (age >= 0 AND age <= 150),
email VARCHAR(100) UNIQUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 创建课程表
CREATE TABLE courses (
id INT PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(100) NOT NULL,
credits INT DEFAULT 3
);
-- 创建选课表(多对多关系)
CREATE TABLE enrollments (
student_id INT,
course_id INT,
grade DECIMAL(3,2),
PRIMARY KEY (student_id, course_id),
FOREIGN KEY (student_id) REFERENCES students(id) ON DELETE CASCADE,
FOREIGN KEY (course_id) REFERENCES courses(id) ON DELETE CASCADE
);
常用 SQL 查询:
-- 基本查询
SELECT * FROM students;
SELECT name, age FROM students WHERE age > 18 ORDER BY age DESC;
-- 聚合函数
SELECT COUNT(*), AVG(age), MAX(age), MIN(age) FROM students;
-- 分组查询
SELECT age, COUNT(*) FROM students GROUP BY age HAVING COUNT(*) > 1;
-- 多表连接(INNER JOIN)
SELECT s.name, c.title, e.grade
FROM students s
JOIN enrollments e ON s.id = e.student_id
JOIN courses c ON c.id = e.course_id;
-- 子查询
SELECT name FROM students
WHERE id IN (SELECT student_id FROM enrollments GROUP BY student_id HAVING COUNT(*) >= 3);
-- 分页
SELECT * FROM students LIMIT 10 OFFSET 20;
6.2 索引的原理与使用
索引类似书的目录,可以加速查询但会降低写入性能。
B+ 树索引(InnoDB 默认):
-- 创建单列索引
CREATE INDEX idx_name ON students(name);
-- 创建复合索引(注意列顺序)
CREATE INDEX idx_age_name ON students(age, name);
-- 唯一索引
CREATE UNIQUE INDEX idx_email ON students(email);
-- 查看索引
SHOW INDEX FROM students;
-- 删除索引
DROP INDEX idx_name ON students;
索引失效场景:
-- 在索引列上使用函数
SELECT * FROM students WHERE YEAR(created_at) = 2024; -- 索引失效
-- 隐式类型转换
SELECT * FROM students WHERE phone = 13800138000; -- phone 是 varchar,索引失效
-- 使用 LIKE 以通配符开头
SELECT * FROM students WHERE name LIKE '%张三'; -- 索引失效
-- OR 条件中只要有一列无索引,全表扫描
SELECT * FROM students WHERE id = 1 OR age = 20; -- age 无索引则全表
-- 复合索引不满足最左前缀
-- 对于 idx_age_name (age, name)
SELECT * FROM students WHERE name = '张三'; -- 索引失效
6.3 事务与 ACID
事务是一组 SQL 操作的逻辑单元,要么全部成功,要么全部失败。
事务隔离级别(从低到高):
READ UNCOMMITTED:脏读、不可重复读、幻读都可能
READ COMMITTED:避免脏读(PostgreSQL 默认)
REPEATABLE READ:避免脏读和不可重复读(MySQL 默认)
SERIALIZABLE:避免所有并发问题,性能最差
-- MySQL 事务示例
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
-- 检查约束
SELECT balance FROM accounts WHERE id = 1;
-- 如果余额为负数,回滚
ROLLBACK;
-- 否则提交
COMMIT;
-- 设置隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
演示脏读、不可重复读、幻读:
-- 脏读(隔离级别 READ UNCOMMITTED)
-- 事务A: UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- 事务B: SELECT balance FROM accounts WHERE id = 1; -- 读到未提交的修改
-- 事务A: ROLLBACK; -- 事务B 读到脏数据
-- 不可重复读(READ COMMITTED 下可能发生)
-- 事务A: SELECT balance FROM accounts WHERE id = 1; -- 100
-- 事务B: UPDATE accounts SET balance = 200 WHERE id = 1; COMMIT;
-- 事务A: SELECT balance FROM accounts WHERE id = 1; -- 200,前后不一致
-- 幻读(REPEATABLE READ 下可能发生)
-- 事务A: SELECT COUNT(*) FROM students WHERE age > 18; -- 5
-- 事务B: INSERT INTO students (name, age) VALUES ('new', 19); COMMIT;
-- 事务A: SELECT COUNT(*) FROM students WHERE age > 18; -- 6,新增一行(幻影)
6.4 NoSQL 简介
NoSQL 适用于大规模数据、高并发、灵活模式的场景。
# Redis 示例(Python)
import redis
r = redis.Redis(host='localhost', port=6379, db=0)
# 字符串
r.set('key', 'value')
print(r.get('key')) # b'value'
# 哈希
r.hset('user:1', mapping={'name': 'Alice', 'age': 25})
print(r.hgetall('user:1')) # {b'name': b'Alice', b'age': b'25'}
# 列表(可用作队列)
r.lpush('queue', 'task1', 'task2')
print(r.rpop('queue')) # b'task1'
# 设置过期时间(秒)
r.setex('temp', 10, 'expire soon')