Quantcast
Channel: IT瘾博客推荐
Viewing all 532 articles
Browse latest View live

微信公众号开发C#系列-11、生成带参数二维码应用场景 - C#快速开发框架 - CSDN博客

$
0
0

1、概述

我们在 微信公众号开发C#系列-7、消息管理-接收事件推送章节有对扫描带参数二维码事件的处理做了讲解。本篇主要讲解通过微信公众号开发平台提供的接口生成带参数的二维码及应用场景。

微信公众号平台提供了生成带参数二维码的接口,使用该接口可以获得多个带不同场景值的二维码,用户扫描后,公众号可以接收到事件推送。
目前有2种类型的二维码,分别是临时二维码和永久二维码,前者有过期时间,最大为1800秒,但能够生成较多数量,后者无过期时间,数量较少(目前参数只支持1–100000)。两种二维码分别适用于帐号绑定、用户来源统计等场景。

用户扫描带场景值二维码时,可能推送以下两种事件:

  1. 如果用户还未关注公众号,则用户可以关注公众号,关注后微信会将带场景值关注事件推送给开发者。

  2. 如果用户已经关注公众号,在用户扫描后会自动进入会话,微信也会将带场景值扫描事件推送给开发者。

获取带参数的二维码的过程包括两步,首先创建二维码ticket,然后凭借ticket到指定URL换取二维码。

2、创建二维码ticket

每次创建二维码ticket需要提供一个开发者自行设定的参数(scene_id),分别介绍临时二维码和永久二维码的创建二维码ticket过程。

2.1、创建临时二维码接口说明

http请求方式: POST

URL: https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=TOKEN
POST数据格式:json
POST数据例子:
{"expire_seconds": 604800, "action_name": "QR_SCENE", "action_info": {"scene": {"scene_id": 123}}}

或者也可以使用以下POST数据创建字符串形式的二维码参数:

{"expire_seconds": 604800, "action_name": "QR_STR_SCENE", "action_info": {"scene": {"scene_str": "test"}}}

2.2、创建永久二维码接口说明

http请求方式: POST
URL: https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=TOKEN
POST数据格式:json
POST数据例子:{"action_name": "QR_LIMIT_SCENE", "action_info": {"scene": {"scene_id": 123}}}

或者也可以使用以下POST数据创建字符串形式的二维码参数:
{"action_name": "QR_LIMIT_STR_SCENE", "action_info": {"scene": {"scene_str": "test"}}}

2.3、参数说明

参数说明

参数说明
expire_seconds该二维码有效时间,以秒为单位。 最大不超过2592000(即30天),此字段如果不填,则默认有效期为30秒。
action_name二维码类型,QR_SCENE为临时的整型参数值,QR_STR_SCENE为临时的字符串参数值,QR_LIMIT_SCENE为永久的整型参数值,QR_LIMIT_STR_SCENE为永久的字符串参数值
action_info 二维码详细信息
scene_id场景值ID,临时二维码时为32位非0整型,永久二维码时最大值为100000(目前参数只支持1–100000)
scene_str场景值ID(字符串形式的ID),字符串类型,长度限制为1到64

返回说明

正确的Json返回结果:

{"ticket":"gQH47joAAAAAAAAAASxodHRwOi8vd2VpeGluLnFxLmNvbS9xL2taZ2Z3TVRtNzJXV1Brb3ZhYmJJAAIEZ23sUwMEmm3sUw==","expire_seconds":60,"url":"http://weixin.qq.com/q/kZgfwMTm72WWPkovabbI"}
参数说明
ticket获取的二维码ticket,凭借此ticket可以在有效时间内换取二维码。
expire_seconds该二维码有效时间,以秒为单位。 最大不超过2592000(即30天)。
url二维码图片解析后的地址,开发者可根据该地址自行生成需要的二维码图片

2.4、通过ticket换取二维码

获取二维码ticket后,开发者可用ticket换取二维码图片。请注意,本接口无须登录态即可调用。
请求说明

HTTP GET请求(请使用https协议)https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=TICKET

提醒:TICKET记得进行UrlEncode

返回说明

ticket正确情况下,http 返回码是200,是一张图片,可以直接展示或者下载。

HTTP头(示例)如下:
Accept-Ranges:bytes
Cache-control:max-age=604800
Connection:keep-alive
Content-Length:28026
Content-Type:image/jpg
Date:Wed, 16 Oct 2013 06:37:10 GMT
Expires:Wed, 23 Oct 2013 14:37:10 +0800
Server:nginx/1.4.1
错误情况下(如ticket非法)返回HTTP错误码404。

3、创建与获取临时或永久二维码代码参考

我们可以直接使用Senparc.Weixin SDK提供的接口Senparc.Weixin.MP.AdvancedAPIs.QrCodeApi.Create来创建临时或永久二维码。

利用Senparc.Weixin.MP.AdvancedAPIs.QrCodeApi.GetShowQrCodeUrl来获取临时或永久二维码。

Senparc.Weixin.MP.AdvancedAPIs.QrCodeApi代码参考:

//API:http://mp.weixin.qq.com/wiki/index.php?title=%E7%94%9F%E6%88%90%E5%B8%A6%E5%8F%82%E6%95%B0%E7%9A%84%E4%BA%8C%E7%BB%B4%E7%A0%81

/// <summary>
/// 二维码接口
/// </summary>
public static class QrCode
{
    /// <summary>
    /// 创建二维码
    /// </summary>
    /// <param name="expireSeconds">该二维码有效时间,以秒为单位。 最大不超过1800。0时为永久二维码</param>
    /// <param name="sceneId">场景值ID,临时二维码时为32位整型,永久二维码时最大值为1000</param>
    /// <returns></returns>
    public static CreateQrCodeResult Create(string accessToken, int expireSeconds, int sceneId)
    {
        var urlFormat = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token={0}";
        object data = null;
        if (expireSeconds > 0)
        {
            data = new
            {
                expire_seconds = expireSeconds,
                action_name = "QR_SCENE",
                action_info = new
                {
                    scene = new
                    {
                        scene_id = sceneId
                    }
                }
            };
        }
        else
        {
            data = new
            {
                action_name = "QR_LIMIT_SCENE",
                action_info = new
                {
                    scene = new
                    {
                        scene_id = sceneId
                    }
                }
            };
        }
        return CommonJsonSend.Send<CreateQrCodeResult>(accessToken, urlFormat, data);
    }

    /// <summary>
    /// 获取二维码(不需要AccessToken)
    /// 错误情况下(如ticket非法)返回HTTP错误码404。
    /// </summary>
    /// <param name="ticket"></param>
    /// <param name="stream"></param>
    public static void ShowQrCode(string ticket, Stream stream)
    {
        var urlFormat = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket={0}";
        HttpUtility.Get.Download(string.Format(urlFormat, ticket), stream);
    }
}

4、二维码创建实现

要使用微信提供的永久或临时二维码的功能,我们需要界面来生成或获取二维码,如下图所示。
创建二维码
控制器代码参考:

[HttpPost]
[ValidateInput(false)]
[LoginAuthorize]
public ActionResult GenerateQrCode()
{
    string ticket = CacheFactory.Cache().GetCache<string>("Weixin-Qr-Ticket");
    if (string.IsNullOrEmpty(ticket))
    {
        WeixinOfficialAccountEntity currentWeixinOfficialAccountEntity = RDIFrameworkService.Instance.WeixinBasicService.GetCurrentOfficialAccountEntity(ManageProvider.Provider.Current());
        string token = currentWeixinOfficialAccountEntity.AccessToken;
        var result = Senparc.Weixin.MP.AdvancedAPIs.QrCodeApi.Create(token, 600, 10, Senparc.Weixin.MP.QrCode_ActionName.QR_SCENE);
        if (result.errcode == Senparc.Weixin.ReturnCode.请求成功)
        {
            ticket = result.ticket;
            CacheFactory.Cache().WriteCache<string>(result.ticket, "Weixin-Qr-Ticket", DateTime.Now.AddSeconds(600));
        }
    }

    string qrUrl = Senparc.Weixin.MP.AdvancedAPIs.QrCodeApi.GetShowQrCodeUrl(ticket);
    return Content(new JsonMessage { Success = true, Data = qrUrl, Type = ResultType.Success, Message = RDIFrameworkMessage.MSG3010 }.ToString());
}

上面的代码我们创建了一个场景值为 10的临时二维码。用户通过扫描这个二维码,我们就可以在服务器端做处理,扫描带参数二维码事件只需要重写OnEvent_ScanRequest事件代码即可,如下我们返回了一个文本消息,实现代码参考:

public override IResponseMessageBase OnEvent_ScanRequest(RequestMessageEvent_Scan requestMessage)
{
    //通过扫描关注
    var responseMessage = CreateResponseMessage<ResponseMessageText>();

    responseMessage.Content = responseMessage.Content ?? string.Format("欢迎关注国思软件,通过扫描二维码进入,场景值:{0}", requestMessage.EventKey);

    return responseMessage;
}

在上面的代码中用户扫描了带场景值的二维码进入公众号后我们返回了一个提示的文本消息。这是非常有用的功能,常用途推广,可以根据不同的二维码场景值分别做不同的业务处理,如可以统计关注的每一个粉丝从哪里来的,做到渠道推广分析,但是关注的都是同一个公众号。
通过扫描带场景值的二维码进入

5、生成带参数的二维码用途

微信公众号生成带参数的二维码有何用途?

  1. 可以区分粉丝来源,只需要生成不同的带参数的二维码,把这些二维码分别投放到各个渠道,粉丝通过这些渠道二维码进来就可以区分粉丝来源,微号帮后台渠道粉丝列表中有粉丝数及明细;
  2. 粉丝通过扫描渠道二维码关注公众号,会打标签分组,比如粉丝扫商店A、B的二维码进来的, 在微信公众号后来的用户管理中可查看到商店A/B二维码名下的粉丝明细及分组情况;
  3. 可以生成多个不同的渠道二维码配置不同的营销活动,设置不同的关注回复信息,让粉丝第一时间了解活动动机,是否有兴趣参与等等;
  4. 可以利用渠道二维码生成功能,可以实现微信收款前关注公众号,间接分析粉丝后续消费情况;
    考核推广员完成任务的进度,如以推广名字生成多不个同的二维码,分配给不同的推广员,每个推广员吸引了多少粉丝关注公众号,微号帮后台都可以一一明细;
  5. 带参数的二维码也叫渠道二维码或者场景二维码,生存的数量有限,且是永久二维码。当数量用完后可以删除一些不用的二维码释放出来,二次利用。
  6. 其他用途。

参考文章

微信公众平台技术文档-官方

Senparc.Weixin SDK + 官网示例源代码

RDIFramework.NET — 基于.NET的快速信息化系统开发框架 — 系列目录

RDIFramework.NET ━ .NET快速信息化系统开发框架 ━ 工作流程组件介绍

RDIFramework.NET框架SOA解决方案(集Windows服务、WinForm形式与IIS形式发布)-分布式应用

RDIFramework.NET代码生成器全新V3.5版本发布-重大升级


一路走来数个年头,感谢RDIFramework.NET框架的支持者与使用者,大家可以通过下面的地址了解详情。
RDIFramework.NET官方网站: http://www.rdiframework.net/
RDIFramework.NET官方博客: http://blog.rdiframework.net/
同时需要说明的,以后的所有技术文章以官方网站为准,欢迎大家收藏!
RDIFramework.NET框架由专业团队长期打造、一直在更新、一直在升级,请放心使用!
欢迎关注RDIFramework.net框架官方公众微信(微信号:guosisoft),及时了解最新动态。
扫描二维码立即关注


春招两次腾讯面试都挂二面,分享下我失败+傻傻的面试经历 - 帅地 - 博客园

$
0
0

这个春招估计也要介绍了吧,自己投的公司也不多吧,投简历的时候,如果你提前批和正常网申都投的话,可能会获得两次笔试/面试的机会,我投了两次腾讯,不过,两次都在二面挂了,特别是第二次二面,我真的决定自己太他妈傻了。作为一个新人,谈谈我面试过程中犯过的一些错吧,或许对你也有点收获。

提前批笔试

腾讯提前批的面试应该是一个月前就开始的,我第一个投的公司就是腾讯了,人生的第一次笔试和面试也献给了腾讯。先说下笔试吧,笔试是 5 道编程题,个人觉得,腾讯的笔试题比较简单吧,也就是说,获得腾讯的面试机会应该算是不难的吧,5 道我好像是做出了 3道 + 一道90%测试用例通过 + 一道也是百分之几十来着的,忘了。

最后的结论就是,个人感觉腾讯笔试不怎么难,投了之后获得面试的机会应该非常大,我的简历背景是没有任何亮点的,没有啥比赛,没拿过啥奖,非211/985,普普通通。这里建议秋招有兴趣的一定要投一下,万一面试也过了呢,好像我听说腾讯技术研发是 2轮技术 + 一轮HR面,万一2轮技术面过了拿offer机会还是挺大。

当然,我听说前端的 HR 面是综合面,听说比比前面的两轮还难还难过。

提前批一面

一面是视频面,人生第一次面试,可以说我是很紧张的,一面我完全就是个小白啥套路也不懂,面试官问啥我答啥,完全不会引导,面试完后我感觉自己是真的傻,主要是问了一下问题:

1、浏览器输入一个地址回车之后都发生了啥?

这个可以说是非常高频的面试题了,我觉得自己这方面还可以,压抑自己的紧张,好好详细秀一波,然而,由于我事先并没有去准备过这个问题,而这个问题又涉及到很多步骤,所以我在逻辑上讲非非常非常不好,卡住了好几下,最后不管三七二十一,就说关键词了:有DNS转换啊,ip网络寻址啊,三次挥手啊,可能还有NAT地址转换啊,还有ARP啊。

可能平时看我文章的人会发现,我是喜欢把这些技术一点点推出来,有逻辑着连起来的,这样说出来会特别舒服,不过我这次面试的时候,连不起来的,归根到底就是, 没有准备,头脑觉得自己都懂,但是不意味着讲的出来,所以我在这里是建议各位,对于那些高频的面试题,自己最后尝试着口头去表述一下,或者文字书写一下,要是你来回答,你会怎么说?

2、TCP 和 UTP 有什么区别?

我在回答这道题的时候,也是非常简单着回答他们的区别,例如一个面向连接一个非面向连接,一个可靠一个非可靠,非常简单就答完的。

不过这道题是一个高频题目,按上面的那个回答,就算回答出来,我觉得也是一点优势都没有。因为这些简单的区别,99%的人都懂,我觉得我们应该再往深入回答,例如TCP有流量控制,拥塞控制,面向流以及基于这些区别,他们的使用场景等等。

不过我并没有回答,不是我不知道,而是我想假装不说,等着他来问,因为我听说面试官都会一直往深问到你不会为止,然而现实是他并没有问,换话题了。感觉自己错过了一些表现的机会。

这里我想说的就是:如果你知道,你可以多说一点,千万别像我这么傻,等着别人来问。

3、写个快速排序

和算法有关的,我觉得自己还好,写的虽然不是特别快,但感觉自己写的还好,无论是从代码的排版(因为这个一般都是在笔记本写的,不可能给你IDE),以及代码的简便程度,我觉得都不错,这里我想说的就是,自己平时写代码的时候,一定要注意排版,别老是依靠IDE给你一键排版。

虽然我已经写出来了,不过他之后让我说下快速排序的步骤,我老实说了,不过我觉得,这些题都太简单,如果面试官的很基础,你也答的很基础,我觉得没啥优势,对于快速排序,我本来想补充的,例如对 中轴的选择问题,与其他排序算法的一些比较,应用场景等,不过,我是想着等他来问我......

按照我这样写文章的话,估计得上万字,我下面简便说下就行了。如果你们感兴趣我这么啰嗦的话,下次我再来吐槽说说我回答的不够好的地方。

4、一条SQL执行的很慢的原因?(之前写过文章,建议看看)

5、MySQL有哪些存储引擎以及他们之间的区别(我其他引擎忘了名字叫啥)

6、如何查看SQL语句的执行状态,知道这条命令吗?(我当时MySQL其实几乎没学,很多都不知道)

7、qq用的是tcp还是udp(两种都有涉及)

8、说说jvm内存模型(其实并没有JVM内存模型,只有Java内存模型以及JVM内存结构,你要问清楚是哪个再答)

9、JVM内存结构中有栈堆等,为啥要用栈这种数据结构?(主要是操作简便,快速)

10、说说垃圾回收?(老年代,新生代,永生代的区别等,各自使用的回收算法,新生代又分eden和survivor区等)

11、说说aop和ioc

12、大致看过哪些书?有逛技术论坛的习惯吗?说几个你常见的技术博客平台?有去逛过国外的技术博客吗?看的懂文档吗?

总体上一面比较简单,面试官没有深入问,不过问的很广,这里问一些,那里问一下。感觉自己答的还好,也顺利过了。第一次面试收获还是挺大。

1、对于懂的一定要多说,可以延伸,只要面试官不打断你的回答。

2、对于高频面试题,一定要自己模拟说一下,而且这些题一般不难,一定要多延伸,不然没啥优势。

3、感觉以后面试不那么紧张了,也就这么回事。

提前提二面

几天后收到复试通知,二面是电话面,二面操作系统和Linux问的有点多,我二面感觉自己就挂在Linux上,我Linux不大懂,被问了好几个个基础的问题都不懂,自己也是醉了,感觉自己应该和面试官说一下我Linux还没学😹。操作系统很多细节也忘了,说说二面的面试题吧。

1、说下四次挥手,什么时候会出现time_wait状态?(我扯了一下,面试结束后,发现自己弄错了,真想给自己一巴掌,主要是,其实我对三次握手和四次挥手没去准备过,虽然看过挺多文章,但是具体的没记住,凉凉)

2、红黑树知道吗?说说,为啥有了平衡树还需要红黑树?(平衡树太严格,插入很容易打破平衡,经常需要调整,而红黑树是一种折中方案......)

3、红黑树是如何保证查找效率不会太低下的?(我后面再写一篇关于红黑树,B+树,平衡树这些的文章吧,现在先给出这些遇到过的面试题)

4、红黑树在调整的时候,不是会用到左旋右旋吗?说说会不会出现无限左旋右旋的情况,如果不会,那最多旋转几次?

5、为什么索引能加快查找效率?

6、说说B+树是怎么分裂、合并的,知道具体步骤逻辑吗?

7、你项目用到redis,知道跳跃表吗?说说他是怎么实现的,查找时间复杂度?

数据结构这部分我觉得自己答的还行,几乎都答出来的,不过下面的Linux和操作系统有点惨,怪自己没准备,不过面试就是一个查漏补缺的过程吧。

8、Linux的proc目录有啥用?(忘了,,,,)

9、怎么查看端口的状态?(有条啥命令来着的,忘了)

10、怎么查看某个进程的状态?(也忘了,呵呵,,,,,)

我简历上是写着 熟悉Linux常用命令,然而一个也没答出来,尴尬啊,其实我会用的是那些 ls, cp, mkdir, tar等常用命令,对于线程、网络相关的都不会,感觉危险了,这么基础的问题都不会。

11、说说什么是进程,进程包括哪些数据?进程切换的过程是怎么样的?

12、如果想要让多个线程执行到某个点,都达到之后再继续执行,可以用java的那些类来实现?(CountDownLatch和CyclicBarrier这两个)

13、什么是反射?反射有啥用?

14、说说垃圾回收,jvm是如何知道这个对象可以回收的?计数法有啥问题?如果我们想要指定某种垃圾回收算法,该怎么设置?

15说说aop(这个问的刁难了,我该说的都说),他说,那我也可以通过自己配置文件来实现这种切面啊,为啥就要用AOP呢?(他问的问题就相当于,我用servlet也能使用实现一个网站啊,为啥要用spring这些框架呢?我当时应该要吹一波spring中AOP带来的好处的,不过我没说,感觉自己应该吹一下的)

15、接着是问了我那个 高并发秒杀系统的项目,说了里面的线程安全问题,然后我这个项目早忘光了,自己也没准备,代码我不是自己敲的,直接看视频的。自己没做过什么项目,就充当了,结果面试官问了一下线程的问题,我一直没get到点,搞的自己也好懵,最后才知道他说的是啥,不过项目这块答的也不好,主要自己也没动手弄过,又是去年快速看视频学的。

总体上,二面除了Linux答的非常非常不好,还有四次挥手说错了,线程包括哪些数据说的也不全之外,以及项目感觉有点被认识不是我做的之后,其它感觉还好。不过。一个多星期后,结果出来,挂了。

不过我也不意外,知道自己有很多需要补的,面试有时运气不好,可能连续问到好几个不好的,估计就得挂了,不过这没方法,最好的做法就是面试过后把自己不足的补上来。

常规网申一面

