• 诗情画意


    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备份。

  • 相关阅读:
    luoguP1558 色板游戏
    Tyvj1147
    Tyvj1147
    带修改的莫队(日常普及知识)
    带修改的莫队(日常普及知识)
    luoguP1903 数颜色(通过一道题认识带修改莫队)
    luoguP1903 数颜色(通过一道题认识带修改莫队)
    108.虚函数表原理(获取虚函数)
    104.virtual虚函数多态与异构数据结构
    106.多态与虚函数
  • 原文地址:https://www.cnblogs.com/bajdcc/p/8972932.html
Copyright © 2020-2023  润新知