美文网首页互联网科技程序员开源工具技巧
跨平台:libcurl+openssl编译(Mac、Androi

跨平台:libcurl+openssl编译(Mac、Androi

作者: TechGhost | 来源:发表于2019-02-28 00:31 被阅读9次


    前提概要

    众所周知,http/https是当下开发应用程序时,网路部分不可或缺的部分,我们可以基于socket自己来实现,因为http/https本身是基于TCP实现的应用层协议(位于网络模型的第7层)。但随着行业的发展,https加密、业内非标准http协议的推广(CDN)等这些部分,都需要耗费大量的开发成本,基于socket自己实现http/https的方案,成本上已经难以接受,选择开源的成熟方案是当下业内的共识。而curl是http/https最成熟的开源方案,其兼顾稳定性和易用性、跨平台性,是作为底层库的首选。当然其他一体化底层解决方案也是不错的选择,例如微信开源框架Mars、Qt等,这里我们仅在单一http/https方案这一选择中来做探讨。

    curl虽然易于使用,但在各平台编译上,有不少晦涩难懂的地方,也是它对于使用者来说的障碍,这篇文章旨在消除这些障碍,拨开云雾,一站式解决各平台编译问题,从而将大家宝贵的精力从中抽出,用在更有价值的事情上。

    参考资料和基本的概述如下:

    https://curl.haxx.se/docs/install.html

    http://p-nand-q.com/programming/windows/building_openssl_with_visual_studio_2013.html

    https://wiki.openssl.org/index.php/Main_Page

    整体工程

    请务必下载下来如下链接中整体curl编译介绍源码,然后后再针对性阅读后续介绍

    百度云盘:https://pan.baidu.com/s/1yXdqiUMBiHqeyVktlFUQ9A

    腾讯微云:https://share.weiyun.com/5t9cdnJ

    针对整体工程,我们分如下几部分做介绍:

    1.mac/ios编译

    2.windows编译

    3.android编译

    4.openssl多线程安全

    工程目录结构如下:


    一、mac/ios编译

    主要参考curl官方文档:

    https://curl.haxx.se/docs/install.html

    编译脚本build/libcurl/build_for_mac.sh执行的步骤:

    1.解压源码:curl-7.63.0.tar.gz

    2.编译libcurl

    关键代码(限于篇幅只贴出部分脚本):其中current_path是当前脚本执行路径,是编译输出路径,也可以配置为自定义的输出路径

    export MACOSX_DEPLOYMENT_TARGET="10.6"

    # buid configure

    ./buildconf

    ./configure --prefix=$current_path/out \

                --disable-shared \

                --enable-static \

                --with-darwinssl \

                --enable-threaded-resolver \

                --disable-ldap \

                --disable-ldaps

    # workaround still works though: make CFLAGS=-Wno-error for buid bug before v7.55.1

    # the build error is:connectx' is only available on macOS 10.11 or newer

    #make CFLAGS=-Wno-error

    make

    # install

    make install

    在win/linux/android下使用openssl,在mac/ios下用apple体系下ssl实现(Apple's SSL/TLS implementation)

    通过指定编译参数来指明:--with-darwinssl, remark: it is not necessary to use the option --without-ssl

    二、windows编译

    windows上libcurl的编译,可以参考libcurl源码下

    winbuild/BUILD.WINDOWS.txt

    准备环境:

    1.安装ActivePerl:官网下载http://www.activestate.com/activeperl/downloads

    2.安装nasm:官网下载http://www.nasm.us,附件中(build/build_depend_tools)也有安装包,并在系统环境变量中添加nasm安装路径(也可以使用附件中包含的批处理文件添加

    3.安装python:确认系统环境变量中是否已自动添加python路径,若没有手动添加


    1.编译入口

    编译脚本build/build_for_win.bat执行步骤:

    1.设置7zip环境变量,用于解压源码(整体工程压缩包中,带有7zip,curl/7-Zip路径下)

    2.编译openssl

    3.编译libcurl

    4.删除openssl临时生成文件

    5.删除libcurl临时文件

    关键代码(限于篇幅只贴出部分脚本):

    @echo off

    @set workdir=%cd%

    @set sevenzip=%workdir%\7-Zip\7z.exe

    :: build openssl

    @cd %workdir%\openssl

    @call build_for_win.bat %sevenzip%

    @cd %workdir%

    :: build libcurl

    @cd %workdir%\libcurl

    @call build_for_win.bat %sevenzip%

    @cd %workdir%

    :: delete openssl temp files

    @if exist %workdir%\openssl\openssl-1.0.2l (rd /s /q "%workdir%\openssl\openssl-1.0.2l") else echo can not find openssl-1.0.2l dir

    @if exist %workdir%\openssl\output_lib (rd /s /q "%workdir%\openssl\output_lib") else echo can not find output_lib dir

    :: delete libcurl temp files

    @if exist %workdir%\libcurl\curl-7.63.0 (rd /s /q "%workdir%\libcurl\curl-7.63.0") else echo can not find curl-7.63 dir

    @echo on

    2.首先编译openssl

    编译脚本build/openssl/build_for_win.bat执行步骤:

    1.设置VC环境变量(这里使用的是VS2015,可以根据自身需要自定义修改)

    2.解压源码:openssl-1.0.2l.tar.gz

    3.设置输出目录

    4.代码工程相关设置

    5.将/MD设置为/MT模式

      这一步根据自己工程的需要来做,如果应用程序其他模块都是/MD模式,则不需要执行这一步

      另外,由于openssl中没有提供脚本选项来自动生成/MT工程,所以只能替换生成的.mak中对应选项

    6.编译

    7.同步生成文件到目标路径,脚本中是对应工程kernel的输出路径,这里可以根据自身工程需要修改为自定义路径

    8.同步生成的.pdb文件目标路径,脚本中是对应工程kernel的输出路径,这里可以根据自身工程需要修改为自定义路径

      由于openssl安装脚本中没有提供pdb文件安装选项,所以这里需要额外从openssl临时生成路径下拷贝出来

    关键代码(限于篇幅只贴出部分脚本):

    :: 3)generate VC project config

    @perl configure VC-WIN32 --prefix=%outputlib%

    @call ms\do_ms.bat

    @call ms\do_nasm.bat

    :: 4)replace "/MD" to "/MT" in ms/ntdll.mak

    @setlocal enabledelayedexpansion

    @set ntdll_mak_file=%currentPath%\openssl-1.0.2l\ms\ntdll.mak

    @set ntdll_mak_file_temp=%currentPath%\openssl-1.0.2l\ms\ntdll_temp.mak if exist %ntdll_mak_file_temp% (del %ntdll_mak_file_temp%) else echo create temp file ntdll_temp.mak"

    for /f "delims=" %%i in (%ntdll_mak_file%) do (

        set str=%%i

        set str=!str:/MD=/MT!

        echo !str!>>%ntdll_mak_file_temp% )

    @move /y "%ntdll_mak_file_temp%" "%ntdll_mak_file%"

    @endlocal enabledelayedexpansion

    :: 5)build

    @nmake -f ms\ntdll.mak

    @nmake -f ms\ntdll.mak install @cd %currentPath%

    3.然后设置openssl依赖,编译curl

    脚本build/libcurl/build_for_win.bat执行步骤:

    1.设置VC环境变量(这里使用的是VS2015,可以根据自身需要自定义修改)

    2.解压源码:curl-7.63.0.tar.gz

    3.支持Windows XP版本

      VS2010以后,XP系统需要单独设置才能支持,若不需要,可以在curl/build/build_for_win.bat中去掉“@call build_for_win.bat %sevenzip% enable_xp”中enable_xp参数即可

      VS2015,推荐使用,兼容性好,工程中curl/build/build_for_win.bat编译脚本中默认开启了XP支持,这种情况下编译脚本自动化不会出错

      VS2013,不推荐,curl源码中对自动化编译支持有兼容性问题,打开了XP支持参数后,VS2013需要手动编译才能编译通过,VS2013工程在curl/build/libcurl/curl-7.63.0/project/windows/VC12路径下

    4.编译这里使用的是/MT模式,如果需要使用/MD模式,择修改“RTLIBCFG=static” 为 “RTLIBCFG=dll”  RTLIBCFG=static,表示libcurl是/MT  RTLIBCFG=dll,表示libcurl是/MD

    5.同步生成文件到目标路径下,脚本中是对应工程kernel的输出路径,这里可以根据自身工程需要修改为自定义路径 

    关键代码(限于篇幅只贴出部分脚本):

    :: 2) support Windows XP (add build command into “winBuild/MakefileBuild.vc”)

    if "%supportXP%"=="enable_xp (

        echo modify "winbuild/MakefileBuild.vc" to support windows xp

        python %currentPath%\build_for_win_support_xp.py %currentPath%\curl-7.63.0\winbuild\MakefileBuild.vc

    )

    :: 3)build

    @cd %currentPath%\curl-7.63.0\winbuild

    @nmake /f Makefile.vc WITH_DEVEL=../../../openssl/output_lib mode=dll VC=14 RTLIBCFG=static WITH_SSL=dll GEN_PDB=yes DEBUG=no MACHINE=x86

    @cd %currentPath%

    :: 4)sync build result to kernel

    @set output=%currentPath%\curl-7.63.0\builds\libcurl-vc14-x86-release-dll-ssl-dll-ipv6-sspi

    @copy /y "%output%\bin\libcurl.dll" "%currentPath%\..\..\..\..\output\bin"

    @copy /y "%output%\lib\libcurl.lib" "%currentPath%\..\..\..\..\output\lib"

    @copy /y "%output%\lib\libcurl.pdb" "%currentPath%\..\..\..\..\output\bin"


    三、android编译

    https://wiki.openssl.org/index.php/Android

    https://wiki.openssl.org/index.php/FIPS_Library_and_Android

    google中搜索 “openssl” “android” “wiki”关键字,从而找到权威文档https://wiki.openssl.org/index.php/Android

    关于NDK相关环境变量设置,参考权威文档中Setenv-android.sh

    1.编译入口

    脚本build/build_for_android.sh执行步骤:

    1.声明环境变量:NDK、NDK根目录、NDK版本、CPU指令架构(arm/x86/…)

    2.抽取NDK对应的CPU指令架构工具集(编译时需要用到)

    3.编译openssl

    4.编译libcurl

    5.删除抽取出来的NDK指令架构工具集

    6.同步生成文件到目标路径下,脚本中是对应工程kernel的输出路径,这里可以根据自身工程需要修改为自定义路径

    关键代码(限于篇幅只贴出部分脚本):

    echo 抽取NDK指令集目录

    ndk_toolchain_dir="$work_dir/ndk_toolchain"

    rm -rf $ndk_toolchain_dir

    $ndk_dir/build/tools/make_standalone_toolchain.py --arch $target_cpu --api $ndk_ver --stl gnustl --install-dir=$ndk_toolchain_dir --force

    echo 编译openssl

    bash $work_dir/openssl/build_for_android.sh $ndk_root $ndk_toolchain_dir $target_cpu

    echo 编译libcurl

    bash $work_dir/libcurl/build_for_android.sh $ndk_toolchain_dir $work_dir/openssl $target_cpu

    echo 删除NDK临时目录$ndk_toolchain_dir

    rm -rf $ndk_toolchain_dir

    echo 同步libcurl和openssl头文件

    cp $work_dir/libcurl/out/$target_cpu/include/curl/*.h $work_dir/../

    cp $work_dir/openssl/out/$target_cpu/include/openssl/*.h $work_dir/../openssl

    2.首先编译openssl

    脚本build/openssl/build_for_android.sh执行步骤:

    1.设置NDK相关环境变量,内部编译时会用到,

      这部分主要参考https://wiki.openssl.org/index.php/Android中的Setenv-android.sh

    2.编译

    关键代码(限于篇幅只贴出部分脚本):

    arch_target=arch-x86

    if [ $target_cpu == "x86" ]; then

        arch_target=android-x86

    fi

    if [ $target_cpu == "arm64" ]; then

        arch_target=android-armv7

    fi

    if [ $target_cpu == "arm" ]; then

        arch_target=android-armv7

    fi

    ./Configure $arch_target no-shared no-comp no-hw no-engine --prefix=$ssl_path --openssldir=$ssl_path --sysroot=$CROSS_SYSROOT -D__ANDROID_API__=18 -isystem$ANDROID_SYSTEM

    if [ $? != 0 ]; then

        exit 1

    fi

    make depend

    if [ $? != 0 ]; then

        exit 1

    fi

    make all

    if [ $? != 0 ]; then

      exit 1

    fi

    make install_sw

    if [ $? != 0 ]; then

        exit 1

    fi

    3.然后设置openssl依赖后,编译curl

    脚本build/libcurl/build_for_android.sh执行步骤:

    1.解压源码:curl-7.63.0.tar.gz

    1.设置NDK工具集目录(入口build/build_for_android.sh编译脚本中抽取的NDK对应的CPU指令架构工具集)

    2.设置openssl输出目录(依赖openssl)

    3.设置目标机器指令集

    4.编译

    关键代码(限于篇幅只贴出部分脚本):

    # 自己的android-toolchain(NDK针对特定配置抽取出来的独立目录)

    export ANDROID_HOME=$ndk_toolchain_dir

    # openssl的输出目录

    export CFLAGS="-isystem$openssl_dir/out/$target_cpu/include"

    export LDFLAGS="-L$openssl_dir/out/$target_cpu/lib"

    export TOOLCHAIN=$ANDROID_HOME/bin

    # 设置目标机器指令集

    arch_flags="-march=i686 -msse3 -mstackrealign -mfpmath=sse"

    arch=arch-x86

    tool_target=i686-linux-android

    host_os=i686-linux-android

    if [ $target_cpu == "x86" ]; then

        arch_flags="-march=i686 -msse3 -mstackrealign -mfpmath=sse"

        arch=arch-x86

        tool_target=i686-linux-android

        host_os=i686-linux-android

    fi

    if [ $target_cpu == "arm" ]; then

        arch_flags="-march=armv7-a -msse3 -mstackrealign -mfpmath=sse"

        arch=arch-arm

        tool_target=arm-linux-androideabi

        host_os=arm-androideabi-linux

    fi

    if [ $target_cpu == "arm64" ]; then

        arch_flags="-march=armv8 -msse3 -mstackrealign -mfpmath=sse"

        arch=arch-arm

        tool_target=arm-linux-androideabi

        host_os=arm-androideabi-linux

    fi

    echo 当前CPU指令集匹配arch为"$arch",arch_flags为$arch_flags

    export TOOL=$tool_target

    export ARCH_FLAGS=$arch_flags

    export ARCH=$arch

    export CC=$TOOLCHAIN/$TOOL-gcc

    export CXX=$TOOLCHAIN/${TOOL}-g++

    export LINK=${CXX}

    export LD=$TOOLCHAIN/${TOOL}-ld

    export AR=$TOOLCHAIN/${TOOL}-ar

    export RANLIB=$TOOLCHAIN/${TOOL}-ranlib

    export STRIP=$TOOLCHAIN/${TOOL}-strip

    export CPPFLAGS="-DANDROID -D__ANDROID_API__=18"

    export LIBS="-lssl -lcrypto"

    export CROSS_SYSROOT=$TOOLCHAIN/sysroot

    cd $source_dir

    ./configure --prefix=$current_path/out/$target_cpu \

                --exec-prefix=$current_path/out/$target_cpu \

                --bindir=$TOOLCHAIN \

                --sbindir=$TOOLCHAIN \

                --libexecdir=$TOOLCHAIN \

                --with-sysroot=$CROSS_SYSROOT \

                --host=$host_os \

                --enable-ipv6 \

                --enable-threaded-resolver \

                --disable-dict \

                --disable-gopher \

                --disable-ldap \

                --disable-ldaps \

                --disable-manual \

                --disable-pop3 \

                --disable-smtp \

                --disable-imap \

                --disable-rtsp \

                --disable-smb \

                --disable-telnet \

                --disable-verbose

    make install


    四、openssl多线程安全

    openssl在多线程这块,有些历史因素,导致不同版本应用层需要做的事情不一样:

    参考文档:https://www.openssl.org/blog/blog/2017/02/21/threads/

    1.v1.0.2和之前的版本,多线程安全需要应用层自己实现,在openssl/crypto.h中有预留实现接口位置

      文档中示例程序th-lock.c有做说明

    2.v1.1.0版本之后,多线程安全加锁的实现,从运行时转到了编译期间

      编译时启用多线程安全参数,则openssl会将各平台加锁实现打包进来

    我们这里使用的版本是v1.0.2l版本,所以多线程安全部分需要应用层自己实现,虽然如此,openssl其实已经做了很多,我们只需要实现特定的几个位置的代码即可,具体代码如下:

    加锁实现部分:

    #include "openssl/crypto.h"

    #include "openssl/err.h"

    #if defined(WIN32)

    #define MUTEX_TYPE HANDLE

    #define MUTEX_SETUP(x) (x) = CreateMutex(NULL, FALSE, NULL)

    #define MUTEX_CLEANUP(x) CloseHandle(x)

    #define MUTEX_LOCK(x) WaitForSingleObject((x), INFINITE)

    #define MUTEX_UNLOCK(x) ReleaseMutex(x)

    #define THREAD_ID GetCurrentThreadId()

    #else

    #include <pthread.h>

    #define MUTEX_TYPE pthread_mutex_t

    #define MUTEX_SETUP(x) pthread_mutex_init(&(x), NULL)

    #define MUTEX_CLEANUP(x) pthread_mutex_destroy(&(x))

    #define MUTEX_LOCK(x) pthread_mutex_lock(&(x))

    #define MUTEX_UNLOCK(x) pthread_mutex_unlock(&(x))

    #define THREAD_ID pthread_self()

    #endif

    static MUTEX_TYPE *mutexArray = NULL;

    static int32_t nNumLocks = 0;

    static void locking_function(int mode, int n, const char * file, int line)

    {

        if (n >= nNumLocks) {

            return;

        }

        if (mode & CRYPTO_LOCK) {

            MUTEX_LOCK(mutexArray[n]);

        } else {

            MUTEX_UNLOCK(mutexArray[n]);

        }

    }

    static unsigned long threadId_function(void)

    {

        return ((unsigned long)THREAD_ID);

    }

    namespace OpenSSLThreadLock

    {

        void OpenSSLLock_Setup(void)

        {

            nNumLocks = CRYPTO_num_locks();

    #ifdef _MSC_VER

            mutexArray = (MUTEX_TYPE*)OPENSSL_malloc(nNumLocks * sizeof(MUTEX_TYPE));

    #else

            mutexArray = (MUTEX_TYPE*)malloc(nNumLocks * sizeof(MUTEX_TYPE));

    #endif

            if (!mutexArray) {

                  return;

            }

            for (int32_t  i = 0;  i  <  nNumLocks;  ++i) {

                MUTEX_SETUP(mutexArray[i]);

            }

            CRYPTO_set_id_callback(threadId_function);

            CRYPTO_set_locking_callback(locking_function);

        }

        void OpenSSLLock_Cleanup(void)

        {

            if (!mutexArray) {

                return;

            }

            CRYPTO_set_id_callback(NULL);

            CRYPTO_set_locking_callback(NULL);

            for (int32_t i = 0; i < CRYPTO_num_locks(); ++i) {

                MUTEX_CLEANUP(mutexArray[i]);

            }

    #ifdef _MSC_VER

            OPENSSL_free(mutexArray);

    #else

            free(mutexArray);

    #endif

            mutexArray = NULL;

        }

    }

    调用部分则比较简单,只需要在libcurl模块整体初始化和退出调用对应接口即可

    1.http模块初始化时

    OpenSSLThreadLock::OpenSSLLock_Setup();

    2.http模块退出时

    // curl_global_cleanup();之前调用

    OpenSSLThreadLock::OpenSSLLock_Cleanup();

    相关文章

      网友评论

        本文标题:跨平台:libcurl+openssl编译(Mac、Androi

        本文链接:https://www.haomeiwen.com/subject/leqcuqtx.html