http2.0 的tsl三次握手过程?

实际上,HTTP/2 协议本身并不直接涉及TLS握手,而是HTTP协议的一个版本,它可以运行在多种传输协议之上,其中就包括运行在TLS加密传输协议之上以实现HTTPS。当HTTP/2与TLS结合使用时,仍然是基于TLS协议进行握手,而非特定的“HTTP/2的TLS三次握手”过程。

标准的TLS握手过程通常涉及到多个消息交换,尽管有时人们可能会简化表述为“几次握手”,但准确来说,TLS握手包含了至少两次完整的消息往返(即四次网络交互),具体步骤如下:

  1. 客户端发起握手

    • 客户端发送一个ClientHello消息,包含所支持的TLS版本、加密套件列表、一个随机数以及其他可能的参数(比如用于SNI(Server Name Indication)扩展的服务器域名)。
  2. 服务器回应

    • 服务器回应一个ServerHello消息,选定一个客户端支持的TLS版本、加密套件,并提供自己的随机数,以及其数字证书(包含服务器的公钥)。
  3. 证书验证及密钥交换

    • 服务器可能还会发送一个或多个证书确认其身份,接着可能会发送一个CertificateRequest消息请求客户端证书(在需要双向认证的情况下)。
    • 服务器随后发送ServerHelloDone消息表明其握手消息已完成。
  4. 密钥协商

    • 客户端验证服务器的证书有效性,如果需要,会发送带有其证书及相关证明的Certificate消息(在客户端认证场景下)。
    • 客户端计算一个 premaster secret(预主密钥),使用服务器证书中的公钥加密该密钥,并通过ClientKeyExchange消息发送给服务器。
    • 客户端可能还会发送一个CertificateVerify消息,使用预主密钥签名一些握手数据以证明它拥有私钥对应的客户端证书。
    • 客户端和服务器都会使用这两个随机数和其他相关信息计算出会话的主密钥(master secret),进而生成对话期间使用的对称加密密钥。
  5. 完成握手

    • 客户端和服务器分别发送ChangeCipherSpec消息告知对方即将开始使用新协商的加密参数。
    • 最后,双方各自发送一个Finished消息,该消息的内容使用新协商的会话密钥加密,用来校验握手过程没有被篡改,同时也作为成功完成握手的信号。

grpc中, 如果客户端需要向服务端发送大文件, 如何实现? (使用客户端流模式)

在 gRPC 中,如果客户端需要向服务端发送大文件,可以通过使用客户端流模式(Client Streaming)来实现。客户端流模式允许客户端发送多个请求消息到服务端,服务端在接收到所有请求后才做出响应。对于大文件传输而言,可以将文件切分成多个小块,然后逐块通过客户端流的方式发送给服务端。

以下是一个简化的流程说明:

  1. 定义服务
    .proto 文件中定义一个客户端流式 RPC 方法,例如:

    syntax = "proto3";
    package file_transfer;
    
    service FileTransfer {
      rpc UploadFile(stream Chunk) returns (UploadResponse);
    }
    
    message Chunk {
      bytes data = 1; // 每个chunk的数据部分
      int64 offset = 2; // 块在文件中的偏移量
      int32 chunk_size = 3; // 当前chunk的大小
    }
    
    message UploadResponse {
      bool success = 1; // 上传是否成功
      string message = 2; // 可选的反馈信息
    }
  2. 客户端实现
    在客户端,你需要创建一个流并逐块发送文件内容:

    import (
      "context"
      "io/ioutil"
      "path/filepath"
    
      "file_transfer/pb" // 包含了上述proto文件生成的Go代码
    )
    
    func uploadFile(client pb.FileTransferClient, filePath string) (*pb.UploadResponse, error) {
      file, err := ioutil.ReadFile(filePath)
      if err != nil {
        return nil, err
      }
    
      // 创建一个新的流
      stream, err := client.UploadFile(context.Background())
      if err != nil {
        return nil, err
      }
    
      // 将文件切分成适当大小的块
      blockSize := 1024 * 1024 // 例如:每块1MB
      for i := 0; i < len(file); i += blockSize {
        end := i + blockSize
        if end > len(file) {
          end = len(file)
        }
    
        chunk := &pb.Chunk{
          Data:     file[i:end],
          Offset:   int64(i),
          ChunkSize: int32(end - i),
        }
    
        // 发送每个块
        if err := stream.Send(chunk); err != nil {
          return nil, err
        }
      }
    
      // 发送完所有块后,等待服务端的响应
      response, err := stream.CloseAndRecv()
      if err != nil {
        return nil, err
      }
    
      return response, nil
    }
  3. 服务端实现
    在服务端,你需要实现对应的方法来接收这些流式传来的文件块,并合并它们以重建整个文件:

    func (s *server) UploadFile(stream pb.FileTransfer_UploadFileServer) error {
      var totalSize int64
      var lastOffset int64
      var buffer bytes.Buffer
    
      for {
        chunk, err := stream.Recv()
        if err == io.EOF {
          // 所有块已接收,现在处理buffer中的数据
          // ...保存或处理buffer.Bytes()...
          return stream.SendAndClose(&pb.UploadResponse{Success: true})
        }
        if err != nil {
          return err
        }
    
        // 检查块的连续性
        if chunk.Offset != lastOffset {
          return status.Errorf(codes.InvalidArgument, "Invalid chunk received, expected offset %d but got %d", lastOffset, chunk.Offset)
        }
        lastOffset = chunk.Offset + int64(chunk.ChunkSize)
    
        // 将块内容追加到缓冲区
        buffer.Write(chunk.Data)
        totalSize += int64(chunk.ChunkSize)
      }
    }
最后编辑: kuteng  文档更新时间: 2024-04-02 09:53   作者:kuteng