一、HTTP/2 协议概述
1.1 HTTP/2 核心特性
python
复制
下载
class HTTP2CoreFeatures:
"""HTTP/2 核心特性分析"""
@staticmethod
def get_key_features():
return {
"二进制分帧": "帧作为最小通信单位,取代HTTP/1.x的文本格式",
"多路复用": "单个连接上并行交错传输多个请求/响应",
"头部压缩": "HPACK算法压缩头部,减少冗余",
"服务器推送": "服务器主动推送资源",
"流优先级": "客户端可以指定流的处理优先级",
"流控制": "基于流的流量控制"
}
@staticmethod
def http1_vs_http2_comparison():
"""HTTP/1.1 与 HTTP/2 对比"""
comparison = {
"连接管理": {
"http1": "多个TCP连接(6-8个),队头阻塞",
"http2": "单个持久连接,多路复用"
},
"头部传输": {
"http1": "每次请求重复发送完整头部",
"http2": "HPACK压缩,静态/动态表"
},
"数据格式": {
"http1": "文本格式,解析复杂",
"http2": "二进制分帧,解析高效"
},
"服务器推送": {
"http1": "不支持",
"http2": "支持主动推送资源"
},
"优先级": {
"http1": "有限支持(管道化)",
"http2": "完善的流优先级"
}
}
return comparison
二、二进制分帧层
2.1 帧格式定义
python
复制
下载
from dataclasses import dataclass
from typing import Tuple, List, Optional
import struct
import enum
class FrameType(enum.IntEnum):
"""HTTP/2 帧类型"""
DATA = 0x0
HEADERS = 0x1
PRIORITY = 0x2
RST_STREAM = 0x3
SETTINGS = 0x4
PUSH_PROMISE = 0x5
PING = 0x6
GOAWAY = 0x7
WINDOW_UPDATE = 0x8
CONTINUATION = 0x9
class FrameFlags(enum.IntFlag):
"""帧标志位"""
END_STREAM = 0x1
ACK = 0x1
END_HEADERS = 0x4
PADDED = 0x8
PRIORITY = 0x20
@dataclass
class FrameHeader:
"""HTTP/2 帧头部(9字节)"""
length: int # 24位,载荷长度(不包括帧头)
type: FrameType # 8位,帧类型
flags: int # 8位,标志位
stream_id: int # 31位,流标识符(0为连接控制帧)
def serialize(self) -> bytes:
"""序列化帧头"""
# 长度: 24位,类型: 8位,标志: 8位,流ID: 31位
length_and_type = (self.length << 8) | self.type
flags_and_stream = (self.flags << 24) | (self.stream_id & 0x7FFFFFFF)
return struct.pack('>IBI', length_and_type, self.flags, self.stream_id)
@classmethod
def deserialize(cls, data: bytes) -> 'FrameHeader':
"""反序列化帧头"""
if len(data) < 9:
raise ValueError("帧头需要9字节")
# 解析前4字节(24位长度 + 8位类型)
length_and_type = struct.unpack('>I', data[:4])[0]
length = length_and_type >> 8
type_val = length_and_type & 0xFF
# 解析标志和流ID
flags, stream_id = struct.unpack('>BI', data[4:9])
# 清除流ID的最高位(保留位)
stream_id = stream_id & 0x7FFFFFFF
return cls(
length=length,
type=FrameType(type_val),
flags=flags,
stream_id=stream_id
)
class HTTP2Frame:
"""HTTP/2 帧基类"""
def __init__(self, header: FrameHeader, payload: bytes = b''):
self.header = header
self.payload = payload
def serialize(self) -> bytes:
"""序列化完整帧"""
return self.header.serialize() + self.payload
@classmethod
def parse(cls, data: bytes) -> 'HTTP2Frame':
"""解析帧"""
header = FrameHeader.deserialize(data[:9])
payload = data[9:9+header.length]
# 根据帧类型创建具体的帧对象
frame_classes = {
FrameType.DATA: DataFrame,
FrameType.HEADERS: HeadersFrame,
FrameType.SETTINGS: SettingsFrame,
FrameType.WINDOW_UPDATE: WindowUpdateFrame,
FrameType.PRIORITY: PriorityFrame,
FrameType.RST_STREAM: RstStreamFrame,
FrameType.PUSH_PROMISE: PushPromiseFrame,
FrameType.PING: PingFrame,
FrameType.GOAWAY: GoAwayFrame,
FrameType.CONTINUATION: ContinuationFrame
}
frame_class = frame_classes.get(header.type, HTTP2Frame)
return frame_class(header, payload)
2.2 各种帧类型的实现
python
复制
下载
class DataFrame(HTTP2Frame):
"""DATA 帧实现"""
def __init__(self, stream_id: int, data: bytes,
end_stream: bool = False, padded: bool = False):
flags = 0
if end_stream:
flags |= FrameFlags.END_STREAM
if padded:
flags |= FrameFlags.PADDED
header = FrameHeader(
length=len(data),
type=FrameType.DATA,
flags=flags,
stream_id=stream_id
)
super().__init__(header, data)
def is_end_stream(self) -> bool:
"""检查是否为流的结束"""
return bool(self.header.flags & FrameFlags.END_STREAM)
def get_data(self) -> bytes:
"""获取数据载荷"""
if self.header.flags & FrameFlags.PADDED:
# 解析填充长度
pad_length = self.payload[0]
return self.payload[1:-pad_length]
return self.payload
class HeadersFrame(HTTP2Frame):
"""HEADERS 帧实现"""
def __init__(self, stream_id: int, headers: List[Tuple[str, str]],
end_stream: bool = False, end_headers: bool = True,
priority: Optional[Tuple[int, bool, int]] = None):
"""
Args:
stream_id: 流ID
headers: 头部字段列表
end_stream: 是否结束流
end_headers: 是否结束头部块
priority: 优先级信息 (依赖流ID, 独占标志, 权重)
"""
flags = 0
if end_stream:
flags |= FrameFlags.END_STREAM
if end_headers:
flags |= FrameFlags.END_HEADERS
if priority:
flags |= FrameFlags.PRIORITY
# 序列化头部块(实际中应使用HPACK压缩)
payload = self._serialize_headers(headers, priority)
header = FrameHeader(
length=len(payload),
type=FrameType.HEADERS,
flags=flags,
stream_id=stream_id
)
super().__init__(header, payload)
def _serialize_headers(self, headers: List[Tuple[str, str]],
priority: Optional[Tuple]) -> bytes:
"""序列化头部(简化版,实际应使用HPACK)"""
payload = b''
# 如果有优先级信息
if priority:
dep_stream_id, exclusive, weight = priority
if exclusive:
dep_stream_id |= 0x80000000
payload += struct.pack('>IB', dep_stream_id, weight)
# 序列化头部字段
for name, value in headers:
name_bytes = name.encode('utf-8')
value_bytes = value.encode('utf-8')
payload += struct.pack('>H', len(name_bytes)) + name_bytes
payload += struct.pack('>H', len(value_bytes)) + value_bytes
return payload
class SettingsFrame(HTTP2Frame):
"""SETTINGS 帧实现"""
# 设置标识符
SETTINGS_HEADER_TABLE_SIZE = 0x1
SETTINGS_ENABLE_PUSH = 0x2
SETTINGS_MAX_CONCURRENT_STREAMS = 0x3
SETTINGS_INITIAL_WINDOW_SIZE = 0x4
SETTINGS_MAX_FRAME_SIZE = 0x5
SETTINGS_MAX_HEADER_LIST_SIZE = 0x6
def __init__(self, settings: dict = None, ack: bool = False):
"""
Args:
settings: 设置字典 {identifier: value}
ack: 是否为ACK帧
"""
flags = FrameFlags.ACK if ack else 0
payload = b''
if settings and not ack:
for identifier, value in settings.items():
payload += struct.pack('>HI', identifier, value)
header = FrameHeader(
length=len(payload),
type=FrameType.SETTINGS,
flags=flags,
stream_id=0 # 连接控制帧
)
super().__init__(header, payload)
def get_settings(self) -> dict:
"""解析设置参数"""
if self.header.flags & FrameFlags.ACK:
return {}
settings = {}
for i in range(0, len(self.payload), 6):
identifier, value = struct.unpack('>HI', self.payload[i:i+6])
settings[identifier] = value
return settings
class WindowUpdateFrame(HTTP2Frame):
"""WINDOW_UPDATE 帧实现"""
def __init__(self, stream_id: int, window_size_increment: int):
"""
Args:
stream_id: 流ID(0为连接级别)
window_size_increment: 窗口增量(1-2^31-1)
"""
payload = struct.pack('>I', window_size_increment & 0x7FFFFFFF)
header = FrameHeader(
length=len(payload),
type=FrameType.WINDOW_UPDATE,
flags=0,
stream_id=stream_id
)
super().__init__(header, payload)
def get_window_increment(self) -> int:
"""获取窗口增量"""
return struct.unpack('>I', self.payload)[0] & 0x7FFFFFFF
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc
需要全套面试笔记及答案
【点击此处即可/免费获取】
三、多路复用实现
3.1 流状态机
python
复制
下载
class StreamState(enum.Enum):
"""流状态定义(RFC 7540 Section 5.1)"""
IDLE = "idle" # 空闲状态
RESERVED_LOCAL = "reserved(local)" # 本地保留
RESERVED_REMOTE = "reserved(remote)" # 远程保留
OPEN = "open" # 打开状态
HALF_CLOSED_LOCAL = "half_closed(local)" # 本地半关闭
HALF_CLOSED_REMOTE = "half_closed(remote)" # 远程半关闭
CLOSED = "closed" # 关闭状态
class Stream:
"""HTTP/2 流实现"""
def __init__(self, stream_id: int, connection):
"""
Args:
stream_id: 流标识符(奇数客户端发起,偶数服务器发起)
connection: 所属连接
"""
self.stream_id = stream_id
self.connection = connection
self.state = StreamState.IDLE
# 流控制
self.send_window = 65535 # 默认初始窗口大小
self.receive_window = 65535
self.window_update_pending = False
# 优先级
self.depends_on = 0 # 依赖的流ID
self.weight = 16 # 权重(1-256)
self.exclusive = False
# 缓冲区
self.send_buffer = bytearray()
self.receive_buffer = bytearray()
self.headers_received = {}
self.headers_to_send = {}
# 状态追踪
self.headers_sent = False
self.data_sent = 0
self.data_received = 0
# 时间戳
self.created_at = time.time()
self.last_activity = self.created_at
def transition_state(self, frame_type: FrameType, flags: int):
"""状态转移(根据接收到的帧)"""
old_state = self.state
# 状态转移表(简化版)
transitions = {
StreamState.IDLE: {
FrameType.HEADERS: lambda: self._handle_idle_to_open(flags),
FrameType.PUSH_PROMISE: lambda: self._handle_idle_to_reserved(flags)
},
StreamState.OPEN: {
FrameType.DATA: lambda: self._handle_open_data(flags),
FrameType.HEADERS: lambda: self._handle_open_headers(flags),
FrameType.RST_STREAM: lambda: StreamState.CLOSED
},
StreamState.HALF_CLOSED_LOCAL: {
FrameType.DATA: lambda: self._handle_half_closed_data(flags),
FrameType.HEADERS: lambda: self._handle_half_closed_headers(flags)
}
}
state_transitions = transitions.get(self.state, {})
handler = state_transitions.get(frame_type)
if handler:
new_state = handler()
if new_state:
self.state = new_state
print(f"流 {self.stream_id}: {old_state} -> {self.state}")
self.last_activity = time.time()
def _handle_idle_to_open(self, flags: int) -> StreamState:
"""IDLE -> OPEN 或 HALF_CLOSED_REMOTE"""
if flags & FrameFlags.END_STREAM:
return StreamState.HALF_CLOSED_REMOTE
return StreamState.OPEN
def _handle_idle_to_reserved(self, flags: int) -> StreamState:
"""IDLE -> RESERVED_REMOTE"""
return StreamState.RESERVED_REMOTE
def _handle_open_data(self, flags: int) -> Optional[StreamState]:
"""处理OPEN状态下的DATA帧"""
if flags & FrameFlags.END_STREAM:
return StreamState.HALF_CLOSED_REMOTE
return None
def _handle_open_headers(self, flags: int) -> Optional[StreamState]:
"""处理OPEN状态下的HEADERS帧"""
if flags & FrameFlags.END_STREAM:
return StreamState.HALF_CLOSED_REMOTE
return None
def can_send_data(self, size: int) -> bool:
"""检查是否可以发送数据(流控制)"""
return size <= self.send_window
def on_data_sent(self, size: int):
"""数据发送后的处理"""
self.send_window -= size
self.data_sent += size
# 如果窗口太小,需要等待WINDOW_UPDATE
if self.send_window < 4096: # 阈值
self.window_update_pending = True
def on_data_received(self, size: int):
"""数据接收后的处理"""
self.receive_window -= size
self.data_received += size
# 更新接收窗口
if self.receive_window < 32768: # 发送WINDOW_UPDATE的阈值
self._send_window_update()
def _send_window_update(self):
"""发送WINDOW_UPDATE帧"""
increment = 65536 # 默认增量
frame = WindowUpdateFrame(self.stream_id, increment)
self.connection.send_frame(frame)
self.receive_window += increment
def get_priority_weight(self) -> float:
"""获取优先级权重(用于调度)"""
return self.weight / 256.0
def close(self, error_code: int = 0):
"""关闭流"""
if self.state != StreamState.CLOSED:
self.state = StreamState.CLOSED
# 发送RST_STREAM帧
rst_frame = RstStreamFrame(self.stream_id, error_code)
self.connection.send_frame(rst_frame)
3.2 连接多路复用器
python
复制
下载
import heapq
import threading
from collections import defaultdict
class HTTP2Connection:
"""HTTP/2 连接管理器"""
def __init__(self, socket):
self.socket = socket
self.lock = threading.RLock()
# 流管理
self.streams = {} # stream_id -> Stream
self.next_stream_id = 1 # 客户端发起流的起始ID(奇数)
# 流优先级树
self.priority_tree = PriorityTree()
# 连接控制
self.connection_window = 65535
self.max_concurrent_streams = 100
self.settings = self._default_settings()
# 帧处理
self.frame_handlers = {
FrameType.DATA: self._handle_data_frame,
FrameType.HEADERS: self._handle_headers_frame,
FrameType.SETTINGS: self._handle_settings_frame,
FrameType.WINDOW_UPDATE: self._handle_window_update_frame,
FrameType.PRIORITY: self._handle_priority_frame,
FrameType.RST_STREAM: self._handle_rst_stream_frame,
FrameType.PUSH_PROMISE: self._handle_push_promise_frame,
FrameType.PING: self._handle_ping_frame,
FrameType.GOAWAY: self._handle_goaway_frame
}
# 发送队列
self.send_queue = []
self.send_thread = None
self.running = False
# 统计信息
self.stats = {
'frames_sent': 0,
'frames_received': 0,
'bytes_sent': 0,
'bytes_received': 0,
'active_streams': 0,
'streams_created': 0,
'streams_closed': 0
}
def _default_settings(self) -> dict:
"""默认连接设置"""
return {
SettingsFrame.SETTINGS_HEADER_TABLE_SIZE: 4096,
SettingsFrame.SETTINGS_ENABLE_PUSH: 1,
SettingsFrame.SETTINGS_MAX_CONCURRENT_STREAMS: 100,
SettingsFrame.SETTINGS_INITIAL_WINDOW_SIZE: 65535,
SettingsFrame.SETTINGS_MAX_FRAME_SIZE: 16384,
SettingsFrame.SETTINGS_MAX_HEADER_LIST_SIZE: 65535
}
def start(self):
"""启动连接处理"""
self.running = True
# 发送初始SETTINGS帧
settings_frame = SettingsFrame(self.settings)
self.send_frame(settings_frame)
# 启动发送线程
self.send_thread = threading.Thread(target=self._sending_loop)
self.send_thread.start()
# 启动接收线程
recv_thread = threading.Thread(target=self._receiving_loop)
recv_thread.start()
def stop(self):
"""停止连接"""
self.running = False
if self.send_thread:
self.send_thread.join(timeout=2.0)
def create_stream(self, headers: List[Tuple[str, str]],
priority: Optional[Tuple] = None) -> int:
"""
创建新的流
Returns:
流ID
"""
with self.lock:
# 检查并发流限制
active_streams = sum(1 for s in self.streams.values()
if s.state != StreamState.CLOSED)
if active_streams >= self.max_concurrent_streams:
raise Exception("达到最大并发流限制")
# 分配流ID(客户端发起为奇数)
stream_id = self.next_stream_id
self.next_stream_id += 2
# 创建流
stream = Stream(stream_id, self)
if priority:
stream.depends_on, stream.exclusive, stream.weight = priority
self.streams[stream_id] = stream
self.priority_tree.add_stream(stream)
# 发送HEADERS帧
end_stream = False # 假设还有DATA帧
headers_frame = HeadersFrame(
stream_id, headers,
end_stream=end_stream,
priority=priority
)
self.send_frame(headers_frame)
self.stats['streams_created'] += 1
self.stats['active_streams'] += 1
return stream_id
def send_data(self, stream_id: int, data: bytes, end_stream: bool = False):
"""在指定流上发送数据"""
with self.lock:
if stream_id not in self.streams:
raise Exception(f"流 {stream_id} 不存在")
stream = self.streams[stream_id]
# 检查流状态
if stream.state in [StreamState.CLOSED, StreamState.HALF_CLOSED_REMOTE]:
raise Exception(f"流 {stream_id} 已关闭")
# 检查流控制
if not stream.can_send_data(len(data)):
# 等待窗口更新
self._wait_for_window_update(stream_id)
# 创建DATA帧
data_frame = DataFrame(stream_id, data, end_stream=end_stream)
# 添加到发送队列(带优先级)
priority = stream.get_priority_weight()
heapq.heappush(self.send_queue, (-priority, time.time(), data_frame))
# 更新流状态
stream.on_data_sent(len(data))
if end_stream:
if stream.state == StreamState.OPEN:
stream.state = StreamState.HALF_CLOSED_LOCAL
elif stream.state == StreamState.HALF_CLOSED_REMOTE:
stream.state = StreamState.CLOSED
def _sending_loop(self):
"""发送循环(基于优先级调度)"""
while self.running:
try:
with self.lock:
if self.send_queue:
# 获取最高优先级的帧
_, _, frame = heapq.heappop(self.send_queue)
self._send_frame_internal(frame)
# 短暂休眠以避免CPU空转
time.sleep(0.001)
except Exception as e:
print(f"发送循环错误: {e}")
time.sleep(0.01)
def _send_frame_internal(self, frame: HTTP2Frame):
"""内部发送帧方法"""
try:
frame_data = frame.serialize()
self.socket.send(frame_data)
self.stats['frames_sent'] += 1
self.stats['bytes_sent'] += len(frame_data)
# 更新连接窗口(针对DATA帧)
if frame.header.type == FrameType.DATA:
self.connection_window -= frame.header.length
# 如果连接窗口太小,发送WINDOW_UPDATE
if self.connection_window < 32768:
self._update_connection_window()
except Exception as e:
print(f"发送帧失败: {e}")
def _update_connection_window(self):
"""更新连接窗口"""
increment = 65536
frame = WindowUpdateFrame(0, increment) # 流ID=0表示连接级别
self.send_frame(frame)
self.connection_window += increment
def _receiving_loop(self):
"""接收循环"""
buffer = bytearray()
while self.running:
try:
# 接收数据
data = self.socket.recv(4096)
if not data:
break
buffer.extend(data)
self.stats['bytes_received'] += len(data)
# 解析帧
while len(buffer) >= 9: # 帧头最小长度
# 解析帧头获取载荷长度
header = FrameHeader.deserialize(buffer[:9])
total_length = 9 + header.length
if len(buffer) >= total_length:
# 解析完整帧
frame_data = buffer[:total_length]
frame = HTTP2Frame.parse(frame_data)
# 处理帧
self._handle_frame(frame)
# 移除已处理的数据
buffer = buffer[total_length:]
else:
# 等待更多数据
break
except Exception as e:
print(f"接收循环错误: {e}")
break
def _handle_frame(self, frame: HTTP2Frame):
"""处理接收到的帧"""
self.stats['frames_received'] += 1
handler = self.frame_handlers.get(frame.header.type)
if handler:
handler(frame)
else:
print(f"未知帧类型: {frame.header.type}")
def _handle_data_frame(self, frame: HTTP2Frame):
"""处理DATA帧"""
stream_id = frame.header.stream_id
with self.lock:
if stream_id in self.streams:
stream = self.streams[stream_id]
# 更新流状态
stream.transition_state(frame.header.type, frame.header.flags)
# 处理数据
data_frame = DataFrame(frame.header, frame.payload)
data = data_frame.get_data()
stream.on_data_received(len(data))
stream.receive_buffer.extend(data)
# 检查流是否结束
if data_frame.is_end_stream():
self._complete_stream_reception(stream_id)
def _handle_headers_frame(self, frame: HTTP2Frame):
"""处理HEADERS帧"""
stream_id = frame.header.stream_id
with self.lock:
# 如果是新流
if stream_id not in self.streams:
stream = Stream(stream_id, self)
self.streams[stream_id] = stream
self.stats['active_streams'] += 1
stream = self.streams[stream_id]
stream.transition_state(frame.header.type, frame.header.flags)
# 解析头部(简化)
headers_frame = HeadersFrame(frame.header, frame.payload)
# 实际应使用HPACK解码
def _complete_stream_reception(self, stream_id: int):
"""完成流接收"""
if stream_id in self.streams:
stream = self.streams[stream_id]
# 处理接收到的数据
if stream.receive_buffer:
# 通知应用层数据到达
self._notify_data_received(stream_id, bytes(stream.receive_buffer))
stream.receive_buffer.clear()
# 更新状态
if stream.state == StreamState.HALF_CLOSED_REMOTE:
stream.state = StreamState.CLOSED
elif stream.state == StreamState.OPEN:
stream.state = StreamState.HALF_CLOSED_REMOTE
self.stats['active_streams'] -= 1
self.stats['streams_closed'] += 1
def send_frame(self, frame: HTTP2Frame):
"""发送帧(外部接口)"""
with self.lock:
# 添加到发送队列(默认优先级)
heapq.heappush(self.send_queue, (0, time.time(), frame))
def _wait_for_window_update(self, stream_id: int):
"""等待窗口更新(简化实现)"""
# 在实际实现中,这里应该有更复杂的等待机制
for _ in range(10): # 重试10次
stream = self.streams.get(stream_id)
if stream and stream.can_send_data(1):
return True
time.sleep(0.01)
raise Exception(f"等待流 {stream_id} 窗口更新超时")
def get_connection_stats(self) -> dict:
"""获取连接统计信息"""
with self.lock:
stats = self.stats.copy()
stats['total_streams'] = len(self.streams)
stats['connection_window'] = self.connection_window
stats['queue_size'] = len(self.send_queue)
# 流状态分布
state_dist = defaultdict(int)
for stream in self.streams.values():
state_dist[stream.state.value] += 1
stats['stream_states'] = dict(state_dist)
return stats
3.3 优先级调度器
python
复制
下载
class PriorityNode:
"""优先级树节点"""
def __init__(self, stream_id: int, weight: int = 16):
self.stream_id = stream_id
self.weight = weight
self.children = [] # 子节点
self.parent = None # 父节点
# 调度相关
self.scheduled_weight = weight
self.remaining_credits = 0
def add_child(self, child: 'PriorityNode', exclusive: bool = False):
"""添加子节点"""
if exclusive:
# 独占模式:新节点成为唯一子节点,原子节点成为新节点的子节点
old_children = self.children.copy()
self.children = [child]
child.parent = self
for old_child in old_children:
child.add_child(old_child)
else:
child.parent = self
self.children.append(child)
def remove_child(self, stream_id: int):
"""移除子节点"""
self.children = [c for c in self.children if c.stream_id != stream_id]
def calculate_distributed_weight(self) -> float:
"""计算分布权重"""
if not self.children:
return self.weight
total_weight = sum(child.weight for child in self.children)
return self.weight * (child.weight / total_weight)
class PriorityTree:
"""优先级树"""
def __init__(self):
# 根节点(虚拟节点,流ID=0)
self.root = PriorityNode(0, 256)
self.nodes = {0: self.root}
# 调度状态
self.current_node = self.root
def add_stream(self, stream: Stream):
"""添加流到优先级树"""
node = PriorityNode(stream.stream_id, stream.weight)
self.nodes[stream.stream_id] = node
if stream.depends_on == 0:
parent = self.root
else:
parent = self.nodes.get(stream.depends_on, self.root)
parent.add_child(node, stream.exclusive)
def remove_stream(self, stream_id: int):
"""从优先级树移除流"""
if stream_id in self.nodes:
node = self.nodes[stream_id]
if node.parent:
node.parent.remove_child(stream_id)
del self.nodes[stream_id]
def get_next_stream(self) -> Optional[int]:
"""
获取下一个应该调度的流
使用加权公平队列(WFQ)算法
"""
if not self.nodes:
return None
# 从根节点开始深度优先遍历
def traverse(node: PriorityNode, credits: float) -> Tuple[Optional[int], float]:
if not node.children:
# 叶子节点(实际流)
if node.stream_id > 0: # 不是根节点
return node.stream_id, credits - node.weight
# 非叶子节点,选择子节点
for child in sorted(node.children, key=lambda x: -x.weight):
result, remaining = traverse(child, credits + child.weight)
if result:
return result, remaining
return None, credits
# 初始信用值
initial_credits = 1000
stream_id, _ = traverse(self.root, initial_credits)
return stream_id
def update_stream_priority(self, stream_id: int,
depends_on: int, weight: int, exclusive: bool):
"""更新流优先级"""
if stream_id not in self.nodes:
return
node = self.nodes[stream_id]
node.weight = weight
# 如果依赖关系改变
new_parent = self.nodes.get(depends_on, self.root)
if node.parent != new_parent:
# 从原父节点移除
if node.parent:
node.parent.remove_child(stream_id)
# 添加到新父节点
new_parent.add_child(node, exclusive)
def visualize_tree(self) -> str:
"""可视化优先级树"""
lines = []
def print_node(node: PriorityNode, level: int, prefix: str):
if node.stream_id == 0:
line = f"{prefix}ROOT (w={node.weight})"
else:
line = f"{prefix}Stream {node.stream_id} (w={node.weight})"
lines.append(line)
for i, child in enumerate(node.children):
is_last = i == len(node.children) - 1
child_prefix = prefix + ("└── " if is_last else "├── ")
print_node(child, level + 1, child_prefix)
print_node(self.root, 0, "")
return "\n".join(lines)
四、HPACK 头部压缩
4.1 HPACK 编码器
python
复制
下载
class HuffmanNode:
"""霍夫曼树节点"""
def __init__(self, symbol: Optional[int] = None, freq: int = 0):
self.symbol = symbol # None表示内部节点
self.freq = freq
self.left = None
self.right = None
def __lt__(self, other):
return self.freq < other.freq
class HPACKEncoder:
"""HPACK 编码器(RFC 7541)"""
# 静态表(RFC 7541 Appendix A)
STATIC_TABLE = [
(":authority", ""),
(":method", "GET"),
(":method", "POST"),
(":path", "/"),
(":path", "/index.html"),
(":scheme", "http"),
(":scheme", "https"),
(":status", "200"),
(":status", "204"),
(":status", "206"),
(":status", "304"),
(":status", "400"),
(":status", "404"),
(":status", "500"),
("accept-charset", ""),
("accept-encoding", "gzip, deflate"),
("accept-language", ""),
("accept-ranges", ""),
("accept", ""),
("access-control-allow-origin", ""),
("age", ""),
("allow", ""),
("authorization", ""),
("cache-control", ""),
("content-disposition", ""),
("content-encoding", ""),
("content-language", ""),
("content-length", ""),
("content-location", ""),
("content-range", ""),
("content-type", ""),
("cookie", ""),
("date", ""),
("etag", ""),
("expect", ""),
("expires", ""),
("from", ""),
("host", ""),
("if-match", ""),
("if-modified-since", ""),
("if-none-match", ""),
("if-range", ""),
("if-unmodified-since", ""),
("last-modified", ""),
("link", ""),
("location", ""),
("max-forwards", ""),
("proxy-authenticate", ""),
("proxy-authorization", ""),
("range", ""),
("referer", ""),
("refresh", ""),
("retry-after", ""),
("server", ""),
("set-cookie", ""),
("strict-transport-security", ""),
("transfer-encoding", ""),
("user-agent", ""),
("vary", ""),
("via", ""),
("www-authenticate", "")
]
def __init__(self, max_table_size: int = 4096):
self.max_table_size = max_table_size
self.dynamic_table = [] # 动态表(最近添加的在前面)
self.current_table_size = 0
# 霍夫曼编码表(RFC 7541 Appendix B)
self.huffman_table = self._build_huffman_table()
# 编码缓存
self.index_cache = {} # (name, value) -> (index, index_type)
def _build_huffman_table(self) -> dict:
"""构建霍夫曼编码表(简化版)"""
# 实际应使用RFC 7541 Appendix B的完整表
huffman_codes = {
0: '1111111111000', # EOS (end of string)
1: '11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111',
# ... 实际有256个字符的编码
256: '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
}
return huffman_codes
def encode_header(self, name: str, value: str) -> bytes:
"""
编码单个头部字段
Returns:
编码后的字节
"""
# 查找完全匹配
full_match_index = self._find_full_match(name, value)
if full_match_index:
return self._encode_indexed(full_match_index)
# 查找名称匹配
name_match_index = self._find_name_match(name)
# 选择编码方式
if name_match_index:
# 带引用名称的文字表示
return self._encode_literal_with_name_ref(name_match_index, value)
else:
# 不带引用名称的文字表示
return self._encode_literal_without_name_ref(name, value)
def _find_full_match(self, name: str, value: str) -> Optional[int]:
"""查找完全匹配的索引"""
# 检查静态表
for i, (n, v) in enumerate(self.STATIC_TABLE, 1):
if n == name and v == value:
return i
# 检查动态表
for i, (n, v) in enumerate(self.dynamic_table, len(self.STATIC_TABLE) + 1):
if n == name and v == value:
return i
return None
def _find_name_match(self, name: str) -> Optional[int]:
"""查找名称匹配的索引"""
# 检查静态表
for i, (n, _) in enumerate(self.STATIC_TABLE, 1):
if n == name:
return i
# 检查动态表
for i, (n, _) in enumerate(self.dynamic_table, len(self.STATIC_TABLE) + 1):
if n == name:
return i
return None
def _encode_indexed(self, index: int) -> bytes:
"""
编码索引表示(6.1)
格式: 1 (1位) + 索引值
"""
# 索引从1开始,但编码时使用0-based
index -= 1
if index < 0x3F: # 6位可以表示
return bytes([0x80 | index]) # 最高位为1
# 使用多字节整数编码
return self._encode_integer(0x80, 7, index)
def _encode_literal_with_name_ref(self, name_index: int, value: str) -> bytes:
"""
编码带引用名称的文字表示(6.2.1)
"""
# 名称索引(从1开始)
name_index -= 1
# 第一个字节:01 (2位) + 名称索引前缀
first_byte = 0x40 # 01xxxxxx
if name_index < 0x3F: # 6位可以表示
first_byte |= name_index
header = bytes([first_byte])
else:
header = self._encode_integer(0x40, 6, name_index)
# 编码值(霍夫曼编码)
value_encoded = self._encode_string(value)
return header + value_encoded
def _encode_literal_without_name_ref(self, name: str, value: str) -> bytes:
"""
编码不带引用名称的文字表示(6.2.2)
"""
# 第一个字节:00 (2位)
header = bytes([0x00])
# 编码名称
name_encoded = self._encode_string(name)
# 编码值
value_encoded = self._encode_string(value)
return header + name_encoded + value_encoded
def _encode_string(self, s: str) -> bytes:
"""
编码字符串(5.2)
Returns:
编码后的字节:1位H标志 + 长度 + 字符串数据
"""
# 选择使用霍夫曼编码
use_huffman = len(s) > 10 # 简化:长字符串使用霍夫曼编码
if use_huffman:
# 霍夫曼编码
encoded = self._huffman_encode(s)
h_bit = 1 << 7
else:
# 直接编码
encoded = s.encode('utf-8')
h_bit = 0
length = len(encoded)
if length < 0x7F: # 7位可以表示
length_byte = bytes([h_bit | length])
else:
# 使用多字节整数编码
length_byte = self._encode_integer(h_bit, 7, length)
return length_byte + encoded
def _huffman_encode(self, s: str) -> bytes:
"""霍夫曼编码(简化实现)"""
# 实际实现应使用完整的霍夫曼表
encoded_bits = []
for char in s:
code = self.huffman_table.get(ord(char), '')
encoded_bits.append(code)
bit_string = ''.join(encoded_bits)
# 填充到字节边界
padding_bits = (8 - len(bit_string) % 8) % 8
bit_string += '1' * padding_bits
# 转换为字节
bytes_list = []
for i in range(0, len(bit_string), 8):
byte_str = bit_string[i:i+8]
byte_val = int(byte_str, 2)
bytes_list.append(byte_val)
return bytes(bytes_list)
def _encode_integer(self, prefix: int, prefix_bits: int, value: int) -> bytes:
"""
编码整数(5.1)
Args:
prefix: 前缀值
prefix_bits: 前缀位数
value: 要编码的整数值
"""
max_prefix = (1 << prefix_bits) - 1
if value < max_prefix:
return bytes([prefix | value])
bytes_list = [prefix | max_prefix]
value -= max_prefix
while value >= 128:
bytes_list.append((value % 128) + 128)
value //= 128
bytes_list.append(value)
return bytes(bytes_list)
def add_to_dynamic_table(self, name: str, value: str):
"""添加条目到动态表"""
entry_size = 32 + len(name) + len(value) # 估算大小
# 确保表大小不超过限制
while self.current_table_size + entry_size > self.max_table_size and self.dynamic_table:
self._evict_from_dynamic_table()
# 添加到动态表头部
self.dynamic_table.insert(0, (name, value))
self.current_table_size += entry_size
def _evict_from_dynamic_table(self):
"""从动态表驱逐条目"""
if not self.dynamic_table:
return
name, value = self.dynamic_table.pop()
entry_size = 32 + len(name) + len(value)
self.current_table_size -= entry_size
def update_table_size(self, new_size: int):
"""更新动态表大小"""
self.max_table_size = new_size
# 如果新大小小于当前大小,驱逐条目
while self.current_table_size > new_size and self.dynamic_table:
self._evict_from_dynamic_table()
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc
需要全套面试笔记及答案
【点击此处即可/免费获取】
4.2 HPACK 解码器
python
复制
下载
class HPACKDecoder:
"""HPACK 解码器"""
def __init__(self, max_table_size: int = 4096):
self.max_table_size = max_table_size
self.dynamic_table = []
self.current_table_size = 0
# 霍夫曼解码表
self.huffman_decode_table = self._build_huffman_decode_table()
def decode(self, data: bytes) -> List[Tuple[str, str]]:
"""解码头部块"""
headers = []
offset = 0
while offset < len(data):
first_byte = data[offset]
if first_byte & 0x80: # 最高位为1:索引表示
index, offset = self._decode_index(data, offset)
name, value = self._get_header_field(index)
headers.append((name, value))
# 添加到动态表
self.add_to_dynamic_table(name, value)
elif first_byte & 0x40: # 01xxxxxx:带引用名称的文字表示
index, offset = self._decode_integer(data, offset, 6)
value, offset = self._decode_string(data, offset)
name = self._get_header_name(index)
headers.append((name, value))
# 添加到动态表
self.add_to_dynamic_table(name, value)
elif first_byte & 0x20: # 001xxxxx:表大小更新
new_size, offset = self._decode_integer(data, offset, 5)
self.update_table_size(new_size)
else: # 00xxxxxx:不带引用名称的文字表示
# 解析名称
if first_byte & 0x0F == 0: # 0000xxxx
name, offset = self._decode_string(data, offset)
else:
index, offset = self._decode_integer(data, offset, 4)
name = self._get_header_name(index)
# 解析值
value, offset = self._decode_string(data, offset)
headers.append((name, value))
# 添加到动态表
self.add_to_dynamic_table(name, value)
return headers
def _decode_index(self, data: bytes, offset: int) -> Tuple[int, int]:
"""解码索引表示"""
index, new_offset = self._decode_integer(data, offset, 7)
return index + 1, new_offset # 索引从1开始
def _decode_integer(self, data: bytes, offset: int, prefix_bits: int) -> Tuple[int, int]:
"""
解码整数(5.1)
Returns:
(整数值, 新的偏移量)
"""
max_prefix = (1 << prefix_bits) - 1
first_byte = data[offset]
prefix = first_byte & max_prefix
if prefix != max_prefix:
return prefix, offset + 1
value = 0
multiplier = 1
offset += 1
while offset < len(data):
byte = data[offset]
offset += 1
value += (byte & 0x7F) * multiplier
multiplier *= 128
if not (byte & 0x80):
break
return value + max_prefix, offset
def _decode_string(self, data: bytes, offset: int) -> Tuple[str, int]:
"""解码字符串"""
first_byte = data[offset]
huffman = bool(first_byte & 0x80)
length, offset = self._decode_integer(data, offset, 7)
string_data = data[offset:offset+length]
offset += length
if huffman:
string = self._huffman_decode(string_data)
else:
string = string_data.decode('utf-8')
return string, offset
def _huffman_decode(self, data: bytes) -> str:
"""霍夫曼解码(简化实现)"""
# 实际实现应使用完整的霍夫曼表
result = []
current_code = ""
# 将字节转换为位字符串
bit_string = ""
for byte in data:
bit_string += f"{byte:08b}"
# 移除填充位
while bit_string and bit_string[-1] == '1':
bit_string = bit_string[:-1]
# 简化解码(实际应使用树遍历)
# 这里假设使用ASCII编码
for i in range(0, len(bit_string), 8):
if i + 8 <= len(bit_string):
byte_str = bit_string[i:i+8]
char_code = int(byte_str, 2)
if 32 <= char_code <= 126: # 可打印ASCII
result.append(chr(char_code))
return ''.join(result)
def _get_header_field(self, index: int) -> Tuple[str, str]:
"""获取头部字段(包含静态表和动态表)"""
if index <= len(HPACKEncoder.STATIC_TABLE):
return HPACKEncoder.STATIC_TABLE[index - 1]
dynamic_index = index - len(HPACKEncoder.STATIC_TABLE) - 1
if 0 <= dynamic_index < len(self.dynamic_table):
return self.dynamic_table[dynamic_index]
raise ValueError(f"无效的索引: {index}")
def _get_header_name(self, index: int) -> str:
"""获取头部名称"""
name, _ = self._get_header_field(index)
return name
def add_to_dynamic_table(self, name: str, value: str):
"""添加到动态表(与编码器相同)"""
entry_size = 32 + len(name) + len(value)
while self.current_table_size + entry_size > self.max_table_size and self.dynamic_table:
self._evict_from_dynamic_table()
self.dynamic_table.insert(0, (name, value))
self.current_table_size += entry_size
def _evict_from_dynamic_table(self):
"""从动态表驱逐条目"""
if not self.dynamic_table:
return
name, value = self.dynamic_table.pop()
entry_size = 32 + len(name) + len(value)
self.current_table_size -= entry_size
def update_table_size(self, new_size: int):
"""更新表大小"""
self.max_table_size = new_size
while self.current_table_size > new_size and self.dynamic_table:
self._evict_from_dynamic_table()
五、服务器推送实现
5.1 服务器推送管理器
python
复制
下载
class ServerPushManager:
"""服务器推送管理器"""
def __init__(self, connection):
self.connection = connection
self.pending_pushes = {} # promised_stream_id -> PushInfo
self.active_pushes = {} # push_stream_id -> PushInfo
# 推送缓存(避免重复推送)
self.push_cache = {}
def initiate_push(self, original_stream_id: int, request_headers: List[Tuple[str, str]]):
"""
发起服务器推送
Args:
original_stream_id: 原始请求的流ID
request_headers: 推送资源的请求头部
"""
# 检查是否允许推送
if not self._can_push():
return None
# 分配推送流ID(偶数,服务器发起)
push_stream_id = self._allocate_push_stream_id()
# 创建PUSH_PROMISE帧
push_promise_frame = self._create_push_promise_frame(
original_stream_id, push_stream_id, request_headers
)
# 发送PUSH_PROMISE帧
self.connection.send_frame(push_promise_frame)
# 记录推送信息
push_info = PushInfo(
original_stream_id=original_stream_id,
push_stream_id=push_stream_id,
request_headers=request_headers,
state=PushState.PROMISED
)
self.pending_pushes[push_stream_id] = push_info
return push_stream_id
def _can_push(self) -> bool:
"""检查是否允许推送"""
# 检查SETTINGS_ENABLE_PUSH设置
if self.connection.settings.get(SettingsFrame.SETTINGS_ENABLE_PUSH, 1) == 0:
return False
# 检查并发流限制
active_streams = len([s for s in self.connection.streams.values()
if s.state != StreamState.CLOSED])
max_streams = self.connection.settings.get(
SettingsFrame.SETTINGS_MAX_CONCURRENT_STREAMS, 100
)
return active_streams < max_streams - 5 # 预留一些空间
def _allocate_push_stream_id(self) -> int:
"""分配推送流ID"""
# 服务器发起的流ID为偶数
last_id = getattr(self, '_last_push_id', 0)
next_id = last_id + 2
self._last_push_id = next_id
return next_id
def _create_push_promise_frame(self, original_stream_id: int,
push_stream_id: int,
request_headers: List[Tuple[str, str]]) -> HTTP2Frame:
"""创建PUSH_PROMISE帧"""
# 序列化头部块(使用HPACK)
encoder = HPACKEncoder()
header_block = b''
for name, value in request_headers:
encoded = encoder.encode_header(name, value)
header_block += encoded
# 构建PUSH_PROMISE帧载荷
payload = struct.pack('>I', push_stream_id & 0x7FFFFFFF) + header_block
header = FrameHeader(
length=len(payload),
type=FrameType.PUSH_PROMISE,
flags=FrameFlags.END_HEADERS,
stream_id=original_stream_id
)
return HTTP2Frame(header, payload)
def fulfill_push(self, push_stream_id: int, response_headers: List[Tuple[str, str]],
data: bytes = b''):
"""履行推送承诺"""
if push_stream_id not in self.pending_pushes:
raise ValueError(f"未找到推送承诺: {push_stream_id}")
push_info = self.pending_pushes.pop(push_stream_id)
push_info.state = PushState.FULFILLING
# 发送响应头部
headers_frame = HeadersFrame(
push_stream_id, response_headers,
end_stream=not data # 如果没有数据,立即结束流
)
self.connection.send_frame(headers_frame)
# 如果有数据,发送DATA帧
if data:
# 分块发送数据(考虑流控制)
chunk_size = 16384 # 最大帧大小
for i in range(0, len(data), chunk_size):
chunk = data[i:i+chunk_size]
end_stream = (i + chunk_size >= len(data))
data_frame = DataFrame(push_stream_id, chunk, end_stream=end_stream)
self.connection.send_frame(data_frame)
push_info.state = PushState.FULFILLED
self.active_pushes[push_stream_id] = push_info
# 添加到推送缓存
cache_key = self._get_cache_key(request_headers)
self.push_cache[cache_key] = {
'headers': response_headers,
'data': data,
'timestamp': time.time()
}
def _get_cache_key(self, headers: List[Tuple[str, str]]) -> str:
"""生成缓存键"""
# 基于方法、路径等生成键
key_parts = []
for name, value in headers:
if name in [':method', ':path', ':scheme', ':authority']:
key_parts.append(f"{name}:{value}")
return '|'.join(sorted(key_parts))
def handle_push_promise(self, frame: HTTP2Frame, original_stream_id: int):
"""客户端处理PUSH_PROMISE帧"""
# 解析推送流ID
push_stream_id = struct.unpack('>I', frame.payload[:4])[0] & 0x7FFFFFFF
# 解析请求头部(使用HPACK)
decoder = HPACKDecoder()
request_headers = decoder.decode(frame.payload[4:])
# 客户端可以选择接受或拒绝推送
# 这里简化处理,总是接受
print(f"收到服务器推送承诺: 流{push_stream_id}, 头部: {request_headers}")
# 记录推送信息
push_info = PushInfo(
original_stream_id=original_stream_id,
push_stream_id=push_stream_id,
request_headers=request_headers,
state=PushState.PROMISED
)
self.pending_pushes[push_stream_id] = push_info
def cancel_push(self, push_stream_id: int, error_code: int = 0):
"""取消推送"""
if push_stream_id in self.pending_pushes:
push_info = self.pending_pushes.pop(push_stream_id)
push_info.state = PushState.CANCELLED
# 发送RST_STREAM帧
rst_frame = RstStreamFrame(push_stream_id, error_code)
self.connection.send_frame(rst_frame)
elif push_stream_id in self.active_pushes:
push_info = self.active_pushes.pop(push_stream_id)
push_info.state = PushState.CANCELLED
def get_push_stats(self) -> dict:
"""获取推送统计"""
return {
'pending_pushes': len(self.pending_pushes),
'active_pushes': len(self.active_pushes),
'cache_entries': len(self.push_cache),
'states': {
'promised': sum(1 for p in self.pending_pushes.values()
if p.state == PushState.PROMISED),
'fulfilling': sum(1 for p in self.active_pushes.values()
if p.state == PushState.FULFILLING),
'fulfilled': sum(1 for p in self.active_pushes.values()
if p.state == PushState.FULFILLED),
'cancelled': sum(1 for p in list(self.pending_pushes.values()) +
list(self.active_pushes.values())
if p.state == PushState.CANCELLED)
}
}
class PushState(enum.Enum):
"""推送状态"""
PROMISED = "promised" # 已承诺,未履行
FULFILLING = "fulfilling" # 正在履行
FULFILLED = "fulfilled" # 已履行
CANCELLED = "cancelled" # 已取消
@dataclass
class PushInfo:
"""推送信息"""
original_stream_id: int
push_stream_id: int
request_headers: List[Tuple[str, str]]
state: PushState
response_headers: Optional[List[Tuple[str, str]]] = None
response_data: Optional[bytes] = None
started_at: float = field(default_factory=time.time)
completed_at: Optional[float] = None
六、性能测试与优化
6.1 HTTP/2 性能分析器
python
复制
下载
class HTTP2PerformanceAnalyzer:
"""HTTP/2 性能分析器"""
def __init__(self):
self.metrics = {
'connection': {
'setup_time': None,
'total_requests': 0,
'total_responses': 0,
'total_bytes_sent': 0,
'total_bytes_received': 0
},
'streams': {
'active': 0,
'completed': 0,
'failed': 0,
'avg_latency': 0,
'throughput': 0
},
'multiplexing': {
'concurrent_streams_max': 0,
'stream_contention_count': 0,
'priority_effectiveness': 0
},
'compression': {
'header_compression_ratio': 0,
'static_table_hits': 0,
'dynamic_table_hits': 0,
'huffman_usage': 0
},
'flow_control': {
'window_updates_sent': 0,
'window_updates_received': 0,
'stalled_streams': 0
}
}
self.stream_timings = {} # stream_id -> timing_info
self.samples = []
def record_stream_start(self, stream_id: int):
"""记录流开始时间"""
self.stream_timings[stream_id] = {
'start': time.time(),
'headers_sent': None,
'headers_received': None,
'data_start': None,
'data_end': None,
'complete': None
}
self.metrics['streams']['active'] += 1
self.metrics['connection']['total_requests'] += 1
def record_stream_end(self, stream_id: int, success: bool = True):
"""记录流结束时间"""
if stream_id in self.stream_timings:
timing = self.stream_timings[stream_id]
timing['complete'] = time.time()
latency = timing['complete'] - timing['start']
# 更新平均延迟(指数移动平均)
old_avg = self.metrics['streams']['avg_latency']
if old_avg == 0:
new_avg = latency
else:
alpha = 0.1
new_avg = (1 - alpha) * old_avg + alpha * latency
self.metrics['streams']['avg_latency'] = new_avg
self.metrics['streams']['active'] -= 1
self.metrics['streams']['completed'] += 1
if not success:
self.metrics['streams']['failed'] += 1
# 记录样本
self.samples.append({
'stream_id': stream_id,
'latency': latency,
'success': success,
'timestamp': time.time()
})
# 保持最近1000个样本
if len(self.samples) > 1000:
self.samples.pop(0)
def record_multiplexing_metric(self, concurrent_streams: int):
"""记录多路复用指标"""
if concurrent_streams > self.metrics['multiplexing']['concurrent_streams_max']:
self.metrics['multiplexing']['concurrent_streams_max'] = concurrent_streams
def record_compression_metric(self, original_size: int, compressed_size: int,
table_type: str = 'static'):
"""记录压缩指标"""
if original_size > 0:
ratio = compressed_size / original_size
self.metrics['compression']['header_compression_ratio'] = (
self.metrics['compression']['header_compression_ratio'] * 0.9 +
ratio * 0.1
)
if table_type == 'static':
self.metrics['compression']['static_table_hits'] += 1
else:
self.metrics['compression']['dynamic_table_hits'] += 1
def calculate_throughput(self, window_seconds: int = 10):
"""计算吞吐量"""
now = time.time()
cutoff = now - window_seconds
# 筛选时间窗口内的样本
recent_samples = [s for s in self.samples if s['timestamp'] > cutoff]
if not recent_samples:
return 0
total_bytes = sum(s.get('bytes', 0) for s in recent_samples)
throughput = total_bytes / window_seconds
self.metrics['streams']['throughput'] = throughput
return throughput
def analyze_bottlenecks(self) -> dict:
"""分析性能瓶颈"""
bottlenecks = []
# 检查并发流数量
max_concurrent = self.metrics['multiplexing']['concurrent_streams_max']
if max_concurrent < 10:
bottlenecks.append({
'type': 'low_concurrency',
'description': f'最大并发流数较低: {max_concurrent}',
'suggestion': '调整最大并发流设置或优化服务器配置'
})
# 检查头部压缩效率
compression_ratio = self.metrics['compression']['header_compression_ratio']
if compression_ratio > 0.7:
bottlenecks.append({
'type': 'poor_header_compression',
'description': f'头部压缩率较差: {compression_ratio:.2%}',
'suggestion': '优化头部字段,增加动态表使用'
})
# 检查流控制
stalled = self.metrics['flow_control']['stalled_streams']
if stalled > 0:
bottlenecks.append({
'type': 'flow_control_stalls',
'description': f'流控制导致 {stalled} 个流停滞',
'suggestion': '调整初始窗口大小或优化窗口更新策略'
})
# 检查优先级效果
priority_effect = self.metrics['multiplexing']['priority_effectiveness']
if priority_effect < 0.5:
bottlenecks.append({
'type': 'ineffective_priority',
'description': '流优先级效果不佳',
'suggestion': '优化优先级树结构或调整权重分配'
})
return bottlenecks
def generate_report(self) -> dict:
"""生成性能报告"""
report = {
'summary': {
'total_requests': self.metrics['connection']['total_requests'],
'success_rate': (
self.metrics['streams']['completed'] /
max(self.metrics['connection']['total_requests'], 1)
),
'avg_latency_ms': self.metrics['streams']['avg_latency'] * 1000,
'throughput_mbps': self.calculate_throughput() * 8 / 1_000_000
},
'multiplexing': {
'max_concurrent_streams': self.metrics['multiplexing']['concurrent_streams_max'],
'stream_completion_rate': (
self.metrics['streams']['completed'] /
max(self.metrics['streams']['active'] + self.metrics['streams']['completed'], 1)
)
},
'compression': {
'compression_ratio': self.metrics['compression']['header_compression_ratio'],
'static_table_hit_rate': (
self.metrics['compression']['static_table_hits'] /
max(self.metrics['compression']['static_table_hits'] +
self.metrics['compression']['dynamic_table_hits'], 1)
)
},
'flow_control': {
'window_updates_per_request': (
self.metrics['flow_control']['window_updates_sent'] /
max(self.metrics['connection']['total_requests'], 1)
)
},
'bottlenecks': self.analyze_bottlenecks()
}
return report
6.2 HTTP/2 调优指南
python
复制
下载
class HTTP2TuningGuide:
"""HTTP/2 性能调优指南"""
@staticmethod
def get_optimization_recommendations(metrics: dict) -> list:
"""根据指标提供优化建议"""
recommendations = []
# 基于并发流的建议
max_concurrent = metrics.get('multiplexing', {}).get('max_concurrent_streams', 0)
if max_concurrent < 50:
recommendations.append({
'area': '并发度',
'issue': '并发流数量较低',
'suggestions': [
'增加SETTINGS_MAX_CONCURRENT_STREAMS设置',
'优化服务器资源分配',
'考虑使用HTTP/2连接池'
]
})
# 基于压缩率的建议
compression_ratio = metrics.get('compression', {}).get('compression_ratio', 1.0)
if compression_ratio > 0.6:
recommendations.append({
'area': '头部压缩',
'issue': f'头部压缩率较低 ({compression_ratio:.2%})',
'suggestions': [
'使用常见的头部字段以利用静态表',
'启用动态表并适当调整大小',
'避免发送重复的头部字段'
]
})
# 基于延迟的建议
avg_latency = metrics.get('summary', {}).get('avg_latency_ms', 0)
if avg_latency > 100:
recommendations.append({
'area': '延迟',
'issue': f'平均延迟较高 ({avg_latency:.1f}ms)',
'suggestions': [
'优化服务器处理逻辑',
'使用服务器推送减少往返',
'启用TCP快速打开(TFO)',
'调整TCP拥塞控制算法'
]
})
return recommendations
@staticmethod
def server_configuration_tuning():
"""服务器配置调优"""
config = {
'nginx': {
'http2_max_concurrent_streams': '建议128-256',
'http2_max_field_size': '建议4k-8k',
'http2_max_header_size': '建议16k-32k',
'http2_body_preread_size': '建议64k',
'http2_idle_timeout': '建议180s',
'http2_recv_timeout': '建议30s'
},
'apache': {
'H2MaxSessionStreams': '建议100',
'H2StreamMaxMemSize': '建议65536',
'H2MaxWorkerIdleSeconds': '建议300',
'H2MinWorkers': '建议10',
'H2MaxWorkers': '建议400'
},
'general': {
'tcp_fastopen': '建议启用',
'tcp_nodelay': '建议启用',
'keepalive_timeout': '建议75s',
'sendfile': '建议启用',
'tcp_nopush': '建议启用'
}
}
return config
@staticmethod
def client_optimization_techniques():
"""客户端优化技术"""
techniques = [
{
'name': '连接复用',
'description': '尽可能复用HTTP/2连接',
'implementation': '实现连接池,避免频繁创建新连接'
},
{
'name': '优先级优化',
'description': '合理设置流优先级',
'implementation': '''
// 关键资源(CSS、JS、关键图片)设置为高优先级
// 非关键资源(广告、分析脚本)设置为低优先级
// 使用依赖关系表达资源加载顺序
'''
},
{
'name': '服务器推送优化',
'description': '有效利用服务器推送',
'implementation': '''
// 推送关键子资源(CSS、关键JS)
// 避免推送过大的资源
// 监控推送接受率,调整推送策略
'''
},
{
'name': '头部优化',
'description': '减少头部大小和数量',
'implementation': '''
// 使用cookie-free域名
// 压缩或省略不必要的头部字段
// 使用简化的User-Agent
'''
}
]
return techniques
七、安全考虑
python
复制
下载
class HTTP2Security:
"""HTTP/2 安全考虑"""
@staticmethod
def security_considerations():
"""安全考虑因素"""
considerations = {
'加密要求': {
'issue': 'HTTP/2规范强烈建议使用TLS',
'reason': '防止中间人攻击和协议降级',
'recommendation': '始终使用HTTPS,禁用HTTP/2 over cleartext'
},
'头部长度的攻击': {
'issue': '攻击者可能发送超长头部进行拒绝服务',
'reason': 'HPACK解码需要分配内存',
'recommendation': '限制头部列表大小和字段大小'
},
'流数量的攻击': {
'issue': '攻击者可能创建大量流消耗服务器资源',
'reason': '每个流都需要状态跟踪',
'recommendation': '限制最大并发流数量,实现速率限制'
},
'依赖循环': {
'issue': '流优先级依赖可能形成循环',
'reason': '导致调度器死锁',
'recommendation': '实现依赖循环检测和恢复'
},
'压缩攻击': {
'issue': 'CRIME/BREACH等压缩相关攻击',
'reason': 'HPACK可能泄露敏感信息',
'recommendation': '避免压缩包含秘密的头部字段'
}
}
return considerations
@staticmethod
def security_best_practices():
"""安全最佳实践"""
practices = [
'始终使用TLS 1.2或更高版本',
'启用完整的TLS特性(完美前向保密等)',
'实现严格的头部大小限制',
'监控异常流创建模式',
'定期更新HTTP/2实现以修复安全漏洞',
'使用应用层防火墙监控HTTP/2流量',
'禁用不必要的HTTP/2特性(如服务器推送)',
'实现连接超时和空闲连接清理',
'记录安全事件并进行审计'
]
return practices
@staticmethod
def vulnerability_mitigations():
"""漏洞缓解措施"""
mitigations = {
'CVE-2019-9511': {
'name': 'Data Dribble',
'description': '攻击者通过小窗口更新消耗服务器资源',
'mitigation': '实现最小窗口大小限制和速率限制'
},
'CVE-2019-9512': {
'name': 'Ping Flood',
'description': '攻击者发送大量PING帧消耗服务器资源',
'mitigation': '限制PING帧速率,实现超时检测'
},
'CVE-2019-9513': {
'name': 'Resource Loop',
'description': '攻击者通过RST_STREAM帧创建资源循环',
'mitigation': '限制流重置速率,实现资源清理'
},
'CVE-2019-9518': {
'name': 'Reset Flood',
'description': '攻击者发送大量RST_STREAM帧',
'mitigation': '实现连接级别的重置限制'
}
}
return mitigations
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc
需要全套面试笔记及答案
【点击此处即可/免费获取】
八、实际部署示例
8.1 Web服务器集成
python
复制
下载
class HTTP2WebServer:
"""HTTP/2 Web服务器示例"""
def __init__(self, host='0.0.0.0', port=8443):
self.host = host
self.port = port
self.connections = {}
self.running = False
# 资源映射
self.resources = {
'/': self._serve_index,
'/style.css': self._serve_css,
'/app.js': self._serve_js,
'/api/data': self._serve_api_data
}
# 推送映射
self.push_resources = {
'/': ['/style.css', '/app.js']
}
async def start(self):
"""启动服务器"""
import asyncio
import ssl
# 创建SSL上下文(HTTP/2需要TLS)
ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
ssl_context.load_cert_chain('cert.pem', 'key.pem')
# 设置ALPN协议(HTTP/2)
ssl_context.set_alpn_protocols(['h2', 'http/1.1'])
# 创建服务器
server = await asyncio.start_server(
self.handle_connection,
self.host, self.port,
ssl=ssl_context
)
self.running = True
print(f"HTTP/2服务器启动在 https://{self.host}:{self.port}")
async with server:
await server.serve_forever()
async def handle_connection(self, reader, writer):
"""处理客户端连接"""
try:
# 创建HTTP/2连接
connection = HTTP2Connection(writer)
connection_id = id(connection)
self.connections[connection_id] = connection
# 处理请求
await self._process_requests(reader, connection)
except Exception as e:
print(f"连接处理错误: {e}")
finally:
if connection_id in self.connections:
del self.connections[connection_id]
writer.close()
async def _process_requests(self, reader, connection):
"""处理HTTP/2请求"""
while connection.running:
try:
# 读取数据
data = await reader.read(4096)
if not data:
break
# 处理帧
frame = HTTP2Frame.parse(data)
await self._handle_frame(frame, connection)
except Exception as e:
print(f"请求处理错误: {e}")
break
async def _handle_frame(self, frame, connection):
"""处理帧"""
if frame.header.type == FrameType.HEADERS:
await self._handle_request(frame, connection)
elif frame.header.type == FrameType.DATA:
await self._handle_request_data(frame, connection)
elif frame.header.type == FrameType.SETTINGS:
connection._handle_settings_frame(frame)
elif frame.header.type == FrameType.WINDOW_UPDATE:
connection._handle_window_update_frame(frame)
async def _handle_request(self, frame, connection):
"""处理请求HEADERS帧"""
stream_id = frame.header.stream_id
# 解析头部
decoder = HPACKDecoder()
headers = decoder.decode(frame.payload)
# 提取请求信息
method = self._get_header_value(headers, ':method')
path = self._get_header_value(headers, ':path')
print(f"请求: {method} {path} (流{stream_id})")
# 检查是否需要服务器推送
if path in self.push_resources:
await self._initiate_push(stream_id, path, connection)
# 处理请求
handler = self.resources.get(path)
if handler:
response = await handler(headers)
# 发送响应头部
response_headers = [
(':status', '200'),
('content-type', response['content_type']),
('content-length', str(len(response['data'])))
]
headers_frame = HeadersFrame(stream_id, response_headers)
connection.send_frame(headers_frame)
# 发送响应数据
if response['data']:
data_frame = DataFrame(stream_id, response['data'], end_stream=True)
connection.send_frame(data_frame)
else:
# 404 Not Found
response_headers = [(':status', '404')]
headers_frame = HeadersFrame(stream_id, response_headers, end_stream=True)
connection.send_frame(headers_frame)
async def _initiate_push(self, original_stream_id, path, connection):
"""发起服务器推送"""
push_resources = self.push_resources.get(path, [])
for resource_path in push_resources:
# 创建推送请求头部
request_headers = [
(':method', 'GET'),
(':path', resource_path),
(':scheme', 'https'),
(':authority', f'{self.host}:{self.port}')
]
# 模拟推送管理器
push_manager = ServerPushManager(connection)
push_stream_id = push_manager.initiate_push(
original_stream_id, request_headers
)
if push_stream_id:
# 履行推送
resource_data = await self._load_resource(resource_path)
if resource_data:
response_headers = [
(':status', '200'),
('content-type', self._get_content_type(resource_path)),
('content-length', str(len(resource_data)))
]
push_manager.fulfill_push(
push_stream_id, response_headers, resource_data
)
async def _serve_index(self, headers):
"""服务首页"""
html = '''
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="/style.css">
<title>HTTP/2 Demo</title>
</head>
<body>
<h1>HTTP/2 多路复用演示</h1>
<div id="content"></div>
<script src="/app.js"></script>
</body>
</html>
'''
return {
'content_type': 'text/html; charset=utf-8',
'data': html.encode('utf-8')
}
def _get_header_value(self, headers, name):
"""获取头部值"""
for n, v in headers:
if n == name:
return v
return None
def _get_content_type(self, path):
"""根据路径获取内容类型"""
extensions = {
'.html': 'text/html',
'.css': 'text/css',
'.js': 'application/javascript',
'.json': 'application/json'
}
for ext, content_type in extensions.items():
if path.endswith(ext):
return content_type
return 'application/octet-stream'
这个完整的HTTP/2实现指南涵盖了多路复用、头部压缩、优先级调度、服务器推送等核心特性。实际部署时,还需要考虑安全性、性能调优和与现有HTTP/1.1系统的兼容性。