HDFSRPC通信框架详解

本文主要对HDFSRPC通信框架解析。包括listener,reader,handler,responser等实现类的源码分析。注意hadoop版本为3.1.1。

写在前面

rpc肯定依赖于socket通信,并且使用的是java NIO。读者最好对nio有一定的了解,文章中不会对相关知识作过多的介绍。

https://blog.csdn.net/yhl_jxy/article/details/79332092

还有本文中涉及到的代码大部分都是作者都整理过的,会和server源码有些许区别。

RPC框架架构图

1871_2.jpeg

从架构图中可以看出一个socket连接的数据处理被多个模块分割,每个模块处理特定的问题。这样做的好处一方面保证了call的并发,另一方面也保证了代码的可扩展性。

Listener

listener就是监听线程,那到底是监听什么?显而易见是socket连接又称connection。

Listener.run、doAccpect

public void run() {
    LOG.info(Thread.currentThread().getName() + ": starting");
    Server.connectionManager.startIdleScan();
    while (Server.running) {
      SelectionKey key = null;
      try {
        getSelector().select();
        Iterator<SelectionKey> iter = getSelector().selectedKeys().iterator();
        while (iter.hasNext()) {
          key = iter.next();
          iter.remove();
          try {
            if (key.isValid()) {
              if (key.isAcceptable())
                doAccept(key);
            }
          } catch (IOException e) {
          }
          key = null;
        }
      } catch (OutOfMemoryError e) {
        // we can run out of memory if we have too many threads
        // log the event and sleep for a minute and give 
        // some thread(s) a chance to finish
        LOG.warn("Out of Memory in server select", e);
        closeCurrentConnection(key, e);
        Server.connectionManager.closeIdle(true);
        try { Thread.sleep(60000); } catch (Exception ie) {}
      } catch (Exception e) {
        closeCurrentConnection(key, e);
      }
    }
    LOG.info("Stopping " + Thread.currentThread().getName());

    synchronized (this) {
      try {
        acceptChannel.close();
        selector.close();
      } catch (IOException e) { }

      selector= null;
      acceptChannel= null;
      
      // close all connections
      Server.connectionManager.stopIdleScan();
      Server.connectionManager.closeAll();
    }
  }

void doAccept(SelectionKey key) throws InterruptedException, IOException,  OutOfMemoryError {
    ServerSocketChannel server = (ServerSocketChannel) key.channel();
    SocketChannel channel;
    while ((channel = server.accept()) != null) {

      channel.configureBlocking(false);
      channel.socket().setTcpNoDelay(tcpNoDelay);
      channel.socket().setKeepAlive(true);
      
      Reader reader = getReader();
      Connection c = Server.connectionManager.register(channel);
      // If the connectionManager can't take it, close the connection.
      if (c == null) {
        if (channel.isOpen()) {
          IOUtils.cleanup(null, channel);
        }
        Server.connectionManager.droppedConnections.getAndIncrement();
        continue;
      }
      key.attach(c);  // so closeCurrentConnection can get the object
      reader.addConnection(c);
    }
  }

简单来说就是accept channel,变成connection,然后交给reader处理。

Reader

Reader在整个RPC框架中起着举足轻重的作用。在HDFSRPC协议详解一文中processOneRpc之前的工作都是reader完成的。总结一下就是以下几点:

  1. rpc connection初始7字节的检查。
  2. sasl握手与验证。
  3. IpcConnectionContext读取。
  4. processOneRpc准备工作,包括RequestHeaderProto解析。

还有一点要注意的一次reader就包含完成这所有工作,而不是多次完成。单次reader生成call以后,就会马上下次call的read,本质上call是并发的,由handler处理。

reader的源码其实很简单,本质上是循环执行了connection.readAndProcess()。本文不会对readAndProcess过多介绍,有兴趣可以查看HDFSRPC协议详解。

