深入理解HTTP/2:nghttp2库源码解析及客户端实现示例

文章目录

    • [一、HTTP/2 特性实现:nghttp2 源码剖析](#一、HTTP/2 特性实现:nghttp2 源码剖析)
      • [1.1 二进制帧](#1.1 二进制帧)
      • [1.2 多路复用](#1.2 多路复用)
      • [1.3 头部压缩](#1.3 头部压缩)
      • [1.4 服务器推送](#1.4 服务器推送)
    • [二、使用 nghttp2 库创建一个 HTTP/2 客户端](#二、使用 nghttp2 库创建一个 HTTP/2 客户端)
    • 三、结语

一、HTTP/2 特性实现:nghttp2 源码剖析

在本节中,我们将结合 nghttp2 库的源码,了解 HTTP/2 的主要特性是如何实现的。

1.1 二进制帧

HTTP/2 使用二进制帧来传输数据,这使得数据传输更加高效和可靠。在 nghttp2 中,二进制帧的实现可以在 nghttp2_frame.c 文件中找到。每个帧由一个固定长度的帧头(9 字节)和一个可选的帧负载组成。帧头包括以下字段:长度(24位)、类型(8位)、标志(8位)、保留位(1位)和流标识符(31位)。

nghttp2 提供了一系列 API 来处理二进制帧,如 nghttp2_frame_pack() 用于将帧结构体编码为二进制数据,nghttp2_frame_unpack() 用于将二进制数据解码为帧结构体。

1.2 多路复用

HTTP/2 可以在一个连接上并行处理多个请求和响应,这大大提高了网络利用率。在 nghttp2 中,多路复用的实现可以在 nghttp2_stream.c 文件中找到。nghttp2 使用优先级队列来管理多个流,以实现多路复用。

当新的帧到达时,nghttp2 会根据帧头中的流标识符找到对应的流。然后,根据帧类型和优先级,对流进行处理。例如,数据帧会被传递给应用程序进行处理,而控制帧(如 WINDOW_UPDATE)会被用来更新流的状态。

1.3 头部压缩

HTTP/2 使用 HPACK 算法压缩头部,减少了网络传输的开销。在 nghttp2 中,头部压缩的实现可以在 nghttp2_hd.c 文件中找到。nghttp2 提供了一系列 API 来处理头部压缩,如 nghttp2_hd_deflate() 用于压缩头部,nghttp2_hd_inflate() 用于解压缩头部。

HPACK 算法使用了两种技术来压缩头部:静态表和动态表。静态表包含了常见的头部字段,动态表则在连接过程中逐渐学习头部字段。通过这两个表,HPACK 可以有效地压缩头部数据。

1.4 服务器推送

HTTP/2 允许服务器主动向客户端推送资源,提高了页面加载速度。在 nghttp2 中,服务器推送的实现可以在 nghttp2_push.c 文件中找到。服务器可以通过 nghttp2_submit_push_promise() 函数提交一个 PUSH_PROMISE 帧,告知客户端即将推送的资源。然后,服务器可以使用 nghttp2_submit_response() 函数发送推送资源的响应。

客户端可以通过设置回调函数来接收服务器推送的资源。例如,可以使用 nghttp2_session_callbacks_set_on_push_promise() 函数设置 PUSH_PROMISE 帧的回调,以及 nghttp2_session_callbacks_set_on_data_chunk_recv() 函数设置接收到推送数据的回调。

通过以上分析,我们了解了如何在 nghttp2 源码中实现 HTTP/2 的主要特性。这些特性共同让 HTTP/2 成为了一个高效、可靠的网络传输协议,为我们的 Web 开发提供了强大的支持。

二、使用 nghttp2 库创建一个 HTTP/2 客户端

下面的 C 语言示例代码演示了如何使用 nghttp2 库创建一个 HTTP/2 客户端。这个客户端会向服务器发送一个 GET 请求,打印出响应,并加入错误处理、超时、取消请求、流量控制等特性。

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <nghttp2/nghttp2.h>

#define PORT 8080
#define BUFFER_SIZE 4096

// 发送回调函数
static ssize_t send_callback(nghttp2_session *session, const uint8_t *data, size_t length, int flags, void *user_data) {
    int fd = *(int *)user_data;
    ssize_t sent = write(fd, data, length);
    if (sent < 0) {
        perror("Failed to send data");
        exit(EXIT_FAILURE);
    }
    return sent;
}

// on_frame_send_callback 函数
static int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) {
    if (frame->hd.type == NGHTTP2_DATA) {
        printf("Sent data frame\n");
    }
    return 0;
}

// on_data_chunk_recv_callback 函数
static int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags, int32_t stream_id, const uint8_t *data, size_t len, void *user_data) {
    printf("Received data chunk: %.*s\n", (int)len, data);
    // 检查是否需要流量控制
    ssize_t window_size = nghttp2_session_get_stream_remote_window_size(session, stream_id);
    if (window_size < len) {
        printf("Stream window size is too small, need to increase\n");
        nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, stream_id, len - window_size);
    }
    return 0;
}

// on_stream_close_callback 函数
static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id, uint32_t error_code, void *user_data) {
    printf("Stream %d closed with error code %d\n", stream_id, error_code);
    return 0;
}

int main(int argc, char **argv) {
    if (argc != 3) {
        fprintf(stderr, "Usage: %s <host> <path>\n", argv[0]);
        return 1;
    }

    const char *host = argv[1];
    const char *path = argv[2];

    struct addrinfo hints = {0}, *res;
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    int error = getaddrinfo(host, "80", &hints, &res);
    if (error) {
        fprintf(stderr, "Failed to resolve host: %s\n", gai_strerror(error));
        return 1;
    }

    int fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
    if (fd < 0) {
        perror("Failed to create socket");
        return 1;
    }

    if (connect(fd, res->ai_addr, res->ai_addrlen) < 0) {
        perror("Failed to connect");
        return 1;
    }

    // 设置套接字超时
    struct timeval timeout = {5, 0};  // 5 seconds
    setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));

    nghttp2_session_callbacks *callbacks;
    nghttp2_session_callbacks_new(&callbacks);
    nghttp2_session_callbacks_set_send_callback(callbacks, send_callback);
    nghttp2_session_callbacks_set_on_frame_send_callback(callbacks, on_frame_send_callback);
    nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks, on_data_chunk_recv_callback);
    nghttp2_session_callbacks_set_on_stream_close_callback(callbacks, on_stream_close_callback);

    nghttp2_session *session;
    nghttp2_session_client_new(&session, callbacks, &fd);
    nghttp2_session_callbacks_del(callbacks);

    nghttp2_nv headers[] = {
        MAKE_NV(":method", "GET"),
        MAKE_NV(":path", path),
        MAKE_NV(":scheme", "http"),
        MAKE_NV(":authority", host),
    };
    int32_t stream_id = nghttp2_submit_request(session, NULL, headers, sizeof(headers) / sizeof(headers[0]), NULL, NULL);

    char buffer[BUFFER_SIZE];
    ssize_t bytes_read;

    while ((bytes_read = read(fd, buffer, BUFFER_SIZE))```c
> 0) {
        if (nghttp2_session_mem_recv(session, (const uint8_t *)buffer, bytes_read) < 0) {
            fprintf(stderr, "Failed to process data\n");
            break;
        }
        if (nghttp2_session_send(session) < 0) {
            fprintf(stderr, "Failed to send data\n");
            break;
        }
    }

    // 取消请求
    nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, stream_id, NGHTTP2_CANCEL);

    nghttp2_session_del(session);
    close(fd);

    return 0;
}

在上述代码中,我们创建了一个 HTTP/2 客户端,这个客户端连接到服务器,发送一个 GET 请求,并打印出响应。

on_frame_send_callback 函数在每次发送帧时被调用。在这个函数中,我们打印了一条消息,表明我们发送了一个数据帧。

on_data_chunk_recv_callback 函数在每次接收数据块时被调用。在这个函数中,我们打印了接收到的数据块,并检查了流的窗口大小。如果窗口大小小于数据块的长度,我们就提交一个窗口更新帧,以增加窗口大小。这是流量控制的一种形式。

on_stream_close_callback 函数在流关闭时被调用。在这个函数中,我们打印了流的 ID 和错误代码。

在主函数中,我们增加了错误检查,以处理获取地址信息、创建套接字和连接套接字时可能出现的错误。我们还设置了套接字的接收超时。如果在指定的时间内没有接收到数据,read 函数将返回一个错误。

在读取和处理数据的循环中,我们增加了错误检查,以处理接收和发送数据时可能出现的错误。

最后,我们使用 nghttp2_submit_rst_stream 函数提交了一个 RST_STREAM 帧,以取消请求。这个帧将导致流立即关闭,任何未发送或未接收的数据都将被丢弃。

三、结语

通过本文的分析,我们深入了解了HTTP/2协议的核心特性及其在nghttp2库中的实现。这些知识将有助于我们更好地理解HTTP/2协议,为Web开发提供更高效、可靠的网络传输支持。

相关推荐
_.Switch1 小时前
高级Python自动化运维:容器安全与网络策略的深度解析
运维·网络·python·安全·自动化·devops
qq_254674411 小时前
工作流初始错误 泛微提交流程提示_泛微协同办公平台E-cology8.0版本后台维护手册(11)–系统参数设置
网络
JokerSZ.1 小时前
【基于LSM的ELF文件安全模块设计】参考
运维·网络·安全
小松学前端4 小时前
第六章 7.0 LinkList
java·开发语言·网络
城南vision4 小时前
计算机网络——TCP篇
网络·tcp/ip·计算机网络
Ciderw5 小时前
块存储、文件存储和对象存储详细介绍
网络·数据库·nvme·对象存储·存储·块存储·文件存储
石牌桥网管5 小时前
OpenSSL 生成根证书、中间证书和网站证书
网络协议·https·openssl
Tony聊跨境6 小时前
独立站SEO类型及优化:来检查这些方面你有没有落下
网络·人工智能·tcp/ip·ip
2403_875736876 小时前
道品科技智慧农业中的自动气象检测站
网络·人工智能·智慧城市