常规网申的笔试题中是 20道选择题+3道编程题,过几天就收到面试通知了,有了前面两次之后的面试经验之后,也了解到腾讯是特别喜欢问 操作系统+计算机网络+Linux的,于是我把这方面的高频面试题都好好准备了,Linux 进程等相关都准备了,操作系统也大致准备了,下面说说这次涉及到的面试题。

1、说说网络建立连接之后是怎么关闭的(其实就是四次挥手,我详细说了,之前写过文章)。如果出现大量time_wait 状态的连接是怎么回事?(我说了第四次挥手出现了丢包、超时等),假如不会出现丢包、超时等,网络非常流畅呢?(说了短连接导致的)那怎么解决?(我说了限制最大连接,用队列来缓冲),问我还有其他方法吗?(我不大知道,好像面试官不大满意我的回答。不过最好的做法应该是修改 time_wait 等待的最大之间,把时间改小一点)。

2、说说IO阻塞与非阻塞是什么?各自有啥好处?知道多路复用吗?了解过 select 吗?说说他与 epoll 的区别。

我知道的也不是很深入,就常规答法,他问啥就答啥。这里我要说一下,我觉得很多人可能 混淆了阻塞、非阻塞与异步、同步的概念。我实话,我觉得自己已经理解了,结果我看了好几篇文章,发现自己反而不懂了,懵了,总觉得缺了点什么影响我对阻塞非阻塞与异步同步的理解,说实话,我看了大概有10篇文章,有80%我觉得都没有说清楚阻塞非阻塞与异步同步的本质区别。这会影响你对同步阻塞,同步非阻塞,异步阻塞,异步非阻塞的理解。有时间我再来写一篇文章说说他们的区别,相信看了你就一定懂了。

3、知道乐观锁吗?说说他们适合应用的情景。

4、说说自适应锁,与其他锁相比,他的优缺点,什么情况下你觉得可以用自适应锁?(之前写过几种锁的文章,不懂的建议看)

5、你平时开发是在Linux上开发的吗?(我靠,Linux上开发??那不是得对Linux上很熟悉?我虽然准备了Linux面试相关,可是这Linux上开发压根没体验过啊,我马上怂了,马上说没有,并且还加了我对Linux不熟,这门课刚学没几天。之后他就不问了Linux了,然后接下来我就扎心了)

6、那你平时就是在windows下开发了,那如果你平时项目要上线,这个项目的性能突然很差,你是怎么定位问题的?(我靠,在下从来没有上线过项目,连用心写过项目都没有,这下完,我迟疑了一会),他补充说,例如你可以通过观察你电脑的 cup, 内存,IO 的指标来进行行定位,可以分析下吗?(可以打开任务管理器来查看cup等的运行情况,然后.....其实我没有上线过项目,也没有这样的经历),他说没事,假如遇到这种情况你会怎么分析?(我下面说一下我是怎么扯的)

心想,扎心了,其实我不大知道究竟要分析啥,好吧不管三七二十一了,然后我开始扯了:

1、如果cup一直很高而其他地方内存,IO很低的话,那很有可能项目里面有某些东西在疯狂运行,但却没啥用,很有可能就是自己设计了乐观锁,但竞争的线程太多,导致一直做无用功。

2、如果磁盘操作很高,但cup很低,这是意味着项目一直在做IO操作,很有可能是SQL语句写的不好,导致扫描了大量的磁盘,然后我就针对SQL语句的问题开始扯了,例如没有用上索引啊,选错索引啊,扯了一大堆,哈哈。(心想,舒服,刚好写过sql语句执行的很慢的原因有哪些)。

7、说说进程与线程的区别?进程有哪些通信方式,(我说了共享内存,消息队列,管道,信号量,套接字,信号驱动),他问还有吗?(我说其他的不知道了),描述下这些具体的通信方式(我有些具体的给忘了,大致描述了几个)

8、说说快速排序的思想,删除单链表倒数第二个节点,你会怎么做?

9、了解MySQL事务吗?说说ACID是啥(我靠,我对事务的隔离理解还强,研究过隔离是如何实现的,然而,,,我突然短路,ACID的D忘了啥,就像你高中那会对一首古诗背的很熟,然后突然就忘了哪句,心里有点扎心),那你具体说说ACID是啥(原子性是,,,隔离性是,,,,一致性?????,我靠,我觉得自己对这四大特性很理解,感觉看名字就知道是啥了,然后说到一致性的时候,我自己的心中产生了疑问,感觉自己并没有真正意义上理解了一致性,然后我没说出来)

心中那个苦,一致性都没说出来,其实我是可以说出来的,但是我总觉得缺了点什么, 就像你看了几篇文章觉得自己懂了,结果让你在看几篇文章,你感觉自己有点醒悟了,然后突然发现自己并没有真正意义上的懂,我查了好多篇文章,呵呵,千篇一律,都不能解答我的疑惑,说实话,别觉得自己懂了,其实你根本就没有懂,最后是懵逼了好久,才觉得自己应该是懂了,有时间也要写一篇事务的四大特性的文章。

说实话,网上的很多文章,真的是千篇一律,当你产生了疑惑,很难找到解答,因为你会发现,这几篇说的都是一样的,那个你感觉 特别重要的疑惑都没有说到。好吧,不吐槽了,等下要被大佬门怼我写的文章了。

一面大致说了这么多,有些忘了。比起提前批一面,我觉得自己在表述上好多了,也会扯了,这里我想跟大家说的就是,问题就那些,一定要搞懂,延伸,要理解,该背的要背,例如进程有哪些通信方式。如果要面试腾讯的时候,计算机网络、操作系统,Linux这些底层的,要学好,尽管你是面试 java 岗位的,可能他一个 Java 的问题都不问的。

想杀了自己的二面

二面我应该就是个傻逼吧,自己给自己挖坑了,最近这些题一直在刷题,复习基础,对于数据结构,算法题,Java,操作系统什么的,自己也想看看有哪些不足,然后他一开始就叫我介绍项目。

1、说说你做的项目。

其实这期间我为了应付以后的面试弄过做过一个项目,不过还没弄好。然后我之前不是说了自己有个很水的高并发秒杀系统项目吗?我自己也没啥项目经验,然后我觉得 高并发也是很牛逼的样子,就打算用它了,简历上也写了。不过这个项目我自己没实操过,看视频的,很短,几个小时而已,用到了redis等,不过我已经准备了很多redis相关的知识了,怎么优化的也准备好了。

现实是,呵呵,我被怼的体无完肤

优化之一就是通过 减少事务持有锁的时间来优化的,例如我们秒杀一个物品的话,如果减库存和插入订单两个操作都成功的话,代表秒杀成功,这两个操作会在一个事务里完成。问题来了,是先减库存呢?还是先插入订单呢?

我说了先插入订单在减库存能够减少事务锁的持有时间,然后我就被面试官怼死了,如下:

面试官:你觉得你这个优化有用吗?

我:有啊,可以减少锁的持有时间(确实可以减少锁的持有时间)

面试官:那你说说提高了多少并发量?

我:这个,,没测试过(我是看视频的,自己也没有这样的测试工具,有点尴尬,连测试过都没有??)

面试官:那你说说这条事务执行需要多少时间

我:我,,好像忘了具体数据,大概是0.00几秒吧。

面试官:你自己都说0.00几秒了,这么短时间,你觉得你的这个优化,用处大吗?有数据来支撑下吗?

我:,,,,,(真的自己给自己挖坑了,早知道随便介绍下就好了,不主动说自己的优化了过程了,毕竟自己没实操过)

面试官:你这个系统能扛多少并发量?

我:...没测试过(我觉得,面试官觉得这个项目是假的了,可能觉得我是从网上随便找的,然后来应付面试的,我觉得自己凉了, 欺骗可是非常严重的)。

面试官:那你说说,完成一条请求用了多少时间(测试最大的并发量可能需要相应的工具,他可能为了再次确认我的真伪,故意是一条请求的?这样就不需要工具了)

我:.....没测试过(好吧,我的心已凉)

面试官:好吧,就说到这里,你用过lowJS吗?

我:啊??lowJS,什么鬼东西?而且他的声音有点小,我再次确认,是指lowJS吗?他说是,我说没听说过(后来才知道,他说的是Node.js,,,,,,麻痹,,,)。

面试官:你还有啥问题要问的?

呵呵,,,已经凉了,这次面试真的是自己给自己挖坑,面试官肯定是知道我项目是伪造的,这是非常严重的事情,然后这个面试大概是持续了10几分钟,其他的面试官的也没问了。想找给人吐槽下自己。

然后,明天就查到挂的信息了,之前二面是一个星期多才查到挂了,这次是十二个小时之后就收到挂了,我也知道10000%挂的了。此刻需要一波安慰!!

总结

总结就是一首凉凉送给自己,还有就是有一些建议,当然,这也是我自己给自己的建义。

1、高频面试题,别停留于表面,大家都懂,说的很表面没啥优势。

2、别以为自己懂了,如果可以,最好自己尝试描述一下,你可能会描述的很没有逻辑。

3、好好准备一个项目吧,一定要自己脚踏实地做一下, 别像我,被怼的体无完肤。

4、框架真的问的很少,好好打基础,数据结构与算法,计算机网络,操作系统,Linux 进程等相关的命令。

5、面试的第一关是笔试,一般都是编程题,建议要刷刷题,不然面试的机会都没有。

先写到这里了,后面会写一下面试相关的题,建议最后看,虽然你看过相关文章了,但看我的,可能你也能够有新的收获!还有就是祝各位大佬面试顺序,不好的运气我来帮你们包揽就行了(留下了委屈的眼泪)

最后推荐下我的公众号:苦逼的码农,主要分享一下技术文章、面试题、算法题,各种工具、视频资源等,里面已有100多篇原创文章,期待各路英雄来交流。(欢迎扫右上角的二维码关注哦)

RTMP流媒体服务端应用开发系列 – Nginx-Rtmp鉴权设置 | 贝壳博客

$
0
0

鉴权验证在自建直播流媒体服务端应用中是非常重要的。用以防止非法推流,控制收费播放盗链等场景。
目前各大直播云平台都支持url参数形式的鉴权验证法,例如rtmp://serverhost/app-name/stream-name?key=md5(secret+expires_timestemp)&time=expires_timestemp 这种既有私密key,又有时间戳过期判断的最简单形式,当然还能根据使用场景增加其他判断条件。
Nginx-Rtmp模块作为rtmp直播流媒体服务端,本身没有这种鉴权验证法,但能通过 Notify 转为本地的http请求,使用Nginx内置的 ngx_http_secure_link_module即能达到同样效果。

首先利用Nginx-Rtmp的on_play通知,将rtmp的播放事件通知到本地的http处理上来

1
2
3
4
5
6
7
8
9
10
rtmp{
    server{
        listen1935;
        applicationplay{
            liveon;
            notify_methodget;
            on_playhttp://127.0.0.1/on_play;
        }
    }
}

注意一定要将通知改为get形式

本地http处理/on_play

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
http{
    server{
        listen80;
        set$auth_key"nodemedia2016";
 
        location/on_play{
            secure_link$arg_key,$arg_time;
            secure_link_md5"$auth_key$arg_time";
            
            if($secure_link=""){
                return404;#验证失败
            }
 
            if($secure_link="0"){
                return410;#验证成功,但时间失效
            }
            
            return200;
        }
    }
}

rtmp url的生成者负责生成一条包含key和time参数的url,并计算好过期时间。
比如原始rtmp url 是: rtmp://stream.nodemedia.cn/live/demo,生成url的时间是2016/7/24 17:00:00 时间戳即是1469350800,我们希望1分钟后这个url失效,那么失效时间戳就是1469350860,我们和服务端约定好的auth_key是”nodemedia2016″,那么按照nginx的约定先对『nodemedia20161469350860』这个拼装的字符串md5,再base64,如果有再替换”+/”为 “-_”,如果有再删除”=”(比较繁琐)

Shell
1
2
echo-n'nodemedia20161469350860'|\
    opensslmd5-binary|opensslbase64|tr+/-_|tr-d=

得到 ldorjTG99nd0juc88bltiQ这样的字符串,这就是key参数的值了,拼装为

rtmp://stream.nodemedia.cn/live/demo?key=ldorjTG99nd0juc88bltiQ&time=1469350860

1分钟内请求是可以正常播放的,1分钟后再请求,会直接被nginx服务器断开socket。

本方法利用Nginx-Rtmp 的开始播放的通知事件转为内部http利用内置ngx_http_secure_link_module模块达到鉴权实现。原理很简单,不过略繁琐。

在这里,本人参照网宿CDN的模式对ngx_http_secure_link_module进行了修改,增加了secure_link_extime参数,在服务器内部控制过期时间,rtmp_url的生成者只需传入当前时间戳,来隐蔽过期时间。加密部分只需一次md5,不再转base64再替换字符串那么繁琐,时间戳也采用HEX形式隐蔽。

原创文章,转载请注明:转载自 贝壳博客

本文链接地址:RTMP流媒体服务端应用开发系列 – Nginx-Rtmp鉴权设置

有赞想干什么 - 阿朱=行业趋势+开发管理+架构 - CSDN博客

$
0
0

该信息来自昨日的有赞年度发布大会。


(1)有赞商业模式


有赞2018年财报数据是:总营收7.8亿,SaaS软件系统服务营收4.3亿,交易服务营收3.1亿。可能有赞把支付通道费、交易抽佣、营销代理费都算到这个交易服务营收里面了。


有赞一直在践行这一模型。即:推广获客(Acquisition) 、 成交转化(Activation) 、客户留存(Retention)、复购增购(Revenue)、分享裂变 (Refer)


有赞提供了一系列的SaaS软件产品,有通用的有赞微商城,也有面向零售业的、连锁门店的、美业的、在线教育的行业细化版本。


甚至,有赞现在也开展分销供货供应链(类1688)、营销推广(类阿里妈妈)、仓储物流配送调度(类菜鸟网络)、金融支付与信贷(类蚂蚁金服)、人才培训业务(类淘宝大学)。


通过电梯广告、公交车广告等推广获客


借助有赞的营销玩法实现成交转化


通过微信、会员体系做留存


通过有赞广告、公众号、包裹上二维码触达老客户,提高客户复购增购


通过社交网络和有赞提供的营销玩法,让客户分享和裂变带来新客


(2)打造类淘宝皇冠等级体系


该榜单不仅限于交易额,而是按照交易指数、推广指数、留存指数及品牌认知、营销创新、舆论评价等维度综合排名。


有赞认为,未来对商家的估值也不应局限于交易额,而是基于客户资产来估值:商家有多少客户、客户的生命周期有多久、能产生多少复购


(3)有赞走向大型客户:品牌官网


有赞认为:未来所有的新零售门店都也都应该拥有三个货架。即门店货架、销售人员的线上导购货架、24小时营业在线货架。


有赞微商城SaaS不再只是自有商城,而是具备在线销售能力的“品牌官网”。


过去2008年到2016年长达八年里,多数中国品牌商非常可怜,没有自己的官网,只能向平台砸广告、求流量,海报上加个搜索框让客户去平台的旗舰店购买。


现在很多都换成了自己的公众号和小程序。


帮助商家搭建具备在线销售能力的“品牌官网”,包括:

1、更丰富的模板

2、店长笔记

3、店铺圈


让商家更好地展示品牌内涵


帮助商家搭建在线服务和营销的“顾客增值系统”。从激励、服务、复购、返佣等各个环节帮商家提效。


推出“顾客每日任务”,让消费者养成习惯持续到店


帮助商家搭建基于销售和消费大数据的在线营销平台。发布更多涨粉、裂变、内容导购等社群玩法,帮商家盘活流量。通过“一物一码/一店一码/一人一码”功能帮商家提升成交转化。


(4)有赞的增值营销插件SaaS商业模式


2018年,经财报审计,有赞全网电商交易额达330亿,订单数为2.33亿笔,客单价超过140元


其中227亿交易额来自于各类营销玩法:


1、满减满送41.9亿

2、限时折扣39亿

3、分销员29.3亿

4、多人拼团12亿

5、优惠券和裂变发券占53.7亿


整体来看,只有32%真正来源于商家自有流量。


“微信聊天主界面下拉”占比接近30%,公众号菜单栏占17%。


5%是搜索,还是微信主搜没有开放的前提下。未来微信主搜会是小程序搜索加搜一搜的N倍。


扫码收款、电子卡券、到店自提、同城配送类订单均超千万笔,同城增量订单占比达15.9%。其中,到店自提订单2458万笔,同城配送订单1251万笔。10元以下到店订单744万笔,基本可理解这类订单商家在做“线上营销、引客到店”


这些流量能不能用好的关键一步,就是让客户先把你的小程序收藏起来。有赞有一个非常重要的营销插件“收藏有礼”。


2019年,有赞还增加了几种主要营销玩法:0元抽奖、礼品卡组合支付。


(5)有赞SaaS未来走向


有赞除了继续加深线下零售场景支撑、连锁门店支撑,除了继续加深重点行业版本,在功能上要更扩展关注:人货场  进销存、人财物这九大类。


一、人:

将门店顾客变成线上粉丝,提高复购,并让老顾客带来新顾客


做好离店复购的客户关怀,加速消费决策带来更多生意


二、货:

基于客户需求大数据驱动,突破品类限制,个性化货品运营;


三、场:

通过互联网的手段,扩大经营范围、经营时间、经营方式:通过提供门店预约、外卖配送可以帮助商家多出一个在线货架


四、进:

智能化采购更加适合销售的产品;


五、销:

便捷高效的销售和收款,让收银人员回归客户交流;


六、存:

清晰的管理库存,追求更合理的动销管理;


七、人:

通过导购智能名片,让每个顾客跟店员在线联系起来,精准洞察客户提升人效


八、财:

清晰的账务管理,持续提升资金利用效率;


九、物:

合理规划和使用耗材


清晰固定资产管理


(6)有赞中台


一、业务中台


主打商品通、库存通、订单通、会员通、储值通、营销通、数据通、资产通八大模块,同时支持网店和门店管理,可以帮连锁商家快速打通线上/线下,门店/总部实现一体化经营


二、应用中台


有赞还提供有赞云这个应用中台,供各种想做应用但不想自己再做商城系统的研发团队来做集成。有赞云因此也获得了更多的客户、流量、订单数据。有赞云也成为了众多应用的中枢。




视频监控中运动物体检测与跟踪----相邻帧差法和三帧差法 - 牧野的博客 - CSDN博客

$
0
0

帧间差分法是通过对视频中相邻两帧图像做差分运算来标记运动物体的方法。


帧差法依据的原则是:当视频中存在移动物体的时候,相邻帧(或相邻三帧)之间在灰度上会有差别,求取两帧图像灰度差的绝对值,则静止的物体在差值图像上表现出来全是0,而移动物体特别是移动物体的轮廓处由于存在灰度变化为非0,这样就能大致计算出移动物体的位置、轮廓和移动路径等。


帧间差分法的优点是算法实现简单,程序设计复杂度低;对光线等场景变化不太敏感,能够适应各种动态环境,稳定性较好。缺点是不能提取出对象的完整区域,对象内部有“空洞”,只能提取出边界,边界轮廓比较粗,往往比实际物体要大。对快速运动的物体,容易出现鬼影的现象,甚至会被检测为两个不同的运动物体,对慢速运动的物体,当物体在前后两帧中几乎完全重叠时,则检测不到物体。


相邻帧间差分法

相邻帧间差分法直接对相邻的两帧图像做差分运算,并取差分运算的绝对值构成移动物体,优点是运算快速,实时性高,缺点是无法应对光照的突变,物体间一般具有空洞。


C++、Opencv实现:

#include "core/core.hpp"
#include "highgui/highgui.hpp"
#include "imgproc/imgproc.hpp"

using namespace cv;

int main(int argc,char *argv[])
{
	VideoCapture videoCap(argv[1]);
	if(!videoCap.isOpened())
	{
		return -1;
	}
	double videoFPS=videoCap.get(CV_CAP_PROP_FPS);  //获取帧率
	double videoPause=1000/videoFPS;

	Mat framePre; //上一帧
	Mat frameNow; //当前帧
	Mat frameDet; //运动物体
	videoCap>>framePre;
	cvtColor(framePre,framePre,CV_RGB2GRAY);	
	while(true)
	{
		videoCap>>frameNow;
		if(frameNow.empty()||waitKey(2500)==27)
		{
			break;
		}
		cvtColor(frameNow,frameNow,CV_RGB2GRAY);
		absdiff(frameNow,framePre,frameDet);
		framePre=frameNow;		
		imshow("Video",frameNow);
		imshow("Detection",frameDet);		
	}
	return 0;
}


调用了Opencv自带的视频文件“768x576.avi”,视频文件位置:“opencv\sources\samples\gpu”,下图是视频第100帧时图像:



下图是相邻两帧差法检测到的物体,检测效果没有经过膨胀或腐蚀等处理:



可以看到物体的轮廓是“双边”的,并且物体的移动速度越快,双边轮廓现象越粗越明显(这是不是给监控中速度检测提供了一个思路~~),另一个就是物体具有较大的空洞。


三帧差法


三帧差法是在相邻帧差法基础上改进的算法,在一定程度上优化了运动物体双边,粗轮廓的现象,相比之下,三帧差法比相邻帧差法更适用于物体移动速度较快的情况,比如道路上车辆的智能监控。


三帧差法基本实现步骤:

1. 前两帧图像做灰度差

2. 当前帧图像与前一帧图像做灰度差

3. 1和2的结果图像按位做“与”操作


C++、Opencv实现:

#include "core/core.hpp"
#include "highgui/highgui.hpp"
#include "imgproc/imgproc.hpp"

using namespace cv;

int main(int argc,char *argv[])
{
	VideoCapture videoCap(argv[1]);
	if(!videoCap.isOpened())
	{
		return -1;
	}
	double videoFPS=videoCap.get(CV_CAP_PROP_FPS);  //获取帧率
	double videoPause=1000/videoFPS;
	Mat framePrePre; //上上一帧
	Mat framePre; //上一帧
	Mat frameNow; //当前帧
	Mat frameDet; //运动物体
	videoCap>>framePrePre;
	videoCap>>framePre;
	cvtColor(framePrePre,framePrePre,CV_RGB2GRAY);	
	cvtColor(framePre,framePre,CV_RGB2GRAY);	
	int save=0;
	while(true)
	{
		videoCap>>frameNow;
		if(frameNow.empty()||waitKey(videoPause)==27)
		{
			break;
		}
		cvtColor(frameNow,frameNow,CV_RGB2GRAY);	
		Mat Det1;
		Mat Det2;
		absdiff(framePrePre,framePre,Det1);  //帧差1
		absdiff(framePre,frameNow,Det2);     //帧差2
		threshold(Det1,Det1,0,255,CV_THRESH_OTSU);  //自适应阈值化
		threshold(Det2,Det2,0,255,CV_THRESH_OTSU);
		Mat element=getStructuringElement(0,Size(3,3));  //膨胀核
		dilate(Det1,Det1,element);    //膨胀
		dilate(Det2,Det2,element);
		bitwise_and(Det1,Det2,frameDet);		
		framePrePre=framePre;		
		framePre=frameNow;		
		imshow("Video",frameNow);
		imshow("Detection",frameDet);
	}
	return 0;
}

同样是“768x576.avi”视频文件,并且也保存了第100帧的原始图像和运动物体检测图像:


未经形态学处理的原始的三帧差法检测到的运动物体:



未经任何形态学处理的原始的三帧差法检测到的物体的双边轮廓现象有所改善,但同时也有丢失轮廓的现象。


下图是在两个帧差图像按位与操作之前做了一下膨胀处理的效果:



相比相邻两帧差法,原始的三帧差法对物体的双边粗轮廓和“鬼影”现象有所改善,比较适合对运动速度较快物体的检测,但是仍然会有空洞出现,并且物体移动速度较慢时容易丢失轮廓。


当然三帧差法做了两次的差分运算,给了三帧差法更多可操作和优化的空间,为更优秀的检测效果提供了可能。


机器学习实践系列之5 - 目标跟踪 - 跟随技术的脚步-linolzhang的专栏 - CSDN博客

$
0
0

       提到 目标跟踪(Object Tracking),很多专业人士都不陌生,它是计算机视觉里面 用于视频分析的一个很大的分类,就像目标检测一样,是视频分析算法的底层支撑。

       目标跟踪的算法有很多,像 Mean-Shift、光流法、粒子滤波、卡尔曼滤波等 传统方法,也有 TLD、CT、Struct、KCF 等掺杂了某些 “外力”,不那么纯粹的方法。但不管怎样,Tracking这项工作有着很大的研究群体,也不乏有人为之奋斗终生!


• CamShift

        CamShift连续自适应的Mean-Shift算法(Continuously Adaptive Mean-SHIFT),作为入门级的目标跟踪,包括两部分:

1. 对视频中的每一帧做 Mean-Shift;

2. 将上一帧的 Mean-Shift 结果 作为下一帧的输入,反复迭代;

       算法步骤非常简单,问题可以演化为:Mean-Shift 是什么?其输入输出又是什么?

        Mean-Shift就是大名鼎鼎的 均值漂移,这里面有两层含义:

       1)均值 

             空间R中有N个样本点,任选一点x0,假定有k个点落在x的邻域范围(半径h)内,那么MeanShift向量可以定义为:

            

            其中Sk是一个半径为h的高维球区域,满足公式:

              

       说的再通俗一点,分两步:

A) 任选空间内的一点x0,以该点为圆心做一个半径h的球(可能扩展到高维),统计球内的所有点,为x0的邻域点;

B) 圆心到邻域点连线作为向量,所有向量和求均值,即得到  Mean-Shift 向量

       

       通过均值计算,我们得到一个(x0, x'0)的向量,如图橘色箭头。

       2)漂移

            将圆心 x0移动到 Mean-Shift 向量的终点 x'0,就是 漂移(比平移好听)。以 x'0为新的中心,重复上面的过程,迭代计算,直到收敛。

        Mean-Shift这种特征使得其在 目标跟踪、聚类、图像平滑等问题上都有应用。

       核心思想是  利用概率密度的梯度爬升 来寻找局部最优。输入一个图像的区域范围,逐步迭代,对应区域朝 质心(重心)漂移。

      

概率密度函数 与 反向投影

       事实上,我们需要将 待跟踪的目标 ROI进行提取 直方图,计算输入图像对应直方图的  反向投影,得到输入图像在已知目标颜色直方图的条件下的颜色概率密度分布图,包含了目标在当前帧中的相干信息。 

       对于目标区域内的像素,可得到该像素属于目标像素的概率,而对于非目标区域内的像素,该概率为0。

       参考下面代码进行理解:

/* linolzhang 2013.11
   CamShift跟踪
*/
#include "opencv/cv.h"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/video/tracking.hpp"
#include "opencv2/highgui/highgui.hpp"

#pragma comment(lib,"opencv_core2410.lib")
#pragma comment(lib,"opencv_imgproc2410.lib")
#pragma comment(lib,"opencv_video2410.lib")
#pragma comment(lib,"opencv_highgui2410.lib")

#define SAT_MIN 65 // 定义最小饱和度,低于该饱和度的色调不稳定
#define V_MIN 10

using namespace cv;

IplImage* getHSV(const IplImage *img,IplImage **img_h,IplImage **img_s,IplImage **img_v)
{
	IplImage *img_hsv = cvCreateImage(cvGetSize(img), IPL_DEPTH_8U, 3);
	cvCvtColor(img,img_hsv,CV_BGR2HSV);
	cvSplit(img_hsv, *img_h, img_s==NULL?NULL:*img_s,img_v==NULL?NULL:*img_v, NULL);

	// 定义掩码,只处理像素值为H:0~180,S:SAT_MIN~255的部分
	IplImage *img_msk = cvCreateImage(cvGetSize(img), IPL_DEPTH_8U, 1);
	cvInRangeS(img_hsv, cvScalar(0,SAT_MIN,V_MIN,0),cvScalar(180,255,255,0), img_msk);

	cvReleaseImage(&img_hsv);
	return img_msk;
}

int main(int argc, char** argv)
{
	IplImage *src = cvLoadImage("1.jpg", -1); // 加载源图像 - 包含待跟踪目标
	CvRect rcROI = cvRect(132,296,132,176);    // 待跟踪目标位置

	// 1.将目标转换到HSV空间,提取Hue分量
	IplImage *src_h = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1);
	IplImage *src_s = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1);
	IplImage *hsv_mask = getHSV(src,&src_h,&src_s,NULL);

	// 2.计算分量直方图 - 1维
	int hist_size = 256; // 64 | 128 | 256
	float range[] = {0,180};
	float *ranges[] ={ range };
	CvHistogram *hist = cvCreateHist(1, &hist_size, CV_HIST_ARRAY, ranges);

	cvSetImageROI(src,rcROI); // 设置源图像ROI
	cvCalcHist(&src_h, hist,0,hsv_mask);
	cvReleaseImage(&hsv_mask);
	cvResetImageROI(src);     // 清空ROI

	// 3.计算反向投影,得到概率密度图 img_prob
	IplImage *dest = cvLoadImage("2.jpg", -1); // 从这幅图搜索
	IplImage *dest_h = cvCreateImage(cvGetSize(dest), IPL_DEPTH_8U, 1);
	IplImage *dest_s = cvCreateImage(cvGetSize(dest), IPL_DEPTH_8U, 1);
	hsv_mask = getHSV(dest,&dest_h,&dest_s,NULL);
	IplImage *img_prob = cvCreateImage(cvGetSize(dest),IPL_DEPTH_8U,1);

	cvCalcBackProject(&dest_h,img_prob,hist);
	cvAnd(img_prob, hsv_mask, img_prob, 0);
	cvReleaseImage(&hsv_mask);

	// 4.调用MeanShift函数计算
	// int cvMeanShift(IplImage* imgprob,CvRect windowIn,CvTermCriteria criteria,CvConnectedComp* out);
    // 参数说明:imgprob: 2D概率密度图  windowIn:初始窗口  criteria:迭代终止条件  out:输出结果
	CvConnectedComp conn_comp;
	cvMeanShift(img_prob,rcROI, cvTermCriteria(CV_TERMCRIT_ITER,10,0.1), &conn_comp);
	//CvBox2D track_box;
	//cvCamShift(img_prob,rcROI,cvTermCriteria(CV_TERMCRIT_ITER, 100, 0.01 ),&conn_comp, &track_box );

	// 5.绘制结果
	cvRectangle( src,cvPoint(rcROI.x,rcROI.y),cvPoint(rcROI.x+rcROI.width,rcROI.y+rcROI.height),cvScalar(0,255,0) );
	cvShowImage("源图像",src);

	cvShowImage("概率密度图",img_prob);
	cvRectangle( dest,cvPoint(rcROI.x,rcROI.y),cvPoint(rcROI.x+rcROI.width,rcROI.y+rcROI.height),cvScalar(0,255,0) );
	CvRect& rc = conn_comp.rect;
	cvRectangle( dest,cvPoint(rc.x,rc.y),cvPoint(rc.x+rc.width, rc.y+rc.height),cvScalar(255,255,0) );
	cvShowImage("Track结果",dest);
	cvWaitKey(0);

	// 释放资源
	cvReleaseImage(&src);
	cvReleaseImage(&dest);
	cvReleaseImage(&img_prob);
	return 0;
}

        CamShift算法在 Mean-Shift算法基础上进行了改进:

        连续自适应:利用前一帧的目标尺寸调节搜索窗口大小,对有尺寸变化的目标可准确定位;

       不过, CamShift算法在计算目标模板直方图分布时,没有使用核函数进行加权处理,也就是说目标区域内的每个像素点在目标模型中有着相同的权重,故算法的抗噪能力低于 Mean-Shift算法。

       另外, CamShift 算法仍然采用目标的色彩信息来进行跟踪(Hue in HSV),很难处理目标与背景颜色(或其他对象)相近的情况,跟踪结果的鲁棒性仍然较差。

        Cam Shift通常用于单目标跟踪,虽然也有人进行过多目标跟踪扩展,不过效果并不好。


• TLD

       纯粹的跟踪已经out了,Tracking by Detecting 才是主流。

        TLD(Tracking-Learning-Detection)是一种新的单目标长时间跟踪算法。该算法的贡献在于 将传统的 跟踪算法检测算法相结合解决 目标在跟踪过程中发生的形变、部分遮挡等问题。

       原作者网站: http://personal.ee.surrey.ac.uk/Personal/Z.Kalal/

       C++ 封装代码: https://github.com/arthurv/OpenTLD   

       Ubuntu下编译过程,进入OpenTLD-master目录:

1)$mkdir build

2)$cd build

3)$cmake ../src

4)make

       可能会提示错误 PatchGenerator不是cv的一个成员,这是OpenCV版本过高导致的兼容问题(原版本是2.3),可以在TLD.h文件头文件添加:

       #include <opencv2/legacy/legacy.hpp>

       调试运行:

           5)cd ..                     # 进入程序根目录

           6)sudo bin/run_tld -p parameters.yml -s datasets/06_car/car.mpg

              能够看到,TLD的跟踪效果还是很不错的。

        

       TLD算法原理:

       1)通过 跟踪器对目标进行跟踪,作者采用的是 光流法(Lucas-Kanade),这里作者引用了一个FB误差,可以描述为:

             a)在  t 时刻,目标框随机初始化跟踪点,通过正向追踪得到 t+1时刻的目标位置;

             b)反向追踪到  t 时刻,计算误差,选择其中误差最小的一半点作为最佳跟踪点;

                  类似 RanSac,找一些对结果贡献大的点。

             示意如下图所示:

        

       2)通过  检测器 对目标进行检测,作者使用了一个 级联分类器 作为Detector,其中用到了随机蕨(Random Ferns),这里不再多说;

       3)通过 学习器在线学习目标特征,根据上面 跟踪器和检测器获得的正负样本进行在线训练,学习目标特征,并将训练特征更新到 检测器

             话说Online 真是个好思路,都在用,由于光线、遮挡、观测角度等原因,在运动过程中目标特征会有所变化。


       算法不算复杂,这里面有个关键,就是作者提出的 P-N学习(P-N Learning)方法

        P-N学习针对 检测器对样本分类时产生的两种错误提供了两种“专家”进行纠正: 

P专家(P-expert):检出漏检(false negative,正样本误分为负样本)的正样本;

N专家(N-expert):改正误检(false positive,负样本误分为正样本)的正样本。

       说的通俗点就是,将检测结果和跟踪结果进行整合,哪个好用哪个, P-N学习是一个裁判员。

        TLD 方法思想是非常值得借鉴的,当然这里面的 检测、跟踪、特征学习环节都可以基于你的需要进行修改和替换,毕竟TLD也已经不新了,你可以用深度学习的方法,也可以用效率更高的方法,Whatever。


• CT

       压缩跟踪(Compressive Tracking) 是一种基于压缩感知的跟踪算法,来看作者(Kaihua Zhang,香港理工大学) 对该算法的简介:

        首先利用符合压缩感知 RIP条件的随机感知矩对多尺度图像特征进行降维,然后在降维后的特征上采用简单的朴素贝叶斯分类器进行分类。

       论文参考: Real-time compressive tracking. Kaihua Zhang, Lei Zhang, Ming-Hsuan Yang. ECCV 2012.(有代码可参考)

        核心点:

1. 在 t时刻,进行图像采样(Patch),得到若干 正样本(目标)和 负样本(背景),通过金字塔变换得到多尺度特征;

2. 通过 稀疏测量矩阵M 对多尺度图像特征降维,然后利用降维后的特征 训练 分类器C(作者用了朴素贝叶斯);

3. 在 t+1时刻,在目标位置周围采样N个Candidate(邻域原则),同样通过  稀疏测量矩阵M 对其降维,提取特征;

4. 用  t 时刻用 分类器C进行分类,Score最大的窗口就是目标窗口。

       可以看到, 压缩跟踪方法是一种 Tracking by Detecting的思路,实际就是在 目标邻域进行检测。

        压缩跟踪的关键在于压缩,也就是降维,压缩感知有很多 Blog进行了介绍,可以自己学习一下(给个参考):
                       http://blog.csdn.net/zouxy09/article/details/8118313

• Struct

       来自一篇2011年的ICCV:

       Struck:Structured Output Tracking with Kernels

       这里面用到了 高斯核函数,具体方法作者并没有详细研究,请自行脑补吧。


• KCF

       KCF是一个非常经典的算法(kernelized correlation filters),速度快、效果好,来自论文:

       High-speed tracking with kernelized correlation filters(ECCV 2012, TPAMI 2015)

       Paper及源码下载参考作者主页: http://www.robots.ox.ac.uk/~joao/#

        KCF算法的主要贡献:

1. 使用目标周围区域的循环矩阵采集正负样本,利用岭回归训练目标检测器;

    算法利用 循环矩阵在傅里叶空间可对角化的性质将矩阵的运算转化为向量的Hadamad积,即元素的点乘,大大降低了运算量。

2. 将线性空间的岭回归通过核函数映射到非线性空间,在非线性空间通过求解一个对偶问题和某些常见的约束,同样的可以使用循环矩阵傅里叶空间对角化简化计算。

3. 给出了一种将多通道数据融入该算法的途径。

       OpenCV3.1.0 在 contrib里提供了KCF的实现,需要用CMake重新编译,这里作者用的是VS2013(对应vc12)。

       配置编译步骤:

            1. 下载及项目配置

               下载contrib库: https://github.com/opencv/opencv_contrib

               选择Source及binaries(生成位置),指定generator,Finish完成配置,如下图所示:

               

            2. 添加编译选项

               找到OPENCV_EXTRA_MODULES_PATH,加入opencv_contrib目录。

               例如:作者的opencv_contrib 路径为D:/opencv3.1/opencv_contrib-master/modules

                编译并生成,configure & generate

            3. 配置VS2013

               配置头文件 和 库文件,设置环境dll,调试运行程序


参考代码:

#include <opencv2/core/utility.hpp>
#include <opencv2/tracking.hpp>
#include <opencv2/videoio.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>

using namespace std;
using namespace cv;

int main() 
{
	Rect2d roi;
	Mat frame;

	// create a tracker object  
	Ptr<Tracker> tracker = Tracker::create("KCF");

	VideoCapture cap("1.avi");
	cap >> frame;

	// [selectroi]选择目标roi以GUI的形式  
	roi = selectROI("tracker", frame);
	if (roi.width == 0 || roi.height == 0)	// Invalid ROI 
		return 0;

	// init
	tracker->init(frame, roi);

	// perform the tracking process  
	printf("Start the tracking process\n");
	while(true)
	{
		cap >> frame;
		if (frame.rows == 0 || frame.cols == 0)
			break;

		tracker->update(frame, roi);
		rectangle(frame, roi, Scalar(255, 0, 0), 2, 1); // draw roi

		imshow("tracker", frame); // show image
		if(waitKey(1) == 27)
			break;
	}

	return 0;
}

【原创】IP摄像头技术纵览(七)---P2P技术—UDP打洞实现内网NAT穿透 - 池上好风---码农改变世界 - CSDN博客

$
0
0

【原创】IP摄像头技术纵览(七)—P2P技术—UDP打洞实现内网NAT穿透

本文属于《IP摄像头技术纵览》系列文章之一:

Author: chad
Mail: linczone@163.com

本文可以自由转载,但转载请务必注明出处以及本声明信息。

NAT技术的实际需求在10几年前就已经出现,为了解决这个问题,10几年来全世界的牛人早已经研究好了完整的解决方案,网上有大量优秀的解决方案文章,笔者自知无法超越,所以秉承拿来主义,将优秀文章根据个人实验及理解整理汇录于此,用于解释IP摄像头整个技术链路。

  P2P(peer-to-peer, 点对点技术)又称对等互联网络技术,研究该技术的原因在于:首先我们不希望我们的视频数据通过服务器中转,这样容易造成隐私泄漏;再者,如果我们自己是IP摄像头供货商,通过服务器中转的方式也会增加我们的产品成本,毕竟当用户数量非常庞大时,服务器数量以及带宽都是一笔不小的开销。基于这两点,可以说P2P通信方式是IP摄像头实现的最好方式。而P2P通信中最重要的一点就是NAT(Network Address Translation,网络地址转换)穿透。

  本文主要内容包含:P2P通信与网络设备的关系、不同的网络设备特征对P2P产生的影响、网络地址转换(NAT)的类型、NAT类型的检测方法、协议防火墙的突破方法、隧道技术、对于不同的NAT类型采取的穿透方法。

  目前P2P通信在穿透上至少存在着两个问题:防火墙穿透和NAT穿透,两者对于网络访问的限制是处于不同角度而实现的,其中防火墙是基于网络数据传输安全上的考虑,其行为主要表现为对网络协议和访问端口的限制,实际上每种限制都包含了两个方向:进和出。而NAT则是基于网络地址转换的实现对内网主机进行的保护,目前来说NAT的存在至少存在以下两方面的意义:解决IPV4地址 匮乏的问题和保护网内主机的目的,所以即使将来IPV6解决了IP地址数量上的问题,但出于对内网主机的保护,NAT仍然有其存在的必要。
  综上所述,要实现一个完善的P2P程序必须至少突破以上两个方面的限制,当然,实际情况会存在一些无法突破的情况,比如双方都是对称型NAT或对称型与端口限制型NAT的通信,对于此类问题在实际开发时可使用服务器转发或代理服务来处理。这篇文章的目的是提出一个能够在两个NAT设备内部的主机间建立直接的Internet连接的方法,同时又尽量不依赖于第三方主机。

