由一次NoHttpResponseException异常,追究到Http长连接和短连接_resttemplate 报错长连接-程序员宅基地

技术标签: java原理  java  http  spring相关  

 一片春愁待酒浇,江上舟摇,楼上帘招。秋娘渡与泰娘桥,风又漂漂,雨又潇潇。何日归家洗客袍?银字笙调,心字香烧。流光容易把人抛,红了樱桃,绿了芭蕉。

——蒋捷《一剪梅.舟过吴江》

一、异常描述

调用smk短信出现NoHttpResponseException异常:

用的是公司配置好的RestTemplate对象。 

原因分析:公司配置好的RestTemplate自带链接池,下次请求时时,连接池里有的连接已经断开了;所以会出现上面的错误。

用李哥的原话:这个问题应该是对方超时把连接关了,池子里的还认为是有效的。(要确认smk那边每个请求的keepalived设置多久的,他们响应的请求头上有带,然后在设置我们这边的会比较准确)

二、解决方法

解决方法有两种,一种是将RestTemplate设置成长连接(RestTemplate配置了连接池管理器,同时也要配置keepAlive);另一种是将RestTemplate设置成短连接。

2.1 RestTemplate设置成长连接

长连接以保证高性能,RestTemplate 本身也是一个 wrapper 其底层默认是 SimpleClientHttpRequestFactory ,如果要保证长连接HttpComponentsClientHttpRequestFactory 是个更好的选择,它不仅可以控制能够建立的连接数还能细粒度的控制到某个 server 的连接数,非常方便。在默认情况下,RestTemplate 到某个 server 的最大连接数只有 2, 一般需要调的更高些,最好等于 server 的 CPU 个数,所以通常下建议使用连接池的方式处理RestTeamplate;

 

 

先来看下原来的配置:

package com.hfi.health.starters.web;

import com.hfi.health.starters.common.util.HfiLogger;
import java.security.GeneralSecurityException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.List;
import javax.net.ssl.SSLContext;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.config.RequestConfig.Builder;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.ssl.SSLContextBuilder;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

@Configuration
@EnableConfigurationProperties({HttpClientProperties.class})
public class RestTemplateAutoConfiguration
{
  private static final Logger logger = HfiLogger.create(RestTemplateAutoConfiguration.class);
  @Autowired
  private HttpClientProperties httpClientProperties;
  
  @Bean
  public RestTemplate restTemplate()
  {
    RestTemplate template = new RestTemplate(httpRequestFactory());
    template.getInterceptors().add(new LoggingReqRespInterceptor());
    
    return template;
  }
  
  @Bean
  public ClientHttpRequestFactory httpRequestFactory()
  {
    return new HttpComponentsClientHttpRequestFactory(httpClient());
  }
  
  @Bean
  public HttpClient httpClient()
  {
    Registry<ConnectionSocketFactory> registry = RegistryBuilder.create().register("http", PlainConnectionSocketFactory.getSocketFactory()).register("https", SSLConnectionSocketFactory.getSocketFactory()).build();
    PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry);
    connectionManager.setMaxTotal(this.httpClientProperties.getMaxTotal());
    connectionManager.setDefaultMaxPerRoute(this.httpClientProperties.getDefaultMaxPerRoute());
    connectionManager.setValidateAfterInactivity(this.httpClientProperties.getValidateAfterInactivity());    

    RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(this.httpClientProperties.getSocketTimeout()).setConnectTimeout(this.httpClientProperties.getConnectTimeout()).setConnectionRequestTimeout(this.httpClientProperties.getConnectionRequestTimeout()).build();
    
    HttpClientBuilder clientBuilder = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig).setConnectionManager(connectionManager);
    try
    {
      SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy()
      {
        public boolean isTrusted(X509Certificate[] arg0, String arg1)
          throws CertificateException
        {
          return true;
        }
      }).build();
      SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext, new String[] { "TLSv1" }, null, NoopHostnameVerifier.INSTANCE);
      
      clientBuilder.setConnectionManager(connectionManager).setSSLSocketFactory(csf);
    }
    catch (NoSuchAlgorithmException|KeyStoreException|KeyManagementException e)
    {
      logger.error("SSL context configuring failed, HTTPS cannot be used in RestTemplate.", e);
    }
    return clientBuilder.build();
  }
}

 改后的配置:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.hfi.health.starters.web;

import com.alibaba.fastjson.JSON;
import com.hfi.health.starters.common.util.HfiLogger;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Map.Entry;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import javax.net.ssl.SSLContext;
import org.apache.http.HeaderElement;
import org.apache.http.HttpHost;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.ssl.SSLContextBuilder;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

