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:

  1. HTTP Requests in Go – HTTP1.1 Request Flow Analysis
  2. 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:

  1. Payload Specifies the length of the Payload. It takes three bytes.
  2. Data frame type, occupying one byte.
  3. Data frame identifier, occupying one byte.
  4. 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
  • iff.connNil means that the control level of this controller is connected, so the maximum number of bytes that can be sent isf.n.
  • iff.connNon-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:

  1. The current connection processing data flow has been reachedmaxConcurrentStreams(see the previous article(*http2ClientConn).awaitOpenSlotForRequestMethod analysis).
  2. 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:

  1. For non-GZIP encoding, the constructed res.body type ishttp2transportResponseBody.
  2. When gzip is encoded, the constructed res.body type ishttp2gzipReader.

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:

  1. 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 consumedtake.
  2. 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

  1. 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.
  2. Flow control can be divided into two steps:
    • At the beginning, passhttp2FrameSettingsAnd a data framehttp2WindowUpdateFrameThe 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 sendinghttp2WindowUpdateFrameThe data frame controls the size of the write window at the other end.

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