【envoy源码走读】Envoy的接收和处理数据

Envoy通过LibEvent的event_base_loop函数来处理网络事件,下游连接数据到达时,lievent库通过向assignEvents注册的回调方法来分发事件。 在connection_impl.cc文件中,事件被分发到ConnectionImpl::onFileEvent回调方法中。先处理关闭事件,然后处理写事件,最后处理读事件。

c++ 复制代码
void ConnectionImpl::onFileEvent(uint32_t events) {
    closeSocket(ConnectionEvent::RemoteClose);
    return;
  }
  if (events & Event::FileReadyType::Write) {
    onWriteReady();
  }
  if (ioHandle().isOpen() && (events & Event::FileReadyType::Read)) {
    onReadReady();
  }
}

读取数据

connection_impl.cc文件中,onReadReady方法通过doRead从socket中读取数据,每次最多读取16KB数据放入Buffer中,此处是循环去取数据的,在以下几种情况下会退出循环:

  • 读到buffer区空了,正常情况、
  • 读够limit大小的数据。默认就是1MB。需要格外关注的是,由于envoy采用了边缘触发,所以如果没有新数据进来,则无法将监听到read事件,这样可能导致数据无法被消费完。为了解决这个问题,所以会通过callbacks_->setReadBufferReady();重新触发Read事件。
  • 远端关闭连接,即连接被动关闭。
  • 发生异常

当读取的数据长度不为0或者读取完成时,进入onRead回调方法。

c++ 复制代码
  IoResult result = transport_socket_->doRead(*read_buffer_);
  if (result.bytes_processed_ != 0 || result.end_stream_read_ ||
      (latched_dispatch_buffered_data && read_buffer_->length() > 0)) {
    // Skip onRead if no bytes were processed unless we explicitly want to force onRead for
    // buffered data. For instance, skip onRead if the connection was closed without producing
    // more data.
    onRead(new_buffer_size);
  }

connection_impl.cc文件的onRead方法,调用了filter_manager_.onRead()。 在filter_manager_impl.cc文件的onRead方法,调用了onContinueReading方法,该方法在上篇创建连接的时候也用到了,当时是调用相关filter的onNewConnection回调方法。而此处因为已经初始化过了,所以进入每个filter的onData回调方法中。

c++ 复制代码
void FilterManagerImpl::onContinueReading(ActiveReadFilter* filter,
                                          ReadBufferSource& buffer_source) {

  for (; entry != upstream_filters_.end(); entry++) {
    if (!(*entry)->filter_) {
      continue;
    }
    if (!(*entry)->initialized_) {
      ... // 回调filter的onNewConnection方法
    }

    StreamBuffer read_buffer = buffer_source.getReadBuffer();
    if (read_buffer.buffer.length() > 0 || read_buffer.end_stream) {
      FilterStatus status = (*entry)->filter_->onData(read_buffer.buffer, read_buffer.end_stream);
    }
  }
}

接收数据

对于一个Sidecar来说,最核心的能力必然就是路由。没有路由,其他的功能都是枉谈。所以这边也就会引出Envoy里面最核心的一个Filter ------ 连接管理器ConnectionManagerImpl。 在conn_manager_impl.cc文件中,上述filter的onData回调方法。首先createCodec中根据监听器config配置以及收到的请求头部内容创建解码器codec,看代码此处是针对http的处理(其他协议在哪处理的todo),最终调用到了conn_manager_utility.cc的determineNextProtocol方法,根据接收到数据的起始内容确实http版本。

c++ 复制代码
std::string ConnectionManagerUtility::determineNextProtocol(Network::Connection& connection,
                                                            const Buffer::Instance& data) {
  // See if the data we have so far shows the HTTP/2 prefix. We ignore the case where someone sends
  // us the first few bytes of the HTTP/2 prefix since in all public cases we use SSL/ALPN. For
  // internal cases this should practically never happen.
  if (data.startsWith(Http2::CLIENT_MAGIC_PREFIX)) {
    return Utility::AlpnNames::get().Http2;
  }

  return "";
}

然后继续在conn_manager_impl.cc文件中,执行解码器codec的dispatcher对消息的内容进行处理(就是解码呗)。

从上图可以看到每个协议有单独实现的codec解码器,此处以http1进行分析。 在http1/codec_impl.cc文件中的dispatch方法中,调用dispatchSlice方法循环处理接收到的数据。

c++ 复制代码
void ConnectionImpl::dispatch(Buffer::Instance& data) {
  //......
if (data.length() > 0) {
    current_dispatching_buffer_ = &data;
    while (data.length() > 0) {
      // 每次去data的头部分片slice数据
      auto slice = data.frontSlice();
      dispatching_slice_already_drained_ = false;
      auto statusor_parsed = dispatchSlice(static_cast<const char*>(slice.mem_), slice.len_);
      if (!statusor_parsed.ok()) {
        return statusor_parsed.status();
      }
      if (!dispatching_slice_already_drained_) {
        ASSERT(statusor_parsed.value() <= slice.len_);
        // 清理已处理过的分片slice数据
        data.drain(statusor_parsed.value());
      }

      total_parsed += statusor_parsed.value();
    }
    current_dispatching_buffer_ = nullptr;
    dispatchBufferedBody();
  } else {
    auto result = dispatchSlice(nullptr, 0);
    if (!result.ok()) {
      return result.status();
    }
  }
}

真实解码的过程即在dispatchSlice中,如下:

c++ 复制代码
Envoy::StatusOr<size_t> ConnectionImpl::dispatchSlice(const char* slice, size_t len) {
  ASSERT(codec_status_.ok() && dispatching_);
  auto [nread, rc] = parser_->execute(slice, len);
  ...
  return nread;
}

legacy_parser_impl.cc文件中,相关execute的具体实现如下:

c++ 复制代码
  RcVal execute(const char* slice, int len) {
    return {http_parser_execute(&parser_, &settings_, slice, len), HTTP_PARSER_ERRNO(&parser_)};
  }

处理数据

此处用到了http_parser库来进行处理。我们继续看下是如何处理的: 在legacy_parser_impl.cc文件中,http_parse_execute方法会根据当前消息偏移调用http_parser_settings变量中注册的回调方法,包括:on_message_begin、on_url、on_status、on_header_field、on_header_value等等。

c++ 复制代码
settings_ = {
        [](http_parser* parser) -> int {
          auto* conn_impl = static_cast<ParserCallbacks*>(parser->data);
          auto status = conn_impl->onMessageBegin();
          return conn_impl->setAndCheckCallbackStatus(std::move(status));
        },
        [](http_parser* parser, const char* at, size_t length) -> int {
          auto* conn_impl = static_cast<ParserCallbacks*>(parser->data);
          auto status = conn_impl->onUrl(at, length);
          return conn_impl->setAndCheckCallbackStatus(std::move(status));
        },
        [](http_parser* parser, const char* at, size_t length) -> int {
          auto* conn_impl = static_cast<ParserCallbacks*>(parser->data);
          auto status = conn_impl->onStatus(at, length);
          return conn_impl->setAndCheckCallbackStatus(std::move(status));
        },
        [](http_parser* parser, const char* at, size_t length) -> int {
          auto* conn_impl = static_cast<ParserCallbacks*>(parser->data);
          auto status = conn_impl->onHeaderField(at, length);
          return conn_impl->setAndCheckCallbackStatus(std::move(status));
        },
        [](http_parser* parser, const char* at, size_t length) -> int {
          auto* conn_impl = static_cast<ParserCallbacks*>(parser->data);
          auto status = conn_impl->onHeaderValue(at, length);
          return conn_impl->setAndCheckCallbackStatus(std::move(status));
        },
        [](http_parser* parser) -> int {
          auto* conn_impl = static_cast<ParserCallbacks*>(parser->data);
          auto statusor = conn_impl->onHeadersComplete();
          return conn_impl->setAndCheckCallbackStatusOr(std::move(statusor));
        },
        [](http_parser* parser, const char* at, size_t length) -> int {
          static_cast<ParserCallbacks*>(parser->data)->bufferBody(at, length);
          return 0;
        },
        [](http_parser* parser) -> int {
          auto* conn_impl = static_cast<ParserCallbacks*>(parser->data);
          auto status = conn_impl->onMessageComplete();
          return conn_impl->setAndCheckCallbackStatusOr(std::move(status));
        },
        [](http_parser* parser) -> int {
          // A 0-byte chunk header is used to signal the end of the chunked body.
          // When this function is called, http-parser holds the size of the chunk in
          // parser->content_length. See
          // https://github.com/nodejs/http-parser/blob/v2.9.3/http_parser.h#L336
          const bool is_final_chunk = (parser->content_length == 0);
          static_cast<ParserCallbacks*>(parser->data)->onChunkHeader(is_final_chunk);
          return 0;
        },
        nullptr // on_chunk_complete
    }

onMessageBegin环节

设置一个Codec(ServerConnection)的Decoder和Encoder。这边的Encoder即为ServerConnection自己(注意,ServerConnection持有了网络层的ConnectionImpl实例,可以用以进行响应回写,后面会进一步提及),Decoder即为ActiveStream。ActiveStream会持有ServerConnection(有点绕)。 在codec_impl.cc文件的ServerConnectionImpl::onMessageBeginBase()

c++ 复制代码
Status ServerConnectionImpl::onMessageBeginBase() {
    active_request_ = std::make_unique<ActiveRequest>(*this, std::move(bytes_meter_before_stream_));
    active_request_->request_decoder_ = &callbacks_.newStream(active_request_->response_encoder_);
}

总而言之,我们记住一点,在这个阶段,会完成Downstream请求的Decoder和Encoder的初始化,并且提前塞好各种回调信息。便于在各个环节被使用。

相关推荐
海绵波波1073 小时前
flask后端开发(10):问答平台项目结构搭建
后端·python·flask
网络风云5 小时前
【魅力golang】之-反射
开发语言·后端·golang
Q_19284999065 小时前
基于Spring Boot的电影售票系统
java·spring boot·后端
运维&陈同学6 小时前
【Kibana01】企业级日志分析系统ELK之Kibana的安装与介绍
运维·后端·elk·elasticsearch·云原生·自动化·kibana·日志收集
Javatutouhouduan8 小时前
如何系统全面地自学Java语言?
java·后端·程序员·编程·架构师·自学·java八股文
后端转全栈_小伵9 小时前
MySQL外键类型与应用场景总结:优缺点一目了然
数据库·后端·sql·mysql·学习方法
编码浪子9 小时前
Springboot高并发乐观锁
后端·restful
uccs9 小时前
go 第三方库源码解读---go-errorlint
后端·go
Mr.朱鹏10 小时前
操作002:HelloWorld
java·后端·spring·rabbitmq·maven·intellij-idea·java-rabbitmq
编程洪同学11 小时前
Spring Boot 中实现自定义注解记录接口日志功能
android·java·spring boot·后端