@Configuration
@EnableConfigurationProperties({HttpClientProperties.class})
public class RestTemplateAutoConfiguration {
    private static final Logger logger = HfiLogger.create(RestTemplateAutoConfiguration.class);
    @Autowired
    private HttpClientProperties httpClientProperties;

    public RestTemplateAutoConfiguration() {
    }

    @Bean
    public RestTemplate restTemplate() {
        RestTemplate template = new RestTemplate(this.httpRequestFactory());
        template.getInterceptors().add(new LoggingReqRespInterceptor());
        return template;
    }

    @Bean
    public ClientHttpRequestFactory httpRequestFactory() {
        return new HttpComponentsClientHttpRequestFactory(this.httpClient());
    }

    @Bean
    public HttpClient httpClient() {
        Registry<ConnectionSocketFactory> registry = RegistryBuilder.create().register("http", PlainConnectionSocketFactory.getSocketFactory()).register("https", SSLConnectionSocketFactory.getSocketFactory()).build();
        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry);
        connectionManager.setMaxTotal(this.httpClientProperties.getMaxTotal());
        connectionManager.setDefaultMaxPerRoute(this.httpClientProperties.getDefaultMaxPerRoute());
        connectionManager.setValidateAfterInactivity(this.httpClientProperties.getValidateAfterInactivity());
        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(this.httpClientProperties.getSocketTimeout()).setConnectTimeout(this.httpClientProperties.getConnectTimeout()).setConnectionRequestTimeout(this.httpClientProperties.getConnectionRequestTimeout()).build();
        HttpClientBuilder clientBuilder = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig).setConnectionManager(connectionManager);

        try {
            SSLContext sslContext = (new SSLContextBuilder()).loadTrustMaterial((KeyStore)null, new TrustStrategy() {
                public boolean isTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
                    return true;
                }
            }).build();
            SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext, new String[]{"TLSv1"}, (String[])null, NoopHostnameVerifier.INSTANCE);
            clientBuilder.setConnectionManager(connectionManager).setSSLSocketFactory(csf);
            clientBuilder.setKeepAliveStrategy(this.connectionKeepAliveStrategy());
        } catch (KeyStoreException | KeyManagementException | NoSuchAlgorithmException var7) {
            logger.error("SSL context configuring failed, HTTPS cannot be used in RestTemplate.", var7);
        }

        return clientBuilder.build();
    }

    public ConnectionKeepAliveStrategy connectionKeepAliveStrategy() {
        return (response, context) -> {
            BasicHeaderElementIterator it = new BasicHeaderElementIterator(response.headerIterator("Keep-Alive"));

            while(true) {
                String param;
                String value;
                do {
                    do {
                        if (!it.hasNext()) {
                            HttpHost target = (HttpHost)context.getAttribute("http.target_host");
                            Optional<Entry<String, Integer>> any = ((Map)Optional.ofNullable(this.httpClientProperties.getKeepAliveTargetHost()).orElseGet(HashMap::new)).entrySet().stream().filter((e) -> {
                                return ((String)e.getKey()).equalsIgnoreCase(target.getHostName());
                            }).findAny();
                            return (Long)any.map((en) -> {
                                return (long)(Integer)en.getValue() * 1000L;
                            }).orElse(this.httpClientProperties.getKeepAliveTime() * 1000L);
                        }

                        HeaderElement he = it.nextElement();
                        logger.info("HeaderElement:{}", JSON.toJSONString(he));
                        param = he.getName();
                        value = he.getValue();
                    } while(value == null);
                } while(!"timeout".equalsIgnoreCase(param));

                try {
                    return Long.parseLong(value) * 1000L;
                } catch (NumberFormatException var8) {
                    logger.error("Error occurs while parsing timeout settings of keep-alived connection.", var8);
                }
            }
        };
    }
}

 关键代码在这里:

红框的代码是设置http的keep-alive的策略的方法;
keep-alive的作用:使客户端到服务器端的连接持续有效,当出现对服务器的后继请求时,Keep-Alive功能避免了建立或者重新建立连接。Web服务器,基本上都支持HTTP Keep-Alive。

keep-alive使用场景:我们相当于有少数固定客户端,长时间极高频次的访问服务器,启用keep-alive非常合适。

2.2 RestTemplate设置成短连接

 

二、HTTP协议中的长连接和短连接(keep-alive状态)

2.1 HTTP Keep-Alive

在http早期,每个http请求都要求打开一个tpc socket连接,并且使用一次之后就断开这个tcp连接。

