Envoy 物联网模块开发---串口服务器 (一)

news2024/11/26 4:45:28

一、背景

最近业余时间想基于Envoy 开发一个串口网关,主要是想把一些 modbus、bacnet 以及 mqtt 等物联网协议接入Envoy中,当读到串口数据后可以转发成对应的网络协议

二、Envoy的优势

选择Envoy的话主要是因为Envoy的代码已经十分健全了,零信任、连接池、DNS解析、健康检查、集群调度等等Envoy都支持的很完善了,思来想去还是决定在Envoy基础上走二开,Envoy应该是C++里写的最好的网关了,内存小,而且扩展性极强。

三、Envoy在物联网方面的劣势

但是Envoy也有一些缺点

1、Envoy是基于互联网的网关对物联网模块支持不足、对物联网协议支持的不多

2、Envoy 代码巨大,开发难度和成本非常的高,开发起来非常的困难以及复杂,对技术要求十分的高。

3、Envoy代码巨大,变动一个文件就可能要几个小时

4、Envoy当前ListenerManager 并没有很好的扩展性,甚至在Bazel 文件里可见性只有几个模块,而且只支持UDP和TCP两种通信,要加一个串口通信难度并不小。

四、我们该怎么做?

尽量不要自己写基本的串口代码,使用第三方库libserialport

libserial

串口的第三方库

https://github.com/crayzeewulf/libserial

我们需要做的事情是两部:

1、加入新的listener

2、引入第三方串口库

1、加入新的listener

Envoy本身是不支持Listener模块扩展的,只支持Filter,Listener模块如果我们想扩展,就需要动ListenerManager的代码,动刀需要谨慎,所以我拷贝出来一份ListenerMangaer

 

核心代码改动:

修改配置下发格式为:

listeners:
    - name: listener_0
      address:
        socket_address:
          address: 0.0.0.0
          port_value: 10001
        rtu:
          path: /dev/ttyS0
      filter_chains:
        - filters:
            - name: envoy.filters.network.http_connection_manager

修改proto配置文件

api/envoy/config/core/v3/address.proto

加入串口的Rtu形式:


// Addresses specify either a logical or physical address and port, which are
// used to tell Envoy where to bind/listen, connect to upstream and find
// management servers.
message Address {
  option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.core.Address";

  oneof address {
    option (validate.required) = true;

    SocketAddress socket_address = 1;

    Pipe pipe = 2;
 
    // Specifies a user-space address handled by :ref:`internal listeners
    // <envoy_v3_api_field_config.listener.v3.Listener.internal_listener>`.
    EnvoyInternalAddress envoy_internal_address = 3;

    Rtu rtu = 4;
  }
}

message Rtu {
  option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.core.Rtu";

  // Unix Domain Socket path. On Linux, paths starting with '@' will use the
  // abstract namespace. The starting '@' is replaced by a null byte by Envoy.
  // Paths starting with '@' will result in an error in environments other than
  // Linux.
  string path = 1 [(validate.rules).string = {min_len: 1}];

  // The mode for the Rtu. Not applicable for abstract sockets.
  uint32 mode = 2 [(validate.rules).uint32 = {lte: 511}];
}

source/common/network/utility.cc

适配串口:

    case envoy::config::core::v3::SocketAddress::UDP:
      return Socket::Type::Datagram;
    case envoy::config::core::v3::SocketAddress::SERIAL:
      return Socket::Type::Stream;
    }
  }

由于watchermen listener manager 原来Envoy代码不能用了,需要我们扩展,所以注释掉原来Envoy的ListenerManager入口


//REGISTER_FACTORY(DefaultListenerManagerFactoryImpl, ListenerManagerFactory);

加入新的串口实例化方式