1、NAT简介

  NAT(Network Address Translation,网络地址转换)技术的出现从某种意义上解决了IPv4的32位地址不足的问题,它同时也对外隐藏了其内部网络的结构。NAT设备(NAT,一般也被称为中间件)把内部网络跟外部网络隔离开来,并且可以让内部的主机可以使用一个独立的IP地址,并且可以为每个连接动态地翻译这些地址。此外,当内部主机跟外部主机通信时,NAT设备必须为它分配一个唯一的端口号并连接到同样的地址和端口(目标主机)。NAT的另一个特性是它只允许从内部发起的连接的请求,它拒绝了所有不是由内部发起的来到外部的连接,因为它根本不知道要把这个连接转发给内部的哪台主机。
  NAT必须考虑路由器的三个重要的特性:透明的地址分配、透明路由、ICMP包负载解析。
  地址分配是指在一个网络会话开始的时候为内部不可以路由的地址建立一个到可路由地址的映射。NAT必须为原地址和目标地址都进行这样的地址分配。NAT的地址分配有静态的和动态的方式。静态的地址分配必须预先在NAT中定义好,就比如每个会话都指派一对<内部地址,外部端口>映射到某对<外部地址,外部端口>。相反地,动态的映射在每次会话的时候才定义的,它并不保证以后的每次会话都使用相同的映射。
  
  最后一个NAT必须实现的特性是当收到ICMP错误包的时候,NAT使用正常的数据包做出同样的转换。当在网络中发生错误时,比如当TTL过期了,一般地,发送人会收到一个ICMP错误包。ICMP错误包还包含了尝试错误的数据包,这样发送者就可以断定是哪个数据包发生了错误。如果这些错误是从NAT外部产生地,在数据包头部的地址将会被NAT分配的外部地址所代替,而不是内部地址。因此,NAT还是有必要跟对ICMP错误一样,对在ICMP错误包中包含的数据包进行一个反向的转换。
  
  为了能够进行直接的P2P连接,出现了针对UDP的解决方法。UDP打洞技术允许在有限的范围内建立连接。STUN(The Simple Traversal of User Datagram Protocol through Network Address Translators)协议实现了一种打洞技术可以在有限的情况下允许对NAT行为进行自动检测然后建立UDP连接。
  
  UDP打洞技术相对简单,但是UDP连接不能够持久连接。一般地,NAT建了的端口映射如果一段时间不活动后就会过期。为了保持UDP端口映射,必须每隔一段时间发送一次UDP心跳包,只有这样才能保持UDP通信正常。
  
   TCP打洞据说很牛,但是没见过具体的实现,本人水平有限,曾迷失在TCP打洞技术实现中,后来直接放弃,专门研究UDP打洞,世界瞬间明亮了。
  

2、NAT类型及检测

  
  通过UDP打洞实现NAT穿越是一种在处于使用了NAT的私有网络中的Internet主机之间建立双向UDP连接的方法。由于NAT的行为是非标准化的,因此它并不能应用于所有类型的NAT。

  从功能上来说,NAT可以分为:传统NAT,双向NAT(Bi-Directional NAT),两次NAT(Twice NAT),多宿主NAT(Multihomed NAT),但是市场上现在最多的是传统NAT,尤其是NAPT设备,所以本文的穿透也是针对NAPT展开,NAT共分为两大类:非对称NAT(Cone NAT)和对称NAT(Symmetric NAT)。这两大类NAT又可细分为以下下四种类型:

1) 非对称NAT(Cone NAT)

a) 全ConeNAT(Full Cone NAT)
  内网主机建立一个UDP socket(LocalIP:LocalPort) 第一次使用这个socket给外部主机发送数据时NAT会给其分配一个公网(PublicIP:PublicPort),以后用这个socket向外面任何主机发送数据都将使用这对(PublicIP:PublicPort)。此外,任何外部主机只要知道这个(PublicIP:PublicPort)就可以发送数据给(PublicIP:PublicPort),内网的主机就能收到这个数据包

b) 限制性ConeNAT (Restricted Cone NAT)
  内网主机建立一个UDP socket(LocalIP:LocalPort) 第一次使用这个socket给外部主机发送数据时NAT会给其分配一个公网(PublicIP:PublicPort),以后用这个socket向外面任何主机发送数据都将使用这对(PublicIP:PublicPort)。此外,如果任何外部主机想要发送数据给这个内网主机,只要知道这个(PublicIP:PublicPort)并且内网主机之前用这个socket曾向这个外部主机IP发送过数据。只要满足这两个条件,这个外部主机就可以用自己的(IP,任何端口)发送数据给(PublicIP:PublicPort),内网的主机就能收到这个数据包
c) 端口限制性 ConeNAT(Port Restricted Cone NAT)
  内网主机建立一个UDP socket(LocalIP:LocalPort) 第一次使用这个socket给外部主机发送数据时NAT会给其分配一个公网(PublicIP:PublicPort),以后用这个socket向外面任何主机发送数据都将使用这对(PublicIP:PublicPort)。此外,如果任何外部主机想要发送数据给这个内网主机,只要知道这个(PublicIP:PublicPort)并且内网主机之前用这个socket曾向这个外部主机(IP,Port)发送过数据。只要满足这两个条件,这个外部主机就可以用自己的(IP,Port)发送数据给(PublicIP:PublicPort),内网的主机就能收到这个数据包

2) 对称NAT(Symmetric NAT)

  内网主机建立一个UDP socket(LocalIP,LocalPort),当用这个socket第一次发数据给外部主机1时,NAT为其映射一个(PublicIP-1,Port-1),以后内网主机发送给外部主机1的所有数据都是用这个(PublicIP-1,Port-1),如果内网主机同时用这个socket给外部主机2发送数据,第一次发送时,NAT会为其分配一个(PublicIP-2,Port-2), 以后内网主机发送给外部主机2的所有数据都是用这个(PublicIP-2,Port-2).如果NAT有多于一个公网IP,则PublicIP-1和PublicIP-2可能不同,如果NAT只有一个公网IP,则Port-1和Port-2肯定不同,也就是说一定不能是PublicIP-1等于 PublicIP-2且Port-1等于Port-2。此外,如果任何外部主机想要发送数据给这个内网主机,那么它首先应该收到内网主机发给他的数据,然后才能往回发送,否则即使他知道内网主机的一个(PublicIP,Port)也不能发送数据给内网主机,这种NAT无法实现UDP-P2P通信。
  
类型识别
  既然有着这些不同类型的NAT,那么我们在实际应用过程中就应该对处于不同NAT类型组合之后的终端给出不同的打洞策略。
  所以,在给出具体的NAT穿透策略之前,我们需要先识别当前的NAT是什么类型,然后再根据对方的NAT是什么类型,由此得到一个具体的穿透策略。
  STUN检测流程如下:
   这里写图片描述
  类型检测的前提条件是:有一个公网的Server并且绑定了两个公网IP(IP-1,IP-2)。这个Server做UDP监听(IP-1,Port-1),(IP-2,Port-2)并根据客户端的要求进行应答。

第一步:检测客户端是否有能力进行UDP通信以及客户端是否位于NAT后?

  客户端建立UDP socket然后用这个socket向服务器的(IP-1,Port-1)发送数据包要求服务器返回客户端的IP和Port, 客户端发送请求后立即开始接受数据包,要设定socket Timeout(300ms),防止无限堵塞. 重复这个过程若干次。如果每次都超时,无法接受到服务器的回应,则说明客户端无法进行UDP通信,可能是防火墙或NAT阻止UDP通信,这样的客户端也就 不能P2P了(检测停止)。
  当客户端能够接收到服务器的回应时,需要把服务器返回的客户端(IP,Port)和这个客户端socket的 (LocalIP,LocalPort)比较。如果完全相同则客户端不在NAT后,这样的客户端具有公网IP可以直接监听UDP端口接收数据进行通信(检 测停止)。否则客户端在NAT后要做进一步的NAT类型检测(继续)。

第二步:检测客户端NAT是否是Full Cone NAT?

  客户端建立UDP socket然后用这个socket向服务器的(IP-1,Port-1)发送数据包要求服务器用另一对(IP-2,Port-2)响应客户端的请求往回 发一个数据包,客户端发送请求后立即开始接受数据包,要设定socket Timeout(300ms),防止无限堵塞. 重复这个过程若干次。如果每次都超时,无法接受到服务器的回应,则说明客户端的NAT不是一个Full Cone NAT,具体类型有待下一步检测(继续)。如果能够接受到服务器从(IP-2,Port-2)返回的应答UDP包,则说明客户端是一个Full Cone NAT,这样的客户端能够进行UDP-P2P通信(检测停止)。

第三步:检测客户端NAT是否是Symmetric NAT?

  客户端建立UDP socket然后用这个socket向服务器的(IP-1,Port-1)发送数据包要求服务器返回客户端的IP和Port, 客户端发送请求后立即开始接受数据包,要设定socket Timeout(300ms),防止无限堵塞. 重复这个过程直到收到回应(一定能够收到,因为第一步保证了这个客户端可以进行UDP通信)。
  用同样的方法用一个socket向服务器的(IP-2,Port-2)发送数据包要求服务器返回客户端的IP和Port。
  比较上面两个过程从服务器返回的客户端(IP,Port),如果两个过程返回的(IP,Port)有一对不同则说明客户端为Symmetric NAT,这样的客户端无法进行UDP-P2P通信(检测停止)。否则是Restricted Cone NAT,是否为Port Restricted Cone NAT有待检测(继续)。

第四步:检测客户端NAT是否是Restricted Cone NAT还是Port Restricted Cone NAT?

  客户端建立UDP socket然后用这个socket向服务器的(IP-1,Port-1)发送数据包要求服务器用IP-1和一个不同于Port-1的端口发送一个UDP 数据包响应客户端, 客户端发送请求后立即开始接受数据包,要设定socket Timeout(300ms),防止无限堵塞. 重复这个过程若干次。如果每次都超时,无法接受到服务器的回应,则说明客户端是一个Port Restricted Cone NAT,如果能够收到服务器的响应则说明客户端是一个Restricted Cone NAT。以上两种NAT都可以进行UDP-P2P通信。

  注:以上检测过程中只说明了可否进行UDP-P2P的打洞通信,具体怎么通信一般要借助于Rendezvous Server。另外对于Symmetric NAT不是说完全不能进行UDP-P2P达洞通信,可以进行端口预测打洞,不过不能保证成功。

我在家中的NAT类型如下图:
这里写图片描述
在公司的NAT类型如下图:
这里写图片描述

我的全部实验主要在家–公司–阿里云服务器–多个朋友家里完成。

3、UDP内网穿透实验

  通过UDP打洞实现NAT穿越是一种在处于使用了NAT的私有网络中的Internet主机之间建立双向UDP连接的方法。由于NAT的行为是非标准化的,因此它并不能应用于所有类型的NAT。
  其基本思想是这样的:让位于NAT后的两台主机都与处于公共地址空间的服务器相连,然后,一旦NAT设备建立好UDP状态信息就转为直接通信,这项技术需要一个圆锥型NAT设备才能够正常工作。对称型NAT不能使用这项技术。

UDP打洞的过程大体上如下:
主机A和主机B都是通过NAT设备访问互联网,主机S位于互联网上。
1. A和B都与S之间通过UDP进行心跳连接
2. A通知S,要与B通信
3. S把B的公网IP、port告诉A,同时把A的公网IP、port告诉B
4. A向B的公网IP、port发送数据(这个数据包应该会被丢弃,但是打开了B回来的窗户)
5. B向A的公网IP、port发送数据(这个数据包就会被A接受,之后A和B就建立起了连接)

理论非常简单,所以此处不再贴代码,我的linux代码实现请这里—————————>>> UDP打洞代码下载<<<——————————–。
程序中我调用了readline库函数,所以,编译前请保证系统中已经安装了readline库。
ubuntu安装readline库:

sudo apt-get install libreadline5-dev

实验截图如下(代码有改动,这个是实验中截图):
这里写图片描述

程序工作流程说明:
1、在一个开放IP/PORT的主机上运行server程序,并设定监听端口。
2、两个客户端程序分别运行在两个网络环境中,并且使用login 命令登录服务器,登录时需要指定服务器ip信息以及自己的登录名。登录成功服务器会自动推送用户列表到客户端。
3、任何一方都可发起通信,通信时直接输入 sendto 对方用户名 数据
4、链接会在第一次通信时建立,链接建立成功后服务器遍失去作用,此时关闭服务器后两个客户端依然能够正常通信。

4、公开的免费STUN服务器

下面的地址不一定管用,可以多试试。

    stun01.sipphone.com
    stun.ekiga.net
    stun.fwdnet.net
    stun.ideasip.com
    stun.iptel.org
    stun.rixtelecom.se
    stun.schlund.de
    stunserver.org
    stun.softjoys.com
    stun.voiparound.com
    stun.voipbuster.com
    stun.voipstunt.com
    stun.voxgratia.org
    stun.xten.com

opensips - bw_0927 - 博客园

$
0
0

http://www.oschina.net/question/5189_8848

http://blog.chinaunix.net/link.php?url=http://blog.csdn.net%2Fgouooo%2Farchive%2F2009%2F01%2F03%2F3687757.aspx

1、引入

随着通信IP化的发展,IP传输的高带宽、低成本等优势使得越来越多的企业、电信运营商加快建设基于IP的各种通信应用。在通信协议IP化发展中,SIP协 议毫无争议地成为各大电信运营商构建其未来网络的基础协议,越来越多的SIP软件产品也不断出现在行业应用中。SIP协 议的标准化,同时也造就了一大批优秀的开源软件产品,包括Asterisk、SipXecs、FreeSWITCH、OpenSIPS等SIP服务端软 件,也包括X-lite、LinPhone、eyeBeam等SIP客户端软件。本文介绍了国外成熟的SIPServer开源项目OpenSIPS,并结 合其它通信方面的开源项目对其应用情况进行了一些介绍。

2、OpenSIPS介绍

OpenSIPS是一个成熟的开源SIP服务器,除了提供基本的SIP代理及SIP路由功能外,还提供了一些应用级的功能。OpenSIPS的结构非常 灵活,其核心路由功能完全通过脚本来实现,可灵活定制各种路由策略,可灵活应用于语音、视频通信、IM以 及Presence等多种应用。同时OpenSIPS性能上是目前最快的SIP服务器之一,可用于电信级产品构建。

2.1、功能特点

凭借其可扩展、模块化的系统架构,OpenSIPS提供了一个高度灵活的、用户可配置的路由引擎,可以为voice、video、IM和 presence等服务提供强大高效的路由、鉴权、NAT、网关协议转化等功能。由于其稳定高效等特点,OpenSIPS已经被诸多电信运营商应用在自己的网络体系中。其 主要功能如下:

SIP注册服务器/代理服务器(lcr、dynamic routing、dialplan)/重定向服务器
 SIP presence agent
 SIP B2BUA
 SIP IM Server
 SIP to SMS/XMPP网关
 SIP to XMPP网关
 SIP 负载均衡
 SIP NAT traversal

2.2、基本应用配置

OpenSIPS不但提供了丰富的功能,还具有操作简单的特点。所有OpenSIPS的应用功能都可以通过一个配置文件opensips.cfg来 实现的。该配置文件主要分为三个部分,第一部分主要是全局变量的设置;第二部分主要是加载模块,并设置模块的相应参数;第三部分主要是路由的策略和功能应 用。为了更清晰的呈现opensips.cfg配置文件带来的强大功能,接下来对这三个部分以简单的示例进一步说明。

第一部分全局变量的设置,通过一条语句就能指定用于侦听接收sip消息的端口和传输层协议。

第二部分负责模块的加载和参数配置。以之前的OpenSIPS的负载均衡功能配置为例,需要加载load_balancer模块,并进行配置。

第三部分是sip消息的路由和功能应用。还是以OpenSIPS的负载均衡功能为例。

从以上应用示例可以看到,通过在opensips.cfg中进行简单的配置,就能实现强大的功能。

2.3、系统结构

OpenSIPS的架构开放灵活,其核心功能控制均可通过脚本控制实现,各个功能也通过模块加载的方式来构建。采用lex和yacc工具构建的配置 文件分析器是其架构设计中的重要部分之一。通过这个分析器,opensips设计了自己的语法规则,使得我们可以适合SIP规 范的语言来进行配置文件中的脚本编写,从而达到简化程序以及方便代码阅读的目的。同时这样的设计也使opensips.cfg配置文件的执行速度达到了C 语言的级别。其体系结构大体如下图:

框架的最上层是用于实现sip消息路由逻辑的opensips.cfg脚本配置,在配置文件中,可以使用Core提供的Parameter和 Function,也可以使用众多Modules提供的Function。比如在之前的负载均衡示例中,is_method(“INVITE”)就属于 textops模块提供的功能,src_ip和src_port都属于Core提供的参数。下层,提供了网络传输、sip消息解析等基本功能。在左侧,通过相应的数据库适配器,可是使用多种数据库存取数据。在这样的 体系结构下,我们就可以方便地通过增加功能module来添加我们需要的功能,而不会对原有系统造成影响。

除了以上所述的OpenSIPS的优点,OpenSIPS还提供了一系列的管理维护命令的接口。我们可以通过Core和Module提供的MI管理 接口,方便的监控系统以及模块的状态。比如,通过Core的fifo ps命令,可以获取当前进程的状态;通过Core的fifo get_statistics命令,可以获得当前共享内存以及各进程私有内存的使用情况等等。通过MI管理接口,我们还可以方便地在运行时修改部分参数, 比如,对于load_balancer模块,我们可以通过fifo lb_reload命令,更新目标组的配置信息,可以通过fifo lb_status命令激活或关闭某个目标,这些命令在实际应用中都非常实用。如果希望通过WEB图形界面管理OpenSIPS,OpenSIPS社区还 提供了OpenSIPS Control Panel 4.0产品。

3、与其它开源项目共同搭建VOIP服务

OpenSIPS提供了以上那么多的功能,那OpenSIPS是不是已经实现了PBX的功能了?不是!OpenSIPS并不具备一个媒体服务器(Media Server)的功能。媒体服务器主要提供了类似VoiceMail、呼叫中语音交换、会议服务、视频服务等一系列和语音、视频相关的服务;而 OpenSIPS的主要功能主要在于代理、路由和网关。因此,单独的OpenSIPS并不能够提供VOIP服务,只有和Asterisk等具备媒体功能的软件整合,才能构建可靠的语音服务体系。

对于媒体服务器,开源世界也提供了很多选择,如老牌的Asterisk,以及功能全面的sipXecs以及专注于IVR功 能的FreeSwitch等,他们都是非常优秀的开源项目。其中Asterisk功能全面、灵活,但主要面向企业应用,在性能上稍差。但Asterisk 提供了完善的PBX功能,可以连接多种不同的电话终端,支持多种主流的IP电话协议和系统接口。FreeSwtich专注于IVR功能,性能、可靠性非常高。近期 FreeSwitch已被sipXecs采用作为其IVR部分功能。sipXecs则是一个功能比较全面的产品,包括IVR、VoiceMail、人工坐 席等等,更难得的是SipXecs提供了良好的配置、管理界面,易于使用。

只要将OpenSIPS作为前置接入,将多个Asterisk、FreeSwitch、sipXecs挂接在其后,由OpenSIPS实现SIP消 息的转发和负载均衡,就可以轻松地实现各种语音业务以及规模扩展。如下图所示。

4、小结

从文中介绍可以看出,OpenSIPS是一个成熟的电信级SIP Server平台,可广泛应用于SIP应用的路由分发、负载均衡,可用于搭建SIP代理,提供SIP注册服务等。而且目前OpenSIPS自身也提供 SIP Presence以及IM功能。同时,应该注意的是OpenSIPS本身并不提供媒体相关服务,如呼叫中心、VoiceMail等业务,该部分业务可通过 FreeSwtich、sipXecs等平台实现。

参考文献:
[1].OpenSIPS:  http://www.opensips.org/
[2] FreeSwitch wiki: http://wiki.freeswitch.org/wiki/Main_Page
[3] sipXecs:  http://www.sipfoundry.org/


ncnn 载入insightface训练好的性别年龄识别模型 - sinat_31425585的博客 - CSDN博客

$
0
0

1、模型转换

从insightface项目中下载mxnet模型: https://github.com/deepinsight/insightface/tree/master/gender-age/model

2、使用ncnn的模型转换工具mxnet2ncnn进行模型转换

./mxnet2ncnn model-symbol.json model-0000.params ag.param ag.bin

3、使用下面代码测试:

#include "ncnn/net.h"
#include "opencv2/core.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"

#include <iostream>
#include <vector>