使用keep-alive可以改善这种状态,即在一次TCP连接中可以持续发送多份数据而不会断开连接。通过使用keep-alive机制,可以减少tcp连接建立次数,也意味着可以减少TIME_WAIT状态连接,以此提高性能和提高httpd服务器的吞吐率(更少的tcp连接意味着更少的系统内核调用,socket的accept()和close()调用)。

但是,keep-alive并不是免费的午餐,长时间的tcp连接容易导致系统资源无效占用。配置不当的keep-alive,有时比重复利用连接带来的损失还更大。所以,正确地设置keep-alive timeout时间非常重要。

2.2 keepalvie timeout

Httpd守护进程,一般都提供了keep-alive timeout时间设置参数。比如nginx的keepalive_timeout,和Apache的KeepAliveTimeout。这个keepalive_timout时间值意味着:一个http产生的tcp连接在传送完最后一个响应后,还需要hold住keepalive_timeout秒后,才开始关闭这个连接。

 此处参考文章:HTTP Keep-Alive是什么?如何工作?

2.3 长连接与短连接

  • 长连接:client方与server方先建立连接,连接建立后不断开,然后再进行报文发送和接收。这种方式下由于通讯连接一直存在。此种方式常用于P2P通信。
  • 短连接:Client方与server每进行一次报文收发交易时才进行通讯连接,交易完毕后立即断开连接。此方式常用于一点对多点通讯。C/S通信。

2.4 长连接与短连接的操作过程

短连接的操作步骤是:
建立连接——数据传输——关闭连接...建立连接——数据传输——关闭连接

长连接的操作步骤是:
建立连接——数据传输...(保持连接)...数据传输——关闭连接

 2.5 长连接与短连接的使用时机

短连接多用于操作频繁,点对点的通讯,而且连接数不能太多的情况。每个TCP连接的建立都需要三次握手,每个TCP连接的断开要四次握手。

如果每次操作都要建立连接然后再操作的话处理速度会降低,所以每次操作后,下次操作时直接发送数据就可以了,不用再建立TCP连接。例如:数据库的连接用长连接,如果用短连接频繁的通信会造成socket错误,频繁的socket创建也是对资源的浪费。

Web网站的http服务一般都用短连接,因为长连接对于服务器来说要耗费一定的资源。像web网站这么频繁的成千上万甚至上亿客户端的连接用短连接更省一些资源。试想如果都用长连接,而且同时用成千上万的用户,每个用户都占有一个连接的话,可想而知服务器的压力有多大。所以并发量大,但是每个用户又不需频繁操作的情况下需要短连接。

总之:长连接和短连接的选择要根据需求而定。
长连接和短连接的产生在于client和server采取的关闭策略,具体的应用场景采用具体的策略,没有十全十美的选择,只有合适的选择。

2.6 HTTP协议长连接、短连接总结

长连接与短连接的不同主要在于client和server采取的关闭策略不同。短连接在建立连接以后只进行一次数据传输就关闭连接,而长连接在建立连接以后会进行多次数据数据传输直至关闭连接(长连接中关闭连接通过Connection:closed头部字段)。

2.7 二者关闭策略的不同,就产生了长连接的优点

  • 通过开启、关闭更少的TCP连接,节约CPU时间和内存
  • 通过减少TCP开启引起的包的数目,降低网络阻塞。

二者所应用的具体场景不同。短连接多用于操作频繁、点对点的通讯,且连接数不能太多的情况。数据库的连接则采用长连接。

此小节参考文章:HTTP协议中的长连接与短连接

三、TCP的keepalive和HTTP的keepalive之间的区别

两者是完全不同的概念,只是凑巧名字相同。
tcp的keepalive指的是:周期性的去检查链接是否有效(working),经过一个时钟周期(keepalive_timeout)之后发送一个空的探测报文来检查;
HTTP的keepalive 表示:是否允许在同一次TCP链接中进行多次的HTTP请求,从而减少tcp链接建立和断开造成的开销

参考文章:HTTP协议中的长连接和短连接(keep-alive状态)

四、如何配置HTTP自定义KeepAlive策略

4.1RestTemlate自定义KeepAlive策略示例

/**
 * SpringBoot启动类
 */
@SpringBootApplication
@Slf4j
public class CustomerServiceApplication implements ApplicationRunner {
   @Autowired
   private RestTemplate restTemplate;
 
   public static void main(String[] args) {
      new SpringApplicationBuilder()
            .sources(CustomerServiceApplication.class)
            .bannerMode(Banner.Mode.OFF)
            .web(WebApplicationType.NONE)
            .run(args);
   }
 