source/common/network/utility.cc

    return std::make_shared<Address::EnvoyInternalInstance>(
        proto_address.envoy_internal_address().server_listener_name(),
        proto_address.envoy_internal_address().endpoint_id());
  case envoy::config::core::v3::Address::AddressCase::kRtu:
    return std::make_shared<Address::RtuInstance>(proto_address.rtu().path(),
                                                   proto_address.rtu().mode());
  case envoy::config::core::v3::Address::AddressCase::ADDRESS_NOT_SET:
    PANIC_DUE_TO_PROTO_UNSET;
  }

 case envoy::config::core::v3::Address::AddressCase::kEnvoyInternalAddress:
    // Currently internal address supports stream operation only.
    return Socket::Type::Stream;
  case envoy::config::core::v3::Address::AddressCase::kRtu:
    return Socket::Type::Stream;
  case envoy::config::core::v3::Address::AddressCase::ADDRESS_NOT_SET:
    PANIC_DUE_TO_PROTO_UNSET;
  }
/**
 * Implementation of a pipe address (unix domain socket on unix).
 */
class RtuInstance : public InstanceBase {
public:
  /**
   * Construct from an existing unix address.
   */
  explicit RtuInstance(const sockaddr_un* address, socklen_t ss_len, mode_t mode = 0,
                        const SocketInterface* sock_interface = nullptr);

  /**
   * Construct from a string pipe path.
   */
  explicit RtuInstance(const std::string& pipe_path, mode_t mode = 0,
                        const SocketInterface* sock_interface = nullptr);

  static absl::Status validateProtocolSupported() { return absl::OkStatus(); }

  // Network::Address::Instance
  bool operator==(const Instance& rhs) const override;
  const Ip* ip() const override { return nullptr; }
  const Pipe* pipe() const override { return &pipe_; }
  const EnvoyInternalAddress* envoyInternalAddress() const override { return nullptr; }
  const sockaddr* sockAddr() const override {
    return reinterpret_cast<const sockaddr*>(&pipe_.address_);
  }
  const sockaddr_un& getSockAddr() const { return pipe_.address_; }
  socklen_t sockAddrLen() const override {
    if (pipe_.abstract_namespace_) {
      return offsetof(struct sockaddr_un, sun_path) + pipe_.address_length_;
    }
    return sizeof(pipe_.address_);
  }
  absl::string_view addressType() const override { return "default"; }

private:
  /**
   * Construct from an existing unix address.
   * Store the error status code in passed in parameter instead of throwing.
   * It is called by the factory method and the partially constructed instance will be discarded
   * upon error.
   */
  RtuInstance(absl::Status& error, const sockaddr_un* address, socklen_t ss_len, mode_t mode = 0,
               const SocketInterface* sock_interface = nullptr);

  struct PipeHelper : public Pipe {

    bool abstractNamespace() const override { return abstract_namespace_; }
    mode_t mode() const override { return mode_; }

    sockaddr_un address_;
    // For abstract namespaces.
    bool abstract_namespace_{false};
    uint32_t address_length_{0};
    mode_t mode_{0};
  };

  absl::Status initHelper(const sockaddr_un* address, mode_t mode);

  PipeHelper pipe_;
  friend class InstanceFactory;
};
//RTU
RtuInstance::RtuInstance(const sockaddr_un* address, socklen_t ss_len, mode_t mode,
                           const SocketInterface* sock_interface)
    : InstanceBase(Type::Pipe, sockInterfaceOrDefault(sock_interface)) {
  if (address->sun_path[0] == '\0') {
#if !defined(__linux__)
    throw EnvoyException("Abstract AF_UNIX sockets are only supported on linux.");
#endif
    RELEASE_ASSERT(static_cast<unsigned int>(ss_len) >= offsetof(struct sockaddr_un, sun_path) + 1,
                   "");
    pipe_.abstract_namespace_ = true;
    pipe_.address_length_ = ss_len - offsetof(struct sockaddr_un, sun_path);
  }
  absl::Status status = initHelper(address, mode);
  throwOnError(status);
}

