1.statfs系统调用
2.超详细!spdlog源码解析(下)
3.SpringCloud原理OpenFeign原来是这么基于Ribbon来实现负载均衡的
4.从 ExoPlayer 源码分析视频无法播放问题
5.音视频探索(6):浅析MediaCodec工作原理
statfs系统调用
系统调用:获取文件系统统计信息 - statfs与fstatfs statfs系统调用的主要目的是从已装载的文件系统中获取相关信息,通过指定路径,如"/"代表任何文件所在的文件系统。这个调用需要一个指向statfs结构的指针,该结构包含了如下的windows程序设计 源码关键信息:文件系统类型:这些MAGIC常量定义在/usr/include/linux/magic.h中,部分在内核源代码中硬编码。
f_flags:位掩码,指示文件系统挂载选项,如ST_MANDLOCK(强制锁定)、ST_NOATIME(不更新访问时间)、ST_NODEV(禁止设备文件访问)等。
其他选项如ST_RDONLY(只读)、ST_SYNCHRONOUS(写入同步)等。
调用成功时返回零,失败时返回-1并设置相应的错误代码,如EACCES(权限拒绝)、EBADF(无效描述符)、EFAULT(无效地址)等。 需要注意的是,Linux的statfs和fstatfs系统调用与4.4BSD有所不同,且在处理大文件时引入了statfs和fstatfs。另外,LSB不推荐使用库调用statfs和fstatfs,推荐使用statvfs和fstatvfs。关于f_fsid字段,不同操作系统有不同的处理方式,如Solaris、Irix和POSIX使用unsigned long的f_fsid,而Linux和FreeBSD则使用fsid_t结构。 最后,statfs和fstatfs在某些情况下可能会遇到bug,例如在Linux 2.6.至3.1期间,由pipe创建的文件描述符执行fstatfs时可能会返回ENOSYS错误。 欲了解更多详细信息,可查阅stat(2)、statvfs(2)和path_resolution(7)。超详细!spdlog源码解析(下)
回顾spdlog的组成,包含logger、藏头诗生成源码sink、formatter以及registry四个关键部分。在前两篇中,我们深入探讨了logger、sink和formatter的基本功能与使用方法。这三者协同工作,能够实现日志的记录功能。然而,registry作为管理器角色,主要负责协调和配置这些组件,确保日志系统的一致性和高效性。尽管registry并非必须依赖的组件,它的存在能够提供更加便捷的管理方式,例如统一设置日志等级、创建具有默认配置的logger等。
在默认logger和默认sink的实现中,registry扮演着关键角色。当使用spdlog::info方法时,实际上调用了registry中的default_logger_成员变量,获取默认logger的指针。通过静态方法registry::instance()获取registry对象,最终registry::registry()方法创建默认logger,并选择ansicolor_stdout_sink_mt作为sink,实现控制台彩色输出。这种设计使得用户无需深入了解内部细节,即可直接使用默认配置进行日志输出,简化了用户上手过程。
registry的功能不仅限于管理默认logger,它还提供了创建logger的便利接口。通过一系列预设的logger创建函数,spdlog实现了与不同sink的无缝集成,隐藏了sink的概念,使得用户仅需关注日志输出的目的地,而无需深入理解底层实现。例如,stdout_logger创建函数通过调用Factory::create方法,自动将创建的logger注册到registry中,实现日志输出格式的统一化和全局管理。对于异步环境,偷袭预警源码async_factory::create方法同样完成了类似功能,但需额外处理线程池的创建。
通过反思registry的实现,我们可以发现,其核心功能在于管理logger,而这一过程包含了将logger注册到registry中的关键步骤。通过提供Factory(如synchronous_factory或async_factory)的create方法,spdlog确保在创建logger后将其自动注册,这一设计与设计模式中的工厂方法原理相契合。实现这一目标的关键在于注册操作,而非创建logger本身,这突显了registry在spdlog系统中的核心作用。
在介绍spdlog的宏定义使用时,我们探讨了其支持的两种编译版本:header-only version和compiled version。header-only version通过将声明与实现分开,提供了轻量级的集成方式。要实现compiled version,只需复制header-only version的代码,并按照特定规则组织文件结构。在async.cpp文件中,通过SPDLOG_COMPILED_LIB宏定义判断编译方式,相应地include声明与实现文件,实现代码的高效复用。同时,SPDLOG_HEADER_ONLY宏定义控制了代码的包含行为,确保了不同编译方式下的代码正确性。
在多平台支持方面,spdlog通过os.h和os-inl.h文件封装了针对不同平台差异的处理逻辑,使得上层业务无需关注底层实现的细节。通过宏定义和条件编译,spdlog能够提供一致的接口,适应不同操作系统和环境的需求,确保跨平台兼容性和稳定性。
至此,spdlog源码解析系列告一段落。通过深入分析spdlog的架构设计、功能实现以及跨平台支持,我们不仅了解了如何高效地使用spdlog进行日志管理,还洞悉了其设计背后的齐鲁麻将源码巧妙逻辑和实践细节。希望本系列解析能够为开发者提供宝贵的参考,助力构建更加稳定、高效和易于维护的日志系统。
SpringCloud原理OpenFeign原来是这么基于Ribbon来实现负载均衡的
欢迎来到本篇文章,之前我们已经深入探讨了OpenFeign的动态代理生成原理和Ribbon的运行机制。若要对OpenFeign的动态代理生成原理和Ribbon的运行原理有更深入的了解,可关注微信公众号“三友的java日记”,通过菜单栏查看整理的相关内容。接下来,我们将继续深入SpringCloud组件原理,探讨OpenFeign是如何利用Ribbon实现负载均衡的,以及两组件如何协同工作的。
一、Feign动态代理调用实现rpc流程解析
我们从Feign客户端接口的动态代理生成原理出发,了解到动态代理基于JDK实现,所有方法调用最终都会调用到InvocationHandler接口的实现,即ReflectiveFeign.FeignInvocationHandler。接下来,我们将深入探讨FeignInvocationHandler如何实现rpc调用。
FeignInvocationHandler通过invoke方法实现动态代理功能,其主要逻辑如下:
1. 对于调用的方法是否为equals、hashCode、toString等特殊方法进行判断,若为则无需走rpc调用。
2. 从dispatch获取调用方法对应的MethodHandler,然后调用MethodHandler的invoke方法。
3. MethodHandler在构建动态代理时生成,作用是最终实现rpc调用,每个方法有对应的MethodHandler。
4. SynchronousMethodHandler是实现rpc调用的关键类,通过构造RequestTemplate、Options和重试组件,发起http请求,并通过Client接口执行请求,返回响应数据。
二、LoadBalancerFeignClient与Ribbon整合
从整个动态代理调用流程来看,Client是关键组件,负责发送http请求。租车php源码Feign是如何通过Ribbon实现负载均衡的呢?让我们进一步剖析。
通过整合配置类FeignRibbonClientAutoConfiguration,我们可以找到构造Feign.Builder实现的关键组件:LoadBalancerFeignClient。这个组件依赖于负载均衡,是Feign与Ribbon集成的入口。
LoadBalancerFeignClient实现的核心逻辑在于从请求的URL中提取服务名,通过缓存机制获取或创建FeignLoadBalancer,最终调用executeWithLoadBalancer方法发起请求,完成负载均衡与服务调用。
三、FeignLoadBalancer实现负载均衡与发送请求
FeignLoadBalancer是实现选择负载均衡和发送http请求的关键组件。其核心逻辑在于重构请求路径,将服务名替换为具体的服务器IP和端口,然后通过execute方法完成请求。
四、总结与图解
本篇文章完整阐述了OpenFeign、Ribbon与注册中心之间的协同工作原理。通过总结,我们了解到OpenFeign在进行RPC调用时,借助Ribbon选择负载均衡服务器,而Ribbon从注册中心获取服务器列表。通过这张图,我们可以清晰地看到三个组件之间的协同关系。
至此,我们完成了对这三个组件核心源码和流程的深入探讨。希望本篇文章能帮助读者理解微服务架构的基础原理,同时对OpenFeign、Ribbon、Nacos源码有更深入的认识。
从 ExoPlayer 源码分析视频无法播放问题
面对项目中出现的视频无法播放问题,我们在ExoPlayer三方库中发现了Decoder init failed的常见错误,即(ERROR_CODE_DECODER_INIT_FAILED)。在Google搜索未果后,我们决定深入源码以寻找问题根源。最终,通过源码分析,我们找到了问题所在并找到了解决方案,希望能为遇到类似问题的读者提供帮助。
对比应用,我们发现使用ExoPlayer播放动态壁纸在多个机型上均能正常工作,这有助于排除机型因素。随后,我们引入ExoPlayer库并创建了一个简单的Demo,测试对比后发现,虽然在特定机型上可以播放网络视频链接,但无法播放我们的视频链接。这提示我们可能是在视频格式上存在问题。
在源码分析中,我们发现MediaCodecVideoRenderer抛出的ExoPlaybackException是问题的关键。从调用栈关系可以看出,问题最终归咎于MediaCodecRenderer的maybeInitCodecWithFallback()方法。深入源码分析后,我们发现initCodec()方法调用时出现了异常,进一步导致了DecoderInitializationException。异常信息与日志显示一致,我们继续追踪initCodec()的逻辑。
通过断点调试,我们发现逻辑最终到达了DefaultMediaCodecAdapterFactory的createAdapter()方法,进一步跟进到SynchronousMediaCodecAdapter.Factory中的createAdapter()方法,最终调用了MediaCodec的configure()方法,导致异常。从源码中可以看出,无论逻辑是否执行到特定的if条件,最终都会调用到MediaCodec方法,因此无需关注if逻辑。
我们意识到最终调用的是C/C++代码,通常在Android端遇到此类异常时似乎无能为力。然而,我们从另一个角度思考问题,即在能够播放视频的机型和无法播放的机型之间是否存在参数差异。通过逐步回溯排查MediaCodecInfo对象的值,我们最终发现了关键逻辑代码。
分析后,我们得知首先通过getAvailableCodecInfos()方法获取一组可用解码器列表,然后通过逻辑判断将列表中的所有解码器或第一个添加到队列availableCodecInfos中。接下来,通过while循环不断从availableCodecInfos队列中取出第一个解码器进行初始化尝试,直到找到成功初始化的解码器为止。
从代码注释中,我们了解到enableDecoderFallback参数的含义,设置为true可能导致性能降低(软解性能不如硬解),但默认情况下优先初始化硬解。通过设置setEnableDecoderFallback(true),问题得以解决,从而实现了视频的正常播放。
音视频探索(6):浅析MediaCodec工作原理
MediaCodec类是Android平台用于访问低层多媒体编/解码器的接口,它是Android多媒体架构的一部分,通常与MediaExtractor、MediaMuxer、AudioTrack等工具配合使用,可以处理多种常见的音视频格式,包括H.、H.、AAC、3gp等。MediaCodec的工作原理是通过输入/输出缓存区同步或异步处理数据。客户端首先将要编解码的数据写入编解码器的输入缓存区,并提交给编解码器。编解码器处理后,数据转存到输出缓存区,同时收回客户端对输入缓存区的所有权。然后,客户端从编解码器的输出缓存区读取编码好的数据进行处理,读取完毕后编解码器收回客户端对输出缓存区的所有权。这一过程不断重复,直至编码器停止工作或异常退出。
在整个MediaCodec的使用过程中,会经历配置、启动、数据处理、停止、释放等步骤,对应的状态包括停止(Stopped)、执行(Executing)以及释放(Released),而Stopped状态又细分为未初始化(Uninitialized)、配置(Configured)、异常(Error),Executing状态细分为读写数据(Flushed)、运行(Running)和流结束(End-of-Stream)。当MediaCodec被创建后,它会处于未初始化状态,待设置好配置信息并调用start()方法启动后,它会进入运行状态,并可以进行数据读写操作。若在运行过程中出现错误,MediaCodec会进入Stopped状态。此时,使用reset方法来重置编解码器是必要的,否则MediaCodec所持有的资源最终会被释放。如果MediaCodec正常完成使用,可以向编解码器发送EOS指令,同时调用stop和release方法来终止编解码器的使用。
MediaCodec主要提供了createEncoderByType(String type)、createDecoderByType(String type)两个方法来创建编解码器,这两个方法需要传入一个MIME类型多媒体格式。常见的MIME类型多媒体格式有:image/jpeg、audio/amr、video/3gpp、video/h、video/avc等。此外,MediaCodec还提供了createByCodecName (String name)方法,可以使用组件的具体名称来创建编解码器,但这种方法的使用相对繁琐,且官方建议最好配合MediaCodecList使用,因为MediaCodecList记录了所有可用的编解码器。我们也可以使用MediaCodecList对传入的minmeType参数进行判断,以匹配出MediaCodec对该mineType类型的编解码器是否支持。例如,指定MIME类型为“video/avc”时,可以使用如下代码来创建H.编码器:
java
MediaCodecInfo.CodecCapabilities capabilities = MediaCodecList.getCodecCapabilities("video/avc");
if (capabilities != null) {
MediaCodec codec = MediaCodec.createByCodecName(capabilities.getName());
}
配置和启动编解码器使用MediaCodec的configure方法。这个方法首先提取MediaFormat存储的数据map,然后调用本地方法native_configure实现配置工作。在配置时,需要传入format、surface、crypto、flags参数。format是一个MediaFormat实例,它以“key-value”键值对的形式存储多媒体数据格式信息;surface用于指定解码器的数据源;crypto用于指定一个MediaCrypto对象,以便对媒体数据进行安全解密;flags指明配置的是编码器(CONFIGURE_FLAG_ENCODE)。对于H.编码器的配置,可以使用createVideoFormat("video/avc", , )方法创建“video/avc”类型的编码器的MediaFormat对象,并需要指定视频数据的宽高。如果处理音频数据,则可以调用MediaFormat的createAudioFormat(String mime, int sampleRate,int channelCount)方法。
配置完毕后,通过调用MediaCodec的start()方法启动编码器,并调用本地方法ByteBuffer[] getBuffers(input)开辟一系列输入、输出缓存区。start()方法的源码如下:
java
native_start();
ByteBuffer[] buffers = getBuffers(input);
MediaCodec支持同步(synchronous)和异步(asynchronous)两种编解码模式。同步模式下,编解码器的数据输入和输出是同步的,只有当输出数据处理完毕时,编解码器才会接收下一次输入数据。而异步模式下,输入和输出数据是异步的,编解码器不会等待输出数据处理完毕就接收下一次输入数据。这里主要介绍同步编解码模式,因为它更常用。当编解码器启动后,它会拥有输入和输出缓存区,但是这些缓存区暂时无法使用,需要通过MediaCodec的dequeueInputBuffer/dequeueOutputBuffer方法获取输入输出缓存区的授权,并通过返回的ID来操作这些缓存区。下面是一个官方提供的示例代码:
java
for (;;) {
ByteBuffer[] buffers = codec.dequeueInputBuffer();
if (buffers != null) {
// 处理输入缓存区
}
ByteBuffer[] outputBuffers = codec.dequeueOutputBuffer(new MediaCodec.BufferInfo(), );
if (outputBuffers != null) {
// 处理输出缓存区
}
}
获取编解码器的输入缓存区并写入数据。首先调用MediaCodec的dequeueInputBuffer(long timeoutUs)方法从编码器的输入缓存区集合中获取一个输入缓存区,并返回该缓存区的下标index。接着调用MediaCodec的getInputBuffer(int index),该方法返回缓存区的ByteBuffer,并将获得的ByteBuffer对象及其index存储到BufferMap对象中,以便在输入结束后释放缓存区并交还给编解码器。然后,在获得输入缓冲区后,将数据填入并使用queueInputBuffer将其提交到编解码器中处理,同时释放输入缓存区交还给编解码器。queueInputBuffer的源码如下:
java
native_queueInputBuffer(index, offset, size, presentationTimeUs, flags);
获取编解码器的输出缓存区并读出数据。与获取输入缓存区类似,MediaCodec提供了dequeueOutputBuffer和getOutputBuffer方法来获取输出缓存区。但是,在调用dequeueOutputBuffer时,还需要传入一个MediaCodec.BufferInfo对象,它记录了编解码好的数据在输出缓存区中的偏移量和大小。当调用本地方法native_dequeueOutputBuffer返回INFO_OUTPUT_BUFFERS_CHANGED时,会调用cacheBuffers方法重新获取一组输出缓存区。这意味着在使用getOutputBuffers方法(API 后被弃用,使用getOutputBuffer(index)代替)来获取输出缓存区时,需要在调用dequeueOutputBuffer时判断返回值,如果返回值为MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED,则需要重新获取输出缓存区集合。此外,还需要判断dequeueOutputBuffer的其他两个返回值:MediaCodec.INFO_TRY_AGAIN_LATER、MediaCodec.INFO_OUTPUT_FORMAT_CHANGED,以处理获取缓存区超时或输出数据格式改变的情况。最后,当输出缓存区的数据被处理完毕后,通过调用MediaCodec的releaseOutputBuffer释放输出缓存区,交还给编解码器。releaseOutputBuffer方法接收两个参数:Index、render,其中Index为输出缓存区索引,render表示当配置编码器时指定了surface,那么应该置为true,输出缓存区的数据将被传递到surface中。