   @Bean
   public HttpComponentsClientHttpRequestFactory requestFactory() {
      PoolingHttpClientConnectionManager connectionManager =
            new PoolingHttpClientConnectionManager(30, TimeUnit.SECONDS);
      connectionManager.setMaxTotal(200);
      connectionManager.setDefaultMaxPerRoute(20);
 
      CloseableHttpClient httpClient = HttpClients.custom()
            .setConnectionManager(connectionManager)
            .evictIdleConnections(30, TimeUnit.SECONDS)
            .disableAutomaticRetries()
            // 有 Keep-Alive 认里面的值,没有的话永久有效
            //.setKeepAliveStrategy(DefaultConnectionKeepAliveStrategy.INSTANCE)
            // 换成自定义的
            .setKeepAliveStrategy(new CustomConnectionKeepAliveStrategy())
            .build();
 
      HttpComponentsClientHttpRequestFactory requestFactory =
            new HttpComponentsClientHttpRequestFactory(httpClient);
 
      return requestFactory;
   }
 
   @Bean
   public RestTemplate restTemplate(RestTemplateBuilder builder) {
//    return new RestTemplate();
 
      return builder
            .setConnectTimeout(Duration.ofMillis(100))
            .setReadTimeout(Duration.ofMillis(500))
            .requestFactory(this::requestFactory)
            .build();
   }
 
   @Override
   public void run(ApplicationArguments args) throws Exception {
      URI uri = UriComponentsBuilder
            .fromUriString("http://localhost:8080/coffee/?name={name}")
            .build("mocha");
      RequestEntity<Void> req = RequestEntity.get(uri)
//          .accept(MediaType.APPLICATION_XML)
            .build();
      ResponseEntity<String> resp = restTemplate.exchange(req, String.class);
      log.info("Response Status: {}, Response Headers: {}", resp.getStatusCode(), resp.getHeaders().toString());
      log.info("Coffee: {}", resp.getBody());
   }
}
 
 
/**
 * KeepAlive策略
 */
public class CustomConnectionKeepAliveStrategy implements ConnectionKeepAliveStrategy {
    private final long DEFAULT_SECONDS = 30;
 
    @Override
    public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
        return Arrays.asList(response.getHeaders(HTTP.CONN_KEEP_ALIVE))
                .stream()
                .filter(h -> StringUtils.equalsIgnoreCase(h.getName(), "timeout")
                        && StringUtils.isNumeric(h.getValue()))
                .findFirst()
                .map(h -> NumberUtils.toLong(h.getValue(), DEFAULT_SECONDS))
                .orElse(DEFAULT_SECONDS) * 1000;
    }
}

 

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/moneywenxue/article/details/109787242

智能推荐

使用nginx解决浏览器跨域问题_nginx不停的xhr-程序员宅基地