RtuInstance::RtuInstance(const std::string& pipe_path, mode_t mode,
                           const SocketInterface* sock_interface)
    : InstanceBase(Type::Pipe, sockInterfaceOrDefault(sock_interface)) {
  if (pipe_path.size() >= sizeof(pipe_.address_.sun_path)) {
    throw EnvoyException(
        fmt::format("Path \"{}\" exceeds maximum UNIX domain socket path size of {}.", pipe_path,
                    sizeof(pipe_.address_.sun_path)));
  }
  memset(&pipe_.address_, 0, sizeof(pipe_.address_));
  pipe_.address_.sun_family = AF_UNIX;
  if (pipe_path[0] == '@') {
    // This indicates an abstract namespace.
    // In this case, null bytes in the name have no special significance, and so we copy all
    // characters of pipe_path to sun_path, including null bytes in the name. The pathname must also
    // be null terminated. The friendly name is the address path with embedded nulls replaced with
    // '@' for consistency with the first character.
#if !defined(__linux__)
    throw EnvoyException("Abstract AF_UNIX sockets are only supported on linux.");
#endif
    if (mode != 0) {
      throw EnvoyException("Cannot set mode for Abstract AF_UNIX sockets");
    }
    pipe_.abstract_namespace_ = true;
    pipe_.address_length_ = pipe_path.size();
    // The following statement is safe since pipe_path size was checked at the beginning of this
    // function
    memcpy(&pipe_.address_.sun_path[0], pipe_path.data(), pipe_path.size()); // NOLINT(safe-memcpy)
    pipe_.address_.sun_path[0] = '\0';
    pipe_.address_.sun_path[pipe_path.size()] = '\0';
    friendly_name_ = friendlyNameFromAbstractPath(
        absl::string_view(pipe_.address_.sun_path, pipe_.address_length_));
  } else {
    // Throw an error if the pipe path has an embedded null character.
    if (pipe_path.size() != strlen(pipe_path.c_str())) {
      throw EnvoyException("UNIX domain socket pathname contains embedded null characters");
    }
    StringUtil::strlcpy(&pipe_.address_.sun_path[0], pipe_path.c_str(),
                        sizeof(pipe_.address_.sun_path));
    friendly_name_ = pipe_.address_.sun_path;
  }
  pipe_.mode_ = mode;
}

RtuInstance::RtuInstance(absl::Status& error, const sockaddr_un* address, socklen_t ss_len,
                           mode_t mode, const SocketInterface* sock_interface)
    : InstanceBase(Type::Pipe, sockInterfaceOrDefault(sock_interface)) {
  if (address->sun_path[0] == '\0') {
#if !defined(__linux__)
    error = absl::FailedPreconditionError("Abstract AF_UNIX sockets are only supported on linux.");
    return;
#endif
    RELEASE_ASSERT(static_cast<unsigned int>(ss_len) >= offsetof(struct sockaddr_un, sun_path) + 1,
                   "");
    pipe_.abstract_namespace_ = true;
    pipe_.address_length_ = ss_len - offsetof(struct sockaddr_un, sun_path);
  }
  error = initHelper(address, mode);
}

bool RtuInstance::operator==(const Instance& rhs) const { return asString() == rhs.asString(); }

absl::Status RtuInstance::initHelper(const sockaddr_un* address, mode_t mode) {
  pipe_.address_ = *address;
  if (pipe_.abstract_namespace_) {
    if (mode != 0) {
      return absl::FailedPreconditionError("Cannot set mode for Abstract AF_UNIX sockets");
    }
    // Replace all null characters with '@' in friendly_name_.
    friendly_name_ = friendlyNameFromAbstractPath(
        absl::string_view(pipe_.address_.sun_path, pipe_.address_length_));
  } else {
    friendly_name_ = address->sun_path;
  }
  pipe_.mode_ = mode;
  return absl::OkStatus();
}

加入新的常量:

enum class Type { Ip, Pipe, EnvoyInternal, Rtu };

二、引入第三方串口库

第三方串口库我使用的是

GitHub - crayzeewulf/libserial: Serial Port Programming in C++

定义iot.bzl

load("@envoy_api//bazel:envoy_http_archive.bzl", "envoy_http_archive")
load("@envoy_api//bazel:external_deps.bzl", "load_repository_locations")
load("repository_locations.bzl", "WATCHERMEN_REPOSITORY_LOCATIONS_SPEC")

