motan源码分析一:服务发布及注册

article/2025/9/11 3:57:13

motan是新浪微博开源的服务治理框架,具体介绍请看:http://tech.sina.com.cn/i/2016-05-10/doc-ifxryhhh1869879.shtml.

本系列的文章将分析它的底层源码,分析的源码版本为:0.1.2。第一篇文章将以服务的发布和注册开始,注册服务使用zookeeper来分析。源码地址:https://github.com/weibocom/motan

本文涉及到的主要类和接口:MotanApiExportDemo、MotanDemoService、MotanDemoServiceImpl、ServiceConfig、RegistryConfig、ProtocolConfig、DefaultProvider、ZookeeperRegistryFactory、ZookeeperRegistry、SimpleConfigHandler、ProtocolFilterDecorator等。

1.首先来看demo源码:MotanApiExportDemo 

    demo中先后创建了ServiceConfig、RegistryConfig和ProtocolConfig相关的对象,其中ServiceConfig是我们提供服务的相关配置(每个服务一个配置,例如一个服务接口一个配置,本文中的具体服务是:MotanDemoServiceImpl)、RegistryConfig是注册中心相关的配置信息、ProtocolConfig是应用协议相关的配置(在客户端还负责集群相关的配置)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
ServiceConfig<MotanDemoService> motanDemoService =  new  ServiceConfig<MotanDemoService>();
// 设置接口及实现类
motanDemoService.setInterface(MotanDemoService. class ); //设置服务接口,客户端在rpc调用时,会在协议中传递接口名称,从而实现与具体实现类一一对应
motanDemoService.setRef( new  MotanDemoServiceImpl()); //设置接口实现类,实际的业务代码
// 配置服务的group以及版本号
motanDemoService.setGroup( "motan-demo-rpc" ); //服务所属的组
motanDemoService.setVersion( "1.0" );
// 配置ZooKeeper注册中心
RegistryConfig zookeeperRegistry =  new  RegistryConfig();
zookeeperRegistry.setRegProtocol( "zookeeper" ); //使用zookeeper作为注册中心
zookeeperRegistry.setAddress( "127.0.0.1:2181" ); //zookeeper的连接地址
motanDemoService.setRegistry(zookeeperRegistry);
// 配置RPC协议
ProtocolConfig protocol =  new  ProtocolConfig();
protocol.setId( "motan" ); //使用motan应用协议
protocol.setName( "motan" );
motanDemoService.setProtocol(protocol);
motanDemoService.setExport( "motan:8010" ); //本服务的监控端口号是8010
motanDemoService.export(); //发布及在zookeeper上注册此服务

2.从上面的代码可知ServiceConfig类是服务的发布及注册的核心是motanDemoService.export()方法,我们来看一下此方法的实现细节:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public  synchronized  void  export()
{
     if (exported.get())
     {
         LoggerUtil.warn(String.format( "%s has already been expoted, so ignore the export request!" new  Object[] {
             interfaceClass.getName()
         }));
         return ;
     }
     checkInterfaceAndMethods(interfaceClass, methods);
     List registryUrls = loadRegistryUrls(); //加载注册中心的url,支持多个注册中心
     if (registryUrls ==  null  || registryUrls.size() ==  0 )
         throw  new  IllegalStateException(( new  StringBuilder( "Should set registry config for service:" )).append(interfaceClass.getName()).toString());
     Map protocolPorts = getProtocolAndPort();
     ProtocolConfig protocolConfig;
     Integer port;
     for (Iterator iterator = protocols.iterator(); iterator.hasNext(); doExport(protocolConfig, port.intValue(), registryUrls)) //发布服务
     {
         protocolConfig = (ProtocolConfig)iterator.next();
         port = (Integer)protocolPorts.get(protocolConfig.getId());
         if (port ==  null )
             throw  new  MotanServiceException(String.format( "Unknow port in service:%s, protocol:%s" new  Object[] {
                 interfaceClass.getName(), protocolConfig.getId()
             }));
     }
     afterExport();
}

 方法中调用了doexport和afterExport方法:

复制代码
    private void doExport(ProtocolConfig protocolConfig, int port, List registryURLs){String protocolName = protocolConfig.getName();//获取协议名称,此处为motanif(protocolName == null || protocolName.length() == 0)protocolName = URLParamType.protocol.getValue();String hostAddress = host;//本机地址if(StringUtils.isBlank(hostAddress) && basicServiceConfig != null)hostAddress = basicServiceConfig.getHost();if(NetUtils.isInvalidLocalHost(hostAddress))hostAddress = getLocalHostAddress(registryURLs);Map map = new HashMap();map.put(URLParamType.nodeType.getName(), "service");map.put(URLParamType.refreshTimestamp.getName(), String.valueOf(System.currentTimeMillis()));collectConfigParams(map, new AbstractConfig[] {protocolConfig, basicServiceConfig, extConfig, this});collectMethodConfigParams(map, getMethods());URL serviceUrl = new URL(protocolName, hostAddress, port, interfaceClass.getName(), map);//组装serviceUrl信息if(serviceExists(serviceUrl))//判断服务之前是否已经加载过{LoggerUtil.warn(String.format("%s configService is malformed, for same service (%s) already exists ", new Object[] {interfaceClass.getName(), serviceUrl.getIdentity()}));throw new MotanFrameworkException(String.format("%s configService is malformed, for same service (%s) already exists ", new Object[] {interfaceClass.getName(), serviceUrl.getIdentity()}), MotanErrorMsgConstant.FRAMEWORK_INIT_ERROR);//抛出同名服务异常}List urls = new ArrayList();if("injvm".equals(protocolConfig.getId())){URL localRegistryUrl = null;for(Iterator iterator2 = registryURLs.iterator(); iterator2.hasNext();){URL ru = (URL)iterator2.next();if("local".equals(ru.getProtocol())){localRegistryUrl = ru.createCopy();break;}}if(localRegistryUrl == null)localRegistryUrl = new URL("local", hostAddress, 0, com/weibo/api/motan/registry/RegistryService.getName());urls.add(localRegistryUrl);} else{URL ru;for(Iterator iterator = registryURLs.iterator(); iterator.hasNext(); urls.add(ru.createCopy()))ru = (URL)iterator.next();}URL u;for(Iterator iterator1 = urls.iterator(); iterator1.hasNext(); registereUrls.add(u.createCopy())){u = (URL)iterator1.next();u.addParameter(URLParamType.embed.getName(), StringTools.urlEncode(serviceUrl.toFullStr()));}ConfigHandler configHandler = (ConfigHandler)ExtensionLoader.getExtensionLoader(com/weibo/api/motan/config/handler/ConfigHandler).getExtension("default");//使用spi机制加载SimpleConfigHandlerexporters.add(configHandler.export(interfaceClass, ref, urls));//调用SimpleConfigHandler的export方法initLocalAppInfo(serviceUrl);}private void afterExport(){exported.set(true);Exporter ep;for(Iterator iterator = exporters.iterator(); iterator.hasNext(); existingServices.add(ep.getProvider().getUrl().getIdentity()))ep = (Exporter)iterator.next();}
复制代码

 再来看一下SimpleConfigHandler的export方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public  <T> Exporter<T> export(Class<T> interfaceClass, T ref, List<URL> registryUrls) {
     String serviceStr = StringTools.urlDecode(registryUrls.get( 0 ).getParameter(URLParamType.embed.getName()));
     URL serviceUrl = URL.valueOf(serviceStr);
     // export service
     // 利用protocol decorator来增加filter特性
     String protocolName = serviceUrl.getParameter(URLParamType.protocol.getName(), URLParamType.protocol.getValue());
     Protocol protocol =  new  ProtocolFilterDecorator(ExtensionLoader.getExtensionLoader(Protocol. class ).getExtension(protocolName)); //对于Protoclo对象增强filter
     Provider<T> provider =  new  DefaultProvider<T>(ref, serviceUrl, interfaceClass);服务的代理提供者,包装ref的服务
     Exporter<T> exporter = protocol.export(provider, serviceUrl); //发布服务,将代理对象provider与具体的serviceUrl关联
     // register service
     register(registryUrls, serviceUrl);
     return  exporter;
}

3.下面我们来看一下,motan如何对filter进行相应的增强处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
public  class  ProtocolFilterDecorator  implements  Protocol {  //实现Protocol的接口,联系到上文中使用此类对实际的Protocol进行包装
     private  Protocol protocol;
     public  ProtocolFilterDecorator(Protocol protocol) {
         if  (protocol ==  null ) {
             throw  new  MotanFrameworkException( "Protocol is null when construct ProtocolFilterDecorator" ,
                     MotanErrorMsgConstant.FRAMEWORK_INIT_ERROR);
         }
         this .protocol = protocol; //给实际的Protocol进行赋值
     }
     @Override
     public  <T> Exporter<T> export(Provider<T> provider, URL url) {
         return  protocol.export(decorateWithFilter(provider, url), url);发布服务时,调用filter增强处理方法
     }
     private  <T> Provider<T> decorateWithFilter(Provider<T> provider, URL url) {
         List<Filter> filters = getFilters(url, MotanConstants.NODE_TYPE_SERVICE); //获取实际需要增强的filter
         if  (filters ==  null  || filters.size() ==  0 ) {
             return  provider;
         }
         Provider<T> lastProvider = provider;
         for  (Filter filter : filters) { //对于代理对象provider进行包装,包装成一个provider链,返回最后一个provider
             final  Filter f = filter;
             final  Provider<T> lp = lastProvider;
             lastProvider =  new  Provider<T>() {
                 @Override
                 public  Response call(Request request) {
                     return  f.filter(lp, request); //对于后面调用的call方法时,首先调用最外层的filter,最后再调用实际的provider的call方法
                 }
                 @Override
                 public  String desc() {
                     return  lp.desc();
                 }
                 @Override
                 public  void  destroy() {
                     lp.destroy();
                 }
                 @Override
                 public  Class<T> getInterface() {
                     return  lp.getInterface();
                 }
                 @Override
                 public  URL getUrl() {
                     return  lp.getUrl();
                 }
                 @Override
                 public  void  init() {
                     lp.init();
                 }
                 @Override
                 public  boolean  isAvailable() {
                     return  lp.isAvailable();
                 }
             };
         }
         return  lastProvider;
     }
     private  List<Filter> getFilters(URL url, String key) {
         // load default filters
         List<Filter> filters =  new  ArrayList<Filter>();
         List<Filter> defaultFilters = ExtensionLoader.getExtensionLoader(Filter. class ).getExtensions(key); //使用spi机制初始化filer对象
         if  (defaultFilters !=  null  && defaultFilters.size() >  0 ) {
             filters.addAll(defaultFilters);
         }
         // add filters via "filter" config
         String filterStr = url.getParameter(URLParamType.filter.getName());
         if  (StringUtils.isNotBlank(filterStr)) {
             String[] filterNames = MotanConstants.COMMA_SPLIT_PATTERN.split(filterStr);
             for  (String fn : filterNames) {
                 addIfAbsent(filters, fn);
             }
         }
         // add filter via other configs, like accessLog and so on
         boolean  accessLog = url.getBooleanParameter(URLParamType.accessLog.getName(), URLParamType.accessLog.getBooleanValue());
         if  (accessLog) {
             addIfAbsent(filters, AccessLogFilter. class .getAnnotation(SpiMeta. class ).name());
         }
         // sort the filters
         Collections.sort(filters,  new  ActivationComparator<Filter>());
         Collections.reverse(filters);
         return  filters;
     }
}

4.服务发布完成后,需要像注册中心注册此服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private  void  register(List<URL> registryUrls, URL serviceUrl) {
     for  (URL url : registryUrls) { //循环便利多个注册中心的信息
         // 根据check参数的设置,register失败可能会抛异常,上层应该知晓
         RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory. class ).getExtension(url.getProtocol()); //文中使用的是zookeeper
         if  (registryFactory ==  null ) {
             throw  new  MotanFrameworkException( new  MotanErrorMsg( 500 , MotanErrorMsgConstant.FRAMEWORK_REGISTER_ERROR_CODE,
                     "register error! Could not find extension for registry protocol:"  + url.getProtocol()
                             ", make sure registry module for "  + url.getProtocol() +  " is in classpath!" ));
         }
         Registry registry = registryFactory.getRegistry(url); //获取registry
         registry.register(serviceUrl); //将服务注册到zookeeper,也就是把节点信息写入到zookeeper中
     }
}