int main(int argc, char* argv[]) {
    cv::Mat img_src = cv::imread("test.png");
    if (img_src.empty()) {
        std::cout << "input image is empty." << std::endl;
        return -1;
    }
    ncnn::Net net;
    if (net.load_param("ag.param") == -1 ||
        net.load_model("ag.bin") == -1) {
        std::cout << "load ga model failed." << std::endl;
        return -1;
    }

    ncnn::Extractor ex = net.create_extractor();
    ncnn::Mat img_ncnn = ncnn::Mat::from_pixels_resize(img_src.data,
        ncnn::Mat::PIXEL_BGR, img_src.cols, img_src.rows, 112, 112);
    ex.input("data", img_ncnn);
    ncnn::Mat img_out;
    ex.extract("fc1", img_out);
    std::vector<float> out;
    for (int i = 0; i < img_out.w; ++i) {
        out.push_back(img_out[i]);
    }
    if (out[0] > out[1]) {
        std::cout << "female" << std::endl;
    } else {
        std::cout << "male" << std::endl;
    }

    int counts = 0;
    for (int i = 2; i < 102; ++i) {
        if (out[2 * i] < out[2 * i + 1]) {
            ++counts;
        }
    }
    std::cout << "age: " << counts << std::endl;
    return 0;


}

参考资料:

[1] https://github.com/Tencent/ncnn

[2] https://github.com/deepinsight/insightface

CentOS6.9完全离线升级安装gcc-5.4.0 - weixin_40420213的博客 - CSDN博客

$
0
0

1、准备工作

系统自带的gcc版本为4.4.7,升级至5.4.0版本,需要提前准备以下安装包:
gcc-5.4.0.tar.gz 安装包
http://ftp.gnu.org/gnu/gcc/gcc-5.4.0/gcc-5.4.0.tar.gz

gmp-4.3.2.tar.bz2 gcc依赖包
ftp://ftp.gnu.org/gnu/gmp/gmp-4.3.2.tar.bz2

mpfr-2.4.2.tar.bz2 gcc依赖包
http://www.mpfr.org/mpfr-2.4.2/mpfr-2.4.2.tar.bz2

mpc-0.8.1.tar.gz gcc依赖包
http://www.multiprecision.org/mpc/download/mpc-0.8.1.tar.gz

上面三个依赖包的版本依据,可以将gcc-5.4.0.tar.gz解压后在gcc-5.4.0/contrib/download_prerequisites文件中找到


2、安装gmp-4.3.2

/** 解压 */tar -xjvf gmp-4.3.2.tar.bz2 -C /usr
cd /usr/gmp-4.3.2/** 创建编译目录 */mkdir gmp-build
cd gmp-build/** 执行配置  --prefix表示后面将要安装到的目标位置 */../configure --prefix=/usr/local/gmp-4.3.2/** 编译 */make/** 安装 */make install

3、安装mpfr-2.4.2
安装mpfr依赖于gmp,所以应先安装gmp。

/** 解压 */tar -xjvf mpfr-2.4.2.tar.bz2  -C /usr
cd /usr/mpfr-2.4.2/** 创建编译目录 */mkdir mpfr-build
cd mpfr-build/** 执行配置 */../configure --prefix=/usr/local/mpfr-2.4.2--with-gmp=/usr/local/gmp-4.3.2/** 编译 */make/** 安装 */make install

4、安装mpc-0.8.1.tar.gz

/** 解压 */tar -xzvf mpc-0.8.1.tar.gz  -C /usr
cd /usr/mpc-0.8.1/** 创建编译目录 */mkdir mpc-build
cd mpc-build/** 执行配置 */../configure --prefix=/usr/local/mpc-0.8.1--with-gmp=/usr/local/gmp-4.3.2--with-mpfr=/usr/local/mpfr-2.4.2/** 编译 */make/** 安装 */make install

5、添加环境变量

exportLD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/gmp-4.3.2/lib:/usr/local/mpc-0.8.1/lib:/usr/local/mpfr-2.4.2/lib

6、安装gcc-5.4.0

/** 解压 */tar -xzvf gcc-5.4.0.tar.gz  -C /usr
cd /usr/gcc-5.4.0/** 创建编译目录 */mkdir gcc-build
cd gcc-build/** 执行配置 */../configure --prefix=/usr/local/gcc-5.4.0--enable-threads=posix --disable-checking --disable-multilib --enable-languages=c,c++ --with-gmp=/usr/local/gmp-4.3.2--with-mpfr=/usr/local/mpfr-2.4.2--with-mpc=/usr/local/mpc-0.8.1/** 编译(这一步执行时间及其漫长,请耐心等待)*/make/** 安装 */make install

现在gcc-5.4.0安装完成了,但是gcc-5.4.0的环境变量还没有设置,系统中使用的还是旧版的gcc


7、重新设置gcc环境变量,更新系统gcc版本号

/** 备份gcc-4.4.7 */mkdir /usr/gcc447backup/
mv /usr/bin/{gcc,g++} /usr/gcc447backup
ln -s /usr/local/gcc-5.4.0/bin/gcc /usr/bin/gcc
ln -s /usr/local/gcc-5.4.0/bin/g++ /usr/bin/g++
gcc -v

8、验证

gcc -v/** 或者 */g++ -v

如果显示的gcc版本仍是以前的版本,可以重启系统;

/** 查看gcc的安装位置*/which gcc

验证成功
这里写图片描述

VirtualBox文件系统已满--磁盘扩容 - CTHON - 博客园

$
0
0

第1步:为virtualbox虚拟电脑扩容

进入命令行,以Windows系统为例

(特别注意空格和中文)

1.启动CMD命令行,进入VirtualBox的安装目录。如

运行:cmd
C:\Users\Administrator\>D:
D:\>cd  "\Program Files\Oracle\VirtualBox"
D:\Program Files\Oracle\VirtualBox> VBoxManage.exe modifyhd YOUR_HARD_DISK.vdi --resize SIZE_IN_MB
D:\Program Files\Oracle\VirtualBox>
其中参数 YOUR_HARD_DISK.vdi 是您要修改的 VirtualBox 虚拟硬盘镜像文件。而参数 SIZE_IN_MB 是指修改后的硬盘容量,单位是MB。 
3. 调整磁盘空间为15G:
比如下面这行命令将会把名为"ubuntu.vdi "的 VirtualBox 硬盘容量修改为15*1024MB。
C:\Program Files\Oracle\VirtualBox>VBoxManage.exe modifyhd "E:\dds\VirtualBox VMs\linux\linux-bak.vdi" --resize 15360
 
0%....10%....20%....30%....40%....50%....60%....70%....80%....90%....100%.
 
D:\Program Files\VirtualBox>
 

 

注意:如果路径中包含空格或中文,整个路径要用英文状态引号"括起来,在WIN7的CMD下可以输入部分路径然后按TAB键补全,CMD监测到路径中有空格会自动为路径名加引号的,如:

D:\Program Files\Oracle\VirtualBox>VBoxManage.exe modifyhd "F:\VirtualBox\my ubuntu.vdi"--resize 15360

D:\Program Files\Oracle\VirtualBox>VBoxManage.exe modifyhd "F:\虚拟机\my ubuntu.vdi"--resize 15360

 

另,VBOX好像对MS的文件系统,比如NTFS的可能无法更改大小,一网友测试没成功,相对来说对 Linux系的支持更好一些

 

C:\Program Files\Oracle\VirtualBox>VBoxManage.exe modifyhd "E:\dds\VirtualBox VMs\linux-bak\linux-bak.vdi" --resize 20480

0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%

C:\Program Files\Oracle\VirtualBox>cd E:\dds\VirtualBox VMs

 

 

4.查看新的磁盘空间

  重新启动虚拟机,查看磁盘情况。

  [root@aimin ~]# fdisk -l /dev/sda

  Disk /dev/sda: 15.7 GB, 15728640000 bytes 255 heads, 63 sectors/track, 1912 cylinders

  可以看到磁盘空间已经扩展到15G,但这时还不可以使用。

5.Enable新增加的空间

  使用 fdisk 将虚拟磁盘的空闲空间创建为一个新的分区。注意要使用代表 Linux LVM 的分区号 8e 来作为 ID。

  # fdisk /dev/sda     ---用root用户操作

  n {new partition}   

  p {primary partition}

  3 {partition number}

第2步、分区

1、虚拟机开机 
2、查看磁盘情况 

纳尼,我不是已经扩容了吗?怎么还是提醒我空间不够用?原来虽然已经扩大了磁盘,但是由于还没有经过分区,指定文件系统[格式化],所以linux操作系统无法识别(其实就相当于你插入了一块新硬盘,但是你并没有对其进行分区、格式化是一个道理)。

3、开始分区 

输入命令 $sudo fdisk /dev/sda 
然后按m查看帮助文档如下所示: 

 

如上图所示,按n命令的话就增加一个分区,如下所示 

 


如上图所示: 
首先输入命令:n(添加新分区)之后回车: 
接着输入命令:p 
剩下步骤全按回车默认, 
最后输入命令w保存分区信息。

再查看下分区信息:fdisk -l 

发现多了一个分区 /dev/sda4。(之前重复做了一次分区操作,划了一个分区/dev/sda3) 

然后重启Ubuntu虚拟机。

4、格式化分区 
格式化刚才划好的分区/dev/sda4

sudo mkfs -t ext4 /dev/sda4

 

5、挂载分区 
创建目录 /home/cthon/sda4

sudo mkdir sda4

将分区 /dev/sda4 挂载到 /home/cthon/sda4

sudo mount /dev/sda4 /home/cthon/sda4

 

6、开机自动挂载,则修改/etc/fstab文件,在这个文件里面添加一行: 
使用vim命令编辑/etc/fstab文件

sudo vim /etc/fstab

增加如下一行代码

/dev/sda4 /home/cthon/sda4/ ext4 defaults 0 1

 

至此,容量扩展完成了。

再次查看下刚刚挂载好的分区

df -H

 

参考文档:

https://blog.csdn.net/abiggg/article/details/79383255

http://blog.csdn.net/ganshuyu/article/details/17954733

http://www.cnblogs.com/jyzhao/p/4778657.html

http://www.2cto.com/os/201406/309339.html

使用ffmpeg编码时,如何设置恒定码率,并控制好关键帧I帧间隔 - 黑色幽默2018 - 博客园

$
0
0

      1. 大家在 使用ffmpeg进行视频 编码时, 使用-b命令,想 控制比特率,却发现结果并没有如我们 设置所愿,通过码流分析器观察视频码流, 码率的波动还是很大的, ffmpeg控制的并不好,这时候,我们可以通过以下命令解决:

-maxrate biterate -minrate biterate -bf1-b_strategy0

      其中 -maxrate、-minrate为 设置最小最大比特率,-bf为设置B帧数目,其实就是设置 编码是B、P、I帧的结构,我这里设置的为IPBPBP结构,-b_strategy这个命令是为了自适应的添加B帧数目,ffmpeg编码器会根据视频的应用场景,自适应的添加B帧,通过设置-b_strategy 0,,将这个功能关闭,那么就会根据你的设置要求进行编码。除此之外,还可以使用-pass,进行2次 码率控制,编出来的视频效果更好;下面我介绍-pass的使用方法:

      (1) -pass 1 -passlogfile ffmpeg2pass 第一步先编一次,生成 ffmpeg2pass 文件

      (2) -pass 2 -passlogfile ffmpeg2pass 第二次会根据第一次生成的ffmpeg2pass 文件,再进行码率控制。

2.  如何设置视频 关键帧I帧间隔问题

       刚开始我只使用-g命令,设置GOP长度,编码后,发现I帧间隔长度并不是我想要的,后来我通过以下命令问题解决了:

-keyint_min60-g60-sc_threshold0

     其中-keyint_min为最小 关键帧间隔,我这里设置为60帧;-sc_threshold这个命令会根据视频的运动场景,自动为你添加额外的I帧,所以会导致你编出来的视频关键帧间隔不是你设置的长度,这是只要将它设为0,问题就得到解决了!!

 

        3.在用ffmpeg转换视频到flv过程中,需要设置关键帧的间隔,以便在播放过程中实现精确定位。在网上查找了不少,最后发现这个指令有效:

-g1-keyint_min2

 

// 将关键帧帧间隔设置为2s

./ffmpeg -i ~/Documents/video/fc.mkv -acodec libfdk_aac -vcodec libx264 -keyint_min 50 -g 50 -sc_threshold 0 fc_transcode.mkv

 

如果使用OBS推流,可以在设置中设置关键帧间隔

HTTP-FLV直播初探 - 冒雨ing - 博客园

$
0
0

两个flv.js的扩展版本:

https://github.com/saysmy/flvjs-pr354

https://github.com/virgoone/vplyr



目前几种视频流的简单对比:

协议

httpflv

rtmp

hls

dash

传输方式

http流

tcp流

http

http

视频封装格式

flv

flv tag

Ts文件

Mp4 3gp webm

延时

数据分段

连续流

连续流

切片文件

切片文件

Html5播放

可通过html5解封包播放(flv.js)

不支持

可通过html5解封包播放(hls.js)

如果dash文件列表是mp4webm文件,可直接播放

 

  • RTMP(Real Time Messaging Protocol)是基于TCP的,由Adobe公司为Flash播放器和服务器之间音频、视频传输开发的开放协议。

  • HLS(HTTP Live Streaming)是基于HTTP的,是Apple公司开放的音视频传输协议。

  • HTTP FLV则是将RTMP封装在HTTP协议之上的,可以更好的穿透防火墙等。

 

  

 

Http_flv & RTMP

这两个协议实际上传输数据是一样的, 数据都是flv文件的tag。http_flv是一个无限大的http流的文件,相比rtmp就只能直播,而rtmp还可以推流和更多的操作。但是http有个好处,就是是以80http通信的,穿透性强,而且rtmp是非开放协议。

这两个协议是如今直播平台主选的直播方式,主要原因就是延时极低。

将测试:RTMP延迟1s左右,HTTPFLV延迟1-2s左右,可用于对延迟要求比较苛刻的场景,但要注意兼容性,文章最后会说明HTTPFLV兼容性。


 

 

HTTP FLV直播Demo:

复制代码
<!DOCTYPE html><html><head><meta content="text/html; charset=utf-8" http-equiv="Content-Type"><title>flv.js demo</title><style>
        .mainContainer {
            display: block;
            width: 1024px;
            margin-left: auto;
            margin-right: auto;
        }

        .urlInput {
            display: block;
            width: 100%;
            margin-left: auto;
            margin-right: auto;
            margin-top: 8px;
            margin-bottom: 8px;
        }

        .centeredVideo {
            display: block;
            width: 100%;
            height: 576px;
            margin-left: auto;
            margin-right: auto;
            margin-bottom: auto;
        }

        .controls {
            display: block;
            width: 100%;
            text-align: left;
            margin-left: auto;
            margin-right: auto;
            margin-top: 8px;
            margin-bottom: 10px;
        }

        .logcatBox {
            border-color: #CCCCCC;
            font-size: 11px;
            font-family: Menlo, Consolas, monospace;
            display: block;
            width: 100%;
            text-align: left;
            margin-left: auto;
            margin-right: auto;
        }
    </style></head><body><div class="mainContainer"><video name="videoElement" class="centeredVideo" id="videoElement" controls width="1024" height="576" autoplay>
            Your browser is too old which doesn't support HTML5 video.</video></div><script src="./flv.js?v=2"></script><script>
         if (flvjs.isSupported()) {
            startVideo()
        }

        function startVideo(){
            var videoElement = document.getElementById('videoElement');
            var flvPlayer = flvjs.createPlayer({
                type: 'flv',
                isLive: true,
                hasAudio: true,
                hasVideo: true,
                enableStashBuffer: true,
                url: 'https://xl.live-play.acgvideo.com/live-xl/520658/live_12860646_332_c521e483.flv?wsSecret=778d91efcb22c588be28cb67ebe57082&wsTime=1510929009'
            });
            flvPlayer.attachMediaElement(videoElement);
            flvPlayer.load();
            flvPlayer.play();
        }

        videoElement.addEventListener('click', function(){
            alert( '是否支持点播视频:' + flvjs.getFeatureList().mseFlvPlayback + ' 是否支持httpflv直播流:' + flvjs.getFeatureList().mseLiveFlvPlayback )
        })

        function destoryVideo(){
            flvPlayer.pause();
            flvPlayer.unload();
            flvPlayer.detachMediaElement();
            flvPlayer.destroy();
            flvPlayer = null;
        }

        function reloadVideo(){
            destoryVideo()
            startVideo()
        }
    </script></body></html>
复制代码

 

flv.js问题:(暂时发现这几个)

1. 播放一段时间后,音视频不同步

2. 播放一段时间后,音频模糊

3. 暂停后继续播放是接着暂停时的场景继续播,对于直播会产生延迟 =》 临时解决方案:暂停后继续播放时,手动销毁视频再重新加载播放

4. 手机端兼容性差

 --------------------------------------------

1,2 问题解决方案:

尝试设置 config.fixAudioTimestampGap = false

控制台将不会输出大量警告信息。

经检测,不同的推流客户端,会导致音视频同步问题有不一样的体现。

LFLiveKit 的音频流时间戳问题,定期会有两帧之间存在两倍时间戳差,会导致严重音画不同步。

目前在我们平台,ios客户端音视频均同步,安卓客户端音视频不同步,需要设置flvjs的config.fixAudioTimestampGap = false才会音视频同步。

github issue: https://github.com/Bilibili/flv.js/issues/136

----------------------------------------------

判断flv.js在手机端是否支持点播和httpflv直播:

是否支持点播视频:flvjs.getFeatureList().mseFlvPlayback

是否支持httpflv直播流:flvjs.getFeatureList().mseLiveFlvPlayback 

目前测试结果:

ios :均不支持,包括微信和safari

安卓:微信均不支持;其他浏览器部分支持点播,全部不支持直播

 

完整版demo: https://github.com/saysmy/http-flv

 

--------------------------------------------

2019-01-05更新

经过多天的测试,对数十位主播分别用flvjs的master分支、 #136#354进行10分钟到2小时的测试,总结一下结论:

1. master分支、issue 136 都会出现不同程度主播音画不同步的情况,master分支音画不同步情况尤其严重。

2. #354 pr 可以正常播放所有主播的音视频,均同步。

3. 腾讯云TCPlayerLite和Web 直播播放器 1.0均使用flvjs库进行以html5方式播放flv(猜测使用master分支),经测试也是出现大面积音画不同步现象。

结论:

如果想在pc上使用flvjs播放flv格式直播,请选用  flvjs的PR#354

也期待flvjs作者可以尽早确认此PR并合并到master,造福全人类!

免费SSL证书(https网站)申请 - osfipin - 博客园

$
0
0

如何拥有一个自己的免费的SSL证书,并且能够长期拥有。这篇文章让你找到可用的免费证书o(* ̄︶ ̄*)o

各厂商提供的免费SSL基本是Symantec(赛门铁克),申请一年,不支持通配符,有数量限制。

1、阿里云
https://common-buy.aliyun.com/?commodityCode=cas#/buy
免费数字证书,最多保护一个明细子域名,不支持通配符,一个阿云帐户最多签发20张免费证书。兼容性如下操作系统版本IOS 5.0+、Android 2.3.3+、JRE 1.6.5+、WIN 7+。
【动态】Digicert 于 2017年12月1日 ,完成对 Symantec 证书服务的并购。此后,所有新申请的 Symantec/GeoTrust 品牌证书,切换到 Digicert+Symantec 交叉认证 PKI 体系下签发。阿里云平台的Symantec/GeoTrust已签发的旧根,也会按计划更新到新交叉根下。
赛门铁克是 SSL/TLS 证书的领先提供商,为全球一百多万台网络服务器提供安全防护。选择赛门铁克后,证书颁发机构 (CA) 将妥善保护您的网站和信誉,让您安枕无忧。

2、腾讯云DV SSL 证书
https://cloud.tencent.com/product/ssl
同一主域最多只能申请20张亚洲诚信品牌免费型DV版SSL证书(一级域名及其子域名均属于同一主域,例如 domain.com、ssl.domain.com、ssl.ssl.domain.com 都属于同一主域)。赛门铁克亚太白金战略合作伙伴亚洲诚信(TrustAsia)自研证书品牌,由赛门铁克根证书签发。仅支持绑定一个一级域名或者子域名,例如 domain.com、ssl.domain.com、ssl.ssl.domain.com 分别为一个域名。

3、Let’s Encrypt 支持通配符
https://letsencrypt.org/
Let's Encrypt是一个免费并且开源的CA,且已经获得Mozilla、微软等主要浏览器厂商的根授信。它极大低降低DV证书的入门门槛,进而推进全网的HTTPS化。可以使用相关的软件(需要部署到自己的服务器)直接生成设置。
备注:https://letsencrypt.osfipin.com/ 不需要在自己服务器上部署软件,直接申请,目前需要三个月续期一次。

4、百度云
https://cloud.baidu.com/product/ssl.html
Symantec是全球最大的信息安全服务商。该证书为国内证书服务商TrustAsia(亚洲诚信)联合Symantec为百度云用户专供的证书产品。市场价值1900元。