# archives, e.g. cares.
def _build_all_content(exclude = []):
    return """filegroup(name = "all", srcs = glob(["**"], exclude={}), visibility = ["//visibility:public"])""".format(repr(exclude))

BUILD_ALL_CONTENT = _build_all_content()

WATCHERMEN_REPOSITORY_LOCATIONS = load_repository_locations(WATCHERMEN_REPOSITORY_LOCATIONS_SPEC)

# Use this macro to reference any HTTP archive from bazel/repository_locations.bzl.
def external_http_archive(name, **kwargs):
    envoy_http_archive(
        name,
        locations = WATCHERMEN_REPOSITORY_LOCATIONS,
        **kwargs
    )

def watchermen_iot_dependencies():
    external_http_archive(
        name = "com_github_serial",
        build_file_content = BUILD_ALL_CONTENT,
    )
    native.bind(
        name = "serial",
        actual = "//bazel/foreign_cc:serial",
    )

 定义仓库字典:

# This should match the schema defined in external_deps.bzl.

WATCHERMEN_REPOSITORY_LOCATIONS_SPEC = dict(
    com_github_serial = dict(
        project_name = "serial",
        project_desc = "C library for serial port",
        project_url = "https://github.com/crayzeewulf/libserial",
        version = "master",
        strip_prefix = "libserial-{version}",
        # urls = ["https://github.com/crayzeewulf/libserial/archive/refs/tags/v{version}.tar.gz"],
        urls = ["https://github.com/crayzeewulf/libserial/archive/refs/heads/master.zip"],
        release_date = "2022-05-29",
        sha256 = "9f0c6137e56027d496a205072c527d47f552d4c170f24ae5cea2668da54e2a1b",
        use_category = ["dataplane_core"],
        cpe = "cpe:2.3:a:c-serial_project:c-serial:*",
        license = "libserial",
        license_url = "https://github.com/crayzeewulf/libserial/blob/master/LICENSE.txt",
    ),
)

加入bazel cmake

envoy_cmake(
    name = "serial",
    lib_source = "@com_github_serial//:all",
    cache_entries = {
        # "CMAKE_INSTALL_LIBDIR": "lib",
        # "CMAKE_CXX_COMPILER_FORCED": "on",
        "LIBSERIAL_ENABLE_TESTING": "off",
        "LIBSERIAL_BUILD_EXAMPLES": "off",
    },
    # linkopts = select({
    #     # "//bazel:apple": ["-lresolv"],
    #     "//conditions:default": [],
    # }),
    cmake_files_dir = "$BUILD_TMPDIR/",
    out_static_libs = select({
        "//conditions:default": ["libserial.a"],
    }),
    # postfix_script = select({
    #     # "//bazel:windows_x86_64": "cp -L $EXT_BUILD_ROOT/external/com_github_libserial/src/lib/ares_nameser.h $INSTALLDIR/include/ares_nameser.h && cp -L $EXT_BUILD_ROOT/external/com_github_c_ares_c_ares/include/ares_dns.h $INSTALLDIR/include/ares_dns.h",
    #     # "//conditions:default": "rm -f $INSTALLDIR/include/ares_dns.h && cp -L $EXT_BUILD_ROOT/external/com_github_c_ares_c_ares/include/ares_dns.h $INSTALLDIR/include/ares_dns.h",
    # }),
)

 

在模块中引入Envoy 

envoy_cc_library(
    name = "watchermen_iot_factory_lib",
    hdrs = [
        "watchermen_rtu_socket_handle.h",
        "watchermen_rtu_listener_socket.h",
        ],
    srcs = [
        "watchermen_rtu_listener_socket.cc",
        "watchermen_rtu_socket_handle.cc",
        ],
    repository = "@envoy",
    external_deps = ["serial"],
    deps = [
        # "//external:serial"
    ],
)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/585939.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

(数字图像处理MATLAB+Python)第九章图像形态学运算-第三节:二值图像的形态学处理

文章目录 一&#xff1a;形态滤波&#xff08;1&#xff09;概述&#xff08;2&#xff09;程序 二&#xff1a;图像的平滑处理&#xff08;1&#xff09;概述&#xff08;2&#xff09;程序 三&#xff1a;图像的边缘提取&#xff08;1&#xff09;概述&#xff08;2&#xff…