文章浏览阅读1k次。通过使用ajax方法跨域请求是浏览器所不允许的,浏览器出于安全考虑是禁止的。警告信息如下:不过jQuery对跨域问题也有解决方案,使用jsonp的方式解决,方法如下:$.ajax({ async:false, url: 'http://www.mysite.com/demo.do', // 跨域URL ty..._nginx不停的xhr

在 Oracle 中配置 extproc 以访问 ST_Geometry-程序员宅基地

文章浏览阅读2k次。关于在 Oracle 中配置 extproc 以访问 ST_Geometry,也就是我们所说的 使用空间SQL 的方法,官方文档链接如下。http://desktop.arcgis.com/zh-cn/arcmap/latest/manage-data/gdbs-in-oracle/configure-oracle-extproc.htm其实简单总结一下,主要就分为以下几个步骤。..._extproc

Linux C++ gbk转为utf-8_linux c++ gbk->utf8-程序员宅基地

文章浏览阅读1.5w次。linux下没有上面的两个函数,需要使用函数 mbstowcs和wcstombsmbstowcs将多字节编码转换为宽字节编码wcstombs将宽字节编码转换为多字节编码这两个函数,转换过程中受到系统编码类型的影响,需要通过设置来设定转换前和转换后的编码类型。通过函数setlocale进行系统编码的设置。linux下输入命名locale -a查看系统支持的编码_linux c++ gbk->utf8

IMP-00009: 导出文件异常结束-程序员宅基地

文章浏览阅读750次。今天准备从生产库向测试库进行数据导入,结果在imp导入的时候遇到“ IMP-00009:导出文件异常结束” 错误,google一下,发现可能有如下原因导致imp的数据太大,没有写buffer和commit两个数据库字符集不同从低版本exp的dmp文件,向高版本imp导出的dmp文件出错传输dmp文件时,文件损坏解决办法:imp时指定..._imp-00009导出文件异常结束

python程序员需要深入掌握的技能_Python用数据说明程序员需要掌握的技能-程序员宅基地

文章浏览阅读143次。当下是一个大数据的时代,各个行业都离不开数据的支持。因此,网络爬虫就应运而生。网络爬虫当下最为火热的是Python,Python开发爬虫相对简单,而且功能库相当完善,力压众多开发语言。本次教程我们爬取前程无忧的招聘信息来分析Python程序员需要掌握那些编程技术。首先在谷歌浏览器打开前程无忧的首页,按F12打开浏览器的开发者工具。浏览器开发者工具是用于捕捉网站的请求信息,通过分析请求信息可以了解请..._初级python程序员能力要求

Spring @Service生成bean名称的规则(当类的名字是以两个或以上的大写字母开头的话,bean的名字会与类名保持一致)_@service beanname-程序员宅基地

文章浏览阅读7.6k次,点赞2次,收藏6次。@Service标注的bean,类名:ABDemoService查看源码后发现,原来是经过一个特殊处理:当类的名字是以两个或以上的大写字母开头的话,bean的名字会与类名保持一致public class AnnotationBeanNameGenerator implements BeanNameGenerator { private static final String C..._@service beanname

随便推点

二叉树的各种创建方法_二叉树的建立-程序员宅基地

文章浏览阅读6.9w次,点赞73次,收藏463次。1.前序创建#include&lt;stdio.h&gt;#include&lt;string.h&gt;#include&lt;stdlib.h&gt;#include&lt;malloc.h&gt;#include&lt;iostream&gt;#include&lt;stack&gt;#include&lt;queue&gt;using namespace std;typed_二叉树的建立

解决asp.net导出excel时中文文件名乱码_asp.net utf8 导出中文字符乱码-程序员宅基地

文章浏览阅读7.1k次。在Asp.net上使用Excel导出功能,如果文件名出现中文,便会以乱码视之。 解决方法: fileName = HttpUtility.UrlEncode(fileName, System.Text.Encoding.UTF8);_asp.net utf8 导出中文字符乱码

笔记-编译原理-实验一-词法分析器设计_对pl/0作以下修改扩充。增加单词-程序员宅基地

文章浏览阅读2.1k次,点赞4次,收藏23次。第一次实验 词法分析实验报告设计思想词法分析的主要任务是根据文法的词汇表以及对应约定的编码进行一定的识别,找出文件中所有的合法的单词,并给出一定的信息作为最后的结果,用于后续语法分析程序的使用;本实验针对 PL/0 语言 的文法、词汇表编写一个词法分析程序,对于每个单词根据词汇表输出: (单词种类, 单词的值) 二元对。词汇表:种别编码单词符号助记符0beginb..._对pl/0作以下修改扩充。增加单词

android adb shell 权限,android adb shell权限被拒绝-程序员宅基地

文章浏览阅读773次。我在使用adb.exe时遇到了麻烦.我想使用与bash相同的adb.exe shell提示符,所以我决定更改默认的bash二进制文件(当然二进制文件是交叉编译的,一切都很完美)更改bash二进制文件遵循以下顺序> adb remount> adb push bash / system / bin /> adb shell> cd / system / bin> chm..._adb shell mv 权限

投影仪-相机标定_相机-投影仪标定-程序员宅基地

文章浏览阅读6.8k次,点赞12次,收藏125次。1. 单目相机标定引言相机标定已经研究多年,标定的算法可以分为基于摄影测量的标定和自标定。其中,应用最为广泛的还是张正友标定法。这是一种简单灵活、高鲁棒性、低成本的相机标定算法。仅需要一台相机和一块平面标定板构建相机标定系统,在标定过程中,相机拍摄多个角度下(至少两个角度,推荐10~20个角度)的标定板图像(相机和标定板都可以移动),即可对相机的内外参数进行标定。下面介绍张氏标定法(以下也这么称呼)的原理。原理相机模型和单应矩阵相机标定,就是对相机的内外参数进行计算的过程,从而得到物体到图像的投影_相机-投影仪标定

Wayland架构、渲染、硬件支持-程序员宅基地

文章浏览阅读2.2k次。文章目录Wayland 架构Wayland 渲染Wayland的 硬件支持简 述: 翻译一篇关于和 wayland 有关的技术文章, 其英文标题为Wayland Architecture .Wayland 架构若是想要更好的理解 Wayland 架构及其与 X (X11 or X Window System) 结构;一种很好的方法是将事件从输入设备就开始跟踪, 查看期间所有的屏幕上出现的变化。这就是我们现在对 X 的理解。 内核是从一个输入设备中获取一个事件,并通过 evdev 输入_wayland

推荐文章

热门文章

相关标签