Envoy 源码分析--network
Envoy 源码分析--network
申明:本文的 Envoy 源码分析基于 Envoy1.10.0。
Envoy
的服务是通用服务,因此它需要支持 TCP
和 UDP
,同时还需支持 IPV4
和 IPV6
两种网络协议,所以网络模块有点复杂。本次分析的网络模块是底层的模块,没有一整个服务的启动流程,有的地方可能还串不起来。现在先来看下UML类图:
类图看上去略显复杂,主要分为4块:addres
,socket
,listen
和 connection
。
address
是地址相关的,主要包括IPV4
,IPV6
,PIPE
,DNS
和cidr
。socket
是socket
相关的操作,主要包括ListenSocket
,ConnectionSocket
,TransportSocket
以及option
。listen
是网络监听操作,包括TCP
监听和UDP
监听。connection
是连接相关操作。关于 L3/4 过滤的这次暂时不分析,后续再讲。
address
InstanceBase
继承自 Instance
是所有地址类型的基类。Ipv4Instance
,Ipv6Instance
和 PipeInstance
三个地址类都是继承 InstanceBase
。 DNS
解析类使用 c-ares
库,DnsResolverImpl
只是对 c-ares
的进一步封装。 CidrRange
是对 cidr 操作相关。
系统操作返回的值和错误信息封装成一个公用的结构体。具体如下:
template <typename T> struct SysCallResult {
//系统返回值
T rc_;
//系统返回的错误信息
int errno_;
};
Instance
Ipv4Instance
,Ipv6Instance
和 PipeInstance
三个地址类都是继承 InstanceBase
。它们的实现基本都差不多,socket()
、bind()
和 connect()
这三个基础操作都属于它们的成员。现在我们主要来看下 Ipv4Instance
几个主要的操作(其它两个类类似就不再分析)。
Ipv4Instance
的类里有个私有结构体 IpHelper
。这结构体封装着 IPV4
地址的具体内容,比如端口,版本等
struct IpHelper : public Ip {
const std::string& addressAsString() const override { return friendly_address_; }
bool isAnyAddress() const override { return ipv4_.address_.sin_addr.s_addr == INADDR_ANY; }
bool isUnicastAddress() const override {
return !isAnyAddress() && (ipv4_.address_.sin_addr.s_addr != INADDR_BROADCAST) &&
// inlined IN_MULTICAST() to avoid byte swapping
!((ipv4_.address_.sin_addr.s_addr & htonl(0xf0000000)) == htonl(0xe0000000));
}
const Ipv4* ipv4() const override { return &ipv4_; }
const Ipv6* ipv6() const override { return nullptr; }
uint32_t port() const override { return ntohs(ipv4_.address_.sin_port); }
IpVersion version() const override { return IpVersion::v4; }
Ipv4Helper ipv4_;
std::string friendly_address_;
};
bind()
,socket()
和 connect()
基本都是直接调的底层函数。
Api::SysCallIntResult Ipv6Instance::bind(int fd) const {
const int rc = ::bind(fd, reinterpret_cast<const sockaddr*>(&ip_.ipv6_.address_),
sizeof(ip_.ipv6_.address_));
return {rc, errno};
}
Api::SysCallIntResult Ipv6Instance::connect(int fd) const {
const int rc = ::connect(fd, reinterpret_cast<const sockaddr*>(&ip_.ipv6_.address_),
sizeof(ip_.ipv6_.address_));
return {rc, errno};
}
DNS
DNS
使用 c-ares 作为底层库。 c-ares
是个 c 实现的异步 DNS 解析库,很多知名软件(curl,Nodejs,gevent 等)都使用了该库。
c-ares
在构造函数内初始化库,初始化上下文,然后设置 DNS 服务器。
DnsResolverImpl::DnsResolverImpl(
Event::Dispatcher& dispatcher,
const std::vector<Network::Address::InstanceConstSharedPtr>& resolvers)
: dispatcher_(dispatcher),
timer_(dispatcher.createTimer([this] { onEventCallback(ARES_SOCKET_BAD, 0); })) {
//初始化库
ares_library_init(ARES_LIB_INIT_ALL);
ares_options options;
//初始化上下文
initializeChannel(&options, 0);
... ...
const std::string resolvers_csv = StringUtil::join(resolver_addrs, ",");
//设置 DNS 服务器
int result = ares_set_servers_ports_csv(channel_, resolvers_csv.c_str());
}
使用时直接 resolve()
结果返回在 callback 里。
ActiveDnsQuery* DnsResolverImpl::resolve(const std::string& dns_name,
DnsLookupFamily dns_lookup_family, ResolveCb callback) {
... ...
if (dns_lookup_family == DnsLookupFamily::V4Only) {
pending_resolution->getHostByName(AF_INET);
} else {
pending_resolution->getHostByName(AF_INET6);
}
... ...
}
void DnsResolverImpl::PendingResolution::getHostByName(int family) {
ares_gethostbyname(channel_, dns_name_.c_str(), family,
[](void* arg, int status, int timeouts, hostent* hostent) {
static_cast<PendingResolution*>(arg)->onAresHostCallback(status, timeouts, hostent);
},
this);
}
void DnsResolverImpl::PendingResolution::onAresHostCallback(int status, int timeouts, hostent* hostent) {
... ...
//解析内容加入address_list
std::list<Address::InstanceConstSharedPtr> address_list;
if (status == ARES_SUCCESS) {
if (hostent->h_addrtype == AF_INET) {
for (int i = 0; hostent->h_addr_list[i] != nullptr; ++i) {
ASSERT(hostent->h_length == sizeof(in_addr));
sockaddr_in address;
memset(&address, 0, sizeof(address));
address.sin_family = AF_INET;
address.sin_port = 0;
address.sin_addr = *reinterpret_cast<in_addr*>(hostent->h_addr_list[i]);
address_list.emplace_back(new Address::Ipv4Instance(&address));
}
... ...
}
if (completed_) {
if (!cancelled_) {
try {
//调用回调
callback_(std::move(address_list));
} catch (const EnvoyException& e) {
... ...
}
cidr
cidr
的定义是形如 192.168.0.1/24 的 IP 段。想知道具体的定义和 IP 段 可看 cidr。
CidrRange
将 cidr
拆分成两字段地址和长度。下面是判断地址是否属于这个 IP 段。
bool CidrRange::isInRange(const Instance& address) const {
... ...
//长度为0,全匹配(length_初始值为-1)
if (length_ == 0) {
return true;
}
switch (address.ip()->version()) {
case IpVersion::v4:
if (ntohl(address.ip()->ipv4()->address()) >> (32 - length_) ==
ntohl(address_->ip()->ipv4()->address()) >> (32 - length_)) {
return true;
}
break;
case IpVersion::v6:
if ((Utility::Ip6ntohl(address_->ip()->ipv6()->address()) >> (128 - length_)) ==
(Utility::Ip6ntohl(address.ip()->ipv6()->address()) >> (128 - length_))) {
return true;
}
break;
}
return false;
}
socket
我们都知道,创建 TCP 服务时,监听的 fd 和连接的 fd 是不一样的,因此 socket
分为 ListenSocket
和 ConnectionSocket
。socket
里有很多的配置(比如读超时,写超时等)都是调用setsockopt
,所有需要一个 Option
来进行统一的封装。
Option
Option
是对 setsockopt
这个函数操作的封装。封装后再用智能指针的方式进行操作。
typedef std::shared_ptr<const Option> OptionConstSharedPtr;
typedef std::vector<OptionConstSharedPtr> Options;
typedef std::shared_ptr<Options> OptionsSharedPtr;
Option
在全部设置完后,在 applyOptions
后,最终还是调用 setsockopt
。
static bool applyOptions(const OptionsSharedPtr& options, Socket& socket,
envoy::api::v2::core::SocketOption::SocketState state) {
if (options == nullptr) {
return true;
}
for (const auto& option : *options) {
//对所有的option 进行设置
if (!option->setOption(socket, state)) {
return false;
}
}
return true;
}
bool SocketOptionImpl::setOption(Socket& socket,
envoy::api::v2::core::SocketOption::SocketState state) const {
if (in_state_ == state) {
//调用成员函数 setSocketOption
const Api::SysCallIntResult result = SocketOptionImpl::setSocketOption(socket, optname_, value_);
... ...
return true;
}
Api::SysCallIntResult SocketOptionImpl::setSocketOption(Socket& socket, Network::SocketOptionName optname, const absl::string_view value) {
... ...
//最终调用系统函数setsockopt
return os_syscalls.setsockopt(socket.ioHandle().fd(), optname.value().first,
optname.value().second, value.data(), value.size());
}
Socket
Socket
提供基本的 socket 操作。主要是 'Option' 操作(上面已分析过)和地址操作。代码比较简单。
//设置和获取本地地址
const Address::InstanceConstSharedPtr& localAddress() const override { return local_address_; }
void setLocalAddress(const Address::InstanceConstSharedPtr& local_address) override {
local_address_ = local_address;
}
ListenSocket
ListenSocket
是对监听 fd 的封装,继承自 Socket
。主要操作自然就是 bind()
。bind 调用自地址类的 bind() 函数(看上面的 address)。
void ListenSocketImpl::doBind() {
// 地址和handle 继承自socket。调用地址类的 bind。
const Api::SysCallIntResult result = local_address_->bind(io_handle_->fd());
if (result.rc_ == -1) {
close();
throw SocketBindException(
fmt::format("cannot bind '{}': {}", local_address_->asString(), strerror(result.errno_)),
result.errno_);
}
if (local_address_->type() == Address::Type::Ip && local_address_->ip()->port() == 0) {
// If the port we bind is zero, then the OS will pick a free port for us (assuming there are
// any), and we need to find out the port number that the OS picked.
local_address_ = Address::addressFromFd(io_handle_->fd());
}
ConnectionSocket
ConnectionSocket
是对连接 fd 的封装,除了 Socket
的基本操作外,还增加对远程地址和协议的设置。
//设置和获取远程地址
const Address::InstanceConstSharedPtr& remoteAddress() const override { return remote_address_; }
void setRemoteAddress(const Address::InstanceConstSharedPtr& remote_address) override {
remote_address_ = remote_address;
}
//协议相关
void setDetectedTransportProtocol(absl::string_view protocol) override {
transport_protocol_ = std::string(protocol);
}
absl::string_view detectedTransportProtocol() const override { return transport_protocol_; }
TransportSocket
TransportSocket
是一个实际读/写的传输套接字。它可以对数据进行一些转换(比如TLS,TCP代理等)。 TransportSocket
提供了多个接口。
failureReason()
返回最后的一个错误,没错误返回空值。
canFlushClose()
socket 是否能刷新和关闭。
closeSocket()
关闭 socket。
doRead()
读取数据。
doWrite()
写数据。
onConnected()
transport 连接时调用此函数。
Ssl::ConnectionInfo* ssl()
Ssl连接数据。
listen
listen
是对监听操作相关的类,分为 TcpListen
和 UdpListen
。 Listen 抽象类只提供两个接口 disable
和 enable
。 disable
关闭接受新连接,enable
开启接受新连接。
ListenerImpl
实现那两接口的同时,由于它是 TCP 的监听必然就有 listen 和 accept 操作。在构造函数时,调用 setupServerSocket 创造 listen,启用回调
void ListenerImpl::
setupServerSocket(Event::DispatcherImpl& dispatcher, Socket& socket) {
//创建监听,完成后回调 listenCallback
listener_.reset(
evconnlistener_new(&dispatcher.base(), listenCallback, this, 0, -1, socket.ioHandle().fd()));
... ...
//失败回调errorCallback
evconnlistener_set_error_cb(listener_.get(), errorCallback);
}
监听完成后,调用 listenCallback。listenCallback 用回调函数调用 onAccept 接收连接。
void ListenerImpl::listenCallback(evconnlistener*, evutil_socket_t fd, sockaddr* remote_addr, int remote_addr_len, void* arg) {
ListenerImpl* listener = static_cast<ListenerImpl*>(arg);
IoHandlePtr io_handle = std::make_unique<IoSocketHandleImpl>(fd);
// 获取本地地址
const Address::InstanceConstSharedPtr& local_address =
listener->local_address_ ? listener->local_address_
: listener->getLocalAddress(io_handle->fd());
// 获取远程地址
const Address::InstanceConstSharedPtr& remote_address =
(remote_addr->sa_family == AF_UNIX)
? Address::peerAddressFromFd(io_handle->fd())
: Address::addressFromSockAddr(*reinterpret_cast<const sockaddr_storage*>(remote_addr),
remote_addr_len,
local_address->ip()->version() == Address::IpVersion::v6);
//调用 onAccept,
listener->cb_.onAccept(
std::make_unique<AcceptedSocketImpl>(std::move(io_handle), local_address, remote_address),
listener->hand_off_restored_destination_connections_);
}
connection
connection
是连接相关的操作,客户端和服务端的连接都属于这个类。 Connection
是针对原始连接的一个抽象,继承自 DeferredDeletable
和 FilterManager
。关于 DeferredDeletable
延迟析构请看 Envoy 源码分析--event,FilterManager
以后讨论。
ConnectionImpl
ConnectionImpl
是 Connection
,BufferSource
和 TransportSocketCallbacks
三个抽象类的实现类。Connection
是连接操作相关的类,BufferSource
是获得 StreamBuffer 的抽象类(包括读和写),TransportSocketCallbacks
是传输套接字实例与连接进行通信的回调。
每个 ConnectionImpl
实例都有一个唯一的全局ID。在构造时赋值。
std::atomic<uint64_t> ConnectionImpl::next_global_id_;
ConnectionImpl::ConnectionImpl(Event::Dispatcher& dispatcher, ConnectionSocketPtr&& socket, TransportSocketPtr&& transport_socket, bool connected) : id_(next_global_id_++) {
}
ConnectionImpl
事件由 dispatcher_
创建。在构造函数时创建事件。
Event 使用边缘触发,减少内核通知,提高效率(水平触发和边缘触发区别大家自己查阅相关文档)。同时写入读写事件。当有读写事件时,会触发回调 onFileEvent
。
ConnectionImpl::ConnectionImpl(Event::Dispatcher& dispatcher, ConnectionSocketPtr&& socket,TransportSocketPtr&& transport_socket, bool connected) {
... ...
file_event_ = dispatcher_.createFileEvent(
ioHandle().fd(), [this](uint32_t events) -> void { onFileEvent(events); },
Event::FileTriggerType::Edge, Event::FileReadyType::Read | Event::FileReadyType::Write);
... ...
}
onFileEvent
在收到事件后,对不同的事件进行不同的处理。
void ConnectionImpl::onFileEvent(uint32_t events) {
... ...
// 写事件
if (events & Event::FileReadyType::Write) {
onWriteReady();
}
// 读事件
if (ioHandle().isOpen() && (events & Event::FileReadyType::Read)) {
onReadReady();
}
}
对于读事件,在连接调用 readDisable
后,如果是 enable 会触发读事件。
void ConnectionImpl::readDisable(bool disable) {
... ...
read_enabled_ = true;
file_event_->setEnabled(Event::FileReadyType::Read | Event::FileReadyType::Write);
if (read_buffer_.length() > 0) {
file_event_->activate(Event::FileReadyType::Read);
}
}
读事件调用 onReadReady
,onReadReady
先从 buffer中读取数据,同时更新统计数据。对返回的结果进行分析,已关闭直接关闭。正常读到数据,判断是否有数据,有数据会调用 onRead
, onRead
内会调用 ReadFilter 进行下一步处理(L3/4过滤下次分析)。
void ConnectionImpl::onReadReady() {
... ...
IoResult result = transport_socket_->doRead(read_buffer_);
uint64_t new_buffer_size = read_buffer_.length();
updateReadBufferStats(result.bytes_processed_, new_buffer_size);
if ((!enable_half_close_ && result.end_stream_read_)) {
result.end_stream_read_ = false;
result.action_ = PostIoAction::Close;
}
read_end_stream_ |= result.end_stream_read_;
//有读到数据
if (result.bytes_processed_ != 0 || result.end_stream_read_)
onRead(new_buffer_size);
}
// 关闭连接
if (result.action_ == PostIoAction::Close || bothSidesHalfClosed()) {
ENVOY_CONN_LOG(debug, "remote close", *this);
closeSocket(ConnectionEvent::RemoteClose);
}
}
对于写事件,在连接写入数据时,会将数据先进行过滤,然后写入写缓冲。之后调用写事件触发 onFileEvent
void ConnectionImpl::write(Buffer::Instance& data, bool end_stream) {
... ...
// WriteFilter过滤
current_write_buffer_ = &data;
current_write_end_stream_ = end_stream;
FilterStatus status = filter_manager_.onWrite();
current_write_buffer_ = nullptr;
if (FilterStatus::StopIteration == status) {
return;
}
write_end_stream_ = end_stream;
if (data.length() > 0 || end_stream) {
// 写入缓冲
write_buffer_->move(data);
if (!connecting_) {
//触发写事件
file_event_->activate(Event::FileReadyType::Write);
}
}
}
在写入事件后会调用 onWriteReady
。 onWriteReady
先判断是否已连接,未连接会调用 connect
连接事件。连接成功后发送数据并统计信息,连接失败关闭 socket。
void ConnectionImpl::onWriteReady() {
... ...
if (connecting_) {
... ...
if (error == 0) {
connecting_ = false;
//socket 未连接,调用connect。
transport_socket_->onConnected();
... ...
// 发送数据
IoResult result = transport_socket_->doWrite(*write_buffer_, write_end_stream_);
uint64_t new_buffer_size = write_buffer_->length();
//更新统计信息
updateWriteBufferStats(result.bytes_processed_, new_buffer_size);
... ...
}
ClientConnectionImpl
ClientConnectionImpl
是客户端的连接,其继承自 ConnectionImpl
和 ClientConnection
。ClientConnectionImpl
只是在 Connection
的基础上只增加了一个 connect 的接口。
connect 函数内最主要做的就是调用 connect() 连接。
void ClientConnectionImpl::connect() {
// 连接服务器
const Api::SysCallIntResult result = socket_->remoteAddress()->connect(ioHandle().fd());
if (result.rc_ == 0) {
// write will become ready.
ASSERT(connecting_);
} else {
... ...
}
Envoy 源码分析--network的更多相关文章
- Envoy 源码分析--network L4 filter manager
目录 Envoy 源码分析--network L4 filter manager FilterManagerImpl addWriteFilter addReadFilter addFilter in ...
- Envoy 源码分析--buffer
目录 Envoy 源码分析--buffer BufferFragment RawSlice Slice OwnedSlice SliceDeque UnownedSlice OwnedImpl Wat ...
- Envoy 源码分析--程序启动过程
目录 Envoy 源码分析--程序启动过程 初始化 main 入口 MainCommon 初始化 服务 InstanceImpl 初始化 启动 main 启动入口 服务启动流程 LDS 服务启动流程 ...
- Envoy 源码分析--LDS
Envoy 源码分析--LDS LDS 是 Envoy 用来自动获取 listener 的 API. Envoy 通过 API 可以增加.修改或删除 listener. 先来总结下 listener ...
- Envoy 源码分析--event
目录 Envoy 源码分析--event libevent Timer SignalEvent FileEvent RealTimeSystem 任务队列 延迟析构 dispacth_thread E ...
- zookeeper源码分析之三客户端发送请求流程
znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个功能是zookeeper对于应用最重要的特性,通过这个特性可以实现的功能包括配置的 ...
- MyCat源码分析系列之——配置信息和启动流程
更多MyCat源码分析,请戳MyCat源码分析系列 MyCat配置信息 除了一些默认的配置参数,大多数的MyCat配置信息是通过读取若干.xml/.properties文件获取的,主要包括: 1)se ...
- [dpdk] 熟悉SDK与初步使用 (三)(IP Fragmentation源码分析)
对例子IP Fragmentation的熟悉,使用,以及源码分析. 功能: 该例子的功能有二: 一: 将IP分片? 二: 根据路由表,做包转发. 路由表如下: IP_FRAG: Socket : ad ...
- MySQL源码分析以及目录结构 2
原文地址:MySQL源码分析以及目录结构作者:jacky民工 主要模块及数据流经过多年的发展,mysql的主要模块已经稳定,基本不会有大的修改.本文将对MySQL的整体架构及重要目录进行讲述. 源码结 ...
随机推荐
- redis 无序集合(set)函数
sAdd 命令/方法/函数 Adds a value to the set value stored at key. If this value is already in the set, FALS ...
- 关于Xamarin、Qml、数据绑定、MVC、MVVM 相关的散讲
关于Xamarin.Qml.数据绑定.MVC.MVVM 相关的散讲 SURFSKY 2017.02 最近又在学习Xamarin了?为什么是“又”?有几个利好消息,让我重新拾起它: ()微软去年收购了X ...
- 如何提高单片机C语言代码效率
代码效率包括两个方面内容:代码的大小和代码执行速度.如果代码精简和执行速度快,我们就说这个代码效率高.一般情况下,代码精简了速度也相应提上来了.单片机的ROM和RAM的空间都很有限,当您编程时遇到单片 ...
- 关于Axure RP软件的介绍——软件工程实践第二次个人作业
关于Axure RP软件的介绍——软件工程实践第二次个人作业 Axure RP是一个非常专业的快速原型设计的一个工具,客户提出需求,然后根据需求定义和规格.设计功能和界面的专家能够快速创建应用软件或W ...
- Vue入门:Vue环境安装
安装环境 操作系统:Windows 10 64位 安装Node.js 1. 下载系统对应版本安装包 下载地址:https://nodejs.org/en/download/ 2. 安装 除安装地址外其 ...
- 第十三节 Ajax基础
什么是服务器:简单地,可以说服务器就是一个内存超大的计算机,可以存放很多数据和文件(当然,如果不需要太多的数据存储量,我们也可以用电脑.手机等一系列小型计算机作为服务器,只不过性能的差别而已) 网页浏 ...
- js中创建对象的4种方法
1.直接创建,不可复用式创建var obj = new Object(); obj.name = ""; obj.id = ""; 2.使用工厂方法来创建对象, ...
- 【js】箭头函数与普通函数的异同
普通函数在es5中就有了,箭头函数是es6中出现的函数形式,当然也可以继续用es5写法. 普通函数大家知道: 形式基本一致 来看看箭头函数: 开发时根据实际情况可以省略一些东西 单条处理可以省略ret ...
- mysql的sql_mode设置
参考官方文档: mysql可以为不同的客户端设置不同的sql_mode,并且每个应用能够设置他自己的会话级别的sql_mode.sql_mode会影响sql语法以及mysql显示数据的正确性. Whe ...
- redis恢复(aof)
----------------redis备份恢复方法-----------------------------1.采用aof恢复方法若appendonly设置为no的情况下,在每个节点上执行bgre ...