redux与react-redux状态集中管理

一、redux:可用于react、Vue等中 redux应用&#xff1a;状态的管理&#xff0c;共享状态&#xff0c;Redux用一个单独的常量状态树&#xff08;state对象&#xff09;保存这一整个应用&#xff08;如tab选项卡的状态、城市等需要应用在整个页面的信息&#xff09;的状态。其本…

算法|13.贪心

1.字典序最小的字符串连接方案 题意&#xff1a;给定一个由字符串组成的数组strs&#xff0c;必须把所有的字符串拼接起来&#xff0c;返回所有可能的拼接结果中字典序最小的结果。 public static class MyCom implements Comparator<String>{Overridepublic int compa…

@程序员【提升代码质量,快走出学习迷茫的状态吧】

思路清晰&#xff0c;能上钻一 思路清晰&#xff0c;能上钻一写代码如同打游戏上分。写代码如同中医治病。 思路清晰&#xff0c;能上钻一 ⭐⭐想成为一名优秀的电玩高手&#xff0c;你需要有清晰的思路;想成为一名顶级的电玩高手&#xff0c;你需要的是顶级的思路和异于常人的…

裁员后投递了300次简历,面试22家,终于上岸!

这是一位群友的励志故事&#xff0c;生活虽然很苦&#xff0c;但是朝着自己想要的方向去努力很值得&#xff01; 求职109天&#xff0c;沟通2212次&#xff0c;投简历355次&#xff0c;面试22家&#xff0c;涨薪10%&#xff0c;终于上岸&#xff0c;在这里复盘下我的经历&#…

[创业之路-72] :创业公司发展模式的选择:技工贸还是贸工技?

目录 前言&#xff1a; 一、什么是技、工、贸&#xff1f; 二、概述 2.1 推动力不同 2.2 适合领域不同 2.3 经营模式的主导地位不同 三、技、工、贸详解 3.1 常见的七种营销模式 3.2 常见的三种生产模式 3.3 常见的三种研发模式 四、经营模式的战略选择与影响因素 …

短视频矩阵源码如何做应用编程?

短视频矩阵源码&#xff0c; 短视频矩阵系统技术文档&#xff1a; 可以采用电子文档或者纸质文档的形式交付&#xff0c;具体取决于需求方的要求。电子文档可以通过电子邮件、远程指导交付云存储等方式进行传输、 短视频矩阵{seo}源码是指将抖音平台上的视频资源进行筛选、排…

C++ Primer Plus 第一,二章笔记

目录 第一章笔记 1、C简介 2、C简史 3、可移植性和标准 第二章笔记 1. 进入C 1.3、预处理器和头文件 1.4、名称空间&#xff08;namespace&#xff09; 1.5、使用cout进行C的输出 2. C语句 3. 其他C语句 4. 函数 第一章笔记 1、C简介 C融合了3种不同的编程方式&a…

vsdx文件怎么打开,安装什么软件打开这种后缀名(教程)

目录 简介 安装配置 其他 总结 简介 VSDX 文件是 Microsoft Visio 文件格式&#xff0c;它是一种二进制文件&#xff0c;用于保存 Visio 中的绘图和图表。如果你想要打开 VSDX 文件&#xff0c;可以考虑以下几种方法&#xff1a; 方法一&#xff1a;使用 Microsoft Visio …

C++IO流(详解)

C语言的输入与输出 在C语言当中&#xff0c;我们使用最频繁的输入输出方式就是scanf与printf&#xff1a; scanf&#xff1a; 从标准输入设备&#xff08;键盘&#xff09;读取数据&#xff0c;并将读取到的值存放到某一指定变量当中。 printf&#xff1a; 将指定的数据输出到…

Vivado综合属性系列之十三 FSM_ENCODING