4、CloudFlare SSL
https://www.cloudflare.com/
在Cloudflare的“加密”中可以一键开启SSL,这个SSL总共四种模式:关闭、Flexible SSL、Full SSL、Full SSL (strict)、Strict (SSL-Only Origin Pull)。
Flexible SSL:您的网站访问者和Cloudflare之间有加密连接,但是从Cloudflare到您的服务器没有加密。即半程加密。优点在于:你的网站不需要SSL证书,用户也能实现SSL加密访问。
Full SSL:全程加密,即从你的网站到CDN服务器再到用户,全程都是SSL加密的。优点在于:只要你的服务器有SSL证书(不管是自签名证书还是购买的SSL),就可以实现SSL加密访问。
Full SSL (strict):全程加密,它与Full SSL的区别在于你的服务器必须是安装了那些已经受信任的SSL证书(即购买的SSL证书),否则无法开启SSL加密访问。
Strict (SSL-Only Origin Pull):企业模式。自动将所有的Http转化为Https加密访问,要求你的服务器安装了受信任的有效的SSL证书。

一些截图:

certbot在Centos7上配置合法签名证书,实现nginx的https访问-咖啡猫Mr-51CTO博客

$
0
0

咖菲猫-李常明笔记


  公司因之前使用的openssh创建的自签名证书,有一个弊端,就是在某些客户端上不能使用此证书,无法使用https连接,所以,研究了一下certbot 做签名证书!

certbot的官网地址:

https://certbot.eff.org/

1、制作证书前的准备:

你需要有一个公网地址,并绑定合法域名

2、开始制作:

(1)、下载Certbot客户端:

wget https://dl.eff.org/certbot-auto

(2)、下载后,进入下载的目录,添加执行权限

chmod   a+x   ./certbot-auto


3、介绍一下certbot的两种工作方式:

   (1)、 standalone 方式: certbot 会自己运行一个 web server 来进行验证。如果我们自己的服务器上已经有 web server 正在运行 (比如 Nginx 或 Apache ),用 standalone 方式的话需要先关掉它,以免冲突。

  (2)、webroot 方式: certbot 会利用既有的 web server,在其 web root目录下创建隐藏文件, Let’s Encrypt 服务端会通过域名来访问这些隐藏文件,以确认你的确拥有对应域名的控制权。

4、我使用的是webroot方式,自己搭建一个nginx服务器,配置location字段,如下:

        (1)、使用rpm安装nginx

sudo  yum  -y  install  nginx

(2)、编辑nginx的配置文件,修改以下参数:

在http 段范围

clipboard.png


(3)、修改完nginx配置文件后,使用nginx  -t命令,测试配置文件语法:

sudo  nginx   -t 

#返回OK  表示配置文件修改成功

(4)、启动nginx服务

sudo  nginx
netstat  -anplut  | grep  80    #检测80端口,是否在监听

(5)、使用certbot-auto命令,生成证书

./certbot-auto certonly --webroot -w /usr/share/nginx/html/ -d  [填写合法域名的地址]

#-w   表示  nginx中指定的root 网站根目录的路径

(6)、上述命令执行成功后,返回以下界面:

clipboard.png


从上图中可以看到,会在/etc/letsencrypt/live下 生成你的域名的文件夹,并且目录下会有此文件:

[centos@shuzhiyuan1 ~]$ tree /etc/letsencrypt/
/etc/letsencrypt/
├── accounts
│?? └── acme-v01.api.letsencrypt.org [error opening dir]
├── archive [error opening dir]
├── csr
│?? └── 0000_csr-certbot.pem
├── keys [error opening dir]
├── live
│?? └── kafeimao.com (别名,最终,看自己域名)
│??     ├── cert.pem -> ../../archive/kafeimao.com/cert1.pem
│??     ├── chain.pem -> ../../archive/kafeimao.com/chain1.pem
│??     ├── fullchain.pem -> ../../archive/kafeimao.com/fullchain1.pem
│??     ├── privkey.pem -> ../../archive/kafeimao.com/privkey1.pem
│??     └── README
├── options-ssl-apache.conf
├── options-ssl-nginx.conf
├── renewal
│?? └── kafeimao.com.conf
├── renewal-hooks
│?? ├── deploy
│?? ├── post
│?? └── pre
└── ssl-dhparams.pem

12 directories, 10 files


nginx的https 访问,需要用到 上述两个 pem的证书文件:


5、测试配置nginx支持https访问,测试 https的证书是否可用:

server {
        listen       443 ssl http2 default_server;
        listen       [::]:443 ssl http2 default_server;
        server_name  kafeimao.com;
        ssl           on;
        ssl_certificate "/etc/letsencrypt/live/kafeimao.com/fullchain.pem";
        ssl_certificate_key "/etc/letsencrypt/live/kafeimao.com/privkey.pem";
#        ssl_session_cache shared:SSL:1m;
#        ssl_session_timeout  10m;
#        ssl_ciphers HIGH:!aNULL:!MD5;
#        ssl_prefer_server_ciphers on;
# Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;

        location / {
                root  /usr/share/nginx/html/kafeimao.com;
                index  index.html;
        }

        error_page 404 /404.html;
            location = /40x.html {
        }

        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
    }
  }

6、重载nginx服务;

sudo   nginx  -s  reload


7、访问域名,测试https连接:

clipboard.png


8、发现证书是可用的,值得祝贺,你已经成功了!  非常简单


总结:

    certbot默认注册的证书 ,有效期是90天, 需要更新证书

使用命令进行更新:

(1)、手动更新

./certbot-auto  renew   -v

(2)、自动更新

./certbot-auto  renew  --quiet  --no-self-upgrade

 在注册证书时,如果遇到此错误:

clipboard.png


这个错误,跟命令中使用了webroot方式,(官网也推荐这种方式)所以,nginx里要配置正确的location字段,就是server中的配置;


此文仅做个笔记,没有什么深奥的东西,咖菲猫一直在前进;



Flink 全网最全资源(视频、博客、PPT、入门、实战、源码解析、问答等持续更新)

$
0
0


Flink 学习项目代码

https://github.com/zhisheng17/flink-learning

麻烦路过的各位亲给这个项目点个 star,太不易了,写了这么多,算是对我坚持下来的一种鼓励吧!

本项目结构

博客

1、 Flink 从0到1学习 —— Apache Flink 介绍

2、 Flink 从0到1学习 —— Mac 上搭建 Flink 1.6.0 环境并构建运行简单程序入门

3、 Flink 从0到1学习 —— Flink 配置文件详解

4、 Flink 从0到1学习 —— Data Source 介绍

5、 Flink 从0到1学习 —— 如何自定义 Data Source ?

6、 Flink 从0到1学习 —— Data Sink 介绍

7、 Flink 从0到1学习 —— 如何自定义 Data Sink ?

8、 Flink 从0到1学习 —— Flink Data transformation(转换)

9、 Flink 从0到1学习 —— 介绍 Flink 中的 Stream Windows

10、 Flink 从0到1学习 —— Flink 中的几种 Time 详解

11、 Flink 从0到1学习 —— Flink 读取 Kafka 数据写入到 ElasticSearch

12、 Flink 从0到1学习 —— Flink 项目如何运行?

13、 Flink 从0到1学习 —— Flink 读取 Kafka 数据写入到 Kafka

14、 Flink 从0到1学习 —— Flink JobManager 高可用性配置

15、 Flink 从0到1学习 —— Flink parallelism 和 Slot 介绍

16、 Flink 从0到1学习 —— Flink 读取 Kafka 数据批量写入到 MySQL

17、 Flink 从0到1学习 —— Flink 读取 Kafka 数据写入到 RabbitMQ

18、 Flink 从0到1学习 —— Flink 读取 Kafka 数据写入到 HBase

19、 Flink 从0到1学习 —— Flink 读取 Kafka 数据写入到 HDFS

20、 Flink 从0到1学习 —— Flink 读取 Kafka 数据写入到 Redis

21、 Flink 从0到1学习 —— Flink 读取 Kafka 数据写入到 Cassandra

22、 Flink 从0到1学习 —— Flink 读取 Kafka 数据写入到 Flume

23、 Flink 从0到1学习 —— Flink 读取 Kafka 数据写入到 InfluxDB

24、 Flink 从0到1学习 —— Flink 读取 Kafka 数据写入到 RocketMQ

25、 Flink 从0到1学习 —— 你上传的 jar 包藏到哪里去了

26、 Flink 从0到1学习 —— 你的 Flink job 日志跑到哪里去了

Flink 源码项目结构

学习资料

另外我自己整理了些 Flink 的学习资料,目前已经全部放到微信公众号了。
你可以加我的微信: zhisheng_tian,然后回复关键字: Flink即可无条件获取到,转载请联系本人获取授权,违者必究。

更多私密资料请加入知识星球!

有人要问知识星球里面更新什么内容?值得加入吗?

目前知识星球内已更新的系列文章:

1、 Flink 源码解析 —— 源码编译运行

2、 Flink 源码解析 —— 项目结构一览

3、 Flink 源码解析—— local 模式启动流程

4、 Flink 源码解析 —— standalonesession 模式启动流程

5、 Flink 源码解析 —— Standalone Session Cluster 启动流程深度分析之 Job Manager 启动

6、 Flink 源码解析 —— Standalone Session Cluster 启动流程深度分析之 Task Manager 启动

7、 Flink 源码解析 —— 分析 Batch WordCount 程序的执行过程

8、 Flink 源码解析 —— 分析 Streaming WordCount 程序的执行过程

9、 Flink 源码解析 —— 如何获取 JobGraph?

10、 Flink 源码解析 —— 如何获取 StreamGraph?

11、 Flink 源码解析 —— Flink JobManager 有什么作用?

12、 Flink 源码解析 —— Flink TaskManager 有什么作用?

13、 Flink 源码解析 —— JobManager 处理 SubmitJob 的过程

14、 Flink 源码解析 —— TaskManager 处理 SubmitJob 的过程

15、 Flink 源码解析 —— 深度解析 Flink Checkpoint 机制

16、 Flink 源码解析 —— 深度解析 Flink 序列化机制

17、 Flink 源码解析 —— 深度解析 Flink 是如何管理好内存的?

除了《从1到100深入学习Flink》源码学习这个系列文章,《从0到1学习Flink》的案例文章也会优先在知识星球更新,让大家先通过一些 demo 学习 Flink,再去深入源码学习!

如果学习 Flink 的过程中,遇到什么问题,可以在里面提问,我会优先解答,这里做个抱歉,自己平时工作也挺忙,微信的问题不能做全部做一些解答,
但肯定会优先回复给知识星球的付费用户的,庆幸的是现在星球里的活跃氛围还是可以的,有不少问题通过提问和解答的方式沉淀了下来。

1、 为何我使用 ValueState 保存状态 Job 恢复是状态没恢复?

2、 flink中watermark究竟是如何生成的,生成的规则是什么,怎么用来处理乱序数据

3、 消费kafka数据的时候,如果遇到了脏数据,或者是不符合规则的数据等等怎么处理呢?

4、 在Kafka 集群中怎么指定读取/写入数据到指定broker或从指定broker的offset开始消费?

5、 Flink能通过oozie或者azkaban提交吗?

6、 jobmanager挂掉后,提交的job怎么不经过手动重新提交执行?

7、 使用flink-web-ui提交作业并执行 但是/opt/flink/log目录下没有日志文件 请问关于flink的日志(包括jobmanager、taskmanager、每个job自己的日志默认分别存在哪个目录 )需要怎么配置?

8、 通过flink 仪表盘提交的jar 是存储在哪个目录下?

9、 从Kafka消费数据进行etl清洗,把结果写入hdfs映射成hive表,压缩格式、hive直接能够读取flink写出的文件、按照文件大小或者时间滚动生成文件

10、 flink jar包上传至集群上运行,挂掉后,挂掉期间kafka中未被消费的数据,在重新启动程序后,是自动从checkpoint获取挂掉之前的kafka offset位置,自动消费之前的数据进行处理,还是需要某些手动的操作呢?

11、 flink 启动时不自动创建 上传jar的路径,能指定一个创建好的目录吗

12、 Flink sink to es 集群上报 slot 不够,单机跑是好的,为什么?

13、 Fllink to elasticsearch如何创建索引文档期时间戳?

14、 blink有没有api文档或者demo,是否建议blink用于生产环境。

15、 flink的Python api怎样?bug多吗?

16、 Flink VS Spark Streaming VS Storm VS Kafka Stream

17、 你们做实时大屏的技术架构是什么样子的?flume→kafka→flink→redis,然后后端去redis里面捞数据,酱紫可行吗?

18、 做一个统计指标的时候,需要在Flink的计算过程中多次读写redis,感觉好怪,星主有没有好的方案?

19、 Flink 使用场景大分析,列举了很多的常用场景,可以好好参考一下

20、 将kafka中数据sink到mysql时,metadata的数据为空,导入mysql数据不成功???

21、 使用了ValueState来保存中间状态,在运行时中间状态保存正常,但是在手动停止后,再重新运行,发现中间状态值没有了,之前出现的键值是从0开始计数的,这是为什么?是需要实现CheckpointedFunction吗?

22、 flink on yarn jobmanager的HA需要怎么配置。还是说yarn给管理了

23、 有两个数据流就行connect,其中一个是实时数据流(kafka 读取),另一个是配置流。由于配置流是从关系型数据库中读取,速度较慢,导致实时数据流流入数据的时候,配置信息还未发送,这样会导致有些实时数据读取不到配置信息。目前采取的措施是在connect方法后的flatmap的实现的在open 方法中,提前加载一次配置信息,感觉这种实现方式不友好,请问还有其他的实现方式吗?

24、 Flink能通过oozie或者azkaban提交吗?

25、 不采用yarm部署flink,还有其他的方案吗? 主要想解决服务器重启后,flink服务怎么自动拉起? jobmanager挂掉后,提交的job怎么不经过手动重新提交执行?

26、 在一个 Job 里将同份数据昨晚清洗操作后,sink 到后端多个地方(看业务需求),如何保持一致性?(一个sink出错,另外的也保证不能插入)

27、 flink sql任务在某个特定阶段会发生tm和jm丢失心跳,是不是由于gc时间过长呢,

28、 有这样一个需求,统计用户近两周进入产品详情页的来源(1首页大搜索,2产品频道搜索,3其他),为php后端提供数据支持,该信息在端上报事件中,php直接获取有点困难。 我现在的解决方案 通过flink滚动窗口(半小时),统计用户半小时内3个来源pv,然后按照日期序列化,直接写mysql。php从数据库中解析出来,再去统计近两周占比。 问题1,这个需求适合用flink去做吗? 问题2,我的方案总感觉怪怪的,有没有好的方案?

29、 一个task slot 只能同时运行一个任务还是多个任务呢?如果task slot运行的任务比较大,会出现OOM的情况吗?

30、 你们怎么对线上flink做监控的,如果整个程序失败了怎么自动重启等等

31、 flink cep规则动态解析有接触吗?有没有成型的框架?

32、 每一个Window都有一个watermark吗?window是怎么根据watermark进行触发或者销毁的?

33、 CheckPoint与SavePoint的区别是什么?

34、 flink可以在算子中共享状态吗?或者大佬你有什么方法可以共享状态的呢?

35、 运行几分钟就报了,看taskmager日志,报的是 failed elasticsearch bulk request null,可是我代码里面已经做过空值判断了呀 而且也过滤掉了,flink版本1.7.2 es版本6.3.1

36、 这种情况,我们调并行度 还是配置参数好

37、 大家都用jdbc写,各种数据库增删查改拼sql有没有觉得很累,ps.set代码一大堆,还要计算每个参数的位置

38、 关于datasource的配置,每个taskmanager对应一个datasource?还是每个slot? 实际运行下来,每个slot中datasorce线程池只要设置1就行了,多了也用不到?

39、 kafka现在每天出现数据丢失,现在小批量数据,一天200W左右, kafka版本为 1.0.0,集群总共7个节点,TOPIC有十六个分区,单条报文1.5k左右

40、 根据key.hash的绝对值 对并发度求模,进行分组,假设10各并发度,实际只有8个分区有处理数据,有2个始终不处理,还有一个分区处理的数据是其他的三倍,如截图

41、 flink每7小时不知道在处理什么, CPU 负载 每7小时,有一次高峰,5分钟内平均负载超过0.8,如截图

42、 有没有Flink写的项目推荐?我想看到用Flink写的整体项目是怎么组织的,不单单是一个单例子

43、 Flink 源码的结构图

44、 我想根据不同业务表(case when)进行不同的redis sink(hash ,set),我要如何操作?

45、 这个需要清理什么数据呀,我把hdfs里面的已经清理了 启动还是报这个

46、 在流处理系统,在机器发生故障恢复之后,什么情况消息最多会被处理一次?什么情况消息最少会被处理一次呢?

47、 我检查点都调到5分钟了,这是什么问题

48、 reduce方法后 那个交易时间 怎么不是最新的,是第一次进入的那个时间,

49、 Flink on Yarn 模式,用yarn session脚本启动的时候,我在后台没有看到到Jobmanager,TaskManager,ApplicationMaster这几个进程,想请问一下这是什么原因呢?因为之前看官网的时候,说Jobmanager就是一个jvm进程,Taskmanage也是一个JVM进程

50、 Flink on Yarn的时候得指定 多少个TaskManager和每个TaskManager slot去运行任务,这样做感觉不太合理,因为用户也不知道需要多少个TaskManager适合,Flink 有动态启动TaskManager的机制吗。

51、 参考这个例子,Flink 零基础实战教程:如何计算实时热门商品 | Jark’s Blog, 窗口聚合的时候,用keywindow,用的是timeWindowAll,然后在aggregate的时候用aggregate(new CustomAggregateFunction(), new CustomWindowFunction()),打印结果后,发现窗口中一直使用的重复的数据,统计的结果也不变,去掉CustomWindowFunction()就正常了 ? 非常奇怪

52、 用户进入产品预定页面(端埋点上报),并填写了一些信息(端埋点上报),但半小时内并没有产生任何订单,然后给该类用户发送一个push。 1. 这种需求适合用flink去做吗?2. 如果适合,说下大概的思路

53、 业务场景是实时获取数据存redis,请问我要如何按天、按周、按月分别存入redis里?(比方说过了一天自动换一个位置存redis)

54、 有人 AggregatingState 的例子吗, 感觉官方的例子和 官网的不太一样?

55、 flink-jdbc这个jar有吗?怎么没找到啊?1.8.0的没找到,1.6.2的有

56、 现有个关于savepoint的问题,操作流程为,取消任务时设置保存点,更新任务,从保存点启动任务;现在遇到个问题,假设我中间某个算子重写,原先通过state编写,有用定时器,现在更改后,采用窗口,反正就是实现方式完全不一样;从保存点启动就会一直报错,重启,原先的保存点不能还原,此时就会有很多数据重复等各种问题,如何才能保证数据不丢失,不重复等,恢复到停止的时候,现在想到的是记下kafka的偏移量,再做处理,貌似也不是很好弄,有什么解决办法吗

57、 需要在flink计算app页面访问时长,消费Kafka计算后输出到Kafka。第一条log需要等待第二条log的时间戳计算访问时长。我想问的是,flink是分布式的,那么它能否保证执行的顺序性?后来的数据有没有可能先被执行?

58、 我公司想做实时大屏,现有技术是将业务所需指标实时用spark拉到redis里存着,然后再用一条spark streaming流计算简单乘除运算,指标包含了各月份的比较。请问我该如何用flink简化上述流程?

59、 flink on yarn 方式,这样理解不知道对不对,yarn-session这个脚本其实就是准备yarn环境的,执行run任务的时候,根据yarn-session初始化的yarnDescription 把 flink 任务的jobGraph提交到yarn上去执行

60、 同样的代码逻辑写在单独的main函数中就可以成功的消费kafka ,写在一个spring boot的程序中,接受外部请求,然后执行相同的逻辑就不能消费kafka。你遇到过吗?能给一些查问题的建议,或者在哪里打个断点,能看到为什么消费不到kafka的消息呢?

61、 请问下flink可以实现一个流中同时存在订单表和订单商品表的数据 两者是一对多的关系 能实现得到 以订单表为主 一个订单多个商品 这种需求嘛

62、 在用中间状态的时候,如果中间一些信息保存在state中,有没有必要在redis中再保存一份,来做第三方的存储。

63、 能否出一期flink state的文章。什么场景下用什么样的state?如,最简单的,实时累加update到state。

64、 flink的双流join博主有使用的经验吗?会有什么常见的问题吗

65、 窗口触发的条件问题

66、 flink 定时任务怎么做?有相关的demo么?

67、 流式处理过程中数据的一致性如何保证或者如何检测

68、 重启flink单机集群,还报job not found 异常。

69、 kafka的数据是用 org.apache.kafka.common.serialization.ByteArraySerialize序列化的,flink这边消费的时候怎么通过FlinkKafkaConsumer创建DataStream?

