zoukankan      html  css  js  c++  java
  • 诗情画意

    v2-a3575227ada68693bc7eef27cedccb97_1200x500

    Release 实现异步更新网络图片 · bajdcc/GameFramework · GitHub

    写在前面

    计划着将一些好用的东西整合进框架中,目前用了libevent和libcurl,仅当尝鲜。话说libcurl的使用其实很简单,跟php的curl扩展差不多。libevent是初次使用,很多坑尚未发现。

    简单介绍下封面界面的构成:必应背景、一言API、文字、二维码。其中新增的是前二个:必应背景和一言文字。

    下面是主要内容:

    1. libevent和libcurl的编译与使用
    2. 如何实现异步刷新,且涉及网络请求

    使用libevent

    项目中需要用到libevent,实现异步通知功能。libevent支持网络/文件IO、定时器、信号。在这里,我们只需要用到其中的定时器功能。

    vs2015中编译静态库libevent

    1. 下载libevent源码libevent-2.0.22-stable.tar.gz
    2. 下载Makefile.nmake到libevent目录,默认是release版本;若需要编译debug版本,需要修改其中的第26行`CFLAGS=$(CFLAGS) /Ox /W3 /wd4996 /nologo`为`CFLAGS=$(CFLAGS) /Od /Zi /W3 /wd4996 /nologo`
    3. 开始菜单=>vs2015开发人员命令提示,cd切换到libevent目录,执行命令nmake /F makefile.nmake;如果要清空上一次编译的结果,执行nmake /F makefile.nmake clean
    4. 复制目录下的libevent.lib、libevent_core.lib、libevent_extras.lib到项目中

    libevent的简单使用

    void msg_timer(evutil_socket_t fd, short event, void *arg)
    {
    /*do something...*/
    }
    
    struct event_base *evbase = event_base_new();//初始化event_base,一线程一个
    struct event msgtimer;
    struct timeval tv;
    evtimer_assign(&msgtimer, evbase, &msg_timer, NULL);//初始化事件
    evutil_timerclear(&tv);
    tv.tv_sec = 0;
    tv.tv_usec = 10;//10毫秒后触发事件
    evtimer_add(&msgtimer, &tv);//将事件加入到队列中
    event_base_dispatch(evbase);//开始处理队列
    event_base_free(evbase);//释放
    

    上述例子简单介绍了如何用libevent设置定时事件。

    使用libcurl

    curl和wget是做爬虫的常用工具,它们有很多功能。这里,项目中使用libcurl来下载web上的json。

    vs2015中编译静态库libcurl

    1. 下载libcurl源码curl-7.53.1.tar.gz
    2. 打开vs2015工具命令提示,进入到curlwinbuild目录,执行nmake -F Makefile.vc mode=static VC=14 DEBUG=no MACHINE=x86,这里编译的是32位适用于VS2015的静态库release版本;如果需要调试,令DEBUG=yes
    3. 编译好的文件在curluildslibcurl-vc14-x86-debug-static-ipv6-sspi-winssl下;我们需要lib下的静态库,以及include中的头文件

    libcurl的简单使用

    static size_t http_get_process(void *data, size_t size, size_t nmemb, std::string &content)
    {
        auto sizes = size * nmemb;
        content += std::string((char*)data, sizes);
        return sizes;
    }
    
    curl_global_init(CURL_GLOBAL_ALL);
    CURL *curl = curl_easy_init();//初始化
    std::string text;//保存的内容
    curl_easy_setopt(curl, CURLOPT_URL, "http://www.baidu.com");//url
    curl_easy_setopt(curl, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36");
    curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 2L);
    curl_easy_setopt(curl, CURLOPT_TIMEOUT, 2L);//超时,单位秒
    curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);//自动301、302跳转
    curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "");//留空表示自动解压
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, TRUE);
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, TRUE);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &text);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &http_get_process);
    CURLcode res = curl_easy_perform(curl);
    if (res == CURLE_OK)
    {
        /*text中的内容就是url返回的内容,当然这里面有编码问题,暂且不谈*/
    }
    curl_easy_cleanup(curl);
    curl_global_cleanup();
    

    那么后面json的下载就要用到curl了。

    异步模型

    Win32事件驱动模型

    经典的win32程序是基于消息的,程序不断处理操作系统给的消息,总体是单线程的。

    //消息处理函数
    LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    
    //主循环
    while (GetMessage (&msg, NULL, NULL, NULL))
    {
         TranslateMessage (&msg) ; //翻译消息
         DispatchMessage (&msg) ; //分派消息
    }
    

    大道至简,一个循环解决问题。一般而言,这么写没问题。但是,如果涉及耗时的操作如网络IO……程序就假死了!

    比如想做一个下载器,做一个带界面的爬虫,如果只是单线程处理,那么在下载过程中,win32窗口是无响应的,因为它卡在网络IO上了。为了避免这种情况,只能使用多线程。

    有了多线程,也就有了竞争与冲突风险,以及各种线程同步问题,解决这些问题的关键是设计一个好用的、简单的模型。最终的思路必然是简单的,否则出了问题谁也找不出。

    异步模型的思考

    一般的思路:耗时的操作交给工作线程做,主线程处理窗口的消息。这里用libevent解决。

    libevent其实也相当于一个死循环,在这个死循环中,它可以:

    1. 查看当前是否有win32窗口的消息
    2. 查看定时器事件是否到期了
    3. 查看网络/文件IO是否完成

    注意,它一直在“查看”,也就是说,看看没消息,它就继续干别的事,不会卡死在一个地方。

    那么结合win32和libevent,我们有:

    1. 每隔10毫秒查看win32消息,若有,立马处理一个消息
    2. 不断监听定时器消息,若有,立马执行

    这样保证:win32消息的处理和定时器消息的处理处于同一线程中。这个“处于同一线程中”,好处可大了,因为可以避免线程同步等一系列问题。我们在win32主线程中用lua处理各种消息,而lua可以设置定时器;同样地,我们用lua处理定时器消息。换句话说,自始至终,lua都跑在主线程中,跟其他线程无关。

    异步下载网络资源

    实现了整个框架最为核心的异步事件模型,那么如何解决网络资源下载问题呢?比如说,我想点击按钮,就下载一个json,通过分析json,下载相应的背景图片,并将这个图片作为程序的背景。

    目前,libevent的设置定时器功能是可以跨线程调用的,要注意的是只有这里存在跨线程调用。

    那么这一流程如下:

    1. 按下按钮
    2. lua处理单击事件,设置定时器timer1并传参request
    3. timer1中,创建新线程thread2*
    4. thread2*中,用libcurl下载json文件/图片,若下载成功,设置定时器timer2并传参response
    5. timer2中,处理response,得到json/图片,用lua更新UI

    以上,打*星号的是其他线程,只有curl所在的thread2是其他线程,其他操作都在主线程中。这个模型也是实现题图效果的关键。

    其他的问题

    不是说实现了模型就能运行程序了,上得了厅堂、下得了厨房,还有些细节需要考虑。

    编码问题

    默认的std::string是GBK编码的,而一般的json文件是UTF-8,需要转码。

    在curl中,我们用

    char *content_type;
    curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &content_type);
    

    如果content_type中有UTF-8出现,那么文件编码就是UTF8。

    第一步:先用curl下载byte[]二进制数据

    size_t http_get_process_bin(void *data, size_t size, size_t nmemb, std::vector<byte> &content)
    {
        auto sizes = size * nmemb;
        auto bin = (byte*)data;
        for (size_t i = 0; i < sizes; ++i)
        {
            content.push_back(bin[i]);
        }
        return sizes;
    }
    
    auto bindata = new std::vector<byte>();
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, bindata);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &http_get_process_bin);
    /*其他的设置以及curl_easy_perform都省略了*/
    

    将数据存到std::vector<byte>中。

    第二步:转码,UTF8 to GBK

    CString Utf8ToStringT(LPCSTR str)
    {
        _ASSERT(str);
        USES_CONVERSION;
        WCHAR *buf;
        int length = MultiByteToWideChar(CP_UTF8, 0, str, -1, nullptr, 0);
        buf = new WCHAR[length + 1];
        ZeroMemory(buf, (length + 1) * sizeof(WCHAR));
    
        MultiByteToWideChar(CP_UTF8, 0, str, -1, buf, length);
        return (CString(W2T(buf)));
    }
    
    auto gbk = CStringA(content_type);//gbk相当于std::string
    

    其中CString是ATL中的unicode字符串。将CString自行转换至CStringA,而CStringA是ANSI编码的。

    如何呈现网上下载的图片

    先用libcurl下载二进制图片数据std::vector<byte> data,我们需要用一个byte[]类型去呈现它。

    data首先存放在libcurl所在线程中,最终调用者却是渲染图元ImageElement位于主线程中的渲染事件中,两者相距太远,如何联络?

    我采取的解决方法是:

    1. libcurl所在线程thread2*下载完图片数据data,注意,data是new出来的区域,不会自动释放
    2. 在thread*中设置定时器timer2,带参data,为了方便与lua互动,我将用base64字符串表示二进制数据data,那么存放在lua中的UI对象中的text就是将指针地址进行base64编码后的字符串
    3. 一段时间过去了……
    4. 开始渲染事件了
    5. ImageElementRender中,获取UI对象的text,它是个字符串,用base64解码得到图片数据指针
    6. 利用指针中的二进制数据,初始化WICBitmap,进而初始化ID2D1Bitmap用于绘制,释放数据data
    7. 为防止多次渲染闪屏,将WICBitmap保存,仅当图片URL变化时进行重新绘制操作,每次用WICBitmap初始化ID2D1Bitmap

    https://zhuanlan.zhihu.com/p/25476629备份。

  • 相关阅读:
    【leetcode】416. Partition Equal Subset Sum
    【leetcode】893. Groups of Special-Equivalent Strings
    【leetcode】892. Surface Area of 3D Shapes
    【leetcode】883. Projection Area of 3D Shapes
    【leetcode】140. Word Break II
    【leetcode】126. Word Ladder II
    【leetcode】44. Wildcard Matching
    【leetcode】336. Palindrome Pairs
    【leetcode】354. Russian Doll Envelopes
    2017.12.22 英语面试手记
  • 原文地址:https://www.cnblogs.com/bajdcc/p/8972932.html
Copyright © 2011-2022 走看看