@Override
  public void run() {
    LOG.info("Starting " + Thread.currentThread().getName());
    try {
      doRunLoop();
    } finally {
      try {
        readSelector.close();
      } catch (IOException ioe) {
        LOG.error("Error closing read selector in " + Thread.currentThread().getName(), ioe);
      }
    }
  }

  private synchronized void doRunLoop() {
    while (Server.running) {
      SelectionKey key = null;
      try {
        // consume as many connections as currently queued to avoid
        // unbridled acceptance of connections that starves the select
        int size = pendingConnections.size();
        for (int i=size; i>0; i--) {
          Connection conn = pendingConnections.take();
          conn.channel.register(readSelector, SelectionKey.OP_READ, conn);
        }
        readSelector.select();

        Iterator<SelectionKey> iter = readSelector.selectedKeys().iterator();
        while (iter.hasNext()) {
          key = iter.next();
          iter.remove();
          try {
            if (key.isReadable()) {
              doRead(key);
            }
          } catch (CancelledKeyException cke) {
            // something else closed the connection, ex. responder or
            // the listener doing an idle scan.  ignore it and let them
            // clean up.
            LOG.info(Thread.currentThread().getName() +
                ": connection aborted from " + key.attachment());
          }
          key = null;
        }
      } catch (InterruptedException e) {
        if (Server.running) {                      // unexpected -- log it
          LOG.info(Thread.currentThread().getName() + " unexpectedly interrupted", e);
        }
      } catch (IOException ex) {
        LOG.error("Error in Reader", ex);
      } catch (Throwable re) {
        LOG.error("Bug in read selector!", re);
        //ExitUtil.terminate(1, "Bug in read selector!");
      }
    }
  }

//from Listener doRead
  void doRead(SelectionKey key) throws InterruptedException {
    int count;
    Connection c = (Connection)key.attachment();
    if (c == null) {
      return;  
    }
    c.setLastContact(Time.now());
    
    try {
      count = c.readAndProcess();
    } catch (InterruptedException ieo) {
      LOG.info(Thread.currentThread().getName() + ": readAndProcess caught InterruptedException", ieo);
      throw ieo;
    } catch (Exception e) {
      // Any exceptions that reach here are fatal unexpected internal errors
      // that could not be sent to the client.
      LOG.info(Thread.currentThread().getName() +
          ": readAndProcess from client " + c +
          " threw exception [" + e + "]", e);
      count = -1; //so that the (count < 0) block is executed
    }
    // setupResponse will signal the connection should be closed when a
    // fatal response is sent.
    if (count < 0 || c.shouldClose()) {
      Server.closeConnection(c);
      c = null;
    }
    else {
      c.setLastContact(Time.now());
    }
  }   

CallQueue

callQueue主要是存放call队列,由于callqueue在hdfs是一个较为复杂的东西,后期会单做一期介绍。

Handler

handler线程也比较简单,实际上就是执行了call.run()。

@Override
  public void run() {
    LOG.debug(Thread.currentThread().getName() + ": starting");
    while (Server.running) {
      try {
        final Call call = Server.callQueue.take(); // pop the queue; maybe blocked here
        if (LOG.isDebugEnabled()) {
          LOG.debug(Thread.currentThread().getName() + ": " + call);
        }
        CurCall.set(call);
        /*TODO
        UserGroupInformation remoteUser = call.getRemoteUser();
        if (remoteUser != null) {
          remoteUser.doAs(call);
        } else {
          call.run();
        }*/
        call.run();
      } catch (InterruptedException e) {
        if (Server.running) {                          // unexpected -- log it
          LOG.info(Thread.currentThread().getName() + " unexpectedly interrupted", e);
        }
      } catch (Exception e) {
        LOG.info(Thread.currentThread().getName() + " caught an exception", e);
      } finally {
        CurCall.set(null);
      }
    }
    LOG.debug(Thread.currentThread().getName() + ": exiting");
  }

主要的难点是这么执行call.run()。要知道call.run首先要知道protocols。

Protocols

每个server都自己的Protocols,protocols首先是以rpcKind分类的。

enum RpcKindProto {
  RPC_BUILTIN          = 0;  // Used for built in calls by tests
  RPC_WRITABLE         = 1;  // Use WritableRpcEngine 
  RPC_PROTOCOL_BUFFER  = 2;  // Use ProtobufRpcEngine
}

3.x的rpckind都使用的是RPC_PROTOCOL_BUFFER,所以以这个为例。

RPC_PROTOCOL_BUFFER的protocols会放到一个hashmap里面。

Map<ProtoNameVer, ProtoClassProtoImpl> protocolImplMapArray = new HashMap<ProtoNameVer, ProtoClassProtoImpl>(10);

key为ProtoNameVer,要注意的hashcode的实现方法。

static class ProtoNameVer {
    final String protocol;
    final long   version;
    ProtoNameVer(String protocol, long ver) {
      this.protocol = protocol;
      this.version = ver;
    }
    @Override
    public boolean equals(Object o) {
      if (o == null) 
        return false;
      if (this == o) 
        return true;
      if (! (o instanceof ProtoNameVer))
        return false;
      ProtoNameVer pv = (ProtoNameVer) o;
      return ((pv.protocol.equals(this.protocol)) && 
          (pv.version == this.version));     
    }
    @Override
    public int hashCode() {
      return protocol.hashCode() * 37 + (int) version;    
    }
  }

