京东Java面试被问:HTTP/2的多路复用和头部压缩实现

一、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系统的兼容性。

相关推荐
你怎么知道我是队长2 小时前
C语言---未定义行为
java·c语言·开发语言
没有bug.的程序员2 小时前
Java 序列化:Serializable vs. Protobuf 的性能与兼容性深度对比
java·开发语言·后端·反射·序列化·serializable·protobuf
愚公移码2 小时前
蓝凌EKP产品:主文档权限机制浅析
java·前端·数据库·蓝凌
Remember_9932 小时前
【LeetCode精选算法】滑动窗口专题一
java·数据结构·算法·leetcode·哈希算法
开开心心就好3 小时前
音频编辑工具,多端支持基础剪辑易操作
java·网络·windows·java-ee·电脑·maven·excel
Clarence Liu3 小时前
AI Agent开发(2) - 深入解析 A2A 协议与 Go 实战指南
开发语言·人工智能·golang
凯子坚持 c3 小时前
Protocol Buffers C++ 进阶数据类型与应用逻辑深度解析
java·服务器·c++
Francek Chen3 小时前
【大数据基础】大数据处理架构Hadoop:01 Hadoop概述
大数据·hadoop·分布式·架构