HTTP 2 协议学习与实战
字数 1097 2025-08-09 18:43:59
HTTP/2 协议学习与实战
HTTP协议基础问题
在网络编程中,存在两个常见问题:
- 数据粘包:多个数据包粘连在一起,难以区分边界
- 数据不完整:数据包在传输过程中丢失部分内容
传输协议通过设计特定的数据结构来解决这些问题。
HTTP/1.x的局限性
HTTP/1.x的主要缺点:
- 每次请求响应后都会断开连接
- 反复进行TCP三次握手和四次挥手
- 服务器无法主动推送
HTTP/2的核心改进
HTTP/2在不改变HTTP语义、方法、状态码、URL和首部字段的情况下:
- 支持长连接
- 采用二进制分帧传输方式
- 在应用层(HTTP)和传输层(TCP)之间增加二进制分帧层
- 实现低延迟高吞吐量
基础知识准备
数据表示基础
-
字节与比特:
- 1字节(byte) = 8比特(bit)
- 1字节最大值为127 (0111 1111)
-
原码、反码和补码:
- 原码:最高位是符号位(0正1负),其余表示数值
- 反码:正数同原码;负数符号位不变,其余取反
- 补码:正数同原码;负数在反码末位加1
- 计算机系统中数值一律用补码表示和存储
-
& 0xFF的作用:
- 保证补码一致性
- 只保留低8位数据
- 示例:
byte b = -127; // 补码: 1000 0001 int a = b; // 补码扩展: 1111 1111 1111 1111 1111 1111 1000 0001 a = b & 0xFF; // 结果: 0000 0000 0000 0000 0000 0000 1000 0001 (129)
移位运算符
-
左移(<<):低位补0
int a = 1; // 0000 0001 int b = a << 1; // 0000 0010 (2) -
右移(>>):高位补符号位
int b = 2; // 0000 0010 int c = b >> 1; // 0000 0001 (1)
数据分片与合并
存储大数示例(201314):
// 二进制: 0000 0011 0001 0010 0110 0010
byte a = (byte) 201314; // 0110 0010 (98) - 取最低8位
byte b = (byte) (201314 >> 8); // 0001 0010 (18) - 取9-16位
byte c = (byte) (201314 >> 16); // 0000 0011 (3) - 取17-24位
使用或运算符(|)合并:
long merged = (((long)a) << 16) | (((long)b) << 8) | ((long)c);
HTTP/2帧结构
HTTP/2通信的最小单位是帧,所有帧共享8字节首部:
+-----------------------------------------------+
| Length (24) |
+---------------+---------------+---------------+
| Type (8) | Flags (8) |
+-+-------------+---------------+-------------------------------+
|R| Stream Identifier (31) |
+=+=============================================================+
| Frame Payload (0...) ...
+---------------------------------------------------------------+
字段说明:
-
Length (24位):帧有效载荷长度
- 默认不大于2^14(16,384)
- 可通过SETTINGS_MAX_FRAME_SIZE设置更大值(16,384-16,777,215)
-
Type (8位):帧类型
- 决定帧格式和语义
- 未知类型帧应被忽略
-
Flags (8位):帧类型特定的布尔标志
-
R (1位):保留位,必须为0
-
Stream Identifier (31位):流标识符
- 0x0保留用于整个连接的帧
- 流:连接中的虚拟通道,承载双向消息,有唯一整数ID
-
Frame Payload:实际数据
HTTP/2实战实现
简化帧设计
+---------------------+---------------------+
| Length (3字节) | Type (1字节) |
+---------------------+---------------------+
| Payload (变长) |
+-------------------------------------------+
核心代码实现
Frame抽象类:
public abstract class Frame {
public static final int HEADER_LENGTH_SIZE = 3; // 长度字段大小
public static final int HEADER_TYPE_SIZE = 1; // 类型字段大小
public static final int HEADER_SIZE = HEADER_LENGTH_SIZE + HEADER_TYPE_SIZE;
public static final int MAX_CAPACITY = (int) Math.pow(2, HEADER_LENGTH_SIZE * 8) - 1;
public static final byte TYPE_STRING_FRAME = 11; // 字符串类型标识
}
Receive接收处理:
public class Receive {
public static Receive handler(InputStream inputStream) throws IOException {
byte[] header = new byte[Frame.HEADER_SIZE];
int headerReadCount = inputStream.read(header);
int length = getLength(header); // 解析长度
int type = getType(header); // 解析类型
byte[] body = new byte[length];
int bodyReadCount = inputStream.read(body);
return new Receive(length, type, body);
}
// 解析长度:3字节合并为整数
public static int getLength(byte[] header) {
return ((header[0] & 0xFF) << 16 | (header[1] & 0xFF) << 8 | header[2] & 0XFF);
}
// 解析类型
public static int getType(byte[] header) {
return header[3];
}
}
Sender发送处理:
public class Sender {
public Sender(byte type, String data) {
payload = data.getBytes();
length = payload.length;
// 设置长度字段(3字节)
header[0] = (byte) (payload.length >> 16);
header[1] = (byte) (payload.length >> 8);
header[2] = (byte) payload.length;
// 设置类型字段
header[3] = type;
// 合并头部和载荷
packet = new byte[length + Frame.HEADER_SIZE];
System.arraycopy(header, 0, packet, 0, Frame.HEADER_SIZE);
System.arraycopy(payload, 0, packet, Frame.HEADER_SIZE, length);
}
}
Server端示例:
public class Demo1 {
public static void main(String[] args) {
ServerSocket serverSocket = new ServerSocket(8848);
Socket accept = serverSocket.accept();
InputStream inputStream = accept.getInputStream();
Receive receive = Receive.handler(inputStream);
if (receive.type != Frame.TYPE_STRING_FRAME) {
throw new RuntimeException("Invalid packet type");
}
System.out.println(new String(receive.body));
}
}
Client端示例:
public class Demo2 {
public static void main(String[] args) {
Socket socket = new Socket("127.0.0.1", 8848);
Sender sender = new Sender(Frame.TYPE_STRING_FRAME, "Hello World");
OutputStream outputStream = socket.getOutputStream();
outputStream.write(sender.getPacket());
}
}
总结
HTTP/2通过二进制分帧机制解决了HTTP/1.x的性能问题:
- 使用帧结构明确数据边界,解决粘包问题
- 通过长度字段确保数据完整性
- 流标识实现多路复用
- 二进制格式提高解析效率
实现自定义协议时需要注意:
- 合理设计帧结构
- 正确处理字节序和数据类型转换
- 考虑大端小端问题
- 实现完善错误处理机制