所以任何protocol必须有protocol和version,即注解类ProtocolInfo。

@Retention(RetentionPolicy.RUNTIME)
public @interface ProtocolInfo {
  String protocolName();  // the name of the protocol (i.e. rpc service)
  long protocolVersion() default -1; // default means not defined use old way
}

一个protocol的接口类类似这样。

@ProtocolInfo(protocolName = HdfsConstants.CLIENT_NAMENODE_PROTOCOL_NAME, protocolVersion = 1)
/**
 * Protocol that a clients use to communicate with the NameNode.
 *
 * Note: This extends the protocolbuffer service based interface to
 * add annotations required for security.
 */
public interface ClientNamenodeProtocolPB extends ClientNamenodeProtocol.BlockingInterface {
}

那反射的方法怎么来呢?我们可以发现ClientNamenodeProtocol.BlockingInterface其实是protobuf编译出来的,可以看一下ClientNamenodeProtocol.proto文件的最后service定义。

service ClientNamenodeProtocol {
  rpc getBlockLocations(GetBlockLocationsRequestProto)
      returns(GetBlockLocationsResponseProto);
  rpc getServerDefaults(GetServerDefaultsRequestProto)
      returns(GetServerDefaultsResponseProto);
  rpc create(CreateRequestProto)returns(CreateResponseProto);
  rpc append(AppendRequestProto) returns(AppendResponseProto);
  rpc setReplication(SetReplicationRequestProto)
      returns(SetReplicationResponseProto);
  rpc setStoragePolicy(SetStoragePolicyRequestProto)
  ...
}

编译出来就是ClientNamenodeProtocol.BlockingInterface,里面就是方法列表。

我们自己的实现类只需要实现ClientNamenodeProtocolPB即可。例如ClientNamenodeProtocolServerSideTranslatorPB。

//add protocols
ClientNamenodeProtocolServerSideTranslatorPB cnn = new ClientNamenodeProtocolServerSideTranslatorPB();
BlockingService cnnService = ClientNamenodeProtocol.newReflectiveBlockingService(cnn);
Server.addProtocol(ClientNamenodeProtocolPB.class, cnnService);    

最后call.run其实是根据RequestHeaderProto来找到对应的实现类。

message RequestHeaderProto {
  /** Name of the RPC method */
  required string methodName = 1;

  /** 
   * RPCs for a particular interface (ie protocol) are done using a
   * IPC connection that is setup using rpcProxy.
   * The rpcProxy's has a declared protocol name that is 
   * sent form client to server at connection time. 
   * 
   * Each Rpc call also sends a protocol name 
   * (called declaringClassprotocolName). This name is usually the same
   * as the connection protocol name except in some cases. 
   * For example metaProtocols such ProtocolInfoProto which get metainfo
   * about the protocol reuse the connection but need to indicate that
   * the actual protocol is different (i.e. the protocol is
   * ProtocolInfoProto) since they reuse the connection; in this case
   * the declaringClassProtocolName field is set to the ProtocolInfoProto
   */
  required string declaringClassProtocolName = 2;
  
  /** protocol version of class declaring the called method */
  required uint64 clientProtocolVersion = 3;
}