目录 一、前言 二、FSM_ENCODING ​2.1 属性介绍 ​2.2 工程代码 2.3 结果 ​2.4 参考资料 一、前言 ​状态机的实现有很多方式&#xff0c;如auto&#xff0c;one_hot&#xff0c;sequential&#xff0c;如下图中Synthesis中-fsm_extraction的配置项&#xff0c;但此处作用范…

【AI面试】降低过拟合的方式方法横评探究

对于一个“训练调参工程师”来说&#xff0c;在训练过程遇到过拟合现象&#xff0c;是常事。当然&#xff0c;如何降低过拟合&#xff0c;也是在面试过程中&#xff0c;经常被面试官问到的问题&#xff08;没啥可问的&#xff0c;就只能这样问了&#xff09;。以下是我们会常考…

HEVC中,mvd怎么写进码流的?

文章目录 Motion vector difference syntax 标准文档描述语义解释设计意义 Motion vector difference syntax 标准文档描述 语义解释 MvdL1[ x0 ][ y0 ][ compIdx ] L1列表的mvd x0,y0 表示亮度快左上角坐标 compIdx 0表示水平 compIdx 0表示垂直 mvd_l1_zero_flag&#xff1a…

DRF之JWT认证

一、JWT认证 在用户注册或登录后&#xff0c;我们想记录用户的登录状态&#xff0c;或者为用户创建身份认证的凭证。我们不再使用Session认证机制&#xff0c;而使用Json Web Token&#xff08;本质就是token&#xff09;认证机制。 Json web token (JWT), 是为了在网络应用环…

给osg::Geometry(自己绘制的几何体)添加纹理(二)

目录 1. 前言 2. 自会集合体贴纹理 2.1. 一张图贴到整个几何体 2.2. 几何体每个面贴不同的图片纹理 3. 说明 1. 前言 前文讲述了如何给osg自带的几何体&#xff0c;如&#xff1a;BOX等&#xff0c;添加纹理&#xff0c;文章参考链接如下&#xff1a; osg给osg::Geometry&…

动态规划专题一(动态规划的基本模型)

先上例题1 1258&#xff1a;【例9.2】数字金字塔 信息学奥赛一本通&#xff08;C版&#xff09;在线评测系统 (ssoier.cn) 1258&#xff1a;【例9.2】数字金字塔 时间限制: 1000 ms 内存限制: 65536 KB 提交数: 36341 通过数: 21547 【题目描述】 观察下面的数字…

HotSpot虚拟机OutOfMemoryError异常

目录 一、JVM内存区域 二、堆中对象 1. 对象的创建 2. 对象的内存布局 3. 对象的访问定位 三、OOM异常 1. 堆OOM异常测试 2. 栈SOF异常测试 1)&#xff1a;栈容量过小 2)&#xff1a;大量本地变量 3. 常量池OOM异常测试 4. 方法区测试 5. 堆外内存测试 四、参考资料…

详解FreeRTOS:嵌入式多任务系统的任务互斥和优先级反转(理论篇—9)

在嵌入式多任务系统中,有些资源必须是独占使用的,多个任务对这样的资源的并发访问将导致错误的发生。一般来说,对需要独占使用的资源必须使用互斥方法将对其的并发访问串行化。 在优先级多任务系统中引入互斥方案,会导致任务优先级反转的问题:假如某时低优先级的任务占有…

Zabbix之2023 Zabbix6.4最新高级特性、优缺点及其实现原理总结

目录 Zabbix高级特性1. 自动发现 Zabbix高级特性2. 分布式监控 Zabbix高级特性3. 高级报警 Zabbix高级特性4. 可视化 Zabbix高级特性5. API Zabbix高级特性6. 高可用性 Zabbix高级特性7. 安全性 Zabbix高级特性8. 无代理监控 SNMP IPMI JMX Zabbix高级特性9. Agent…

【Windows】局域网内远程桌面控制

【Windows】局域网内远程桌面控制 1、背景2、设置登录密码3、启用远程桌面4、远程示例 1、背景 工作中的很多场景需要远程操作&#xff0c;这时候可以借助远程桌面应用程序实现&#xff0c; 比如AnyDesk、向日葵、TeamViewer等。 windows10系统&#xff0c;其操作系统自带了远…