70、 现在公司有一个需求,一些用户的支付日志,通过sls收集,要把这些日志处理后,结果写入到MySQL,关键这些日志可能连着来好几条才是一个用户的,因为发起请求,响应等每个环节都有相应的日志,这几条日志综合处理才能得到最终的结果,请问博主有什么好的方法没有?

71、 flink 支持hadoop 主备么? hadoop主节点挂了 flink 会切换到hadoop 备用节点?

72、 请教大家: 实际 flink 开发中用 scala 多还是 java多些? 刚入手 flink 大数据 scala 需要深入学习么?

73、 我使用的是flink是1.7.2最近用了split的方式分流,但是底层的SplitStream上却标注为Deprecated,请问是官方不推荐使用分流的方式吗?

74、 KeyBy 的正确理解,和数据倾斜问题的解释

75、 用flink时,遇到个问题 checkpoint大概有2G左右, 有背压时,flink会重启有遇到过这个问题吗

76、 flink使用yarn-session方式部署,如何保证yarn-session的稳定性,如果yarn-session挂了,需要重新部署一个yarn-session,如何恢复之前yarn-session上的job呢,之前的checkpoint还能使用吗?

77、 我想请教一下关于sink的问题。我现在的需求是从Kafka消费Json数据,这个Json数据字段可能会增加,然后将拿到的json数据以parquet的格式存入hdfs。现在我可以拿到json数据的schema,但是在保存parquet文件的时候不知道怎么处理。一是flink没有专门的format parquet,二是对于可变字段的Json怎么处理成parquet比较合适?

78、 flink如何在较大的数据量中做去重计算。

79、 flink能在没有数据的时候也定时执行算子吗?

80、 使用rocksdb状态后端,自定义pojo怎么实现序列化和反序列化的,有相关demo么?

81、 check point 老是失败,是不是自定义的pojo问题?到本地可以,到hdfs就不行,网上也有很多类似的问题 都没有一个很好的解释和解决方案

82、 cep规则如图,当start事件进入时,时间00:00:15,而后进入end事件,时间00:00:40。我发现规则无法命中。请问within 是从start事件开始计时?还是跟window一样根据系统时间划分的?如果是后者,请问怎么配置才能从start开始计时?

83、 Flink聚合结果直接写Mysql的幂等性设计问题

84、 Flink job打开了checkpoint,用的rocksdb,通过观察hdfs上checkpoint目录,为啥算副本总量会暴增爆减

85、 Flink 提交任务的 jar包可以指定路径为 HDFS 上的吗

86、 在flink web Ui上提交的任务,设置的并行度为2,flink是stand alone部署的。两个任务都正常的运行了几天了,今天有个地方逻辑需要修改,于是将任务cancel掉(在命令行cancel也试了),结果taskmanger挂掉了一个节点。后来用其他任务试了,也同样会导致节点挂掉

87、 一个配置动态更新的问题折腾好久(配置用个静态的map变量存着,有个线程定时去数据库捞数据然后存在这个map里面更新一把),本地 idea 调试没问题,集群部署就一直报 空指针异常。下游的算子使用这个静态变量map去get key在集群模式下会出现这个空指针异常,估计就是拿不到 map

88、 批量写入MySQL,完成HBase批量写入

89、 用flink清洗数据,其中要访问redis,根据redis的结果来决定是否把数据传递到下流,这有可能实现吗?

90、 监控页面流处理的时候这个发送和接收字节为0。

91、 sink到MySQL,如果直接用idea的话可以运行,并且成功,大大的代码上面用的FlinkKafkaConsumer010,而我的Flink版本为1.7,kafka版本为2.12,所以当我用FlinkKafkaConsumer010就有问题,于是改为
FlinkKafkaConsumer就可以直接在idea完成sink到MySQL,但是为何当我把该程序打成Jar包,去运行的时候,就是报FlinkKafkaConsumer找不到呢

92、 SocketTextStreamWordCount中输入中文统计不出来,请问这个怎么解决,我猜测应该是需要修改一下代码,应该是这个例子默认统计英文

93、 Flink 应用程序本地 ide 里面运行的时候并行度是怎么算的?

等等等,还有很多,复制粘贴的我手累啊

另外里面还会及时分享 Flink 的一些最新的资料(包括数据、视频、PPT、优秀博客,持续更新,保证全网最全,因为我知道 Flink 目前的资料还不多)

关于自己对 Flink 学习的一些想法和建议

Flink 全网最全资料获取,持续更新,点击可以获取

再就是星球用户给我提的一点要求:不定期分享一些自己遇到的 Flink 项目的实战,生产项目遇到的问题,是如何解决的等经验之谈!

1、 如何查看自己的 Job 执行计划并获取执行计划图

2、 当实时告警遇到 Kafka 千万数据量堆积该咋办?

3、 如何在流数据中比两个数据的大小?多种解决方法

4、 kafka 系列文章

5、 Flink环境部署、应用配置及运行应用程序

6、 监控平台该有架构是长这样子的

7、 《大数据“重磅炸弹”——实时计算框架 Flink》专栏系列文章目录大纲

8、 《大数据“重磅炸弹”——实时计算框架 Flink》Chat 付费文章

9、 Apache Flink 是如何管理好内存的?

10、 Flink On K8s

当然,除了更新 Flink 相关的东西外,我还会更新一些大数据相关的东西,因为我个人之前不是大数据开发,所以现在也要狂补些知识!总之,希望进来的童鞋们一起共同进步!

1、 Java 核心知识点整理.pdf

2、 假如我是面试官,我会问你这些问题

3、 Kafka 系列文章和学习视频

4、 重新定义 Flink 第二期 pdf

5、 GitChat Flink 文章答疑记录

6、 Java 并发课程要掌握的知识点

7、 Lightweight Asynchronous Snapshots for Distributed Dataflows

8、 Apache Flink™- Stream and Batch Processing in a Single Engine

9、 Flink状态管理与容错机制

10、 Flink 流批一体的技术架构以及在阿里 的实践

11、 Flink Checkpoint-轻量级分布式快照

12、 Flink 流批一体的技术架构以及在阿里 的实践

13、 Stream Processing with Apache Flink pdf

Spring Boot 一个依赖搞定 session 共享,没有比这更简单的方案了! - 江南一点雨 - 博客园

$
0
0

有的人可能会觉得题目有点夸张,其实不夸张,题目没有使用任何修辞手法!认真读完本文,你就知道松哥说的是对的了!

在传统的单服务架构中,一般来说,只有一个服务器,那么不存在 Session 共享问题,但是在分布式/集群项目中,Session 共享则是一个必须面对的问题,先看一个简单的架构图:

在这样的架构中,会出现一些单服务中不存在的问题,例如客户端发起一个请求,这个请求到达 Nginx 上之后,被 Nginx 转发到 Tomcat A 上,然后在 Tomcat A 上往 session 中保存了一份数据,下次又来一个请求,这个请求被转发到 Tomcat B 上,此时再去 Session 中获取数据,发现没有之前的数据。对于这一类问题的解决,思路很简单,就是将各个服务之间需要共享的数据,保存到一个公共的地方(主流方案就是 Redis):

当所有 Tomcat 需要往 Session 中写数据时,都往 Redis 中写,当所有 Tomcat 需要读数据时,都从 Redis 中读。这样,不同的服务就可以使用相同的 Session 数据了。

这样的方案,可以由开发者手动实现,即手动往 Redis 中存储数据,手动从 Redis 中读取数据,相当于使用一些 Redis 客户端工具来实现这样的功能,毫无疑问,手动实现工作量还是蛮大的。

一个简化的方案就是使用 Spring Session 来实现这一功能,Spring Session 就是使用 Spring 中的代理过滤器,将所有的 Session 操作拦截下来,自动的将数据 同步到 Redis 中,或者自动的从 Redis 中读取数据。

对于开发者来说,所有关于 Session 同步的操作都是透明的,开发者使用 Spring Session,一旦配置完成后,具体的用法就像使用一个普通的 Session 一样。

1 实战

1.1 创建工程

首先 创建一个 Spring Boot 工程,引入 Web、Spring Session 以及 Redis:

创建成功之后,pom.xml 文件如下:

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-data-redis</artifactId></dependency></dependencies>

注意:

这里我使用的 Spring Boot 版本是 2.1.4 ,如果使用当前最新版 Spring Boot2.1.5 的话,除了上面这些依赖之外,需要额外添加 Spring Security 依赖(其他操作不受影响,仅仅只是多了一个依赖,当然也多了 Spring Security 的一些默认认证流程)。

1.2 配置 Redis

spring.redis.host=192.168.66.128
spring.redis.port=6379
spring.redis.password=123
spring.redis.database=0

这里的 Redis ,我虽然配置了四行,但是考虑到端口默认就是 6379 ,database 默认就是 0,所以真正要配置的,其实就是两行。

1.3 使用

配置完成后 ,就可以使用 Spring Session 了,其实就是使用普通的 HttpSession ,其他的 Session 同步到 Redis 等操作,框架已经自动帮你完成了:

@RestController
public class HelloController {
    @Value("${server.port}")
    Integer port;
    @GetMapping("/set")
    public String set(HttpSession session) {
        session.setAttribute("user", "javaboy");
        return String.valueOf(port);
    }
    @GetMapping("/get")
    public String get(HttpSession session) {
        return session.getAttribute("user") + ":" + port;
    }
}

考虑到一会 Spring Boot 将以集群的方式启动 ,为了获取每一个请求到底是哪一个 Spring Boot 提供的服务,需要在每次请求时返回当前服务的端口号,因此这里我注入了 server.port 。

接下来 ,项目打包:

打包之后,启动项目的两个实例:

java -jar sessionshare-0.0.1-SNAPSHOT.jar --server.port=8080
java -jar sessionshare-0.0.1-SNAPSHOT.jar --server.port=8081

然后先访问 localhost:8080/set8080这个服务的 Session中保存一个变量,访问完成后,数据就已经自动同步到 Redis中 了 :

然后,再调用 localhost:8081/get接口,就可以获取到 8080服务的 session中的数据:

此时关于 session 共享的配置就已经全部完成了,session 共享的效果我们已经看到了,但是每次访问都是我自己手动切换服务实例,因此,接下来我们来引入 Nginx ,实现服务实例自动切换。

1.4 引入 Nginx

很简单,进入 Nginx 的安装目录的 conf 目录下(默认是在 /usr/local/nginx/conf),编辑 nginx.conf 文件:

在这段配置中:

  1. upstream 表示配置上游服务器
  2. javaboy.org 表示服务器集群的名字,这个可以随意取名字
  3. upstream 里边配置的是一个个的单独服务
  4. weight 表示服务的权重,意味者将有多少比例的请求从 Nginx 上转发到该服务上
  5. location 中的 proxy_pass 表示请求转发的地址, /表示拦截到所有的请求,转发转发到刚刚配置好的服务集群中
  6. proxy_redirect 表示设置当发生重定向请求时,nginx 自动修正响应头数据(默认是 Tomcat 返回重定向,此时重定向的地址是 Tomcat 的地址,我们需要将之修改使之成为 Nginx 的地址)。

配置完成后,将本地的 Spring Boot 打包好的 jar 上传到 Linux ,然后在 Linux 上分别启动两个 Spring Boot 实例:

nohup java -jar sessionshare-0.0.1-SNAPSHOT.jar --server.port=8080 &
nohup java -jar sessionshare-0.0.1-SNAPSHOT.jar --server.port=8081 &

其中

  • nohup 表示当终端关闭时,Spring Boot 不要停止运行
  • & 表示让 Spring Boot 在后台启动

配置完成后,重启 Nginx:

/usr/local/nginx/sbin/nginx -s reload

Nginx 启动成功后,我们首先手动清除 Redis 上的数据,然后访问 192.168.66.128/set表示向 session中保存数据,这个请求首先会到达 Nginx上,再由 Nginx转发给某一个 Spring Boot实例:

如上,表示端口为 8081Spring Boot处理了这个 /set请求,再访问 /get请求:

可以看到, /get请求是被端口为 8080 的服务所处理的。

2 总结

本文主要向大家介绍了 Spring Session 的使用,另外也涉及到一些 Nginx 的使用 ,虽然本文较长,但是实际上 Spring Session 的配置没啥。

我们写了一些代码,也做了一些配置,但是全都和 Spring Session 无关,配置是配置 Redis,代码就是普通的 HttpSession,和 Spring Session 没有任何关系!

唯一和 Spring Session 相关的,可能就是我在一开始引入了 Spring Session 的依赖吧!

如果大家没有在 SSM 架构中用过 Spring Session ,可能不太好理解我们在 Spring Boot 中使用 Spring Session 有多么方便,因为在 SSM 架构中,Spring Session 的使用要配置三个地方 ,一个是 web.xml 配置代理过滤器,然后在 Spring 容器中配置 Redis,最后再配置 Spring Session,步骤还是有些繁琐的,而 Spring Boot 中直接帮我们省去了这些繁琐的步骤!不用再去配置 Spring Session。

好了 ,本文就说到这里,本文相关案例我已经上传到 GitHub ,大家可以自行下载: https://github.com/lenve/javaboy-code-samples


在Android手机上使用腾讯的ncnn实现图像分类 - 夜雨飘零 - CSDN博客

$
0
0
前言
在之前笔者有介绍过《在Android设备上使用PaddleMobile实现图像分类》,使用的框架是百度开源的PaddleMobile。在本章中,笔者将会介绍使用腾讯的开源手机深度学习框架ncnn来实现在Android手机实现图像分类,这个框架开源时间比较长,相对稳定很多。

ncnn的GitHub地址:https://github.com/Tencent/ncnn

使用Ubuntu编译ncnn库
1、首先要下载和解压NDK。

wget https://dl.google.com/android/repository/android-ndk-r17b-linux-x86_64.zip
unzip android-ndk-r17b-linux-x86_64.zip
1
2
2、设置NDK环境变量,目录是NDK的解压目录。

export NDK_ROOT="/home/test/paddlepaddle/android-ndk-r17b"
1
设置好之后,可以使用以下的命令查看配置情况。

root@test:/home/test/paddlepaddle# echo $NDK_ROOT
/home/test/paddlepaddle/android-ndk-r17b
1
2
3、安装cmake,需要安装较高版本的,笔者的cmake版本是3.11.2。

下载cmake源码

wget https://cmake.org/files/v3.11/cmake-3.11.2.tar.gz
1
解压cmake源码

tar -zxvf cmake-3.11.2.tar.gz
1
进入到cmake源码根目录,并执行bootstrap。

cd cmake-3.11.2
./bootstrap
1
2
最后执行以下两条命令开始安装cmake。

make
make install
1
2
安装完成之后,可以使用cmake --version是否安装成功。

root@test:/home/test/paddlepaddle# cmake --version
cmake version 3.11.2

CMake suite maintained and supported by Kitware (kitware.com/cmake).
1
2
3
4
4、克隆ncnn源码。

git clone https://github.com/Tencent/ncnn.git
1
5、编译源码。

# 进入到ncnn源码根目录下
cd ncnn
# 创建一个新的文件夹
mkdir -p build-android-armv7
# 进入到该文件夹中
cd build-android-armv7
# 执行编译命令
cmake -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake \
    -DANDROID_ABI="armeabi-v7a" -DANDROID_ARM_NEON=ON \
    -DANDROID_PLATFORM=android-14 ..
# 这里笔者使用4个行程并行编译
make -j4
make install
1
2
3
4
5
6
7
8
9
10
11
12
13
1
2
3
4
5
6
7
8
9
10
11
12
13
6、编译完成,会在build-android-armv7目录下生成一个install目录,我们编译得到的文件都在该文件夹下:

include 调用ncnn所需的头文件,该文件夹会存放在Android项目的src/main/cpp目录下;
lib 编译得到的ncnn库libncnn.a,之后会存放在Android项目的src/main/jniLibs/armeabi-v7a/libncnn.a
转换预测模型
1、克隆Caffe源码。

git clone https://github.com/BVLC/caffe.git
1
2、编译Caffe源码。

# 切换到Caffe目录
cd caffe
# 在当前目录执行cmake
cmake .
# 使用4个线程编译
make -j4
make install
1
2
3
4
5
6
7
1
2
3
4
5
6
7
3、升级Caffe模型。

# 把需要转换的模型复制到caffe/tools,并切入到该目录
cd tools
# 升级Caffe模型
./upgrade_net_proto_text mobilenet_v2_deploy.prototxt mobilenet_v2_deploy_new.prototxt
./upgrade_net_proto_binary mobilenet_v2.caffemodel mobilenet_v2_new.caffemodel
1
2
3
4
5
1
2
3
4
5
4、检查模型配置文件,因为只能一张一张图片预测,所以输入要设置为dim: 1。

