• Proxygen http2 代码分析


    Proxygen 的整体架构
    image

    一个 HTTPSession 对应一个 tcp 连接。

    HTTPSession 中包含HTTPCodec ,HTTPCodec用来在 HTTPMessage(Request/Response) 和 字节流之间做转换(就是解析/序列化)。

    一个 HTTPTransaction 对应一个 HTTP2 的Stream ,也就是一次 Req/Resp
    Handler 是业务逻辑处理的基类。

    其中 HTTP2 部分由几个类构成:

    1. codec/HTTPCodec.h
    2. codec/HTTP2Framer.cpp
    3. codec/HTTP2Codec.cpp , HTTPParallelCodec.cpp
    4. codec/Compress/HeaderCodec.cpp,HPACKCodec.cpp
    5. session/HTTPSession.cpp
    6. session/HTTPTransacion.cpp
    7. session/HTTP2PriorityQueue.cpp

    1. HTTPCodec.h

    定义了 HTTPCodec 这个基类,定义了用来在 内部的 HTTP Request/Response 和 字节流之间做转换的公共接口,是HTTP1.1/SPDY/HTTP2 的公共基类。具体的HTTP2/HTTP1.1等各种协议的编解码,实现在 HTTP1.1/SPDY/HTTP2 等子类中。

    1) HTTPCodec

    HTTPCodec 是 HTTP1.1/ HTTP2 接口的超集,即HTTP1.1也被当成 HTTP2 实现

    i. 首先所有的 子类都要支持 StreamID,HTTP1.1 由于有KeepAlive,看成有多个Stream。
    ii. onIngress(),传入输入数据,驱动解析。
    iii. generateHeader()/ generateBody() /generateChunkHeader() /generateGoaway()/ generatePingRequest()/ generatePriority() 等,是 http1.1/http2 的超集。HTTP1xCodec.cpp 是没有实现HTTP2 的这些Frame 的generateXXX函数。HTTP1.1

    2) PriorityQueue,优先级队列。

    Session/HTTP2PriorityQueue.cpp 实现了这个接口类

    3) HTTPCodec::Callback 类

    解析的过程中,需要通知 HTTPCodec 的使用者做一些操作,因此有个 接口类 HTTPCodec::Callback,HTTPCodec 的使用者实现 Callback 的子类,传给 HTTPCodec 的子类,HTTPCodec 的子类在解析过程中解析出来的各种消息都回调这个callback。
    Callback 的主要方法有:

    • onMessageBegin()
    • onHeadersComplete() // HeaderList 接收完
    • onBody() // Body 开始
    • onMessageComplete() //Message 整个接收完
    • onWindowUpdate() // 收到 WindowUpdate Frame
    • onPriority(), //收到 Priority Frame等

    顾名思义,处理HTTPMessage 开始事件,收到各种 HTTP2 Frame 等等。

    2. HTTP2Framer.cpp

    10种Frame 的 解析/序列化 工具函数,parseXXXFrame, writeXXXFrame,最底层。供HTTP2Codec 使用。

    3. HTTP2Codec

    Framing Layer,即处理 HTTPMesage , HTTP2 Frame 和 输入/输出字节流之间的 解析/序列化。实现了HTTPParallelCodec(HTTP2和SPDY的公共基类,HTTPCodec 的子类),HeaderCodec两个接口类

    1) 输入字节流的解析的时候,做:

    i. HEADERS Frame,

    做各种 Header 的变换处理。

    ii. PRIORITY

    4. compress/HeaderCodec.cpp

    HeaderCodec 是 Header List 的编解码器。HeaderCodec解码的结果通过 HeaderCodec::StreamingCallback接口类通知给使用者(即HTTP2Codec)。HeaderCodec::StreamingCallback主要有个 onHeader(name, value) 接口(HTTP2Codec实现了)。

    5. compress/HPACKCodec.cpp

    HPACK保存 int --> string 的映射,通过发送 int, 代替 string 来压缩。

    HPACK编码,主要需要实现动态表,静态表,Huffman encoding,Header序列化/解析。

    HPACKCodec 主要有 encode(vector

    )/decode(Buffer) 两个接口。重要成员变量:

      HPACKEncoder encoder_;
       HPACKDecoder decoder_;
    

    实际的 动态表/静态表 逻辑,是一个HeaderTable,主要数据结构就是vector + ,

    HPACKDecoder 里面的动态表对应的是 HPACKContext,先 peek 一个字节,判断是否用了HPACKDecodeBuffer来解码

    Integer比特编解码,literal编解码,都实现在 HPACKDecodeBuffer / HPACKEncodeBuffer里面。

    Huffman encoding 实际使用的是 256位的trie,不是2位的,为了优化性能。

    在 HeaderTable.cpp 中,可以看到,实际的 vector 是当成一个 环形队列来用的,
    实际是 把 RFC 中规定的 [ 1 --- n ] 的区间,逆向映射到 这个环形队列中,即 index 1 是,对应到 环形队列的 尾,index n 是对应到环形队列的头。
    这样,按照 RFC,增加 Header 进来的时候,应该是加在 1 前面,并把所有的已有 Header 往后挪1个位置,实现上就可以不用做 “挪动”,而且已有的 name-->index 的映射也不用改。这是一种实现上的小技巧。

    并用 一个

    unordered_map<Header.name , list<index> > 
    

    的形式存储 Header.name 到一组 vector 中的 index 的映射,这是由于 有 相同 name ,对应多个 value 的情况,而且要支持 “查找 name+value 匹配” 和 ”查找 name 匹配“ 两种查找。

    6. session/HTTP2PriorityQueue.cpp

    每一个 Stream都 依赖一个 Stream 或者 Stream 0。
    一个 Stream A 依赖 Stream B ,那么 A 就是 B 的子节点。形成依赖树。
    Stream 0 是树的根节点。

    HTTP2PriorityQueue实现了PriorityQueue接口类,实现了 addPriorityNode(StreamID id, StreamID parent) 接口

    主要的逻辑实现在 Node这个内部类里面。

    PriorityQueue 的数据成员,主要是一个 unorder_map<streamID, Node>

    Node内部的数据成员:

    
    Node {
        HTTP2PriorityQueue& queue_;  //属于哪个 PriorityQueue
        Node *parent_{nullptr}; 		//树中的父节点
        HTTPCodec::StreamID id_{0};    //
        uint16_t weight_{16};       //权重
        HTTPTransaction *txn_{nullptr}; 
    
        bool isPermanent_{false};
        bool enqueued_{false};   //enqueued 表示在 有输出数据的队列中
    
        uint64_t totalEnqueuedWeight_{0}; 
        uint64_t totalChildWeight_{0};
    
        std::list<std::unique_ptr<Node>> children_;  // 子节点
        std::list<std::unique_ptr<Node>>::iterator self_;   
    
        folly::IntrusiveListHook enqueuedHook_;     //一个侵入式链表
        folly::IntrusiveList<Node, &Node::enqueuedHook_> enqueuedChildren_;  //
    
    }
    
    

    有输出数据等待发送 (pending Egress) 的 HTTPTransaction ,会加入

    signalPendingEgress() , 通知 HTTP2PriorityQueue 某一个 Stream 产生了输出数据。
    内部实现是把 这 Stream加入

    clearPendingEgress(), 通知一个 Stream 发完了 输出数据。

    addOrUpdatePriorityNode(),

    nextEgress() , 获取有输出数据等待发送的 HTTPTransaction 的列表,列表的每个元素是 pair< HTTPTransaction * ,double weight > ,按照 weight 从大到小排列,列表中的所有weight 加起来等于1。

    7. session/HTTPTransaction.cpp

    HTTPTransaction 表示一次 Req/Resp,HTTPTransaction 需要和 HTTPSession 交互写入 HTTPMessage 所以提出了Transport 这个概念,这样依赖关系就是单向的,没有 HTTPTransaction – HTTPSession 之间不会产生双向依赖。实际处理业务的代码,定义成一个个 Handler,需要与Handler 交互,

    1) HTTPTransaction::Transport

    是 HTTPTransaction 的下层为 HTTPTransaction 提供的输出HTTPMessage 服务。(下层具体指的是 HTTPSession)。

    Transport 的方法有:sendHeaders()/sendBody()/pauseIngress()/resumeIngress()/ sendPriority() / 等

    2) HTTPTransactionHandler

    是一个个业务Handler 的基类,Handler的方法有:onHeadersComplete()/onBody()/onChunk/onTrailers/onEgressPaused()/ onEgressResumed等,就是解析出 HTTP Message的各个部分,或者流控发现无法再写了时候的通知。

    i. HTTPTransaction::Handler 有好几种,业务Handler 比如直接生成 ErrorPage 的 HTTPDirectResponseHandler就是生成 404 页面的 Handler,在其内部,实现 onHeadersComplete 方法,在这个方法内生成 回包数据,然后调用 HTTPTransaction 的 sendHeaders/sendBody 等方法发回给客户端。

    3) HTTPTransactionIngressSM

    由于 HTTPTransaction 要调用 Handler 的好几种方法,内部要记录当前已经处理到哪一步了,不能允许在任意时机,任意来的数据都触发回调,所以搞了两个状态机,HTTPTransactionIngressSM/ HTTPTransactionEgressSM 来明确地规定 回调可以触发的顺序。

    4) HTTPTransaction 的重要成员变量:

    streamId, handler_ , transport_ ,egressState_ , ingressState_, recvWindow_ , sendWindow_, HTTP2PrioprityQueue::Handle queueHande_,

    8. session/HTTPSession.cpp

    HTTPSession 针对 HTTP2/HTTP1.1/SPDY的超集的,HTTPSession是一个 HTTPCodec::Callback(接受解析出来的各种 Frame),是 HTTPTransaction::Callback(供HTTPTransaction发送输出 HTTP Message),是 FlowControlFilter::Callback(接受输出状态打开/关闭的通知)。

    1) 重要成员变量:

    {
        folly::IOBufQueue readBuf_,
        folly::IOBufQueue writeBuf_,
        map<StreamID, HTTPTransaction>transactions_,
        HTTP2PriorityQueue queue,
        FlowControlFilter connFlowControl_,
    }			
    			
    

    2) getNextToSend()

    是做发送的,可以看到,proxygen 是按相对权重来分派带宽的。txnEgressQueue_.nextEgress() 取出当前可发送的 HTTPTransaction + 相对权重的列表,然后把可发送字节数 按照相对权重分给各个HTTPTransaction。

    9. 细节

    1) IOBufQueue

    多处用到的基础类:IOBufQueue,Zero-Copy,引用计数。一个 Bufffer Chain,设计类似 linux kernel 的sk_buff, BSD 的 mbuf 。

    https://github.com/facebook/folly/blob/master/folly/io/IOBuf.h

    
     * An IOBuf is a pointer to a buffer of data. *
     * IOBuf objects are intended to be used primarily for networking code, and are
     * modelled somewhat after FreeBSD's mbuf data structure, and Linux's sk_buff structure.
     * IOBuf objects facilitate zero-copy network programming, by allowing multiple
     * IOBuf objects to point to the same underlying buffer of data, using a
     * reference count to track when the buffer is no longer needed and can be freed.
    
    

    2) HTTPHeaders

    HTTPHeaders 有个性能优化,用了 静态完美hash函数,把常用的 83个 Header 各自唯一地hash 成1 字节,HeaderList 的查找使用 汇编实现的memchr。

    https://github.com/facebook/proxygen/blob/master/proxygen/lib/http/HTTPHeaders.h

    
    * Headers are stored as Name/Value pairs, in the order they are received on
     * the wire. We hash the names of all common HTTP headers (using a static
     * perfect hash function generated using gperf from HTTPCommonHeaders.gperf)
     * into 1-byte hashes (we call them "codes") and only store these. We search
     * them using memchr, which has an x86_64 assembly implementation with
     * complexity O(n/16) ;)
     
    
  • 相关阅读:
    国家标准比例尺地形图说明(摘要自SuperMap Objects Document)
    常用日期函数
    CMD执行BCP命令
    如何利用.snk文件生成DLL文件中的Publickeytoken
    SQL SERVER数据库的表中修改字段属性被阻止“Prevent saving changes that require table recreation”
    如何生成DLL文件
    如何反编译DLL文件
    Visual Studio 2022激活密钥
    sqlserver跨数据库查询
    jQuery对象与DOM对象之间的转换
  • 原文地址:https://www.cnblogs.com/windydays/p/12810655.html
Copyright © 2020-2023  润新知