http2.0

172 阅读4分钟

kiosk007.top/post/http-2… www.cnblogs.com/jiujuan/p/1…

  • 消息 message:消息由一个或多个帧组成。例如请求的消息和响应的消息。一个消息包含消息头和消息体
  • 帧 frame:HTTP2 中最小通信数据单元,每个帧至少包含了一个标识(stream identifier,简称stream id)该帧所属的流。
  • 流是一个逻辑上的概念,它包含了几个特点,第一:是可以双向传输。第二:单个流是有序的,并且传递的帧都包含了一个流ID。第三:流就是一系列的帧组成的
  • 连接:一个http2.0建立的连接,可以跑多个流

image.png image.png 帧的定义 image.png

HTTP Frame {
  Length (24),
  Type (8),

  Flags (8),

  Reserved (1),
  Stream Identifier (31),

  Frame Payload (..),
}

(来自:httpwg.org/specs/rfc91… 帧的类型

  • DATA 帧
DATA Frame { 
    Length (24), 
    Type (8) = 0x00, 
    Unused Flags (4), 
    PADDED Flag (1), 
    Unused Flags (2),
    END_STREAM Flag (1), 
    Reserved (1), 
    Stream Identifier (31), 
    [Pad Length (8)], 
    Data (..), 
    Padding (..2040), 
}
  • header 帧
HEADERS Frame { 
    Length (24), 
    Type (8) = 0x01, 
    Unused Flags (2), 
    PRIORITY Flag (1), 
    Unused Flag (1), 
    PADDED Flag (1), 
    END_HEADERS Flag (1), 
    Unused Flag (1), 
    END_STREAM Flag (1), 
    Reserved (1), 
    Stream Identifier (31), 
    ......
}

多路复用 http1.1和http2.0的区别 image.png 从流的角度看多路复用 image.png 流的定义

  • 头部的特性
    • 头部压缩
      使用静态字典表 image.png 比如数字8代表状态是200
      如果头部在静态表里面没有,则更新静态表,并且发送一份给客户端 \ 静态表压缩之后 image.png
  • 消息body
    • 二进制
  • 传输过程
    • 加密
  • 多路复用
    • 一个tcp连接上可以处理多个请求,多个请求的数据流跑在上面。http1.0有对头阻塞的概念,就算是有pipeline的技术,对于对头的返回阻塞也无能为力
  • 流的方向
    • 流是双向的,可以是客户端发起的,也可以是服务端发起的。客户端的流id是奇数,服务端的流id是偶数

拥有相同的流标识符的帧属于同一个流,它是有序的。 帧的头部和发送帧的golang代码 在 HTTP2 的golang实现中可以看到Framer这个结构体,这个结构体规定了帧的读取和发送。在创建新的 ClientConn 时,会对一个ClientConn 通过调用 cc.fr = NewFramer(cc.bw, cc.br) 创建一个 Framer ,并且将整个 net.Conn 当做了 io.Reader 和 io.Writer 参数传给了 Framer 。 在Framer内部,会维护一个 wbuf 的字节队列,新写入的 Frame  会源源不断的写入到队列中。每写入的Frame的所携带的 Stream ID 并不一样。但是相同 Stream ID 会严格保持顺序。

// A Framer reads and writes Frames.
type Framer struct {
    r    io.Reader
    w    io.Writer
	wbuf []byte
    ...
}
func (t *Transport) newClientConn(c net.Conn, singleUse bool, hooks *testSyncHooks) (*ClientConn, error) {
   cc := &ClientConn{
      t:                     t,
      tconn:                 c,
      readerDone:            make(chan struct{}),
      nextStreamID:          1,
      maxFrameSize:          16 << 10,                    // spec default
      initialWindowSize:     65535,                       // spec default
      maxConcurrentStreams:  initialMaxConcurrentStreams, // "infinite", per spec. Use a smaller value until we have received server settings.
      peerMaxHeaderListSize: 0xffffffffffffffff,          // "infinite", per spec. Use 2^64-1 instead.
      streams:               make(map[uint32]*clientStream),
      singleUse:             singleUse,
      wantSettingsAck:       true,
      pings:                 make(map[[8]byte]chan struct{}),
      reqHeaderMu:           make(chan struct{}, 1),
      syncHooks:             hooks,
   }
   if hooks != nil {
      hooks.newclientconn(cc)
      c = cc.tconn
   }
   if d := t.idleConnTimeout(); d != 0 {
      cc.idleTimeout = d
      cc.idleTimer = cc.afterFunc(d, cc.onIdleTimeout)
   }
   if VerboseLogs {
      t.vlogf("http2: Transport creating client conn %p to %v", cc, c.RemoteAddr())
   }

   cc.cond = sync.NewCond(&cc.mu)
   cc.flow.add(int32(initialWindowSize))

   // TODO: adjust this writer size to account for frame size +
   // MTU + crypto/tls record padding.
   cc.bw = bufio.NewWriter(stickyErrWriter{
      conn:    c,
      timeout: t.WriteByteTimeout,
      err:     &cc.werr,
   })
   cc.br = bufio.NewReader(c)
   cc.fr = NewFramer(cc.bw, cc.br)
   ------忽略中间代码--------
   cc.bw.Write(clientPreface)
   cc.fr.WriteSettings(initialSettings...)
   cc.fr.WriteWindowUpdate(0, transportDefaultConnFlow)
   cc.inflow.init(transportDefaultConnFlow + initialWindowSize)
   cc.bw.Flush()
   if cc.werr != nil {
      cc.Close()
      return nil, cc.werr
   }

   cc.goRun(cc.readLoop)
   return cc, nil
}

cc.fr = NewFramer(cc.bw, cc.br)上有两个连接,一个是读,一个是写 cc.fr.WriteSettings(initialSettings...)是写头部的帧

// WriteSettings writes a SETTINGS frame with zero or more settings
// specified and the ACK bit not set.
//
// It will perform exactly one Write to the underlying Writer.
// It is the caller's responsibility to not call other Write methods concurrently.
func (f *Framer) WriteSettings(settings ...Setting) error {
	f.startWrite(FrameSettings, 0, 0)
	for _, s := range settings {
		f.writeUint16(uint16(s.ID))
		f.writeUint32(s.Val)
	}
	return f.endWrite()
}

func (f *Framer) startWrite(ftype FrameType, flags Flags, streamID uint32) {
	// Write the FrameHeader.
	f.wbuf = append(f.wbuf[:0],
		0, // 3 bytes of length, filled in in endWrite
		0,
		0,
		byte(ftype),
		byte(flags),
		byte(streamID>>24),
		byte(streamID>>16),
		byte(streamID>>8),
		byte(streamID))
}

func (f *Framer) endWrite() error {
	// Now that we know the final size, fill in the FrameHeader in
	// the space previously reserved for it. Abuse append.
	length := len(f.wbuf) - frameHeaderLen
	if length >= (1 << 24) {
		return ErrFrameTooLarge
	}
	_ = append(f.wbuf[:0],
		byte(length>>16),
		byte(length>>8),
		byte(length))
	if f.logWrites {
		f.logWrite()
	}

	n, err := f.w.Write(f.wbuf)
	if err == nil && n != len(f.wbuf) {
		err = io.ErrShortWrite
	}
	return err
}
  • Data Frame的发送:
// startWriteDataPadded is WriteDataPadded, but only writes the frame to the Framer's internal buffer.
// The caller should call endWrite to flush the frame to the underlying writer.
func (f *Framer) startWriteDataPadded(streamID uint32, endStream bool, data, pad []byte) error {
   if !validStreamID(streamID) && !f.AllowIllegalWrites {
      return errStreamID
   }
   if len(pad) > 0 {
      if len(pad) > 255 {
         return errPadLength
      }
      if !f.AllowIllegalWrites {
         for _, b := range pad {
            if b != 0 {
               // "Padding octets MUST be set to zero when sending."
               return errPadBytes
            }
         }
      }
   }
   var flags Flags
   if endStream {
      flags |= FlagDataEndStream
   }
   if pad != nil {
      flags |= FlagDataPadded
   }
   f.startWrite(FrameData, flags, streamID)
   if pad != nil {
      f.wbuf = append(f.wbuf, byte(len(pad)))
   }
   f.wbuf = append(f.wbuf, data...)
   f.wbuf = append(f.wbuf, pad...)
   return nil
}
  • 流控
    • 流量控制机制的一个重要目的是防止接收端缓冲区溢出。如果发送端发送的数据超过了接收端设置的流量控制窗口大小,那么接收端可能会丢弃这些数据或采取其他措施来防止溢出。
    • tcp的流控和http2.0的流控的区别是,tcp的流控指的是单个连接上的,而http2.0的流控指的具体是连接上的流的控制
    • HTTP2 一般 Connection 和 Stream 的初始 flow-control window 大小都是 65535 bytes