我们来看一下zookeeper注册中心的工厂类:每个Registry都需要独立维护一个ZkClient与zookeeper的链接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@SpiMeta (name =  "zookeeper" )
public  class  ZookeeperRegistryFactory  extends  AbstractRegistryFactory {
     @Override
     protected  Registry createRegistry(URL registryUrl) {
         try  {
             int  timeout = registryUrl.getIntParameter(URLParamType.connectTimeout.getName(), URLParamType.connectTimeout.getIntValue());
             int  sessionTimeout =
                     registryUrl.getIntParameter(URLParamType.registrySessionTimeout.getName(),
                             URLParamType.registrySessionTimeout.getIntValue());
             ZkClient zkClient =  new  ZkClient(registryUrl.getParameter( "address" ), sessionTimeout, timeout); //创建zookeeper的客户端
             return  new  ZookeeperRegistry(registryUrl, zkClient); //创建实际的Registry
         catch  (ZkException e) {
             LoggerUtil.error( "[ZookeeperRegistry] fail to connect zookeeper, cause: "  + e.getMessage());
             throw  e;
         }
     }
}

我们再来分析ZookeeperRegistry中的代码

复制代码
    public ZookeeperRegistry(URL url, ZkClient client) {super(url);this.zkClient = client;IZkStateListener zkStateListener = new IZkStateListener() {@Overridepublic void handleStateChanged(Watcher.Event.KeeperState state) throws Exception {// do nothing
            }@Overridepublic void handleNewSession() throws Exception {//响应zkClient的事件LoggerUtil.info("zkRegistry get new session notify.");reconnectService();//重新注册服务reconnectClient();}};zkClient.subscribeStateChanges(zkStateListener);}private void reconnectService() {Collection<URL> allRegisteredServices = getRegisteredServiceUrls();if (allRegisteredServices != null && !allRegisteredServices.isEmpty()) {try {serverLock.lock();for (URL url : getRegisteredServiceUrls()) {doRegister(url);//注册}LoggerUtil.info("[{}] reconnect: register services {}", registryClassName, allRegisteredServices);for (URL url : availableServices) {if (!getRegisteredServiceUrls().contains(url)) {LoggerUtil.warn("reconnect url not register. url:{}", url);continue;}doAvailable(url);//标识服务可以提供服务}LoggerUtil.info("[{}] reconnect: available services {}", registryClassName, availableServices);} finally {serverLock.unlock();}}}protected void doRegister(URL url) {try {serverLock.lock();// 防止旧节点未正常注销
            removeNode(url, ZkNodeType.AVAILABLE_SERVER);removeNode(url, ZkNodeType.UNAVAILABLE_SERVER);createNode(url, ZkNodeType.UNAVAILABLE_SERVER);} catch (Throwable e) {throw new MotanFrameworkException(String.format("Failed to register %s to zookeeper(%s), cause: %s", url, getUrl(), e.getMessage()), e);} finally {serverLock.unlock();}}protected void doAvailable(URL url) {try{serverLock.lock();if (url == null) {availableServices.addAll(getRegisteredServiceUrls());for (URL u : getRegisteredServiceUrls()) {removeNode(u, ZkNodeType.AVAILABLE_SERVER);removeNode(u, ZkNodeType.UNAVAILABLE_SERVER);createNode(u, ZkNodeType.AVAILABLE_SERVER);}} else {availableServices.add(url);removeNode(url, ZkNodeType.AVAILABLE_SERVER);removeNode(url, ZkNodeType.UNAVAILABLE_SERVER);createNode(url, ZkNodeType.AVAILABLE_SERVER);}} finally {serverLock.unlock();}}
1
2
3
4
5
6
7
private  void  createNode(URL url, ZkNodeType nodeType) {
     String nodeTypePath = ZkUtils.toNodeTypePath(url, nodeType);
     if  (!zkClient.exists(nodeTypePath)) {
         zkClient.createPersistent(nodeTypePath,  true ); //对于服务的标识信息,创建持久化节点
     }
     zkClient.createEphemeral(ZkUtils.toNodePath(url, nodeType), url.toFullStr()); //对于服务的ip和端口号信息使用临时节点,当服务断了后,zookeeper自动摘除目标服务器
}
 
复制代码

      本文分析了motan的服务发布及注册到zookeeper的流程相关的源码,主要涉及到的知识点:

1.利用相关的配置对象进行信息的存储及传递;

2.利用provider对具体的业务类进行封装代理;

3.利用filter链的结构,来包装实际的provider,把所有的过滤器都处理完毕后,最后调用实际的业务类,大家可以想象一下aop相关的原理,有些类似;

4.代码中大量使用jdk的标准spi技术进行类的加载;

5.支持多个注册中心,也就是同一个服务可以注册到不同的注册中心上,每个registry对应一个具体的zkclient;

6.利用了zookeeper的临时节点来维护服务器的host和port信息;

7.支持多个服务发布到同一个端口,在本文中并没分析netty使用相关的代码,后面会分析到。


http://chatgpt.dhexx.cn/article/7FF1B0PF.shtml

相关文章

轻量级Rpc框架设计--motan源码解析六:client端服务发现

一, Client端初始化工作 client端通过RefererConfigBean类实现InitializingBean接口的afterPropertiesSet方法, 进行下面三项检查配置工作: ①checkAndConfigBasicConfig(); // 检查并配置basicConfig ②checkAndConfigProtocols(); //检查并配置protocols ③checkAndConfi…

java rpc motan_【RPC 专栏】Motan 中使用异步 RPC 接口

为什么慢&#xff1f; 多线程加速 异步调用 RPC 异步调用 总结 这周六参加了一个美团点评的技术沙龙&#xff0c;其中一位老师在介绍他们自研的 RPC 框架时提到一点&#xff1a;RPC 请求分为 sync&#xff0c;future&#xff0c;callback&#xff0c;oneway&#xff0c;并且需要…

Motan-远程调用的rpc框架的负载均衡策略

虽然我不会&#xff0c;但是偶然之间看到了Motan远程调用框架的一些内容&#xff0c;然后直接copy过来了&#xff0c;想着以后自己可能看看 集群中的loadbalance负载均衡策略 ActiveWeightLoadBalance"低并发优化" 负载均衡 /** Copyright 2009-2016 Weibo, Inc.** …

motan源码分析五:cluster相关

上一章我们分析了客户端调用服务端相关的源码&#xff0c;但是到了cluster里面的部分我们就没有分析了&#xff0c;本章将深入分析cluster和它的相关支持类。 1.clustersupport的创建过程&#xff0c;上一章的ReferConfig的initRef()方法中调用了相关的创建代码&#xff1a; fo…

java 微博 开源_微博开源框架Motan初体验

前两天&#xff0c;我在开源中国的微信公众号看到新浪微博的轻量Rpc框架——Motan开源了。上网查了下&#xff0c;才得知这个Motan来头不小&#xff0c;支撑着新浪微博的千亿调用&#xff0c;曾经在2014年的春晚中有着千亿次的调用&#xff0c;对抗了春晚的最高峰值。 什么是Mo…

搭建新浪RPC框架motan Demo

motan是新浪微博开源的RPC框架&#xff0c;github官网是&#xff1a;https://github.com/weibocom/motan 今天就先搭建一个Hello world demo&#xff0c;本demo基于motan 0.2.1版本 首先先去github下载源代码&#xff08;motan-manager报错请忽略&#xff0c;eclipse的web Mod…

微博RPC框架Motan

原文来自&#xff1a;http://blog.csdn.net/autfish/article/details/51374798 从14年开始就陆续看到新浪微博RPC框架Motan的介绍&#xff0c;时隔两年后&#xff0c;微博团队终于宣布开源轻量级RPC框架Motan&#xff0c;项目地址&#xff1a; https://github.com/weibocom/mot…

motan rpc 接口统一异常处理

1.hello word 一个Motan扩展 大概需要下面的三点&#xff1a; 实现SPI扩展点接口 package com.weibo.api.motan.filter; Spi public interface Filter {Response filter(Caller<?> caller, Request request); }业务代码实现Filter public class PlsProviderExceptionF…

motan用户开发指南

目录 基本介绍 架构概述 模块概述 配置概述 使用Motan 工程依赖 处理调用异常 配置说明 协议与连接&#xff08;motan:protocol) 介绍 Motan协议 本地调用 注册中心与服务发现(motan:registry) 介绍 使用Consul作为注册中心 使用Zookeeper作为注册中心 不使用…

从motan看RPC框架设计

kris的文章开始 计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决 从零开发一款RPC框架&#xff0c;说难也难说简单也简单。难的是你的设计将如何面对实际中的复杂应用场景&#xff1b;简单的是其思想可以仅仅浓缩成一行方法调用。motan是今年(2016年)新浪微博…

motan与zookeeper框架

新浪科技讯 2016年5月10日&#xff0c;微博方面宣布&#xff0c;支撑微博千亿调用的轻量级 RPC 框架 Motan 正式开源了。微博技术团队希望未来能有更多优秀的开源人入驻&#xff0c;并进一步完善优化。 搭建新浪RPC框架motan Demo&#xff1a;http://blog.csdn.net/linuu/arti…

java rpc motan_RPC框架motan使用

简介 motan是新浪微博开源的一套轻量级、方便使用的RPC框架 Hello World 使用的过程分为Server端和Client端&#xff0c;Server提供RCP的服务接口&#xff0c;Client端发起调用获取结果。 maven的pom文件配置 0.2.1 com.weibo motan-core ${motan.version} com.weibo motan-tra…

轻量级Rpc框架设计--motan源码解析一:框架介绍及框架使用初体验

一, 框架介绍 1.1 概况 motan是新浪微博开源出来的一套高性能、易于使用的分布式远程服务调用(RPC)框架。 1.2 功能 可以spring的配置方式与项目集成. 支持zookeeper服务发现组件, 实现集群环境下服务注册与发现. 保证高并发, 高负载场景下的稳定高性能, , 实现生产环境…

Motan原理、使用、JavaAPI简化、为什么使用Motan

前言&#xff0c;本文包括&#xff0c;rpc解释与为什么使用rpc、rpc性能对比、Motan依赖问题、Motan源码梳理、Motan功能、特点、使用。 主要中心&#xff1a;为什么使用Motan? 一、什么是RPC 官方解释&#xff1a;RPC&#xff08;Remote Procedure Call&#xff09;—远程…

jplayer自动播放

音乐网站的播放器一直都没有解决自动播放的问题&#xff0c;小哲说这样不行的&#xff0c;我也知道不可以这样&#xff0c;毕竟是自己提出要做的&#xff0c;所以要尽自己最大的能力去做好它&#xff01;本周末我一直都在围绕这个问题而研究。 我曾经想过在播放器初始化的时候…

JWPlayer

原文&#xff1a; http://www.cnblogs.com/yukui/archive/2009/03/12/1409469.html The JW MP3 Player (built with Adobes Flash) is the easiest way to add live music or podcasts to your website. It supports playback of a single MP3 file or an RSS, XSPF or ASX pla…

今天开始写些随笔,就从Jplayer开始吧

今天才开始用Jplayer&#xff0c;可能有点落伍了&#xff0c;但是看到网上千篇一律的使用说明&#xff0c;开始决定把自己的使用心得分享一下&#xff0c;废话不多说&#xff0c;开始吧。 Step1&#xff1a; 官网上有具体的搭建顺序&#xff0c;URL&#xff1a;http://www.jp…

关于播放器JPlayer的使用及遇到的问题

jPlayer是一个用于控制和播放mp3文件的jQuery插件。它在后台使用Flash来播放mp3文件&#xff0c;前台播放器外观完全可以使用XHML/CSS自定义。支持&#xff1a; 有一点比较好的是&#xff0c;在支持html5的浏览器上会使用html5的标签audio或者video&#xff0c;而不支持的浏览…

ijkplayer支持播放rtsp、jpeg、gif

ijkplayer版本&#xff1a;k.0.8.8 编译环境&#xff1a;Ubuntu 18.04.6 LTS 使用平台&#xff1a;android 支持rtsp播放 默认的ijkplayer并不支持rtsp流的播放&#xff0c;因为在编译ffmpeg的时候并没有开启rtsp的demuxer&#xff0c;所以在编译ffmpeg的时候需要开启rtsp的d…

【ijkplayer】介绍

【ijkplayer】介绍 0x1 系统架构 ijkplayer是由b站开源的播放器项目&#xff0c;底层基于ffmpeg, 支持Android和iOS。下面我们来简单介绍一下Android上的实现。 Android上的系统架构图如下。 下面分别对各个模块进行介绍&#xff1a; 0x11 ijkplayer-example app的实现&a…