然后通过反射,去执行了实现类的方法。

 Writable call(String protocol, Writable writableRequest, long receiveTime) throws Exception {
     RpcProtobufRequest request = (RpcProtobufRequest) writableRequest;
     RequestHeaderProto rpcRequest = request.getRequestHeader();
     String methodName = rpcRequest.getMethodName();

     /** 
      * RPCs for a particular interface (ie protocol) are done using a
      * IPC connection that is setup using rpcProxy.
      * The rpcProxy's has a declared protocol name that is 
      * sent form client to server at connection time. 
      * 
      * Each Rpc call also sends a protocol name 
      * (called declaringClassprotocolName). This name is usually the same
      * as the connection protocol name except in some cases. 
      * For example metaProtocols such ProtocolInfoProto which get info
      * about the protocol reuse the connection but need to indicate that
      * the actual protocol is different (i.e. the protocol is
      * ProtocolInfoProto) since they reuse the connection; in this case
      * the declaringClassProtocolName field is set to the ProtocolInfoProto.
      */

     String declaringClassProtoName = 
         rpcRequest.getDeclaringClassProtocolName();
     long clientVersion = rpcRequest.getClientProtocolVersion();
     //LOG.info("Call: connectionProtocolName=" + connectionProtocolName + ", method=" + methodName + ", declaringClass=" + declaringClassProtoName);
     ProtoClassProtoImpl protocolImpl = getProtocolImpl(declaringClassProtoName, clientVersion);
     BlockingService service = (BlockingService) protocolImpl.protocolImpl;
     MethodDescriptor methodDescriptor = service.getDescriptorForType()
         .findMethodByName(methodName);
     if (methodDescriptor == null) {
       String msg = "Unknown method " + methodName + " called on " + protocol + " protocol.";
       LOG.warn(msg);
       throw new RpcNoSuchMethodException(msg);
     }
     Message prototype = service.getRequestPrototype(methodDescriptor);
     Message param = request.getValue(prototype);

     Message result = null;
     long startTime = Time.now();
     int qTime = (int) (startTime - receiveTime);
     Exception exception = null;
     boolean isDeferred = false;
     try {
       //server.rpcDetailedMetrics.init(protocolImpl.protocolClass);
       result = service.callBlockingMethod(methodDescriptor, null, param);
       // Check if this needs to be a deferred response,
       // by checking the ThreadLocal callback being set
     } catch (ServiceException e) {
       exception = (Exception) e.getCause();
       throw (Exception) e.getCause();
     } catch (Exception e) {
       exception = e;
       throw e;
     } finally {
       int processingTime = (int) (Time.now() - startTime);
       //if (LOG.isDebugEnabled()) {
         String msg =
             "Served: " + methodName + (isDeferred ? ", deferred" : "") +
                 ", queueTime= " + qTime +
                 " procesingTime= " + processingTime;
         if (exception != null) {
           msg += " exception= " + exception.getClass().getSimpleName();
         }
         //LOG.debug(msg);
         LOG.info(msg);
         //LOG.info("params:" + param.toString());
         //LOG.info("result:" + result.toString());
       //}
       String detailedMetricsName = (exception == null) ?
           methodName :
           exception.getClass().getSimpleName();
       //server.updateMetrics(detailedMetricsName, qTime, processingTime, isDeferred);
       
     }
     return RpcWritable.wrap(result);
   }

完成以后如果有返回Message会放入rpccall.rpcResponse。然后再把call放入ResponseQueue。

ResponseQueue

在connection中,主要存放处理完的rpccall。

Responder

Responder线程主要负责call结果的返回。

 private boolean processResponse(LinkedList<RpcCall> responseQueue,
                                  boolean inHandler) throws IOException {
    boolean error = true;
    boolean done = false;       // there is more data for this channel.
    int numElements = 0;
    RpcCall call = null;
    try {
      synchronized (responseQueue) {
        //
        // If there are no items for this channel, then we are done
        //
        numElements = responseQueue.size();
        if (numElements == 0) {
          error = false;
          return true;              // no more data for this channel.
        }
        //
        // Extract the first call
        //
        call = responseQueue.removeFirst();
        SocketChannel channel = call.connection.channel;
        if (LOG.isDebugEnabled()) {
          LOG.debug(Thread.currentThread().getName() + ": responding to " + call);
        }
        //
        // Send as much data as we can in the non-blocking fashion
        //
        int numBytes = call.connection.channelWrite(channel, call.rpcResponse);
        if (numBytes < 0) {
          return true;
        }
        if (!call.rpcResponse.hasRemaining()) {
          //Clear out the response buffer so it can be collected
          call.rpcResponse = null;
          call.connection.decRpcCount();
          if (numElements == 1) {    // last call fully processes.
            done = true;             // no more data for this channel.
          } else {
            done = false;            // more calls pending to be sent.
          }
          if (LOG.isDebugEnabled()) {
            LOG.debug(Thread.currentThread().getName() + ": responding to " + call
                + " Wrote " + numBytes + " bytes.");
          }
        } else {
          //
          // If we were unable to write the entire response out, then 
          // insert in Selector queue. 
          //
          call.connection.responseQueue.addFirst(call);
          
          if (inHandler) {
            // set the serve time when the response has to be sent later
            call.timestamp = Time.now();
            
            incPending();
            try {
              // Wakeup the thread blocked on select, only then can the call 
              // to channel.register() complete.
              writeSelector.wakeup();
              channel.register(writeSelector, SelectionKey.OP_WRITE, call);
            } catch (ClosedChannelException e) {
              //Its ok. channel might be closed else where.
              done = true;
            } finally {
              decPending();
            }
          }
          if (LOG.isDebugEnabled()) {
            LOG.debug(Thread.currentThread().getName() + ": responding to " + call
                + " Wrote partial " + numBytes + " bytes.");
          }
        }
        error = false;              // everything went off well
      }
    } finally {
      if (error && call != null) {
        LOG.warn(Thread.currentThread().getName()+", call " + call + ": output error");
        done = true;               // error. no more data for this channel.
        Server.closeConnection(call.connection);
      }
    }
    return done;
  }

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Flask】用户身份认证

