From the public account: New World Grocery store
Read the advice
This is the second article in the HTTP2.0 series, so I recommend reading it in this order:
- HTTP Requests in Go – HTTP1.1 Request Flow Analysis
- Go Initiate HTTP2.0 Request Flow Analysis (Previous Article)
This article is divided into three parts: data frame, flow controller and understanding flow control step by step by analyzing the source code.
I intended to split these three parts into three articles, but they were related, so I finally decided to put them in one article. Because of the large content, I think it is better to read the three parts separately in three times.
Data frame
The smallest unit of HTTP2 communication is a data frame, and each frame contains two parts: the frame header and the Payload. Frames of different data streams can be sent interlaced (frames of the same data stream must be sent sequentially) and then reassembled according to the data stream identifier of each frame header.
Since Payload is valid data, only the frame header is analyzed and described.
The frame header
The total length of the frame header is 9 bytes and contains four parts:
- Payload Specifies the length of the Payload. It takes three bytes.
- Data frame type, occupying one byte.
- Data frame identifier, occupying one byte.
- Data flow ID, which takes up four bytes.
It is shown as follows:
Now that the format of the data frame and the meaning of each part are clear, let’s look at the code to read a frame header:
func http2readFrameHeader(buf []byte, r io.Reader) (http2FrameHeader, error) {
_, err := io.ReadFull(r, buf[:http2frameHeaderLen])
iferr ! =nil {
return http2FrameHeader{}, err
}
return http2FrameHeader{
Length: (uint32(buf[0]) < <16 | uint32(buf[1]) < <8 | uint32(buf[2])),
Type: http2FrameType(buf[3]),
Flags: http2Flags(buf[4]),
StreamID: binary.BigEndian.Uint32(buf[5:) & (1<<31 - 1),
valid: true,},nil
}
Copy the code
In the above code http2frameHeaderLen is a constant with a value of 9.
After reading nine bytes from IO.Reader, the first three bytes and the last four bytes are converted to the uint32 type. Then, the Payload length and data flow ID are obtained. It is also important to understand that the first three bytes and the last four bytes of the frame header are stored in the big end format.
Data frame type
According to the http2. Making. IO/http2 – spec /…
const (
http2FrameData http2FrameType = 0x0
http2FrameHeaders http2FrameType = 0x1
http2FramePriority http2FrameType = 0x2
http2FrameRSTStream http2FrameType = 0x3
http2FrameSettings http2FrameType = 0x4
http2FramePushPromise http2FrameType = 0x5
http2FramePing http2FrameType = 0x6
http2FrameGoAway http2FrameType = 0x7
http2FrameWindowUpdate http2FrameType = 0x8
http2FrameContinuation http2FrameType = 0x9
)
Copy the code
Http2FrameData: Data frames used primarily to send the request body and receive the response.
Http2FrameHeaders: Data frames used to send request headers and receive response headers.
Http2FrameSettings: Used to set up data frames for client and server interactions.
Http2FrameWindowUpdate: Data frames used primarily for flow control.
Other data frame types are not described because they are not covered in this article.
Data frame identifier
Due to the variety of data frame identifiers, I only introduce some of them here, first look at the source code:
const (
// Data Frame
http2FlagDataEndStream http2Flags = 0x1
// Headers Frame
http2FlagHeadersEndStream http2Flags = 0x1
// Settings Frame
http2FlagSettingsAck http2Flags = 0x1
// Omit the code defining other data frame identifiers here
)
Copy the code
Http2FlagDataEndStream: As mentioned in the previous article, calling the (*http2ClientConn). NewStream method creates a data stream. When this data stream ends, this is what http2FlagDataEndStream does.
When the client receives a response with a body (HEAD request does not respond with body, 301,302, etc.), An identifier of http2FlagDataEndStream reading up to http2FrameData means that the current stream can be closed at the end of the request.
Http2FrameHeaders http2FlagHeadersEndStream: if read data frame with the identifier also means the end of the request.
Http2FlagSettingsAck: This identifier confirms receipt of the http2FrameSettings data frame.
Flow controller
Flow control is a mechanism that prevents a sender from sending a large amount of data to a receiver that exceeds the latter’s needs or processing capacity. HTTP2 in Go uses the http2flow structure for flow control:
type http2flow struct {
// n is the number of DATA bytes we're allowed to send.
// A flow is kept both on a conn and a per-stream.
n int32
// conn points to the shared connection-level flow that is
// shared by all streams on that conn. It is nil for the flow
// that's on the conn directly.
conn *http2flow
}
Copy the code
The English annotation has described very clearly, so THE author will not translate. Let’s look at some of the methods associated with flow control.
(*http2flow).available
This method returns the maximum number of bytes that the current flow control can send:
func (f *http2flow) available(a) int32 {
n := f.n
iff.conn ! =nil && f.conn.n < n {
n = f.conn.n
}
return n
}
Copy the code
- if
f.conn
Nil means that the control level of this controller is connected, so the maximum number of bytes that can be sent isf.n
. - if
f.conn
Non-nil means that the control level of this controller is data stream, and the maximum number of bytes that can be sent by the current data stream cannot exceed the maximum number of bytes that can be sent by the current connection.
(*http2flow).take
This method is used to consume the number of bytes that can be sent by the current flow controller:
func (f *http2flow) take(n int32) {
if n > f.available() {
panic("internal error: took too much")
}
f.n -= n
iff.conn ! =nil {
f.conn.n -= n
}
}
Copy the code
The actual need to pass a parameter tells the current flow controller how much data it wants to send. If the size sent exceeds the size allowed by the flow controller, panic occurs. If not, the number of bytes that can be sent for the current data stream and the current connection is -n.
(*http2flow).add
This method is used to increase the maximum number of bytes a stream controller can send:
func (f *http2flow) add(n int32) bool {
sum := f.n + n
if (sum > n) == (f.n > 0) {
f.n = sum
return true
}
return false
}
Copy the code
The only caveats to the above code are that it returns false when sum exceeds the maximum positive number int32 (2^31-1).
Recall: Both the (*http2Transport).newClientconn method and the (*http2ClientConn).newstream method mentioned in the previous article initialize the size of the window that can send data through (*http2flow).add.
With the basic concepts of frame and flow controller, let’s analyze and summarize the concrete implementation of flow control with the source code.
(*http2ClientConn).readLoop
We stopped at reading loops when we analyzed (*http2Transport).newClientconn, so today we’ll start with (*http2ClientConn).readloop.
func (cc *http2ClientConn) readLoop(a) {
rl := &http2clientConnReadLoop{cc: cc}
defer rl.cleanup()
cc.readerErr = rl.run()
if ce, ok := cc.readerErr.(http2ConnectionError); ok {
cc.wmu.Lock()
cc.fr.WriteGoAway(0, http2ErrCode(ce), nil)
cc.wmu.Unlock()
}
}
Copy the code
ReadLoop (*http2clientConnReadLoop).run (*http2clientConnReadLoop).
func (rl *http2clientConnReadLoop) run(a) error {
cc := rl.cc
rl.closeWhenIdle = cc.t.disableKeepAlives() || cc.singleUse
gotReply := false // ever saw a HEADERS reply
gotSettings := false
for {
f, err := cc.fr.ReadFrame()
// Omit code here
maybeIdle := false // whether frame might transition us to idle
switch f := f.(type) {
case *http2MetaHeadersFrame:
err = rl.processHeaders(f)
maybeIdle = true
gotReply = true
case *http2DataFrame:
err = rl.processData(f)
maybeIdle = true
case *http2GoAwayFrame:
err = rl.processGoAway(f)
maybeIdle = true
case *http2RSTStreamFrame:
err = rl.processResetStream(f)
maybeIdle = true
case *http2SettingsFrame:
err = rl.processSettings(f)
case *http2PushPromiseFrame:
err = rl.processPushPromise(f)
case *http2WindowUpdateFrame:
err = rl.processWindowUpdate(f)
case *http2PingFrame:
err = rl.processPing(f)
default:
cc.logf("Transport: unhandled response frame type %T", f)
}
iferr ! =nil {
if http2VerboseLogs {
cc.vlogf("http2: Transport conn %p received error from processing frame %v: %v", cc, http2summarizeFrame(f), err)
}
return err
}
if rl.closeWhenIdle && gotReply && maybeIdle {
cc.closeIfIdle()
}
}
}
Copy the code
(*http2clientConnReadLoop).run (*http2clientConnReadLoop).run (*http2clientConnReadLoop)
Cc.fr.readframe () reads data frames according to the data frame format described earlier.
(*http2clientConnReadLoop).run (*http2clientConnReadLoop).run (*http2clientConnReadLoop).run (*http2clientConnReadLoop)
Received http2FrameSettings data frame
The read loop will read the http2FrameSettings data frame first. The (*http2clientConnReadLoop).processSettings method is called upon reading the data frame. (*http2clientConnReadLoop).processSettings contains 3 logic.
Check if it is ack information for http2FrameSettings, if it is returned, otherwise proceed.
if f.IsAck() {
if cc.wantSettingsAck {
cc.wantSettingsAck = false
return nil
}
return http2ConnectionError(http2ErrCodeProtocol)
}
Copy the code
2. Process data frames for different http2FrameSettings and modify maxConcurrentStreams etc based on messages from server.
err := f.ForeachSetting(func(s http2Setting) error {
switch s.ID {
case http2SettingMaxFrameSize:
cc.maxFrameSize = s.Val
case http2SettingMaxConcurrentStreams:
cc.maxConcurrentStreams = s.Val
case http2SettingMaxHeaderListSize:
cc.peerMaxHeaderListSize = uint64(s.Val)
case http2SettingInitialWindowSize:
if s.Val > math.MaxInt32 {
return http2ConnectionError(http2ErrCodeFlowControl)
}
delta := int32(s.Val) - int32(cc.initialWindowSize)
for _, cs := range cc.streams {
cs.flow.add(delta)
}
cc.cond.Broadcast()
cc.initialWindowSize = s.Val
default:
// TODO(bradfitz): handle more settings? SETTINGS_HEADER_TABLE_SIZE probably.
cc.vlogf("Unhandled Setting: %v", s)
}
return nil
})
Copy the code
When I received ID for http2SettingInitialWindowSize frame to adjust all the data flow in the current connection can send data window size, And change the initialWindowSize of the current connection (which is used to initialize the window size for sending data with each newly created data stream) to S.val.
3, send http2FrameSettings ACK message to the server.
cc.wmu.Lock()
defer cc.wmu.Unlock()
cc.fr.WriteSettingsAck()
cc.bw.Flush()
return cc.werr
Copy the code
Received http2WindowUpdateFrame data frame
After processing the http2FrameSettings data frame, I received the http2WindowUpdateFrame data frame. The (*http2clientConnReadLoop).processWindowUpdate method is called upon receiving this data frame:
func (rl *http2clientConnReadLoop) processWindowUpdate(f *http2WindowUpdateFrame) error {
cc := rl.cc
cs := cc.streamByID(f.StreamID, false)
iff.StreamID ! =0 && cs == nil {
return nil
}
cc.mu.Lock()
defer cc.mu.Unlock()
fl := &cc.flow
ifcs ! =nil {
fl = &cs.flow
}
if! fl.add(int32(f.Increment)) {
return http2ConnectionError(http2ErrCodeFlowControl)
}
cc.cond.Broadcast()
return nil
}
Copy the code
The above logic is mainly used to update the window size of the current connection and data flow. If StreamID in the http2WindowUpdateFrame frame is 0, the window size for sending data of the current connection is updated; otherwise, the window size for sending data of the corresponding data flow is updated.
Note: http2FrameSettings was received after an http2WindowUpdateFrame frame was received with http2FlagSettingsAck.
The (*http2Transport).NewClientConn method also sent http2FrameSettings data frames and http2WindowUpdateFrame data frames to the server.
Cc.cond. Broadcast() calls were made during both http2FrameSettings and http2WindowUpdateFrame to wake up requests to Wait for two situations:
- The current connection processing data flow has been reached
maxConcurrentStreams
(see the previous article(*http2ClientConn).awaitOpenSlotForRequest
Method analysis). - A request waiting for an update to the sendable data window because the sending data flow has reached the upper limit of the sendable data window (described later).
Received http2MetaHeadersFrame data frame
Receipt of this data frame means that a request has started receiving response data. The handler for this frame is (*http2clientConnReadLoop). ProcessHeaders:
func (rl *http2clientConnReadLoop) processHeaders(f *http2MetaHeadersFrame) error {
cc := rl.cc
cs := cc.streamByID(f.StreamID, false)
// Omit code here
res, err := rl.handleResponse(cs, f)
iferr ! =nil {
// Omit code here
cs.resc <- http2resAndError{err: err}
return nil // return nil from process* funcs to keep conn alive
}
if res == nil {
// (nil, nil) special case. See handleResponse docs.
return nil
}
cs.resTrailer = &res.Trailer
cs.resc <- http2resAndError{res: res}
return nil
}
Copy the code
Cs. resc < -http2resanderror {res: res} writes http2resAndError to the data stream. In the (*http2ClientConn).roundTrip method there is the line readLoopResCh := cs.resc.
Review: Previous article (*http2ClientConn). Point 7 of the roundTrip method can be combined with this section to form a complete request chain.
Next we examine the RL. handleResponse method.
(*http2clientConnReadLoop).handleResponse
(*http2clientConnReadLoop). The main function of handleResponse is to build a Response variable. The key steps of this function are described below.
1. Build a Response variable.
header := make(Header)
res := &Response{
Proto: "HTTP / 2.0",
ProtoMajor: 2,
Header: header,
StatusCode: statusCode,
Status: status + "" + StatusText(statusCode),
}
Copy the code
2. Build the header (this article will not expand the header).
for _, hf := range f.RegularFields() {
key := CanonicalHeaderKey(hf.Name)
if key == "Trailer" {
t := res.Trailer
if t == nil {
t = make(Header)
res.Trailer = t
}
http2foreachHeaderElement(hf.Value, func(v string) {
t[CanonicalHeaderKey(v)] = nil})}else {
header[key] = append(header[key], hf.Value)
}
}
Copy the code
3. Handle the ContentLength of the response body.
streamEnded := f.StreamEnded()
isHead := cs.req.Method == "HEAD"
if! streamEnded || isHead { res.ContentLength =- 1
if clens := res.Header["Content-Length"]; len(clens) == 1 {
if clen64, err := strconv.ParseInt(clens[0].10.64); err == nil {
res.ContentLength = clen64
} else {
// TODO: care? unlike http/1, it won't mess up our framing, so it's
// more safe smuggling-wise to ignore.}}else if len(clens) > 1 {
// TODO: care? unlike http/1, it won't mess up our framing, so it's
// more safe smuggling-wise to ignore.}}Copy the code
The current data stream does not end or the ContentLength is read by the HEAD request. If the ContentLength in the header does not comply with the rule res.ContentLength is -1.
4. Build res.body.
cs.bufPipe = http2pipe{b: &http2dataBuffer{expected: res.ContentLength}}
cs.bytesRemain = res.ContentLength
res.Body = http2transportResponseBody{cs}
go cs.awaitRequestCancel(cs.req)
if cs.requestedGzip && res.Header.Get("Content-Encoding") = ="gzip" {
res.Header.Del("Content-Encoding")
res.Header.Del("Content-Length")
res.ContentLength = - 1
res.Body = &http2gzipReader{body: res.Body}
res.Uncompressed = true
}
Copy the code
Depending on how content-encoding is encoded, two different bodies are built:
- For non-GZIP encoding, the constructed res.body type is
http2transportResponseBody
. - When gzip is encoded, the constructed res.body type is
http2gzipReader
.
Received http2DataFrame data frame
Receiving this data frame means that we are beginning to receive the actual response, the business data that we need to process in normal development. The corresponding handler for this data frame is (*http2clientConnReadLoop).processData.
Because the server cannot know the status of the data stream on the client, the server may send data to a data stream that no longer exists in the client:
cc := rl.cc
cs := cc.streamByID(f.StreamID, f.StreamEnded())
data := f.Data()
if cs == nil {
cc.mu.Lock()
neverSent := cc.nextStreamID
cc.mu.Unlock()
// Omit code here
if f.Length > 0 {
cc.mu.Lock()
cc.inflow.add(int32(f.Length))
cc.mu.Unlock()
cc.wmu.Lock()
cc.fr.WriteWindowUpdate(0.uint32(f.Length))
cc.bw.Flush()
cc.wmu.Unlock()
}
return nil
}
Copy the code
The received data frame increases the size of the current connection readable window by F. length via the flow controller, and the server is told to increase the size of the current connection writable window by F. length via the http2FrameWindowUpdate data frame.
If client has a corresponding data stream and f.length is greater than 0:
1. If the head request ends the current data stream and returns.
if cs.req.Method == "HEAD" && len(data) > 0 {
cc.logf("protocol error: received DATA on a HEAD request")
rl.endStreamError(cs, http2StreamError{
StreamID: f.StreamID,
Code: http2ErrCodeProtocol,
})
return nil
}
Copy the code
2. Check whether the current data flow can process data of f. length.
cc.mu.Lock()
if cs.inflow.available() >= int32(f.Length) {
cs.inflow.take(int32(f.Length))
} else {
cc.mu.Unlock()
return http2ConnectionError(http2ErrCodeFlowControl)
}
Copy the code
(2) If the current data flow can process the data, the flow controller can call cs.inflow. Take to reduce the acceptable window size of the current data flow.
3. The flow control window needs to be adjusted when the current data stream is reset or closed, i.e. cs.didReset is true or data frame is filled with data.
var refund int
if pad := int(f.Length) - len(data); pad > 0 {
refund += pad
}
// Return len(data) now if the stream is already closed,
// since data will never be read.
didReset := cs.didReset
if didReset {
refund += len(data)
}
if refund > 0 {
cc.inflow.add(int32(refund))
cc.wmu.Lock()
cc.fr.WriteWindowUpdate(0.uint32(refund))
if! didReset { cs.inflow.add(int32(refund))
cc.fr.WriteWindowUpdate(cs.ID, uint32(refund))
}
cc.bw.Flush()
cc.wmu.Unlock()
}
cc.mu.Unlock()
Copy the code
- If the data frame has padding data, calculate the length of padding data to return.
- If the data stream is invalid, the length of the data frame must be returned in full.
Finally, refund increases the acceptable window size for the current connection or data stream based on the calculation, and tells the server to increase the writable window size for the current connection or data stream.
4. If the data length is greater than 0 and the data flow is normal, the data will be written to the data flow buffer.
if len(data) > 0 && !didReset {
if_, err := cs.bufPipe.Write(data); err ! =nil {
rl.endStreamError(cs, err)
return err
}
}
Copy the code
Review: (* http2clientConnReadLoop) in front of handleResponse methods. There is one line of code that res Body = http2transportResponseBody} {cs, Therefore, buffer data can be read in the data stream through Response during business development.
(http2transportResponseBody).Read
In the previous section, if the data flow is healthy and the data frame is not filled with data, the acceptable window for the data flow and connection will always be smaller. This part is to increase the acceptable window size for the data flow.
Due to the problem of length and theme, the author only analyzes and describes the part of the method related to flow control.
1. After reading the response data, calculate the acceptable window size that the current connection needs to increase.
cc.mu.Lock()
defer cc.mu.Unlock()
var connAdd, streamAdd int32
// Check the conn-level first, before the stream-level.
if v := cc.inflow.available(); v < http2transportDefaultConnFlow/2 {
connAdd = http2transportDefaultConnFlow - v
cc.inflow.add(connAdd)
}
Copy the code
If the current connection is less than the size of the acceptable window http2transportDefaultConnFlow half (1 g), The current connection can receive window size need to increase the http2transportDefaultConnFlow – cc. Inflow. The available ().
Review: Http2Transport http2transportDefaultConnFlow in the previous article (*). NewClientConn method section have mentioned, And just connected when server via http2WindowUpdateFrame data frames to the current connection can increase the http2transportDefaultConnFlow send window size.
2. After reading the response data, calculate the acceptable window size that the current data flow needs to increase.
if err == nil { // No need to refresh if the stream is over or failed.
// Consider any buffered body data (read from the conn but not
// consumed by the client) when computing flow control for this
// stream.
v := int(cs.inflow.available()) + cs.bufPipe.Len()
if v < http2transportDefaultStreamFlow-http2transportDefaultStreamMinRefresh {
streamAdd = int32(http2transportDefaultStreamFlow - v)
cs.inflow.add(streamAdd)
}
}
Copy the code
If the current window size, combined with the current data stream acceptable remaining unread data is less than the length of the data stream buffer http2transportDefaultStreamFlow – http2transportDefaultStreamMinRefresh (4 m – 4 KB). The window size is the current data stream acceptable need to increase the http2transportDefaultStreamFlow – v.
Review: http2transportDefaultStreamFlow in the previous article (* http2Transport) NewClientConn method and (* http2ClientConn) newStream methods are mentioned.
Just connected, send http2FrameSettings data frames, inform each data stream server can send window size of http2transportDefaultStreamFlow.
When the newStream, data flow can receive window size by default for http2transportDefaultStreamFlow.
The http2WindowUpdateFrame data frame informs the server of the window size to be increased for the connection and data stream, respectively.
ifconnAdd ! =0|| streamAdd ! =0 {
cc.wmu.Lock()
defer cc.wmu.Unlock()
ifconnAdd ! =0 {
cc.fr.WriteWindowUpdate(0, http2mustUint31(connAdd))
}
ifstreamAdd ! =0 {
cc.fr.WriteWindowUpdate(cs.ID, http2mustUint31(streamAdd))
}
cc.bw.Flush()
}
Copy the code
This is the flow control logic used by the server to send data to the client.
(*http2clientStream).writeRequestBody
Equestbody of (*http2clientStream). WriteRequestBody is raised by roundTrip (*http2clientStream).
func (cs *http2clientStream) writeRequestBody(body io.Reader, bodyCloser io.Closer) (err error) {
cc := cs.cc
sentEnd := false // whether we sent the final DATA frame w/ END_STREAM
// Omit code herereq := cs.req hasTrailers := req.Trailer ! =nilremainLen := http2actualContentLength(req) hasContentLen := remainLen ! =- 1
var sawEOF bool
for! sawEOF { n, err := body.Read(buf[:len(buf)- 1])
// Omit code here
remain := buf[:n]
for len(remain) > 0 && err == nil {
var allowed int32
allowed, err = cs.awaitFlowControl(len(remain))
switch {
case err == http2errStopReqBodyWrite:
return err
case err == http2errStopReqBodyWriteAndCancel:
cc.writeStreamReset(cs.ID, http2ErrCodeCancel, nil)
return err
caseerr ! =nil:
return err
}
cc.wmu.Lock()
data := remain[:allowed]
remain = remain[allowed:]
sentEnd = sawEOF && len(remain) == 0 && !hasTrailers
err = cc.fr.WriteData(cs.ID, sentEnd, data)
if err == nil {
err = cc.bw.Flush()
}
cc.wmu.Unlock()
}
iferr ! =nil {
return err
}
}
// Omit code here
return err
}
Copy the code
The logic can be summarized as follows: Keep reading the request body and sending it to the server via cc.fr.WriteData to http2FrameData until the request body is finished reading. One method related to flow control is awaitFlowControl, which is analyzed below.
(*http2clientStream).awaitFlowControl
The main function of this method is to wait for the current data stream writable window to have capacity to write data.
func (cs *http2clientStream) awaitFlowControl(maxBytes int) (taken int32, err error) {
cc := cs.cc
cc.mu.Lock()
defer cc.mu.Unlock()
for {
if cc.closed {
return 0, http2errClientConnClosed
}
ifcs.stopReqBody ! =nil {
return 0, cs.stopReqBody
}
iferr := cs.checkResetOrDone(); err ! =nil {
return 0, err
}
if a := cs.flow.available(); a > 0 {
take := a
if int(take) > maxBytes {
take = int32(maxBytes) // can't truncate int; take is int32
}
if take > int32(cc.maxFrameSize) {
take = int32(cc.maxFrameSize)
}
cs.flow.take(take)
return take, nil
}
cc.cond.Wait()
}
}
Copy the code
If the data stream is closed or stops sending the request body, the current data stream cannot write data. When the data flow status is normal, it is divided into two situations:
- If the remaining writable data of the current data flow writable window is greater than 0, the number of writable sections is calculated and the size of the current data flow writable window is consumed
take
. - If the remaining writable data of the current data flow writable window is less than or equal to 0, the system waits until it is woken up and enters the next check.
The second case above is mentioned in the section on receiving an http2WindowUpdateFrame data frame.
After the server reads the data in the current data stream, it sends the http2WindowUpdateFrame data frame to the corresponding data stream of the client. After receiving the data frame, the client increases the writable window of the corresponding data stream. Run the cc.cond.Broadcast() command to wake up the data streams that are waiting to send data because the flow control upper limit has been reached.
This is the flow control logic for the client to send data to the server.
conclusion
- The frame header is 9 bytes long and contains four parts: the length of the Payload, the frame type, the frame identifier, and the data stream ID.
- Flow control can be divided into two steps:
- At the beginning, pass
http2FrameSettings
And a data framehttp2WindowUpdateFrame
The data frame informs the other party of the current connection read/write window size and the data flow read/write window size in the connection. - In the process of reading or writing data, by sending
http2WindowUpdateFrame
The data frame controls the size of the write window at the other end.
- At the beginning, pass
trailer
With the previous and middle articles completed, the next installment will examine http2.0 header compression.
Finally, I sincerely hope that this article can be of some help to all readers.
Note: When writing this article, the author used the GO version: GO 1.14.2