name: "MOBILENET_V2"
layer {
  name: "input"
  type: "Input"
  top: "data"
  input_param {
    shape {
      dim: 1
      dim: 3
      dim: 224
      dim: 224
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
5、切换到ncnn的根目录,就是我们上一部分克隆的ncnn源码。

cd ncnn/
1
6、在根目录下编译ncnn源码。

mkdir -p build
cd build
cmake ..
make -j4
make install
1
2
3
4
5
7、把新的Caffe模型转换成NCNN模型。

# 经过上一步,会生产一个tools,我们进入到以下目录
cd tools/caffe/
# 把已经升级的网络定义文件和权重文件复制到当目录,并执行以下命令
./caffe2ncnn mobilenet_v2_deploy_new.prototxt mobilenet_v2_new.caffemodel mobilenet_v2.param mobilenet_v2.bin
1
2
3
4
1
2
3
4
8、对象模型参数进行加密,这样就算别人反编译我们的apk也用不了我们的模型文件。把上一步获得的mobilenet_v2.param、mobilenet_v2.bin复制到该目录的上一个目录,也就是tools目录。

# 切换到上一个目录
cd ../
# 执行命令之后会生成mobilenet_v2.param、mobilenet_v2.id.h、mobilenet_v2.mem.h
./ncnn2mem mobilenet_v2.param mobilenet_v2.bin mobilenet_v2.id.h mobilenet_v2.mem.h
1
2
3
4
1
2
3
4
经过上面的步骤,得到的文件中,以下文件时需要的:

mobilenet_v2.param.bin 网络的模型参数;
mobilenet_v2.bin 网络的权重;
mobilenet_v2.id.h 在预测图片的时候使用到。
开发Android项目
我们在Android Studio上创建一个NCNN1的项目,别忘了选择C++支持。

其他的可以直接默认就可以了,在这里要注意选择C++11支持。


在main目录下创建assets目录,并复制以下目录到该目录:

mobilenet_v2.param.bin 上一步获取网络的模型参数;

mobilenet_v2.bin 上一步获取网络的权重;

synset.txt label对应的名称,下载地址:https://github.com/shicai/MobileNet-Caffe/blob/master/synset.txt。

在cpp目录下复制在使用Ubuntu编译NCNN库部分编译得到的include文件夹,包括里面的C++头文件。

把mobilenet_v2.id.h复制到cpp目录下。

在main目录下创建jniLibs/armeabi-v7a/目录,并把使用Ubuntu编译NCNN库部分编译得到的libncnn.a复制到该目录。

在cpp目录下创建一个C++文件,并编写以下代码,这段代码是用于加载模型和预测图片的:

#include <android/bitmap.h>
#include <android/log.h>
#include <jni.h>
#include <string>
#include <vector>
// ncnn
#include "include/net.h"
#include "mobilenet_v2.id.h"
#include <sys/time.h>
#include <unistd.h>

static ncnn::UnlockedPoolAllocator g_blob_pool_allocator;
static ncnn::PoolAllocator g_workspace_pool_allocator;

static ncnn::Mat ncnn_param;
static ncnn::Mat ncnn_bin;
static ncnn::Net ncnn_net;


extern "C" {

// public native boolean Init(byte[] param, byte[] bin, byte[] words);
JNIEXPORT jboolean JNICALL
Java_com_example_ncnn1_NcnnJni_Init(JNIEnv *env, jobject thiz, jbyteArray param, jbyteArray bin) {
    // init param
    {
        int len = env->GetArrayLength(param);
        ncnn_param.create(len, (size_t) 1u);
        env->GetByteArrayRegion(param, 0, len, (jbyte *) ncnn_param);
        int ret = ncnn_net.load_param((const unsigned char *) ncnn_param);
        __android_log_print(ANDROID_LOG_DEBUG, "NcnnJni", "load_param %d %d", ret, len);
    }

    // init bin
    {
        int len = env->GetArrayLength(bin);
        ncnn_bin.create(len, (size_t) 1u);
        env->GetByteArrayRegion(bin, 0, len, (jbyte *) ncnn_bin);
        int ret = ncnn_net.load_model((const unsigned char *) ncnn_bin);
        __android_log_print(ANDROID_LOG_DEBUG, "NcnnJni", "load_model %d %d", ret, len);
    }

    ncnn::Option opt;
    opt.lightmode = true;
    opt.num_threads = 4;
    opt.blob_allocator = &g_blob_pool_allocator;
    opt.workspace_allocator = &g_workspace_pool_allocator;

    ncnn::set_default_option(opt);

    return JNI_TRUE;
}

// public native String Detect(Bitmap bitmap);
JNIEXPORT jfloatArray JNICALL Java_com_example_ncnn1_NcnnJni_Detect(JNIEnv* env, jobject thiz, jobject bitmap)
{
    // ncnn from bitmap
    ncnn::Mat in;
    {
        AndroidBitmapInfo info;
        AndroidBitmap_getInfo(env, bitmap, &info);
        int width = info.width;
        int height = info.height;
        if (info.format != ANDROID_BITMAP_FORMAT_RGBA_8888)
            return NULL;

        void* indata;
        AndroidBitmap_lockPixels(env, bitmap, &indata);
        // 把像素转换成data,并指定通道顺序
        in = ncnn::Mat::from_pixels((const unsigned char*)indata, ncnn::Mat::PIXEL_RGBA2BGR, width, height);

        AndroidBitmap_unlockPixels(env, bitmap);
    }

    // ncnn_net
    std::vector<float> cls_scores;
    {
        // 减去均值和乘上比例
        const float mean_vals[3] = {103.94f, 116.78f, 123.68f};
        const float scale[3] = {0.017f, 0.017f, 0.017f};

        in.substract_mean_normalize(mean_vals, scale);

        ncnn::Extractor ex = ncnn_net.create_extractor();
        // 如果时不加密是使用ex.input("data", in);
        ex.input(mobilenet_v2_param_id::BLOB_data, in);

        ncnn::Mat out;
        // 如果时不加密是使用ex.extract("prob", out);
        ex.extract(mobilenet_v2_param_id::BLOB_prob, out);

        int output_size = out.w;
        jfloat *output[output_size];
        for (int j = 0; j < out.w; j++) {
            output[j] = &out[j];
        }

        jfloatArray jOutputData = env->NewFloatArray(output_size);
        if (jOutputData == nullptr) return nullptr;
        env->SetFloatArrayRegion(jOutputData, 0, output_size,
                                 reinterpret_cast<const jfloat *>(*output));  // copy

        return jOutputData;
    }
}
}
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
97
98
99
100
101
102
103
104
105
106
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
97
98
99
100
101
102
103
104
105
106
在项目包com.example.ncnn1下,修改MainActivity.java中的代码,修改如下:
package com.example.ncnn1;

import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import com.bumptech.glide.Glide;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.request.RequestOptions;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;


public class MainActivity extends Activity {
    private static final String TAG = MainActivity.class.getName();
    private static final int USE_PHOTO = 1001;
    private String camera_image_path;
    private ImageView show_image;
    private TextView result_text;
    private boolean load_result = false;
    private int[] ddims = {1, 3, 224, 224};
    private int model_index = 1;
    private List<String> resultLabel = new ArrayList<>();
    private NcnnJni squeezencnn = new NcnnJni();

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        try {
            initSqueezeNcnn();
        } catch (IOException e) {
            Log.e("MainActivity", "initSqueezeNcnn error");
        }

        init_view();
        readCacheLabelFromLocalFile();
    }

    private void initSqueezeNcnn() throws IOException {
        byte[] param = null;
        byte[] bin = null;

        {
            InputStream assetsInputStream = getAssets().open("mobilenet_v2.param.bin");
            int available = assetsInputStream.available();
            param = new byte[available];
            int byteCode = assetsInputStream.read(param);
            assetsInputStream.close();
        }
        {
            InputStream assetsInputStream = getAssets().open("mobilenet_v2.bin");
            int available = assetsInputStream.available();
            bin = new byte[available];
            int byteCode = assetsInputStream.read(bin);
            assetsInputStream.close();
        }

        load_result = squeezencnn.Init(param, bin);
        Log.d("load model", "result:" + load_result);
    }

    // initialize view
    private void init_view() {
        request_permissions();
        show_image = (ImageView) findViewById(R.id.show_image);
        result_text = (TextView) findViewById(R.id.result_text);
        result_text.setMovementMethod(ScrollingMovementMethod.getInstance());
        Button use_photo = (Button) findViewById(R.id.use_photo);

        // use photo click
        use_photo.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (!load_result) {
                    Toast.makeText(MainActivity.this, "never load model", Toast.LENGTH_SHORT).show();
                    return;
                }
                PhotoUtil.use_photo(MainActivity.this, USE_PHOTO);
            }
        });
    }

// load label's name
    private void readCacheLabelFromLocalFile() {
        try {
            AssetManager assetManager = getApplicationContext().getAssets();
            BufferedReader reader = new BufferedReader(new InputStreamReader(assetManager.open("synset.txt")));
            String readLine = null;
            while ((readLine = reader.readLine()) != null) {
                resultLabel.add(readLine);
            }
            reader.close();
        } catch (Exception e) {
            Log.e("labelCache", "error " + e);
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        String image_path;
        RequestOptions options = new RequestOptions().skipMemoryCache(true).diskCacheStrategy(DiskCacheStrategy.NONE);
        if (resultCode == Activity.RESULT_OK) {
            switch (requestCode) {
                case USE_PHOTO:
                    if (data == null) {
                        Log.w(TAG, "user photo data is null");
                        return;
                    }
                    Uri image_uri = data.getData();
                    Glide.with(MainActivity.this).load(image_uri).apply(options).into(show_image);
                    // get image path from uri
                    image_path = PhotoUtil.get_path_from_URI(MainActivity.this, image_uri);
                    // predict image
                    predict_image(image_path);
                    break;
            }
        }
    }

    //  predict image
    private void predict_image(String image_path) {
        // picture to float array
        Bitmap bmp = PhotoUtil.getScaleBitmap(image_path);
        Bitmap rgba = bmp.copy(Bitmap.Config.ARGB_8888, true);

        // resize to 227x227
        Bitmap input_bmp = Bitmap.createScaledBitmap(rgba, ddims[2], ddims[3], false);
        try {
            // Data format conversion takes too long
            // Log.d("inputData", Arrays.toString(inputData));
            long start = System.currentTimeMillis();
            // get predict result
            float[] result = squeezencnn.Detect(input_bmp);
            long end = System.currentTimeMillis();
            Log.d(TAG, "origin predict result:" + Arrays.toString(result));
            long time = end - start;
            Log.d("result length", String.valueOf(result.length));
            // show predict result and time
            int r = get_max_result(result);
            String show_text = "result:" + r + "\nname:" + resultLabel.get(r) + "\nprobability:" + result[r] + "\ntime:" + time + "ms";
            result_text.setText(show_text);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // get max probability label
    private int get_max_result(float[] result) {
        float probability = result[0];
        int r = 0;
        for (int i = 0; i < result.length; i++) {
            if (probability < result[i]) {
                probability = result[i];
                r = i;
            }
        }
        return r;
    }

    // request permissions
    private void request_permissions() {

        List<String> permissionList = new ArrayList<>();
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
            permissionList.add(Manifest.permission.CAMERA);
        }

        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            permissionList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
        }

        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            permissionList.add(Manifest.permission.READ_EXTERNAL_STORAGE);
        }

        // if list is not empty will request permissions
        if (!permissionList.isEmpty()) {
            ActivityCompat.requestPermissions(this, permissionList.toArray(new String[permissionList.size()]), 1);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode) {
            case 1:
                if (grantResults.length > 0) {
                    for (int i = 0; i < grantResults.length; i++) {

                        int grantResult = grantResults[i];
                        if (grantResult == PackageManager.PERMISSION_DENIED) {
                            String s = permissions[i];
                            Toast.makeText(this, s + " permission was denied", Toast.LENGTH_SHORT).show();
                        }
                    }
                }
                break;
        }
    }
}
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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
同样在项目的包com.example.ncnn1下,创建一个NcnnJni.java类,用于提供JNI接口,代码如下:
package com.example.ncnn1;

import android.graphics.Bitmap;

public class NcnnJni
{
    public native boolean Init(byte[] param, byte[] bin);

    public native float[] Detect(Bitmap bitmap);

    static {
        System.loadLibrary("ncnn_jni");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
1
2
3
4
5
6
7
8
9
10
11
12
13
14
还是在项目的包com.example.ncnn1下,创建一个PhotoUtil.java类,这个是图片的工具类,代码如下:
package com.example.ncnn1;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.provider.MediaStore;

import java.nio.FloatBuffer;

public class PhotoUtil {
    // get picture in photo
    public static void use_photo(Activity activity, int requestCode) {
        Intent intent = new Intent(Intent.ACTION_PICK);
        intent.setType("image/*");
        activity.startActivityForResult(intent, requestCode);
    }

    // get photo from Uri
    public static String get_path_from_URI(Context context, Uri uri) {
        String result;
        Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
        if (cursor == null) {
            result = uri.getPath();
        } else {
            cursor.moveToFirst();
            int idx = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
            result = cursor.getString(idx);
            cursor.close();
        }
        return result;
    }

    // compress picture
    public static Bitmap getScaleBitmap(String filePath) {
        BitmapFactory.Options opt = new BitmapFactory.Options();
        opt.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(filePath, opt);

        int bmpWidth = opt.outWidth;
        int bmpHeight = opt.outHeight;

        int maxSize = 500;

        // compress picture with inSampleSize
        opt.inSampleSize = 1;
        while (true) {
            if (bmpWidth / opt.inSampleSize < maxSize || bmpHeight / opt.inSampleSize < maxSize) {
                break;
            }
            opt.inSampleSize *= 2;
        }
        opt.inJustDecodeBounds = false;
        return BitmapFactory.decodeFile(filePath, opt);
    }
}
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
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
修改启动页面的布局,修改如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <LinearLayout
        android:id="@+id/btn_ll"
        android:layout_alignParentBottom="true"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:id="@+id/use_photo"
            android:layout_weight="1"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:text="相册" />

    </LinearLayout>

    <TextView
        android:layout_above="@id/btn_ll"
        android:id="@+id/result_text"
        android:textSize="16sp"
        android:layout_width="match_parent"
        android:hint="预测结果会在这里显示"
        android:layout_height="100dp" />

    <ImageView
        android:layout_alignParentTop="true"
        android:layout_above="@id/result_text"
        android:id="@+id/show_image"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</RelativeLayout>
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
修改APP目录下的CMakeLists.txt文件,修改如下:
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.4.1)

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

set(ncnn_lib ${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi-v7a/libncnn.a)
add_library (ncnn_lib STATIC IMPORTED)
set_target_properties(ncnn_lib PROPERTIES IMPORTED_LOCATION ${ncnn_lib})


add_library( # Sets the name of the library.
             ncnn_jni

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             src/main/cpp/ncnn_jni.cpp )

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

find_library( # Sets the name of the path variable.
              log-lib

              # Specifies the name of the NDK library that
              # you want CMake to locate.
              log )

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
                       ncnn_jni
                       ncnn_lib
                       jnigraphics

                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib} )
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
修改APP目录下的build.gradle文件,修改如下:
apply plugin: 'com.android.application'

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.example.ncnn1"
        minSdkVersion 21
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        externalNativeBuild {
            cmake {
                cppFlags "-std=c++11 -fopenmp"
                abiFilters "armeabi-v7a"
            }
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }

    sourceSets {
        main {
            jniLibs.srcDirs = ["src/main/jniLibs"]
            jni.srcDirs = ['src/cpp']
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:28.0.0-rc02'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    testImplementation 'junit:junit:4.12'
    implementation 'com.github.bumptech.glide:glide:4.3.1'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
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
最后别忘了在配置文件中添加权限。
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
1
2
最后的效果图如下:


代码传送门: 上面已经几乎包括所有的代码了,为了读者方便直接使用,可以在这里下载项目源代码。

参考资料
https://github.com/BVLC/caffe
https://github.com/Tencent/ncnn/wiki/how-to-use-ncnn-with-alexnet
https://github.com/Tencent/ncnn/wiki/how-to-build
https://github.com/Tencent/ncnn/tree/master/examples/squeezencnn
--------------------- 
作者:夜雨飘零1 
来源:CSDN 
原文:https://blog.csdn.net/qq_33200967/article/details/82421089 
版权声明:本文为博主原创文章,转载请附上博文链接!

NDK开发第一课:环境配置与第一个JNI程序 - 阿飞的博客 - CSDN博客

$
0
0

一、JNI 与 NDK

1. JNI 是什么

    JNI 是 Java Native Interface 的缩写,即 Java 的本地接口。
    目的是使得 Java 与本地其他语言(如 C/C++)进行交互。
    JNI 是属于 Java 的,与 Android 无直接关系。

2. NDK 是什么

    NDK 是 Native Development Kit 的缩写,是 Android 的工具开发包。
    作用是快速开发 C/C++ 的动态库,并自动将动态库与应用一起打包到 apk。
    NDK 是属于 Android 的,与 Java 无直接关系。

3. JNI 与 NDK 的关系

    JNI 是实现的目的,NDK 是 Android 中实现 JNI 的手段。

 

二、NDK 的下载

1. 官网下载

    地址为: https://developer.android.com/ndk/downloads/
    下载后直接解压即可,注意路径名不能带有中文或者空格。

2. Android Studio 中下载

    点开 Android Studio 的 SDK Manager 可以看到如下截图,这里我们选择下载红框中勾选的3个工具。

  • CMake:一个跨平台的编译构建工具,替代 Android.mk
  • LLDB:一个高效的 C/C++ 的调试工具
  • NDK:即我们需要下载的工具,会生成到 SDK 根目录下的 ndk-bundle 目录下

 

三、我的第一个 JNI 程序

1. 新建工程,并选择包含 C++ support,如下图所示

    在接下来向导中的 Customize C++ Support 部分,您可以使用下列选项自定义项目:

  • C++ Standard:使用下拉列表选择您希望使用哪种 C++ 标准。选择 Toolchain Default 会使用默认的 CMake 设置。
  • Exceptions Support:如果您希望启用对 C++ 异常处理的支持,请选中此复选框。如果启用此复选框,Android Studio 会将 -fexceptions标志添加到模块级 build.gradle 文件的 cppFlags 中,Gradle 会将其传递到 CMake。
  • Runtime Type Information Support:如果您希望支持 RTTI,请选中此复选框。如果启用此复选框,Android Studio 会将 -frtti 标志添加到模块级 build.gradle 文件的 cppFlags 中,Gradle 会将其传递到 CMake。

2. Android Studio 会自动帮你生成一个可执行的 Hello World 程序,我们简单看一下这个工程

    相比平常的 Android 工程,这里主要是多了上面截图中红框处的文件。其中 cpp 目录就是我们的 C/C++ 代码、预编译库的默认路径了,而 CMakeList.txt 就是编译的脚本文件了。

    接下来我们详细分析一下每一块的原理。

3. MainActivity

public class MainActivity extends AppCompatActivity {
    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // Example of a call to a native method
        TextView tv = (TextView) findViewById(R.id.sample_text);
        tv.setText(stringFromJNI());
    }
    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();
}

    我们知道,Java 调用本地方法,是使用 native 关键字。而本例中,本地方法的实现是在一个叫做 “native-lib” 的动态库里(动态库的名称是在 CMakeList.txt 中指定的),要想使用这个动态库,就必须先加载这个库,即 System.loadLibrary(native-lib)。这些都是 Java 的语法定义。

4. native-lib.cpp

#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring
JNICALL
Java_com_afei_jnidemo_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

    上面提到的 public native String stringFromJNI()方法的实现就是在这里,那怎么知道 Java 中的某个 native 方法是对应的 cpp 中的哪个方法呢?这就和 JNI 的注册有关了,本例中使用的是静态注册,即 “Java 包名类名_方法名” 的形式,其中包名也是用下划线替代点号。

    后续章节将详细介绍 JNI 方法的注册,以及参数的转换等。

5. 整个调用流程就是:

  1. Gradle 调用您的外部构建脚本 CMakeLists.txt。
  2. CMake 按照构建脚本中的命令将 C++ 源文件 native-lib.cpp 编译到共享的对象库中,并命名为 libnative-lib.so,Gradle 随后会将其打包到 APK 中。
  3. 运行时,应用的 MainActivity 会使用 System.loadLibrary()加载原生库。现在,应用可以使用库的原生函数 stringFromJNI()
  4. MainActivity.onCreate() 调用 stringFromJNI(),这将返回“Hello from C++”并使用这些文字更新 TextView。

 

四、 手动配置  gradle 支持 NDK 开发

要手动配置 Gradle 以关联到您的原生库,需要将 externalNativeBuild {} 块添加到模块级 build.gradle 文件中,并使用 cmake {} 或 ndkBuild {} 对其进行配置:

android {
  ...
  defaultConfig {...}
  buildTypes {...}
  // Encapsulates your external native build configurations.
  externalNativeBuild {
    // Encapsulates your CMake build configurations.
    cmake {
      // Provides a relative path to your CMake build script.
      path "CMakeLists.txt"
    }
  }
}

注意:

    如果您想要将 Gradle 关联到现有 ndk-build 项目,请使用 ndkBuild {} 块而不是 cmake {},并提供 Android.mk 文件的相对路径。如果 Application.mk 文件与您的 Android.mk 文件位于相同目录下,Gradle 也会包含此文件。

 

 

五、 CMakeLists.txt 简单分析

1. 首先我们看一下自动生成的 CMakeLists.txt 都有些什么内容

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.4.1)
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
add_library( # Sets the name of the library.
             native-lib
             # Sets the library as a shared library.
             SHARED
             # Provides a relative path to your source file(s).
             src/main/cpp/native-lib.cpp )
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
find_library( # Sets the name of the path variable.
              log-lib
              # Specifies the name of the NDK library that
              # you want CMake to locate.
              log )
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( # Specifies the target library.
                       native-lib
                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib} )

2. cmake_minimum_required

    指定 Cmake 需要的最低版本

3. add_library

    创建和命名该库,第一个参数是库的名字,例如取名为 native-lib,将会生成一个命名为 libnative-lib.so 的库。
    第二个参数是指定库的类型,一般为 SHARED,即动态库(以 .so 为后缀),还有一种是静态库 STATIC,即静态库(以 .a 为后缀)。
    第三个参数是指定该库使用的源文件路径。
    使用多个 add_library() 命令,您可以为 CMake 定义要从其他源文件构建的更多库。

4. find_library

    找到一个 NDK 的库,并且将这个库的路径存储在一个变量中。例如上例中是找到 NDK 中的 log 库(Android 特定的日志支持库),并将其路径存储在 “log-lib” 变量中,在后面你就可以通过 “${log-lib}” 命令取出变量中的值了。

5. target_link_libraries

    关联库。将指定的库关联起来 。

6. 注释应该基本也能看得懂,关于 CMake 的更多语法,将在后续章节介绍。

博客链接: CMakeLists.txt 语法介绍与实例演练

 

其它:

​NDK 学习系列: Android NDK 从入门到精通(汇总篇)

Gradle 修改 Maven 仓库地址(阿里镜像) - 芊鸟咔咔 - 博客园

$
0
0
buildscript {
    repositories {
        maven { url'http://maven.aliyun.com/nexus/content/groups/public/'}
        jcenter()
    }
    dependencies {
        classpath'com.android.tools.build:gradle:2.2.3'//NOTE: Do not place your application dependencies here; they belong//in the individual module build.gradle files}
}

allprojects {
    repositories {
        maven { url'http://maven.aliyun.com/nexus/content/groups/public/'}
        jcenter()
    }
}      

转:https://my.oschina.net/ranvane/blog/820262

步骤一:进入GRADLE_USER_HOME
一般情况下是C:\Users\Administrator.gradle\这个目录,如果你还没有配置过,这个目录是不会变的,我们讲windows下,linux用户大同小异。
C:\Users\Administrator.gradle\

步骤二:新建一个init.gradle文件
该文件是每一个Gradle 项目执行之前的脚本文件

步骤三:文件中填入如下内容
allprojects {
    repositories {
        mavenLocal()
		maven { name "Alibaba" ; url "https://maven.aliyun.com/repository/public" }
		maven { name "Bstek" ; url "http://nexus.bsdn.org/content/groups/public/" }
    }
}
另外一个连插件都帮你配置好了

allprojects {
    repositories {
        mavenLocal()
		maven { name "Alibaba" ; url "https://maven.aliyun.com/repository/public" }
		maven { name "Bstek" ; url "http://nexus.bsdn.org/content/groups/public/" }
    }

	buildscript { 
		repositories { 
			maven { name "Alibaba" ; url 'https://maven.aliyun.com/repository/public' }
			maven { name "Bstek" ; url 'http://nexus.bsdn.org/content/groups/public/' }
			maven { name "M2" ; url 'https://plugins.gradle.org/m2/' }
		}
	}
}
Viewing all 532 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>