Flask 用户身份认证 项目代码见&#xff1a;GitHub - euansu123/FlaskMarket 前提条件 # flask-bcrypt 用户密码加密存储 pip install flask_bcrypt -i https://pypi.tuna.tsinghua.edu.cn/simple/ # flask提供的用户登录方法 pip install flask_login -i https://pypi.tuna…

JetBrains全家桶激活,分享 DataGrip 2024 激活的方案

大家好&#xff0c;欢迎来到金榜探云手&#xff01; DataGrip 公司简介 JetBrains 是一家专注于开发工具的软件公司&#xff0c;总部位于捷克。他们以提供强大的集成开发环境&#xff08;IDE&#xff09;而闻名&#xff0c;如 IntelliJ IDEA、PyCharm、和 WebStorm等。这些工…

git clone没有权限的解决方法

一般情况 git clone时没有权限&#xff0c;一般是因为在代码库平台上没有配置本地电脑的id_rsa.pub 只要配置上&#xff0c;一般就可以正常下载了。 非一般情况 但是也有即使配置了id_rsa.pub后&#xff0c;仍然无法clone代码的情况。如下 原因 这种情况是因为ssh客户端…

阿里云安全产品简介,Web应用防火墙与云防火墙产品各自作用介绍

在阿里云的安全类云产品中&#xff0c;Web应用防火墙与云防火墙是用户比较关注的安全类云产品&#xff0c;二则在作用上并不是完全一样的&#xff0c;Web应用防火墙是一款网站Web应用安全的防护产品&#xff0c;云防火墙是一款公共云环境下的SaaS化防火墙&#xff0c;本文为大家…

[深度学习]yolov8+pyqt5搭建精美界面GUI设计源码实现四

【简单介绍】 经过精心设计和深度整合&#xff0c;我们成功推出了这款融合了先进目标检测算法YOLOv8与高效PyQt5界面开发框架的目标检测GUI界面软件。该软件在直观性、易用性和功能性方面均表现出色&#xff0c;为用户提供了高效稳定的操作体验。 在界面设计方面&#xff0c;…

Spring Boot整合Redis

GitHub&#xff1a;SpringBootDemo Gitee&#xff1a;SpringBootDemo 微信公众号&#xff1a; 0 开发环境 JDK&#xff1a;1.8Spring Boot&#xff1a;2.7.18 1 导入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>sp…

Anaconda和Python是什么关系?

Anaconda和Python相当于是汽车和发动机的关系&#xff0c;你安装Anaconda后&#xff0c;就像买了一台车&#xff0c;无需你自己安装发动机和其他零配件&#xff0c;而Python作为发动机提供Anaconda工作所需的内核。 简单来说&#xff0c;Anaconda是一个集成了IDE、Notepad、P…

IDEA使用常用的设置

一、IDEA常用设置 可参考&#xff1a;IDEA这样配置太香了_哔哩哔哩_bilibili 波波老师 二、插件 可参考&#xff1a;IDEA好用插件&#xff0c;强烈推荐_哔哩哔哩_bilibili 波波老师 三、其他 学会用点“.” IDEA弹窗Servers certificate is not trusted怎么禁止&#xf…

在项目中缓存如何优化?SpringCache接口返回值的缓存【CachePut、CacheEvict、Cacheable】

SpringCache 介绍&#xff08;不同的缓存技术有不同的CacheManager&#xff09;注解入门程序环境准备数据库准备环境准备注入CacheManager引导类上加EnableCaching CachePut注解(缓存方法返回值)1). 在save方法上加注解CachePut2). 测试 CacheEvict注解&#xff08;清理指定缓存…

canal: 连接kafka (docker)

一、确保mysql binlog开启并使用ROW作为日志格式 docker 启动mysql 5.7配置文件 my.cnf [mysqld] log-binmysql-bin # 开启 binlog binlog-formatROW # 选择 ROW 模式 server-id1一定要确保上述两个值一个为ROW&#xff0c;一个为ON 二、下载canal的run.sh https://github.c…

[串讲]MySQL 存储原理 B+树

InnoDB 是一种兼顾高可靠性和高性能的通用存储引擎&#xff0c;在 MySQL 5.5 之后&#xff0c;InnoDB 是默认的 MySQL 存储引擎。 InnoDB 对每张表在磁盘中的存储以 xxx.ibd 后缀结尾&#xff0c;innoDB 引擎的每张表都会对应这样一个表空间文件&#xff0c;用来存储该表的表结…

全局自定义指令实现图片懒加载,vue2通过js和vueuse的useintersectionObserver实现

整体逻辑&#xff1a; 1.使用全局自定义指令创建图片懒加载指令 2.在全局自定义指令中获取图片距离顶部的高度和整个视口的高度 3.实现判断图片是否在视口内的逻辑 一、使用原生js在vue2中实现图片懒加载 1.创建dom元素,v-lazy为自定义指令&#xff0c;在自定义指令传入图片…

python使用pygame做第一个孩子游戏

作者&#xff1a;ISDF 功能&#xff1a;孩子游戏 版本&#xff1a;3.0 日期&#xff1a;03/29/2019作者&#xff1a;ISDF 功能&#xff1a;孩子游戏 版本&#xff1a;4.0 日期&#xff1a;03/27/2024 import pygame from pygame.locals import * import sys from itertools imp…

Python7:接口自动化学习1 RPC

API&#xff08;Application Programmming Interface&#xff09; 应用编程接口&#xff0c;简称“接口” 接口&#xff1a;程序之间约定的通信方法 特点&#xff1a;约定了调用方法&#xff0c;以及预期的行为&#xff0c;但是不透露具体细节 意义&#xff1a;程序能解耦&…

FPGA高端项目:解码索尼IMX390 MIPI相机转HDMI输出,提供FPGA开发板+2套工程源码+技术支持

目录 1、前言2、相关方案推荐本博主所有FPGA工程项目-->汇总目录我这里已有的 MIPI 编解码方案 3、本 MIPI CSI-RX IP 介绍4、个人 FPGA高端图像处理开发板简介5、详细设计方案设计原理框图IMX390 及其配置MIPI CSI RX图像 ISP 处理图像缓存HDMI输出工程源码架构 6、工程源码…

Trapcode Particular---打造惊艳粒子效果

Trapcode Particular是Adobe After Effects中的一款强大3D粒子系统插件&#xff0c;其能够创造出丰富多样的自然特效&#xff0c;如烟雾、火焰和闪光&#xff0c;以及有机的和高科技风格的图形效果。Trapcode Particular功能丰富且特色鲜明&#xff0c;是一款为Adobe After Eff…

视觉里程计之对极几何

视觉里程计之对极几何 前言 上一个章节介绍了视觉里程计关于特征点的一些内容&#xff0c;相信大家对视觉里程计关于特征的描述已经有了一定的认识。本章节给大家介绍视觉里程计另外一个概念&#xff0c;对极几何。 对极几何 对极几何是立体视觉中的几何关系&#xff0c;描…

新能源汽车充电桩主板各模块成本占比解析

汽车充电桩主板是汽车充电桩的重要组件&#xff0c;主要由微处理器模块、通信模块、控制模块、安全保护模块、传感器模块等多个模块构成。深入探究各模块在总成本中的比重&#xff0c;我们可以更好地优化成本结构、提高生产效率&#xff0c;并为未来的技术创新和市场需求变化做…

网络层介绍,IP地址分类以及作用

IP地址组成&#xff1a; TTL&#xff1a;生存时间 基于ICMP报文 特殊地址&#xff1a; 0.0.0.0-0.255.255.255 1.代表未指定的地址 默认路由 DHCP下发地址的时候&#xff0c;发个报文给DHCP服务器 临时用0.0.0.0借用地址&#xff0c;未指定地址。 2.全网地址&#xff1a;目…

iNet Network Scanner Mac 网络扫描工具

iNet Network Scanner for Mac是一款功能强大的网络扫描工具&#xff0c;专为Mac用户设计。它提供了全面而深入的网络分析功能&#xff0c;使用户能够轻松获取Mac连接的网络和设备的详细信息。 软件下载&#xff1a;iNet Network Scanner Mac v3.1.0激活版 这款软件具备多种扫描…