commit 339c93e477481411e94c72ca0592d82594f20f0e Author: xlt-evil Date: Tue Apr 23 11:41:01 2024 +0800 first commit diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..5074bd3 --- /dev/null +++ b/.clang-format @@ -0,0 +1,89 @@ +# This is for clang-format >= 9.0. +# +# clang-format --version +# clang-format version 9.0.1 (Red Hat 9.0.1-2.module+el8.2.0+5494+7b8075cf) +# +# 详细说明见: https://clang.llvm.org/docs/ClangFormatStyleOptions.html +# 部分参数会随版本变化. +--- +Language: Cpp +# 基于 WebKit 的风格, https://www.webkit.org/coding/coding-style.html +BasedOnStyle: WebKit + +# 以下各选项按字母排序 + +# public/protected/private 不缩进 +AccessModifierOffset: -4 +# 参数过长时统一换行 +AlignAfterOpenBracket: AlwaysBreak +# clang-format >= 13 required, map 之类的内部列对齐 +# AlignArrayOfStructures: Left +# 换行符统一在 ColumnLimit 最右侧 +AlignEscapedNewlines: Right +# 不允许短代码块单行, 即不允许单行代码: if (x) return; +AllowShortBlocksOnASingleLine: false +# 只允许 Inline 函数单行 +AllowShortFunctionsOnASingleLine: Inline +# 模板声明换行 +AlwaysBreakTemplateDeclarations: Yes +# 左开括号不换行 +BreakBeforeBraces: Custom +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + # BraceWrappingAfterControlStatementStyle: MultiLine + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false +# 构造函数初始化时在 `,` 前换行, 和 `:` 对齐显得整齐 +BreakConstructorInitializers: BeforeComma +# 继承过长需要换行时也在 `,` 前 +BreakInheritanceList: BeforeComma +# 列宽 120 +ColumnLimit: 120 +# c++11 括号内起始/结束无空格, false 会加上 +Cpp11BracedListStyle: false +# 命名空间后的注释会修正为: // namespace_name +FixNamespaceComments: true + +#switch case的缩进 +IndentCaseLabels: true +#允许单行case +AllowShortCaseLabelsOnASingleLine: true + +# clang-format >= 13 required, lambda 函数内部缩进级别和外部一致, 默认会增加一级缩进 +# LambdaBodyIndentation: OuterScope +# 命名空间不缩进 +NamespaceIndentation: None +# PPIndentWidth: 2 +# */& 靠近变量, 向右靠 +PointerAlignment: Right +# c++11 使用 {} 构造时和变量加个空格 +SpaceBeforeCpp11BracedList: true +# 继承时 `:` 前加空格 +SpaceBeforeInheritanceColon: true +# () 前不加空格, do/for/if/switch/while 除外 +SpaceBeforeParens: ControlStatements +# 空 {} 中不加空格 +SpaceInEmptyBlock: false +Standard: C++11 +# Tab 占 4 位 +TabWidth: 4 +# 不使用 TAB +UseTab: Never +--- +Language: Java +--- +Language: JavaScript +... diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..b7f1a8b --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +*.h linguist-language=cpp +*.c linguist-language=cpp diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md new file mode 100644 index 0000000..4622097 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -0,0 +1,85 @@ +--- +name: bug 反馈 +about: 反馈 ZLMediaKit 代码本身的 bug +title: "[BUG]: BUG 现象描述" +labels: bug +assignees: '' + +--- + + + + + +### 现象描述 + + + +### 如何复现? + + + +### 相关日志或截图 + + + +
+展开查看详细日志 +
+日志内容...
+
+
+ +### 配置 + + + +
+展开查看详细配置 +
+配置内容...
+
+
+ +### 各种环境信息 + + + +* **代码提交记录/git commit hash**: +* **操作系统及版本**: +* **硬件信息**: +* **其他需要补充的信息**: diff --git a/.github/ISSUE_TEMPLATE/compile.md b/.github/ISSUE_TEMPLATE/compile.md new file mode 100644 index 0000000..4e05219 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/compile.md @@ -0,0 +1,57 @@ +--- +name: 编译问题反馈 +about: 反馈 ZLMediaKit 编译相关的问题 +title: "[编译问题]: " +labels: 编译问题 +assignees: '' + +--- + + + + + +### 相关日志及环境信息 + + + +**清除编译缓存后,完整执行 cmake && make 命令的输出** + +
+展开查看详细编译日志 +
+
+```
+详细日志粘在这里!
+```
+
+
+
+ +编译目录下的 `CMakeCache.txt` 文件内容,请直接上传为附件。 + +### 各种环境信息 + + + +* **代码提交记录/git commit hash**: +* **操作系统及版本**: +* **硬件信息**: +* **其他需要补充的信息**: diff --git a/.github/ISSUE_TEMPLATE/feature.md b/.github/ISSUE_TEMPLATE/feature.md new file mode 100644 index 0000000..4660cf0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature.md @@ -0,0 +1,14 @@ +--- +name: 新增功能请求 +about: 请求新增某些新功能或新特性,或者对已有功能的改进 +title: "[功能请求]" +labels: 意见建议 +assignees: '' + +--- + +**描述该功能的用处,可以提供相关资料描述该功能** + +**该功能是否用于改进项目缺陷,如果是,请描述现有缺陷** + +**描述你期望实现该功能的方式和最终效果** diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 0000000..66d235c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,19 @@ +--- +name: 技术咨询 +about: 使用咨询、技术咨询等 +title: "[技术咨询]" +labels: 技术咨询 +assignees: '' + +--- + +**咨询的功能模块** +- 请描述您想咨询zlmediakit的哪部分功能 + +**咨询的具体内容和问题** +- 此处展开您咨询内容的描述 + +**注意事项** +- 技术咨询前请先认真阅读readme, [wiki](https://github.com/xia-chu/ZLMediaKit/wiki),如有必要,您也可以同时搜索已经答复的issue,如果没找到答案才在此提issue + +- 技术咨询不属于bug缺陷,建议先star本项目,否则可能会降低答复优先级 diff --git a/.github/ISSUE_TEMPLATE/requirement.md b/.github/ISSUE_TEMPLATE/requirement.md new file mode 100644 index 0000000..42b72f2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/requirement.md @@ -0,0 +1,8 @@ +--- +name: issue创建要求 +about: 不符合模板要求不便定位问题,可能会被管理员直接关闭 +title: "" +labels: '' +assignees: '' + +--- diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml new file mode 100644 index 0000000..a4d36bc --- /dev/null +++ b/.github/workflows/android.yml @@ -0,0 +1,25 @@ +name: Android +on: [push, pull_request] +jobs: + build: + runs-on: ubuntu-20.04 + steps: + + - name: 下载源码 + uses: actions/checkout@v1 + + - name: 配置JDK + uses: actions/setup-java@v3 + with: + java-version: '11' + distribution: 'temurin' + cache: gradle + + - name: 下载submodule源码 + run: mv -f .gitmodules_github .gitmodules && git submodule sync && git submodule update --init + + - name: 赋予gradlew文件可执行权限 + run: chmod +x ./Android/gradlew + + - name: 编译 + run: cd Android && ./gradlew build diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..75f23ee --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,62 @@ +name: CodeQL + +on: [push, pull_request] + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-20.04 + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'cpp', 'javascript' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - uses: actions/checkout@v1 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + - name: 下载submodule源码 + run: mv -f .gitmodules_github .gitmodules && git submodule sync && git submodule update --init + + - name: apt-get安装依赖库(非必选) + run: sudo apt-get update && sudo apt-get install -y cmake libssl-dev libsdl-dev libavcodec-dev libavutil-dev libswscale-dev libresample-dev + + - name: 下载 SRTP + uses: actions/checkout@v2 + with: + repository: cisco/libsrtp + fetch-depth: 1 + ref: v2.3.0 + path: 3rdpart/libsrtp + + - name: 编译 SRTP + run: cd 3rdpart/libsrtp && ./configure --enable-openssl && make -j4 && sudo make install + + - name: 编译 + run: mkdir -p linux_build && cd linux_build && cmake .. -DENABLE_WEBRTC=true -DENABLE_FFMPEG=true && make -j $(nproc) + + - name: 运行MediaServer + run: pwd && cd release/linux/Debug && sudo ./MediaServer -d & + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + + + diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..db67603 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,86 @@ +name: Docker + +on: [push, pull_request] + +env: + # Use docker.io for Docker Hub if empty + REGISTRY: docker.io + IMAGE_NAME: zlmediakit/zlmediakit + +jobs: + build: + + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + # This is used to complete the identity challenge + # with sigstore/fulcio when running outside of PRs. + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: 下载submodule源码 + run: mv -f .gitmodules_github .gitmodules && git submodule sync && git submodule update --init + + # Install the cosign tool except on PR + # https://github.com/sigstore/cosign-installer + - name: Install cosign + if: github.event_name != 'pull_request' + uses: sigstore/cosign-installer@d6a3abf1bdea83574e28d40543793018b6035605 + with: + cosign-release: 'v1.7.1' + + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + + + # Workaround: https://github.com/docker/build-push-action/issues/461 + - name: Setup Docker buildx + uses: docker/setup-buildx-action@79abd3f86f79a9d68a23c75a09a9a85889262adf + + # Login against a Docker registry except on PR + # https://github.com/docker/login-action + - name: Log into registry ${{ env.REGISTRY }} + if: github.event_name != 'pull_request' + uses: docker/login-action@28218f9b04b4f3f62068d7b6ce6ca5b26e35336c + with: + registry: ${{ env.REGISTRY }} + username: zlmediakit + password: ${{ secrets.DOCKER_IO_SECRET }} + + # Extract metadata (tags, labels) for Docker + # https://github.com/docker/metadata-action + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + # Build and push Docker image with Buildx (don't push on PR) + # https://github.com/docker/build-push-action + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@ac9327eae2b366085ac7f6a2d02df8aa8ead720a + with: + context: . + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: MODEL=Release + platforms: linux/amd64,linux/arm64 + + # Sign the resulting Docker image digest except on PRs. + # This will only write to the public Rekor transparency log when the Docker + # repository is public to avoid leaking data. If you would like to publish + # transparency data even for private images, pass --force to cosign below. + # https://github.com/sigstore/cosign +# - name: Sign the published Docker image +# if: ${{ github.event_name != 'pull_request' }} +# env: +# COSIGN_EXPERIMENTAL: "true" +# # This step uses the identity token to provision an ephemeral certificate +# # against the sigstore community Fulcio instance. +# run: cosign sign ${{ steps.meta.outputs.tags }}@${{ steps.build-and-push.outputs.digest }} diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml new file mode 100644 index 0000000..58580e6 --- /dev/null +++ b/.github/workflows/linux.yml @@ -0,0 +1,36 @@ +name: Linux + +on: [push, pull_request] + +jobs: + build: + + runs-on: ubuntu-20.04 + + steps: + - uses: actions/checkout@v1 + + - name: 下载submodule源码 + run: mv -f .gitmodules_github .gitmodules && git submodule sync && git submodule update --init + + - name: apt-get安装依赖库(非必选) + run: sudo apt-get update && sudo apt-get install -y cmake libssl-dev libsdl-dev libavcodec-dev libavutil-dev libswscale-dev libresample-dev libusrsctp-dev + + - name: 下载 SRTP + uses: actions/checkout@v2 + with: + repository: cisco/libsrtp + fetch-depth: 1 + ref: v2.3.0 + path: 3rdpart/libsrtp + + - name: 编译 SRTP + run: cd 3rdpart/libsrtp && ./configure --enable-openssl && make -j4 && sudo make install + + - name: 编译 + run: mkdir -p linux_build && cd linux_build && cmake .. -DENABLE_WEBRTC=true -DENABLE_FFMPEG=true && make -j $(nproc) + + - name: 运行MediaServer + run: pwd && cd release/linux/Debug && sudo ./MediaServer -d & + + diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml new file mode 100644 index 0000000..291b712 --- /dev/null +++ b/.github/workflows/macos.yml @@ -0,0 +1,39 @@ +name: macOS + +on: [push, pull_request] + +jobs: + build: + + runs-on: macOS-latest + + steps: + - uses: actions/checkout@v1 + + - name: 下载submodule源码 + run: mv -f .gitmodules_github .gitmodules && git submodule sync && git submodule update --init + +# - name: 安装brew +# run: ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" +# +# - name: brew安装依赖库(非必选) +# run: brew update && brew install cmake openssl sdl2 ffmpeg + +# - name: 下载 SRTP +# uses: actions/checkout@v2 +# with: +# repository: cisco/libsrtp +# fetch-depth: 1 +# ref: v2.3.0 +# path: 3rdpart/libsrtp +# +# - name: 编译 SRTP +# run: cd 3rdpart/libsrtp && ./configure --enable-openssl && make -j4 && sudo make install + + - name: 编译 + run: mkdir -p build && cd build && cmake .. && make -j $(nproc) + + - name: 运行MediaServer + run: pwd && cd release/linux/Debug && sudo ./MediaServer -d & + + diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml new file mode 100644 index 0000000..361add9 --- /dev/null +++ b/.github/workflows/style.yml @@ -0,0 +1,27 @@ +name: style check + +on: [pull_request] + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + # with all history + fetch-depth: 0 + - name: Validate BOM + run: | + ret=0 + for i in $(git diff --name-only origin/${GITHUB_BASE_REF}...${GITHUB_SHA}); do + if [ -f ${i} ]; then + case ${i} in + *.c|*.cc|*.cpp|*.h) + if file ${i} | grep -qv BOM; then + echo "Missing BOM in ${i}" && ret=1; + fi + ;; + esac + fi + done + exit ${ret} diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml new file mode 100644 index 0000000..ec36608 --- /dev/null +++ b/.github/workflows/windows.yml @@ -0,0 +1,30 @@ +name: Windows + +on: [push, pull_request] + +jobs: + build: + runs-on: windows-2019 + + steps: + - uses: actions/checkout@v1 + + - name: 下载submodule源码 + run: mv -Force .gitmodules_github .gitmodules && git submodule sync && git submodule update --init + + - name: 配置 vcpkg + uses: lukka/run-vcpkg@v7 + with: + vcpkgDirectory: '${{github.workspace}}/vcpkg' + vcpkgTriplet: x64-windows-static + # 2021.05.12 + vcpkgGitCommitId: '5568f110b509a9fd90711978a7cb76bae75bb092' + vcpkgArguments: 'openssl libsrtp' + + - name: 编译 + uses: lukka/run-cmake@v3 + with: + useVcpkgToolchainFile: true + buildDirectory: '${{github.workspace}}/build' + cmakeAppendedArgs: '-DCMAKE_ENABLE_WEBRTC:BOOL=ON' + cmakeBuildType: 'RelWithDebInfo' diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..952d632 --- /dev/null +++ b/.gitignore @@ -0,0 +1,51 @@ +# Compiled Object files +*.slo +*.lo +*.o +*.obj +*.d + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +#*.dylib +#*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.lib + +# Executables +#*.exe +*.out +*.app +/X64/ + + +*.DS_Store + +/cmake-build-debug/ +/cmake-build-release/ +/linux/ +/.vs/ +/.vscode/ +/.idea/ +/c_wrapper/.idea/ +/release/ +/out/ +/Android/.idea/ +/Android/app/src/main/cpp/libs_export/ +/3rdpart/media-server/.idea/ +/3rdpart/media-server/.idea/ +/build/ +/3rdpart/media-server/.idea/ +/ios/ +/cmake-build-* +/3rdpart/ZLToolKit/cmake-build-mq/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..7ef0bc7 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,9 @@ +[submodule "ZLToolKit"] + path = 3rdpart/ZLToolKit + url = https://gitee.com/xia-chu/ZLToolKit +[submodule "3rdpart/media-server"] + path = 3rdpart/media-server + url = https://gitee.com/ireader/media-server +[submodule "3rdpart/jsoncpp"] + path = 3rdpart/jsoncpp + url = https://gitee.com/mirrors/jsoncpp.git \ No newline at end of file diff --git a/.gitmodules_github b/.gitmodules_github new file mode 100644 index 0000000..ddd310e --- /dev/null +++ b/.gitmodules_github @@ -0,0 +1,9 @@ +[submodule "ZLToolKit"] + path = 3rdpart/ZLToolKit + url = https://github.com/ZLMediaKit/ZLToolKit +[submodule "3rdpart/media-server"] + path = 3rdpart/media-server + url = https://github.com/ireader/media-server +[submodule "3rdpart/jsoncpp"] + path = 3rdpart/jsoncpp + url = https://github.com/open-source-parsers/jsoncpp.git diff --git a/3rdpart/CMakeLists.txt b/3rdpart/CMakeLists.txt new file mode 100644 index 0000000..f489b1a --- /dev/null +++ b/3rdpart/CMakeLists.txt @@ -0,0 +1,215 @@ +# MIT License +# +# Copyright (c) 2016-2022 The ZLMediaKit project authors. All Rights Reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# + +############################################################################## + +# jsoncpp +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/jsoncpp/src/lib_json JSONCPP_SRC_LIST) +add_library(jsoncpp STATIC ${JSONCPP_SRC_LIST}) +target_compile_options(jsoncpp + PRIVATE ${COMPILE_OPTIONS_DEFAULT}) +target_include_directories(jsoncpp + PRIVATE + "$/jsoncpp/include" + PUBLIC + "$/jsoncpp/include") + +update_cached_list(MK_LINK_LIBRARIES jsoncpp) + +############################################################################## + +# media-server +set(MediaServer_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/media-server") +# TODO: 补一个函数处理各种库 + +# 添加 mov、flv 库用于 MP4 录制 +if(ENABLE_MP4) + message(STATUS "ENABLE_MP4 defined") + + # MOV + set(MediaServer_MOV_ROOT ${MediaServer_ROOT}/libmov) + aux_source_directory(${MediaServer_MOV_ROOT}/include MOV_SRC_LIST) + aux_source_directory(${MediaServer_MOV_ROOT}/source MOV_SRC_LIST) + add_library(mov STATIC ${MOV_SRC_LIST}) + add_library(MediaServer::mov ALIAS mov) + target_compile_definitions(mov + PUBLIC -DENABLE_MP4) + target_compile_options(mov + PRIVATE ${COMPILE_OPTIONS_DEFAULT}) + target_include_directories(mov + PRIVATE + "$" + PUBLIC + "$") + + # FLV + set(MediaServer_FLV_ROOT ${MediaServer_ROOT}/libflv) + aux_source_directory(${MediaServer_FLV_ROOT}/include FLV_SRC_LIST) + aux_source_directory(${MediaServer_FLV_ROOT}/source FLV_SRC_LIST) + add_library(flv STATIC ${FLV_SRC_LIST}) + add_library(MediaServer::flv ALIAS flv) + target_compile_options(flv + PRIVATE ${COMPILE_OPTIONS_DEFAULT}) + target_include_directories(flv + PRIVATE + "$" + PUBLIC + "$") + + update_cached_list(MK_LINK_LIBRARIES + MediaServer::flv MediaServer::mov) + update_cached_list(MK_COMPILE_DEFINITIONS + ENABLE_MP4) +endif() + +# 添加 mpeg 用于支持 ts 生成 +if(ENABLE_RTPPROXY OR ENABLE_HLS) + # mpeg + set(MediaServer_MPEG_ROOT ${MediaServer_ROOT}/libmpeg) + aux_source_directory(${MediaServer_MPEG_ROOT}/include MPEG_SRC_LIST) + aux_source_directory(${MediaServer_MPEG_ROOT}/source MPEG_SRC_LIST) + add_library(mpeg STATIC ${MPEG_SRC_LIST}) + add_library(MediaServer::mpeg ALIAS mpeg) + # media-server库相关编译宏 + # MPEG_H26X_VERIFY - 视频流类型识别 + # MPEG_ZERO_PAYLOAD_LENGTH - 兼容hik流 + # MPEG_DAHUA_AAC_FROM_G711 - 兼容dahua流 + target_compile_options(mpeg + PRIVATE ${COMPILE_OPTIONS_DEFAULT} -DMPEG_H26X_VERIFY -DMPEG_ZERO_PAYLOAD_LENGTH -DMPEG_DAHUA_AAC_FROM_G711) + target_include_directories(mpeg + PRIVATE + "$" + PUBLIC + "$") + + update_cached_list(MK_LINK_LIBRARIES MediaServer::mpeg) + if(ENABLE_RTPPROXY) + update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_RTPPROXY) + endif() + if(ENABLE_HLS) + update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_HLS) + endif() +endif() + +############################################################################## + +# toolkit +# TODO: 改造 toolkit 以便直接引用 + +include(CheckStructHasMember) +include(CheckSymbolExists) + +# 检查 sendmmsg 相关依赖并设置对应的宏, 配置 _GNU_SOURCE 以启用 GNU 扩展特性 +list(APPEND CMAKE_REQUIRED_DEFINITIONS -D_GNU_SOURCE) +check_struct_has_member("struct mmsghdr" msg_hdr sys/socket.h HAVE_MMSG_HDR) +check_symbol_exists(sendmmsg sys/socket.h HAVE_SENDMMSG_API) +check_symbol_exists(recvmmsg sys/socket.h HAVE_RECVMMSG_API) + +set(COMPILE_DEFINITIONS) +# ToolKit 依赖 ENABLE_OPENSSL 以及 ENABLE_MYSQL +list(FIND MK_COMPILE_DEFINITIONS ENABLE_OPENSSL ENABLE_OPENSSL_INDEX) +if(NOT ENABLE_OPENSSL_INDEX EQUAL -1) + list(APPEND COMPILE_DEFINITIONS ENABLE_OPENSSL) +endif() +list(FIND MK_COMPILE_DEFINITIONS ENABLE_MYSQL ENABLE_MYSQL_INDEX) +if(NOT ENABLE_MYSQL_INDEX EQUAL -1) + list(APPEND COMPILE_DEFINITIONS ENABLE_MYSQL) +endif() +if(HAVE_MMSG_HDR) + list(APPEND COMPILE_DEFINITIONS HAVE_MMSG_HDR) +endif() +if(HAVE_SENDMMSG_API) + list(APPEND COMPILE_DEFINITIONS HAVE_SENDMMSG_API) +endif() +if(HAVE_RECVMMSG_API) + list(APPEND COMPILE_DEFINITIONS HAVE_RECVMMSG_API) +endif() + +set(ToolKit_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/ZLToolKit) +# 收集源代码 +file(GLOB ToolKit_SRC_LIST + ${ToolKit_ROOT}/src/*/*.cpp + ${ToolKit_ROOT}/src/*/*.h + ${ToolKit_ROOT}/src/*/*.c) +if(IOS) + list(APPEND ToolKit_SRC_LIST + ${ToolKit_ROOT}/Network/Socket_ios.mm) +endif() + +################################################################### +#使用wepoll windows iocp 模拟 epoll +if(ENABLE_WEPOLL) + if(WIN32) + message(STATUS "Enable wepoll") + #增加wepoll源文件及api参数兼容文件 + list(APPEND ToolKit_SRC_LIST + ${CMAKE_CURRENT_SOURCE_DIR}/wepoll/wepoll.c + ${CMAKE_CURRENT_SOURCE_DIR}/wepoll/sys/epoll.cpp) + #增加wepoll头文件目录 + include_directories(${CMAKE_CURRENT_SOURCE_DIR}/wepoll) + #开启epoll + add_definitions(-DHAS_EPOLL) + endif() +endif() +################################################################### + +# 去除 win32 的适配代码 +if(NOT WIN32) + list(REMOVE_ITEM ToolKit_SRC_LIST ${ToolKit_ROOT}/win32/getopt.c) +else() + # 防止 Windows.h 包含 Winsock.h + list(APPEND COMPILE_DEFINITIONS + WIN32_LEAN_AND_MEAN MP4V2_NO_STDINT_DEFS + # 禁用警告 + _CRT_SECURE_NO_WARNINGS _WINSOCK_DEPRECATED_NO_WARNINGS) +endif() + +# 添加库 +add_library(zltoolkit STATIC ${ToolKit_SRC_LIST}) +add_library(ZLMediaKit::ToolKit ALIAS zltoolkit) +target_compile_definitions(zltoolkit + PUBLIC ${COMPILE_DEFINITIONS}) +target_compile_options(zltoolkit + PRIVATE ${COMPILE_OPTIONS_DEFAULT}) +target_include_directories(zltoolkit + PRIVATE + "$" + PUBLIC + "$/src") + +update_cached_list(MK_LINK_LIBRARIES ZLMediaKit::ToolKit) + +if(USE_SOLUTION_FOLDERS AND (NOT GROUP_BY_EXPLORER)) + # 在 IDE 中对文件进行分组, 源文件和头文件分开 + set_file_group(${ToolKit_ROOT}/src ${ToolKit_SRC_LIST}) +endif() + +# 未在使用 +if(ENABLE_CXX_API) + # 保留目录结构 + install(DIRECTORY ${ToolKit_ROOT}/ + DESTINATION ${INSTALL_PATH_INCLUDE}/ZLToolKit + REGEX "(.*[.](md|cpp)|win32)$" EXCLUDE) + install(TARGETS zltoolkit + DESTINATION ${INSTALL_PATH_LIB}) +endif() diff --git a/3rdpart/ZLToolKit/.github/workflows/linux.yml b/3rdpart/ZLToolKit/.github/workflows/linux.yml new file mode 100644 index 0000000..cd53d04 --- /dev/null +++ b/3rdpart/ZLToolKit/.github/workflows/linux.yml @@ -0,0 +1,46 @@ +name: Linux + +on: [push, pull_request] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally + # well on Windows or Mac. You can convert this to a matrix build if you need + # cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Create Build Environment + # Some projects don't allow in-source building, so create a separate build directory + # We'll use this as our working directory for all subsequent commands + run: cmake -E make_directory ${{github.workspace}}/build + + - name: Configure CMake + # Use a bash shell so we can use the same syntax for environment variable + # access regardless of the host operating system + shell: bash + working-directory: ${{github.workspace}}/build + # Note the current convention is to use the -S and -B options here to specify source + # and build directories, but this is only available with CMake 3.13 and higher. + # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE + + - name: Build + working-directory: ${{github.workspace}}/build + shell: bash + # Execute the build. You can specify a specific target with "--target " + run: cmake --build . --config $BUILD_TYPE -j $(nproc) + + - name: Test + working-directory: ${{github.workspace}}/build + shell: bash + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C $BUILD_TYPE diff --git a/3rdpart/ZLToolKit/.github/workflows/macos.yml b/3rdpart/ZLToolKit/.github/workflows/macos.yml new file mode 100644 index 0000000..d435819 --- /dev/null +++ b/3rdpart/ZLToolKit/.github/workflows/macos.yml @@ -0,0 +1,46 @@ +name: MacOS + +on: [push, pull_request] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally + # well on Windows or Mac. You can convert this to a matrix build if you need + # cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: macOS-latest + + steps: + - uses: actions/checkout@v2 + + - name: Create Build Environment + # Some projects don't allow in-source building, so create a separate build directory + # We'll use this as our working directory for all subsequent commands + run: cmake -E make_directory ${{github.workspace}}/build + + - name: Configure CMake + # Use a bash shell so we can use the same syntax for environment variable + # access regardless of the host operating system + shell: bash + working-directory: ${{github.workspace}}/build + # Note the current convention is to use the -S and -B options here to specify source + # and build directories, but this is only available with CMake 3.13 and higher. + # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE + + - name: Build + working-directory: ${{github.workspace}}/build + shell: bash + # Execute the build. You can specify a specific target with "--target " + run: cmake --build . --config $BUILD_TYPE -j $(nproc) + + - name: Test + working-directory: ${{github.workspace}}/build + shell: bash + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C $BUILD_TYPE diff --git a/3rdpart/ZLToolKit/.github/workflows/style.yml b/3rdpart/ZLToolKit/.github/workflows/style.yml new file mode 100644 index 0000000..361add9 --- /dev/null +++ b/3rdpart/ZLToolKit/.github/workflows/style.yml @@ -0,0 +1,27 @@ +name: style check + +on: [pull_request] + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + # with all history + fetch-depth: 0 + - name: Validate BOM + run: | + ret=0 + for i in $(git diff --name-only origin/${GITHUB_BASE_REF}...${GITHUB_SHA}); do + if [ -f ${i} ]; then + case ${i} in + *.c|*.cc|*.cpp|*.h) + if file ${i} | grep -qv BOM; then + echo "Missing BOM in ${i}" && ret=1; + fi + ;; + esac + fi + done + exit ${ret} diff --git a/3rdpart/ZLToolKit/.github/workflows/windows.yml b/3rdpart/ZLToolKit/.github/workflows/windows.yml new file mode 100644 index 0000000..017bbc7 --- /dev/null +++ b/3rdpart/ZLToolKit/.github/workflows/windows.yml @@ -0,0 +1,30 @@ +name: Windows + +on: [push, pull_request] + +jobs: + build: + runs-on: windows-2019 + + steps: + - uses: actions/checkout@v1 + with: + submodules: 'recursive' + fetch-depth: 1 + + - name: 配置 vcpkg + uses: lukka/run-vcpkg@v7 + with: + vcpkgDirectory: '${{github.workspace}}/vcpkg' + vcpkgTriplet: x64-windows-static + # 2021.05.12 + vcpkgGitCommitId: '5568f110b509a9fd90711978a7cb76bae75bb092' + vcpkgArguments: 'openssl' + + - name: 编译 + uses: lukka/run-cmake@v3 + with: + useVcpkgToolchainFile: true + buildDirectory: '${{github.workspace}}/build' + cmakeAppendedArgs: '' + cmakeBuildType: 'RelWithDebInfo' diff --git a/3rdpart/ZLToolKit/.gitignore b/3rdpart/ZLToolKit/.gitignore new file mode 100644 index 0000000..5a332f8 --- /dev/null +++ b/3rdpart/ZLToolKit/.gitignore @@ -0,0 +1,33 @@ +# Compiled Object files +*.slo +*.lo +*.o +*.obj +*.d + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +#*.dylib +#*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.lib + +# Executables +*.exe +*.out +*.app +/X64/ + +*.DS_Store +/cmake-build-debug/ +/.idea/ \ No newline at end of file diff --git a/3rdpart/ZLToolKit/.travis.yml b/3rdpart/ZLToolKit/.travis.yml new file mode 100644 index 0000000..d0c77d9 --- /dev/null +++ b/3rdpart/ZLToolKit/.travis.yml @@ -0,0 +1,13 @@ +language: cpp +sudo: required +dist: trusty +compiler: +- gcc +os: +- linux +before_install: +script: +- ./build_for_linux.sh + + + diff --git a/3rdpart/ZLToolKit/AUTHORS b/3rdpart/ZLToolKit/AUTHORS new file mode 100644 index 0000000..d2b518c --- /dev/null +++ b/3rdpart/ZLToolKit/AUTHORS @@ -0,0 +1,5 @@ +#代码贡献者列表,提交pr时请留下您的联系方式 +#Code contributor list, please leave your contact information when submitting a pull request + +xiongziliang <771730766@qq.com> +[清涩绿茶](https://github.com/baiyfcu) diff --git a/3rdpart/ZLToolKit/CMakeLists.txt b/3rdpart/ZLToolKit/CMakeLists.txt new file mode 100644 index 0000000..ba4b1b7 --- /dev/null +++ b/3rdpart/ZLToolKit/CMakeLists.txt @@ -0,0 +1,121 @@ +project(ZLToolKit) +cmake_minimum_required(VERSION 3.1.3) + +include(CheckStructHasMember) +include(CheckSymbolExists) + +list(APPEND CMAKE_REQUIRED_DEFINITIONS -D_GNU_SOURCE) +check_struct_has_member("struct mmsghdr" msg_hdr sys/socket.h HAVE_MMSG_HDR) +check_symbol_exists(sendmmsg sys/socket.h HAVE_SENDMMSG_API) +check_symbol_exists(recvmmsg sys/socket.h HAVE_RECVMMSG_API) + +if(HAVE_MMSG_HDR) + add_definitions(-DHAVE_MMSG_HDR) +endif() +if(HAVE_SENDMMSG_API) + add_definitions(-DHAVE_SENDMMSG_API) +endif() +if(HAVE_RECVMMSG_API) + add_definitions(-DHAVE_RECVMMSG_API) +endif() + + +#加载自定义模块 +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake") +#设置库文件路径 +set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib) +#设置可执行程序路径 +set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) +#设置子目录 +set(SUB_DIR_LIST "Network" "Poller" "Thread" "Util") + +if(WIN32) + list(APPEND SUB_DIR_LIST "win32") + #防止Windows.h包含Winsock.h + add_definitions(-DWIN32_LEAN_AND_MEAN) + set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) +endif() + +#安装目录 +if(WIN32) + set(INSTALL_PATH_LIB $ENV{HOME}/${PROJECT_NAME}/lib) + set(INSTALL_PATH_INCLUDE $ENV{HOME}/${PROJECT_NAME}/include) +else() + set(INSTALL_PATH_LIB lib) + set(INSTALL_PATH_INCLUDE include) +endif() + +foreach(SUB_DIR ${SUB_DIR_LIST}) + #遍历源文件 + aux_source_directory(src/${SUB_DIR} SRC_LIST) + #安装头文件至系统目录 + install(DIRECTORY src/${SUB_DIR} DESTINATION ${INSTALL_PATH_INCLUDE} FILES_MATCHING PATTERN "*.h") +endforeach() + +#非苹果平台移除.mm类型的文件 +if(NOT APPLE) + list(REMOVE_ITEM SRC_LIST "src/Network/Socket_ios.mm") +endif() + +if(WIN32) + set(LINK_LIB_LIST WS2_32 Iphlpapi shlwapi) +else() + set(LINK_LIB_LIST) +endif() + + +set(ENABLE_OPENSSL ON CACHE BOOL "enable openssl") +set(ENABLE_MYSQL ON CACHE BOOL "enable mysql") + + +#查找openssl是否安装 +find_package(OpenSSL QUIET) +if(OPENSSL_FOUND AND ENABLE_OPENSSL) + message(STATUS "找到openssl库:\"${OPENSSL_INCLUDE_DIR}\",ENABLE_OPENSSL宏已打开") + include_directories(${OPENSSL_INCLUDE_DIR}) + add_definitions(-DENABLE_OPENSSL) + list(APPEND LINK_LIB_LIST ${OPENSSL_LIBRARIES}) +endif() + +#查找mysql是否安装 +find_package(MYSQL QUIET) +if(MYSQL_FOUND AND ENABLE_MYSQL) + message(STATUS "找到mysqlclient库:\"${MYSQL_INCLUDE_DIR}\",ENABLE_MYSQL宏已打开") + include_directories(${MYSQL_INCLUDE_DIR}) + include_directories(${MYSQL_INCLUDE_DIR}/mysql) + add_definitions(-DENABLE_MYSQL) + list(APPEND LINK_LIB_LIST ${MYSQL_LIBRARIES}) +endif() + +#打印库文件 +message(STATUS "将链接依赖库:${LINK_LIB_LIST}") +#使能c++11 +set(CMAKE_CXX_STANDARD 11) + +if(NOT WIN32) + add_compile_options(-Wno-deprecated-declarations) + add_compile_options(-Wno-predefined-identifier-outside-function) +endif() + +#编译动态库 +if(NOT IOS AND NOT ANDROID AND NOT WIN32) + add_library(${PROJECT_NAME}_shared SHARED ${SRC_LIST}) + target_include_directories(${PROJECT_NAME}_shared PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src) + target_link_libraries(${PROJECT_NAME}_shared ${LINK_LIB_LIST}) + set_target_properties(${PROJECT_NAME}_shared PROPERTIES OUTPUT_NAME "${PROJECT_NAME}") + install(TARGETS ${PROJECT_NAME}_shared ARCHIVE DESTINATION ${INSTALL_PATH_LIB} LIBRARY DESTINATION ${INSTALL_PATH_LIB}) +endif() + +#编译静态库 +add_library(${PROJECT_NAME}_static STATIC ${SRC_LIST}) +#引用头文件路径 +target_include_directories(${PROJECT_NAME}_static PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src) +set_target_properties(${PROJECT_NAME}_static PROPERTIES OUTPUT_NAME "${PROJECT_NAME}") +#安装静态库至系统目录 +install(TARGETS ${PROJECT_NAME}_static ARCHIVE DESTINATION ${INSTALL_PATH_LIB}) + + +#测试程序 +if(NOT IOS) + add_subdirectory(tests) +endif() diff --git a/3rdpart/ZLToolKit/LICENSE b/3rdpart/ZLToolKit/LICENSE new file mode 100644 index 0000000..9a6eea4 --- /dev/null +++ b/3rdpart/ZLToolKit/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/3rdpart/ZLToolKit/README.md b/3rdpart/ZLToolKit/README.md new file mode 100644 index 0000000..53e1c8e --- /dev/null +++ b/3rdpart/ZLToolKit/README.md @@ -0,0 +1,130 @@ +# 一个基于C++11简单易用的轻量级网络编程框架 + +![](https://github.com/ZLMediaKit/ZLToolKit/actions/workflows/linux.yml/badge.svg) +![](https://github.com/ZLMediaKit/ZLToolKit/actions/workflows/macos.yml/badge.svg) +![](https://github.com/ZLMediaKit/ZLToolKit/actions/workflows/windows.yml/badge.svg) + +## 项目特点 +- 基于C++11开发,避免使用裸指针,代码稳定可靠;同时跨平台移植简单方便,代码清晰简洁。 +- 使用epoll+线程池+异步网络IO模式开发,并发性能优越。 +- 代码经过大量的稳定性、性能测试,可满足商用服务器项目。 +- 支持linux、macos、ios、android、windows平台 +- 了解更多:[ZLMediaKit](https://github.com/ZLMediaKit/ZLMediaKit) + +## 特性 +- 网络库 + - tcp/udp客户端,接口简单易用并且是线程安全的,用户不必关心具体的socket api操作。 + - tcp/udp服务器,使用非常简单,只要实现具体的tcp/udp会话(Session类)逻辑,使用模板的方式可以快速的构建高性能的服务器。 + - 对套接字多种操作的封装。 +- 线程库 + - 使用线程实现的简单易用的定时器。 + - 信号量。 + - 线程组。 + - 简单易用的线程池,可以异步或同步执行任务,支持functional 和 lambad表达式。 +- 工具库 + - 文件操作。 + - std::cout风格的日志库,支持颜色高亮、代码定位、异步打印。 + - INI配置文件的读写。 + - 监听者模式的消息广播器。 + - 基于智能指针的循环池,不需要显式手动释放。 + - 环形缓冲,支持主动读取和读取事件两种模式。 + - mysql链接池,使用占位符(?)方式生成sql语句,支持同步异步操作。 + - 简单易用的ssl加解密黑盒,支持多线程。 + - 其他一些有用的工具。 + - 命令行解析工具,可以很便捷的实现可配置应用程序 + +## 编译(Linux) +- 我的编译环境 + - Ubuntu16.04 64 bit + gcc5.4(最低gcc4.7) + - cmake 3.5.1 +- 编译 + + ``` + cd ZLToolKit + ./build_for_linux.sh + ``` + +## 编译(macOS) +- 我的编译环境 + - macOS Sierra(10.12.1) + xcode8.3.1 + - Homebrew 1.1.3 + - cmake 3.8.0 +- 编译 + + ``` + cd ZLToolKit + ./build_for_mac.sh + ``` + +## 编译(iOS) +- 编译环境:`请参考macOS的编译指导。` +- 编译 + + ``` + cd ZLToolKit + ./build_for_ios.sh + ``` +- 你也可以生成Xcode工程再编译: + + ``` + cd ZLToolKit + mkdir -p build + cd build + # 生成Xcode工程,工程文件在build目录下 + cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/iOS.cmake -DIOS_PLATFORM=SIMULATOR64 -G "Xcode" + ``` +## 编译(Android) +- 我的编译环境 + - macOS Sierra(10.12.1) + xcode8.3.1 + - Homebrew 1.1.3 + - cmake 3.8.0 + - [android-ndk-r14b](https://dl.google.com/android/repository/android-ndk-r14b-darwin-x86_64.zip) +- 编译 + + ``` + cd ZLToolKit + export ANDROID_NDK_ROOT=/path/to/ndk + ./build_for_android.sh + ``` +## 编译(Windows) +- 我的编译环境 + - windows 10 + - visual studio 2017 + - [openssl](http://slproweb.com/download/Win32OpenSSL-1_1_0f.exe) + - [mysqlclient](https://dev.mysql.com/downloads/file/?id=472430) + - [cmake-gui](https://cmake.org/files/v3.10/cmake-3.10.0-rc1-win32-x86.msi) + +- 编译 +``` +   1 使用cmake-gui打开工程并生成vs工程文件. +   2 找到工程文件(ZLToolKit.sln),双击用vs2017打开. +   3 选择编译Release 版本. +   4 依次编译 ZLToolKit_static、ZLToolKit_shared、ALL_BUILD、INSTALL. + 5 找到目标文件并运行测试用例. +   6 找到安装的头文件及库文件(在源码所在分区根目录). +``` +## 授权协议 + +本项目自有代码使用宽松的MIT协议,在保留版权信息的情况下可以自由应用于各自商用、非商业的项目。 +但是本项目也零碎的使用了一些其他的开源代码,在商用的情况下请自行替代或剔除; +由于使用本项目而产生的商业纠纷或侵权行为一概与本项项目及开发者无关,请自行承担法律风险。 + +## QA + - 该库性能怎么样? + +基于ZLToolKit,我实现了一个流媒体服务器[ZLMediaKit](https://github.com/ZLMediaKit/ZLMediaKit);作者已经对其进行了性能测试,可以查看[benchmark.md](https://github.com/ZLMediaKit/ZLMediaKit/blob/master/benchmark.md)了解详情。 + + - 该库稳定性怎么样? + +该库经过作者严格的valgrind测试,长时间大负荷的测试;作者也使用该库进行了多个线上项目的开发。实践证明该库稳定性很好;可以无看门狗脚本的方式连续运行几个月。 + + - 在windows下编译很多错误? + + 由于本项目主体代码在macOS/linux下开发,部分源码采用的是无bom头的UTF-8编码;由于windows对于utf-8支持不甚友好,所以如果发现编译错误请先尝试添加bom头再编译。 + + +## 联系方式 +- 邮箱:<1213642868@qq.com>(本项目相关或网络编程相关问题请走issue流程,否则恕不邮件答复) +- QQ群:542509000 + + diff --git a/3rdpart/ZLToolKit/build_for_android.sh b/3rdpart/ZLToolKit/build_for_android.sh new file mode 100644 index 0000000..d426cc4 --- /dev/null +++ b/3rdpart/ZLToolKit/build_for_android.sh @@ -0,0 +1,13 @@ +#!/bin/bash +ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" +brew install cmake +cd .. +git clone --depth=50 https://github.com/xia-chu/ZLToolKit.git +cd ZLToolKit +mkdir -p android_build +rm -rf ./build +ln -s ./android_build ./build +cd android_build +cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/android.toolchain.cmake -DANDROID_NDK=$ANDROID_NDK_ROOT -DCMAKE_BUILD_TYPE=Release -DANDROID_ABI="armeabi" -DANDROID_NATIVE_API_LEVEL=android-9 +make -j4 +sudo make install diff --git a/3rdpart/ZLToolKit/build_for_ios.sh b/3rdpart/ZLToolKit/build_for_ios.sh new file mode 100644 index 0000000..423521a --- /dev/null +++ b/3rdpart/ZLToolKit/build_for_ios.sh @@ -0,0 +1,13 @@ +#!/bin/bash +ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" +brew install cmake +cd .. +git clone --depth=50 https://github.com/xia-chu/ZLToolKit.git +cd ZLToolKit +mkdir -p ios_build +rm -rf ./build +ln -s ./ios_build ./build +cd ios_build +cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/iOS.cmake -DIOS_PLATFORM=OS +make -j4 +sudo make install diff --git a/3rdpart/ZLToolKit/build_for_linux.sh b/3rdpart/ZLToolKit/build_for_linux.sh new file mode 100644 index 0000000..c03e754 --- /dev/null +++ b/3rdpart/ZLToolKit/build_for_linux.sh @@ -0,0 +1,14 @@ +#!/bin/bash +sudo apt-get install cmake +sudo apt-get install libmysqlclient-dev +sudo apt-get install libssl-dev +cd .. +git clone --depth=50 https://github.com/xia-chu/ZLToolKit.git +cd ZLToolKit +mkdir -p linux_build +rm -rf ./build +ln -s ./linux_build ./build +cd linux_build +cmake .. +make -j4 +sudo make install diff --git a/3rdpart/ZLToolKit/build_for_mac.sh b/3rdpart/ZLToolKit/build_for_mac.sh new file mode 100644 index 0000000..f455383 --- /dev/null +++ b/3rdpart/ZLToolKit/build_for_mac.sh @@ -0,0 +1,15 @@ +#!/bin/bash +ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" +brew install cmake +brew install mysql +brew install openssl +cd .. +git clone --depth=50 https://github.com/xia-chu/ZLToolKit.git +cd ZLToolKit +mkdir -p mac_build +rm -rf ./build +ln -s ./mac_build ./build +cd mac_build +cmake .. -DOPENSSL_ROOT_DIR=/usr/local/Cellar/openssl/1.0.2j/ +make -j4 +sudo make install diff --git a/3rdpart/ZLToolKit/cmake/AndroidNdkGdb.cmake b/3rdpart/ZLToolKit/cmake/AndroidNdkGdb.cmake new file mode 100644 index 0000000..0677dcd --- /dev/null +++ b/3rdpart/ZLToolKit/cmake/AndroidNdkGdb.cmake @@ -0,0 +1,96 @@ +# Copyright (c) 2014, Pavel Rojtberg +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +# ------------------------------------------------------------------------------ +# Usage: +# 1. place AndroidNdkGdb.cmake somewhere inside ${CMAKE_MODULE_PATH} +# 2. inside your project add +# +# include(AndroidNdkGdb) +# android_ndk_gdb_enable() +# # for each target +# add_library(MyLibrary ...) +# android_ndk_gdb_debuggable(MyLibrary) + + +# add gdbserver and general gdb configuration to project +# also create a mininal NDK skeleton so ndk-gdb finds the paths +# +# the optional parameter defines the path to the android project. +# uses PROJECT_SOURCE_DIR by default. +macro(android_ndk_gdb_enable) + if(ANDROID) + # create custom target that depends on the real target so it gets executed afterwards + add_custom_target(NDK_GDB ALL) + + if(${ARGC}) + set(ANDROID_PROJECT_DIR ${ARGV0}) + else() + set(ANDROID_PROJECT_DIR ${PROJECT_SOURCE_DIR}) + endif() + + set(NDK_GDB_SOLIB_PATH ${ANDROID_PROJECT_DIR}/obj/local/${ANDROID_NDK_ABI_NAME}/) + file(MAKE_DIRECTORY ${NDK_GDB_SOLIB_PATH}) + + # 1. generate essential Android Makefiles + file(MAKE_DIRECTORY ${ANDROID_PROJECT_DIR}/jni) + if(NOT EXISTS ${ANDROID_PROJECT_DIR}/jni/Android.mk) + file(WRITE ${ANDROID_PROJECT_DIR}/jni/Android.mk "APP_ABI := ${ANDROID_NDK_ABI_NAME}\n") + endif() + if(NOT EXISTS ${ANDROID_PROJECT_DIR}/jni/Application.mk) + file(WRITE ${ANDROID_PROJECT_DIR}/jni/Application.mk "APP_ABI := ${ANDROID_NDK_ABI_NAME}\n") + endif() + + # 2. generate gdb.setup + get_directory_property(PROJECT_INCLUDES DIRECTORY ${PROJECT_SOURCE_DIR} INCLUDE_DIRECTORIES) + string(REGEX REPLACE ";" " " PROJECT_INCLUDES "${PROJECT_INCLUDES}") + file(WRITE ${LIBRARY_OUTPUT_PATH}/gdb.setup "set solib-search-path ${NDK_GDB_SOLIB_PATH}\n") + file(APPEND ${LIBRARY_OUTPUT_PATH}/gdb.setup "directory ${PROJECT_INCLUDES}\n") + + # 3. copy gdbserver executable + file(COPY ${ANDROID_NDK}/prebuilt/android-${ANDROID_ARCH_NAME}/gdbserver/gdbserver DESTINATION ${LIBRARY_OUTPUT_PATH}) + endif() +endmacro() + +# register a target for remote debugging +# copies the debug version to NDK_GDB_SOLIB_PATH then strips symbols of original +macro(android_ndk_gdb_debuggable TARGET_NAME) + if(ANDROID) + get_property(TARGET_LOCATION TARGET ${TARGET_NAME} PROPERTY LOCATION) + + # create custom target that depends on the real target so it gets executed afterwards + add_dependencies(NDK_GDB ${TARGET_NAME}) + + # 4. copy lib to obj + add_custom_command(TARGET NDK_GDB POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${TARGET_LOCATION} ${NDK_GDB_SOLIB_PATH}) + + # 5. strip symbols + add_custom_command(TARGET NDK_GDB POST_BUILD COMMAND ${CMAKE_STRIP} ${TARGET_LOCATION}) + endif() +endmacro() diff --git a/3rdpart/ZLToolKit/cmake/AndroidNdkModules.cmake b/3rdpart/ZLToolKit/cmake/AndroidNdkModules.cmake new file mode 100644 index 0000000..64f37fd --- /dev/null +++ b/3rdpart/ZLToolKit/cmake/AndroidNdkModules.cmake @@ -0,0 +1,58 @@ +# Copyright (c) 2014, Pavel Rojtberg +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +macro(android_ndk_import_module_cpufeatures) + if(ANDROID) + include_directories(${ANDROID_NDK}/sources/android/cpufeatures) + add_library(cpufeatures ${ANDROID_NDK}/sources/android/cpufeatures/cpu-features.c) + target_link_libraries(cpufeatures dl) + endif() +endmacro() + +macro(android_ndk_import_module_native_app_glue) + if(ANDROID) + include_directories(${ANDROID_NDK}/sources/android/native_app_glue) + add_library(native_app_glue ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c) + target_link_libraries(native_app_glue log) + endif() +endmacro() + +macro(android_ndk_import_module_ndk_helper) + if(ANDROID) + android_ndk_import_module_cpufeatures() + android_ndk_import_module_native_app_glue() + + include_directories(${ANDROID_NDK}/sources/android/ndk_helper) + file(GLOB _NDK_HELPER_SRCS ${ANDROID_NDK}/sources/android/ndk_helper/*.cpp ${ANDROID_NDK}/sources/android/ndk_helper/gl3stub.c) + add_library(ndk_helper ${_NDK_HELPER_SRCS}) + target_link_libraries(ndk_helper log android EGL GLESv2 cpufeatures native_app_glue) + + unset(_NDK_HELPER_SRCS) + endif() +endmacro() \ No newline at end of file diff --git a/3rdpart/ZLToolKit/cmake/FindMYSQL.cmake b/3rdpart/ZLToolKit/cmake/FindMYSQL.cmake new file mode 100644 index 0000000..6eebbad --- /dev/null +++ b/3rdpart/ZLToolKit/cmake/FindMYSQL.cmake @@ -0,0 +1,132 @@ +# - Try to find MySQL / MySQL Embedded library +# Find the MySQL includes and client library +# This module defines +# MYSQL_INCLUDE_DIR, where to find mysql.h +# MYSQL_LIBRARIES, the libraries needed to use MySQL. +# MYSQL_LIB_DIR, path to the MYSQL_LIBRARIES +# MYSQL_EMBEDDED_LIBRARIES, the libraries needed to use MySQL Embedded. +# MYSQL_EMBEDDED_LIB_DIR, path to the MYSQL_EMBEDDED_LIBRARIES +# MYSQL_FOUND, If false, do not try to use MySQL. +# MYSQL_EMBEDDED_FOUND, If false, do not try to use MySQL Embedded. + +# Copyright (c) 2006-2008, Jarosław Staniek +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +include(CheckCXXSourceCompiles) + +if(WIN32) + find_path(MYSQL_INCLUDE_DIR mysql.h + PATHS + $ENV{MYSQL_INCLUDE_DIR} + $ENV{MYSQL_DIR}/include + $ENV{ProgramFiles}/MySQL/*/include + $ENV{SystemDrive}/MySQL/*/include + $ENV{ProgramW6432}/MySQL/*/include + ) +else(WIN32) + #在Mac OS下, mysql.h的文件可能不是在mysql目录下 + #可能存在/usr/local/mysql/include/mysql.h + find_path(MYSQL_INCLUDE_DIR mysql.h + PATHS + $ENV{MYSQL_INCLUDE_DIR} + $ENV{MYSQL_DIR}/include + /usr/local/mysql/include + /usr/local/mysql/include/mysql + /opt/mysql/mysql/include + PATH_SUFFIXES + mysql + ) +endif(WIN32) + +if(WIN32) + if (${CMAKE_BUILD_TYPE}) + string(TOLOWER ${CMAKE_BUILD_TYPE} CMAKE_BUILD_TYPE_TOLOWER) + endif() + + # path suffix for debug/release mode + # binary_dist: mysql binary distribution + # build_dist: custom build + if(CMAKE_BUILD_TYPE_TOLOWER MATCHES "debug") + set(binary_dist debug) + set(build_dist Debug) + else(CMAKE_BUILD_TYPE_TOLOWER MATCHES "debug") + ADD_DEFINITIONS(-DDBUG_OFF) + set(binary_dist opt) + set(build_dist Release) + endif(CMAKE_BUILD_TYPE_TOLOWER MATCHES "debug") + +# find_library(MYSQL_LIBRARIES NAMES mysqlclient + set(MYSQL_LIB_PATHS + $ENV{MYSQL_DIR}/lib/${binary_dist} + $ENV{MYSQL_DIR}/libmysql/${build_dist} + $ENV{MYSQL_DIR}/client/${build_dist} + $ENV{ProgramFiles}/MySQL/*/lib/${binary_dist} + $ENV{SystemDrive}/MySQL/*/lib/${binary_dist} + $ENV{MYSQL_DIR}/lib/opt + $ENV{MYSQL_DIR}/client/release + $ENV{ProgramFiles}/MySQL/*/lib/opt + $ENV{ProgramFiles}/MySQL/*/lib/ + $ENV{SystemDrive}/MySQL/*/lib/opt + $ENV{ProgramW6432}/MySQL/*/lib + ) + find_library(MYSQL_LIBRARIES NAMES libmysql + PATHS + ${MYSQL_LIB_PATHS} + ) +else(WIN32) +# find_library(MYSQL_LIBRARIES NAMES mysqlclient + set(MYSQL_LIB_PATHS + $ENV{MYSQL_DIR}/libmysql_r/.libs + $ENV{MYSQL_DIR}/lib + $ENV{MYSQL_DIR}/lib/mysql + /usr/local/mysql/lib + /opt/mysql/mysql/lib + $ENV{MYSQL_DIR}/libmysql_r/.libs + $ENV{MYSQL_DIR}/lib + $ENV{MYSQL_DIR}/lib/mysql + /usr/local/mysql/lib + /opt/mysql/mysql/lib + PATH_SUFFIXES + mysql + ) + find_library(MYSQL_LIBRARIES NAMES mysqlclient + PATHS + ${MYSQL_LIB_PATHS} + ) +endif(WIN32) + +find_library(MYSQL_EMBEDDED_LIBRARIES NAMES mysqld + PATHS + ${MYSQL_LIB_PATHS} +) + +if(MYSQL_LIBRARIES) + get_filename_component(MYSQL_LIB_DIR ${MYSQL_LIBRARIES} PATH) +endif(MYSQL_LIBRARIES) + +if(MYSQL_EMBEDDED_LIBRARIES) + get_filename_component(MYSQL_EMBEDDED_LIB_DIR ${MYSQL_EMBEDDED_LIBRARIES} PATH) +endif(MYSQL_EMBEDDED_LIBRARIES) + +set( CMAKE_REQUIRED_INCLUDES ${MYSQL_INCLUDE_DIR} ) +set( CMAKE_REQUIRED_LIBRARIES ${MYSQL_EMBEDDED_LIBRARIES} ) +check_cxx_source_compiles( "#include \nint main() { int i = MYSQL_OPT_USE_EMBEDDED_CONNECTION; }" HAVE_MYSQL_OPT_EMBEDDED_CONNECTION ) +if(MYSQL_INCLUDE_DIR AND MYSQL_LIBRARIES) + set(MYSQL_FOUND TRUE) + message(STATUS "Found MySQL: ${MYSQL_INCLUDE_DIR}, ${MYSQL_LIBRARIES}") +else(MYSQL_INCLUDE_DIR AND MYSQL_LIBRARIES) + set(MYSQL_FOUND FALSE) + message(STATUS "MySQL not found.") +endif(MYSQL_INCLUDE_DIR AND MYSQL_LIBRARIES) + +if(MYSQL_INCLUDE_DIR AND MYSQL_EMBEDDED_LIBRARIES AND HAVE_MYSQL_OPT_EMBEDDED_CONNECTION) + set(MYSQL_EMBEDDED_FOUND TRUE) + message(STATUS "Found MySQL Embedded: ${MYSQL_INCLUDE_DIR}, ${MYSQL_EMBEDDED_LIBRARIES}") +else(MYSQL_INCLUDE_DIR AND MYSQL_EMBEDDED_LIBRARIES AND HAVE_MYSQL_OPT_EMBEDDED_CONNECTION) + set(MYSQL_EMBEDDED_FOUND FALSE) + message(STATUS "MySQL Embedded not found.") +endif(MYSQL_INCLUDE_DIR AND MYSQL_EMBEDDED_LIBRARIES AND HAVE_MYSQL_OPT_EMBEDDED_CONNECTION) + +mark_as_advanced(MYSQL_INCLUDE_DIR MYSQL_LIBRARIES MYSQL_EMBEDDED_LIBRARIES) diff --git a/3rdpart/ZLToolKit/cmake/android.toolchain.cmake b/3rdpart/ZLToolKit/cmake/android.toolchain.cmake new file mode 100644 index 0000000..ffa2612 --- /dev/null +++ b/3rdpart/ZLToolKit/cmake/android.toolchain.cmake @@ -0,0 +1,1693 @@ +# Copyright (c) 2010-2011, Ethan Rublee +# Copyright (c) 2011-2014, Andrey Kamaev +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +# ------------------------------------------------------------------------------ +# Android CMake toolchain file, for use with the Android NDK r5-r10d +# Requires cmake 2.6.3 or newer (2.8.9 or newer is recommended). +# See home page: https://github.com/taka-no-me/android-cmake +# +# Usage Linux: +# $ export ANDROID_NDK=/absolute/path/to/the/android-ndk +# $ mkdir build && cd build +# $ cmake -DCMAKE_TOOLCHAIN_FILE=path/to/the/android.toolchain.cmake .. +# $ make -j8 +# +# Usage Windows: +# You need native port of make to build your project. +# Android NDK r7 (and newer) already has make.exe on board. +# For older NDK you have to install it separately. +# For example, this one: http://gnuwin32.sourceforge.net/packages/make.htm +# +# $ SET ANDROID_NDK=C:\absolute\path\to\the\android-ndk +# $ mkdir build && cd build +# $ cmake.exe -G"MinGW Makefiles" +# -DCMAKE_TOOLCHAIN_FILE=path\to\the\android.toolchain.cmake +# -DCMAKE_MAKE_PROGRAM="%ANDROID_NDK%\prebuilt\windows\bin\make.exe" .. +# $ cmake.exe --build . +# +# +# Options (can be set as cmake parameters: -D=): +# ANDROID_NDK=/opt/android-ndk - path to the NDK root. +# Can be set as environment variable. Can be set only at first cmake run. +# +# ANDROID_ABI=armeabi-v7a - specifies the target Application Binary +# Interface (ABI). This option nearly matches to the APP_ABI variable +# used by ndk-build tool from Android NDK. +# +# Possible targets are: +# "armeabi" - ARMv5TE based CPU with software floating point operations +# "armeabi-v7a" - ARMv7 based devices with hardware FPU instructions +# this ABI target is used by default +# "armeabi-v7a with NEON" - same as armeabi-v7a, but +# sets NEON as floating-point unit +# "armeabi-v7a with VFPV3" - same as armeabi-v7a, but +# sets VFPV3 as floating-point unit (has 32 registers instead of 16) +# "armeabi-v6 with VFP" - tuned for ARMv6 processors having VFP +# "x86" - IA-32 instruction set +# "mips" - MIPS32 instruction set +# +# 64-bit ABIs for NDK r10 and newer: +# "arm64-v8a" - ARMv8 AArch64 instruction set +# "x86_64" - Intel64 instruction set (r1) +# "mips64" - MIPS64 instruction set (r6) +# +# ANDROID_NATIVE_API_LEVEL=android-8 - level of Android API compile for. +# Option is read-only when standalone toolchain is used. +# Note: building for "android-L" requires explicit configuration. +# +# ANDROID_TOOLCHAIN_NAME=arm-linux-androideabi-4.9 - the name of compiler +# toolchain to be used. The list of possible values depends on the NDK +# version. For NDK r10c the possible values are: +# +# * aarch64-linux-android-4.9 +# * aarch64-linux-android-clang3.4 +# * aarch64-linux-android-clang3.5 +# * arm-linux-androideabi-4.6 +# * arm-linux-androideabi-4.8 +# * arm-linux-androideabi-4.9 (default) +# * arm-linux-androideabi-clang3.4 +# * arm-linux-androideabi-clang3.5 +# * mips64el-linux-android-4.9 +# * mips64el-linux-android-clang3.4 +# * mips64el-linux-android-clang3.5 +# * mipsel-linux-android-4.6 +# * mipsel-linux-android-4.8 +# * mipsel-linux-android-4.9 +# * mipsel-linux-android-clang3.4 +# * mipsel-linux-android-clang3.5 +# * x86-4.6 +# * x86-4.8 +# * x86-4.9 +# * x86-clang3.4 +# * x86-clang3.5 +# * x86_64-4.9 +# * x86_64-clang3.4 +# * x86_64-clang3.5 +# +# ANDROID_FORCE_ARM_BUILD=OFF - set ON to generate 32-bit ARM instructions +# instead of Thumb. Is not available for "armeabi-v6 with VFP" +# (is forced to be ON) ABI. +# +# ANDROID_NO_UNDEFINED=ON - set ON to show all undefined symbols as linker +# errors even if they are not used. +# +# ANDROID_SO_UNDEFINED=OFF - set ON to allow undefined symbols in shared +# libraries. Automatically turned for NDK r5x and r6x due to GLESv2 +# problems. +# +# ANDROID_STL=gnustl_static - specify the runtime to use. +# +# Possible values are: +# none -> Do not configure the runtime. +# system -> Use the default minimal system C++ runtime library. +# Implies -fno-rtti -fno-exceptions. +# Is not available for standalone toolchain. +# system_re -> Use the default minimal system C++ runtime library. +# Implies -frtti -fexceptions. +# Is not available for standalone toolchain. +# gabi++_static -> Use the GAbi++ runtime as a static library. +# Implies -frtti -fno-exceptions. +# Available for NDK r7 and newer. +# Is not available for standalone toolchain. +# gabi++_shared -> Use the GAbi++ runtime as a shared library. +# Implies -frtti -fno-exceptions. +# Available for NDK r7 and newer. +# Is not available for standalone toolchain. +# stlport_static -> Use the STLport runtime as a static library. +# Implies -fno-rtti -fno-exceptions for NDK before r7. +# Implies -frtti -fno-exceptions for NDK r7 and newer. +# Is not available for standalone toolchain. +# stlport_shared -> Use the STLport runtime as a shared library. +# Implies -fno-rtti -fno-exceptions for NDK before r7. +# Implies -frtti -fno-exceptions for NDK r7 and newer. +# Is not available for standalone toolchain. +# gnustl_static -> Use the GNU STL as a static library. +# Implies -frtti -fexceptions. +# gnustl_shared -> Use the GNU STL as a shared library. +# Implies -frtti -fno-exceptions. +# Available for NDK r7b and newer. +# Silently degrades to gnustl_static if not available. +# +# ANDROID_STL_FORCE_FEATURES=ON - turn rtti and exceptions support based on +# chosen runtime. If disabled, then the user is responsible for settings +# these options. +# +# What?: +# android-cmake toolchain searches for NDK/toolchain in the following order: +# ANDROID_NDK - cmake parameter +# ANDROID_NDK - environment variable +# ANDROID_STANDALONE_TOOLCHAIN - cmake parameter +# ANDROID_STANDALONE_TOOLCHAIN - environment variable +# ANDROID_NDK - default locations +# ANDROID_STANDALONE_TOOLCHAIN - default locations +# +# Make sure to do the following in your scripts: +# SET( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${my_cxx_flags}" ) +# SET( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${my_cxx_flags}" ) +# The flags will be prepopulated with critical flags, so don't loose them. +# Also be aware that toolchain also sets configuration-specific compiler +# flags and linker flags. +# +# ANDROID and BUILD_ANDROID will be set to true, you may test any of these +# variables to make necessary Android-specific configuration changes. +# +# Also ARMEABI or ARMEABI_V7A or X86 or MIPS or ARM64_V8A or X86_64 or MIPS64 +# will be set true, mutually exclusive. NEON option will be set true +# if VFP is set to NEON. +# +# ------------------------------------------------------------------------------ + +cmake_minimum_required( VERSION 2.6.3 ) + +if( DEFINED CMAKE_CROSSCOMPILING ) + # subsequent toolchain loading is not really needed + return() +endif() + +if( CMAKE_TOOLCHAIN_FILE ) + # touch toolchain variable to suppress "unused variable" warning +endif() + +# inherit settings in recursive loads +get_property( _CMAKE_IN_TRY_COMPILE GLOBAL PROPERTY IN_TRY_COMPILE ) +if( _CMAKE_IN_TRY_COMPILE ) + include( "${CMAKE_CURRENT_SOURCE_DIR}/../android.toolchain.config.cmake" OPTIONAL ) +endif() + +# this one is important +if( CMAKE_VERSION VERSION_GREATER "3.0.99" ) + set( CMAKE_SYSTEM_NAME Android ) +else() + set( CMAKE_SYSTEM_NAME Linux ) +endif() + +# this one not so much +set( CMAKE_SYSTEM_VERSION 1 ) + +# rpath makes low sense for Android +set( CMAKE_SHARED_LIBRARY_RUNTIME_C_FLAG "" ) +set( CMAKE_SKIP_RPATH TRUE CACHE BOOL "If set, runtime paths are not added when using shared libraries." ) + +# NDK search paths +set( ANDROID_SUPPORTED_NDK_VERSIONS ${ANDROID_EXTRA_NDK_VERSIONS} -r10d -r10c -r10b -r10 -r9d -r9c -r9b -r9 -r8e -r8d -r8c -r8b -r8 -r7c -r7b -r7 -r6b -r6 -r5c -r5b -r5 "" ) +if( NOT DEFINED ANDROID_NDK_SEARCH_PATHS ) + if( CMAKE_HOST_WIN32 ) + file( TO_CMAKE_PATH "$ENV{PROGRAMFILES}" ANDROID_NDK_SEARCH_PATHS ) + set( ANDROID_NDK_SEARCH_PATHS "${ANDROID_NDK_SEARCH_PATHS}" "$ENV{SystemDrive}/NVPACK" ) + else() + file( TO_CMAKE_PATH "$ENV{HOME}" ANDROID_NDK_SEARCH_PATHS ) + set( ANDROID_NDK_SEARCH_PATHS /opt "${ANDROID_NDK_SEARCH_PATHS}/NVPACK" ) + endif() +endif() +if( NOT DEFINED ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH ) + set( ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH /opt/android-toolchain ) +endif() + +# known ABIs +set( ANDROID_SUPPORTED_ABIS_arm "armeabi-v7a;armeabi;armeabi-v7a with NEON;armeabi-v7a with VFPV3;armeabi-v6 with VFP" ) +set( ANDROID_SUPPORTED_ABIS_arm64 "arm64-v8a" ) +set( ANDROID_SUPPORTED_ABIS_x86 "x86" ) +set( ANDROID_SUPPORTED_ABIS_x86_64 "x86_64" ) +set( ANDROID_SUPPORTED_ABIS_mips "mips" ) +set( ANDROID_SUPPORTED_ABIS_mips64 "mips64" ) + +# API level defaults +set( ANDROID_DEFAULT_NDK_API_LEVEL 8 ) +set( ANDROID_DEFAULT_NDK_API_LEVEL_arm64 21 ) +set( ANDROID_DEFAULT_NDK_API_LEVEL_x86 9 ) +set( ANDROID_DEFAULT_NDK_API_LEVEL_x86_64 21 ) +set( ANDROID_DEFAULT_NDK_API_LEVEL_mips 9 ) +set( ANDROID_DEFAULT_NDK_API_LEVEL_mips64 21 ) + + +macro( __LIST_FILTER listvar regex ) + if( ${listvar} ) + foreach( __val ${${listvar}} ) + if( __val MATCHES "${regex}" ) + list( REMOVE_ITEM ${listvar} "${__val}" ) + endif() + endforeach() + endif() +endmacro() + +macro( __INIT_VARIABLE var_name ) + set( __test_path 0 ) + foreach( __var ${ARGN} ) + if( __var STREQUAL "PATH" ) + set( __test_path 1 ) + break() + endif() + endforeach() + + if( __test_path AND NOT EXISTS "${${var_name}}" ) + unset( ${var_name} CACHE ) + endif() + + if( " ${${var_name}}" STREQUAL " " ) + set( __values 0 ) + foreach( __var ${ARGN} ) + if( __var STREQUAL "VALUES" ) + set( __values 1 ) + elseif( NOT __var STREQUAL "PATH" ) + if( __var MATCHES "^ENV_.*$" ) + string( REPLACE "ENV_" "" __var "${__var}" ) + set( __value "$ENV{${__var}}" ) + elseif( DEFINED ${__var} ) + set( __value "${${__var}}" ) + elseif( __values ) + set( __value "${__var}" ) + else() + set( __value "" ) + endif() + + if( NOT " ${__value}" STREQUAL " " AND (NOT __test_path OR EXISTS "${__value}") ) + set( ${var_name} "${__value}" ) + break() + endif() + endif() + endforeach() + unset( __value ) + unset( __values ) + endif() + + if( __test_path ) + file( TO_CMAKE_PATH "${${var_name}}" ${var_name} ) + endif() + unset( __test_path ) +endmacro() + +macro( __DETECT_NATIVE_API_LEVEL _var _path ) + set( __ndkApiLevelRegex "^[\t ]*#define[\t ]+__ANDROID_API__[\t ]+([0-9]+)[\t ]*.*$" ) + file( STRINGS ${_path} __apiFileContent REGEX "${__ndkApiLevelRegex}" ) + if( NOT __apiFileContent ) + message( SEND_ERROR "Could not get Android native API level. Probably you have specified invalid level value, or your copy of NDK/toolchain is broken." ) + endif() + string( REGEX REPLACE "${__ndkApiLevelRegex}" "\\1" ${_var} "${__apiFileContent}" ) + unset( __apiFileContent ) + unset( __ndkApiLevelRegex ) +endmacro() + +macro( __DETECT_TOOLCHAIN_MACHINE_NAME _var _root ) + if( EXISTS "${_root}" ) + file( GLOB __gccExePath RELATIVE "${_root}/bin/" "${_root}/bin/*-gcc${TOOL_OS_SUFFIX}" ) + __LIST_FILTER( __gccExePath "^[.].*" ) + list( LENGTH __gccExePath __gccExePathsCount ) + if( NOT __gccExePathsCount EQUAL 1 AND NOT _CMAKE_IN_TRY_COMPILE ) + message( WARNING "Could not determine machine name for compiler from ${_root}" ) + set( ${_var} "" ) + else() + get_filename_component( __gccExeName "${__gccExePath}" NAME_WE ) + string( REPLACE "-gcc" "" ${_var} "${__gccExeName}" ) + endif() + unset( __gccExePath ) + unset( __gccExePathsCount ) + unset( __gccExeName ) + else() + set( ${_var} "" ) + endif() +endmacro() + + +# fight against cygwin +set( ANDROID_FORBID_SYGWIN TRUE CACHE BOOL "Prevent cmake from working under cygwin and using cygwin tools") +mark_as_advanced( ANDROID_FORBID_SYGWIN ) +if( ANDROID_FORBID_SYGWIN ) + if( CYGWIN ) + message( FATAL_ERROR "Android NDK and android-cmake toolchain are not welcome Cygwin. It is unlikely that this cmake toolchain will work under cygwin. But if you want to try then you can set cmake variable ANDROID_FORBID_SYGWIN to FALSE and rerun cmake." ) + endif() + + if( CMAKE_HOST_WIN32 ) + # remove cygwin from PATH + set( __new_path "$ENV{PATH}") + __LIST_FILTER( __new_path "cygwin" ) + set(ENV{PATH} "${__new_path}") + unset(__new_path) + endif() +endif() + + +# detect current host platform +if( NOT DEFINED ANDROID_NDK_HOST_X64 AND (CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "amd64|x86_64|AMD64" OR CMAKE_HOST_APPLE) ) + set( ANDROID_NDK_HOST_X64 1 CACHE BOOL "Try to use 64-bit compiler toolchain" ) + mark_as_advanced( ANDROID_NDK_HOST_X64 ) +endif() + +set( TOOL_OS_SUFFIX "" ) +if( CMAKE_HOST_APPLE ) + set( ANDROID_NDK_HOST_SYSTEM_NAME "darwin-x86_64" ) + set( ANDROID_NDK_HOST_SYSTEM_NAME2 "darwin-x86" ) +elseif( CMAKE_HOST_WIN32 ) + set( ANDROID_NDK_HOST_SYSTEM_NAME "windows-x86_64" ) + set( ANDROID_NDK_HOST_SYSTEM_NAME2 "windows" ) + set( TOOL_OS_SUFFIX ".exe" ) +elseif( CMAKE_HOST_UNIX ) + set( ANDROID_NDK_HOST_SYSTEM_NAME "linux-x86_64" ) + set( ANDROID_NDK_HOST_SYSTEM_NAME2 "linux-x86" ) +else() + message( FATAL_ERROR "Cross-compilation on your platform is not supported by this cmake toolchain" ) +endif() + +if( NOT ANDROID_NDK_HOST_X64 ) + set( ANDROID_NDK_HOST_SYSTEM_NAME ${ANDROID_NDK_HOST_SYSTEM_NAME2} ) +endif() + +# see if we have path to Android NDK +if( NOT ANDROID_NDK AND NOT ANDROID_STANDALONE_TOOLCHAIN ) + __INIT_VARIABLE( ANDROID_NDK PATH ENV_ANDROID_NDK ) +endif() +if( NOT ANDROID_NDK ) + # see if we have path to Android standalone toolchain + __INIT_VARIABLE( ANDROID_STANDALONE_TOOLCHAIN PATH ENV_ANDROID_STANDALONE_TOOLCHAIN ) + + if( NOT ANDROID_STANDALONE_TOOLCHAIN ) + #try to find Android NDK in one of the the default locations + set( __ndkSearchPaths ) + foreach( __ndkSearchPath ${ANDROID_NDK_SEARCH_PATHS} ) + foreach( suffix ${ANDROID_SUPPORTED_NDK_VERSIONS} ) + list( APPEND __ndkSearchPaths "${__ndkSearchPath}/android-ndk${suffix}" ) + endforeach() + endforeach() + __INIT_VARIABLE( ANDROID_NDK PATH VALUES ${__ndkSearchPaths} ) + unset( __ndkSearchPaths ) + + if( ANDROID_NDK ) + message( STATUS "Using default path for Android NDK: ${ANDROID_NDK}" ) + message( STATUS " If you prefer to use a different location, please define a cmake or environment variable: ANDROID_NDK" ) + else() + #try to find Android standalone toolchain in one of the the default locations + __INIT_VARIABLE( ANDROID_STANDALONE_TOOLCHAIN PATH ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH ) + + if( ANDROID_STANDALONE_TOOLCHAIN ) + message( STATUS "Using default path for standalone toolchain ${ANDROID_STANDALONE_TOOLCHAIN}" ) + message( STATUS " If you prefer to use a different location, please define the variable: ANDROID_STANDALONE_TOOLCHAIN" ) + endif( ANDROID_STANDALONE_TOOLCHAIN ) + endif( ANDROID_NDK ) + endif( NOT ANDROID_STANDALONE_TOOLCHAIN ) +endif( NOT ANDROID_NDK ) + +# remember found paths +if( ANDROID_NDK ) + get_filename_component( ANDROID_NDK "${ANDROID_NDK}" ABSOLUTE ) + set( ANDROID_NDK "${ANDROID_NDK}" CACHE INTERNAL "Path of the Android NDK" FORCE ) + set( BUILD_WITH_ANDROID_NDK True ) + if( EXISTS "${ANDROID_NDK}/RELEASE.TXT" ) + file( STRINGS "${ANDROID_NDK}/RELEASE.TXT" ANDROID_NDK_RELEASE_FULL LIMIT_COUNT 1 REGEX "r[0-9]+[a-z]?" ) + string( REGEX MATCH "r([0-9]+)([a-z]?)" ANDROID_NDK_RELEASE "${ANDROID_NDK_RELEASE_FULL}" ) + else() + set( ANDROID_NDK_RELEASE "r1x" ) + set( ANDROID_NDK_RELEASE_FULL "unreleased" ) + endif() + string( REGEX REPLACE "r([0-9]+)([a-z]?)" "\\1*1000" ANDROID_NDK_RELEASE_NUM "${ANDROID_NDK_RELEASE}" ) + string( FIND " abcdefghijklmnopqastuvwxyz" "${CMAKE_MATCH_2}" __ndkReleaseLetterNum ) + math( EXPR ANDROID_NDK_RELEASE_NUM "${ANDROID_NDK_RELEASE_NUM}+${__ndkReleaseLetterNum}" ) +elseif( ANDROID_STANDALONE_TOOLCHAIN ) + get_filename_component( ANDROID_STANDALONE_TOOLCHAIN "${ANDROID_STANDALONE_TOOLCHAIN}" ABSOLUTE ) + # try to detect change + if( CMAKE_AR ) + string( LENGTH "${ANDROID_STANDALONE_TOOLCHAIN}" __length ) + string( SUBSTRING "${CMAKE_AR}" 0 ${__length} __androidStandaloneToolchainPreviousPath ) + if( NOT __androidStandaloneToolchainPreviousPath STREQUAL ANDROID_STANDALONE_TOOLCHAIN ) + message( FATAL_ERROR "It is not possible to change path to the Android standalone toolchain on subsequent run." ) + endif() + unset( __androidStandaloneToolchainPreviousPath ) + unset( __length ) + endif() + set( ANDROID_STANDALONE_TOOLCHAIN "${ANDROID_STANDALONE_TOOLCHAIN}" CACHE INTERNAL "Path of the Android standalone toolchain" FORCE ) + set( BUILD_WITH_STANDALONE_TOOLCHAIN True ) +else() + list(GET ANDROID_NDK_SEARCH_PATHS 0 ANDROID_NDK_SEARCH_PATH) + message( FATAL_ERROR "Could not find neither Android NDK nor Android standalone toolchain. + You should either set an environment variable: + export ANDROID_NDK=~/my-android-ndk + or + export ANDROID_STANDALONE_TOOLCHAIN=~/my-android-toolchain + or put the toolchain or NDK in the default path: + sudo ln -s ~/my-android-ndk ${ANDROID_NDK_SEARCH_PATH}/android-ndk + sudo ln -s ~/my-android-toolchain ${ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH}" ) +endif() + +# android NDK layout +if( BUILD_WITH_ANDROID_NDK ) + if( NOT DEFINED ANDROID_NDK_LAYOUT ) + # try to automatically detect the layout + if( EXISTS "${ANDROID_NDK}/RELEASE.TXT") + set( ANDROID_NDK_LAYOUT "RELEASE" ) + elseif( EXISTS "${ANDROID_NDK}/../../linux-x86/toolchain/" ) + set( ANDROID_NDK_LAYOUT "LINARO" ) + elseif( EXISTS "${ANDROID_NDK}/../../gcc/" ) + set( ANDROID_NDK_LAYOUT "ANDROID" ) + endif() + endif() + set( ANDROID_NDK_LAYOUT "${ANDROID_NDK_LAYOUT}" CACHE STRING "The inner layout of NDK" ) + mark_as_advanced( ANDROID_NDK_LAYOUT ) + if( ANDROID_NDK_LAYOUT STREQUAL "LINARO" ) + set( ANDROID_NDK_HOST_SYSTEM_NAME ${ANDROID_NDK_HOST_SYSTEM_NAME2} ) # only 32-bit at the moment + set( ANDROID_NDK_TOOLCHAINS_PATH "${ANDROID_NDK}/../../${ANDROID_NDK_HOST_SYSTEM_NAME}/toolchain" ) + set( ANDROID_NDK_TOOLCHAINS_SUBPATH "" ) + set( ANDROID_NDK_TOOLCHAINS_SUBPATH2 "" ) + elseif( ANDROID_NDK_LAYOUT STREQUAL "ANDROID" ) + set( ANDROID_NDK_HOST_SYSTEM_NAME ${ANDROID_NDK_HOST_SYSTEM_NAME2} ) # only 32-bit at the moment + set( ANDROID_NDK_TOOLCHAINS_PATH "${ANDROID_NDK}/../../gcc/${ANDROID_NDK_HOST_SYSTEM_NAME}/arm" ) + set( ANDROID_NDK_TOOLCHAINS_SUBPATH "" ) + set( ANDROID_NDK_TOOLCHAINS_SUBPATH2 "" ) + else() # ANDROID_NDK_LAYOUT STREQUAL "RELEASE" + set( ANDROID_NDK_TOOLCHAINS_PATH "${ANDROID_NDK}/toolchains" ) + set( ANDROID_NDK_TOOLCHAINS_SUBPATH "/prebuilt/${ANDROID_NDK_HOST_SYSTEM_NAME}" ) + set( ANDROID_NDK_TOOLCHAINS_SUBPATH2 "/prebuilt/${ANDROID_NDK_HOST_SYSTEM_NAME2}" ) + endif() + get_filename_component( ANDROID_NDK_TOOLCHAINS_PATH "${ANDROID_NDK_TOOLCHAINS_PATH}" ABSOLUTE ) + + # try to detect change of NDK + if( CMAKE_AR ) + string( LENGTH "${ANDROID_NDK_TOOLCHAINS_PATH}" __length ) + string( SUBSTRING "${CMAKE_AR}" 0 ${__length} __androidNdkPreviousPath ) + if( NOT __androidNdkPreviousPath STREQUAL ANDROID_NDK_TOOLCHAINS_PATH ) + message( FATAL_ERROR "It is not possible to change the path to the NDK on subsequent CMake run. You must remove all generated files from your build folder first. + " ) + endif() + unset( __androidNdkPreviousPath ) + unset( __length ) + endif() +endif() + + +# get all the details about standalone toolchain +if( BUILD_WITH_STANDALONE_TOOLCHAIN ) + __DETECT_NATIVE_API_LEVEL( ANDROID_SUPPORTED_NATIVE_API_LEVELS "${ANDROID_STANDALONE_TOOLCHAIN}/sysroot/usr/include/android/api-level.h" ) + set( ANDROID_STANDALONE_TOOLCHAIN_API_LEVEL ${ANDROID_SUPPORTED_NATIVE_API_LEVELS} ) + set( __availableToolchains "standalone" ) + __DETECT_TOOLCHAIN_MACHINE_NAME( __availableToolchainMachines "${ANDROID_STANDALONE_TOOLCHAIN}" ) + if( NOT __availableToolchainMachines ) + message( FATAL_ERROR "Could not determine machine name of your toolchain. Probably your Android standalone toolchain is broken." ) + endif() + if( __availableToolchainMachines MATCHES x86_64 ) + set( __availableToolchainArchs "x86_64" ) + elseif( __availableToolchainMachines MATCHES i686 ) + set( __availableToolchainArchs "x86" ) + elseif( __availableToolchainMachines MATCHES aarch64 ) + set( __availableToolchainArchs "arm64" ) + elseif( __availableToolchainMachines MATCHES arm ) + set( __availableToolchainArchs "arm" ) + elseif( __availableToolchainMachines MATCHES mips64el ) + set( __availableToolchainArchs "mips64" ) + elseif( __availableToolchainMachines MATCHES mipsel ) + set( __availableToolchainArchs "mips" ) + endif() + execute_process( COMMAND "${ANDROID_STANDALONE_TOOLCHAIN}/bin/${__availableToolchainMachines}-gcc${TOOL_OS_SUFFIX}" -dumpversion + OUTPUT_VARIABLE __availableToolchainCompilerVersions OUTPUT_STRIP_TRAILING_WHITESPACE ) + string( REGEX MATCH "[0-9]+[.][0-9]+([.][0-9]+)?" __availableToolchainCompilerVersions "${__availableToolchainCompilerVersions}" ) + if( EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/bin/clang${TOOL_OS_SUFFIX}" ) + list( APPEND __availableToolchains "standalone-clang" ) + list( APPEND __availableToolchainMachines ${__availableToolchainMachines} ) + list( APPEND __availableToolchainArchs ${__availableToolchainArchs} ) + list( APPEND __availableToolchainCompilerVersions ${__availableToolchainCompilerVersions} ) + endif() +endif() + +macro( __GLOB_NDK_TOOLCHAINS __availableToolchainsVar __availableToolchainsLst __toolchain_subpath ) + foreach( __toolchain ${${__availableToolchainsLst}} ) + if( "${__toolchain}" MATCHES "-clang3[.][0-9]$" AND NOT EXISTS "${ANDROID_NDK_TOOLCHAINS_PATH}/${__toolchain}${__toolchain_subpath}" ) + SET( __toolchainVersionRegex "^TOOLCHAIN_VERSION[\t ]+:=[\t ]+(.*)$" ) + FILE( STRINGS "${ANDROID_NDK_TOOLCHAINS_PATH}/${__toolchain}/setup.mk" __toolchainVersionStr REGEX "${__toolchainVersionRegex}" ) + if( __toolchainVersionStr ) + string( REGEX REPLACE "${__toolchainVersionRegex}" "\\1" __toolchainVersionStr "${__toolchainVersionStr}" ) + string( REGEX REPLACE "-clang3[.][0-9]$" "-${__toolchainVersionStr}" __gcc_toolchain "${__toolchain}" ) + else() + string( REGEX REPLACE "-clang3[.][0-9]$" "-4.6" __gcc_toolchain "${__toolchain}" ) + endif() + unset( __toolchainVersionStr ) + unset( __toolchainVersionRegex ) + else() + set( __gcc_toolchain "${__toolchain}" ) + endif() + __DETECT_TOOLCHAIN_MACHINE_NAME( __machine "${ANDROID_NDK_TOOLCHAINS_PATH}/${__gcc_toolchain}${__toolchain_subpath}" ) + if( __machine ) + string( REGEX MATCH "[0-9]+[.][0-9]+([.][0-9x]+)?$" __version "${__gcc_toolchain}" ) + if( __machine MATCHES x86_64 ) + set( __arch "x86_64" ) + elseif( __machine MATCHES i686 ) + set( __arch "x86" ) + elseif( __machine MATCHES aarch64 ) + set( __arch "arm64" ) + elseif( __machine MATCHES arm ) + set( __arch "arm" ) + elseif( __machine MATCHES mips64el ) + set( __arch "mips64" ) + elseif( __machine MATCHES mipsel ) + set( __arch "mips" ) + else() + set( __arch "" ) + endif() + #message("machine: !${__machine}!\narch: !${__arch}!\nversion: !${__version}!\ntoolchain: !${__toolchain}!\n") + if (__arch) + list( APPEND __availableToolchainMachines "${__machine}" ) + list( APPEND __availableToolchainArchs "${__arch}" ) + list( APPEND __availableToolchainCompilerVersions "${__version}" ) + list( APPEND ${__availableToolchainsVar} "${__toolchain}" ) + endif() + endif() + unset( __gcc_toolchain ) + endforeach() +endmacro() + +# get all the details about NDK +if( BUILD_WITH_ANDROID_NDK ) + file( GLOB ANDROID_SUPPORTED_NATIVE_API_LEVELS RELATIVE "${ANDROID_NDK}/platforms" "${ANDROID_NDK}/platforms/android-*" ) + string( REPLACE "android-" "" ANDROID_SUPPORTED_NATIVE_API_LEVELS "${ANDROID_SUPPORTED_NATIVE_API_LEVELS}" ) + set( __availableToolchains "" ) + set( __availableToolchainMachines "" ) + set( __availableToolchainArchs "" ) + set( __availableToolchainCompilerVersions "" ) + if( ANDROID_TOOLCHAIN_NAME AND EXISTS "${ANDROID_NDK_TOOLCHAINS_PATH}/${ANDROID_TOOLCHAIN_NAME}/" ) + # do not go through all toolchains if we know the name + set( __availableToolchainsLst "${ANDROID_TOOLCHAIN_NAME}" ) + __GLOB_NDK_TOOLCHAINS( __availableToolchains __availableToolchainsLst "${ANDROID_NDK_TOOLCHAINS_SUBPATH}" ) + if( NOT __availableToolchains AND NOT ANDROID_NDK_TOOLCHAINS_SUBPATH STREQUAL ANDROID_NDK_TOOLCHAINS_SUBPATH2 ) + __GLOB_NDK_TOOLCHAINS( __availableToolchains __availableToolchainsLst "${ANDROID_NDK_TOOLCHAINS_SUBPATH2}" ) + if( __availableToolchains ) + set( ANDROID_NDK_TOOLCHAINS_SUBPATH ${ANDROID_NDK_TOOLCHAINS_SUBPATH2} ) + endif() + endif() + endif() + if( NOT __availableToolchains ) + file( GLOB __availableToolchainsLst RELATIVE "${ANDROID_NDK_TOOLCHAINS_PATH}" "${ANDROID_NDK_TOOLCHAINS_PATH}/*" ) + if( __availableToolchainsLst ) + list(SORT __availableToolchainsLst) # we need clang to go after gcc + endif() + __LIST_FILTER( __availableToolchainsLst "^[.]" ) + __LIST_FILTER( __availableToolchainsLst "llvm" ) + __LIST_FILTER( __availableToolchainsLst "renderscript" ) + __GLOB_NDK_TOOLCHAINS( __availableToolchains __availableToolchainsLst "${ANDROID_NDK_TOOLCHAINS_SUBPATH}" ) + if( NOT __availableToolchains AND NOT ANDROID_NDK_TOOLCHAINS_SUBPATH STREQUAL ANDROID_NDK_TOOLCHAINS_SUBPATH2 ) + __GLOB_NDK_TOOLCHAINS( __availableToolchains __availableToolchainsLst "${ANDROID_NDK_TOOLCHAINS_SUBPATH2}" ) + if( __availableToolchains ) + set( ANDROID_NDK_TOOLCHAINS_SUBPATH ${ANDROID_NDK_TOOLCHAINS_SUBPATH2} ) + endif() + endif() + endif() + if( NOT __availableToolchains ) + message( FATAL_ERROR "Could not find any working toolchain in the NDK. Probably your Android NDK is broken." ) + endif() +endif() + +# build list of available ABIs +set( ANDROID_SUPPORTED_ABIS "" ) +set( __uniqToolchainArchNames ${__availableToolchainArchs} ) +list( REMOVE_DUPLICATES __uniqToolchainArchNames ) +list( SORT __uniqToolchainArchNames ) +foreach( __arch ${__uniqToolchainArchNames} ) + list( APPEND ANDROID_SUPPORTED_ABIS ${ANDROID_SUPPORTED_ABIS_${__arch}} ) +endforeach() +unset( __uniqToolchainArchNames ) +if( NOT ANDROID_SUPPORTED_ABIS ) + message( FATAL_ERROR "No one of known Android ABIs is supported by this cmake toolchain." ) +endif() + +# choose target ABI +__INIT_VARIABLE( ANDROID_ABI VALUES ${ANDROID_SUPPORTED_ABIS} ) +# verify that target ABI is supported +list( FIND ANDROID_SUPPORTED_ABIS "${ANDROID_ABI}" __androidAbiIdx ) +if( __androidAbiIdx EQUAL -1 ) + string( REPLACE ";" "\", \"" PRINTABLE_ANDROID_SUPPORTED_ABIS "${ANDROID_SUPPORTED_ABIS}" ) + message( FATAL_ERROR "Specified ANDROID_ABI = \"${ANDROID_ABI}\" is not supported by this cmake toolchain or your NDK/toolchain. + Supported values are: \"${PRINTABLE_ANDROID_SUPPORTED_ABIS}\" + " ) +endif() +unset( __androidAbiIdx ) + +# set target ABI options +if( ANDROID_ABI STREQUAL "x86" ) + set( X86 true ) + set( ANDROID_NDK_ABI_NAME "x86" ) + set( ANDROID_ARCH_NAME "x86" ) + set( ANDROID_LLVM_TRIPLE "i686-none-linux-android" ) + set( CMAKE_SYSTEM_PROCESSOR "i686" ) +elseif( ANDROID_ABI STREQUAL "x86_64" ) + set( X86 true ) + set( X86_64 true ) + set( ANDROID_NDK_ABI_NAME "x86_64" ) + set( ANDROID_ARCH_NAME "x86_64" ) + set( CMAKE_SYSTEM_PROCESSOR "x86_64" ) + set( ANDROID_LLVM_TRIPLE "x86_64-none-linux-android" ) +elseif( ANDROID_ABI STREQUAL "mips64" ) + set( MIPS64 true ) + set( ANDROID_NDK_ABI_NAME "mips64" ) + set( ANDROID_ARCH_NAME "mips64" ) + set( ANDROID_LLVM_TRIPLE "mips64el-none-linux-android" ) + set( CMAKE_SYSTEM_PROCESSOR "mips64" ) +elseif( ANDROID_ABI STREQUAL "mips" ) + set( MIPS true ) + set( ANDROID_NDK_ABI_NAME "mips" ) + set( ANDROID_ARCH_NAME "mips" ) + set( ANDROID_LLVM_TRIPLE "mipsel-none-linux-android" ) + set( CMAKE_SYSTEM_PROCESSOR "mips" ) +elseif( ANDROID_ABI STREQUAL "arm64-v8a" ) + set( ARM64_V8A true ) + set( ANDROID_NDK_ABI_NAME "arm64-v8a" ) + set( ANDROID_ARCH_NAME "arm64" ) + set( ANDROID_LLVM_TRIPLE "aarch64-none-linux-android" ) + set( CMAKE_SYSTEM_PROCESSOR "aarch64" ) + set( VFPV3 true ) + set( NEON true ) +elseif( ANDROID_ABI STREQUAL "armeabi" ) + set( ARMEABI true ) + set( ANDROID_NDK_ABI_NAME "armeabi" ) + set( ANDROID_ARCH_NAME "arm" ) + set( ANDROID_LLVM_TRIPLE "armv5te-none-linux-androideabi" ) + set( CMAKE_SYSTEM_PROCESSOR "armv5te" ) +elseif( ANDROID_ABI STREQUAL "armeabi-v6 with VFP" ) + set( ARMEABI_V6 true ) + set( ANDROID_NDK_ABI_NAME "armeabi" ) + set( ANDROID_ARCH_NAME "arm" ) + set( ANDROID_LLVM_TRIPLE "armv5te-none-linux-androideabi" ) + set( CMAKE_SYSTEM_PROCESSOR "armv6" ) + # need always fallback to older platform + set( ARMEABI true ) +elseif( ANDROID_ABI STREQUAL "armeabi-v7a") + set( ARMEABI_V7A true ) + set( ANDROID_NDK_ABI_NAME "armeabi-v7a" ) + set( ANDROID_ARCH_NAME "arm" ) + set( ANDROID_LLVM_TRIPLE "armv7-none-linux-androideabi" ) + set( CMAKE_SYSTEM_PROCESSOR "armv7-a" ) +elseif( ANDROID_ABI STREQUAL "armeabi-v7a with VFPV3" ) + set( ARMEABI_V7A true ) + set( ANDROID_NDK_ABI_NAME "armeabi-v7a" ) + set( ANDROID_ARCH_NAME "arm" ) + set( ANDROID_LLVM_TRIPLE "armv7-none-linux-androideabi" ) + set( CMAKE_SYSTEM_PROCESSOR "armv7-a" ) + set( VFPV3 true ) +elseif( ANDROID_ABI STREQUAL "armeabi-v7a with NEON" ) + set( ARMEABI_V7A true ) + set( ANDROID_NDK_ABI_NAME "armeabi-v7a" ) + set( ANDROID_ARCH_NAME "arm" ) + set( ANDROID_LLVM_TRIPLE "armv7-none-linux-androideabi" ) + set( CMAKE_SYSTEM_PROCESSOR "armv7-a" ) + set( VFPV3 true ) + set( NEON true ) +else() + message( SEND_ERROR "Unknown ANDROID_ABI=\"${ANDROID_ABI}\" is specified." ) +endif() + +if( CMAKE_BINARY_DIR AND EXISTS "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeSystem.cmake" ) + # really dirty hack + # it is not possible to change CMAKE_SYSTEM_PROCESSOR after the first run... + file( APPEND "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeSystem.cmake" "SET(CMAKE_SYSTEM_PROCESSOR \"${CMAKE_SYSTEM_PROCESSOR}\")\n" ) +endif() + +if( ANDROID_ARCH_NAME STREQUAL "arm" AND NOT ARMEABI_V6 ) + __INIT_VARIABLE( ANDROID_FORCE_ARM_BUILD VALUES OFF ) + set( ANDROID_FORCE_ARM_BUILD ${ANDROID_FORCE_ARM_BUILD} CACHE BOOL "Use 32-bit ARM instructions instead of Thumb-1" FORCE ) + mark_as_advanced( ANDROID_FORCE_ARM_BUILD ) +else() + unset( ANDROID_FORCE_ARM_BUILD CACHE ) +endif() + +# choose toolchain +if( ANDROID_TOOLCHAIN_NAME ) + list( FIND __availableToolchains "${ANDROID_TOOLCHAIN_NAME}" __toolchainIdx ) + if( __toolchainIdx EQUAL -1 ) + list( SORT __availableToolchains ) + string( REPLACE ";" "\n * " toolchains_list "${__availableToolchains}" ) + set( toolchains_list " * ${toolchains_list}") + message( FATAL_ERROR "Specified toolchain \"${ANDROID_TOOLCHAIN_NAME}\" is missing in your NDK or broken. Please verify that your NDK is working or select another compiler toolchain. +To configure the toolchain set CMake variable ANDROID_TOOLCHAIN_NAME to one of the following values:\n${toolchains_list}\n" ) + endif() + list( GET __availableToolchainArchs ${__toolchainIdx} __toolchainArch ) + if( NOT __toolchainArch STREQUAL ANDROID_ARCH_NAME ) + message( SEND_ERROR "Selected toolchain \"${ANDROID_TOOLCHAIN_NAME}\" is not able to compile binaries for the \"${ANDROID_ARCH_NAME}\" platform." ) + endif() +else() + set( __toolchainIdx -1 ) + set( __applicableToolchains "" ) + set( __toolchainMaxVersion "0.0.0" ) + list( LENGTH __availableToolchains __availableToolchainsCount ) + math( EXPR __availableToolchainsCount "${__availableToolchainsCount}-1" ) + foreach( __idx RANGE ${__availableToolchainsCount} ) + list( GET __availableToolchainArchs ${__idx} __toolchainArch ) + if( __toolchainArch STREQUAL ANDROID_ARCH_NAME ) + list( GET __availableToolchainCompilerVersions ${__idx} __toolchainVersion ) + string( REPLACE "x" "99" __toolchainVersion "${__toolchainVersion}") + if( __toolchainVersion VERSION_GREATER __toolchainMaxVersion ) + set( __toolchainMaxVersion "${__toolchainVersion}" ) + set( __toolchainIdx ${__idx} ) + endif() + endif() + endforeach() + unset( __availableToolchainsCount ) + unset( __toolchainMaxVersion ) + unset( __toolchainVersion ) +endif() +unset( __toolchainArch ) +if( __toolchainIdx EQUAL -1 ) + message( FATAL_ERROR "No one of available compiler toolchains is able to compile for ${ANDROID_ARCH_NAME} platform." ) +endif() +list( GET __availableToolchains ${__toolchainIdx} ANDROID_TOOLCHAIN_NAME ) +list( GET __availableToolchainMachines ${__toolchainIdx} ANDROID_TOOLCHAIN_MACHINE_NAME ) +list( GET __availableToolchainCompilerVersions ${__toolchainIdx} ANDROID_COMPILER_VERSION ) + +unset( __toolchainIdx ) +unset( __availableToolchains ) +unset( __availableToolchainMachines ) +unset( __availableToolchainArchs ) +unset( __availableToolchainCompilerVersions ) + +# choose native API level +__INIT_VARIABLE( ANDROID_NATIVE_API_LEVEL ENV_ANDROID_NATIVE_API_LEVEL ANDROID_API_LEVEL ENV_ANDROID_API_LEVEL ANDROID_STANDALONE_TOOLCHAIN_API_LEVEL ANDROID_DEFAULT_NDK_API_LEVEL_${ANDROID_ARCH_NAME} ANDROID_DEFAULT_NDK_API_LEVEL ) +string( REPLACE "android-" "" ANDROID_NATIVE_API_LEVEL "${ANDROID_NATIVE_API_LEVEL}" ) +string( STRIP "${ANDROID_NATIVE_API_LEVEL}" ANDROID_NATIVE_API_LEVEL ) +# adjust API level +set( __real_api_level ${ANDROID_DEFAULT_NDK_API_LEVEL_${ANDROID_ARCH_NAME}} ) +foreach( __level ${ANDROID_SUPPORTED_NATIVE_API_LEVELS} ) + if( (__level LESS ANDROID_NATIVE_API_LEVEL OR __level STREQUAL ANDROID_NATIVE_API_LEVEL) AND NOT __level LESS __real_api_level ) + set( __real_api_level ${__level} ) + endif() +endforeach() +if( __real_api_level AND NOT ANDROID_NATIVE_API_LEVEL STREQUAL __real_api_level ) + message( STATUS "Adjusting Android API level 'android-${ANDROID_NATIVE_API_LEVEL}' to 'android-${__real_api_level}'") + set( ANDROID_NATIVE_API_LEVEL ${__real_api_level} ) +endif() +unset(__real_api_level) +# validate +list( FIND ANDROID_SUPPORTED_NATIVE_API_LEVELS "${ANDROID_NATIVE_API_LEVEL}" __levelIdx ) +if( __levelIdx EQUAL -1 ) + message( SEND_ERROR "Specified Android native API level 'android-${ANDROID_NATIVE_API_LEVEL}' is not supported by your NDK/toolchain." ) +else() + if( BUILD_WITH_ANDROID_NDK ) + __DETECT_NATIVE_API_LEVEL( __realApiLevel "${ANDROID_NDK}/platforms/android-${ANDROID_NATIVE_API_LEVEL}/arch-${ANDROID_ARCH_NAME}/usr/include/android/api-level.h" ) + if( NOT __realApiLevel EQUAL ANDROID_NATIVE_API_LEVEL AND NOT __realApiLevel GREATER 9000 ) + message( SEND_ERROR "Specified Android API level (${ANDROID_NATIVE_API_LEVEL}) does not match to the level found (${__realApiLevel}). Probably your copy of NDK is broken." ) + endif() + unset( __realApiLevel ) + endif() + set( ANDROID_NATIVE_API_LEVEL "${ANDROID_NATIVE_API_LEVEL}" CACHE STRING "Android API level for native code" FORCE ) + set( CMAKE_ANDROID_API ${ANDROID_NATIVE_API_LEVEL} ) + if( CMAKE_VERSION VERSION_GREATER "2.8" ) + list( SORT ANDROID_SUPPORTED_NATIVE_API_LEVELS ) + set_property( CACHE ANDROID_NATIVE_API_LEVEL PROPERTY STRINGS ${ANDROID_SUPPORTED_NATIVE_API_LEVELS} ) + endif() +endif() +unset( __levelIdx ) + + +# remember target ABI +set( ANDROID_ABI "${ANDROID_ABI}" CACHE STRING "The target ABI for Android. If arm, then armeabi-v7a is recommended for hardware floating point." FORCE ) +if( CMAKE_VERSION VERSION_GREATER "2.8" ) + list( SORT ANDROID_SUPPORTED_ABIS_${ANDROID_ARCH_NAME} ) + set_property( CACHE ANDROID_ABI PROPERTY STRINGS ${ANDROID_SUPPORTED_ABIS_${ANDROID_ARCH_NAME}} ) +endif() + + +# runtime choice (STL, rtti, exceptions) +if( NOT ANDROID_STL ) + set( ANDROID_STL gnustl_static ) +endif() +set( ANDROID_STL "${ANDROID_STL}" CACHE STRING "C++ runtime" ) +set( ANDROID_STL_FORCE_FEATURES ON CACHE BOOL "automatically configure rtti and exceptions support based on C++ runtime" ) +mark_as_advanced( ANDROID_STL ANDROID_STL_FORCE_FEATURES ) + +if( BUILD_WITH_ANDROID_NDK ) + if( NOT "${ANDROID_STL}" MATCHES "^(none|system|system_re|gabi\\+\\+_static|gabi\\+\\+_shared|stlport_static|stlport_shared|gnustl_static|gnustl_shared)$") + message( FATAL_ERROR "ANDROID_STL is set to invalid value \"${ANDROID_STL}\". +The possible values are: + none -> Do not configure the runtime. + system -> Use the default minimal system C++ runtime library. + system_re -> Same as system but with rtti and exceptions. + gabi++_static -> Use the GAbi++ runtime as a static library. + gabi++_shared -> Use the GAbi++ runtime as a shared library. + stlport_static -> Use the STLport runtime as a static library. + stlport_shared -> Use the STLport runtime as a shared library. + gnustl_static -> (default) Use the GNU STL as a static library. + gnustl_shared -> Use the GNU STL as a shared library. +" ) + endif() +elseif( BUILD_WITH_STANDALONE_TOOLCHAIN ) + if( NOT "${ANDROID_STL}" MATCHES "^(none|gnustl_static|gnustl_shared)$") + message( FATAL_ERROR "ANDROID_STL is set to invalid value \"${ANDROID_STL}\". +The possible values are: + none -> Do not configure the runtime. + gnustl_static -> (default) Use the GNU STL as a static library. + gnustl_shared -> Use the GNU STL as a shared library. +" ) + endif() +endif() + +unset( ANDROID_RTTI ) +unset( ANDROID_EXCEPTIONS ) +unset( ANDROID_STL_INCLUDE_DIRS ) +unset( __libstl ) +unset( __libsupcxx ) + +if( NOT _CMAKE_IN_TRY_COMPILE AND ANDROID_NDK_RELEASE STREQUAL "r7b" AND ARMEABI_V7A AND NOT VFPV3 AND ANDROID_STL MATCHES "gnustl" ) + message( WARNING "The GNU STL armeabi-v7a binaries from NDK r7b can crash non-NEON devices. The files provided with NDK r7b were not configured properly, resulting in crashes on Tegra2-based devices and others when trying to use certain floating-point functions (e.g., cosf, sinf, expf). +You are strongly recommended to switch to another NDK release. +" ) +endif() + +if( NOT _CMAKE_IN_TRY_COMPILE AND X86 AND ANDROID_STL MATCHES "gnustl" AND ANDROID_NDK_RELEASE STREQUAL "r6" ) + message( WARNING "The x86 system header file from NDK r6 has incorrect definition for ptrdiff_t. You are recommended to upgrade to a newer NDK release or manually patch the header: +See https://android.googlesource.com/platform/development.git f907f4f9d4e56ccc8093df6fee54454b8bcab6c2 + diff --git a/ndk/platforms/android-9/arch-x86/include/machine/_types.h b/ndk/platforms/android-9/arch-x86/include/machine/_types.h + index 5e28c64..65892a1 100644 + --- a/ndk/platforms/android-9/arch-x86/include/machine/_types.h + +++ b/ndk/platforms/android-9/arch-x86/include/machine/_types.h + @@ -51,7 +51,11 @@ typedef long int ssize_t; + #endif + #ifndef _PTRDIFF_T + #define _PTRDIFF_T + -typedef long ptrdiff_t; + +# ifdef __ANDROID__ + + typedef int ptrdiff_t; + +# else + + typedef long ptrdiff_t; + +# endif + #endif +" ) +endif() + + +# setup paths and STL for standalone toolchain +if( BUILD_WITH_STANDALONE_TOOLCHAIN ) + set( ANDROID_TOOLCHAIN_ROOT "${ANDROID_STANDALONE_TOOLCHAIN}" ) + set( ANDROID_CLANG_TOOLCHAIN_ROOT "${ANDROID_STANDALONE_TOOLCHAIN}" ) + set( ANDROID_SYSROOT "${ANDROID_STANDALONE_TOOLCHAIN}/sysroot" ) + + if( NOT ANDROID_STL STREQUAL "none" ) + set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_STANDALONE_TOOLCHAIN}/include/c++/${ANDROID_COMPILER_VERSION}" ) + if( NOT EXISTS "${ANDROID_STL_INCLUDE_DIRS}" ) + # old location ( pre r8c ) + set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/include/c++/${ANDROID_COMPILER_VERSION}" ) + endif() + if( ARMEABI_V7A AND EXISTS "${ANDROID_STL_INCLUDE_DIRS}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/${CMAKE_SYSTEM_PROCESSOR}/bits" ) + list( APPEND ANDROID_STL_INCLUDE_DIRS "${ANDROID_STL_INCLUDE_DIRS}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/${CMAKE_SYSTEM_PROCESSOR}" ) + elseif( ARMEABI AND NOT ANDROID_FORCE_ARM_BUILD AND EXISTS "${ANDROID_STL_INCLUDE_DIRS}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/thumb/bits" ) + list( APPEND ANDROID_STL_INCLUDE_DIRS "${ANDROID_STL_INCLUDE_DIRS}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/thumb" ) + else() + list( APPEND ANDROID_STL_INCLUDE_DIRS "${ANDROID_STL_INCLUDE_DIRS}/${ANDROID_TOOLCHAIN_MACHINE_NAME}" ) + endif() + # always search static GNU STL to get the location of libsupc++.a + if( ARMEABI_V7A AND NOT ANDROID_FORCE_ARM_BUILD AND EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/thumb/libstdc++.a" ) + set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/thumb" ) + elseif( ARMEABI_V7A AND EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/libstdc++.a" ) + set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}" ) + elseif( ARMEABI AND NOT ANDROID_FORCE_ARM_BUILD AND EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/thumb/libstdc++.a" ) + set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/thumb" ) + elseif( EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/libstdc++.a" ) + set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib" ) + endif() + if( __libstl ) + set( __libsupcxx "${__libstl}/libsupc++.a" ) + set( __libstl "${__libstl}/libstdc++.a" ) + endif() + if( NOT EXISTS "${__libsupcxx}" ) + message( FATAL_ERROR "The required libstdsupc++.a is missing in your standalone toolchain. + Usually it happens because of bug in make-standalone-toolchain.sh script from NDK r7, r7b and r7c. + You need to either upgrade to newer NDK or manually copy + $ANDROID_NDK/sources/cxx-stl/gnu-libstdc++/libs/${ANDROID_NDK_ABI_NAME}/libsupc++.a + to + ${__libsupcxx} + " ) + endif() + if( ANDROID_STL STREQUAL "gnustl_shared" ) + if( ARMEABI_V7A AND EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/libgnustl_shared.so" ) + set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/libgnustl_shared.so" ) + elseif( ARMEABI AND NOT ANDROID_FORCE_ARM_BUILD AND EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/thumb/libgnustl_shared.so" ) + set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/thumb/libgnustl_shared.so" ) + elseif( EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/libgnustl_shared.so" ) + set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/libgnustl_shared.so" ) + endif() + endif() + endif() +endif() + +# clang +if( "${ANDROID_TOOLCHAIN_NAME}" STREQUAL "standalone-clang" ) + set( ANDROID_COMPILER_IS_CLANG 1 ) + execute_process( COMMAND "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/clang${TOOL_OS_SUFFIX}" --version OUTPUT_VARIABLE ANDROID_CLANG_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE ) + string( REGEX MATCH "[0-9]+[.][0-9]+" ANDROID_CLANG_VERSION "${ANDROID_CLANG_VERSION}") +elseif( "${ANDROID_TOOLCHAIN_NAME}" MATCHES "-clang3[.][0-9]?$" ) + string( REGEX MATCH "3[.][0-9]$" ANDROID_CLANG_VERSION "${ANDROID_TOOLCHAIN_NAME}") + string( REGEX REPLACE "-clang${ANDROID_CLANG_VERSION}$" "-${ANDROID_COMPILER_VERSION}" ANDROID_GCC_TOOLCHAIN_NAME "${ANDROID_TOOLCHAIN_NAME}" ) + if( NOT EXISTS "${ANDROID_NDK_TOOLCHAINS_PATH}/llvm-${ANDROID_CLANG_VERSION}${ANDROID_NDK_TOOLCHAINS_SUBPATH}/bin/clang${TOOL_OS_SUFFIX}" ) + message( FATAL_ERROR "Could not find the Clang compiler driver" ) + endif() + set( ANDROID_COMPILER_IS_CLANG 1 ) + set( ANDROID_CLANG_TOOLCHAIN_ROOT "${ANDROID_NDK_TOOLCHAINS_PATH}/llvm-${ANDROID_CLANG_VERSION}${ANDROID_NDK_TOOLCHAINS_SUBPATH}" ) +else() + set( ANDROID_GCC_TOOLCHAIN_NAME "${ANDROID_TOOLCHAIN_NAME}" ) + unset( ANDROID_COMPILER_IS_CLANG CACHE ) +endif() + +string( REPLACE "." "" _clang_name "clang${ANDROID_CLANG_VERSION}" ) +if( NOT EXISTS "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/${_clang_name}${TOOL_OS_SUFFIX}" ) + set( _clang_name "clang" ) +endif() + + +# setup paths and STL for NDK +if( BUILD_WITH_ANDROID_NDK ) + set( ANDROID_TOOLCHAIN_ROOT "${ANDROID_NDK_TOOLCHAINS_PATH}/${ANDROID_GCC_TOOLCHAIN_NAME}${ANDROID_NDK_TOOLCHAINS_SUBPATH}" ) + set( ANDROID_SYSROOT "${ANDROID_NDK}/platforms/android-${ANDROID_NATIVE_API_LEVEL}/arch-${ANDROID_ARCH_NAME}" ) + + if( ANDROID_STL STREQUAL "none" ) + # do nothing + elseif( ANDROID_STL STREQUAL "system" ) + set( ANDROID_RTTI OFF ) + set( ANDROID_EXCEPTIONS OFF ) + set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_NDK}/sources/cxx-stl/system/include" ) + elseif( ANDROID_STL STREQUAL "system_re" ) + set( ANDROID_RTTI ON ) + set( ANDROID_EXCEPTIONS ON ) + set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_NDK}/sources/cxx-stl/system/include" ) + elseif( ANDROID_STL MATCHES "gabi" ) + if( ANDROID_NDK_RELEASE_NUM LESS 7000 ) # before r7 + message( FATAL_ERROR "gabi++ is not available in your NDK. You have to upgrade to NDK r7 or newer to use gabi++.") + endif() + set( ANDROID_RTTI ON ) + set( ANDROID_EXCEPTIONS OFF ) + set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_NDK}/sources/cxx-stl/gabi++/include" ) + set( __libstl "${ANDROID_NDK}/sources/cxx-stl/gabi++/libs/${ANDROID_NDK_ABI_NAME}/libgabi++_static.a" ) + elseif( ANDROID_STL MATCHES "stlport" ) + if( NOT ANDROID_NDK_RELEASE_NUM LESS 8004 ) # before r8d + set( ANDROID_EXCEPTIONS ON ) + else() + set( ANDROID_EXCEPTIONS OFF ) + endif() + if( ANDROID_NDK_RELEASE_NUM LESS 7000 ) # before r7 + set( ANDROID_RTTI OFF ) + else() + set( ANDROID_RTTI ON ) + endif() + set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_NDK}/sources/cxx-stl/stlport/stlport" ) + set( __libstl "${ANDROID_NDK}/sources/cxx-stl/stlport/libs/${ANDROID_NDK_ABI_NAME}/libstlport_static.a" ) + elseif( ANDROID_STL MATCHES "gnustl" ) + set( ANDROID_EXCEPTIONS ON ) + set( ANDROID_RTTI ON ) + if( EXISTS "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/${ANDROID_COMPILER_VERSION}" ) + if( ARMEABI_V7A AND ANDROID_COMPILER_VERSION VERSION_EQUAL "4.7" AND ANDROID_NDK_RELEASE STREQUAL "r8d" ) + # gnustl binary for 4.7 compiler is buggy :( + # TODO: look for right fix + set( __libstl "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/4.6" ) + else() + set( __libstl "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/${ANDROID_COMPILER_VERSION}" ) + endif() + else() + set( __libstl "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++" ) + endif() + set( ANDROID_STL_INCLUDE_DIRS "${__libstl}/include" "${__libstl}/libs/${ANDROID_NDK_ABI_NAME}/include" "${__libstl}/include/backward" ) + if( EXISTS "${__libstl}/libs/${ANDROID_NDK_ABI_NAME}/libgnustl_static.a" ) + set( __libstl "${__libstl}/libs/${ANDROID_NDK_ABI_NAME}/libgnustl_static.a" ) + else() + set( __libstl "${__libstl}/libs/${ANDROID_NDK_ABI_NAME}/libstdc++.a" ) + endif() + else() + message( FATAL_ERROR "Unknown runtime: ${ANDROID_STL}" ) + endif() + # find libsupc++.a - rtti & exceptions + if( ANDROID_STL STREQUAL "system_re" OR ANDROID_STL MATCHES "gnustl" ) + set( __libsupcxx "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/${ANDROID_COMPILER_VERSION}/libs/${ANDROID_NDK_ABI_NAME}/libsupc++.a" ) # r8b or newer + if( NOT EXISTS "${__libsupcxx}" ) + set( __libsupcxx "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/libs/${ANDROID_NDK_ABI_NAME}/libsupc++.a" ) # r7-r8 + endif() + if( NOT EXISTS "${__libsupcxx}" ) # before r7 + if( ARMEABI_V7A ) + if( ANDROID_FORCE_ARM_BUILD ) + set( __libsupcxx "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/libsupc++.a" ) + else() + set( __libsupcxx "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/thumb/libsupc++.a" ) + endif() + elseif( ARMEABI AND NOT ANDROID_FORCE_ARM_BUILD ) + set( __libsupcxx "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/thumb/libsupc++.a" ) + else() + set( __libsupcxx "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/libsupc++.a" ) + endif() + endif() + if( NOT EXISTS "${__libsupcxx}") + message( ERROR "Could not find libsupc++.a for a chosen platform. Either your NDK is not supported or is broken.") + endif() + endif() +endif() + + +# case of shared STL linkage +if( ANDROID_STL MATCHES "shared" AND DEFINED __libstl ) + string( REPLACE "_static.a" "_shared.so" __libstl "${__libstl}" ) + # TODO: check if .so file exists before the renaming +endif() + + +# ccache support +__INIT_VARIABLE( _ndk_ccache NDK_CCACHE ENV_NDK_CCACHE ) +if( _ndk_ccache ) + if( DEFINED NDK_CCACHE AND NOT EXISTS NDK_CCACHE ) + unset( NDK_CCACHE CACHE ) + endif() + find_program( NDK_CCACHE "${_ndk_ccache}" DOC "The path to ccache binary") +else() + unset( NDK_CCACHE CACHE ) +endif() +unset( _ndk_ccache ) + + +# setup the cross-compiler +if( NOT CMAKE_C_COMPILER ) + if( NDK_CCACHE AND NOT ANDROID_SYSROOT MATCHES "[ ;\"]" ) + set( CMAKE_C_COMPILER "${NDK_CCACHE}" CACHE PATH "ccache as C compiler" ) + set( CMAKE_CXX_COMPILER "${NDK_CCACHE}" CACHE PATH "ccache as C++ compiler" ) + if( ANDROID_COMPILER_IS_CLANG ) + set( CMAKE_C_COMPILER_ARG1 "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/${_clang_name}${TOOL_OS_SUFFIX}" CACHE PATH "C compiler") + set( CMAKE_CXX_COMPILER_ARG1 "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/${_clang_name}++${TOOL_OS_SUFFIX}" CACHE PATH "C++ compiler") + else() + set( CMAKE_C_COMPILER_ARG1 "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-gcc${TOOL_OS_SUFFIX}" CACHE PATH "C compiler") + set( CMAKE_CXX_COMPILER_ARG1 "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-g++${TOOL_OS_SUFFIX}" CACHE PATH "C++ compiler") + endif() + else() + if( ANDROID_COMPILER_IS_CLANG ) + set( CMAKE_C_COMPILER "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/${_clang_name}${TOOL_OS_SUFFIX}" CACHE PATH "C compiler") + set( CMAKE_CXX_COMPILER "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/${_clang_name}++${TOOL_OS_SUFFIX}" CACHE PATH "C++ compiler") + else() + set( CMAKE_C_COMPILER "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-gcc${TOOL_OS_SUFFIX}" CACHE PATH "C compiler" ) + set( CMAKE_CXX_COMPILER "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-g++${TOOL_OS_SUFFIX}" CACHE PATH "C++ compiler" ) + endif() + endif() + set( CMAKE_ASM_COMPILER "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-gcc${TOOL_OS_SUFFIX}" CACHE PATH "assembler" ) + set( CMAKE_STRIP "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-strip${TOOL_OS_SUFFIX}" CACHE PATH "strip" ) + if( EXISTS "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-gcc-ar${TOOL_OS_SUFFIX}" ) + # Use gcc-ar if we have it for better LTO support. + set( CMAKE_AR "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-gcc-ar${TOOL_OS_SUFFIX}" CACHE PATH "archive" ) + else() + set( CMAKE_AR "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-ar${TOOL_OS_SUFFIX}" CACHE PATH "archive" ) + endif() + set( CMAKE_LINKER "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-ld${TOOL_OS_SUFFIX}" CACHE PATH "linker" ) + set( CMAKE_NM "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-nm${TOOL_OS_SUFFIX}" CACHE PATH "nm" ) + set( CMAKE_OBJCOPY "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-objcopy${TOOL_OS_SUFFIX}" CACHE PATH "objcopy" ) + set( CMAKE_OBJDUMP "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-objdump${TOOL_OS_SUFFIX}" CACHE PATH "objdump" ) + set( CMAKE_RANLIB "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-ranlib${TOOL_OS_SUFFIX}" CACHE PATH "ranlib" ) +endif() + +set( _CMAKE_TOOLCHAIN_PREFIX "${ANDROID_TOOLCHAIN_MACHINE_NAME}-" ) +if( CMAKE_VERSION VERSION_LESS 2.8.5 ) + set( CMAKE_ASM_COMPILER_ARG1 "-c" ) +endif() +if( APPLE ) + find_program( CMAKE_INSTALL_NAME_TOOL NAMES install_name_tool ) + if( NOT CMAKE_INSTALL_NAME_TOOL ) + message( FATAL_ERROR "Could not find install_name_tool, please check your installation." ) + endif() + mark_as_advanced( CMAKE_INSTALL_NAME_TOOL ) +endif() + +# Force set compilers because standard identification works badly for us +include( CMakeForceCompiler ) +CMAKE_FORCE_C_COMPILER( "${CMAKE_C_COMPILER}" GNU ) +if( ANDROID_COMPILER_IS_CLANG ) + set( CMAKE_C_COMPILER_ID Clang ) +endif() +set( CMAKE_C_PLATFORM_ID Linux ) +if( X86_64 OR MIPS64 OR ARM64_V8A ) + set( CMAKE_C_SIZEOF_DATA_PTR 8 ) +else() + set( CMAKE_C_SIZEOF_DATA_PTR 4 ) +endif() +set( CMAKE_C_HAS_ISYSROOT 1 ) +set( CMAKE_C_COMPILER_ABI ELF ) +CMAKE_FORCE_CXX_COMPILER( "${CMAKE_CXX_COMPILER}" GNU ) +if( ANDROID_COMPILER_IS_CLANG ) + set( CMAKE_CXX_COMPILER_ID Clang) +endif() +set( CMAKE_CXX_PLATFORM_ID Linux ) +set( CMAKE_CXX_SIZEOF_DATA_PTR ${CMAKE_C_SIZEOF_DATA_PTR} ) +set( CMAKE_CXX_HAS_ISYSROOT 1 ) +set( CMAKE_CXX_COMPILER_ABI ELF ) +set( CMAKE_CXX_SOURCE_FILE_EXTENSIONS cc cp cxx cpp CPP c++ C ) +# force ASM compiler (required for CMake < 2.8.5) +set( CMAKE_ASM_COMPILER_ID_RUN TRUE ) +set( CMAKE_ASM_COMPILER_ID GNU ) +set( CMAKE_ASM_COMPILER_WORKS TRUE ) +set( CMAKE_ASM_COMPILER_FORCED TRUE ) +set( CMAKE_COMPILER_IS_GNUASM 1) +set( CMAKE_ASM_SOURCE_FILE_EXTENSIONS s S asm ) + +foreach( lang C CXX ASM ) + if( ANDROID_COMPILER_IS_CLANG ) + set( CMAKE_${lang}_COMPILER_VERSION ${ANDROID_CLANG_VERSION} ) + else() + set( CMAKE_${lang}_COMPILER_VERSION ${ANDROID_COMPILER_VERSION} ) + endif() +endforeach() + +# flags and definitions +remove_definitions( -DANDROID ) +add_definitions( -DANDROID ) + +if( ANDROID_SYSROOT MATCHES "[ ;\"]" ) + if( CMAKE_HOST_WIN32 ) + # try to convert path to 8.3 form + file( WRITE "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/cvt83.cmd" "@echo %~s1" ) + execute_process( COMMAND "$ENV{ComSpec}" /c "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/cvt83.cmd" "${ANDROID_SYSROOT}" + OUTPUT_VARIABLE __path OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE __result ERROR_QUIET ) + if( __result EQUAL 0 ) + file( TO_CMAKE_PATH "${__path}" ANDROID_SYSROOT ) + set( ANDROID_CXX_FLAGS "--sysroot=${ANDROID_SYSROOT}" ) + else() + set( ANDROID_CXX_FLAGS "--sysroot=\"${ANDROID_SYSROOT}\"" ) + endif() + else() + set( ANDROID_CXX_FLAGS "'--sysroot=${ANDROID_SYSROOT}'" ) + endif() + if( NOT _CMAKE_IN_TRY_COMPILE ) + # quotes can break try_compile and compiler identification + message(WARNING "Path to your Android NDK (or toolchain) has non-alphanumeric symbols.\nThe build might be broken.\n") + endif() +else() + set( ANDROID_CXX_FLAGS "--sysroot=${ANDROID_SYSROOT}" ) +endif() + +# NDK flags +if (ARM64_V8A ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -funwind-tables" ) + set( ANDROID_CXX_FLAGS_RELEASE "-fomit-frame-pointer -fstrict-aliasing" ) + set( ANDROID_CXX_FLAGS_DEBUG "-fno-omit-frame-pointer -fno-strict-aliasing" ) + if( NOT ANDROID_COMPILER_IS_CLANG ) + set( ANDROID_CXX_FLAGS_RELEASE "${ANDROID_CXX_FLAGS_RELEASE} -funswitch-loops -finline-limit=300" ) + endif() +elseif( ARMEABI OR ARMEABI_V7A) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -funwind-tables" ) + if( NOT ANDROID_FORCE_ARM_BUILD AND NOT ARMEABI_V6 ) + set( ANDROID_CXX_FLAGS_RELEASE "-mthumb -fomit-frame-pointer -fno-strict-aliasing" ) + set( ANDROID_CXX_FLAGS_DEBUG "-marm -fno-omit-frame-pointer -fno-strict-aliasing" ) + if( NOT ANDROID_COMPILER_IS_CLANG ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -finline-limit=64" ) + endif() + else() + # always compile ARMEABI_V6 in arm mode; otherwise there is no difference from ARMEABI + set( ANDROID_CXX_FLAGS_RELEASE "-marm -fomit-frame-pointer -fstrict-aliasing" ) + set( ANDROID_CXX_FLAGS_DEBUG "-marm -fno-omit-frame-pointer -fno-strict-aliasing" ) + if( NOT ANDROID_COMPILER_IS_CLANG ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -funswitch-loops -finline-limit=300" ) + endif() + endif() +elseif( X86 OR X86_64 ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -funwind-tables" ) + if( NOT ANDROID_COMPILER_IS_CLANG ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -funswitch-loops -finline-limit=300" ) + endif() + set( ANDROID_CXX_FLAGS_RELEASE "-fomit-frame-pointer -fstrict-aliasing" ) + set( ANDROID_CXX_FLAGS_DEBUG "-fno-omit-frame-pointer -fno-strict-aliasing" ) +elseif( MIPS OR MIPS64 ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fno-strict-aliasing -finline-functions -funwind-tables -fmessage-length=0" ) + set( ANDROID_CXX_FLAGS_RELEASE "-fomit-frame-pointer" ) + set( ANDROID_CXX_FLAGS_DEBUG "-fno-omit-frame-pointer" ) + if( NOT ANDROID_COMPILER_IS_CLANG ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fno-inline-functions-called-once -fgcse-after-reload -frerun-cse-after-loop -frename-registers" ) + set( ANDROID_CXX_FLAGS_RELEASE "${ANDROID_CXX_FLAGS_RELEASE} -funswitch-loops -finline-limit=300" ) + endif() +elseif() + set( ANDROID_CXX_FLAGS_RELEASE "" ) + set( ANDROID_CXX_FLAGS_DEBUG "" ) +endif() + +set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fsigned-char" ) # good/necessary when porting desktop libraries + +if( NOT X86 AND NOT ANDROID_COMPILER_IS_CLANG ) + set( ANDROID_CXX_FLAGS "-Wno-psabi ${ANDROID_CXX_FLAGS}" ) +endif() + +if( NOT ANDROID_COMPILER_VERSION VERSION_LESS "4.6" ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -no-canonical-prefixes" ) # see https://android-review.googlesource.com/#/c/47564/ +endif() + +# ABI-specific flags +if( ARMEABI_V7A ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -march=armv7-a -mfloat-abi=softfp" ) + if( NEON ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -mfpu=neon" ) + elseif( VFPV3 ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -mfpu=vfpv3" ) + else() + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -mfpu=vfpv3-d16" ) + endif() +elseif( ARMEABI_V6 ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -march=armv6 -mfloat-abi=softfp -mfpu=vfp" ) # vfp == vfpv2 +elseif( ARMEABI ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -march=armv5te -mtune=xscale -msoft-float" ) +endif() + +if( ANDROID_STL MATCHES "gnustl" AND (EXISTS "${__libstl}" OR EXISTS "${__libsupcxx}") ) + set( CMAKE_CXX_CREATE_SHARED_LIBRARY " -o " ) + set( CMAKE_CXX_CREATE_SHARED_MODULE " -o " ) + set( CMAKE_CXX_LINK_EXECUTABLE " -o " ) +else() + set( CMAKE_CXX_CREATE_SHARED_LIBRARY " -o " ) + set( CMAKE_CXX_CREATE_SHARED_MODULE " -o " ) + set( CMAKE_CXX_LINK_EXECUTABLE " -o " ) +endif() + +# STL +if( EXISTS "${__libstl}" OR EXISTS "${__libsupcxx}" ) + if( EXISTS "${__libstl}" ) + set( CMAKE_CXX_CREATE_SHARED_LIBRARY "${CMAKE_CXX_CREATE_SHARED_LIBRARY} \"${__libstl}\"" ) + set( CMAKE_CXX_CREATE_SHARED_MODULE "${CMAKE_CXX_CREATE_SHARED_MODULE} \"${__libstl}\"" ) + set( CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE} \"${__libstl}\"" ) + endif() + if( EXISTS "${__libsupcxx}" ) + set( CMAKE_CXX_CREATE_SHARED_LIBRARY "${CMAKE_CXX_CREATE_SHARED_LIBRARY} \"${__libsupcxx}\"" ) + set( CMAKE_CXX_CREATE_SHARED_MODULE "${CMAKE_CXX_CREATE_SHARED_MODULE} \"${__libsupcxx}\"" ) + set( CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE} \"${__libsupcxx}\"" ) + # C objects: + set( CMAKE_C_CREATE_SHARED_LIBRARY " -o " ) + set( CMAKE_C_CREATE_SHARED_MODULE " -o " ) + set( CMAKE_C_LINK_EXECUTABLE " -o " ) + set( CMAKE_C_CREATE_SHARED_LIBRARY "${CMAKE_C_CREATE_SHARED_LIBRARY} \"${__libsupcxx}\"" ) + set( CMAKE_C_CREATE_SHARED_MODULE "${CMAKE_C_CREATE_SHARED_MODULE} \"${__libsupcxx}\"" ) + set( CMAKE_C_LINK_EXECUTABLE "${CMAKE_C_LINK_EXECUTABLE} \"${__libsupcxx}\"" ) + endif() + if( ANDROID_STL MATCHES "gnustl" ) + if( NOT EXISTS "${ANDROID_LIBM_PATH}" ) + set( ANDROID_LIBM_PATH -lm ) + endif() + set( CMAKE_CXX_CREATE_SHARED_LIBRARY "${CMAKE_CXX_CREATE_SHARED_LIBRARY} ${ANDROID_LIBM_PATH}" ) + set( CMAKE_CXX_CREATE_SHARED_MODULE "${CMAKE_CXX_CREATE_SHARED_MODULE} ${ANDROID_LIBM_PATH}" ) + set( CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE} ${ANDROID_LIBM_PATH}" ) + endif() +endif() + +# variables controlling optional build flags +if( ANDROID_NDK_RELEASE_NUM LESS 7000 ) # before r7 + # libGLESv2.so in NDK's prior to r7 refers to missing external symbols. + # So this flag option is required for all projects using OpenGL from native. + __INIT_VARIABLE( ANDROID_SO_UNDEFINED VALUES ON ) +else() + __INIT_VARIABLE( ANDROID_SO_UNDEFINED VALUES OFF ) +endif() +__INIT_VARIABLE( ANDROID_NO_UNDEFINED VALUES ON ) +__INIT_VARIABLE( ANDROID_FUNCTION_LEVEL_LINKING VALUES ON ) +__INIT_VARIABLE( ANDROID_GOLD_LINKER VALUES ON ) +__INIT_VARIABLE( ANDROID_NOEXECSTACK VALUES ON ) +__INIT_VARIABLE( ANDROID_RELRO VALUES ON ) + +set( ANDROID_NO_UNDEFINED ${ANDROID_NO_UNDEFINED} CACHE BOOL "Show all undefined symbols as linker errors" ) +set( ANDROID_SO_UNDEFINED ${ANDROID_SO_UNDEFINED} CACHE BOOL "Allows or disallows undefined symbols in shared libraries" ) +set( ANDROID_FUNCTION_LEVEL_LINKING ${ANDROID_FUNCTION_LEVEL_LINKING} CACHE BOOL "Put each function in separate section and enable garbage collection of unused input sections at link time" ) +set( ANDROID_GOLD_LINKER ${ANDROID_GOLD_LINKER} CACHE BOOL "Enables gold linker" ) +set( ANDROID_NOEXECSTACK ${ANDROID_NOEXECSTACK} CACHE BOOL "Allows or disallows undefined symbols in shared libraries" ) +set( ANDROID_RELRO ${ANDROID_RELRO} CACHE BOOL "Enables RELRO - a memory corruption mitigation technique" ) +mark_as_advanced( ANDROID_NO_UNDEFINED ANDROID_SO_UNDEFINED ANDROID_FUNCTION_LEVEL_LINKING ANDROID_GOLD_LINKER ANDROID_NOEXECSTACK ANDROID_RELRO ) + +# linker flags +set( ANDROID_LINKER_FLAGS "" ) + +if( ARMEABI_V7A ) + # this is *required* to use the following linker flags that routes around + # a CPU bug in some Cortex-A8 implementations: + set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,--fix-cortex-a8" ) +endif() + +if( ANDROID_NO_UNDEFINED ) + if( MIPS ) + # there is some sysroot-related problem in mips linker... + if( NOT ANDROID_SYSROOT MATCHES "[ ;\"]" ) + set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,--no-undefined -Wl,-rpath-link,${ANDROID_SYSROOT}/usr/lib" ) + endif() + else() + set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,--no-undefined" ) + endif() +endif() + +if( ANDROID_SO_UNDEFINED ) + set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,-allow-shlib-undefined" ) +endif() + +if( ANDROID_FUNCTION_LEVEL_LINKING ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fdata-sections -ffunction-sections" ) + set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,--gc-sections" ) +endif() + +if( ANDROID_COMPILER_VERSION VERSION_EQUAL "4.6" ) + if( ANDROID_GOLD_LINKER AND (CMAKE_HOST_UNIX OR ANDROID_NDK_RELEASE_NUM GREATER 8002) AND (ARMEABI OR ARMEABI_V7A OR X86) ) + set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -fuse-ld=gold" ) + elseif( ANDROID_NDK_RELEASE_NUM GREATER 8002 ) # after r8b + set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -fuse-ld=bfd" ) + elseif( ANDROID_NDK_RELEASE STREQUAL "r8b" AND ARMEABI AND NOT _CMAKE_IN_TRY_COMPILE ) + message( WARNING "The default bfd linker from arm GCC 4.6 toolchain can fail with 'unresolvable R_ARM_THM_CALL relocation' error message. See https://code.google.com/p/android/issues/detail?id=35342 + On Linux and OS X host platform you can workaround this problem using gold linker (default). + Rerun cmake with -DANDROID_GOLD_LINKER=ON option in case of problems. +" ) + endif() +endif() # version 4.6 + +if( ANDROID_NOEXECSTACK ) + if( ANDROID_COMPILER_IS_CLANG ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -Xclang -mnoexecstack" ) + else() + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -Wa,--noexecstack" ) + endif() + set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,-z,noexecstack" ) +endif() + +if( ANDROID_RELRO ) + set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,-z,relro -Wl,-z,now" ) +endif() + +if( ANDROID_COMPILER_IS_CLANG ) + set( ANDROID_CXX_FLAGS "-target ${ANDROID_LLVM_TRIPLE} -Qunused-arguments ${ANDROID_CXX_FLAGS}" ) + if( BUILD_WITH_ANDROID_NDK ) + set( ANDROID_CXX_FLAGS "-gcc-toolchain ${ANDROID_TOOLCHAIN_ROOT} ${ANDROID_CXX_FLAGS}" ) + endif() +endif() + +# cache flags +set( CMAKE_CXX_FLAGS "" CACHE STRING "c++ flags" ) +set( CMAKE_C_FLAGS "" CACHE STRING "c flags" ) +set( CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG" CACHE STRING "c++ Release flags" ) +set( CMAKE_C_FLAGS_RELEASE "-O3 -DNDEBUG" CACHE STRING "c Release flags" ) +set( CMAKE_CXX_FLAGS_DEBUG "-O0 -g -DDEBUG -D_DEBUG" CACHE STRING "c++ Debug flags" ) +set( CMAKE_C_FLAGS_DEBUG "-O0 -g -DDEBUG -D_DEBUG" CACHE STRING "c Debug flags" ) +set( CMAKE_SHARED_LINKER_FLAGS "" CACHE STRING "shared linker flags" ) +set( CMAKE_MODULE_LINKER_FLAGS "" CACHE STRING "module linker flags" ) +set( CMAKE_EXE_LINKER_FLAGS "-Wl,-z,nocopyreloc" CACHE STRING "executable linker flags" ) + +# put flags to cache (for debug purpose only) +set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS}" CACHE INTERNAL "Android specific c/c++ flags" ) +set( ANDROID_CXX_FLAGS_RELEASE "${ANDROID_CXX_FLAGS_RELEASE}" CACHE INTERNAL "Android specific c/c++ Release flags" ) +set( ANDROID_CXX_FLAGS_DEBUG "${ANDROID_CXX_FLAGS_DEBUG}" CACHE INTERNAL "Android specific c/c++ Debug flags" ) +set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS}" CACHE INTERNAL "Android specific c/c++ linker flags" ) + +# finish flags +set( CMAKE_CXX_FLAGS "${ANDROID_CXX_FLAGS} ${CMAKE_CXX_FLAGS}" ) +set( CMAKE_C_FLAGS "${ANDROID_CXX_FLAGS} ${CMAKE_C_FLAGS}" ) +set( CMAKE_CXX_FLAGS_RELEASE "${ANDROID_CXX_FLAGS_RELEASE} ${CMAKE_CXX_FLAGS_RELEASE}" ) +set( CMAKE_C_FLAGS_RELEASE "${ANDROID_CXX_FLAGS_RELEASE} ${CMAKE_C_FLAGS_RELEASE}" ) +set( CMAKE_CXX_FLAGS_DEBUG "${ANDROID_CXX_FLAGS_DEBUG} ${CMAKE_CXX_FLAGS_DEBUG}" ) +set( CMAKE_C_FLAGS_DEBUG "${ANDROID_CXX_FLAGS_DEBUG} ${CMAKE_C_FLAGS_DEBUG}" ) +set( CMAKE_SHARED_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} ${CMAKE_SHARED_LINKER_FLAGS}" ) +set( CMAKE_MODULE_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} ${CMAKE_MODULE_LINKER_FLAGS}" ) +set( CMAKE_EXE_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} ${CMAKE_EXE_LINKER_FLAGS}" ) + +if( MIPS AND BUILD_WITH_ANDROID_NDK AND ANDROID_NDK_RELEASE STREQUAL "r8" ) + set( CMAKE_SHARED_LINKER_FLAGS "-Wl,-T,${ANDROID_NDK_TOOLCHAINS_PATH}/${ANDROID_GCC_TOOLCHAIN_NAME}/mipself.xsc ${CMAKE_SHARED_LINKER_FLAGS}" ) + set( CMAKE_MODULE_LINKER_FLAGS "-Wl,-T,${ANDROID_NDK_TOOLCHAINS_PATH}/${ANDROID_GCC_TOOLCHAIN_NAME}/mipself.xsc ${CMAKE_MODULE_LINKER_FLAGS}" ) + set( CMAKE_EXE_LINKER_FLAGS "-Wl,-T,${ANDROID_NDK_TOOLCHAINS_PATH}/${ANDROID_GCC_TOOLCHAIN_NAME}/mipself.x ${CMAKE_EXE_LINKER_FLAGS}" ) +endif() + +# pie/pic +if( NOT (ANDROID_NATIVE_API_LEVEL LESS 16) AND (NOT DEFINED ANDROID_APP_PIE OR ANDROID_APP_PIE) AND (CMAKE_VERSION VERSION_GREATER 2.8.8) ) + set( CMAKE_POSITION_INDEPENDENT_CODE TRUE ) + set( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fPIE -pie") +else() + set( CMAKE_POSITION_INDEPENDENT_CODE FALSE ) + set( CMAKE_CXX_FLAGS "-fpic ${CMAKE_CXX_FLAGS}" ) + set( CMAKE_C_FLAGS "-fpic ${CMAKE_C_FLAGS}" ) +endif() + +# configure rtti +if( DEFINED ANDROID_RTTI AND ANDROID_STL_FORCE_FEATURES ) + if( ANDROID_RTTI ) + set( CMAKE_CXX_FLAGS "-frtti ${CMAKE_CXX_FLAGS}" ) + else() + set( CMAKE_CXX_FLAGS "-fno-rtti ${CMAKE_CXX_FLAGS}" ) + endif() +endif() + +# configure exceptios +if( DEFINED ANDROID_EXCEPTIONS AND ANDROID_STL_FORCE_FEATURES ) + if( ANDROID_EXCEPTIONS ) + set( CMAKE_CXX_FLAGS "-fexceptions ${CMAKE_CXX_FLAGS}" ) + set( CMAKE_C_FLAGS "-fexceptions ${CMAKE_C_FLAGS}" ) + else() + set( CMAKE_CXX_FLAGS "-fno-exceptions ${CMAKE_CXX_FLAGS}" ) + set( CMAKE_C_FLAGS "-fno-exceptions ${CMAKE_C_FLAGS}" ) + endif() +endif() + +# global includes and link directories +include_directories( SYSTEM "${ANDROID_SYSROOT}/usr/include" ${ANDROID_STL_INCLUDE_DIRS} ) +get_filename_component(__android_install_path "${CMAKE_INSTALL_PREFIX}/libs/${ANDROID_NDK_ABI_NAME}" ABSOLUTE) # avoid CMP0015 policy warning +link_directories( "${__android_install_path}" ) + +# detect if need link crtbegin_so.o explicitly +if( NOT DEFINED ANDROID_EXPLICIT_CRT_LINK ) + set( __cmd "${CMAKE_CXX_CREATE_SHARED_LIBRARY}" ) + string( REPLACE "" "${CMAKE_CXX_COMPILER} ${CMAKE_CXX_COMPILER_ARG1}" __cmd "${__cmd}" ) + string( REPLACE "" "${CMAKE_C_COMPILER} ${CMAKE_C_COMPILER_ARG1}" __cmd "${__cmd}" ) + string( REPLACE "" "${CMAKE_CXX_FLAGS}" __cmd "${__cmd}" ) + string( REPLACE "" "" __cmd "${__cmd}" ) + string( REPLACE "" "${CMAKE_SHARED_LINKER_FLAGS}" __cmd "${__cmd}" ) + string( REPLACE "" "-shared" __cmd "${__cmd}" ) + string( REPLACE "" "" __cmd "${__cmd}" ) + string( REPLACE "" "" __cmd "${__cmd}" ) + string( REPLACE "" "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/toolchain_crtlink_test.so" __cmd "${__cmd}" ) + string( REPLACE "" "\"${ANDROID_SYSROOT}/usr/lib/crtbegin_so.o\"" __cmd "${__cmd}" ) + string( REPLACE "" "" __cmd "${__cmd}" ) + separate_arguments( __cmd ) + foreach( __var ANDROID_NDK ANDROID_NDK_TOOLCHAINS_PATH ANDROID_STANDALONE_TOOLCHAIN ) + if( ${__var} ) + set( __tmp "${${__var}}" ) + separate_arguments( __tmp ) + string( REPLACE "${__tmp}" "${${__var}}" __cmd "${__cmd}") + endif() + endforeach() + string( REPLACE "'" "" __cmd "${__cmd}" ) + string( REPLACE "\"" "" __cmd "${__cmd}" ) + execute_process( COMMAND ${__cmd} RESULT_VARIABLE __cmd_result OUTPUT_QUIET ERROR_QUIET ) + if( __cmd_result EQUAL 0 ) + set( ANDROID_EXPLICIT_CRT_LINK ON ) + else() + set( ANDROID_EXPLICIT_CRT_LINK OFF ) + endif() +endif() + +if( ANDROID_EXPLICIT_CRT_LINK ) + set( CMAKE_CXX_CREATE_SHARED_LIBRARY "${CMAKE_CXX_CREATE_SHARED_LIBRARY} \"${ANDROID_SYSROOT}/usr/lib/crtbegin_so.o\"" ) + set( CMAKE_CXX_CREATE_SHARED_MODULE "${CMAKE_CXX_CREATE_SHARED_MODULE} \"${ANDROID_SYSROOT}/usr/lib/crtbegin_so.o\"" ) +endif() + +# setup output directories +set( CMAKE_INSTALL_PREFIX "${ANDROID_TOOLCHAIN_ROOT}/user" CACHE STRING "path for installing" ) + +if( DEFINED LIBRARY_OUTPUT_PATH_ROOT + OR EXISTS "${CMAKE_SOURCE_DIR}/AndroidManifest.xml" + OR (EXISTS "${CMAKE_SOURCE_DIR}/../AndroidManifest.xml" AND EXISTS "${CMAKE_SOURCE_DIR}/../jni/") ) + set( LIBRARY_OUTPUT_PATH_ROOT ${CMAKE_SOURCE_DIR} CACHE PATH "Root for binaries output, set this to change where Android libs are installed to" ) + if( NOT _CMAKE_IN_TRY_COMPILE ) + if( EXISTS "${CMAKE_SOURCE_DIR}/jni/CMakeLists.txt" ) + set( EXECUTABLE_OUTPUT_PATH "${LIBRARY_OUTPUT_PATH_ROOT}/bin/${ANDROID_NDK_ABI_NAME}" CACHE PATH "Output directory for applications" ) + else() + set( EXECUTABLE_OUTPUT_PATH "${LIBRARY_OUTPUT_PATH_ROOT}/bin" CACHE PATH "Output directory for applications" ) + endif() + set( LIBRARY_OUTPUT_PATH "${LIBRARY_OUTPUT_PATH_ROOT}/libs/${ANDROID_NDK_ABI_NAME}" CACHE PATH "Output directory for Android libs" ) + endif() +endif() + +# copy shaed stl library to build directory +if( NOT _CMAKE_IN_TRY_COMPILE AND __libstl MATCHES "[.]so$" AND DEFINED LIBRARY_OUTPUT_PATH ) + get_filename_component( __libstlname "${__libstl}" NAME ) + execute_process( COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${__libstl}" "${LIBRARY_OUTPUT_PATH}/${__libstlname}" RESULT_VARIABLE __fileCopyProcess ) + if( NOT __fileCopyProcess EQUAL 0 OR NOT EXISTS "${LIBRARY_OUTPUT_PATH}/${__libstlname}") + message( SEND_ERROR "Failed copying of ${__libstl} to the ${LIBRARY_OUTPUT_PATH}/${__libstlname}" ) + endif() + unset( __fileCopyProcess ) + unset( __libstlname ) +endif() + + +# set these global flags for cmake client scripts to change behavior +set( ANDROID True ) +set( BUILD_ANDROID True ) + +# where is the target environment +set( CMAKE_FIND_ROOT_PATH "${ANDROID_TOOLCHAIN_ROOT}/bin" "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}" "${ANDROID_SYSROOT}" "${CMAKE_INSTALL_PREFIX}" "${CMAKE_INSTALL_PREFIX}/share" ) + +# only search for libraries and includes in the ndk toolchain +set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY ) +set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY ) +set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY ) + + +# macro to find packages on the host OS +macro( find_host_package ) + set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER ) + set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER ) + set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER ) + if( CMAKE_HOST_WIN32 ) + SET( WIN32 1 ) + SET( UNIX ) + elseif( CMAKE_HOST_APPLE ) + SET( APPLE 1 ) + SET( UNIX ) + endif() + find_package( ${ARGN} ) + SET( WIN32 ) + SET( APPLE ) + SET( UNIX 1 ) + set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY ) + set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY ) + set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY ) +endmacro() + + +# macro to find programs on the host OS +macro( find_host_program ) + set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER ) + set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER ) + set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER ) + if( CMAKE_HOST_WIN32 ) + SET( WIN32 1 ) + SET( UNIX ) + elseif( CMAKE_HOST_APPLE ) + SET( APPLE 1 ) + SET( UNIX ) + endif() + find_program( ${ARGN} ) + SET( WIN32 ) + SET( APPLE ) + SET( UNIX 1 ) + set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY ) + set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY ) + set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY ) +endmacro() + + +# export toolchain settings for the try_compile() command +if( NOT _CMAKE_IN_TRY_COMPILE ) + set( __toolchain_config "") + foreach( __var NDK_CCACHE LIBRARY_OUTPUT_PATH_ROOT ANDROID_FORBID_SYGWIN + ANDROID_NDK_HOST_X64 + ANDROID_NDK + ANDROID_NDK_LAYOUT + ANDROID_STANDALONE_TOOLCHAIN + ANDROID_TOOLCHAIN_NAME + ANDROID_ABI + ANDROID_NATIVE_API_LEVEL + ANDROID_STL + ANDROID_STL_FORCE_FEATURES + ANDROID_FORCE_ARM_BUILD + ANDROID_NO_UNDEFINED + ANDROID_SO_UNDEFINED + ANDROID_FUNCTION_LEVEL_LINKING + ANDROID_GOLD_LINKER + ANDROID_NOEXECSTACK + ANDROID_RELRO + ANDROID_LIBM_PATH + ANDROID_EXPLICIT_CRT_LINK + ANDROID_APP_PIE + ) + if( DEFINED ${__var} ) + if( ${__var} MATCHES " ") + set( __toolchain_config "${__toolchain_config}set( ${__var} \"${${__var}}\" CACHE INTERNAL \"\" )\n" ) + else() + set( __toolchain_config "${__toolchain_config}set( ${__var} ${${__var}} CACHE INTERNAL \"\" )\n" ) + endif() + endif() + endforeach() + file( WRITE "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/android.toolchain.config.cmake" "${__toolchain_config}" ) + unset( __toolchain_config ) +endif() + + +# force cmake to produce / instead of \ in build commands for Ninja generator +if( CMAKE_GENERATOR MATCHES "Ninja" AND CMAKE_HOST_WIN32 ) + # it is a bad hack after all + # CMake generates Ninja makefiles with UNIX paths only if it thinks that we are going to build with MinGW + set( CMAKE_COMPILER_IS_MINGW TRUE ) # tell CMake that we are MinGW + set( CMAKE_CROSSCOMPILING TRUE ) # stop recursion + enable_language( C ) + enable_language( CXX ) + # unset( CMAKE_COMPILER_IS_MINGW ) # can't unset because CMake does not convert back-slashes in response files without it + unset( MINGW ) +endif() + + +# Variables controlling behavior or set by cmake toolchain: +# ANDROID_ABI : "armeabi-v7a" (default), "armeabi", "armeabi-v7a with NEON", "armeabi-v7a with VFPV3", "armeabi-v6 with VFP", "x86", "mips", "arm64-v8a", "x86_64", "mips64" +# ANDROID_NATIVE_API_LEVEL : 3,4,5,8,9,14,15,16,17,18,19,21 (depends on NDK version) +# ANDROID_STL : gnustl_static/gnustl_shared/stlport_static/stlport_shared/gabi++_static/gabi++_shared/system_re/system/none +# ANDROID_FORBID_SYGWIN : ON/OFF +# ANDROID_NO_UNDEFINED : ON/OFF +# ANDROID_SO_UNDEFINED : OFF/ON (default depends on NDK version) +# ANDROID_FUNCTION_LEVEL_LINKING : ON/OFF +# ANDROID_GOLD_LINKER : ON/OFF +# ANDROID_NOEXECSTACK : ON/OFF +# ANDROID_RELRO : ON/OFF +# ANDROID_FORCE_ARM_BUILD : ON/OFF +# ANDROID_STL_FORCE_FEATURES : ON/OFF +# ANDROID_LIBM_PATH : path to libm.so (set to something like $(TOP)/out/target/product//obj/lib/libm.so) to workaround unresolved `sincos` +# Can be set only at the first run: +# ANDROID_NDK : path to your NDK install +# NDK_CCACHE : path to your ccache executable +# ANDROID_TOOLCHAIN_NAME : the NDK name of compiler toolchain +# ANDROID_NDK_HOST_X64 : try to use x86_64 toolchain (default for x64 host systems) +# ANDROID_NDK_LAYOUT : the inner NDK structure (RELEASE, LINARO, ANDROID) +# LIBRARY_OUTPUT_PATH_ROOT : +# ANDROID_STANDALONE_TOOLCHAIN +# +# Primary read-only variables: +# ANDROID : always TRUE +# ARMEABI : TRUE for arm v6 and older devices +# ARMEABI_V6 : TRUE for arm v6 +# ARMEABI_V7A : TRUE for arm v7a +# ARM64_V8A : TRUE for arm64-v8a +# NEON : TRUE if NEON unit is enabled +# VFPV3 : TRUE if VFP version 3 is enabled +# X86 : TRUE if configured for x86 +# X86_64 : TRUE if configured for x86_64 +# MIPS : TRUE if configured for mips +# MIPS64 : TRUE if configured for mips64 +# BUILD_WITH_ANDROID_NDK : TRUE if NDK is used +# BUILD_WITH_STANDALONE_TOOLCHAIN : TRUE if standalone toolchain is used +# ANDROID_NDK_HOST_SYSTEM_NAME : "windows", "linux-x86" or "darwin-x86" depending on host platform +# ANDROID_NDK_ABI_NAME : "armeabi", "armeabi-v7a", "x86", "mips", "arm64-v8a", "x86_64", "mips64" depending on ANDROID_ABI +# ANDROID_NDK_RELEASE : from r5 to r10d; set only for NDK +# ANDROID_NDK_RELEASE_NUM : numeric ANDROID_NDK_RELEASE version (1000*major+minor) +# ANDROID_ARCH_NAME : "arm", "x86", "mips", "arm64", "x86_64", "mips64" depending on ANDROID_ABI +# ANDROID_SYSROOT : path to the compiler sysroot +# TOOL_OS_SUFFIX : "" or ".exe" depending on host platform +# ANDROID_COMPILER_IS_CLANG : TRUE if clang compiler is used +# +# Secondary (less stable) read-only variables: +# ANDROID_COMPILER_VERSION : GCC version used (not Clang version) +# ANDROID_CLANG_VERSION : version of clang compiler if clang is used +# ANDROID_CXX_FLAGS : C/C++ compiler flags required by Android platform +# ANDROID_SUPPORTED_ABIS : list of currently allowed values for ANDROID_ABI +# ANDROID_TOOLCHAIN_MACHINE_NAME : "arm-linux-androideabi", "arm-eabi" or "i686-android-linux" +# ANDROID_TOOLCHAIN_ROOT : path to the top level of toolchain (standalone or placed inside NDK) +# ANDROID_CLANG_TOOLCHAIN_ROOT : path to clang tools +# ANDROID_SUPPORTED_NATIVE_API_LEVELS : list of native API levels found inside NDK +# ANDROID_STL_INCLUDE_DIRS : stl include paths +# ANDROID_RTTI : if rtti is enabled by the runtime +# ANDROID_EXCEPTIONS : if exceptions are enabled by the runtime +# ANDROID_GCC_TOOLCHAIN_NAME : read-only, differs from ANDROID_TOOLCHAIN_NAME only if clang is used +# +# Defaults: +# ANDROID_DEFAULT_NDK_API_LEVEL +# ANDROID_DEFAULT_NDK_API_LEVEL_${ARCH} +# ANDROID_NDK_SEARCH_PATHS +# ANDROID_SUPPORTED_ABIS_${ARCH} +# ANDROID_SUPPORTED_NDK_VERSIONS diff --git a/3rdpart/ZLToolKit/cmake/iOS.cmake b/3rdpart/ZLToolKit/cmake/iOS.cmake new file mode 100644 index 0000000..f436778 --- /dev/null +++ b/3rdpart/ZLToolKit/cmake/iOS.cmake @@ -0,0 +1,209 @@ +# This file is based off of the Platform/Darwin.cmake and Platform/UnixPaths.cmake +# files which are included with CMake 2.8.4 +# It has been altered for iOS development + +# Options: +# +# IOS_PLATFORM = OS (default) or SIMULATOR or SIMULATOR64 +# This decides if SDKS will be selected from the iPhoneOS.platform or iPhoneSimulator.platform folders +# OS - the default, used to build for iPhone and iPad physical devices, which have an arm arch. +# SIMULATOR - used to build for the Simulator platforms, which have an x86 arch. +# +# CMAKE_IOS_DEVELOPER_ROOT = automatic(default) or /path/to/platform/Developer folder +# By default this location is automatcially chosen based on the IOS_PLATFORM value above. +# If set manually, it will override the default location and force the user of a particular Developer Platform +# +# CMAKE_IOS_SDK_ROOT = automatic(default) or /path/to/platform/Developer/SDKs/SDK folder +# By default this location is automatcially chosen based on the CMAKE_IOS_DEVELOPER_ROOT value. +# In this case it will always be the most up-to-date SDK found in the CMAKE_IOS_DEVELOPER_ROOT path. +# If set manually, this will force the use of a specific SDK version + +# Macros: +# +# set_xcode_property (TARGET XCODE_PROPERTY XCODE_VALUE) +# A convenience macro for setting xcode specific properties on targets +# example: set_xcode_property (myioslib IPHONEOS_DEPLOYMENT_TARGET "3.1") +# +# find_host_package (PROGRAM ARGS) +# A macro used to find executable programs on the host system, not within the iOS environment. +# Thanks to the android-cmake project for providing the command + +# Standard settings +set (CMAKE_SYSTEM_NAME Darwin) +set (CMAKE_SYSTEM_VERSION 1) +set (UNIX True) +set (APPLE True) +set (IOS True) + +# Required as of cmake 2.8.10 +set (CMAKE_OSX_DEPLOYMENT_TARGET "" CACHE STRING "Force unset of the deployment target for iOS" FORCE) + +# Determine the cmake host system version so we know where to find the iOS SDKs +find_program (CMAKE_UNAME uname /bin /usr/bin /usr/local/bin) +if (CMAKE_UNAME) +exec_program(uname ARGS -r OUTPUT_VARIABLE CMAKE_HOST_SYSTEM_VERSION) +string (REGEX REPLACE "^([0-9]+)\\.([0-9]+).*$" "\\1" DARWIN_MAJOR_VERSION "${CMAKE_HOST_SYSTEM_VERSION}") +endif (CMAKE_UNAME) + +# Force the compilers to gcc for iOS +include (CMakeForceCompiler) +#CMAKE_FORCE_C_COMPILER (/usr/bin/gcc Apple) +set(CMAKE_C_COMPILER /usr/bin/clang) +#CMAKE_FORCE_CXX_COMPILER (/usr/bin/g++ Apple) +set(CMAKE_CXX_COMPILER /usr/bin/clang++) +set(CMAKE_AR ar CACHE FILEPATH "" FORCE) + +# Skip the platform compiler checks for cross compiling +set (CMAKE_CXX_COMPILER_WORKS TRUE) +set (CMAKE_C_COMPILER_WORKS TRUE) + +# All iOS/Darwin specific settings - some may be redundant +set (CMAKE_SHARED_LIBRARY_PREFIX "lib") +set (CMAKE_SHARED_LIBRARY_SUFFIX ".dylib") +set (CMAKE_SHARED_MODULE_PREFIX "lib") +set (CMAKE_SHARED_MODULE_SUFFIX ".so") +set (CMAKE_MODULE_EXISTS 1) +set (CMAKE_DL_LIBS "") + +set (CMAKE_C_OSX_COMPATIBILITY_VERSION_FLAG "-compatibility_version ") +set (CMAKE_C_OSX_CURRENT_VERSION_FLAG "-current_version ") +set (CMAKE_CXX_OSX_COMPATIBILITY_VERSION_FLAG "${CMAKE_C_OSX_COMPATIBILITY_VERSION_FLAG}") +set (CMAKE_CXX_OSX_CURRENT_VERSION_FLAG "${CMAKE_C_OSX_CURRENT_VERSION_FLAG}") + +# Hidden visibilty is required for cxx on iOS +set (CMAKE_C_FLAGS_INIT "") +set (CMAKE_CXX_FLAGS_INIT "-fvisibility=hidden -fvisibility-inlines-hidden") + +set (CMAKE_C_LINK_FLAGS "-Wl,-search_paths_first ${CMAKE_C_LINK_FLAGS}") +set (CMAKE_CXX_LINK_FLAGS "-Wl,-search_paths_first ${CMAKE_CXX_LINK_FLAGS}") + +set (CMAKE_PLATFORM_HAS_INSTALLNAME 1) +set (CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS "-dynamiclib -headerpad_max_install_names") +set (CMAKE_SHARED_MODULE_CREATE_C_FLAGS "-bundle -headerpad_max_install_names") +set (CMAKE_SHARED_MODULE_LOADER_C_FLAG "-Wl,-bundle_loader,") +set (CMAKE_SHARED_MODULE_LOADER_CXX_FLAG "-Wl,-bundle_loader,") +set (CMAKE_FIND_LIBRARY_SUFFIXES ".dylib" ".so" ".a") + +# hack: if a new cmake (which uses CMAKE_INSTALL_NAME_TOOL) runs on an old build tree +# (where install_name_tool was hardcoded) and where CMAKE_INSTALL_NAME_TOOL isn't in the cache +# and still cmake didn't fail in CMakeFindBinUtils.cmake (because it isn't rerun) +# hardcode CMAKE_INSTALL_NAME_TOOL here to install_name_tool, so it behaves as it did before, Alex +if (NOT DEFINED CMAKE_INSTALL_NAME_TOOL) +find_program(CMAKE_INSTALL_NAME_TOOL install_name_tool) +endif (NOT DEFINED CMAKE_INSTALL_NAME_TOOL) + +# Setup iOS platform unless specified manually with IOS_PLATFORM +if (NOT DEFINED IOS_PLATFORM) +set (IOS_PLATFORM "OS") +endif (NOT DEFINED IOS_PLATFORM) +set (IOS_PLATFORM ${IOS_PLATFORM} CACHE STRING "Type of iOS Platform") + +# Setup building for arm64 or not +if (NOT DEFINED BUILD_ARM64) +set (BUILD_ARM64 true) +endif (NOT DEFINED BUILD_ARM64) +set (BUILD_ARM64 ${BUILD_ARM64} CACHE STRING "Build arm64 arch or not") + +# Check the platform selection and setup for developer root +if (${IOS_PLATFORM} STREQUAL "OS") +set (IOS_PLATFORM_LOCATION "iPhoneOS.platform") + +# This causes the installers to properly locate the output libraries +set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-iphoneos") +elseif (${IOS_PLATFORM} STREQUAL "SIMULATOR") +set (SIMULATOR true) +set (IOS_PLATFORM_LOCATION "iPhoneSimulator.platform") + +# This causes the installers to properly locate the output libraries +set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-iphonesimulator") +elseif (${IOS_PLATFORM} STREQUAL "SIMULATOR64") +set (SIMULATOR true) +set (IOS_PLATFORM_LOCATION "iPhoneSimulator.platform") + +# This causes the installers to properly locate the output libraries +set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-iphonesimulator") +else (${IOS_PLATFORM} STREQUAL "OS") +message (FATAL_ERROR "Unsupported IOS_PLATFORM value selected. Please choose OS or SIMULATOR") +endif (${IOS_PLATFORM} STREQUAL "OS") + +# Setup iOS developer location unless specified manually with CMAKE_IOS_DEVELOPER_ROOT +# Note Xcode 4.3 changed the installation location, choose the most recent one available +exec_program(/usr/bin/xcode-select ARGS -print-path OUTPUT_VARIABLE CMAKE_XCODE_DEVELOPER_DIR) +set (XCODE_POST_43_ROOT "${CMAKE_XCODE_DEVELOPER_DIR}/Platforms/${IOS_PLATFORM_LOCATION}/Developer") +set (XCODE_PRE_43_ROOT "/Developer/Platforms/${IOS_PLATFORM_LOCATION}/Developer") +if (NOT DEFINED CMAKE_IOS_DEVELOPER_ROOT) +if (EXISTS ${XCODE_POST_43_ROOT}) +set (CMAKE_IOS_DEVELOPER_ROOT ${XCODE_POST_43_ROOT}) +elseif(EXISTS ${XCODE_PRE_43_ROOT}) +set (CMAKE_IOS_DEVELOPER_ROOT ${XCODE_PRE_43_ROOT}) +endif (EXISTS ${XCODE_POST_43_ROOT}) +endif (NOT DEFINED CMAKE_IOS_DEVELOPER_ROOT) +set (CMAKE_IOS_DEVELOPER_ROOT ${CMAKE_IOS_DEVELOPER_ROOT} CACHE PATH "Location of iOS Platform") + +# Find and use the most recent iOS sdk unless specified manually with CMAKE_IOS_SDK_ROOT +if (NOT DEFINED CMAKE_IOS_SDK_ROOT) +file (GLOB _CMAKE_IOS_SDKS "${CMAKE_IOS_DEVELOPER_ROOT}/SDKs/*") +if (_CMAKE_IOS_SDKS) +list (SORT _CMAKE_IOS_SDKS) +list (REVERSE _CMAKE_IOS_SDKS) +list (GET _CMAKE_IOS_SDKS 0 CMAKE_IOS_SDK_ROOT) +else (_CMAKE_IOS_SDKS) +message (FATAL_ERROR "No iOS SDK's found in default search path ${CMAKE_IOS_DEVELOPER_ROOT}. Manually set CMAKE_IOS_SDK_ROOT or install the iOS SDK.") +endif (_CMAKE_IOS_SDKS) +message (STATUS "Toolchain using default iOS SDK: ${CMAKE_IOS_SDK_ROOT}") +endif (NOT DEFINED CMAKE_IOS_SDK_ROOT) +set (CMAKE_IOS_SDK_ROOT ${CMAKE_IOS_SDK_ROOT} CACHE PATH "Location of the selected iOS SDK") + +# Set the sysroot default to the most recent SDK +set (CMAKE_OSX_SYSROOT ${CMAKE_IOS_SDK_ROOT} CACHE PATH "Sysroot used for iOS support") + +# set the architecture for iOS +if (${IOS_PLATFORM} STREQUAL "OS") +set (IOS_ARCH armv7 armv7s arm64) +elseif (${IOS_PLATFORM} STREQUAL "SIMULATOR") +set (IOS_ARCH i386) +elseif (${IOS_PLATFORM} STREQUAL "SIMULATOR64") +set (IOS_ARCH x86_64) +endif (${IOS_PLATFORM} STREQUAL "OS") + +set (CMAKE_OSX_ARCHITECTURES ${IOS_ARCH} CACHE string "Build architecture for iOS") + +# Set the find root to the iOS developer roots and to user defined paths +set (CMAKE_FIND_ROOT_PATH ${CMAKE_IOS_DEVELOPER_ROOT} ${CMAKE_IOS_SDK_ROOT} ${CMAKE_PREFIX_PATH} CACHE string "iOS find search path root") + +# default to searching for frameworks first +set (CMAKE_FIND_FRAMEWORK FIRST) + +# set up the default search directories for frameworks +set (CMAKE_SYSTEM_FRAMEWORK_PATH +${CMAKE_IOS_SDK_ROOT}/System/Library/Frameworks +${CMAKE_IOS_SDK_ROOT}/System/Library/PrivateFrameworks +${CMAKE_IOS_SDK_ROOT}/Developer/Library/Frameworks +) + +# only search the iOS sdks, not the remainder of the host filesystem +set (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY) +set (CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set (CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) + + +# This little macro lets you set any XCode specific property +macro (set_xcode_property TARGET XCODE_PROPERTY XCODE_VALUE) +set_property (TARGET ${TARGET} PROPERTY XCODE_ATTRIBUTE_${XCODE_PROPERTY} ${XCODE_VALUE}) +endmacro (set_xcode_property) + + +# This macro lets you find executable programs on the host system +macro (find_host_package) +set (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set (CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER) +set (CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER) +set (IOS FALSE) + +find_package(${ARGN}) + +set (IOS TRUE) +set (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY) +set (CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set (CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endmacro (find_host_package) diff --git a/3rdpart/ZLToolKit/src/Network/Buffer.cpp b/3rdpart/ZLToolKit/src/Network/Buffer.cpp new file mode 100644 index 0000000..b9a6b17 --- /dev/null +++ b/3rdpart/ZLToolKit/src/Network/Buffer.cpp @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include "Buffer.h" +#include "Util/onceToken.h" + +namespace toolkit { + +StatisticImp(Buffer) +StatisticImp(BufferRaw) +StatisticImp(BufferLikeString) + +BufferRaw::Ptr BufferRaw::create() { +#if 0 + static ResourcePool packet_pool; + static onceToken token([]() { + packet_pool.setSize(1024); + }); + auto ret = packet_pool.obtain2(); + ret->setSize(0); + return ret; +#else + return Ptr(new BufferRaw); +#endif +} + +}//namespace toolkit diff --git a/3rdpart/ZLToolKit/src/Network/Buffer.h b/3rdpart/ZLToolKit/src/Network/Buffer.h new file mode 100644 index 0000000..6724519 --- /dev/null +++ b/3rdpart/ZLToolKit/src/Network/Buffer.h @@ -0,0 +1,449 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef ZLTOOLKIT_BUFFER_H +#define ZLTOOLKIT_BUFFER_H + +#include +#include +#include +#include +#include +#include +#include "Util/util.h" +#include "Util/ResourcePool.h" + +namespace toolkit { + +template struct is_pointer : public std::false_type {}; +template struct is_pointer > : public std::true_type {}; +template struct is_pointer > : public std::true_type {}; +template struct is_pointer : public std::true_type {}; +template struct is_pointer : public std::true_type {}; + +//缓存基类 +class Buffer : public noncopyable { +public: + using Ptr = std::shared_ptr; + + Buffer() = default; + virtual ~Buffer() = default; + + //返回数据长度 + virtual char *data() const = 0; + virtual size_t size() const = 0; + + virtual std::string toString() const { + return std::string(data(), size()); + } + + virtual size_t getCapacity() const { + return size(); + } + +private: + //对象个数统计 + ObjectStatistic _statistic; +}; + +template +class BufferOffset : public Buffer { +public: + using Ptr = std::shared_ptr; + + BufferOffset(C data, size_t offset = 0, size_t len = 0) : _data(std::move(data)) { + setup(offset, len); + } + + ~BufferOffset() override = default; + + char *data() const override { + return const_cast(getPointer(_data)->data()) + _offset; + } + + size_t size() const override { + return _size; + } + + std::string toString() const override { + return std::string(data(), size()); + } + +private: + void setup(size_t offset = 0, size_t size = 0) { + auto max_size = getPointer(_data)->size(); + assert(offset + size <= max_size); + if (!size) { + size = max_size - offset; + } + _size = size; + _offset = offset; + } + + template + static typename std::enable_if<::toolkit::is_pointer::value, const T &>::type + getPointer(const T &data) { + return data; + } + + template + static typename std::enable_if::value, const T *>::type + getPointer(const T &data) { + return &data; + } + +private: + C _data; + size_t _size; + size_t _offset; +}; + +using BufferString = BufferOffset; + +//指针式缓存对象, +class BufferRaw : public Buffer { +public: + using Ptr = std::shared_ptr; + + static Ptr create(); + + ~BufferRaw() override { + if (_data) { + delete[] _data; + } + } + + //在写入数据时请确保内存是否越界 + char *data() const override { + return _data; + } + + //有效数据大小 + size_t size() const override { + return _size; + } + + //分配内存大小 + void setCapacity(size_t capacity) { + if (_data) { + do { + if (capacity > _capacity) { + //请求的内存大于当前内存,那么重新分配 + break; + } + + if (_capacity < 2 * 1024) { + //2K以下,不重复开辟内存,直接复用 + return; + } + + if (2 * capacity > _capacity) { + //如果请求的内存大于当前内存的一半,那么也复用 + return; + } + } while (false); + + delete[] _data; + } + _data = new char[capacity]; + _capacity = capacity; + } + + //设置有效数据大小 + virtual void setSize(size_t size) { + if (size > _capacity) { + throw std::invalid_argument("Buffer::setSize out of range"); + } + _size = size; + } + + //赋值数据 + void assign(const char *data, size_t size = 0) { + if (size <= 0) { + size = strlen(data); + } + setCapacity(size + 1); + memcpy(_data, data, size); + _data[size] = '\0'; + setSize(size); + } + + size_t getCapacity() const override { + return _capacity; + } + +protected: + friend class ResourcePool_l; + + BufferRaw(size_t capacity = 0) { + if (capacity) { + setCapacity(capacity); + } + } + + BufferRaw(const char *data, size_t size = 0) { + assign(data, size); + } + +private: + size_t _size = 0; + size_t _capacity = 0; + char *_data = nullptr; + //对象个数统计 + ObjectStatistic _statistic; +}; + +class BufferLikeString : public Buffer { +public: + ~BufferLikeString() override = default; + + BufferLikeString() { + _erase_head = 0; + _erase_tail = 0; + } + + BufferLikeString(std::string str) { + _str = std::move(str); + _erase_head = 0; + _erase_tail = 0; + } + + BufferLikeString &operator=(std::string str) { + _str = std::move(str); + _erase_head = 0; + _erase_tail = 0; + return *this; + } + + BufferLikeString(const char *str) { + _str = str; + _erase_head = 0; + _erase_tail = 0; + } + + BufferLikeString &operator=(const char *str) { + _str = str; + _erase_head = 0; + _erase_tail = 0; + return *this; + } + + BufferLikeString(BufferLikeString &&that) { + _str = std::move(that._str); + _erase_head = that._erase_head; + _erase_tail = that._erase_tail; + that._erase_head = 0; + that._erase_tail = 0; + } + + BufferLikeString &operator=(BufferLikeString &&that) { + _str = std::move(that._str); + _erase_head = that._erase_head; + _erase_tail = that._erase_tail; + that._erase_head = 0; + that._erase_tail = 0; + return *this; + } + + BufferLikeString(const BufferLikeString &that) { + _str = that._str; + _erase_head = that._erase_head; + _erase_tail = that._erase_tail; + } + + BufferLikeString &operator=(const BufferLikeString &that) { + _str = that._str; + _erase_head = that._erase_head; + _erase_tail = that._erase_tail; + return *this; + } + + char *data() const override { + return (char *) _str.data() + _erase_head; + } + + size_t size() const override { + return _str.size() - _erase_tail - _erase_head; + } + + BufferLikeString &erase(size_t pos = 0, size_t n = std::string::npos) { + if (pos == 0) { + //移除前面的数据 + if (n != std::string::npos) { + //移除部分 + if (n > size()) { + //移除太多数据了 + throw std::out_of_range("BufferLikeString::erase out_of_range in head"); + } + //设置起始便宜量 + _erase_head += n; + data()[size()] = '\0'; + return *this; + } + //移除全部数据 + _erase_head = 0; + _erase_tail = _str.size(); + data()[0] = '\0'; + return *this; + } + + if (n == std::string::npos || pos + n >= size()) { + //移除末尾所有数据 + if (pos >= size()) { + //移除太多数据 + throw std::out_of_range("BufferLikeString::erase out_of_range in tail"); + } + _erase_tail += size() - pos; + data()[size()] = '\0'; + return *this; + } + + //移除中间的 + if (pos + n > size()) { + //超过长度限制 + throw std::out_of_range("BufferLikeString::erase out_of_range in middle"); + } + _str.erase(_erase_head + pos, n); + return *this; + } + + BufferLikeString &append(const BufferLikeString &str) { + return append(str.data(), str.size()); + } + + BufferLikeString &append(const std::string &str) { + return append(str.data(), str.size()); + } + + BufferLikeString &append(const char *data) { + return append(data, strlen(data)); + } + + BufferLikeString &append(const char *data, size_t len) { + if (len <= 0) { + return *this; + } + if (_erase_head > _str.capacity() / 2) { + moveData(); + } + if (_erase_tail == 0) { + _str.append(data, len); + return *this; + } + _str.insert(_erase_head + size(), data, len); + return *this; + } + + void push_back(char c) { + if (_erase_tail == 0) { + _str.push_back(c); + return; + } + data()[size()] = c; + --_erase_tail; + data()[size()] = '\0'; + } + + BufferLikeString &insert(size_t pos, const char *s, size_t n) { + _str.insert(_erase_head + pos, s, n); + return *this; + } + + BufferLikeString &assign(const char *data) { + return assign(data, strlen(data)); + } + + BufferLikeString &assign(const char *data, size_t len) { + if (len <= 0) { + return *this; + } + if (data >= _str.data() && data < _str.data() + _str.size()) { + _erase_head = data - _str.data(); + if (data + len > _str.data() + _str.size()) { + throw std::out_of_range("BufferLikeString::assign out_of_range"); + } + _erase_tail = _str.data() + _str.size() - (data + len); + return *this; + } + _str.assign(data, len); + _erase_head = 0; + _erase_tail = 0; + return *this; + } + + void clear() { + _erase_head = 0; + _erase_tail = 0; + _str.clear(); + } + + char &operator[](size_t pos) { + if (pos >= size()) { + throw std::out_of_range("BufferLikeString::operator[] out_of_range"); + } + return data()[pos]; + } + + const char &operator[](size_t pos) const { + return (*const_cast(this))[pos]; + } + + size_t capacity() const { + return _str.capacity(); + } + + void reserve(size_t size) { + _str.reserve(size); + } + + void resize(size_t size, char c = '\0') { + _str.resize(size, c); + _erase_head = 0; + _erase_tail = 0; + } + + bool empty() const { + return size() <= 0; + } + + std::string substr(size_t pos, size_t n = std::string::npos) const { + if (n == std::string::npos) { + //获取末尾所有的 + if (pos >= size()) { + throw std::out_of_range("BufferLikeString::substr out_of_range"); + } + return _str.substr(_erase_head + pos, size() - pos); + } + + //获取部分 + if (pos + n > size()) { + throw std::out_of_range("BufferLikeString::substr out_of_range"); + } + return _str.substr(_erase_head + pos, n); + } + +private: + void moveData() { + if (_erase_head) { + _str.erase(0, _erase_head); + _erase_head = 0; + } + } + +private: + size_t _erase_head; + size_t _erase_tail; + std::string _str; + //对象个数统计 + ObjectStatistic _statistic; +}; + +}//namespace toolkit +#endif //ZLTOOLKIT_BUFFER_H diff --git a/3rdpart/ZLToolKit/src/Network/BufferSock.cpp b/3rdpart/ZLToolKit/src/Network/BufferSock.cpp new file mode 100644 index 0000000..1cf85a5 --- /dev/null +++ b/3rdpart/ZLToolKit/src/Network/BufferSock.cpp @@ -0,0 +1,434 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include "BufferSock.h" +#include "Util/logger.h" +#include "Util/uv_errno.h" + +#if defined(__linux__) || defined(__linux) + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#ifndef MSG_WAITFORONE +#define MSG_WAITFORONE 0x10000 +#endif + +#ifndef HAVE_MMSG_HDR +struct mmsghdr { + struct msghdr msg_hdr; + unsigned msg_len; +}; +#endif + +#ifndef HAVE_SENDMMSG_API +#include +#include +static inline int sendmmsg(int fd, struct mmsghdr *mmsg, + unsigned vlen, unsigned flags) +{ + return syscall(__NR_sendmmsg, fd, mmsg, vlen, flags); +} +#endif + +#ifndef HAVE_RECVMMSG_API +#include +#include +static inline int recvmmsg(int fd, struct mmsghdr *mmsg, + unsigned vlen, unsigned flags, struct timespec *timeout) +{ + return syscall(__NR_recvmmsg, fd, mmsg, vlen, flags, timeout); +} +#endif + +#endif// defined(__linux__) || defined(__linux) + +namespace toolkit { + +StatisticImp(BufferList) + +/////////////////////////////////////// BufferSock /////////////////////////////////////// + +BufferSock::BufferSock(Buffer::Ptr buffer, struct sockaddr *addr, int addr_len) { + if (addr) { + _addr_len = addr_len ? addr_len : SockUtil::get_sock_len(addr); + memcpy(&_addr, addr, _addr_len); + } + assert(buffer); + _buffer = std::move(buffer); +} + +char *BufferSock::data() const { + return _buffer->data(); +} + +size_t BufferSock::size() const { + return _buffer->size(); +} + +const struct sockaddr *BufferSock::sockaddr() const { + return (struct sockaddr *)&_addr; +} + +socklen_t BufferSock::socklen() const { + return _addr_len; +} + +/////////////////////////////////////// BufferCallBack /////////////////////////////////////// + +class BufferCallBack { +public: + BufferCallBack(List > list, BufferList::SendResult cb) + : _cb(std::move(cb)) + , _pkt_list(std::move(list)) {} + + ~BufferCallBack() { + sendCompleted(false); + } + + void sendCompleted(bool flag) { + if (_cb) { + //全部发送成功或失败回调 + while (!_pkt_list.empty()) { + _cb(_pkt_list.front().first, flag); + _pkt_list.pop_front(); + } + } else { + _pkt_list.clear(); + } + } + + void sendFrontSuccess() { + if (_cb) { + //发送成功回调 + _cb(_pkt_list.front().first, true); + } + _pkt_list.pop_front(); + } + +protected: + BufferList::SendResult _cb; + List > _pkt_list; +}; + +/////////////////////////////////////// BufferSendMsg /////////////////////////////////////// + +#if !defined(_WIN32) + +class BufferSendMsg : public BufferList, public BufferCallBack { +public: + BufferSendMsg(List > list, SendResult cb); + ~BufferSendMsg() override = default; + + bool empty() override; + size_t count() override; + ssize_t send(int fd, int flags) override; + +private: + void reOffset(size_t n); + ssize_t send_l(int fd, int flags); + +private: + size_t _iovec_off = 0; + size_t _remain_size = 0; + std::vector _iovec; +}; + +bool BufferSendMsg::empty() { + return _remain_size == 0; +} + +size_t BufferSendMsg::count() { + return _iovec.size() - _iovec_off; +} + +ssize_t BufferSendMsg::send_l(int fd, int flags) { + ssize_t n; + do { + struct msghdr msg; + msg.msg_name = nullptr; + msg.msg_namelen = 0; + msg.msg_iov = &(_iovec[_iovec_off]); + msg.msg_iovlen = _iovec.size() - _iovec_off; + if (msg.msg_iovlen > IOV_MAX) { + msg.msg_iovlen = IOV_MAX; + } + msg.msg_control = nullptr; + msg.msg_controllen = 0; + msg.msg_flags = flags; + n = sendmsg(fd, &msg, flags); + } while (-1 == n && UV_EINTR == get_uv_error(true)); + + if (n >= (ssize_t)_remain_size) { + //全部写完了 + _remain_size = 0; + sendCompleted(true); + return n; + } + + if (n > 0) { + //部分发送成功 + reOffset(n); + return n; + } + + //一个字节都未发送 + return n; +} + +ssize_t BufferSendMsg::send(int fd, int flags) { + auto remain_size = _remain_size; + while (_remain_size && send_l(fd, flags) != -1); + + ssize_t sent = remain_size - _remain_size; + if (sent > 0) { + //部分或全部发送成功 + return sent; + } + //一个字节都未发送成功 + return -1; +} + +void BufferSendMsg::reOffset(size_t n) { + _remain_size -= n; + size_t offset = 0; + for (auto i = _iovec_off; i != _iovec.size(); ++i) { + auto &ref = _iovec[i]; + offset += ref.iov_len; + if (offset < n) { + //此包发送完毕 + sendFrontSuccess(); + continue; + } + _iovec_off = i; + if (offset == n) { + //这是末尾发送完毕的一个包 + ++_iovec_off; + sendFrontSuccess(); + break; + } + //这是末尾发送部分成功的一个包 + size_t remain = offset - n; + ref.iov_base = (char *)ref.iov_base + ref.iov_len - remain; + ref.iov_len = remain; + break; + } +} + +BufferSendMsg::BufferSendMsg(List> list, SendResult cb) + : BufferCallBack(std::move(list), std::move(cb)) + , _iovec(_pkt_list.size()) { + auto it = _iovec.begin(); + _pkt_list.for_each([&](std::pair &pr) { + it->iov_base = pr.first->data(); + it->iov_len = pr.first->size(); + _remain_size += it->iov_len; + ++it; + }); +} + +#endif //!_WIN32 + +/////////////////////////////////////// BufferSendTo /////////////////////////////////////// + +class BufferSendTo : public BufferList, public BufferCallBack { +public: + BufferSendTo(List > list, SendResult cb, bool is_udp); + ~BufferSendTo() override = default; + + bool empty() override; + size_t count() override; + ssize_t send(int fd, int flags) override; + +private: + bool _is_udp; + size_t _offset = 0; +}; + +BufferSendTo::BufferSendTo(List> list, BufferList::SendResult cb, bool is_udp) + : BufferCallBack(std::move(list), std::move(cb)) + , _is_udp(is_udp) {} + +bool BufferSendTo::empty() { + return _pkt_list.empty(); +} + +size_t BufferSendTo::count() { + return _pkt_list.size(); +} + +static inline BufferSock *getBufferSockPtr(std::pair &pr) { + if (!pr.second) { + return nullptr; + } + return static_cast(pr.first.get()); +} + +ssize_t BufferSendTo::send(int fd, int flags) { + size_t sent = 0; + ssize_t n; + while (!_pkt_list.empty()) { + auto &front = _pkt_list.front(); + auto &buffer = front.first; + if (_is_udp) { + auto ptr = getBufferSockPtr(front); + n = ::sendto(fd, buffer->data() + _offset, buffer->size() - _offset, flags, ptr ? ptr->sockaddr() : nullptr, ptr ? ptr->socklen() : 0); + } else { + n = ::send(fd, buffer->data() + _offset, buffer->size() - _offset, flags); + } + + if (n >= 0) { + assert(n); + _offset += n; + if (_offset == buffer->size()) { + sendFrontSuccess(); + _offset = 0; + } + sent += n; + continue; + } + + //n == -1的情况 + if (get_uv_error(true) == UV_EINTR) { + //被打断,需要继续发送 + continue; + } + //其他原因导致的send返回-1 + break; + } + return sent ? sent : -1; +} + +/////////////////////////////////////// BufferSendMmsg /////////////////////////////////////// + +#if defined(__linux__) || defined(__linux) + +class BufferSendMMsg : public BufferList, public BufferCallBack { +public: + BufferSendMMsg(List > list, SendResult cb); + ~BufferSendMMsg() override = default; + + bool empty() override; + size_t count() override; + ssize_t send(int fd, int flags) override; + +private: + void reOffset(size_t n); + ssize_t send_l(int fd, int flags); + +private: + size_t _remain_size = 0; + std::vector _iovec; + std::vector _hdrvec; +}; + +bool BufferSendMMsg::empty() { + return _remain_size == 0; +} + +size_t BufferSendMMsg::count() { + return _hdrvec.size(); +} + +ssize_t BufferSendMMsg::send_l(int fd, int flags) { + ssize_t n; + do { + n = sendmmsg(fd, &_hdrvec[0], _hdrvec.size(), flags); + } while (-1 == n && UV_EINTR == get_uv_error(true)); + + if (n > 0) { + //部分或全部发送成功 + reOffset(n); + return n; + } + + //一个字节都未发送 + return n; +} + +ssize_t BufferSendMMsg::send(int fd, int flags) { + auto remain_size = _remain_size; + while (_remain_size && send_l(fd, flags) != -1); + ssize_t sent = remain_size - _remain_size; + if (sent > 0) { + //部分或全部发送成功 + return sent; + } + //一个字节都未发送成功 + return -1; +} + +void BufferSendMMsg::reOffset(size_t n) { + for (auto it = _hdrvec.begin(); it != _hdrvec.end();) { + auto &hdr = *it; + auto &io = *(hdr.msg_hdr.msg_iov); + assert(hdr.msg_len <= io.iov_len); + _remain_size -= hdr.msg_len; + if (hdr.msg_len == io.iov_len) { + //这个udp包全部发送成功 + it = _hdrvec.erase(it); + sendFrontSuccess(); + continue; + } + //部分发送成功 + io.iov_base = (char *)io.iov_base + hdr.msg_len; + io.iov_len -= hdr.msg_len; + break; + } +} + +BufferSendMMsg::BufferSendMMsg(List> list, SendResult cb) + : BufferCallBack(std::move(list), std::move(cb)) + , _iovec(_pkt_list.size()) + , _hdrvec(_pkt_list.size()) { + auto i = 0U; + _pkt_list.for_each([&](std::pair &pr) { + auto &io = _iovec[i]; + io.iov_base = pr.first->data(); + io.iov_len = pr.first->size(); + _remain_size += io.iov_len; + + auto ptr = getBufferSockPtr(pr); + auto &mmsg = _hdrvec[i]; + auto &msg = mmsg.msg_hdr; + mmsg.msg_len = 0; + msg.msg_name = ptr ? (void *)ptr->sockaddr() : nullptr; + msg.msg_namelen = ptr ? ptr->socklen() : 0; + msg.msg_iov = &io; + msg.msg_iovlen = 1; + msg.msg_control = nullptr; + msg.msg_controllen = 0; + msg.msg_flags = 0; + ++i; + }); +} + +#endif //defined(__linux__) || defined(__linux) + +BufferList::Ptr BufferList::create(List > list, SendResult cb, bool is_udp) { +#if defined(_WIN32) + //win32目前未做网络发送性能优化 + return std::make_shared(std::move(list), std::move(cb), is_udp); +#elif defined(__linux__) || defined(__linux) + if (is_udp) { + return std::make_shared(std::move(list), std::move(cb)); + } + return std::make_shared(std::move(list), std::move(cb)); +#else + if (is_udp) { + return std::make_shared(std::move(list), std::move(cb), is_udp); + } + return std::make_shared(std::move(list), std::move(cb)); +#endif +} + +} //toolkit diff --git a/3rdpart/ZLToolKit/src/Network/BufferSock.h b/3rdpart/ZLToolKit/src/Network/BufferSock.h new file mode 100644 index 0000000..0ee264d --- /dev/null +++ b/3rdpart/ZLToolKit/src/Network/BufferSock.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef ZLTOOLKIT_BUFFERSOCK_H +#define ZLTOOLKIT_BUFFERSOCK_H + +#if !defined(_WIN32) +#include +#include +#endif +#include +#include +#include +#include +#include +#include +#include "Util/util.h" +#include "Util/List.h" +#include "Util/ResourcePool.h" +#include "sockutil.h" +#include "Buffer.h" + +namespace toolkit { + +#if !defined(IOV_MAX) +#define IOV_MAX 1024 +#endif + +class BufferSock : public Buffer { +public: + using Ptr = std::shared_ptr; + BufferSock(Buffer::Ptr ptr, struct sockaddr *addr = nullptr, int addr_len = 0); + ~BufferSock() override = default; + + char *data() const override; + size_t size() const override; + const struct sockaddr *sockaddr() const; + socklen_t socklen() const; + +private: + int _addr_len = 0; + struct sockaddr_storage _addr; + Buffer::Ptr _buffer; +}; + +class BufferList : public noncopyable { +public: + using Ptr = std::shared_ptr; + using SendResult = std::function; + + BufferList() = default; + virtual ~BufferList() = default; + + virtual bool empty() = 0; + virtual size_t count() = 0; + virtual ssize_t send(int fd, int flags) = 0; + + static Ptr create(List > list, SendResult cb, bool is_udp); + +private: + //对象个数统计 + ObjectStatistic _statistic; +}; + +} +#endif //ZLTOOLKIT_BUFFERSOCK_H diff --git a/3rdpart/ZLToolKit/src/Network/Server.cpp b/3rdpart/ZLToolKit/src/Network/Server.cpp new file mode 100644 index 0000000..9ab0cb9 --- /dev/null +++ b/3rdpart/ZLToolKit/src/Network/Server.cpp @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2021 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include "Server.h" + +using namespace std; + +namespace toolkit { + +Server::Server(EventPoller::Ptr poller) { + _poller = poller ? std::move(poller) : EventPollerPool::Instance().getPoller(); +} + +//////////////////////////////////////////////////////////////////////////////////// + +SessionHelper::SessionHelper(const std::weak_ptr &server, Session::Ptr session) { + _server = server; + _session = std::move(session); + //记录session至全局的map,方便后面管理 + _session_map = SessionMap::Instance().shared_from_this(); + _identifier = _session->getIdentifier(); + _session_map->add(_identifier, _session); +} + +SessionHelper::~SessionHelper() { + if (!_server.lock()) { + //务必通知Session已从TcpServer脱离 + _session->onError(SockException(Err_other, "Server shutdown")); + } + //从全局map移除相关记录 + _session_map->del(_identifier); +} + +const Session::Ptr &SessionHelper::session() const { + return _session; +} + +//////////////////////////////////////////////////////////////////////////////////// + +bool SessionMap::add(const string &tag, const Session::Ptr &session) { + lock_guard lck(_mtx_session); + return _map_session.emplace(tag, session).second; +} + +bool SessionMap::del(const string &tag) { + lock_guard lck(_mtx_session); + return _map_session.erase(tag); +} + +Session::Ptr SessionMap::get(const string &tag) { + lock_guard lck(_mtx_session); + auto it = _map_session.find(tag); + if (it == _map_session.end()) { + return nullptr; + } + return it->second.lock(); +} + +void SessionMap::for_each_session(const function &cb) { + lock_guard lck(_mtx_session); + for (auto it = _map_session.begin(); it != _map_session.end();) { + auto session = it->second.lock(); + if (!session) { + it = _map_session.erase(it); + continue; + } + cb(it->first, session); + ++it; + } +} + +} // namespace toolkit \ No newline at end of file diff --git a/3rdpart/ZLToolKit/src/Network/Server.h b/3rdpart/ZLToolKit/src/Network/Server.h new file mode 100644 index 0000000..b3be89e --- /dev/null +++ b/3rdpart/ZLToolKit/src/Network/Server.h @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2021 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef ZLTOOLKIT_SERVER_H +#define ZLTOOLKIT_SERVER_H + +#include +#include "Util/mini.h" +#include "Session.h" + +namespace toolkit { + +// 全局的 Session 记录对象, 方便后面管理 +// 线程安全的 +class SessionMap : public std::enable_shared_from_this { +public: + friend class SessionHelper; + using Ptr = std::shared_ptr; + + //单例 + static SessionMap &Instance(); + ~SessionMap() = default; + + //获取Session + Session::Ptr get(const std::string &tag); + void for_each_session(const std::function &cb); + +private: + SessionMap() = default; + + //移除Session + bool del(const std::string &tag); + //添加Session + bool add(const std::string &tag, const Session::Ptr &session); + +private: + std::mutex _mtx_session; + std::unordered_map > _map_session; +}; + +class Server; + +class SessionHelper { +public: + using Ptr = std::shared_ptr; + + SessionHelper(const std::weak_ptr &server, Session::Ptr session); + ~SessionHelper(); + + const Session::Ptr &session() const; + +private: + std::string _identifier; + Session::Ptr _session; + SessionMap::Ptr _session_map; + std::weak_ptr _server; +}; + +// server 基类, 暂时仅用于剥离 SessionHelper 对 TcpServer 的依赖 +// 后续将 TCP 与 UDP 服务通用部分加到这里. +class Server : public std::enable_shared_from_this, public mINI { +public: + using Ptr = std::shared_ptr; + + explicit Server(EventPoller::Ptr poller = nullptr); + virtual ~Server() = default; + +protected: + EventPoller::Ptr _poller; +}; + +} // namespace toolkit + +#endif // ZLTOOLKIT_SERVER_H \ No newline at end of file diff --git a/3rdpart/ZLToolKit/src/Network/Session.cpp b/3rdpart/ZLToolKit/src/Network/Session.cpp new file mode 100644 index 0000000..15f1694 --- /dev/null +++ b/3rdpart/ZLToolKit/src/Network/Session.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2021 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include "Session.h" + +using namespace std; + +namespace toolkit { + +class TcpSession : public Session {}; +class UdpSession : public Session {}; + +StatisticImp(UdpSession) +StatisticImp(TcpSession) + +Session::Session(const Socket::Ptr &sock) : SocketHelper(sock) { + if (sock->sockType() == SockNum::Sock_TCP) { + _statistic_tcp.reset(new ObjectStatistic); + } else { + _statistic_udp.reset(new ObjectStatistic); + } +} + +static atomic s_session_index{0}; + +string Session::getIdentifier() const { + if (_id.empty()) { + _id = to_string(++s_session_index) + '-' + to_string(getSock()->rawFD()); + } + return _id; +} + +void Session::safeShutdown(const SockException &ex) { + std::weak_ptr weakSelf = shared_from_this(); + async_first([weakSelf,ex](){ + auto strongSelf = weakSelf.lock(); + if (strongSelf) { + strongSelf->shutdown(ex); + } + }); +} + +} // namespace toolkit diff --git a/3rdpart/ZLToolKit/src/Network/Session.h b/3rdpart/ZLToolKit/src/Network/Session.h new file mode 100644 index 0000000..e27db3b --- /dev/null +++ b/3rdpart/ZLToolKit/src/Network/Session.h @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2021 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef ZLTOOLKIT_SESSION_H +#define ZLTOOLKIT_SESSION_H + +#include +#include "Socket.h" +#include "Util/util.h" +#include "Util/SSLBox.h" + +namespace toolkit { + +// 会话, 用于存储一对客户端与服务端间的关系 +class Server; +class TcpSession; +class UdpSession; + +class Session : public std::enable_shared_from_this, public SocketHelper { +public: + using Ptr = std::shared_ptr; + + Session(const Socket::Ptr &sock); + ~Session() override = default; + + /** + * 接收数据入口 + * @param buf 数据,可以重复使用内存区,不可被缓存使用 + */ + virtual void onRecv(const Buffer::Ptr &buf) = 0; + + /** + * 收到 eof 或其他导致脱离 Server 事件的回调 + * 收到该事件时, 该对象一般将立即被销毁 + * @param err 原因 + */ + virtual void onError(const SockException &err) = 0; + + /** + * 每隔一段时间触发, 用来做超时管理 + */ + virtual void onManager() = 0; + + /** + * 在创建 Session 后, Server 会把自身的配置参数通过该函数传递给 Session + * @param server, 服务器对象 + */ + virtual void attachServer(const Server &server) {} + + /** + * 作为该 Session 的唯一标识符 + * @return 唯一标识符 + */ + std::string getIdentifier() const override; + + /** + * 线程安全的脱离 Server 并触发 onError 事件 + * @param ex 触发 onError 事件的原因 + */ + void safeShutdown(const SockException &ex = SockException(Err_shutdown, "self shutdown")); + +private: + mutable std::string _id; + std::unique_ptr > _statistic_tcp; + std::unique_ptr > _statistic_udp; +}; + +// 通过该模板可以让TCP服务器快速支持TLS +template +class SessionWithSSL : public SessionType { +public: + template + SessionWithSSL(ArgsType &&...args) + : SessionType(std::forward(args)...) { + _ssl_box.setOnEncData([&](const Buffer::Ptr &buf) { public_send(buf); }); + _ssl_box.setOnDecData([&](const Buffer::Ptr &buf) { public_onRecv(buf); }); + } + + ~SessionWithSSL() override { _ssl_box.flush(); } + + void onRecv(const Buffer::Ptr &buf) override { _ssl_box.onRecv(buf); } + + // 添加public_onRecv和public_send函数是解决较低版本gcc一个lambad中不能访问protected或private方法的bug + inline void public_onRecv(const Buffer::Ptr &buf) { SessionType::onRecv(buf); } + inline void public_send(const Buffer::Ptr &buf) { SessionType::send(std::move(const_cast(buf))); } + +protected: + ssize_t send(Buffer::Ptr buf) override { + auto size = buf->size(); + _ssl_box.onSend(std::move(buf)); + return size; + } + +private: + SSL_Box _ssl_box; +}; + +} // namespace toolkit + +#endif // ZLTOOLKIT_SESSION_H \ No newline at end of file diff --git a/3rdpart/ZLToolKit/src/Network/Socket.cpp b/3rdpart/ZLToolKit/src/Network/Socket.cpp new file mode 100644 index 0000000..f99ecf8 --- /dev/null +++ b/3rdpart/ZLToolKit/src/Network/Socket.cpp @@ -0,0 +1,1034 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include "sockutil.h" +#include "Socket.h" +#include "Util/util.h" +#include "Util/logger.h" +#include "Util/uv_errno.h" +#include "Thread/semaphore.h" +#include "Poller/EventPoller.h" +#include "Thread/WorkThreadPool.h" +using namespace std; + +#define LOCK_GUARD(mtx) lock_guard lck(mtx) + +namespace toolkit { + +StatisticImp(Socket) + +static SockException toSockException(int error) { + switch (error) { + case 0: + case UV_EAGAIN: return SockException(Err_success, "success"); + case UV_ECONNREFUSED: return SockException(Err_refused, uv_strerror(error), error); + case UV_ETIMEDOUT: return SockException(Err_timeout, uv_strerror(error), error); + default: return SockException(Err_other, uv_strerror(error), error); + } +} + +static SockException getSockErr(const SockFD::Ptr &sock, bool try_errno = true) { + int error = 0, len = sizeof(int); + getsockopt(sock->rawFd(), SOL_SOCKET, SO_ERROR, (char *) &error, (socklen_t *) &len); + if (error == 0) { + if (try_errno) { + error = get_uv_error(true); + } + } else { + error = uv_translate_posix_error(error); + } + return toSockException(error); +} + +Socket::Ptr Socket::createSocket(const EventPoller::Ptr &poller, bool enable_mutex){ + return std::make_shared(poller, enable_mutex); +} + +Socket::Socket(const EventPoller::Ptr &poller, bool enable_mutex) : + _mtx_sock_fd(enable_mutex), _mtx_event(enable_mutex), + _mtx_send_buf_waiting(enable_mutex), _mtx_send_buf_sending(enable_mutex){ + + _poller = poller; + if (!_poller) { + _poller = EventPollerPool::Instance().getPoller(); + } + setOnRead(nullptr); + setOnErr(nullptr); + setOnAccept(nullptr); + setOnFlush(nullptr); + setOnBeforeAccept(nullptr); + setOnSendResult(nullptr); +} + +Socket::~Socket() { + closeSock(); + //未发送完毕的数据主动触发onSendResult回调,防止_send_result可能先析构再触发回调时崩溃 + _send_buf_sending.clear(); +} + +void Socket::setOnRead(onReadCB cb) { + LOCK_GUARD(_mtx_event); + if (cb) { + _on_read = std::move(cb); + } else { + _on_read = [](const Buffer::Ptr &buf, struct sockaddr *, int) { + WarnL << "Socket not set read callback, data ignored: " << buf->size(); + }; + } +} + +void Socket::setOnErr(onErrCB cb) { + LOCK_GUARD(_mtx_event); + if (cb) { + _on_err = std::move(cb); + } else { + _on_err = [](const SockException &err) { + WarnL << "Socket not set err callback, err: " << err.what(); + }; + } +} + +void Socket::setOnAccept(onAcceptCB cb) { + LOCK_GUARD(_mtx_event); + if (cb) { + _on_accept = std::move(cb); + } else { + _on_accept = [](Socket::Ptr &sock, shared_ptr &complete) { + WarnL << "Socket not set accept callback, peer fd: " << sock->rawFD(); + }; + } +} + +void Socket::setOnFlush(onFlush cb) { + LOCK_GUARD(_mtx_event); + if (cb) { + _on_flush = std::move(cb); + } else { + _on_flush = []() {return true;}; + } +} + +void Socket::setOnBeforeAccept(onCreateSocket cb){ + LOCK_GUARD(_mtx_event); + if (cb) { + _on_before_accept = std::move(cb); + } else { + _on_before_accept = [](const EventPoller::Ptr &poller) { + return nullptr; + }; + } +} + +void Socket::setOnSendResult(onSendResult cb) { + LOCK_GUARD(_mtx_event); + _send_result = std::move(cb); +} + +#define CLOSE_SOCK(fd) if(fd != -1) {close(fd);} + +void Socket::connect(const string &url, uint16_t port, const onErrCB &con_cb_in, float timeout_sec, const string &local_ip, uint16_t local_port) { + weak_ptr weak_self = shared_from_this(); + _poller->async([=] { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + strong_self->connect_l(url, port, con_cb_in, timeout_sec, local_ip, local_port); + }); +} + +void Socket::connect_l(const string &url, uint16_t port, const onErrCB &con_cb_in, float timeout_sec, const string &local_ip, uint16_t local_port) { + //重置当前socket + closeSock(); + + weak_ptr weak_self = shared_from_this(); + auto con_cb = [con_cb_in, weak_self](const SockException &err) { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + strong_self->_async_con_cb = nullptr; + strong_self->_con_timer = nullptr; + if (err) { + LOCK_GUARD(strong_self->_mtx_sock_fd); + strong_self->_sock_fd = nullptr; + } + con_cb_in(err); + }; + + auto async_con_cb = std::make_shared >([weak_self, con_cb](int sock) { + auto strong_self = weak_self.lock(); + if (sock == -1 || !strong_self) { + if (!strong_self) { + CLOSE_SOCK(sock); + } else { + con_cb(SockException(Err_dns, get_uv_errmsg(true))); + } + return; + } + + auto sock_fd = strong_self->makeSock(sock, SockNum::Sock_TCP); + weak_ptr weak_sock_fd = sock_fd; + + //监听该socket是否可写,可写表明已经连接服务器成功 + int result = strong_self->_poller->addEvent(sock, EventPoller::Event_Write, [weak_self, weak_sock_fd, con_cb](int event) { + auto strong_sock_fd = weak_sock_fd.lock(); + auto strong_self = weak_self.lock(); + if (strong_sock_fd && strong_self) { + //socket可写事件,说明已经连接服务器成功 + strong_self->onConnected(strong_sock_fd, con_cb); + } + }); + + if (result == -1) { + con_cb(SockException(Err_other, "add event to poller failed when start connect")); + return; + } + + //保存fd + LOCK_GUARD(strong_self->_mtx_sock_fd); + strong_self->_sock_fd = std::move(sock_fd); + }); + + if (isIP(url.data())) { + (*async_con_cb)(SockUtil::connect(url.data(), port, true, local_ip.data(), local_port)); + } else { + auto poller = _poller; + weak_ptr> weak_task = async_con_cb; + WorkThreadPool::Instance().getExecutor()->async([url, port, local_ip, local_port, weak_task, poller]() { + //阻塞式dns解析放在后台线程执行 + int sock = SockUtil::connect(url.data(), port, true, local_ip.data(), local_port); + poller->async([sock, weak_task]() { + auto strong_task = weak_task.lock(); + if (strong_task) { + (*strong_task)(sock); + } else { + CLOSE_SOCK(sock); + } + }); + }); + _async_con_cb = async_con_cb; + } + + //连接超时定时器 + _con_timer = std::make_shared(timeout_sec, [weak_self, con_cb]() { + con_cb(SockException(Err_timeout, uv_strerror(UV_ETIMEDOUT))); + return false; + }, _poller); +} + +void Socket::onConnected(const SockFD::Ptr &sock, const onErrCB &cb) { + auto err = getSockErr(sock, false); + if (err) { + //连接失败 + cb(err); + return; + } + + //先删除之前的可写事件监听 + _poller->delEvent(sock->rawFd()); + if (!attachEvent(sock)) { + //连接失败 + cb(SockException(Err_other, "add event to poller failed when connected")); + return; + } + + sock->setConnected(); + //连接成功 + cb(err); +} + +bool Socket::attachEvent(const SockFD::Ptr &sock) { + weak_ptr weak_self = shared_from_this(); + weak_ptr weak_sock = sock; + _enable_recv = true; + _read_buffer = _poller->getSharedBuffer(); + auto is_udp = sock->type() == SockNum::Sock_UDP; + int result = _poller->addEvent(sock->rawFd(), EventPoller::Event_Read | EventPoller::Event_Error | EventPoller::Event_Write, [weak_self, weak_sock, is_udp](int event) { + auto strong_self = weak_self.lock(); + auto strong_sock = weak_sock.lock(); + if (!strong_self || !strong_sock) { + return; + } + + if (event & EventPoller::Event_Read) { + strong_self->onRead(strong_sock, is_udp); + } + if (event & EventPoller::Event_Write) { + strong_self->onWriteAble(strong_sock); + } + if (event & EventPoller::Event_Error) { + strong_self->emitErr(getSockErr(strong_sock)); + } + }); + + return -1 != result; +} + +ssize_t Socket::onRead(const SockFD::Ptr &sock, bool is_udp) noexcept{ + ssize_t ret = 0, nread = 0; + auto sock_fd = sock->rawFd(); + + auto data = _read_buffer->data(); + //最后一个字节设置为'\0' + auto capacity = _read_buffer->getCapacity() - 1; + + struct sockaddr_storage addr; + socklen_t len = sizeof(addr); + + while (_enable_recv) { + do { + nread = recvfrom(sock_fd, data, capacity, 0, (struct sockaddr *)&addr, &len); + } while (-1 == nread && UV_EINTR == get_uv_error(true)); + + if (nread == 0) { + if (!is_udp) { + emitErr(SockException(Err_eof, "end of file")); + } else { + WarnL << "Recv eof on udp socket[" << sock_fd << "]"; + } + return ret; + } + + if (nread == -1) { + auto err = get_uv_error(true); + if (err != UV_EAGAIN) { + if (!is_udp) { + emitErr(toSockException(err)); + } else { + WarnL << "Recv err on udp socket[" << sock_fd << "]: " << uv_strerror(err); + } + } + return ret; + } + + if (_enable_speed) { + // 更新接收速率 + _recv_speed += nread; + } + + ret += nread; + data[nread] = '\0'; + //设置buffer有效数据大小 + _read_buffer->setSize(nread); + + //触发回调 + LOCK_GUARD(_mtx_event); + try { + //此处捕获异常,目的是防止数据未读尽,epoll边沿触发失效的问题 + _on_read(_read_buffer, (struct sockaddr *)&addr, len); + } catch (std::exception &ex) { + ErrorL << "Exception occurred when emit on_read: " << ex.what(); + } + } + return 0; +} + +bool Socket::emitErr(const SockException& err) noexcept{ + { + LOCK_GUARD(_mtx_sock_fd); + if (!_sock_fd) { + //防止多次触发onErr事件 + return false; + } + } + + //先关闭socket,再触发on_err事件,防止死循环:https://github.com/ZLMediaKit/ZLMediaKit/issues/2121 + closeSock(); + weak_ptr weak_self = shared_from_this(); + _poller->async([weak_self, err]() { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + LOCK_GUARD(strong_self->_mtx_event); + try { + strong_self->_on_err(err); + } catch (std::exception &ex) { + ErrorL << "Exception occurred when emit on_err: " << ex.what(); + } + }); + return true; +} + +ssize_t Socket::send(const char *buf, size_t size, struct sockaddr *addr, socklen_t addr_len, bool try_flush) { + if (size <= 0) { + size = strlen(buf); + if (!size) { + return 0; + } + } + auto ptr = BufferRaw::create(); + ptr->assign(buf, size); + return send(std::move(ptr), addr, addr_len, try_flush); +} + +ssize_t Socket::send(string buf, struct sockaddr *addr, socklen_t addr_len, bool try_flush) { + return send(std::make_shared(std::move(buf)), addr, addr_len, try_flush); +} + +ssize_t Socket::send(Buffer::Ptr buf, struct sockaddr *addr, socklen_t addr_len, bool try_flush) { + if (!addr) { + return send_l(std::move(buf), false, try_flush); + } + return send_l(std::make_shared(std::move(buf), addr, addr_len), true, try_flush); +} + +ssize_t Socket::send_l(Buffer::Ptr buf, bool is_buf_sock, bool try_flush) { + auto size = buf ? buf->size() : 0; + if (!size) { + return 0; + } + + { + LOCK_GUARD(_mtx_send_buf_waiting); + _send_buf_waiting.emplace_back(std::move(buf), is_buf_sock); + } + + if (try_flush) { + if (flushAll()) { + return -1; + } + } + + return size; +} + +int Socket::flushAll() { + LOCK_GUARD(_mtx_sock_fd); + + if (!_sock_fd) { + //如果已断开连接或者发送超时 + return -1; + } + if (_sendable) { + //该socket可写 + return flushData(_sock_fd, false) ? 0 : -1; + } + + //该socket不可写,判断发送超时 + if (_send_flush_ticker.elapsedTime() > _max_send_buffer_ms) { + //如果发送列队中最老的数据距今超过超时时间限制,那么就断开socket连接 + emitErr(SockException(Err_other, "socket send timeout")); + return -1; + } + return 0; +} + +void Socket::onFlushed(const SockFD::Ptr &pSock) { + bool flag; + { + LOCK_GUARD(_mtx_event); + flag = _on_flush(); + } + if (!flag) { + setOnFlush(nullptr); + } +} + +void Socket::closeSock() { + _con_timer = nullptr; + _async_con_cb = nullptr; + + LOCK_GUARD(_mtx_sock_fd); + _sock_fd = nullptr; +} + +size_t Socket::getSendBufferCount(){ + size_t ret = 0; + { + LOCK_GUARD(_mtx_send_buf_waiting); + ret += _send_buf_waiting.size(); + } + + { + LOCK_GUARD(_mtx_send_buf_sending); + _send_buf_sending.for_each([&](BufferList::Ptr &buf) { + ret += buf->count(); + }); + } + return ret; +} + +uint64_t Socket::elapsedTimeAfterFlushed(){ + return _send_flush_ticker.elapsedTime(); +} + +int Socket::getRecvSpeed() { + _enable_speed = true; + return _recv_speed.getSpeed(); +} + +int Socket::getSendSpeed() { + _enable_speed = true; + return _send_speed.getSpeed(); +} + +bool Socket::listen(const SockFD::Ptr &sock){ + closeSock(); + weak_ptr weak_sock = sock; + weak_ptr weak_self = shared_from_this(); + _enable_recv = true; + int result = _poller->addEvent(sock->rawFd(), EventPoller::Event_Read | EventPoller::Event_Error, [weak_self, weak_sock](int event) { + auto strong_self = weak_self.lock(); + auto strong_sock = weak_sock.lock(); + if (!strong_self || !strong_sock) { + return; + } + strong_self->onAccept(strong_sock, event); + }); + + if (result == -1) { + return false; + } + + LOCK_GUARD(_mtx_sock_fd); + _sock_fd = sock; + return true; +} + +bool Socket::listen(uint16_t port, const string &local_ip, int backlog) { + int sock = SockUtil::listen(port, local_ip.data(), backlog); + if (sock == -1) { + return false; + } + return listen(makeSock(sock, SockNum::Sock_TCP)); +} + +bool Socket::bindUdpSock(uint16_t port, const string &local_ip, bool enable_reuse) { + closeSock(); + int fd = SockUtil::bindUdpSock(port, local_ip.data(), enable_reuse); + if (fd == -1) { + return false; + } + auto sock = makeSock(fd, SockNum::Sock_UDP); + if (!attachEvent(sock)) { + return false; + } + LOCK_GUARD(_mtx_sock_fd); + _sock_fd = std::move(sock); + return true; +} + +int Socket::onAccept(const SockFD::Ptr &sock, int event) noexcept { + int fd; + while (true) { + if (event & EventPoller::Event_Read) { + do { + fd = (int)accept(sock->rawFd(), nullptr, nullptr); + } while (-1 == fd && UV_EINTR == get_uv_error(true)); + + if (fd == -1) { + int err = get_uv_error(true); + if (err == UV_EAGAIN) { + //没有新连接 + return 0; + } + auto ex = toSockException(err); + emitErr(ex); + ErrorL << "Accept socket failed: " << ex.what(); + return -1; + } + + SockUtil::setNoSigpipe(fd); + SockUtil::setNoBlocked(fd); + SockUtil::setNoDelay(fd); + SockUtil::setSendBuf(fd); + SockUtil::setRecvBuf(fd); + SockUtil::setCloseWait(fd); + SockUtil::setCloExec(fd); + + Socket::Ptr peer_sock; + try { + //此处捕获异常,目的是防止socket未accept尽,epoll边沿触发失效的问题 + LOCK_GUARD(_mtx_event); + //拦截Socket对象的构造 + peer_sock = _on_before_accept(_poller); + } catch (std::exception &ex) { + ErrorL << "Exception occurred when emit on_before_accept: " << ex.what(); + close(fd); + continue; + } + + if (!peer_sock) { + //此处是默认构造行为,也就是子Socket共用父Socket的poll线程并且关闭互斥锁 + peer_sock = Socket::createSocket(_poller, false); + } + + //设置好fd,以备在onAccept事件中可以正常访问该fd + auto peer_sock_fd = peer_sock->setPeerSock(fd); + + shared_ptr completed(nullptr, [peer_sock, peer_sock_fd](void *) { + try { + //然后把该fd加入poll监听(确保先触发onAccept事件然后再触发onRead等事件) + if (!peer_sock->attachEvent(peer_sock_fd)) { + //加入poll监听失败,触发onErr事件,通知该Socket无效 + peer_sock->emitErr(SockException(Err_eof, "add event to poller failed when accept a socket")); + } + } catch (std::exception &ex) { + ErrorL << "Exception occurred: "<< ex.what(); + } + }); + + try { + //此处捕获异常,目的是防止socket未accept尽,epoll边沿触发失效的问题 + LOCK_GUARD(_mtx_event); + //先触发onAccept事件,此时应该监听该Socket的onRead等事件 + _on_accept(peer_sock, completed); + } catch (std::exception &ex) { + ErrorL << "Exception occurred when emit on_accept: " << ex.what(); + continue; + } + } + + if (event & EventPoller::Event_Error) { + auto ex = getSockErr(sock); + emitErr(ex); + ErrorL << "TCP listener occurred a err: " << ex.what(); + return -1; + } + } +} + +SockFD::Ptr Socket::setPeerSock(int fd) { + closeSock(); + auto sock = makeSock(fd, SockNum::Sock_TCP); + LOCK_GUARD(_mtx_sock_fd); + _sock_fd = sock; + return sock; +} + +string Socket::get_local_ip() { + LOCK_GUARD(_mtx_sock_fd); + if (!_sock_fd) { + return ""; + } + return SockUtil::get_local_ip(_sock_fd->rawFd()); +} + +uint16_t Socket::get_local_port() { + LOCK_GUARD(_mtx_sock_fd); + if (!_sock_fd) { + return 0; + } + return SockUtil::get_local_port(_sock_fd->rawFd()); +} + +string Socket::get_peer_ip() { + LOCK_GUARD(_mtx_sock_fd); + if (!_sock_fd) { + return ""; + } + return SockUtil::get_peer_ip(_sock_fd->rawFd()); +} + +uint16_t Socket::get_peer_port() { + LOCK_GUARD(_mtx_sock_fd); + if (!_sock_fd) { + return 0; + } + return SockUtil::get_peer_port(_sock_fd->rawFd()); +} + +string Socket::getIdentifier() const{ + static string class_name = "Socket: "; + return class_name + to_string(reinterpret_cast(this)); +} + +bool Socket::flushData(const SockFD::Ptr &sock, bool poller_thread) { + decltype(_send_buf_sending) send_buf_sending_tmp; + { + //转移出二级缓存 + LOCK_GUARD(_mtx_send_buf_sending); + if(!_send_buf_sending.empty()){ + send_buf_sending_tmp.swap(_send_buf_sending); + } + } + + if (send_buf_sending_tmp.empty()) { + _send_flush_ticker.resetTime(); + do { + { + //二级发送缓存为空,那么我们接着消费一级缓存中的数据 + LOCK_GUARD(_mtx_send_buf_waiting); + if (!_send_buf_waiting.empty()) { + //把一级缓中数数据放置到二级缓存中并清空 + LOCK_GUARD(_mtx_event); + auto send_result = _enable_speed ? [this](const Buffer::Ptr &buffer, bool send_success) { + if (send_success) { + //更新发送速率 + _send_speed += buffer->size(); + } + LOCK_GUARD(_mtx_event); + if (_send_result) { + _send_result(buffer, send_success); + } + } : _send_result; + send_buf_sending_tmp.emplace_back(BufferList::create(std::move(_send_buf_waiting), std::move(send_result), sock->type() == SockNum::Sock_UDP)); + break; + } + } + //如果一级缓存也为空,那么说明所有数据均写入socket了 + if (poller_thread) { + //poller线程触发该函数,那么该socket应该已经加入了可写事件的监听; + //那么在数据列队清空的情况下,我们需要关闭监听以免触发无意义的事件回调 + stopWriteAbleEvent(sock); + onFlushed(sock); + } + return true; + } while (0); + } + + int fd = sock->rawFd(); + bool is_udp = sock->type() == SockNum::Sock_UDP; + while (!send_buf_sending_tmp.empty()) { + auto &packet = send_buf_sending_tmp.front(); + auto n = packet->send(fd, _sock_flags); + if (n > 0) { + //全部或部分发送成功 + if (packet->empty()) { + //全部发送成功 + send_buf_sending_tmp.pop_front(); + continue; + } + //部分发送成功 + if (!poller_thread) { + //如果该函数是poller线程触发的,那么该socket应该已经加入了可写事件的监听,所以我们不需要再次加入监听 + startWriteAbleEvent(sock); + } + break; + } + + //一个都没发送成功 + int err = get_uv_error(true); + if (err == UV_EAGAIN) { + //等待下一次发送 + if (!poller_thread) { + //如果该函数是poller线程触发的,那么该socket应该已经加入了可写事件的监听,所以我们不需要再次加入监听 + startWriteAbleEvent(sock); + } + break; + } + + //其他错误代码,发生异常 + if (is_udp) { + // udp发送异常,把数据丢弃 + send_buf_sending_tmp.pop_front(); + WarnL << "Send udp socket[" << fd << "] failed, data ignored: " << uv_strerror(err); + continue; + } + // tcp发送失败时,触发异常 + emitErr(toSockException(err)); + return false; + } + + //回滚未发送完毕的数据 + if (!send_buf_sending_tmp.empty()) { + //有剩余数据 + LOCK_GUARD(_mtx_send_buf_sending); + send_buf_sending_tmp.swap(_send_buf_sending); + _send_buf_sending.append(send_buf_sending_tmp); + //二级缓存未全部发送完毕,说明该socket不可写,直接返回 + return true; + } + + //二级缓存已经全部发送完毕,说明该socket还可写,我们尝试继续写 + //如果是poller线程,我们尝试再次写一次(因为可能其他线程调用了send函数又有新数据了) + return poller_thread ? flushData(sock, poller_thread) : true; +} + +void Socket::onWriteAble(const SockFD::Ptr &sock) { + bool empty_waiting; + bool empty_sending; + { + LOCK_GUARD(_mtx_send_buf_waiting); + empty_waiting = _send_buf_waiting.empty(); + } + + { + LOCK_GUARD(_mtx_send_buf_sending); + empty_sending = _send_buf_sending.empty(); + } + + if (empty_waiting && empty_sending) { + //数据已经清空了,我们停止监听可写事件 + stopWriteAbleEvent(sock); + } else { + //socket可写,我们尝试发送剩余的数据 + flushData(sock, true); + } +} + +void Socket::startWriteAbleEvent(const SockFD::Ptr &sock) { + //开始监听socket可写事件 + _sendable = false; + int flag = _enable_recv ? EventPoller::Event_Read : 0; + _poller->modifyEvent(sock->rawFd(), flag | EventPoller::Event_Error | EventPoller::Event_Write); +} + +void Socket::stopWriteAbleEvent(const SockFD::Ptr &sock) { + //停止监听socket可写事件 + _sendable = true; + int flag = _enable_recv ? EventPoller::Event_Read : 0; + _poller->modifyEvent(sock->rawFd(), flag | EventPoller::Event_Error); +} + +void Socket::enableRecv(bool enabled) { + if (_enable_recv == enabled) { + return; + } + _enable_recv = enabled; + int read_flag = _enable_recv ? EventPoller::Event_Read : 0; + //可写时,不监听可写事件 + int send_flag = _sendable ? 0 : EventPoller::Event_Write; + _poller->modifyEvent(rawFD(), read_flag | send_flag | EventPoller::Event_Error); +} + +SockFD::Ptr Socket::makeSock(int sock, SockNum::SockType type) { + return std::make_shared(sock, type, _poller); +} + +int Socket::rawFD() const{ + LOCK_GUARD(_mtx_sock_fd); + if (!_sock_fd) { + return -1; + } + return _sock_fd->rawFd(); +} + +SockNum::SockType Socket::sockType() const { + LOCK_GUARD(_mtx_sock_fd); + if (!_sock_fd) { + return SockNum::Sock_Invalid; + } + return _sock_fd->type(); +} + +void Socket::setSendTimeOutSecond(uint32_t second){ + _max_send_buffer_ms = second * 1000; +} + +bool Socket::isSocketBusy() const{ + return !_sendable.load(); +} + +const EventPoller::Ptr &Socket::getPoller() const{ + return _poller; +} + +SockFD::Ptr Socket::cloneSockFD(const Socket &other) { + SockFD::Ptr sock; + { + LOCK_GUARD(other._mtx_sock_fd); + if (!other._sock_fd) { + WarnL << "sockfd of src socket is null"; + return nullptr; + } + sock = std::make_shared(*(other._sock_fd), _poller); + } + return sock; +} + +bool Socket::cloneFromListenSocket(const Socket &other) { + auto sock = cloneSockFD(other); + if (!sock) { + return false; + } + return listen(sock); +} + +bool Socket::cloneFromPeerSocket(const Socket &other) { + auto sock = cloneSockFD(other); + if (!sock) { + return false; + } + if (!attachEvent(sock)) { + return false; + } + LOCK_GUARD(_mtx_sock_fd); + _sock_fd = std::move(sock); + return true; +} + +bool Socket::bindPeerAddr(const struct sockaddr *dst_addr, socklen_t addr_len) { + LOCK_GUARD(_mtx_sock_fd); + if (!_sock_fd) { + return false; + } + if (_sock_fd->type() != SockNum::Sock_UDP) { + return false; + } + if (-1 == ::connect(_sock_fd->rawFd(), dst_addr, addr_len ? addr_len : SockUtil::get_sock_len(dst_addr))) { + WarnL << "Connect socket to peer address failed: " << SockUtil::inet_ntoa(dst_addr); + return false; + } + return true; +} + +void Socket::setSendFlags(int flags) { + _sock_flags = flags; +} + +///////////////SockSender/////////////////// + +SockSender &SockSender::operator<<(const char *buf) { + send(buf); + return *this; +} + +SockSender &SockSender::operator<<(string buf) { + send(std::move(buf)); + return *this; +} + +SockSender &SockSender::operator<<(Buffer::Ptr buf) { + send(std::move(buf)); + return *this; +} + +ssize_t SockSender::send(string buf) { + return send(std::make_shared(std::move(buf))); +} + +ssize_t SockSender::send(const char *buf, size_t size) { + auto buffer = BufferRaw::create(); + buffer->assign(buf, size); + return send(std::move(buffer)); +} + +///////////////SocketHelper/////////////////// + +SocketHelper::SocketHelper(const Socket::Ptr &sock) { + setSock(sock); + setOnCreateSocket(nullptr); +} + +void SocketHelper::setPoller(const EventPoller::Ptr &poller){ + _poller = poller; +} + +void SocketHelper::setSock(const Socket::Ptr &sock) { + _peer_port = 0; + _local_port = 0; + _peer_ip.clear(); + _local_ip.clear(); + _sock = sock; + if (_sock) { + _poller = _sock->getPoller(); + } +} + +const EventPoller::Ptr& SocketHelper::getPoller() const { + assert(_poller); + return _poller; +} + +const Socket::Ptr& SocketHelper::getSock() const{ + return _sock; +} + +int SocketHelper::flushAll() { + if (!_sock) { + return -1; + } + return _sock->flushAll(); +} + +ssize_t SocketHelper::send(Buffer::Ptr buf) { + if (!_sock) { + return -1; + } + return _sock->send(std::move(buf), nullptr, 0, _try_flush); +} + +void SocketHelper::shutdown(const SockException &ex) { + if (_sock) { + _sock->emitErr(ex); + } +} + +string SocketHelper::get_local_ip() { + if (_sock && _local_ip.empty()) { + _local_ip = _sock->get_local_ip(); + } + return _local_ip; +} + +uint16_t SocketHelper::get_local_port() { + if (_sock && _local_port == 0) { + _local_port = _sock->get_local_port(); + } + return _local_port; +} + +string SocketHelper::get_peer_ip() { + if (_sock && _peer_ip.empty()) { + _peer_ip = _sock->get_peer_ip(); + } + return _peer_ip; +} + +uint16_t SocketHelper::get_peer_port() { + if (_sock && _peer_port == 0) { + _peer_port = _sock->get_peer_port(); + } + return _peer_port; +} + +bool SocketHelper::isSocketBusy() const { + if (!_sock) { + return true; + } + return _sock->isSocketBusy(); +} + +Task::Ptr SocketHelper::async(TaskIn task, bool may_sync) { + return _poller->async(std::move(task), may_sync); +} + +Task::Ptr SocketHelper::async_first(TaskIn task, bool may_sync) { + return _poller->async_first(std::move(task), may_sync); +} + +void SocketHelper::setSendFlushFlag(bool try_flush) { + _try_flush = try_flush; +} + +void SocketHelper::setSendFlags(int flags) { + if (!_sock) { + return; + } + _sock->setSendFlags(flags); +} + +void SocketHelper::setOnCreateSocket(Socket::onCreateSocket cb){ + if (cb) { + _on_create_socket = std::move(cb); + } else { + _on_create_socket = [](const EventPoller::Ptr &poller) { + return Socket::createSocket(poller, false); + }; + } +} + +Socket::Ptr SocketHelper::createSocket(){ + return _on_create_socket(_poller); +} + +std::ostream &operator<<(std::ostream &ost, const SockException &err) { + ost << err.getErrCode() << "(" << err.what() << ")"; + return ost; +} + +} // namespace toolkit + + + diff --git a/3rdpart/ZLToolKit/src/Network/Socket.h b/3rdpart/ZLToolKit/src/Network/Socket.h new file mode 100644 index 0000000..cb47fc7 --- /dev/null +++ b/3rdpart/ZLToolKit/src/Network/Socket.h @@ -0,0 +1,682 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef NETWORK_SOCKET_H +#define NETWORK_SOCKET_H + +#include +#include +#include +#include +#include +#include +#include "Util/SpeedStatistic.h" +#include "sockutil.h" +#include "Poller/Timer.h" +#include "Poller/EventPoller.h" +#include "BufferSock.h" + +namespace toolkit { + +#if defined(MSG_NOSIGNAL) +#define FLAG_NOSIGNAL MSG_NOSIGNAL +#else +#define FLAG_NOSIGNAL 0 +#endif //MSG_NOSIGNAL + +#if defined(MSG_MORE) +#define FLAG_MORE MSG_MORE +#else +#define FLAG_MORE 0 +#endif //MSG_MORE + +#if defined(MSG_DONTWAIT) +#define FLAG_DONTWAIT MSG_DONTWAIT +#else +#define FLAG_DONTWAIT 0 +#endif //MSG_DONTWAIT + +//默认的socket flags:不触发SIGPIPE,非阻塞发送 +#define SOCKET_DEFAULE_FLAGS (FLAG_NOSIGNAL | FLAG_DONTWAIT ) + +//发送超时时间,如果在规定时间内一直没有发送数据成功,那么将触发onErr事件 +#define SEND_TIME_OUT_SEC 10 + +//错误类型枚举 +typedef enum { + Err_success = 0, //成功 + Err_eof, //eof + Err_timeout, //超时 + Err_refused,//连接被拒绝 + Err_dns,//dns解析失败 + Err_shutdown,//主动关闭 + Err_other = 0xFF,//其他错误 +} ErrCode; + +//错误信息类 +class SockException : public std::exception { +public: + SockException(ErrCode code = Err_success, const std::string &msg = "", int custom_code = 0) { + _msg = msg; + _code = code; + _custom_code = custom_code; + } + + //重置错误 + void reset(ErrCode code, const std::string &msg, int custom_code = 0) { + _msg = msg; + _code = code; + _custom_code = custom_code; + } + + //错误提示 + const char *what() const noexcept override { + return _msg.c_str(); + } + + //错误代码 + ErrCode getErrCode() const { + return _code; + } + + //用户自定义错误代码 + int getCustomCode() const { + return _custom_code; + } + + //判断是否真的有错 + operator bool() const { + return _code != Err_success; + } + +private: + ErrCode _code; + int _custom_code = 0; + std::string _msg; +}; + +//std::cout等输出流可以直接输出SockException对象 +std::ostream &operator<<(std::ostream &ost, const SockException &err); + +class SockNum { +public: + using Ptr = std::shared_ptr; + + typedef enum { + Sock_Invalid = -1, + Sock_TCP = 0, + Sock_UDP = 1 + } SockType; + + SockNum(int fd, SockType type) { + _fd = fd; + _type = type; + } + + ~SockNum() { +#if defined (OS_IPHONE) + unsetSocketOfIOS(_fd); +#endif //OS_IPHONE + ::shutdown(_fd, SHUT_RDWR); + close(_fd); + } + + int rawFd() const { + return _fd; + } + + SockType type() { + return _type; + } + + void setConnected() { +#if defined (OS_IPHONE) + setSocketOfIOS(_fd); +#endif //OS_IPHONE + } + +#if defined (OS_IPHONE) +private: + void *readStream=nullptr; + void *writeStream=nullptr; + bool setSocketOfIOS(int socket); + void unsetSocketOfIOS(int socket); +#endif //OS_IPHONE + +private: + int _fd; + SockType _type; +}; + +//socket 文件描述符的包装 +//在析构时自动溢出监听并close套接字 +//防止描述符溢出 +class SockFD : public noncopyable { +public: + using Ptr = std::shared_ptr; + + /** + * 创建一个fd对象 + * @param num 文件描述符,int数字 + * @param poller 事件监听器 + */ + SockFD(int num, SockNum::SockType type, const EventPoller::Ptr &poller) { + _num = std::make_shared(num, type); + _poller = poller; + } + + /** + * 复制一个fd对象 + * @param that 源对象 + * @param poller 事件监听器 + */ + SockFD(const SockFD &that, const EventPoller::Ptr &poller) { + _num = that._num; + _poller = poller; + if (_poller == that._poller) { + throw std::invalid_argument("Copy a SockFD with same poller"); + } + } + + ~SockFD() { + auto num = _num; + _poller->delEvent(_num->rawFd(), [num](bool) {}); + } + + void setConnected() { + _num->setConnected(); + } + + int rawFd() const { + return _num->rawFd(); + } + + SockNum::SockType type() { + return _num->type(); + } + +private: + SockNum::Ptr _num; + EventPoller::Ptr _poller; +}; + +template +class MutexWrapper { +public: + MutexWrapper(bool enable) { + _enable = enable; + } + + ~MutexWrapper() = default; + + inline void lock() { + if (_enable) { + _mtx.lock(); + } + } + + inline void unlock() { + if (_enable) { + _mtx.unlock(); + } + } + +private: + bool _enable; + Mtx _mtx; +}; + +class SockInfo { +public: + SockInfo() = default; + virtual ~SockInfo() = default; + + //获取本机ip + virtual std::string get_local_ip() = 0; + //获取本机端口号 + virtual uint16_t get_local_port() = 0; + //获取对方ip + virtual std::string get_peer_ip() = 0; + //获取对方端口号 + virtual uint16_t get_peer_port() = 0; + //获取标识符 + virtual std::string getIdentifier() const { return ""; } +}; + +#define TraceP(ptr) TraceL << ptr->getIdentifier() << "(" << ptr->get_peer_ip() << ":" << ptr->get_peer_port() << ") " +#define DebugP(ptr) DebugL << ptr->getIdentifier() << "(" << ptr->get_peer_ip() << ":" << ptr->get_peer_port() << ") " +#define InfoP(ptr) InfoL << ptr->getIdentifier() << "(" << ptr->get_peer_ip() << ":" << ptr->get_peer_port() << ") " +#define WarnP(ptr) WarnL << ptr->getIdentifier() << "(" << ptr->get_peer_ip() << ":" << ptr->get_peer_port() << ") " +#define ErrorP(ptr) ErrorL << ptr->getIdentifier() << "(" << ptr->get_peer_ip() << ":" << ptr->get_peer_port() << ") " + +//异步IO Socket对象,包括tcp客户端、服务器和udp套接字 +class Socket : public std::enable_shared_from_this, public noncopyable, public SockInfo { +public: + using Ptr = std::shared_ptr; + //接收数据回调 + using onReadCB = std::function; + //发生错误回调 + using onErrCB = std::function; + //tcp监听接收到连接请求 + using onAcceptCB = std::function &complete)>; + //socket发送缓存清空事件,返回true代表下次继续监听该事件,否则停止 + using onFlush = std::function; + //在接收到连接请求前,拦截Socket默认生成方式 + using onCreateSocket = std::function; + //发送buffer成功与否回调 + using onSendResult = BufferList::SendResult; + + /** + * 构造socket对象,尚未有实质操作 + * @param poller 绑定的poller线程 + * @param enable_mutex 是否启用互斥锁(接口是否线程安全) + */ + static Ptr createSocket(const EventPoller::Ptr &poller = nullptr, bool enable_mutex = true); + Socket(const EventPoller::Ptr &poller = nullptr, bool enable_mutex = true); + ~Socket() override; + + /** + * 创建tcp客户端并异步连接服务器 + * @param url 目标服务器ip或域名 + * @param port 目标服务器端口 + * @param con_cb 结果回调 + * @param timeout_sec 超时时间 + * @param local_ip 绑定本地网卡ip + * @param local_port 绑定本地网卡端口号 + */ + virtual void connect(const std::string &url, uint16_t port, const onErrCB &con_cb, float timeout_sec = 5, + const std::string &local_ip = "::", uint16_t local_port = 0); + + /** + * 创建tcp监听服务器 + * @param port 监听端口,0则随机 + * @param local_ip 监听的网卡ip + * @param backlog tcp最大积压数 + * @return 是否成功 + */ + virtual bool listen(uint16_t port, const std::string &local_ip = "::", int backlog = 1024); + + /** + * 创建udp套接字,udp是无连接的,所以可以作为服务器和客户端 + * @param port 绑定的端口为0则随机 + * @param local_ip 绑定的网卡ip + * @return 是否成功 + */ + virtual bool bindUdpSock(uint16_t port, const std::string &local_ip = "::", bool enable_reuse = true); + + ////////////设置事件回调//////////// + + /** + * 设置数据接收回调,tcp或udp客户端有效 + * @param cb 回调对象 + */ + virtual void setOnRead(onReadCB cb); + + /** + * 设置异常事件(包括eof等)回调 + * @param cb 回调对象 + */ + virtual void setOnErr(onErrCB cb); + + /** + * 设置tcp监听接收到连接回调 + * @param cb 回调对象 + */ + virtual void setOnAccept(onAcceptCB cb); + + /** + * 设置socket写缓存清空事件回调 + * 通过该回调可以实现发送流控 + * @param cb 回调对象 + */ + virtual void setOnFlush(onFlush cb); + + /** + * 设置accept时,socket构造事件回调 + * @param cb 回调 + */ + virtual void setOnBeforeAccept(onCreateSocket cb); + + /** + * 设置发送buffer结果回调 + * @param cb 回调 + */ + virtual void setOnSendResult(onSendResult cb); + + ////////////发送数据相关接口//////////// + + /** + * 发送数据指针 + * @param buf 数据指针 + * @param size 数据长度 + * @param addr 目标地址 + * @param addr_len 目标地址长度 + * @param try_flush 是否尝试写socket + * @return -1代表失败(socket无效),0代表数据长度为0,否则返回数据长度 + */ + ssize_t send(const char *buf, size_t size = 0, struct sockaddr *addr = nullptr, socklen_t addr_len = 0, bool try_flush = true); + + /** + * 发送string + */ + ssize_t send(std::string buf, struct sockaddr *addr = nullptr, socklen_t addr_len = 0, bool try_flush = true); + + /** + * 发送Buffer对象,Socket对象发送数据的统一出口 + * socket对象发送数据的统一出口 + */ + virtual ssize_t send(Buffer::Ptr buf, struct sockaddr *addr = nullptr, socklen_t addr_len = 0, bool try_flush = true); + + /** + * 尝试将所有数据写socket + * @return -1代表失败(socket无效或者发送超时),0代表成功? + */ + int flushAll(); + + /** + * 关闭socket且触发onErr回调,onErr回调将在poller线程中进行 + * @param err 错误原因 + * @return 是否成功触发onErr回调 + */ + virtual bool emitErr(const SockException &err) noexcept; + + /** + * 关闭或开启数据接收 + * @param enabled 是否开启 + */ + virtual void enableRecv(bool enabled); + + /** + * 获取裸文件描述符,请勿进行close操作(因为Socket对象会管理其生命周期) + * @return 文件描述符 + */ + virtual int rawFD() const; + + /** + * 返回socket类型 + */ + virtual SockNum::SockType sockType() const; + + /** + * 设置发送超时主动断开时间;默认10秒 + * @param second 发送超时数据,单位秒 + */ + virtual void setSendTimeOutSecond(uint32_t second); + + /** + * 套接字是否忙,如果套接字写缓存已满则返回true + * @return 套接字是否忙 + */ + virtual bool isSocketBusy() const; + + /** + * 获取poller线程对象 + * @return poller线程对象 + */ + virtual const EventPoller::Ptr &getPoller() const; + + /** + * 从另外一个Socket克隆 + * 目的是一个socket可以被多个poller对象监听,提高性能 + * @param other 原始的socket对象 + * @return 是否成功 + */ + virtual bool cloneFromListenSocket(const Socket &other); + + /** + * 从源tcp peer socket或udp socket克隆 + * 目的是为了实现socket切换poller线程 + */ + virtual bool cloneFromPeerSocket(const Socket &other); + + /** + * 绑定udp 目标地址,后续发送时就不用再单独指定了 + * @param dst_addr 目标地址 + * @param addr_len 目标地址长度 + * @return 是否成功 + */ + virtual bool bindPeerAddr(const struct sockaddr *dst_addr, socklen_t addr_len = 0); + + /** + * 设置发送flags + * @param flags 发送的flag + */ + virtual void setSendFlags(int flags = SOCKET_DEFAULE_FLAGS); + + /** + * 关闭套接字 + */ + virtual void closeSock(); + + /** + * 获取发送缓存包个数(不是字节数) + */ + virtual size_t getSendBufferCount(); + + /** + * 获取上次socket发送缓存清空至今的毫秒数,单位毫秒 + */ + virtual uint64_t elapsedTimeAfterFlushed(); + + /** + * 获取接收速率,单位bytes/s + */ + int getRecvSpeed(); + + /** + * 获取发送速率,单位bytes/s + */ + int getSendSpeed(); + + ////////////SockInfo override//////////// + std::string get_local_ip() override; + uint16_t get_local_port() override; + std::string get_peer_ip() override; + uint16_t get_peer_port() override; + std::string getIdentifier() const override; + +private: + SockFD::Ptr cloneSockFD(const Socket &other); + SockFD::Ptr setPeerSock(int fd); + SockFD::Ptr makeSock(int sock, SockNum::SockType type); + int onAccept(const SockFD::Ptr &sock, int event) noexcept; + ssize_t onRead(const SockFD::Ptr &sock, bool is_udp = false) noexcept; + void onWriteAble(const SockFD::Ptr &sock); + void onConnected(const SockFD::Ptr &sock, const onErrCB &cb); + void onFlushed(const SockFD::Ptr &pSock); + void startWriteAbleEvent(const SockFD::Ptr &sock); + void stopWriteAbleEvent(const SockFD::Ptr &sock); + bool listen(const SockFD::Ptr &sock); + bool flushData(const SockFD::Ptr &sock, bool poller_thread); + bool attachEvent(const SockFD::Ptr &sock); + ssize_t send_l(Buffer::Ptr buf, bool is_buf_sock, bool try_flush = true); + void connect_l(const std::string &url, uint16_t port, const onErrCB &con_cb_in, float timeout_sec, const std::string &local_ip, uint16_t local_port); + +private: + //send socket时的flag + int _sock_flags = SOCKET_DEFAULE_FLAGS; + //最大发送缓存,单位毫秒,距上次发送缓存清空时间不能超过该参数 + uint32_t _max_send_buffer_ms = SEND_TIME_OUT_SEC * 1000; + //控制是否接收监听socket可读事件,关闭后可用于流量控制 + std::atomic _enable_recv {true}; + //标记该socket是否可写,socket写缓存满了就不可写 + std::atomic _sendable {true}; + + //tcp连接超时定时器 + Timer::Ptr _con_timer; + //tcp连接结果回调对象 + std::shared_ptr > _async_con_cb; + + //记录上次发送缓存(包括socket写缓存、应用层缓存)清空的计时器 + Ticker _send_flush_ticker; + //复用的socket读缓存,每次read socket后,数据存放在此 + BufferRaw::Ptr _read_buffer; + //socket fd的抽象类 + SockFD::Ptr _sock_fd; + //本socket绑定的poller线程,事件触发于此线程 + EventPoller::Ptr _poller; + //跨线程访问_sock_fd时需要上锁 + mutable MutexWrapper _mtx_sock_fd; + + //socket异常事件(比如说断开) + onErrCB _on_err; + //收到数据事件 + onReadCB _on_read; + //socket缓存清空事件(可用于发送流速控制) + onFlush _on_flush; + //tcp监听收到accept请求事件 + onAcceptCB _on_accept; + //tcp监听收到accept请求,自定义创建peer Socket事件(可以控制子Socket绑定到其他poller线程) + onCreateSocket _on_before_accept; + //设置上述回调函数的锁 + MutexWrapper _mtx_event; + + //一级发送缓存, socket可写时,会把一级缓存批量送入到二级缓存 + List > _send_buf_waiting; + //一级发送缓存锁 + MutexWrapper _mtx_send_buf_waiting; + //二级发送缓存, socket可写时,会把二级缓存批量写入到socket + List _send_buf_sending; + //二级发送缓存锁 + MutexWrapper _mtx_send_buf_sending; + //发送buffer结果回调 + BufferList::SendResult _send_result; + //对象个数统计 + ObjectStatistic _statistic; + + //是否启用网速统计 + bool _enable_speed = false; + //接收速率统计 + BytesSpeed _recv_speed; + //发送速率统计 + BytesSpeed _send_speed; +}; + +class SockSender { +public: + SockSender() = default; + virtual ~SockSender() = default; + virtual ssize_t send(Buffer::Ptr buf) = 0; + virtual void shutdown(const SockException &ex = SockException(Err_shutdown, "self shutdown")) = 0; + + //发送char * + SockSender &operator << (const char *buf); + //发送字符串 + SockSender &operator << (std::string buf); + //发送Buffer对象 + SockSender &operator << (Buffer::Ptr buf); + + //发送其他类型是数据 + template + SockSender &operator << (T &&buf) { + std::ostringstream ss; + ss << std::forward(buf); + send(ss.str()); + return *this; + } + + ssize_t send(std::string buf); + ssize_t send(const char *buf, size_t size = 0); +}; + +//Socket对象的包装类 +class SocketHelper : public SockSender, public SockInfo, public TaskExecutorInterface { +public: + SocketHelper(const Socket::Ptr &sock); + ~SocketHelper() override = default; + + ///////////////////// Socket util std::functions ///////////////////// + /** + * 获取poller线程 + */ + const EventPoller::Ptr& getPoller() const; + + /** + * 设置批量发送标记,用于提升性能 + * @param try_flush 批量发送标记 + */ + void setSendFlushFlag(bool try_flush); + + /** + * 设置socket发送flags + * @param flags socket发送flags + */ + void setSendFlags(int flags); + + /** + * 套接字是否忙,如果套接字写缓存已满则返回true + */ + bool isSocketBusy() const; + + /** + * 设置Socket创建器,自定义Socket创建方式 + * @param cb 创建器 + */ + void setOnCreateSocket(Socket::onCreateSocket cb); + + /** + * 创建socket对象 + */ + Socket::Ptr createSocket(); + + /** + * 获取socket对象 + */ + const Socket::Ptr &getSock() const; + + /** + * 尝试将所有数据写socket + * @return -1代表失败(socket无效或者发送超时),0代表成功? + */ + int flushAll(); + + ///////////////////// SockInfo override ///////////////////// + std::string get_local_ip() override; + uint16_t get_local_port() override; + std::string get_peer_ip() override; + uint16_t get_peer_port() override; + + ///////////////////// TaskExecutorInterface override ///////////////////// + /** + * 任务切换到所属poller线程执行 + * @param task 任务 + * @param may_sync 是否运行同步执行任务 + */ + Task::Ptr async(TaskIn task, bool may_sync = true) override; + Task::Ptr async_first(TaskIn task, bool may_sync = true) override; + + ///////////////////// SockSender override ///////////////////// + /** + * 统一发送数据的出口 + */ + ssize_t send(Buffer::Ptr buf) override; + + /** + * 触发onErr事件 + */ + void shutdown(const SockException &ex = SockException(Err_shutdown, "self shutdown")) override; + +protected: + void setPoller(const EventPoller::Ptr &poller); + void setSock(const Socket::Ptr &sock); + +private: + bool _try_flush = true; + uint16_t _peer_port = 0; + uint16_t _local_port = 0; + std::string _peer_ip; + std::string _local_ip; + Socket::Ptr _sock; + EventPoller::Ptr _poller; + Socket::onCreateSocket _on_create_socket; +}; + +} // namespace toolkit +#endif /* NETWORK_SOCKET_H */ diff --git a/3rdpart/ZLToolKit/src/Network/Socket_ios.mm b/3rdpart/ZLToolKit/src/Network/Socket_ios.mm new file mode 100644 index 0000000..30a9fa7 --- /dev/null +++ b/3rdpart/ZLToolKit/src/Network/Socket_ios.mm @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/xia-chu/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ +#import "Socket.h" +#include "Util/logger.h" + +#if defined (OS_IPHONE) +#import +#endif //OS_IPHONE + +namespace toolkit { + +#if defined (OS_IPHONE) +bool SockNum::setSocketOfIOS(int sock){ + + CFStreamCreatePairWithSocket(NULL, (CFSocketNativeHandle)sock, (CFReadStreamRef *)(&readStream), (CFWriteStreamRef*)(&writeStream)); + if (readStream) + CFReadStreamSetProperty((CFReadStreamRef)readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse); + if (writeStream) + CFWriteStreamSetProperty((CFWriteStreamRef)writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse); + if ((readStream == NULL) || (writeStream == NULL)) + { + WarnL<<"Unable to create read and write stream..."; + if (readStream) + { + CFReadStreamClose((CFReadStreamRef)readStream); + CFRelease(readStream); + readStream = NULL; + } + if (writeStream) + { + CFWriteStreamClose((CFWriteStreamRef)writeStream); + CFRelease(writeStream); + writeStream = NULL; + } + return false; + } + + + Boolean r1 = CFReadStreamSetProperty((CFReadStreamRef)readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); + Boolean r2 = CFWriteStreamSetProperty((CFWriteStreamRef)writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); + + if (!r1 || !r2) + { + return false; + } + + CFStreamStatus readStatus = CFReadStreamGetStatus((CFReadStreamRef)readStream); + CFStreamStatus writeStatus = CFWriteStreamGetStatus((CFWriteStreamRef)writeStream); + + if ((readStatus == kCFStreamStatusNotOpen) || (writeStatus == kCFStreamStatusNotOpen)) + { + BOOL r1 = CFReadStreamOpen((CFReadStreamRef)readStream); + BOOL r2 = CFWriteStreamOpen((CFWriteStreamRef)writeStream); + + if (!r1 || !r2) + { + WarnL<<"Error in CFStreamOpen"; + return false; + } + } + //NSLog(@"setSocketOfIOS:%d",sock); + return true; +} +void SockNum::unsetSocketOfIOS(int sock){ + //NSLog(@"unsetSocketOfIOS:%d",sock); + if (readStream) { + CFReadStreamClose((CFReadStreamRef)readStream); + readStream=NULL; + } + if (writeStream) { + CFWriteStreamClose((CFWriteStreamRef)writeStream); + writeStream=NULL; + } +} +#endif //OS_IPHONE + + + +} // namespace toolkit diff --git a/3rdpart/ZLToolKit/src/Network/TcpClient.cpp b/3rdpart/ZLToolKit/src/Network/TcpClient.cpp new file mode 100644 index 0000000..0a74c47 --- /dev/null +++ b/3rdpart/ZLToolKit/src/Network/TcpClient.cpp @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include "TcpClient.h" + +using namespace std; + +namespace toolkit { + +StatisticImp(TcpClient) + +TcpClient::TcpClient(const EventPoller::Ptr &poller) : SocketHelper(nullptr) { + setPoller(poller ? poller : EventPollerPool::Instance().getPoller()); + setOnCreateSocket([](const EventPoller::Ptr &poller) { + //TCP客户端默认开启互斥锁 + return Socket::createSocket(poller, true); + }); +} + +TcpClient::~TcpClient() {} + +void TcpClient::shutdown(const SockException &ex) { + _timer.reset(); + SocketHelper::shutdown(ex); +} + +bool TcpClient::alive() const { + if (_timer) { + //连接中或已连接 + return true; + } + //在websocket client(zlmediakit)相关代码中, + //_timer一直为空,但是socket fd有效,alive状态也应该返回true + auto sock = getSock(); + return sock && sock->rawFD() >= 0; +} + +void TcpClient::setNetAdapter(const string &local_ip) { + _net_adapter = local_ip; +} + +void TcpClient::startConnect(const string &url, uint16_t port, float timeout_sec, uint16_t local_port) { + weak_ptr weak_self = shared_from_this(); + + _timer = std::make_shared(2.0f, [weak_self]() { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return false; + } + strong_self->onManager(); + return true; + }, getPoller()); + + setSock(createSocket()); + + auto sock_ptr = getSock().get(); + sock_ptr->setOnErr([weak_self, sock_ptr](const SockException &ex) { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + if (sock_ptr != strong_self->getSock().get()) { + //已经重连socket,上次的socket的事件忽略掉 + return; + } + strong_self->_timer.reset(); + strong_self->onErr(ex); + }); + + sock_ptr->connect(url, port, [weak_self](const SockException &err) { + auto strong_self = weak_self.lock(); + if (strong_self) { + strong_self->onSockConnect(err); + } + }, timeout_sec, _net_adapter, local_port); +} + +void TcpClient::onSockConnect(const SockException &ex) { + if (ex) { + //连接失败 + _timer.reset(); + onConnect(ex); + return; + } + + auto sock_ptr = getSock().get(); + weak_ptr weak_self = shared_from_this(); + + sock_ptr->setOnFlush([weak_self, sock_ptr]() { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return false; + } + if (sock_ptr != strong_self->getSock().get()) { + //已经重连socket,上传socket的事件忽略掉 + return false; + } + strong_self->onFlush(); + return true; + }); + + sock_ptr->setOnRead([weak_self, sock_ptr](const Buffer::Ptr &pBuf, struct sockaddr *, int) { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + if (sock_ptr != strong_self->getSock().get()) { + //已经重连socket,上传socket的事件忽略掉 + return; + } + try { + strong_self->onRecv(pBuf); + } catch (std::exception &ex) { + strong_self->shutdown(SockException(Err_other, ex.what())); + } + }); + + onConnect(ex); +} + +} /* namespace toolkit */ diff --git a/3rdpart/ZLToolKit/src/Network/TcpClient.h b/3rdpart/ZLToolKit/src/Network/TcpClient.h new file mode 100644 index 0000000..ee60e96 --- /dev/null +++ b/3rdpart/ZLToolKit/src/Network/TcpClient.h @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef NETWORK_TCPCLIENT_H +#define NETWORK_TCPCLIENT_H + +#include +#include "Socket.h" +#include "Util/SSLBox.h" + +namespace toolkit { + +//Tcp客户端,Socket对象默认开始互斥锁 +class TcpClient : public std::enable_shared_from_this, public SocketHelper { +public: + using Ptr = std::shared_ptr; + TcpClient(const EventPoller::Ptr &poller = nullptr); + ~TcpClient() override; + + /** + * 开始连接tcp服务器 + * @param url 服务器ip或域名 + * @param port 服务器端口 + * @param timeout_sec 超时时间,单位秒 + * @param local_port 本地端口 + */ + virtual void startConnect(const std::string &url, uint16_t port, float timeout_sec = 5, uint16_t local_port = 0); + + /** + * 通过代理开始连接tcp服务器 + * @param url 服务器ip或域名 + * @proxy_host 代理ip + * @proxy_port 代理端口 + * @param timeout_sec 超时时间,单位秒 + * @param local_port 本地端口 + */ + virtual void startConnectWithProxy(const std::string &url, const std::string &proxy_host, uint16_t proxy_port, float timeout_sec = 5, uint16_t local_port = 0){}; + + /** + * 主动断开连接 + * @param ex 触发onErr事件时的参数 + */ + void shutdown(const SockException &ex = SockException(Err_shutdown, "self shutdown")) override; + + /** + * 连接中或已连接返回true,断开连接时返回false + */ + virtual bool alive() const; + + /** + * 设置网卡适配器,使用该网卡与服务器通信 + * @param local_ip 本地网卡ip + */ + virtual void setNetAdapter(const std::string &local_ip); + +protected: + /** + * 连接服务器结果回调 + * @param ex 成功与否 + */ + virtual void onConnect(const SockException &ex) = 0; + + /** + * 收到数据回调 + * @param buf 接收到的数据(该buffer会重复使用) + */ + virtual void onRecv(const Buffer::Ptr &buf) = 0; + + /** + * 数据全部发送完毕后回调 + */ + virtual void onFlush() {} + + /** + * 被动断开连接回调 + * @param ex 断开原因 + */ + virtual void onErr(const SockException &ex) = 0; + + /** + * tcp连接成功后每2秒触发一次该事件 + */ + virtual void onManager() {} + +private: + void onSockConnect(const SockException &ex); + +private: + std::string _net_adapter = "::"; + std::shared_ptr _timer; + //对象个数统计 + ObjectStatistic _statistic; +}; + +//用于实现TLS客户端的模板对象 +template +class TcpClientWithSSL : public TcpClientType { +public: + using Ptr = std::shared_ptr; + + template + TcpClientWithSSL(ArgsType &&...args):TcpClientType(std::forward(args)...) {} + + ~TcpClientWithSSL() override { + if (_ssl_box) { + _ssl_box->flush(); + } + } + + void onRecv(const Buffer::Ptr &buf) override { + if (_ssl_box) { + _ssl_box->onRecv(buf); + } else { + TcpClientType::onRecv(buf); + } + } + + ssize_t send(Buffer::Ptr buf) override { + if (_ssl_box) { + auto size = buf->size(); + _ssl_box->onSend(std::move(buf)); + return size; + } + return TcpClientType::send(std::move(buf)); + } + + //添加public_onRecv和public_send函数是解决较低版本gcc一个lambad中不能访问protected或private方法的bug + inline void public_onRecv(const Buffer::Ptr &buf) { + TcpClientType::onRecv(buf); + } + + inline void public_send(const Buffer::Ptr &buf) { + TcpClientType::send(std::move(const_cast(buf))); + } + + void startConnect(const std::string &url, uint16_t port, float timeout_sec = 5, uint16_t local_port = 0) override { + _host = url; + TcpClientType::startConnect(url, port, timeout_sec, local_port); + } + void startConnectWithProxy(const std::string &url, const std::string &proxy_host, uint16_t proxy_port, float timeout_sec = 5, uint16_t local_port = 0) override { + _host = url; + TcpClientType::startConnect(proxy_host, proxy_port, timeout_sec, local_port); + } +protected: + void onConnect(const SockException &ex) override { + if (!ex) { + _ssl_box = std::make_shared(false); + _ssl_box->setOnDecData([this](const Buffer::Ptr &buf) { + public_onRecv(buf); + }); + _ssl_box->setOnEncData([this](const Buffer::Ptr &buf) { + public_send(buf); + }); + + if (!isIP(_host.data())) { + //设置ssl域名 + _ssl_box->setHost(_host.data()); + } + } + TcpClientType::onConnect(ex); + } + /** + * 重置ssl, 主要为了解决一些302跳转时http与https的转换 + */ + void setDoNotUseSSL() { + _ssl_box.reset(); + } +private: + std::string _host; + std::shared_ptr _ssl_box; +}; + +} /* namespace toolkit */ +#endif /* NETWORK_TCPCLIENT_H */ diff --git a/3rdpart/ZLToolKit/src/Network/TcpServer.cpp b/3rdpart/ZLToolKit/src/Network/TcpServer.cpp new file mode 100644 index 0000000..d404d93 --- /dev/null +++ b/3rdpart/ZLToolKit/src/Network/TcpServer.cpp @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include "TcpServer.h" +#include "Util/uv_errno.h" +#include "Util/onceToken.h" + +using namespace std; + +namespace toolkit { + +INSTANCE_IMP(SessionMap) +StatisticImp(TcpServer) + +TcpServer::TcpServer(const EventPoller::Ptr &poller) : Server(poller) { + setOnCreateSocket(nullptr); + _socket = createSocket(_poller); + _socket->setOnBeforeAccept([this](const EventPoller::Ptr &poller) { + return onBeforeAcceptConnection(poller); + }); + _socket->setOnAccept([this](Socket::Ptr &sock, shared_ptr &complete) { + auto ptr = sock->getPoller().get(); + auto server = getServer(ptr); + ptr->async([server, sock, complete]() { + //该tcp客户端派发给对应线程的TcpServer服务器 + server->onAcceptConnection(sock); + }); + }); +} + +TcpServer::~TcpServer() { + if (!_parent && _socket->rawFD() != -1) { + InfoL << "Close tcp server [" << _socket->get_local_ip() << "]: " << _socket->get_local_port(); + } + _timer.reset(); + //先关闭socket监听,防止收到新的连接 + _socket.reset(); + _session_map.clear(); + _cloned_server.clear(); +} + +uint16_t TcpServer::getPort() { + if (!_socket) { + return 0; + } + return _socket->get_local_port(); +} + +void TcpServer::setOnCreateSocket(Socket::onCreateSocket cb) { + if (cb) { + _on_create_socket = std::move(cb); + } else { + _on_create_socket = [](const EventPoller::Ptr &poller) { + return Socket::createSocket(poller, false); + }; + } + for (auto &pr : _cloned_server) { + pr.second->setOnCreateSocket(cb); + } +} + +TcpServer::Ptr TcpServer::onCreatServer(const EventPoller::Ptr &poller) { + return std::make_shared(poller); +} + +Socket::Ptr TcpServer::onBeforeAcceptConnection(const EventPoller::Ptr &poller) { + assert(_poller->isCurrentThread()); + //此处改成自定义获取poller对象,防止负载不均衡 + return createSocket(EventPollerPool::Instance().getPoller(false)); +} + +void TcpServer::cloneFrom(const TcpServer &that) { + if (!that._socket) { + throw std::invalid_argument("TcpServer::cloneFrom other with null socket"); + } + _on_create_socket = that._on_create_socket; + _session_alloc = that._session_alloc; + _socket->cloneFromListenSocket(*(that._socket)); + weak_ptr weak_self = std::dynamic_pointer_cast(shared_from_this()); + _timer = std::make_shared(2.0f, [weak_self]() -> bool { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return false; + } + strong_self->onManagerSession(); + return true; + }, _poller); + this->mINI::operator=(that); + _parent = &that; +} + +// 接收到客户端连接请求 +Session::Ptr TcpServer::onAcceptConnection(const Socket::Ptr &sock) { + assert(_poller->isCurrentThread()); + weak_ptr weak_self = std::dynamic_pointer_cast(shared_from_this()); + //创建一个Session;这里实现创建不同的服务会话实例 + auto helper = _session_alloc(std::dynamic_pointer_cast(shared_from_this()), sock); + auto session = helper->session(); + //把本服务器的配置传递给Session + session->attachServer(*this); + + //_session_map::emplace肯定能成功 + auto success = _session_map.emplace(helper.get(), helper).second; + assert(success == true); + + weak_ptr weak_session = session; + //会话接收数据事件 + sock->setOnRead([weak_session](const Buffer::Ptr &buf, struct sockaddr *, int) { + //获取会话强应用 + auto strong_session = weak_session.lock(); + if (!strong_session) { + return; + } + try { + strong_session->onRecv(buf); + } catch (SockException &ex) { + strong_session->shutdown(ex); + } catch (exception &ex) { + strong_session->shutdown(SockException(Err_shutdown, ex.what())); + } + }); + + SessionHelper *ptr = helper.get(); + //会话接收到错误事件 + sock->setOnErr([weak_self, weak_session, ptr](const SockException &err) { + //在本函数作用域结束时移除会话对象 + //目的是确保移除会话前执行其onError函数 + //同时避免其onError函数抛异常时没有移除会话对象 + onceToken token(nullptr, [&]() { + //移除掉会话 + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + + assert(strong_self->_poller->isCurrentThread()); + if (!strong_self->_is_on_manager) { + //该事件不是onManager时触发的,直接操作map + strong_self->_session_map.erase(ptr); + } else { + //遍历map时不能直接删除元素 + strong_self->_poller->async([weak_self, ptr]() { + auto strong_self = weak_self.lock(); + if (strong_self) { + strong_self->_session_map.erase(ptr); + } + }, false); + } + }); + + //获取会话强应用 + auto strong_session = weak_session.lock(); + if (strong_session) { + //触发onError事件回调 + strong_session->onError(err); + } + }); + return session; +} + +void TcpServer::start_l(uint16_t port, const std::string &host, uint32_t backlog) { + if (!_socket->listen(port, host.c_str(), backlog)) { + //创建tcp监听失败,可能是由于端口占用或权限问题 + string err = (StrPrinter << "Listen on " << host << " " << port << " failed: " << get_uv_errmsg(true)); + throw std::runtime_error(err); + } + + //新建一个定时器定时管理这些tcp会话 + weak_ptr weak_self = std::dynamic_pointer_cast(shared_from_this()); + _timer = std::make_shared(2.0f, [weak_self]() -> bool { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return false; + } + strong_self->onManagerSession(); + return true; + }, _poller); + + EventPollerPool::Instance().for_each([&](const TaskExecutor::Ptr &executor) { + EventPoller::Ptr poller = dynamic_pointer_cast(executor); + if (poller == _poller || !poller) { + return; + } + auto &serverRef = _cloned_server[poller.get()]; + if (!serverRef) { + serverRef = onCreatServer(poller); + } + if (serverRef) { + serverRef->cloneFrom(*this); + } + }); + + InfoL << "TCP server listening on [" << host << "]: " << port; +} + +void TcpServer::onManagerSession() { + assert(_poller->isCurrentThread()); + + onceToken token([&]() { + _is_on_manager = true; + }, [&]() { + _is_on_manager = false; + }); + + for (auto &pr : _session_map) { + //遍历时,可能触发onErr事件(也会操作_session_map) + try { + pr.second->session()->onManager(); + } catch (exception &ex) { + WarnL << ex.what(); + } + } +} + +Socket::Ptr TcpServer::createSocket(const EventPoller::Ptr &poller) { + return _on_create_socket(poller); +} + +TcpServer::Ptr TcpServer::getServer(const EventPoller *poller) const { + auto &ref = _parent ? _parent->_cloned_server : _cloned_server; + auto it = ref.find(poller); + if (it != ref.end()) { + //派发到cloned server + return it->second; + } + //派发到parent server + return static_pointer_cast(_parent ? const_cast(_parent)->shared_from_this() : + const_cast(this)->shared_from_this()); +} + +Session::Ptr TcpServer::createSession(const Socket::Ptr &sock) { + return getServer(sock->getPoller().get())->onAcceptConnection(sock); +} + +} /* namespace toolkit */ + diff --git a/3rdpart/ZLToolKit/src/Network/TcpServer.h b/3rdpart/ZLToolKit/src/Network/TcpServer.h new file mode 100644 index 0000000..bbd6d31 --- /dev/null +++ b/3rdpart/ZLToolKit/src/Network/TcpServer.h @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef TCPSERVER_TCPSERVER_H +#define TCPSERVER_TCPSERVER_H + +#include +#include +#include +#include "Server.h" +#include "Session.h" +#include "Poller/Timer.h" +#include "Util/util.h" + +namespace toolkit { + +//TCP服务器,可配置的;配置通过Session::attachServer方法传递给会话对象 +class TcpServer : public Server { +public: + using Ptr = std::shared_ptr; + + /** + * 创建tcp服务器,listen fd的accept事件会加入到所有的poller线程中监听 + * 在调用TcpServer::start函数时,内部会创建多个子TcpServer对象, + * 这些子TcpServer对象通过Socket对象克隆的方式在多个poller线程中监听同一个listen fd + * 这样这个TCP服务器将会通过抢占式accept的方式把客户端均匀的分布到不同的poller线程 + * 通过该方式能实现客户端负载均衡以及提高连接接收速度 + */ + explicit TcpServer(const EventPoller::Ptr &poller = nullptr); + ~TcpServer() override; + + /** + * @brief 开始tcp server + * @param port 本机端口,0则随机 + * @param host 监听网卡ip + * @param backlog tcp listen backlog + */ + template + void start(uint16_t port, const std::string &host = "::", uint32_t backlog = 1024) { + //Session创建器,通过它创建不同类型的服务器 + _session_alloc = [](const TcpServer::Ptr &server, const Socket::Ptr &sock) { + auto session = std::make_shared(sock); + session->setOnCreateSocket(server->_on_create_socket); + return std::make_shared(server, session); + }; + start_l(port, host, backlog); + } + + /** + * @brief 获取服务器监听端口号, 服务器可以选择监听随机端口 + */ + uint16_t getPort(); + + /** + * @brief 自定义socket构建行为 + */ + void setOnCreateSocket(Socket::onCreateSocket cb); + + /** + * 根据socket对象创建Session对象 + * 需要确保在socket归属poller线程执行本函数 + */ + Session::Ptr createSession(const Socket::Ptr &socket); + +protected: + virtual void cloneFrom(const TcpServer &that); + virtual TcpServer::Ptr onCreatServer(const EventPoller::Ptr &poller); + + virtual Session::Ptr onAcceptConnection(const Socket::Ptr &sock); + virtual Socket::Ptr onBeforeAcceptConnection(const EventPoller::Ptr &poller); + +private: + void onManagerSession(); + Socket::Ptr createSocket(const EventPoller::Ptr &poller); + void start_l(uint16_t port, const std::string &host, uint32_t backlog); + Ptr getServer(const EventPoller *) const; + +private: + bool _is_on_manager = false; + const TcpServer *_parent = nullptr; + Socket::Ptr _socket; + std::shared_ptr _timer; + Socket::onCreateSocket _on_create_socket; + std::unordered_map _session_map; + std::function _session_alloc; + std::unordered_map _cloned_server; + //对象个数统计 + ObjectStatistic _statistic; +}; + +} /* namespace toolkit */ +#endif /* TCPSERVER_TCPSERVER_H */ diff --git a/3rdpart/ZLToolKit/src/Network/UdpServer.cpp b/3rdpart/ZLToolKit/src/Network/UdpServer.cpp new file mode 100644 index 0000000..a4710bb --- /dev/null +++ b/3rdpart/ZLToolKit/src/Network/UdpServer.cpp @@ -0,0 +1,335 @@ +/* + * Copyright (c) 2021 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include "Util/uv_errno.h" +#include "Util/onceToken.h" +#include "UdpServer.h" + +using namespace std; + +namespace toolkit { + +static const uint8_t s_in6_addr_maped[] + = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00 }; + +static UdpServer::PeerIdType makeSockId(sockaddr *addr, int) { + UdpServer::PeerIdType ret; + switch (addr->sa_family) { + case AF_INET : { + ret.resize(18); + ret[0] = ((struct sockaddr_in *) addr)->sin_port >> 8; + ret[1] = ((struct sockaddr_in *) addr)->sin_port & 0xFF; + //ipv4地址统一转换为ipv6方式处理 + memcpy(&ret[2], &s_in6_addr_maped, 12); + memcpy(&ret[14], &(((struct sockaddr_in *) addr)->sin_addr), 4); + return ret; + } + case AF_INET6 : { + ret.resize(18); + ret[0] = ((struct sockaddr_in6 *) addr)->sin6_port >> 8; + ret[1] = ((struct sockaddr_in6 *) addr)->sin6_port & 0xFF; + memcpy(&ret[2], &(((struct sockaddr_in6 *)addr)->sin6_addr), 16); + return ret; + } + default: assert(0); return ""; + } +} + +UdpServer::UdpServer(const EventPoller::Ptr &poller) : Server(poller) { + setOnCreateSocket(nullptr); + _socket = createSocket(_poller); + _socket->setOnRead([this](const Buffer::Ptr &buf, struct sockaddr *addr, int addr_len) { + onRead(buf, addr, addr_len); + }); +} + +UdpServer::~UdpServer() { + if (!_cloned && _socket->rawFD() != -1) { + InfoL << "Close udp server [" << _socket->get_local_ip() << "]: " << _socket->get_local_port(); + } + _timer.reset(); + _socket.reset(); + _cloned_server.clear(); + if (!_cloned && _session_mutex && _session_map) { + lock_guard lck(*_session_mutex); + _session_map->clear(); + } +} + +void UdpServer::start_l(uint16_t port, const std::string &host) { + //主server才创建session map,其他cloned server共享之 + _session_mutex = std::make_shared(); + _session_map = std::make_shared >(); + + if (!_socket->bindUdpSock(port, host.c_str())) { + // udp 绑定端口失败, 可能是由于端口占用或权限问题 + std::string err = (StrPrinter << "Bind udp socket on " << host << " " << port << " failed: " << get_uv_errmsg(true)); + throw std::runtime_error(err); + } + + // 新建一个定时器定时管理这些 udp 会话,这些对象只由主server做超时管理,cloned server不管理 + std::weak_ptr weak_self = std::dynamic_pointer_cast(shared_from_this()); + _timer = std::make_shared(2.0f, [weak_self]() -> bool { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return false; + } + strong_self->onManagerSession(); + return true; + }, _poller); + + //clone server至不同线程,让udp server支持多线程 + EventPollerPool::Instance().for_each([&](const TaskExecutor::Ptr &executor) { + auto poller = std::dynamic_pointer_cast(executor); + if (poller == _poller || !poller) { + return; + } + + auto &serverRef = _cloned_server[poller.get()]; + if (!serverRef) { + serverRef = onCreatServer(poller); + } + if (serverRef) { + serverRef->cloneFrom(*this); + } + }); + + InfoL << "UDP server bind to [" << host << "]: " << port; +} + +UdpServer::Ptr UdpServer::onCreatServer(const EventPoller::Ptr &poller) { + return std::make_shared(poller); +} + +void UdpServer::cloneFrom(const UdpServer &that) { + if (!that._socket) { + throw std::invalid_argument("UdpServer::cloneFrom other with null socket"); + } + // clone callbacks + _on_create_socket = that._on_create_socket; + _session_alloc = that._session_alloc; + _session_mutex = that._session_mutex; + _session_map = that._session_map; + // clone udp socket + _socket->cloneFromPeerSocket(*(that._socket)); + // clone properties + this->mINI::operator=(that); + _cloned = true; +} + +void UdpServer::onRead(const Buffer::Ptr &buf, sockaddr *addr, int addr_len) { + const auto id = makeSockId(addr, addr_len); + onRead_l(true, id, buf, addr, addr_len); +} + +static void emitSessionRecv(const Session::Ptr &session, const Buffer::Ptr &buf) { + try { + session->onRecv(buf); + } catch (SockException &ex) { + session->shutdown(ex); + } catch (exception &ex) { + session->shutdown(SockException(Err_shutdown, ex.what())); + } +} + +void UdpServer::onRead_l(bool is_server_fd, const UdpServer::PeerIdType &id, const Buffer::Ptr &buf, sockaddr *addr, int addr_len) { + // udp server fd收到数据时触发此函数;大部分情况下数据应该在peer fd触发,此函数应该不是热点函数 + bool is_new = false; + if (auto session = getOrCreateSession(id, buf, addr, addr_len, is_new)) { + if (session->getPoller()->isCurrentThread()) { + //当前线程收到数据,直接处理数据 + emitSessionRecv(session, buf); + } else { + //数据漂移到其他线程,需要先切换线程 + WarnL << "UDP packet incoming from other thread"; + std::weak_ptr weak_session = session; + //由于socket读buffer是该线程上所有socket共享复用的,所以不能跨线程使用,必须先拷贝一下 + auto cacheable_buf = std::make_shared(buf->toString()); + session->async([weak_session, cacheable_buf]() { + if (auto strong_session = weak_session.lock()) { + emitSessionRecv(strong_session, cacheable_buf); + } + }); + } + +#if !defined(NDEBUG) + if (!is_new) { + TraceL << "UDP packet incoming from " << (is_server_fd ? "server fd" : "other peer fd"); + } +#endif + } +} + +void UdpServer::onManagerSession() { + decltype(_session_map) copy_map; + { + std::lock_guard lock(*_session_mutex); + //拷贝map,防止遍历时移除对象 + copy_map = std::make_shared >(*_session_map); + } + EventPollerPool::Instance().for_each([copy_map](const TaskExecutor::Ptr &executor) { + auto poller = std::dynamic_pointer_cast(executor); + if (!poller) { + return; + } + poller->async([copy_map]() { + for (auto &pr : *copy_map) { + auto &session = pr.second->session(); + if (!session->getPoller()->isCurrentThread()) { + //该session不归属该poller管理 + continue; + } + try { + // UDP 会话需要处理超时 + session->onManager(); + } catch (exception &ex) { + WarnL << "Exception occurred when emit onManager: " << ex.what(); + } + } + }); + }); +} + +const Session::Ptr &UdpServer::getOrCreateSession(const UdpServer::PeerIdType &id, const Buffer::Ptr &buf, sockaddr *addr, int addr_len, bool &is_new) { + { + //减小临界区 + std::lock_guard lock(*_session_mutex); + auto it = _session_map->find(id); + if (it != _session_map->end()) { + return it->second->session(); + } + } + is_new = true; + return createSession(id, buf, addr, addr_len); +} + +static Session::Ptr s_null_session; + +const Session::Ptr &UdpServer::createSession(const PeerIdType &id, const Buffer::Ptr &buf, struct sockaddr *addr, int addr_len) { + auto socket = createSocket(_poller, buf, addr, addr_len); + if (!socket) { + //创建socket失败,本次onRead事件收到的数据直接丢弃 + return s_null_session; + } + + auto addr_str = string((char *) addr, addr_len); + std::weak_ptr weak_self = std::dynamic_pointer_cast(shared_from_this()); + auto session_creator = [this, weak_self, socket, addr_str, id]() -> const Session::Ptr & { + auto server = weak_self.lock(); + if (!server) { + return s_null_session; + } + + //如果已经创建该客户端对应的UdpSession类,那么直接返回 + lock_guard lck(*_session_mutex); + auto it = _session_map->find(id); + if (it != _session_map->end()) { + return it->second->session(); + } + + socket->bindUdpSock(_socket->get_local_port(), _socket->get_local_ip()); + socket->bindPeerAddr((struct sockaddr *) addr_str.data(), addr_str.size()); + + auto helper = _session_alloc(server, socket); + auto session = helper->session(); + // 把本服务器的配置传递给 Session + session->attachServer(*this); + + std::weak_ptr weak_session = session; + socket->setOnRead([weak_self, weak_session, id](const Buffer::Ptr &buf, struct sockaddr *addr, int addr_len) { + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + + //快速判断是否为本会话的的数据, 通常应该成立 + if (id == makeSockId(addr, addr_len)) { + if (auto strong_session = weak_session.lock()) { + emitSessionRecv(strong_session, buf); + } + return; + } + + //收到非本peer fd的数据,让server去派发此数据到合适的session对象 + strong_self->onRead_l(false, id, buf, addr, addr_len); + }); + socket->setOnErr([weak_self, weak_session, id](const SockException &err) { + // 在本函数作用域结束时移除会话对象 + // 目的是确保移除会话前执行其 onError 函数 + // 同时避免其 onError 函数抛异常时没有移除会话对象 + onceToken token(nullptr, [&]() { + // 移除掉会话 + auto strong_self = weak_self.lock(); + if (!strong_self) { + return; + } + //从共享map中移除本session对象 + lock_guard lck(*strong_self->_session_mutex); + strong_self->_session_map->erase(id); + }); + + // 获取会话强应用 + if (auto strong_session = weak_session.lock()) { + // 触发 onError 事件回调 + strong_session->onError(err); + } + }); + + auto pr = _session_map->emplace(id, std::move(helper)); + assert(pr.second); + return pr.first->second->session(); + }; + + if (socket->getPoller()->isCurrentThread()) { + //该socket分配在本线程,直接创建session对象,并处理数据 + return session_creator(); + } + + //该socket分配在其他线程,需要先拷贝buffer,然后在其所在线程创建session对象并处理数据 + auto cacheable_buf = std::make_shared(buf->toString()); + socket->getPoller()->async([session_creator, cacheable_buf]() { + //在该socket所在线程创建session对象 + auto session = session_creator(); + if (session) { + //该数据不能丢弃,给session对象消费 + emitSessionRecv(session, cacheable_buf); + } + }); + return s_null_session; +} + +void UdpServer::setOnCreateSocket(onCreateSocket cb) { + if (cb) { + _on_create_socket = std::move(cb); + } else { + _on_create_socket = [](const EventPoller::Ptr &poller, const Buffer::Ptr &buf, struct sockaddr *addr, int addr_len) { + return Socket::createSocket(poller, false); + }; + } + for (auto &pr : _cloned_server) { + pr.second->setOnCreateSocket(cb); + } +} + +uint16_t UdpServer::getPort() { + if (!_socket) { + return 0; + } + return _socket->get_local_port(); +} + +Socket::Ptr UdpServer::createSocket(const EventPoller::Ptr &poller, const Buffer::Ptr &buf, struct sockaddr *addr, int addr_len) { + return _on_create_socket(poller, buf, addr, addr_len); +} + + +StatisticImp(UdpServer) + +} // namespace toolkit diff --git a/3rdpart/ZLToolKit/src/Network/UdpServer.h b/3rdpart/ZLToolKit/src/Network/UdpServer.h new file mode 100644 index 0000000..c1d3665 --- /dev/null +++ b/3rdpart/ZLToolKit/src/Network/UdpServer.h @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2021 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef TOOLKIT_NETWORK_UDPSERVER_H +#define TOOLKIT_NETWORK_UDPSERVER_H + +#include "Server.h" +#include "Session.h" + +namespace toolkit { + +class UdpServer : public Server { +public: + using Ptr = std::shared_ptr; + using PeerIdType = std::string; + using onCreateSocket = std::function; + + explicit UdpServer(const EventPoller::Ptr &poller = nullptr); + ~UdpServer() override; + + /** + * @brief 开始监听服务器 + */ + template + void start(uint16_t port, const std::string &host = "::") { + // Session 创建器, 通过它创建不同类型的服务器 + _session_alloc = [](const UdpServer::Ptr &server, const Socket::Ptr &sock) { + auto session = std::make_shared(sock); + auto sock_creator = server->_on_create_socket; + session->setOnCreateSocket([sock_creator](const EventPoller::Ptr &poller) { + return sock_creator(poller, nullptr, nullptr, 0); + }); + return std::make_shared(server, session); + }; + start_l(port, host); + } + + /** + * @brief 获取服务器监听端口号, 服务器可以选择监听随机端口 + */ + uint16_t getPort(); + + /** + * @brief 自定义socket构建行为 + */ + void setOnCreateSocket(onCreateSocket cb); + +protected: + virtual Ptr onCreatServer(const EventPoller::Ptr &poller); + virtual void cloneFrom(const UdpServer &that); + +private: + /** + * @brief 开始udp server + * @param port 本机端口,0则随机 + * @param host 监听网卡ip + */ + void start_l(uint16_t port, const std::string &host = "::"); + + /** + * @brief 定时管理 Session, UDP 会话需要根据需要处理超时 + */ + void onManagerSession(); + + void onRead(const Buffer::Ptr &buf, struct sockaddr *addr, int addr_len); + + /** + * @brief 接收到数据,可能来自server fd,也可能来自peer fd + * @param is_server_fd 时候为server fd + * @param id 客户端id + * @param buf 数据 + * @param addr 客户端地址 + * @param addr_len 客户端地址长度 + */ + void onRead_l(bool is_server_fd, const PeerIdType &id, const Buffer::Ptr &buf, struct sockaddr *addr, int addr_len); + + /** + * @brief 根据对端信息获取或创建一个会话 + */ + const Session::Ptr &getOrCreateSession(const PeerIdType &id, const Buffer::Ptr &buf, struct sockaddr *addr, int addr_len, bool &is_new); + + /** + * @brief 创建一个会话, 同时进行必要的设置 + */ + const Session::Ptr &createSession(const PeerIdType &id, const Buffer::Ptr &buf, struct sockaddr *addr, int addr_len); + + /** + * @brief 创建socket + */ + Socket::Ptr createSocket(const EventPoller::Ptr &poller, const Buffer::Ptr &buf = nullptr, struct sockaddr *addr = nullptr, int addr_len = 0); + +private: + bool _cloned = false; + Socket::Ptr _socket; + std::shared_ptr _timer; + onCreateSocket _on_create_socket; + //cloned server共享主server的session map,防止数据在不同server间漂移 + std::shared_ptr _session_mutex; + std::shared_ptr > _session_map; + //主server持有cloned server的引用 + std::unordered_map _cloned_server; + std::function _session_alloc; + // 对象个数统计 + ObjectStatistic _statistic; +}; + +} // namespace toolkit + +#endif // TOOLKIT_NETWORK_UDPSERVER_H diff --git a/3rdpart/ZLToolKit/src/Network/sockutil.cpp b/3rdpart/ZLToolKit/src/Network/sockutil.cpp new file mode 100644 index 0000000..8585933 --- /dev/null +++ b/3rdpart/ZLToolKit/src/Network/sockutil.cpp @@ -0,0 +1,1108 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ +#include +#include +#include +#include +#include +#include +#include +#include "sockutil.h" +#include "Util/util.h" +#include "Util/logger.h" +#include "Util/uv_errno.h" +#include "Util/onceToken.h" +#if defined (__APPLE__) +#include +#endif +using namespace std; + +namespace toolkit { + +#if defined(_WIN32) +static onceToken g_token([]() { + WORD wVersionRequested = MAKEWORD(2, 2); + WSADATA wsaData; + WSAStartup(wVersionRequested, &wsaData); +}, []() { + WSACleanup(); +}); +int ioctl(int fd, long cmd, u_long *ptr) { + return ioctlsocket(fd, cmd, ptr); +} +int close(int fd) { + return closesocket(fd); +} +#if (_WIN32_WINNT < _WIN32_WINNT_VISTA) +const char *inet_ntop(int af, const void *src, char *dst, socklen_t size) { + struct sockaddr_storage ss; + unsigned long s = size; + + ZeroMemory(&ss, sizeof(ss)); + ss.ss_family = af; + + switch (af) { + case AF_INET: + ((struct sockaddr_in *)&ss)->sin_addr = *(struct in_addr *)src; + break; + case AF_INET6: + ((struct sockaddr_in6 *)&ss)->sin6_addr = *(struct in6_addr *)src; + break; + default: + return NULL; + } + /* cannot direclty use &size because of strict aliasing rules */ + return (WSAAddressToString((struct sockaddr *)&ss, sizeof(ss), NULL, dst, &s) == 0) ? dst : NULL; +} +int inet_pton(int af, const char *src, void *dst) { + struct sockaddr_storage ss; + int size = sizeof(ss); + char src_copy[INET6_ADDRSTRLEN + 1]; + + ZeroMemory(&ss, sizeof(ss)); + /* stupid non-const API */ + strncpy(src_copy, src, INET6_ADDRSTRLEN + 1); + src_copy[INET6_ADDRSTRLEN] = 0; + + if (WSAStringToAddress(src_copy, af, NULL, (struct sockaddr *)&ss, &size) == 0) { + switch (af) { + case AF_INET: + *(struct in_addr *)dst = ((struct sockaddr_in *)&ss)->sin_addr; + return 1; + case AF_INET6: + *(struct in6_addr *)dst = ((struct sockaddr_in6 *)&ss)->sin6_addr; + return 1; + } + } + return 0; +} +#endif +#endif // defined(_WIN32) + +static inline string my_inet_ntop(int af, const void *addr) { + string ret; + ret.resize(128); + if (!inet_ntop(af, const_cast(addr), (char *) ret.data(), ret.size())) { + ret.clear(); + } else { + ret.resize(strlen(ret.data())); + } + return ret; +} + +static inline bool support_ipv6_l() { + auto fd = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); + if (fd == -1) { + return false; + } + close(fd); + return true; +} + +static inline bool support_ipv6() { + static auto flag = support_ipv6_l(); + return flag; +} + +string SockUtil::inet_ntoa(const struct in_addr &addr) { + return my_inet_ntop(AF_INET, &addr); +} + +std::string SockUtil::inet_ntoa(const struct in6_addr &addr) { + return my_inet_ntop(AF_INET6, &addr); +} + +std::string SockUtil::inet_ntoa(const struct sockaddr *addr) { + switch (addr->sa_family) { + case AF_INET: return SockUtil::inet_ntoa(((struct sockaddr_in *)addr)->sin_addr); + case AF_INET6: { + if (IN6_IS_ADDR_V4MAPPED(&((struct sockaddr_in6 *)addr)->sin6_addr)) { + struct in_addr addr4; + memcpy(&addr4, 12 + (char *)&(((struct sockaddr_in6 *)addr)->sin6_addr), 4); + return SockUtil::inet_ntoa(addr4); + } + return SockUtil::inet_ntoa(((struct sockaddr_in6 *)addr)->sin6_addr); + } + default: assert(false); return ""; + } +} + +uint16_t SockUtil::inet_port(const struct sockaddr *addr) { + switch (addr->sa_family) { + case AF_INET: return ntohs(((struct sockaddr_in *)addr)->sin_port); + case AF_INET6: return ntohs(((struct sockaddr_in6 *)addr)->sin6_port); + default: assert(false); return 0; + } +} + +int SockUtil::setCloseWait(int fd, int second) { + linger m_sLinger; + //在调用closesocket()时还有数据未发送完,允许等待 + // 若m_sLinger.l_onoff=0;则调用closesocket()后强制关闭 + m_sLinger.l_onoff = (second > 0); + m_sLinger.l_linger = second; //设置等待时间为x秒 + int ret = setsockopt(fd, SOL_SOCKET, SO_LINGER, (char *) &m_sLinger, sizeof(linger)); + if (ret == -1) { +#ifndef _WIN32 + TraceL << "setsockopt SO_LINGER failed"; +#endif + } + return ret; +} + +int SockUtil::setNoDelay(int fd, bool on) { + int opt = on ? 1 : 0; + int ret = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *) &opt, static_cast(sizeof(opt))); + if (ret == -1) { + TraceL << "setsockopt TCP_NODELAY failed"; + } + return ret; +} + +int SockUtil::setReuseable(int fd, bool on, bool reuse_port) { + int opt = on ? 1 : 0; + int ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &opt, static_cast(sizeof(opt))); + if (ret == -1) { + TraceL << "setsockopt SO_REUSEADDR failed"; + return ret; + } +#if defined(SO_REUSEPORT) + if (reuse_port) { + ret = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, (char *) &opt, static_cast(sizeof(opt))); + if (ret == -1) { + TraceL << "setsockopt SO_REUSEPORT failed"; + } + } +#endif + return ret; +} + +int SockUtil::setBroadcast(int fd, bool on) { + int opt = on ? 1 : 0; + int ret = setsockopt(fd, SOL_SOCKET, SO_BROADCAST, (char *) &opt, static_cast(sizeof(opt))); + if (ret == -1) { + TraceL << "setsockopt SO_BROADCAST failed"; + } + return ret; +} + +int SockUtil::setKeepAlive(int fd, bool on) { + int opt = on ? 1 : 0; + int ret = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (char *) &opt, static_cast(sizeof(opt))); + if (ret == -1) { + TraceL << "setsockopt SO_KEEPALIVE failed"; + } + return ret; +} + +int SockUtil::setCloExec(int fd, bool on) { +#if !defined(_WIN32) + int flags = fcntl(fd, F_GETFD); + if (flags == -1) { + TraceL << "fcntl F_GETFD failed"; + return -1; + } + if (on) { + flags |= FD_CLOEXEC; + } else { + int cloexec = FD_CLOEXEC; + flags &= ~cloexec; + } + int ret = fcntl(fd, F_SETFD, flags); + if (ret == -1) { + TraceL << "fcntl F_SETFD failed"; + return -1; + } + return ret; +#else + return -1; +#endif +} + +int SockUtil::setNoSigpipe(int fd) { +#if defined(SO_NOSIGPIPE) + int set = 1; + auto ret = setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, (char *) &set, sizeof(int)); + if (ret == -1) { + TraceL << "setsockopt SO_NOSIGPIPE failed"; + } + return ret; +#else + return -1; +#endif +} + +int SockUtil::setNoBlocked(int fd, bool noblock) { +#if defined(_WIN32) + unsigned long ul = noblock; +#else + int ul = noblock; +#endif //defined(_WIN32) + int ret = ioctl(fd, FIONBIO, &ul); //设置为非阻塞模式 + if (ret == -1) { + TraceL << "ioctl FIONBIO failed"; + } + + return ret; +} + +int SockUtil::setRecvBuf(int fd, int size) { + int ret = setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (char *) &size, sizeof(size)); + if (ret == -1) { + TraceL << "setsockopt SO_RCVBUF failed"; + } + return ret; +} + +int SockUtil::setSendBuf(int fd, int size) { + int ret = setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (char *) &size, sizeof(size)); + if (ret == -1) { + TraceL << "setsockopt SO_SNDBUF failed"; + } + return ret; +} + +class DnsCache { +public: + static DnsCache &Instance() { + static DnsCache instance; + return instance; + } + + bool getDomainIP(const char *host, sockaddr_storage &storage, int ai_family = AF_INET, + int ai_socktype = SOCK_STREAM, int ai_protocol = IPPROTO_TCP, int expire_sec = 60) { + try { + storage = SockUtil::make_sockaddr(host, 0); + return true; + } catch (...) { + auto item = getCacheDomainIP(host, expire_sec); + if (!item) { + item = getSystemDomainIP(host); + if (item) { + setCacheDomainIP(host, item); + } + } + if (item) { + auto addr = getPerferredAddress(item.get(), ai_family, ai_socktype, ai_protocol); + memcpy(&storage, addr->ai_addr, addr->ai_addrlen); + } + return (bool)item; + } + } + +private: + class DnsItem { + public: + std::shared_ptr addr_info; + time_t create_time; + }; + + std::shared_ptr getCacheDomainIP(const char *host, int expireSec) { + lock_guard lck(_mtx); + auto it = _dns_cache.find(host); + if (it == _dns_cache.end()) { + //没有记录 + return nullptr; + } + if (it->second.create_time + expireSec < time(nullptr)) { + //已过期 + _dns_cache.erase(it); + return nullptr; + } + return it->second.addr_info; + } + + void setCacheDomainIP(const char *host, std::shared_ptr addr) { + lock_guard lck(_mtx); + DnsItem item; + item.addr_info = std::move(addr); + item.create_time = time(nullptr); + _dns_cache[host] = std::move(item); + } + + std::shared_ptr getSystemDomainIP(const char *host) { + struct addrinfo *answer = nullptr; + //阻塞式dns解析,可能被打断 + int ret = -1; + do { + ret = getaddrinfo(host, nullptr, nullptr, &answer); + } while (ret == -1 && get_uv_error(true) == UV_EINTR); + + if (!answer) { + WarnL << "getaddrinfo failed: " << host; + return nullptr; + } + return std::shared_ptr(answer, freeaddrinfo); + } + + struct addrinfo *getPerferredAddress(struct addrinfo *answer, int ai_family, int ai_socktype, int ai_protocol) { + auto ptr = answer; + while (ptr) { + if (ptr->ai_family == ai_family && ptr->ai_socktype == ai_socktype && ptr->ai_protocol == ai_protocol) { + return ptr; + } + ptr = ptr->ai_next; + } + return answer; + } + +private: + mutex _mtx; + unordered_map _dns_cache; +}; + +bool SockUtil::getDomainIP(const char *host, uint16_t port, struct sockaddr_storage &addr, + int ai_family, int ai_socktype, int ai_protocol, int expire_sec) { + bool flag = DnsCache::Instance().getDomainIP(host, addr, ai_family, ai_socktype, ai_protocol, expire_sec); + if (flag) { + switch (addr.ss_family ) { + case AF_INET : ((sockaddr_in *) &addr)->sin_port = htons(port); break; + case AF_INET6 : ((sockaddr_in6 *) &addr)->sin6_port = htons(port); break; + default: assert(0); break; + } + } + return flag; +} + +static int set_ipv6_only(int fd, bool flag) { + int opt = flag; + int ret = setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&opt, sizeof opt); + if (ret == -1) { + TraceL << "setsockopt IPV6_V6ONLY failed"; + } + return ret; +} + +static int bind_sock6(int fd, const char *ifr_ip, uint16_t port) { + set_ipv6_only(fd, false); + struct sockaddr_in6 addr; + bzero(&addr, sizeof(addr)); + addr.sin6_family = AF_INET6; + addr.sin6_port = htons(port); + if (1 != inet_pton(AF_INET6, ifr_ip, &(addr.sin6_addr))) { + if (strcmp(ifr_ip, "0.0.0.0")) { + WarnL << "inet_pton to ipv6 address failed: " << ifr_ip; + } + addr.sin6_addr = IN6ADDR_ANY_INIT; + } + if (::bind(fd, (struct sockaddr *) &addr, sizeof(addr)) == -1) { + WarnL << "Bind socket failed: " << get_uv_errmsg(true); + return -1; + } + return 0; +} + +static int bind_sock4(int fd, const char *ifr_ip, uint16_t port) { + struct sockaddr_in addr; + bzero(&addr, sizeof(addr)); + + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + if (1 != inet_pton(AF_INET, ifr_ip, &(addr.sin_addr))) { + if (strcmp(ifr_ip, "::")) { + WarnL << "inet_pton to ipv4 address failed: " << ifr_ip; + } + addr.sin_addr.s_addr = INADDR_ANY; + } + if (::bind(fd, (struct sockaddr *) &addr, sizeof(addr)) == -1) { + WarnL << "Bind socket failed: " << get_uv_errmsg(true); + return -1; + } + return 0; +} + +static int bind_sock(int fd, const char *ifr_ip, uint16_t port, int family) { + switch (family) { + case AF_INET: return bind_sock4(fd, ifr_ip, port); + case AF_INET6: return bind_sock6(fd, ifr_ip, port); + default: assert(0); return -1; + } +} + +int SockUtil::connect(const char *host, uint16_t port, bool async, const char *local_ip, uint16_t local_port) { + sockaddr_storage addr; + //优先使用ipv4地址 + if (!getDomainIP(host, port, addr, AF_INET, SOCK_STREAM, IPPROTO_TCP)) { + //dns解析失败 + return -1; + } + + int sockfd = (int) socket(addr.ss_family, SOCK_STREAM, IPPROTO_TCP); + if (sockfd < 0) { + WarnL << "Create socket failed: " << host; + return -1; + } + + setReuseable(sockfd); + setNoSigpipe(sockfd); + setNoBlocked(sockfd, async); + setNoDelay(sockfd); + setSendBuf(sockfd); + setRecvBuf(sockfd); + setCloseWait(sockfd); + setCloExec(sockfd); + + if (bind_sock(sockfd, local_ip, local_port, addr.ss_family) == -1) { + close(sockfd); + return -1; + } + + if (::connect(sockfd, (sockaddr *) &addr, get_sock_len((sockaddr *)&addr)) == 0) { + //同步连接成功 + return sockfd; + } + if (async && get_uv_error(true) == UV_EAGAIN) { + //异步连接成功 + return sockfd; + } + WarnL << "Connect socket to " << host << " " << port << " failed: " << get_uv_errmsg(true); + close(sockfd); + return -1; +} + +int SockUtil::listen(const uint16_t port, const char *local_ip, int back_log) { + int fd = -1; + int family = support_ipv6() ? (is_ipv4(local_ip) ? AF_INET : AF_INET6) : AF_INET; + if ((fd = (int)socket(family, SOCK_STREAM, IPPROTO_TCP)) == -1) { + WarnL << "Create socket failed: " << get_uv_errmsg(true); + return -1; + } + + setReuseable(fd, true, false); + setNoBlocked(fd); + setCloExec(fd); + + if (bind_sock(fd, local_ip, port, family) == -1) { + close(fd); + return -1; + } + + //开始监听 + if (::listen(fd, back_log) == -1) { + WarnL << "Listen socket failed: " << get_uv_errmsg(true); + close(fd); + return -1; + } + + return fd; +} + +int SockUtil::getSockError(int fd) { + int opt; + socklen_t optLen = static_cast(sizeof(opt)); + + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (char *) &opt, &optLen) < 0) { + return get_uv_error(true); + } else { + return uv_translate_posix_error(opt); + } +} + +using getsockname_type = decltype(getsockname); + +static bool get_socket_addr(int fd, struct sockaddr_storage &addr, getsockname_type func) { + socklen_t addr_len = sizeof(addr); + if (-1 == func(fd, (struct sockaddr *)&addr, &addr_len)) { + return false; + } + return true; +} + +bool SockUtil::get_sock_local_addr(int fd, struct sockaddr_storage &addr) { + return get_socket_addr(fd, addr, getsockname); +} + +bool SockUtil::get_sock_peer_addr(int fd, struct sockaddr_storage &addr) { + return get_socket_addr(fd, addr, getpeername); +} + +static string get_socket_ip(int fd, getsockname_type func) { + struct sockaddr_storage addr; + if (!get_socket_addr(fd, addr, func)) { + return ""; + } + return SockUtil::inet_ntoa((struct sockaddr *)&addr); +} + +static uint16_t get_socket_port(int fd, getsockname_type func) { + struct sockaddr_storage addr; + if (!get_socket_addr(fd, addr, func)) { + return 0; + } + return SockUtil::inet_port((struct sockaddr *)&addr); +} + +string SockUtil::get_local_ip(int fd) { + return get_socket_ip(fd, getsockname); +} + +string SockUtil::get_peer_ip(int fd) { + return get_socket_ip(fd, getpeername); +} + +uint16_t SockUtil::get_local_port(int fd) { + return get_socket_port(fd, getsockname); +} + +uint16_t SockUtil::get_peer_port(int fd) { + return get_socket_port(fd, getpeername); +} + +#if defined(__APPLE__) +template +void for_each_netAdapter_apple(FUN &&fun) { //type: struct ifaddrs * + struct ifaddrs *interfaces = nullptr; + struct ifaddrs *adapter = nullptr; + if (getifaddrs(&interfaces) == 0) { + adapter = interfaces; + while (adapter) { + if (adapter->ifa_addr->sa_family == AF_INET) { + if (fun(adapter)) { + break; + } + } + adapter = adapter->ifa_next; + } + freeifaddrs(interfaces); + } +} +#endif //defined(__APPLE__) + +#if defined(_WIN32) +template +void for_each_netAdapter_win32(FUN && fun) { //type: PIP_ADAPTER_INFO + unsigned long nSize = sizeof(IP_ADAPTER_INFO); + PIP_ADAPTER_INFO adapterList = (PIP_ADAPTER_INFO)new char[nSize]; + int nRet = GetAdaptersInfo(adapterList, &nSize); + if (ERROR_BUFFER_OVERFLOW == nRet) { + delete[] adapterList; + adapterList = (PIP_ADAPTER_INFO)new char[nSize]; + nRet = GetAdaptersInfo(adapterList, &nSize); + } + auto adapterPtr = adapterList; + while (adapterPtr && ERROR_SUCCESS == nRet) { + if (fun(adapterPtr)) { + break; + } + adapterPtr = adapterPtr->Next; + } + //释放内存空间 + delete[] adapterList; +} +#endif //defined(_WIN32) + +#if !defined(_WIN32) && !defined(__APPLE__) +template +void for_each_netAdapter_posix(FUN &&fun){ //type: struct ifreq * + struct ifconf ifconf; + char buf[1024 * 10]; + //初始化ifconf + ifconf.ifc_len = sizeof(buf); + ifconf.ifc_buf = buf; + int sockfd = ::socket(AF_INET, SOCK_DGRAM, 0); + if (sockfd < 0) { + WarnL << "Create socket failed: " << get_uv_errmsg(true); + return; + } + if (-1 == ioctl(sockfd, SIOCGIFCONF, &ifconf)) { //获取所有接口信息 + WarnL << "ioctl SIOCGIFCONF failed: " << get_uv_errmsg(true); + close(sockfd); + return; + } + close(sockfd); + //接下来一个一个的获取IP地址 + struct ifreq * adapter = (struct ifreq*) buf; + for (int i = (ifconf.ifc_len / sizeof(struct ifreq)); i > 0; --i,++adapter) { + if(fun(adapter)){ + break; + } + } +} +#endif //!defined(_WIN32) && !defined(__APPLE__) + +bool check_ip(string &address, const string &ip) { + if (ip != "127.0.0.1" && ip != "0.0.0.0") { + /*获取一个有效IP*/ + address = ip; + uint32_t addressInNetworkOrder = htonl(inet_addr(ip.data())); + if (/*(addressInNetworkOrder >= 0x0A000000 && addressInNetworkOrder < 0x0E000000) ||*/ + (addressInNetworkOrder >= 0xAC100000 && addressInNetworkOrder < 0xAC200000) || + (addressInNetworkOrder >= 0xC0A80000 && addressInNetworkOrder < 0xC0A90000)) { + //A类私有IP地址: + //10.0.0.0~10.255.255.255 + //B类私有IP地址: + //172.16.0.0~172.31.255.255 + //C类私有IP地址: + //192.168.0.0~192.168.255.255 + //如果是私有地址 说明在nat内部 + + /* 优先采用局域网地址,该地址很可能是wifi地址 + * 一般来说,无线路由器分配的地址段是BC类私有ip地址 + * 而A类地址多用于蜂窝移动网络 + */ + return true; + } + } + return false; +} + +string SockUtil::get_local_ip() { +#if defined(__APPLE__) + string address = "127.0.0.1"; + for_each_netAdapter_apple([&](struct ifaddrs *adapter) { + string ip = SockUtil::inet_ntoa(adapter->ifa_addr); + if (strstr(adapter->ifa_name, "docker")) { + return false; + } + return check_ip(address, ip); + }); + return address; +#elif defined(_WIN32) + string address = "127.0.0.1"; + for_each_netAdapter_win32([&](PIP_ADAPTER_INFO adapter) { + IP_ADDR_STRING *ipAddr = &(adapter->IpAddressList); + while (ipAddr) { + string ip = ipAddr->IpAddress.String; + if (strstr(adapter->AdapterName, "docker")) { + return false; + } + if(check_ip(address,ip)){ + return true; + } + ipAddr = ipAddr->Next; + } + return false; + }); + return address; +#else + string address = "127.0.0.1"; + for_each_netAdapter_posix([&](struct ifreq *adapter){ + string ip = SockUtil::inet_ntoa(&(adapter->ifr_addr)); + if (strstr(adapter->ifr_name, "docker")) { + return false; + } + return check_ip(address,ip); + }); + return address; +#endif +} + +vector > SockUtil::getInterfaceList() { + vector > ret; +#if defined(__APPLE__) + for_each_netAdapter_apple([&](struct ifaddrs *adapter) { + map obj; + obj["ip"] = SockUtil::inet_ntoa(adapter->ifa_addr); + obj["name"] = adapter->ifa_name; + ret.emplace_back(std::move(obj)); + return false; + }); +#elif defined(_WIN32) + for_each_netAdapter_win32([&](PIP_ADAPTER_INFO adapter) { + IP_ADDR_STRING *ipAddr = &(adapter->IpAddressList); + while (ipAddr) { + map obj; + obj["ip"] = ipAddr->IpAddress.String; + obj["name"] = adapter->AdapterName; + ret.emplace_back(std::move(obj)); + ipAddr = ipAddr->Next; + } + return false; + }); +#else + for_each_netAdapter_posix([&](struct ifreq *adapter){ + map obj; + obj["ip"] = SockUtil::inet_ntoa(&(adapter->ifr_addr)); + obj["name"] = adapter->ifr_name; + ret.emplace_back(std::move(obj)); + return false; + }); +#endif + return ret; +} + +int SockUtil::bindUdpSock(const uint16_t port, const char *local_ip, bool enable_reuse) { + int fd = -1; + int family = support_ipv6() ? (is_ipv4(local_ip) ? AF_INET : AF_INET6) : AF_INET; + if ((fd = (int)socket(family, SOCK_DGRAM, IPPROTO_UDP)) == -1) { + WarnL << "Create socket failed: " << get_uv_errmsg(true); + return -1; + } + if (enable_reuse) { + setReuseable(fd); + } + setNoSigpipe(fd); + setNoBlocked(fd); + setSendBuf(fd); + setRecvBuf(fd); + setCloseWait(fd); + setCloExec(fd); + + if (bind_sock(fd, local_ip, port, family) == -1) { + close(fd); + return -1; + } + return fd; +} + +int SockUtil::dissolveUdpSock(int fd) { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + if (-1 == getsockname(fd, (struct sockaddr *)&addr, &addr_len)) { + return -1; + } + addr.ss_family = AF_UNSPEC; + if (-1 == ::connect(fd, (struct sockaddr *)&addr, addr_len) && get_uv_error() != UV_EAFNOSUPPORT) { + // mac/ios时返回EAFNOSUPPORT错误 + WarnL << "Connect socket AF_UNSPEC failed: " << get_uv_errmsg(true); + return -1; + } + return 0; +} + +string SockUtil::get_ifr_ip(const char *if_name) { +#if defined(__APPLE__) + string ret; + for_each_netAdapter_apple([&](struct ifaddrs *adapter) { + if (strcmp(adapter->ifa_name, if_name) == 0) { + ret = SockUtil::inet_ntoa(adapter->ifa_addr); + return true; + } + return false; + }); + return ret; +#elif defined(_WIN32) + string ret; + for_each_netAdapter_win32([&](PIP_ADAPTER_INFO adapter) { + IP_ADDR_STRING *ipAddr = &(adapter->IpAddressList); + while (ipAddr){ + if (strcmp(if_name,adapter->AdapterName) == 0){ + //ip匹配到了 + ret.assign(ipAddr->IpAddress.String); + return true; + } + ipAddr = ipAddr->Next; + } + return false; + }); + return ret; +#else + string ret; + for_each_netAdapter_posix([&](struct ifreq *adapter){ + if(strcmp(adapter->ifr_name,if_name) == 0) { + ret = SockUtil::inet_ntoa(&(adapter->ifr_addr)); + return true; + } + return false; + }); + return ret; +#endif +} + +string SockUtil::get_ifr_name(const char *local_ip) { +#if defined(__APPLE__) + string ret = "en0"; + for_each_netAdapter_apple([&](struct ifaddrs *adapter) { + string ip = SockUtil::inet_ntoa(adapter->ifa_addr); + if (ip == local_ip) { + ret = adapter->ifa_name; + return true; + } + return false; + }); + return ret; +#elif defined(_WIN32) + string ret = "en0"; + for_each_netAdapter_win32([&](PIP_ADAPTER_INFO adapter) { + IP_ADDR_STRING *ipAddr = &(adapter->IpAddressList); + while (ipAddr){ + if (strcmp(local_ip,ipAddr->IpAddress.String) == 0){ + //ip匹配到了 + ret.assign(adapter->AdapterName); + return true; + } + ipAddr = ipAddr->Next; + } + return false; + }); + return ret; +#else + string ret = "en0"; + for_each_netAdapter_posix([&](struct ifreq *adapter){ + string ip = SockUtil::inet_ntoa(&(adapter->ifr_addr)); + if(ip == local_ip) { + ret = adapter->ifr_name; + return true; + } + return false; + }); + return ret; +#endif +} + +string SockUtil::get_ifr_mask(const char *if_name) { +#if defined(__APPLE__) + string ret; + for_each_netAdapter_apple([&](struct ifaddrs *adapter) { + if (strcmp(if_name, adapter->ifa_name) == 0) { + ret = SockUtil::inet_ntoa(adapter->ifa_netmask); + return true; + } + return false; + }); + return ret; +#elif defined(_WIN32) + string ret; + for_each_netAdapter_win32([&](PIP_ADAPTER_INFO adapter) { + if (strcmp(if_name,adapter->AdapterName) == 0){ + //找到了该网卡 + IP_ADDR_STRING *ipAddr = &(adapter->IpAddressList); + //获取第一个ip的子网掩码 + ret.assign(ipAddr->IpMask.String); + return true; + } + return false; + }); + return ret; +#else + int fd; + struct ifreq ifr_mask; + fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd == -1) { + WarnL << "Create socket failed: " << get_uv_errmsg(true); + return ""; + } + memset(&ifr_mask, 0, sizeof(ifr_mask)); + strncpy(ifr_mask.ifr_name, if_name, sizeof(ifr_mask.ifr_name) - 1); + if ((ioctl(fd, SIOCGIFNETMASK, &ifr_mask)) < 0) { + WarnL << "ioctl SIOCGIFNETMASK on " << if_name << " failed: " << get_uv_errmsg(true); + close(fd); + return ""; + } + close(fd); + return SockUtil::inet_ntoa(&(ifr_mask.ifr_netmask)); +#endif // defined(_WIN32) +} + +string SockUtil::get_ifr_brdaddr(const char *if_name) { +#if defined(__APPLE__) + string ret; + for_each_netAdapter_apple([&](struct ifaddrs *adapter) { + if (strcmp(if_name, adapter->ifa_name) == 0) { + ret = SockUtil::inet_ntoa(adapter->ifa_broadaddr); + return true; + } + return false; + }); + return ret; +#elif defined(_WIN32) + string ret; + for_each_netAdapter_win32([&](PIP_ADAPTER_INFO adapter) { + if (strcmp(if_name, adapter->AdapterName) == 0) { + //找到该网卡 + IP_ADDR_STRING *ipAddr = &(adapter->IpAddressList); + in_addr broadcast; + broadcast.S_un.S_addr = (inet_addr(ipAddr->IpAddress.String) & inet_addr(ipAddr->IpMask.String)) | (~inet_addr(ipAddr->IpMask.String)); + ret = SockUtil::inet_ntoa(broadcast); + return true; + } + return false; + }); + return ret; +#else + int fd; + struct ifreq ifr_mask; + fd = socket( AF_INET, SOCK_STREAM, 0); + if (fd == -1) { + WarnL << "Create socket failed: " << get_uv_errmsg(true); + return ""; + } + memset(&ifr_mask, 0, sizeof(ifr_mask)); + strncpy(ifr_mask.ifr_name, if_name, sizeof(ifr_mask.ifr_name) - 1); + if ((ioctl(fd, SIOCGIFBRDADDR, &ifr_mask)) < 0) { + WarnL << "ioctl SIOCGIFBRDADDR failed: " << get_uv_errmsg(true); + close(fd); + return ""; + } + close(fd); + return SockUtil::inet_ntoa(&(ifr_mask.ifr_broadaddr)); +#endif +} + +#define ip_addr_netcmp(addr1, addr2, mask) (((addr1) & (mask)) == ((addr2) & (mask))) + +bool SockUtil::in_same_lan(const char *myIp, const char *dstIp) { + string mask = get_ifr_mask(get_ifr_name(myIp).data()); + return ip_addr_netcmp(inet_addr(myIp), inet_addr(dstIp), inet_addr(mask.data())); +} + +static void clearMulticastAllSocketOption(int socket) { +#if defined(IP_MULTICAST_ALL) + // This option is defined in modern versions of Linux to overcome a bug in the Linux kernel's default behavior. + // When set to 0, it ensures that we receive only packets that were sent to the specified IP multicast address, + // even if some other process on the same system has joined a different multicast group with the same port number. + int multicastAll = 0; + (void)setsockopt(socket, IPPROTO_IP, IP_MULTICAST_ALL, (void*)&multicastAll, sizeof multicastAll); + // Ignore the call's result. Should it fail, we'll still receive packets (just perhaps more than intended) +#endif +} + +int SockUtil::setMultiTTL(int fd, uint8_t ttl) { + int ret = -1; +#if defined(IP_MULTICAST_TTL) + ret = setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, (char *) &ttl, sizeof(ttl)); + if (ret == -1) { + TraceL << "setsockopt IP_MULTICAST_TTL failed"; + } +#endif + clearMulticastAllSocketOption(fd); + return ret; +} + +int SockUtil::setMultiIF(int fd, const char *local_ip) { + int ret = -1; +#if defined(IP_MULTICAST_IF) + struct in_addr addr; + addr.s_addr = inet_addr(local_ip); + ret = setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, (char *) &addr, sizeof(addr)); + if (ret == -1) { + TraceL << "setsockopt IP_MULTICAST_IF failed"; + } +#endif + clearMulticastAllSocketOption(fd); + return ret; +} + +int SockUtil::setMultiLOOP(int fd, bool accept) { + int ret = -1; +#if defined(IP_MULTICAST_LOOP) + uint8_t loop = accept; + ret = setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, (char *) &loop, sizeof(loop)); + if (ret == -1) { + TraceL << "setsockopt IP_MULTICAST_LOOP failed"; + } +#endif + clearMulticastAllSocketOption(fd); + return ret; +} + +int SockUtil::joinMultiAddr(int fd, const char *addr, const char *local_ip) { + int ret = -1; +#if defined(IP_ADD_MEMBERSHIP) + struct ip_mreq imr; + imr.imr_multiaddr.s_addr = inet_addr(addr); + imr.imr_interface.s_addr = inet_addr(local_ip); + ret = setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *) &imr, sizeof(struct ip_mreq)); + if (ret == -1) { + TraceL << "setsockopt IP_ADD_MEMBERSHIP failed: " << get_uv_errmsg(true); + } +#endif + clearMulticastAllSocketOption(fd); + return ret; +} + +int SockUtil::leaveMultiAddr(int fd, const char *addr, const char *local_ip) { + int ret = -1; +#if defined(IP_DROP_MEMBERSHIP) + struct ip_mreq imr; + imr.imr_multiaddr.s_addr = inet_addr(addr); + imr.imr_interface.s_addr = inet_addr(local_ip); + ret = setsockopt(fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, (char *) &imr, sizeof(struct ip_mreq)); + if (ret == -1) { + TraceL << "setsockopt IP_DROP_MEMBERSHIP failed: " << get_uv_errmsg(true); + } +#endif + clearMulticastAllSocketOption(fd); + return ret; +} + +template +static inline void write4Byte(A &&a, B &&b) { + memcpy(&a, &b, sizeof(a)); +} + +int SockUtil::joinMultiAddrFilter(int fd, const char *addr, const char *src_ip, const char *local_ip) { + int ret = -1; +#if defined(IP_ADD_SOURCE_MEMBERSHIP) + struct ip_mreq_source imr; + + write4Byte(imr.imr_multiaddr, inet_addr(addr)); + write4Byte(imr.imr_sourceaddr, inet_addr(src_ip)); + write4Byte(imr.imr_interface, inet_addr(local_ip)); + + ret = setsockopt(fd, IPPROTO_IP, IP_ADD_SOURCE_MEMBERSHIP, (char *) &imr, sizeof(struct ip_mreq_source)); + if (ret == -1) { + TraceL << "setsockopt IP_ADD_SOURCE_MEMBERSHIP failed: " << get_uv_errmsg(true); + } +#endif + clearMulticastAllSocketOption(fd); + return ret; +} + +int SockUtil::leaveMultiAddrFilter(int fd, const char *addr, const char *src_ip, const char *local_ip) { + int ret = -1; +#if defined(IP_DROP_SOURCE_MEMBERSHIP) + struct ip_mreq_source imr; + + write4Byte(imr.imr_multiaddr, inet_addr(addr)); + write4Byte(imr.imr_sourceaddr, inet_addr(src_ip)); + write4Byte(imr.imr_interface, inet_addr(local_ip)); + + ret = setsockopt(fd, IPPROTO_IP, IP_DROP_SOURCE_MEMBERSHIP, (char *) &imr, sizeof(struct ip_mreq_source)); + if (ret == -1) { + TraceL << "setsockopt IP_DROP_SOURCE_MEMBERSHIP failed: " << get_uv_errmsg(true); + } +#endif + clearMulticastAllSocketOption(fd); + return ret; +} + +bool SockUtil::is_ipv4(const char *host) { + struct in_addr addr; + return 1 == inet_pton(AF_INET, host, &addr); +} + +bool SockUtil::is_ipv6(const char *host) { + struct in6_addr addr; + return 1 == inet_pton(AF_INET6, host, &addr); +} + +socklen_t SockUtil::get_sock_len(const struct sockaddr *addr) { + switch (addr->sa_family) { + case AF_INET : return sizeof(sockaddr_in); + case AF_INET6 : return sizeof(sockaddr_in6); + default: assert(0); return 0; + } +} + +struct sockaddr_storage SockUtil::make_sockaddr(const char *host, uint16_t port) { + struct sockaddr_storage storage; + bzero(&storage, sizeof(storage)); + + struct in_addr addr; + struct in6_addr addr6; + if (1 == inet_pton(AF_INET, host, &addr)) { + // host是ipv4 + reinterpret_cast(storage).sin_addr = addr; + reinterpret_cast(storage).sin_family = AF_INET; + reinterpret_cast(storage).sin_port = htons(port); + return storage; + } + if (1 == inet_pton(AF_INET6, host, &addr6)) { + // host是ipv6 + reinterpret_cast(storage).sin6_addr = addr6; + reinterpret_cast(storage).sin6_family = AF_INET6; + reinterpret_cast(storage).sin6_port = htons(port); + return storage; + } + throw std::invalid_argument(string("Not ip address: ") + host); +} + +} // namespace toolkit \ No newline at end of file diff --git a/3rdpart/ZLToolKit/src/Network/sockutil.h b/3rdpart/ZLToolKit/src/Network/sockutil.h new file mode 100644 index 0000000..8a9d739 --- /dev/null +++ b/3rdpart/ZLToolKit/src/Network/sockutil.h @@ -0,0 +1,340 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef NETWORK_SOCKUTIL_H +#define NETWORK_SOCKUTIL_H + +#if defined(_WIN32) +#include +#include +#include +#pragma comment (lib, "Ws2_32.lib") +#pragma comment(lib,"Iphlpapi.lib") +#else +#include +#include +#include +#include +#include +#include +#include +#endif // defined(_WIN32) + +#include +#include +#include +#include +#include + +namespace toolkit { + +#if defined(_WIN32) +#ifndef socklen_t +#define socklen_t int +#endif //!socklen_t +#ifndef SHUT_RDWR +#define SHUT_RDWR 2 +#endif //!SHUT_RDWR +int ioctl(int fd, long cmd, u_long *ptr); +int close(int fd); +#endif // defined(_WIN32) + +#define SOCKET_DEFAULT_BUF_SIZE (256 * 1024) + +//套接字工具类,封装了socket、网络的一些基本操作 +class SockUtil { +public: + /** + * 创建tcp客户端套接字并连接服务器 + * @param host 服务器ip或域名 + * @param port 服务器端口号 + * @param async 是否异步连接 + * @param local_ip 绑定的本地网卡ip + * @param local_port 绑定的本地端口号 + * @return -1代表失败,其他为socket fd号 + */ + static int connect(const char *host, uint16_t port, bool async = true, const char *local_ip = "::", uint16_t local_port = 0); + + /** + * 创建tcp监听套接字 + * @param port 监听的本地端口 + * @param local_ip 绑定的本地网卡ip + * @param back_log accept列队长度 + * @return -1代表失败,其他为socket fd号 + */ + static int listen(const uint16_t port, const char *local_ip = "::", int back_log = 1024); + + /** + * 创建udp套接字 + * @param port 监听的本地端口 + * @param local_ip 绑定的本地网卡ip + * @param enable_reuse 是否允许重复bind端口 + * @return -1代表失败,其他为socket fd号 + */ + static int bindUdpSock(const uint16_t port, const char *local_ip = "::", bool enable_reuse = true); + + /** + * @brief 解除与 sock 相关的绑定关系 + * @param sock, socket fd 号 + * @return 0 成功, -1 失败 + */ + static int dissolveUdpSock(int sock); + + /** + * 开启TCP_NODELAY,降低TCP交互延时 + * @param fd socket fd号 + * @param on 是否开启 + * @return 0代表成功,-1为失败 + */ + static int setNoDelay(int fd, bool on = true); + + /** + * 写socket不触发SIG_PIPE信号(貌似只有mac有效) + * @param fd socket fd号 + * @return 0代表成功,-1为失败 + */ + static int setNoSigpipe(int fd); + + /** + * 设置读写socket是否阻塞 + * @param fd socket fd号 + * @param noblock 是否阻塞 + * @return 0代表成功,-1为失败 + */ + static int setNoBlocked(int fd, bool noblock = true); + + /** + * 设置socket接收缓存,默认貌似8K左右,一般有设置上限 + * 可以通过配置内核配置文件调整 + * @param fd socket fd号 + * @param size 接收缓存大小 + * @return 0代表成功,-1为失败 + */ + static int setRecvBuf(int fd, int size = SOCKET_DEFAULT_BUF_SIZE); + + /** + * 设置socket接收缓存,默认貌似8K左右,一般有设置上限 + * 可以通过配置内核配置文件调整 + * @param fd socket fd号 + * @param size 接收缓存大小 + * @return 0代表成功,-1为失败 + */ + static int setSendBuf(int fd, int size = SOCKET_DEFAULT_BUF_SIZE); + + /** + * 设置后续可绑定复用端口(处于TIME_WAITE状态) + * @param fd socket fd号 + * @param on 是否开启该特性 + * @return 0代表成功,-1为失败 + */ + static int setReuseable(int fd, bool on = true, bool reuse_port = true); + + /** + * 运行发送或接收udp广播信息 + * @param fd socket fd号 + * @param on 是否开启该特性 + * @return 0代表成功,-1为失败 + */ + static int setBroadcast(int fd, bool on = true); + + /** + * 是否开启TCP KeepAlive特性 + * @param fd socket fd号 + * @param on 是否开启该特性 + * @return 0代表成功,-1为失败 + */ + static int setKeepAlive(int fd, bool on = true); + + /** + * 是否开启FD_CLOEXEC特性(多进程相关) + * @param fd fd号,不一定是socket + * @param on 是否开启该特性 + * @return 0代表成功,-1为失败 + */ + static int setCloExec(int fd, bool on = true); + + /** + * 开启SO_LINGER特性 + * @param sock socket fd号 + * @param second 内核等待关闭socket超时时间,单位秒 + * @return 0代表成功,-1为失败 + */ + static int setCloseWait(int sock, int second = 0); + + /** + * dns解析 + * @param host 域名或ip + * @param port 端口号 + * @param addr sockaddr结构体 + * @return 是否成功 + */ + static bool getDomainIP(const char *host, uint16_t port, struct sockaddr_storage &addr, int ai_family = AF_INET, + int ai_socktype = SOCK_STREAM, int ai_protocol = IPPROTO_TCP, int expire_sec = 60); + + /** + * 设置组播ttl + * @param sock socket fd号 + * @param ttl ttl值 + * @return 0代表成功,-1为失败 + */ + static int setMultiTTL(int sock, uint8_t ttl = 64); + + /** + * 设置组播发送网卡 + * @param sock socket fd号 + * @param local_ip 本机网卡ip + * @return 0代表成功,-1为失败 + */ + static int setMultiIF(int sock, const char *local_ip); + + /** + * 设置是否接收本机发出的组播包 + * @param fd socket fd号 + * @param acc 是否接收 + * @return 0代表成功,-1为失败 + */ + static int setMultiLOOP(int fd, bool acc = false); + + /** + * 加入组播 + * @param fd socket fd号 + * @param addr 组播地址 + * @param local_ip 本机网卡ip + * @return 0代表成功,-1为失败 + */ + static int joinMultiAddr(int fd, const char *addr, const char *local_ip = "0.0.0.0"); + + /** + * 退出组播 + * @param fd socket fd号 + * @param addr 组播地址 + * @param local_ip 本机网卡ip + * @return 0代表成功,-1为失败 + */ + static int leaveMultiAddr(int fd, const char *addr, const char *local_ip = "0.0.0.0"); + + /** + * 加入组播并只接受该源端的组播数据 + * @param sock socket fd号 + * @param addr 组播地址 + * @param src_ip 数据源端地址 + * @param local_ip 本机网卡ip + * @return 0代表成功,-1为失败 + */ + static int joinMultiAddrFilter(int sock, const char *addr, const char *src_ip, const char *local_ip = "0.0.0.0"); + + /** + * 退出组播 + * @param fd socket fd号 + * @param addr 组播地址 + * @param src_ip 数据源端地址 + * @param local_ip 本机网卡ip + * @return 0代表成功,-1为失败 + */ + static int leaveMultiAddrFilter(int fd, const char *addr, const char *src_ip, const char *local_ip = "0.0.0.0"); + + /** + * 获取该socket当前发生的错误 + * @param fd socket fd号 + * @return 错误代码 + */ + static int getSockError(int fd); + + /** + * 获取网卡列表 + * @return vector > + */ + static std::vector> getInterfaceList(); + + /** + * 获取本机默认网卡ip + */ + static std::string get_local_ip(); + + /** + * 获取该socket绑定的本地ip + * @param sock socket fd号 + */ + static std::string get_local_ip(int sock); + + /** + * 获取该socket绑定的本地端口 + * @param sock socket fd号 + */ + static uint16_t get_local_port(int sock); + + /** + * 获取该socket绑定的远端ip + * @param sock socket fd号 + */ + static std::string get_peer_ip(int sock); + + /** + * 获取该socket绑定的远端端口 + * @param sock socket fd号 + */ + static uint16_t get_peer_port(int sock); + + /** + * 线程安全的in_addr转ip字符串 + */ + static std::string inet_ntoa(const struct in_addr &addr); + static std::string inet_ntoa(const struct in6_addr &addr); + static std::string inet_ntoa(const struct sockaddr *addr); + static uint16_t inet_port(const struct sockaddr *addr); + static struct sockaddr_storage make_sockaddr(const char *ip, uint16_t port); + static socklen_t get_sock_len(const struct sockaddr *addr); + static bool get_sock_local_addr(int fd, struct sockaddr_storage &addr); + static bool get_sock_peer_addr(int fd, struct sockaddr_storage &addr); + + /** + * 获取网卡ip + * @param if_name 网卡名 + */ + static std::string get_ifr_ip(const char *if_name); + + /** + * 获取网卡名 + * @param local_op 网卡ip + */ + static std::string get_ifr_name(const char *local_op); + + /** + * 根据网卡名获取子网掩码 + * @param if_name 网卡名 + */ + static std::string get_ifr_mask(const char *if_name); + + /** + * 根据网卡名获取广播地址 + * @param if_name 网卡名 + */ + static std::string get_ifr_brdaddr(const char *if_name); + + /** + * 判断两个ip是否为同一网段 + * @param src_ip 我的ip + * @param dts_ip 对方ip + */ + static bool in_same_lan(const char *src_ip, const char *dts_ip); + + /** + * 判断是否为ipv4地址 + */ + static bool is_ipv4(const char *str); + + /** + * 判断是否为ipv6地址 + */ + static bool is_ipv6(const char *str); +}; + +} // namespace toolkit +#endif // !NETWORK_SOCKUTIL_H diff --git a/3rdpart/ZLToolKit/src/Poller/EventPoller.cpp b/3rdpart/ZLToolKit/src/Poller/EventPoller.cpp new file mode 100644 index 0000000..c419eb6 --- /dev/null +++ b/3rdpart/ZLToolKit/src/Poller/EventPoller.cpp @@ -0,0 +1,478 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include "SelectWrap.h" +#include "EventPoller.h" +#include "Util/util.h" +#include "Util/uv_errno.h" +#include "Util/TimeTicker.h" +#include "Network/sockutil.h" + +#if defined(HAS_EPOLL) +#include + +#if !defined(EPOLLEXCLUSIVE) +#define EPOLLEXCLUSIVE 0 +#endif + +#define EPOLL_SIZE 1024 + +//防止epoll惊群 +#ifndef EPOLLEXCLUSIVE +#define EPOLLEXCLUSIVE 0 +#endif + +#define toEpoll(event) (((event) & Event_Read) ? EPOLLIN : 0) \ + | (((event) & Event_Write) ? EPOLLOUT : 0) \ + | (((event) & Event_Error) ? (EPOLLHUP | EPOLLERR) : 0) \ + | (((event) & Event_LT) ? 0 : EPOLLET) + +#define toPoller(epoll_event) (((epoll_event) & EPOLLIN) ? Event_Read : 0) \ + | (((epoll_event) & EPOLLOUT) ? Event_Write : 0) \ + | (((epoll_event) & EPOLLHUP) ? Event_Error : 0) \ + | (((epoll_event) & EPOLLERR) ? Event_Error : 0) +#endif //HAS_EPOLL + +using namespace std; + +namespace toolkit { + +EventPoller &EventPoller::Instance() { + return *(EventPollerPool::Instance().getFirstPoller()); +} + +EventPoller::EventPoller(std::string name, ThreadPool::Priority priority) { + _name = std::move(name); + _priority = priority; + SockUtil::setNoBlocked(_pipe.readFD()); + SockUtil::setNoBlocked(_pipe.writeFD()); + +#if defined(HAS_EPOLL) + _epoll_fd = epoll_create(EPOLL_SIZE); + if (_epoll_fd == -1) { + throw runtime_error(StrPrinter << "Create epoll fd failed: " << get_uv_errmsg()); + } + SockUtil::setCloExec(_epoll_fd); +#endif //HAS_EPOLL + _logger = Logger::Instance().shared_from_this(); + _loop_thread_id = this_thread::get_id(); + + //添加内部管道事件 + if (addEvent(_pipe.readFD(), Event_Read, [this](int event) { onPipeEvent(); }) == -1) { + throw std::runtime_error("Add pipe fd to poller failed"); + } +} + +void EventPoller::shutdown() { + async_l([]() { + throw ExitException(); + }, false, true); + + if (_loop_thread) { + //防止作为子进程时崩溃 + try { _loop_thread->join(); } catch (...) {} + delete _loop_thread; + _loop_thread = nullptr; + } +} + +EventPoller::~EventPoller() { + shutdown(); + wait(); +#if defined(HAS_EPOLL) + if (_epoll_fd != -1) { + close(_epoll_fd); + _epoll_fd = -1; + } +#endif //defined(HAS_EPOLL) + //退出前清理管道中的数据 + _loop_thread_id = this_thread::get_id(); + onPipeEvent(); + InfoL << this; +} + +int EventPoller::addEvent(int fd, int event, PollEventCB cb) { + TimeTicker(); + if (!cb) { + WarnL << "PollEventCB is empty"; + return -1; + } + + if (isCurrentThread()) { +#if defined(HAS_EPOLL) + struct epoll_event ev = {0}; + ev.events = (toEpoll(event)) | EPOLLEXCLUSIVE; + ev.data.fd = fd; + int ret = epoll_ctl(_epoll_fd, EPOLL_CTL_ADD, fd, &ev); + if (ret == 0) { + _event_map.emplace(fd, std::make_shared(std::move(cb))); + } + return ret; +#else +#ifndef _WIN32 + //win32平台,socket套接字不等于文件描述符,所以可能不适用这个限制 + if (fd >= FD_SETSIZE || _event_map.size() >= FD_SETSIZE) { + WarnL << "select() can not watch fd bigger than " << FD_SETSIZE; + return -1; + } +#endif + auto record = std::make_shared(); + record->event = event; + record->call_back = std::move(cb); + _event_map.emplace(fd, record); + return 0; +#endif //HAS_EPOLL + } + + async([this, fd, event, cb]() { + addEvent(fd, event, std::move(const_cast(cb))); + }); + return 0; +} + +int EventPoller::delEvent(int fd, PollDelCB cb) { + TimeTicker(); + if (!cb) { + cb = [](bool success) {}; + } + + if (isCurrentThread()) { +#if defined(HAS_EPOLL) + bool success = epoll_ctl(_epoll_fd, EPOLL_CTL_DEL, fd, nullptr) == 0 && _event_map.erase(fd) > 0; + cb(success); + return success ? 0 : -1; +#else + cb(_event_map.erase(fd)); + return 0; +#endif //HAS_EPOLL + + } + + //跨线程操作 + async([this, fd, cb]() { + delEvent(fd, std::move(const_cast(cb))); + }); + return 0; +} + +int EventPoller::modifyEvent(int fd, int event) { + TimeTicker(); +#if defined(HAS_EPOLL) + struct epoll_event ev = {0}; + ev.events = toEpoll(event); + ev.data.fd = fd; + return epoll_ctl(_epoll_fd, EPOLL_CTL_MOD, fd, &ev); +#else + if (isCurrentThread()) { + auto it = _event_map.find(fd); + if (it != _event_map.end()) { + it->second->event = event; + } + return 0; + } + async([this, fd, event]() { + modifyEvent(fd, event); + }); + return 0; +#endif //HAS_EPOLL +} + +Task::Ptr EventPoller::async(TaskIn task, bool may_sync) { + return async_l(std::move(task), may_sync, false); +} + +Task::Ptr EventPoller::async_first(TaskIn task, bool may_sync) { + return async_l(std::move(task), may_sync, true); +} + +Task::Ptr EventPoller::async_l(TaskIn task, bool may_sync, bool first) { + TimeTicker(); + if (may_sync && isCurrentThread()) { + task(); + return nullptr; + } + + auto ret = std::make_shared(std::move(task)); + { + lock_guard lck(_mtx_task); + if (first) { + _list_task.emplace_front(ret); + } else { + _list_task.emplace_back(ret); + } + } + //写数据到管道,唤醒主线程 + _pipe.write("", 1); + return ret; +} + +bool EventPoller::isCurrentThread() { + return _loop_thread_id == this_thread::get_id(); +} + +inline void EventPoller::onPipeEvent() { + char buf[1024]; + int err = 0; + do { + if (_pipe.read(buf, sizeof(buf)) > 0) { + continue; + } + err = get_uv_error(true); + } while (err != UV_EAGAIN); + + decltype(_list_task) _list_swap; + { + lock_guard lck(_mtx_task); + _list_swap.swap(_list_task); + } + + _list_swap.for_each([&](const Task::Ptr &task) { + try { + (*task)(); + } catch (ExitException &) { + _exit_flag = true; + } catch (std::exception &ex) { + ErrorL << "Exception occurred when do async task: " << ex.what(); + } + }); +} + +void EventPoller::wait() { + lock_guard lck(_mtx_running); +} + +BufferRaw::Ptr EventPoller::getSharedBuffer() { + auto ret = _shared_buffer.lock(); + if (!ret) { + //预留一个字节存放\0结尾符 + ret = BufferRaw::create(); + ret->setCapacity(1 + SOCKET_DEFAULT_BUF_SIZE); + _shared_buffer = ret; + } + return ret; +} + +const thread::id& EventPoller::getThreadId() const { + return _loop_thread_id; +} + +const std::string& EventPoller::getThreadName() const { + return _name; +} + +static thread_local std::weak_ptr s_current_poller; + +// static +EventPoller::Ptr EventPoller::getCurrentPoller() { + return s_current_poller.lock(); +} + +void EventPoller::runLoop(bool blocked, bool ref_self) { + if (blocked) { + ThreadPool::setPriority(_priority); + lock_guard lck(_mtx_running); + _loop_thread_id = this_thread::get_id(); + if (ref_self) { + s_current_poller = shared_from_this(); + } + _sem_run_started.post(); + _exit_flag = false; + uint64_t minDelay; +#if defined(HAS_EPOLL) + struct epoll_event events[EPOLL_SIZE]; + while (!_exit_flag) { + minDelay = getMinDelay(); + startSleep();//用于统计当前线程负载情况 + int ret = epoll_wait(_epoll_fd, events, EPOLL_SIZE, minDelay ? minDelay : -1); + sleepWakeUp();//用于统计当前线程负载情况 + if (ret <= 0) { + //超时或被打断 + continue; + } + for (int i = 0; i < ret; ++i) { + struct epoll_event &ev = events[i]; + int fd = ev.data.fd; + auto it = _event_map.find(fd); + if (it == _event_map.end()) { + epoll_ctl(_epoll_fd, EPOLL_CTL_DEL, fd, nullptr); + continue; + } + auto cb = it->second; + try { + (*cb)(toPoller(ev.events)); + } catch (std::exception &ex) { + ErrorL << "Exception occurred when do event task: " << ex.what(); + } + } + } +#else + int ret, max_fd; + FdSet set_read, set_write, set_err; + List callback_list; + struct timeval tv; + while (!_exit_flag) { + //定时器事件中可能操作_event_map + minDelay = getMinDelay(); + tv.tv_sec = (decltype(tv.tv_sec)) (minDelay / 1000); + tv.tv_usec = 1000 * (minDelay % 1000); + + set_read.fdZero(); + set_write.fdZero(); + set_err.fdZero(); + max_fd = 0; + for (auto &pr : _event_map) { + if (pr.first > max_fd) { + max_fd = pr.first; + } + if (pr.second->event & Event_Read) { + set_read.fdSet(pr.first);//监听管道可读事件 + } + if (pr.second->event & Event_Write) { + set_write.fdSet(pr.first);//监听管道可写事件 + } + if (pr.second->event & Event_Error) { + set_err.fdSet(pr.first);//监听管道错误事件 + } + } + + startSleep();//用于统计当前线程负载情况 + ret = zl_select(max_fd + 1, &set_read, &set_write, &set_err, minDelay ? &tv : nullptr); + sleepWakeUp();//用于统计当前线程负载情况 + + if (ret <= 0) { + //超时或被打断 + continue; + } + //收集select事件类型 + for (auto &pr : _event_map) { + int event = 0; + if (set_read.isSet(pr.first)) { + event |= Event_Read; + } + if (set_write.isSet(pr.first)) { + event |= Event_Write; + } + if (set_err.isSet(pr.first)) { + event |= Event_Error; + } + if (event != 0) { + pr.second->attach = event; + callback_list.emplace_back(pr.second); + } + } + + callback_list.for_each([](Poll_Record::Ptr &record) { + try { + record->call_back(record->attach); + } catch (std::exception &ex) { + ErrorL << "Exception occurred when do event task: " << ex.what(); + } + }); + callback_list.clear(); + } +#endif //HAS_EPOLL + } else { + _loop_thread = new thread(&EventPoller::runLoop, this, true, ref_self); + _sem_run_started.wait(); + } +} + +uint64_t EventPoller::flushDelayTask(uint64_t now_time) { + decltype(_delay_task_map) task_copy; + task_copy.swap(_delay_task_map); + + for (auto it = task_copy.begin(); it != task_copy.end() && it->first <= now_time; it = task_copy.erase(it)) { + //已到期的任务 + try { + auto next_delay = (*(it->second))(); + if (next_delay) { + //可重复任务,更新时间截止线 + _delay_task_map.emplace(next_delay + now_time, std::move(it->second)); + } + } catch (std::exception &ex) { + ErrorL << "Exception occurred when do delay task: " << ex.what(); + } + } + + task_copy.insert(_delay_task_map.begin(), _delay_task_map.end()); + task_copy.swap(_delay_task_map); + + auto it = _delay_task_map.begin(); + if (it == _delay_task_map.end()) { + //没有剩余的定时器了 + return 0; + } + //最近一个定时器的执行延时 + return it->first - now_time; +} + +uint64_t EventPoller::getMinDelay() { + auto it = _delay_task_map.begin(); + if (it == _delay_task_map.end()) { + //没有剩余的定时器了 + return 0; + } + auto now = getCurrentMillisecond(); + if (it->first > now) { + //所有任务尚未到期 + return it->first - now; + } + //执行已到期的任务并刷新休眠延时 + return flushDelayTask(now); +} + +EventPoller::DelayTask::Ptr EventPoller::doDelayTask(uint64_t delay_ms, function task) { + DelayTask::Ptr ret = std::make_shared(std::move(task)); + auto time_line = getCurrentMillisecond() + delay_ms; + async_first([time_line, ret, this]() { + //异步执行的目的是刷新select或epoll的休眠时间 + _delay_task_map.emplace(time_line, ret); + }); + return ret; +} + +/////////////////////////////////////////////// + +static size_t s_pool_size = 0; +static bool s_enable_cpu_affinity = true; + +INSTANCE_IMP(EventPollerPool) + +EventPoller::Ptr EventPollerPool::getFirstPoller() { + return dynamic_pointer_cast(_threads.front()); +} + +EventPoller::Ptr EventPollerPool::getPoller(bool prefer_current_thread) { + auto poller = EventPoller::getCurrentPoller(); + if (prefer_current_thread && _prefer_current_thread && poller) { + return poller; + } + return dynamic_pointer_cast(getExecutor()); +} + +void EventPollerPool::preferCurrentThread(bool flag) { + _prefer_current_thread = flag; +} + +EventPollerPool::EventPollerPool() { + auto size = addPoller("event poller", s_pool_size, ThreadPool::PRIORITY_HIGHEST, true, s_enable_cpu_affinity); + InfoL << "EventPoller created size: " << size; +} + +void EventPollerPool::setPoolSize(size_t size) { + s_pool_size = size; +} + +void EventPollerPool::enableCpuAffinity(bool enable) { + s_enable_cpu_affinity = enable; +} + +} // namespace toolkit + diff --git a/3rdpart/ZLToolKit/src/Poller/EventPoller.h b/3rdpart/ZLToolKit/src/Poller/EventPoller.h new file mode 100644 index 0000000..2511478 --- /dev/null +++ b/3rdpart/ZLToolKit/src/Poller/EventPoller.h @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef EventPoller_h +#define EventPoller_h + +#include +#include +#include +#include +#include +#include +#include "PipeWrap.h" +#include "Util/logger.h" +#include "Util/List.h" +#include "Thread/TaskExecutor.h" +#include "Thread/ThreadPool.h" +#include "Network/Buffer.h" + +#if defined(__linux__) || defined(__linux) +#define HAS_EPOLL +#endif //__linux__ + +namespace toolkit { + +class EventPoller : public TaskExecutor, public AnyStorage, public std::enable_shared_from_this { +public: + friend class TaskExecutorGetterImp; + + using Ptr = std::shared_ptr; + using PollEventCB = std::function; + using PollDelCB = std::function; + using DelayTask = TaskCancelableImp; + + typedef enum { + Event_Read = 1 << 0, //读事件 + Event_Write = 1 << 1, //写事件 + Event_Error = 1 << 2, //错误事件 + Event_LT = 1 << 3,//水平触发 + } Poll_Event; + + ~EventPoller(); + + /** + * 获取EventPollerPool单例中的第一个EventPoller实例, + * 保留该接口是为了兼容老代码 + * @return 单例 + */ + static EventPoller &Instance(); + + /** + * 添加事件监听 + * @param fd 监听的文件描述符 + * @param event 事件类型,例如 Event_Read | Event_Write + * @param cb 事件回调functional + * @return -1:失败,0:成功 + */ + int addEvent(int fd, int event, PollEventCB cb); + + /** + * 删除事件监听 + * @param fd 监听的文件描述符 + * @param cb 删除成功回调functional + * @return -1:失败,0:成功 + */ + int delEvent(int fd, PollDelCB cb = nullptr); + + /** + * 修改监听事件类型 + * @param fd 监听的文件描述符 + * @param event 事件类型,例如 Event_Read | Event_Write + * @return -1:失败,0:成功 + */ + int modifyEvent(int fd, int event); + + /** + * 异步执行任务 + * @param task 任务 + * @param may_sync 如果调用该函数的线程就是本对象的轮询线程,那么may_sync为true时就是同步执行任务 + * @return 是否成功,一定会返回true + */ + Task::Ptr async(TaskIn task, bool may_sync = true) override; + + /** + * 同async方法,不过是把任务打入任务列队头,这样任务优先级最高 + * @param task 任务 + * @param may_sync 如果调用该函数的线程就是本对象的轮询线程,那么may_sync为true时就是同步执行任务 + * @return 是否成功,一定会返回true + */ + Task::Ptr async_first(TaskIn task, bool may_sync = true) override; + + /** + * 判断执行该接口的线程是否为本对象的轮询线程 + * @return 是否为本对象的轮询线程 + */ + bool isCurrentThread(); + + /** + * 延时执行某个任务 + * @param delay_ms 延时毫秒数 + * @param task 任务,返回值为0时代表不再重复任务,否则为下次执行延时,如果任务中抛异常,那么默认不重复任务 + * @return 可取消的任务标签 + */ + DelayTask::Ptr doDelayTask(uint64_t delay_ms, std::function task); + + /** + * 获取当前线程关联的Poller实例 + */ + static EventPoller::Ptr getCurrentPoller(); + + /** + * 获取当前线程下所有socket共享的读缓存 + */ + BufferRaw::Ptr getSharedBuffer(); + + /** + * 获取poller线程id + */ + const std::thread::id& getThreadId() const; + + /** + * 获取线程名 + */ + const std::string& getThreadName() const; + +private: + /** + * 本对象只允许在EventPollerPool中构造 + */ + EventPoller(std::string name, ThreadPool::Priority priority = ThreadPool::PRIORITY_HIGHEST); + + /** + * 执行事件轮询 + * @param blocked 是否用执行该接口的线程执行轮询 + * @param ref_self 是记录本对象到thread local变量 + */ + void runLoop(bool blocked, bool ref_self); + + /** + * 内部管道事件,用于唤醒轮询线程用 + */ + void onPipeEvent(); + + /** + * 切换线程并执行任务 + * @param task + * @param may_sync + * @param first + * @return 可取消的任务本体,如果已经同步执行,则返回nullptr + */ + Task::Ptr async_l(TaskIn task, bool may_sync = true, bool first = false); + + /** + * 阻塞当前线程,等待轮询线程退出; + * 在执行shutdown接口时本函数会退出 + */ + void wait(); + + /** + * 结束事件轮询 + * 需要指出的是,一旦结束就不能再次恢复轮询线程 + */ + void shutdown(); + + /** + * 刷新延时任务 + */ + uint64_t flushDelayTask(uint64_t now); + + /** + * 获取select或epoll休眠时间 + */ + uint64_t getMinDelay(); + +private: + class ExitException : public std::exception {}; + +private: + //标记loop线程是否退出 + bool _exit_flag; + //线程名 + std::string _name; + //当前线程下,所有socket共享的读缓存 + std::weak_ptr _shared_buffer; + //线程优先级 + ThreadPool::Priority _priority; + //正在运行事件循环时该锁处于被锁定状态 + std::mutex _mtx_running; + //执行事件循环的线程 + std::thread *_loop_thread = nullptr; + //事件循环的线程id + std::thread::id _loop_thread_id; + //通知事件循环的线程已启动 + semaphore _sem_run_started; + + //内部事件管道 + PipeWrap _pipe; + //从其他线程切换过来的任务 + std::mutex _mtx_task; + List _list_task; + + //保持日志可用 + Logger::Ptr _logger; + +#if defined(HAS_EPOLL) + //epoll相关 + int _epoll_fd = -1; + unordered_map > _event_map; +#else + //select相关 + struct Poll_Record { + using Ptr = std::shared_ptr; + int event; + int attach; + PollEventCB call_back; + }; + unordered_map _event_map; +#endif //HAS_EPOLL + + //定时器相关 + std::multimap _delay_task_map; +}; + +class EventPollerPool : public std::enable_shared_from_this, public TaskExecutorGetterImp { +public: + using Ptr = std::shared_ptr; + ~EventPollerPool() = default; + + /** + * 获取单例 + * @return + */ + static EventPollerPool &Instance(); + + /** + * 设置EventPoller个数,在EventPollerPool单例创建前有效 + * 在不调用此方法的情况下,默认创建thread::hardware_concurrency()个EventPoller实例 + * @param size EventPoller个数,如果为0则为thread::hardware_concurrency() + */ + static void setPoolSize(size_t size = 0); + + /** + * 内部创建线程是否设置cpu亲和性,默认设置cpu亲和性 + */ + static void enableCpuAffinity(bool enable); + + /** + * 获取第一个实例 + * @return + */ + EventPoller::Ptr getFirstPoller(); + + /** + * 根据负载情况获取轻负载的实例 + * 如果优先返回当前线程,那么会返回当前线程 + * 返回当前线程的目的是为了提高线程安全性 + * @param prefer_current_thread 是否优先获取当前线程 + */ + EventPoller::Ptr getPoller(bool prefer_current_thread = true); + + /** + * 设置 getPoller() 是否优先返回当前线程 + * 在批量创建Socket对象时,如果优先返回当前线程, + * 那么将导致负载不够均衡,所以可以暂时关闭然后再开启 + * @param flag 是否优先返回当前线程 + */ + void preferCurrentThread(bool flag = true); + +private: + EventPollerPool(); + +private: + bool _prefer_current_thread = true; +}; + +} // namespace toolkit +#endif /* EventPoller_h */ \ No newline at end of file diff --git a/3rdpart/ZLToolKit/src/Poller/Pipe.cpp b/3rdpart/ZLToolKit/src/Poller/Pipe.cpp new file mode 100644 index 0000000..f2338ca --- /dev/null +++ b/3rdpart/ZLToolKit/src/Poller/Pipe.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include "Pipe.h" +#include "Network/sockutil.h" + +using namespace std; + +namespace toolkit { + +Pipe::Pipe(const onRead &cb, const EventPoller::Ptr &poller) { + _poller = poller; + if (!_poller) { + _poller = EventPollerPool::Instance().getPoller(); + } + _pipe = std::make_shared(); + auto pipe = _pipe; + _poller->addEvent(_pipe->readFD(), EventPoller::Event_Read, [cb, pipe](int event) { +#if defined(_WIN32) + unsigned long nread = 1024; +#else + int nread = 1024; +#endif //defined(_WIN32) + ioctl(pipe->readFD(), FIONREAD, &nread); +#if defined(_WIN32) + std::shared_ptr buf(new char[nread + 1], [](char *ptr) {delete[] ptr; }); + buf.get()[nread] = '\0'; + nread = pipe->read(buf.get(), nread + 1); + if (cb) { + cb(nread, buf.get()); + } +#else + char buf[nread + 1]; + buf[nread] = '\0'; + nread = pipe->read(buf, sizeof(buf)); + if (cb) { + cb(nread, buf); + } +#endif // defined(_WIN32) + }); +} + +Pipe::~Pipe() { + if (_pipe) { + auto pipe = _pipe; + _poller->delEvent(pipe->readFD(), [pipe](bool success) {}); + } +} + +void Pipe::send(const char *buf, int size) { + _pipe->write(buf, size); +} + +} // namespace toolkit \ No newline at end of file diff --git a/3rdpart/ZLToolKit/src/Poller/Pipe.h b/3rdpart/ZLToolKit/src/Poller/Pipe.h new file mode 100644 index 0000000..801ae31 --- /dev/null +++ b/3rdpart/ZLToolKit/src/Poller/Pipe.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef Pipe_h +#define Pipe_h + +#include +#include "PipeWrap.h" +#include "EventPoller.h" + +namespace toolkit { + +class Pipe { +public: + using onRead = std::function; + + Pipe(const onRead &cb = nullptr, const EventPoller::Ptr &poller = nullptr); + ~Pipe(); + + void send(const char *send, int size = 0); + +private: + std::shared_ptr _pipe; + EventPoller::Ptr _poller; +}; + +} // namespace toolkit +#endif /* Pipe_h */ \ No newline at end of file diff --git a/3rdpart/ZLToolKit/src/Poller/PipeWrap.cpp b/3rdpart/ZLToolKit/src/Poller/PipeWrap.cpp new file mode 100644 index 0000000..46780e6 --- /dev/null +++ b/3rdpart/ZLToolKit/src/Poller/PipeWrap.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include "PipeWrap.h" +#include "Util/util.h" +#include "Util/uv_errno.h" +#include "Network/sockutil.h" + +using namespace std; + +#define checkFD(fd) \ + if (fd == -1) { \ + clearFD(); \ + throw runtime_error(StrPrinter << "Create windows pipe failed: " << get_uv_errmsg());\ + } + +#define closeFD(fd) \ + if (fd != -1) { \ + close(fd);\ + fd = -1;\ + } + +namespace toolkit { + +PipeWrap::PipeWrap() { +#if defined(_WIN32) + auto listener_fd = SockUtil::listen(0, "127.0.0.1"); + checkFD(listener_fd) + SockUtil::setNoBlocked(listener_fd,false); + auto localPort = SockUtil::get_local_port(listener_fd); + _pipe_fd[1] = SockUtil::connect("127.0.0.1", localPort,false); + checkFD(_pipe_fd[1]) + _pipe_fd[0] = (int)accept(listener_fd, nullptr, nullptr); + checkFD(_pipe_fd[0]) + SockUtil::setNoDelay(_pipe_fd[0]); + SockUtil::setNoDelay(_pipe_fd[1]); + close(listener_fd); +#else + if (pipe(_pipe_fd) == -1) { + throw runtime_error(StrPrinter << "Create posix pipe failed: " << get_uv_errmsg()); + } +#endif // defined(_WIN32) + SockUtil::setNoBlocked(_pipe_fd[0], true); + SockUtil::setNoBlocked(_pipe_fd[1], false); + SockUtil::setCloExec(_pipe_fd[0]); + SockUtil::setCloExec(_pipe_fd[1]); +} + +void PipeWrap::clearFD() { + closeFD(_pipe_fd[0]); + closeFD(_pipe_fd[1]); +} + +PipeWrap::~PipeWrap() { + clearFD(); +} + +int PipeWrap::write(const void *buf, int n) { + int ret; + do { +#if defined(_WIN32) + ret = send(_pipe_fd[1], (char *)buf, n, 0); +#else + ret = ::write(_pipe_fd[1], buf, n); +#endif // defined(_WIN32) + } while (-1 == ret && UV_EINTR == get_uv_error(true)); + return ret; +} + +int PipeWrap::read(void *buf, int n) { + int ret; + do { +#if defined(_WIN32) + ret = recv(_pipe_fd[0], (char *)buf, n, 0); +#else + ret = ::read(_pipe_fd[0], buf, n); +#endif // defined(_WIN32) + } while (-1 == ret && UV_EINTR == get_uv_error(true)); + return ret; +} + +} /* namespace toolkit*/ diff --git a/3rdpart/ZLToolKit/src/Poller/PipeWrap.h b/3rdpart/ZLToolKit/src/Poller/PipeWrap.h new file mode 100644 index 0000000..06ea695 --- /dev/null +++ b/3rdpart/ZLToolKit/src/Poller/PipeWrap.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef PipeWarp_h +#define PipeWarp_h + +namespace toolkit { + +class PipeWrap { +public: + PipeWrap(); + ~PipeWrap(); + int write(const void *buf, int n); + int read(void *buf, int n); + int readFD() const { + return _pipe_fd[0]; + } + int writeFD() const { + return _pipe_fd[1]; + } + +private: + void clearFD(); + +private: + int _pipe_fd[2] = {-1, -1}; +}; + +} /* namespace toolkit */ +#endif // !PipeWarp_h + diff --git a/3rdpart/ZLToolKit/src/Poller/SelectWrap.cpp b/3rdpart/ZLToolKit/src/Poller/SelectWrap.cpp new file mode 100644 index 0000000..475c590 --- /dev/null +++ b/3rdpart/ZLToolKit/src/Poller/SelectWrap.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include "SelectWrap.h" + +using namespace std; + +namespace toolkit { + +FdSet::FdSet() { + _ptr = new fd_set; +} + +FdSet::~FdSet() { + delete (fd_set *)_ptr; +} + +void FdSet::fdZero() { + FD_ZERO((fd_set *)_ptr); +} + +void FdSet::fdClr(int fd) { + FD_CLR(fd, (fd_set *)_ptr); +} + +void FdSet::fdSet(int fd) { + FD_SET(fd, (fd_set *)_ptr); +} + +bool FdSet::isSet(int fd) { + return FD_ISSET(fd, (fd_set *)_ptr); +} + +int zl_select(int cnt, FdSet *read, FdSet *write, FdSet *err, struct timeval *tv) { + void *rd, *wt, *er; + rd = read ? read->_ptr : nullptr; + wt = write ? write->_ptr : nullptr; + er = err ? err->_ptr : nullptr; + return ::select(cnt, (fd_set *) rd, (fd_set *) wt, (fd_set *) er, tv); +} + +} /* namespace toolkit */ + + + diff --git a/3rdpart/ZLToolKit/src/Poller/SelectWrap.h b/3rdpart/ZLToolKit/src/Poller/SelectWrap.h new file mode 100644 index 0000000..c68b35a --- /dev/null +++ b/3rdpart/ZLToolKit/src/Poller/SelectWrap.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef SRC_POLLER_SELECTWRAP_H_ +#define SRC_POLLER_SELECTWRAP_H_ + +#include "Util/util.h" + +namespace toolkit { + +class FdSet { +public: + FdSet(); + ~FdSet(); + void fdZero(); + void fdSet(int fd); + void fdClr(int fd); + bool isSet(int fd); + void *_ptr; +}; + +int zl_select(int cnt, FdSet *read, FdSet *write, FdSet *err, struct timeval *tv); + +} /* namespace toolkit */ +#endif /* SRC_POLLER_SELECTWRAP_H_ */ diff --git a/3rdpart/ZLToolKit/src/Poller/Timer.cpp b/3rdpart/ZLToolKit/src/Poller/Timer.cpp new file mode 100644 index 0000000..2512fbb --- /dev/null +++ b/3rdpart/ZLToolKit/src/Poller/Timer.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include "Timer.h" + +namespace toolkit { + +Timer::Timer(float second, const std::function &cb, const EventPoller::Ptr &poller) { + _poller = poller; + if (!_poller) { + _poller = EventPollerPool::Instance().getPoller(); + } + _tag = _poller->doDelayTask((uint64_t) (second * 1000), [cb, second]() { + try { + if (cb()) { + //重复的任务 + return (uint64_t) (1000 * second); + } + //该任务不再重复 + return (uint64_t) 0; + } catch (std::exception &ex) { + ErrorL << "Exception occurred when do timer task: " << ex.what(); + return (uint64_t) (1000 * second); + } + }); +} + +Timer::~Timer() { + auto tag = _tag.lock(); + if (tag) { + tag->cancel(); + } +} + +} // namespace toolkit diff --git a/3rdpart/ZLToolKit/src/Poller/Timer.h b/3rdpart/ZLToolKit/src/Poller/Timer.h new file mode 100644 index 0000000..99ed419 --- /dev/null +++ b/3rdpart/ZLToolKit/src/Poller/Timer.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef Timer_h +#define Timer_h + +#include +#include "EventPoller.h" + +namespace toolkit { + +class Timer { +public: + using Ptr = std::shared_ptr; + + /** + * 构造定时器 + * @param second 定时器重复秒数 + * @param cb 定时器任务,返回true表示重复下次任务,否则不重复,如果任务中抛异常,则默认重复下次任务 + * @param poller EventPoller对象,可以为nullptr + */ + Timer(float second, const std::function &cb, const EventPoller::Ptr &poller); + ~Timer(); + +private: + std::weak_ptr _tag; + //定时器保持EventPoller的强引用 + EventPoller::Ptr _poller; +}; + +} // namespace toolkit +#endif /* Timer_h */ diff --git a/3rdpart/ZLToolKit/src/README.md b/3rdpart/ZLToolKit/src/README.md new file mode 100644 index 0000000..f961818 --- /dev/null +++ b/3rdpart/ZLToolKit/src/README.md @@ -0,0 +1,64 @@ +源代码放置在`src`文件夹下,里面有若干模块: + +``` +src +| +|-- NetWork # 网络模块 +| |-- Socket.cpp # 套接字抽象封装,包含了TCP服务器/客户端,UDP套接字 +| |-- Socket.h +| |-- sockutil.cpp # 系统网络相关API的统一封装 +| |-- sockutil.h +| |-- TcpClient.cpp # TCP客户端封装,派生该类可以很容易实现客户端程序 +| |-- TcpClient.h +| |-- TcpServer.h # TCP服务器模板类,可以很容易就实现一个高性能私有协议服务器 +| |-- Session.h # TCP/UDP服务私有协议实现会话基类,用于处理TCP/UDP长连接数据及响应 +| +|-- Poller # 主线程事件轮询模块 +| |-- EventPoller.cpp # 主线程,所有网络事件由此线程轮询并触发 +| |-- EventPoller.h +| |-- Pipe.cpp # 管道的对象封装 +| |-- Pipe.h +| |-- PipeWrap.cpp # 管道的包装,windows下由socket模拟 +| |-- SelectWrap.cpp # select 模型的简单包装 +| |-- SelectWrap.h +| |-- Timer.cpp # 在主线程触发的定时器 +| |-- Timer.h +| +|-- Thread # 线程模块 +| |-- AsyncTaskThread.cpp # 后台异步任务线程,可以提交一个可定时重复的任务后台执行 +| |-- AsyncTaskThread.h +| |-- rwmutex.h # 读写锁,实验性质的 +| |-- semaphore.h # 信号量,由条件变量实现 +| |-- spin_mutex.h # 自旋锁,在低延时临界区适用,单核/低性能设备慎用 +| |-- TaskQueue.h # functional的任务列队 +| |-- threadgroup.h # 线程组,移植自boost +| |-- ThreadPool.h # 线程池,可以输入functional任务至后台线程执行 +| |-- WorkThreadPool.cpp # 获取一个可用的线程池(可以加入线程负载均衡分配算法) +| |-- WorkThreadPool.h +| +|-- Util # 工具模块 + |-- File.cpp # 文件/目录操作模块 + |-- File.h + |-- function_traits.h # 函数、lambda转functional + |-- logger.h # 日志模块 + |-- MD5.cpp # md5加密模块 + |-- MD5.h + |-- mini.h # ini配置文件读写模块,支持unix/windows格式的回车符 + |-- NoticeCenter.h # 消息广播器,可以广播传递任意个数任意类型参数 + |-- onceToken.h # 使用RAII模式实现,可以在对象构造和析构时执行一段代码 + |-- ResourcePool.h # 基于智能指针实现的一个循环池,不需要手动回收对象 + |-- RingBuffer.h # 环形缓冲,可以自适应大小,适用于GOP缓存等 + |-- SqlConnection.cpp # mysql客户端 + |-- SqlConnection.h + |-- SqlPool.h # mysql连接池,以及简单易用的sql语句生成工具 + |-- SSLBox.cpp # openssl的黑盒封装,屏蔽了ssl握手细节,支持多线程 + |-- SSLBox.h + |-- TimeTicker.h # 计时器,可以用于统计函数执行时间 + |-- util.cpp # 其他一些工具代码,适配了多种系统 + |-- util.h + |-- uv_errno.cpp # 提取自libuv的错误代码系统,主要是为了兼容windows + |-- uv_errno.h + +``` + + diff --git a/3rdpart/ZLToolKit/src/Thread/TaskExecutor.cpp b/3rdpart/ZLToolKit/src/Thread/TaskExecutor.cpp new file mode 100644 index 0000000..c4b1f75 --- /dev/null +++ b/3rdpart/ZLToolKit/src/Thread/TaskExecutor.cpp @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include "TaskExecutor.h" +#include "Poller/EventPoller.h" +#include "Util/onceToken.h" +#include "Util/TimeTicker.h" + +using namespace std; + +namespace toolkit { + +ThreadLoadCounter::ThreadLoadCounter(uint64_t max_size, uint64_t max_usec) { + _last_sleep_time = _last_wake_time = getCurrentMicrosecond(); + _max_size = max_size; + _max_usec = max_usec; +} + +void ThreadLoadCounter::startSleep() { + lock_guard lck(_mtx); + _sleeping = true; + auto current_time = getCurrentMicrosecond(); + auto run_time = current_time - _last_wake_time; + _last_sleep_time = current_time; + _time_list.emplace_back(run_time, false); + if (_time_list.size() > _max_size) { + _time_list.pop_front(); + } +} + +void ThreadLoadCounter::sleepWakeUp() { + lock_guard lck(_mtx); + _sleeping = false; + auto current_time = getCurrentMicrosecond(); + auto sleep_time = current_time - _last_sleep_time; + _last_wake_time = current_time; + _time_list.emplace_back(sleep_time, true); + if (_time_list.size() > _max_size) { + _time_list.pop_front(); + } +} + +int ThreadLoadCounter::load() { + lock_guard lck(_mtx); + uint64_t totalSleepTime = 0; + uint64_t totalRunTime = 0; + _time_list.for_each([&](const TimeRecord &rcd) { + if (rcd._sleep) { + totalSleepTime += rcd._time; + } else { + totalRunTime += rcd._time; + } + }); + + if (_sleeping) { + totalSleepTime += (getCurrentMicrosecond() - _last_sleep_time); + } else { + totalRunTime += (getCurrentMicrosecond() - _last_wake_time); + } + + uint64_t totalTime = totalRunTime + totalSleepTime; + while ((_time_list.size() != 0) && (totalTime > _max_usec || _time_list.size() > _max_size)) { + TimeRecord &rcd = _time_list.front(); + if (rcd._sleep) { + totalSleepTime -= rcd._time; + } else { + totalRunTime -= rcd._time; + } + totalTime -= rcd._time; + _time_list.pop_front(); + } + if (totalTime == 0) { + return 0; + } + return (int) (totalRunTime * 100 / totalTime); +} + +//////////////////////////////////////////////////////////////////////////// + +Task::Ptr TaskExecutorInterface::async_first(TaskIn task, bool may_sync) { + return async(std::move(task), may_sync); +} + +void TaskExecutorInterface::sync(const TaskIn &task) { + semaphore sem; + auto ret = async([&]() { + onceToken token(nullptr, [&]() { + //通过RAII原理防止抛异常导致不执行这句代码 + sem.post(); + }); + task(); + }); + if (ret && *ret) { + sem.wait(); + } +} + +void TaskExecutorInterface::sync_first(const TaskIn &task) { + semaphore sem; + auto ret = async_first([&]() { + onceToken token(nullptr, [&]() { + //通过RAII原理防止抛异常导致不执行这句代码 + sem.post(); + }); + task(); + }); + if (ret && *ret) { + sem.wait(); + } +} + +////////////////////////////////////////////////////////////////// + +TaskExecutor::TaskExecutor(uint64_t max_size, uint64_t max_usec) : ThreadLoadCounter(max_size, max_usec) {} + +////////////////////////////////////////////////////////////////// + +TaskExecutor::Ptr TaskExecutorGetterImp::getExecutor() { + auto thread_pos = _thread_pos; + if (thread_pos >= _threads.size()) { + thread_pos = 0; + } + + TaskExecutor::Ptr executor_min_load = _threads[thread_pos]; + auto min_load = executor_min_load->load(); + + for (size_t i = 0; i < _threads.size(); ++i, ++thread_pos) { + if (thread_pos >= _threads.size()) { + thread_pos = 0; + } + + auto th = _threads[thread_pos]; + auto load = th->load(); + + if (load < min_load) { + min_load = load; + executor_min_load = th; + } + if (min_load == 0) { + break; + } + } + _thread_pos = thread_pos; + return executor_min_load; +} + +vector TaskExecutorGetterImp::getExecutorLoad() { + vector vec(_threads.size()); + int i = 0; + for (auto &executor : _threads) { + vec[i++] = executor->load(); + } + return vec; +} + +void TaskExecutorGetterImp::getExecutorDelay(const function &)> &callback) { + std::shared_ptr > delay_vec = std::make_shared>(_threads.size()); + shared_ptr finished(nullptr, [callback, delay_vec](void *) { + //此析构回调触发时,说明已执行完毕所有async任务 + callback((*delay_vec)); + }); + int index = 0; + for (auto &th : _threads) { + std::shared_ptr delay_ticker = std::make_shared(); + th->async([finished, delay_vec, index, delay_ticker]() { + (*delay_vec)[index] = (int) delay_ticker->elapsedTime(); + }, false); + ++index; + } +} + +void TaskExecutorGetterImp::for_each(const function &cb) { + for (auto &th : _threads) { + cb(th); + } +} + +size_t TaskExecutorGetterImp::getExecutorSize() const { + return _threads.size(); +} + +size_t TaskExecutorGetterImp::addPoller(const string &name, size_t size, int priority, bool register_thread, bool enable_cpu_affinity) { + auto cpus = thread::hardware_concurrency(); + size = size > 0 ? size : cpus; + for (size_t i = 0; i < size; ++i) { + auto full_name = name + " " + to_string(i); + EventPoller::Ptr poller(new EventPoller(full_name, (ThreadPool::Priority) priority)); + poller->runLoop(false, register_thread); + poller->async([i, cpus, full_name, enable_cpu_affinity]() { + setThreadName(full_name.data()); + if (enable_cpu_affinity) { + setThreadAffinity(i % cpus); + } + }); + _threads.emplace_back(std::move(poller)); + } + return size; +} + +}//toolkit diff --git a/3rdpart/ZLToolKit/src/Thread/TaskExecutor.h b/3rdpart/ZLToolKit/src/Thread/TaskExecutor.h new file mode 100644 index 0000000..efbbfc8 --- /dev/null +++ b/3rdpart/ZLToolKit/src/Thread/TaskExecutor.h @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef ZLTOOLKIT_TASKEXECUTOR_H +#define ZLTOOLKIT_TASKEXECUTOR_H + +#include +#include +#include +#include "Util/List.h" +#include "Util/util.h" + +namespace toolkit { + +/** +* cpu负载计算器 +*/ +class ThreadLoadCounter { +public: + /** + * 构造函数 + * @param max_size 统计样本数量 + * @param max_usec 统计时间窗口,亦即最近{max_usec}的cpu负载率 + */ + ThreadLoadCounter(uint64_t max_size, uint64_t max_usec); + ~ThreadLoadCounter() = default; + + /** + * 线程进入休眠 + */ + void startSleep(); + + /** + * 休眠唤醒,结束休眠 + */ + void sleepWakeUp(); + + /** + * 返回当前线程cpu使用率,范围为 0 ~ 100 + * @return 当前线程cpu使用率 + */ + int load(); + +private: + struct TimeRecord { + TimeRecord(uint64_t tm, bool slp) { + _time = tm; + _sleep = slp; + } + + bool _sleep; + uint64_t _time; + }; + +private: + bool _sleeping = true; + uint64_t _last_sleep_time; + uint64_t _last_wake_time; + uint64_t _max_size; + uint64_t _max_usec; + std::mutex _mtx; + List _time_list; +}; + +class TaskCancelable : public noncopyable { +public: + TaskCancelable() = default; + virtual ~TaskCancelable() = default; + virtual void cancel() = 0; +}; + +template +class TaskCancelableImp; + +template +class TaskCancelableImp : public TaskCancelable { +public: + using Ptr = std::shared_ptr; + using func_type = std::function; + + ~TaskCancelableImp() = default; + + template + TaskCancelableImp(FUNC &&task) { + _strongTask = std::make_shared(std::forward(task)); + _weakTask = _strongTask; + } + + void cancel() override { + _strongTask = nullptr; + } + + operator bool() { + return _strongTask && *_strongTask; + } + + void operator=(std::nullptr_t) { + _strongTask = nullptr; + } + + R operator()(ArgTypes ...args) const { + auto strongTask = _weakTask.lock(); + if (strongTask && *strongTask) { + return (*strongTask)(std::forward(args)...); + } + return defaultValue(); + } + + template + static typename std::enable_if::value, void>::type + defaultValue() {} + + template + static typename std::enable_if::value, T>::type + defaultValue() { + return nullptr; + } + + template + static typename std::enable_if::value, T>::type + defaultValue() { + return 0; + } + +protected: + std::weak_ptr _weakTask; + std::shared_ptr _strongTask; +}; + +using TaskIn = std::function; +using Task = TaskCancelableImp; + +class TaskExecutorInterface { +public: + TaskExecutorInterface() = default; + virtual ~TaskExecutorInterface() = default; + + /** + * 异步执行任务 + * @param task 任务 + * @param may_sync 是否允许同步执行该任务 + * @return 任务是否添加成功 + */ + virtual Task::Ptr async(TaskIn task, bool may_sync = true) = 0; + + /** + * 最高优先级方式异步执行任务 + * @param task 任务 + * @param may_sync 是否允许同步执行该任务 + * @return 任务是否添加成功 + */ + virtual Task::Ptr async_first(TaskIn task, bool may_sync = true); + + /** + * 同步执行任务 + * @param task + * @return + */ + void sync(const TaskIn &task); + + /** + * 最高优先级方式同步执行任务 + * @param task + * @return + */ + void sync_first(const TaskIn &task); +}; + +/** +* 任务执行器 +*/ +class TaskExecutor : public ThreadLoadCounter, public TaskExecutorInterface { +public: + using Ptr = std::shared_ptr; + + /** + * 构造函数 + * @param max_size cpu负载统计样本数 + * @param max_usec cpu负载统计时间窗口大小 + */ + TaskExecutor(uint64_t max_size = 32, uint64_t max_usec = 2 * 1000 * 1000); + ~TaskExecutor() = default; +}; + +class TaskExecutorGetter { +public: + using Ptr = std::shared_ptr; + + virtual ~TaskExecutorGetter() = default; + + /** + * 获取任务执行器 + * @return 任务执行器 + */ + virtual TaskExecutor::Ptr getExecutor() = 0; + + /** + * 获取执行器个数 + */ + virtual size_t getExecutorSize() const = 0; +}; + +class TaskExecutorGetterImp : public TaskExecutorGetter { +public: + TaskExecutorGetterImp() = default; + ~TaskExecutorGetterImp() = default; + + /** + * 根据线程负载情况,获取最空闲的任务执行器 + * @return 任务执行器 + */ + TaskExecutor::Ptr getExecutor() override; + + /** + * 获取所有线程的负载率 + * @return 所有线程的负载率 + */ + std::vector getExecutorLoad(); + + /** + * 获取所有线程任务执行延时,单位毫秒 + * 通过此函数也可以大概知道线程负载情况 + * @return + */ + void getExecutorDelay(const std::function &)> &callback); + + /** + * 遍历所有线程 + */ + void for_each(const std::function &cb); + + /** + * 获取线程数 + */ + size_t getExecutorSize() const override; + +protected: + size_t addPoller(const std::string &name, size_t size, int priority, bool register_thread, bool enable_cpu_affinity = true); + +protected: + size_t _thread_pos = 0; + std::vector _threads; +}; + +}//toolkit +#endif //ZLTOOLKIT_TASKEXECUTOR_H diff --git a/3rdpart/ZLToolKit/src/Thread/TaskQueue.h b/3rdpart/ZLToolKit/src/Thread/TaskQueue.h new file mode 100644 index 0000000..584e05a --- /dev/null +++ b/3rdpart/ZLToolKit/src/Thread/TaskQueue.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef TASKQUEUE_H_ +#define TASKQUEUE_H_ + +#include +#include "Util/List.h" +#include "semaphore.h" + +namespace toolkit { + +//实现了一个基于函数对象的任务列队,该列队是线程安全的,任务列队任务数由信号量控制 +template +class TaskQueue { +public: + //打入任务至列队 + template + void push_task(C &&task_func) { + { + std::lock_guard lock(_mutex); + _queue.emplace_back(std::forward(task_func)); + } + _sem.post(); + } + + template + void push_task_first(C &&task_func) { + { + std::lock_guard lock(_mutex); + _queue.emplace_front(std::forward(task_func)); + } + _sem.post(); + } + + //清空任务列队 + void push_exit(size_t n) { + _sem.post(n); + } + + //从列队获取一个任务,由执行线程执行 + bool get_task(T &tsk) { + _sem.wait(); + std::lock_guard lock(_mutex); + if (_queue.empty()) { + return false; + } + tsk = std::move(_queue.front()); + _queue.pop_front(); + return true; + } + + size_t size() const { + std::lock_guard lock(_mutex); + return _queue.size(); + } + +private: + List _queue; + mutable std::mutex _mutex; + semaphore _sem; +}; + +} /* namespace toolkit */ +#endif /* TASKQUEUE_H_ */ diff --git a/3rdpart/ZLToolKit/src/Thread/ThreadPool.h b/3rdpart/ZLToolKit/src/Thread/ThreadPool.h new file mode 100644 index 0000000..154b38b --- /dev/null +++ b/3rdpart/ZLToolKit/src/Thread/ThreadPool.h @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef THREADPOOL_H_ +#define THREADPOOL_H_ + +#include "threadgroup.h" +#include "TaskQueue.h" +#include "TaskExecutor.h" +#include "Util/util.h" +#include "Util/logger.h" + +namespace toolkit { + +class ThreadPool : public TaskExecutor { +public: + enum Priority { + PRIORITY_LOWEST = 0, + PRIORITY_LOW, + PRIORITY_NORMAL, + PRIORITY_HIGH, + PRIORITY_HIGHEST + }; + + ThreadPool(int num = 1, Priority priority = PRIORITY_HIGHEST, bool auto_run = true) { + _thread_num = num; + _priority = priority; + if (auto_run) { + start(); + } + _logger = Logger::Instance().shared_from_this(); + } + + ~ThreadPool() { + shutdown(); + wait(); + } + + //把任务打入线程池并异步执行 + Task::Ptr async(TaskIn task, bool may_sync = true) override { + if (may_sync && _thread_group.is_this_thread_in()) { + task(); + return nullptr; + } + auto ret = std::make_shared(std::move(task)); + _queue.push_task(ret); + return ret; + } + + Task::Ptr async_first(TaskIn task, bool may_sync = true) override { + if (may_sync && _thread_group.is_this_thread_in()) { + task(); + return nullptr; + } + + auto ret = std::make_shared(std::move(task)); + _queue.push_task_first(ret); + return ret; + } + + size_t size() { + return _queue.size(); + } + + static bool setPriority(Priority priority = PRIORITY_NORMAL, std::thread::native_handle_type threadId = 0) { + // set priority +#if defined(_WIN32) + static int Priorities[] = { THREAD_PRIORITY_LOWEST, THREAD_PRIORITY_BELOW_NORMAL, THREAD_PRIORITY_NORMAL, THREAD_PRIORITY_ABOVE_NORMAL, THREAD_PRIORITY_HIGHEST }; + if (priority != PRIORITY_NORMAL && SetThreadPriority(GetCurrentThread(), Priorities[priority]) == 0) { + return false; + } + return true; +#else + static int Min = sched_get_priority_min(SCHED_OTHER); + if (Min == -1) { + return false; + } + static int Max = sched_get_priority_max(SCHED_OTHER); + if (Max == -1) { + return false; + } + static int Priorities[] = {Min, Min + (Max - Min) / 4, Min + (Max - Min) / 2, Min + (Max - Min) * 3 / 4, Max}; + + if (threadId == 0) { + threadId = pthread_self(); + } + struct sched_param params; + params.sched_priority = Priorities[priority]; + return pthread_setschedparam(threadId, SCHED_OTHER, ¶ms) == 0; +#endif + } + + void start() { + if (_thread_num <= 0) { + return; + } + size_t total = _thread_num - _thread_group.size(); + for (size_t i = 0; i < total; ++i) { + _thread_group.create_thread(std::bind(&ThreadPool::run, this)); + } + } + +private: + void run() { + ThreadPool::setPriority(_priority); + Task::Ptr task; + while (true) { + startSleep(); + if (!_queue.get_task(task)) { + //空任务,退出线程 + break; + } + sleepWakeUp(); + try { + (*task)(); + task = nullptr; + } catch (std::exception &ex) { + ErrorL << "ThreadPool catch a exception: " << ex.what(); + } + } + } + + void wait() { + _thread_group.join_all(); + } + + void shutdown() { + _queue.push_exit(_thread_num); + } + +private: + size_t _thread_num; + TaskQueue _queue; + thread_group _thread_group; + Priority _priority; + Logger::Ptr _logger; +}; + +} /* namespace toolkit */ +#endif /* THREADPOOL_H_ */ diff --git a/3rdpart/ZLToolKit/src/Thread/WorkThreadPool.cpp b/3rdpart/ZLToolKit/src/Thread/WorkThreadPool.cpp new file mode 100644 index 0000000..9677996 --- /dev/null +++ b/3rdpart/ZLToolKit/src/Thread/WorkThreadPool.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include "WorkThreadPool.h" + +namespace toolkit { + +static size_t s_pool_size = 0; +static bool s_enable_cpu_affinity = true; + +INSTANCE_IMP(WorkThreadPool) + +EventPoller::Ptr WorkThreadPool::getFirstPoller() { + return std::dynamic_pointer_cast(_threads.front()); +} + +EventPoller::Ptr WorkThreadPool::getPoller() { + return std::dynamic_pointer_cast(getExecutor()); +} + +WorkThreadPool::WorkThreadPool() { + //最低优先级 + addPoller("work poller", s_pool_size, ThreadPool::PRIORITY_LOWEST, false, s_enable_cpu_affinity); +} + +void WorkThreadPool::setPoolSize(size_t size) { + s_pool_size = size; +} + +void WorkThreadPool::enableCpuAffinity(bool enable) { + s_enable_cpu_affinity = enable; +} + +} /* namespace toolkit */ + diff --git a/3rdpart/ZLToolKit/src/Thread/WorkThreadPool.h b/3rdpart/ZLToolKit/src/Thread/WorkThreadPool.h new file mode 100644 index 0000000..ae7f0a6 --- /dev/null +++ b/3rdpart/ZLToolKit/src/Thread/WorkThreadPool.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef UTIL_WORKTHREADPOOL_H_ +#define UTIL_WORKTHREADPOOL_H_ + +#include +#include "Poller/EventPoller.h" + +namespace toolkit { + +class WorkThreadPool : public std::enable_shared_from_this, public TaskExecutorGetterImp { +public: + using Ptr = std::shared_ptr; + + ~WorkThreadPool() override = default; + + /** + * 获取单例 + */ + static WorkThreadPool &Instance(); + + /** + * 设置EventPoller个数,在WorkThreadPool单例创建前有效 + * 在不调用此方法的情况下,默认创建thread::hardware_concurrency()个EventPoller实例 + * @param size EventPoller个数,如果为0则为thread::hardware_concurrency() + */ + static void setPoolSize(size_t size = 0); + + /** + * 内部创建线程是否设置cpu亲和性,默认设置cpu亲和性 + */ + static void enableCpuAffinity(bool enable); + + /** + * 获取第一个实例 + * @return + */ + EventPoller::Ptr getFirstPoller(); + + /** + * 根据负载情况获取轻负载的实例 + * 如果优先返回当前线程,那么会返回当前线程 + * 返回当前线程的目的是为了提高线程安全性 + * @return + */ + EventPoller::Ptr getPoller(); + +protected: + WorkThreadPool(); +}; + +} /* namespace toolkit */ +#endif /* UTIL_WORKTHREADPOOL_H_ */ diff --git a/3rdpart/ZLToolKit/src/Thread/semaphore.h b/3rdpart/ZLToolKit/src/Thread/semaphore.h new file mode 100644 index 0000000..46f3155 --- /dev/null +++ b/3rdpart/ZLToolKit/src/Thread/semaphore.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef SEMAPHORE_H_ +#define SEMAPHORE_H_ + +/* + * 目前发现信号量在32位的系统上有问题, + * 休眠的线程无法被正常唤醒,先禁用之 +#if defined(__linux__) +#include +#define HAVE_SEM +#endif //HAVE_SEM +*/ + +#include +#include + +namespace toolkit { + +class semaphore { +public: + explicit semaphore(size_t initial = 0) { +#if defined(HAVE_SEM) + sem_init(&_sem, 0, initial); +#else + _count = 0; +#endif + } + + ~semaphore() { +#if defined(HAVE_SEM) + sem_destroy(&_sem); +#endif + } + + void post(size_t n = 1) { +#if defined(HAVE_SEM) + while (n--) { + sem_post(&_sem); + } +#else + std::unique_lock lock(_mutex); + _count += n; + if (n == 1) { + _condition.notify_one(); + } else { + _condition.notify_all(); + } +#endif + } + + void wait() { +#if defined(HAVE_SEM) + sem_wait(&_sem); +#else + std::unique_lock lock(_mutex); + while (_count == 0) { + _condition.wait(lock); + } + --_count; +#endif + } + +private: +#if defined(HAVE_SEM) + sem_t _sem; +#else + size_t _count; + std::recursive_mutex _mutex; + std::condition_variable_any _condition; +#endif +}; + +} /* namespace toolkit */ +#endif /* SEMAPHORE_H_ */ diff --git a/3rdpart/ZLToolKit/src/Thread/threadgroup.h b/3rdpart/ZLToolKit/src/Thread/threadgroup.h new file mode 100644 index 0000000..67b1644 --- /dev/null +++ b/3rdpart/ZLToolKit/src/Thread/threadgroup.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef THREADGROUP_H_ +#define THREADGROUP_H_ + +#include +#include +#include + +namespace toolkit { + +class thread_group { +private: + thread_group(thread_group const &); + thread_group &operator=(thread_group const &); + +public: + thread_group() {} + + ~thread_group() { + _threads.clear(); + } + + bool is_this_thread_in() { + auto thread_id = std::this_thread::get_id(); + if (_thread_id == thread_id) { + return true; + } + return _threads.find(thread_id) != _threads.end(); + } + + bool is_thread_in(std::thread *thrd) { + if (!thrd) { + return false; + } + auto it = _threads.find(thrd->get_id()); + return it != _threads.end(); + } + + template + std::thread *create_thread(F &&threadfunc) { + auto thread_new = std::make_shared(threadfunc); + _thread_id = thread_new->get_id(); + _threads[_thread_id] = thread_new; + return thread_new.get(); + } + + void remove_thread(std::thread *thrd) { + auto it = _threads.find(thrd->get_id()); + if (it != _threads.end()) { + _threads.erase(it); + } + } + + void join_all() { + if (is_this_thread_in()) { + throw std::runtime_error("Trying joining itself in thread_group"); + } + for (auto &it : _threads) { + if (it.second->joinable()) { + it.second->join(); //等待线程主动退出 + } + } + _threads.clear(); + } + + size_t size() { + return _threads.size(); + } + +private: + std::thread::id _thread_id; + std::unordered_map> _threads; +}; + +} /* namespace toolkit */ +#endif /* THREADGROUP_H_ */ diff --git a/3rdpart/ZLToolKit/src/Util/CMD.cpp b/3rdpart/ZLToolKit/src/Util/CMD.cpp new file mode 100644 index 0000000..44e8337 --- /dev/null +++ b/3rdpart/ZLToolKit/src/Util/CMD.cpp @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#include "CMD.h" +#include "onceToken.h" + +#if defined(_WIN32) +#include "win32/getopt.h" +#else +#include +#endif // defined(_WIN32) + +using namespace std; + +namespace toolkit { + +//默认注册exit/quit/help/clear命令 +static onceToken s_token([]() { + REGIST_CMD(exit) + REGIST_CMD(quit) + REGIST_CMD(help) + REGIST_CMD(clear) +}); + +CMDRegister &CMDRegister::Instance() { + static CMDRegister instance; + return instance; +} + +void OptionParser::operator()(mINI &all_args, int argc, char *argv[], const std::shared_ptr &stream) { + vector vec_long_opt; + string str_short_opt; + do { + struct option tmp; + for (auto &pr : _map_options) { + auto &opt = pr.second; + //long opt + tmp.name = (char *) opt._long_opt.data(); + tmp.has_arg = opt._type; + tmp.flag = nullptr; + tmp.val = pr.first; + vec_long_opt.emplace_back(tmp); + //short opt + if (!opt._short_opt) { + continue; + } + str_short_opt.push_back(opt._short_opt); + switch (opt._type) { + case Option::ArgRequired: str_short_opt.append(":"); break; + case Option::ArgOptional: str_short_opt.append("::"); break; + default: break; + } + } + tmp.flag = 0; + tmp.name = 0; + tmp.has_arg = 0; + tmp.val = 0; + vec_long_opt.emplace_back(tmp); + } while (0); + + static mutex s_mtx_opt; + lock_guard lck(s_mtx_opt); + + int index; + optind = 0; + opterr = 0; + while ((index = getopt_long(argc, argv, &str_short_opt[0], &vec_long_opt[0], nullptr)) != -1) { + stringstream ss; + ss << " 未识别的选项,输入\"-h\"获取帮助."; + if (index < 0xFF) { + //短参数 + auto it = _map_char_index.find(index); + if (it == _map_char_index.end()) { + throw std::invalid_argument(ss.str()); + } + index = it->second; + } + + auto it = _map_options.find(index); + if (it == _map_options.end()) { + throw std::invalid_argument(ss.str()); + } + auto &opt = it->second; + auto pr = all_args.emplace(opt._long_opt, optarg ? optarg : ""); + if (!opt(stream, pr.first->second)) { + return; + } + optarg = nullptr; + } + for (auto &pr : _map_options) { + if (pr.second._default_value && all_args.find(pr.second._long_opt) == all_args.end()) { + //有默认值,赋值默认值 + all_args.emplace(pr.second._long_opt, *pr.second._default_value); + } + } + for (auto &pr : _map_options) { + if (pr.second._must_exist) { + if (all_args.find(pr.second._long_opt) == all_args.end()) { + stringstream ss; + ss << " 参数\"" << pr.second._long_opt << "\"必须提供,输入\"-h\"选项获取帮助"; + throw std::invalid_argument(ss.str()); + } + } + } + if (all_args.empty() && _map_options.size() > 1 && !_enable_empty_args) { + _helper(stream, ""); + return; + } + if (_on_completed) { + _on_completed(stream, all_args); + } +} + +}//namespace toolkit \ No newline at end of file diff --git a/3rdpart/ZLToolKit/src/Util/CMD.h b/3rdpart/ZLToolKit/src/Util/CMD.h new file mode 100644 index 0000000..c4e8364 --- /dev/null +++ b/3rdpart/ZLToolKit/src/Util/CMD.h @@ -0,0 +1,384 @@ +/* + * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved. + * + * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit). + * + * Use of this source code is governed by MIT license that can be found in the + * LICENSE file in the root of the source tree. All contributing project authors + * may be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef SRC_UTIL_CMD_H_ +#define SRC_UTIL_CMD_H_ + +#include +#include +#include +#include +#include +#include +#include +#include "mini.h" + +namespace toolkit{ + +class Option { +public: + using OptionHandler = std::function &stream, const std::string &arg)>; + + enum ArgType { + ArgNone = 0,//no_argument, + ArgRequired = 1,//required_argument, + ArgOptional = 2,//optional_argument + }; + + Option() = default; + + Option(char short_opt, const char *long_opt, enum ArgType type, const char *default_value, bool must_exist, + const char *des, const OptionHandler &cb) { + _short_opt = short_opt; + _long_opt = long_opt; + _type = type; + if (type != ArgNone) { + if (default_value) { + _default_value = std::make_shared(default_value); + } + if (!_default_value && must_exist) { + _must_exist = true; + } + } + _des = des; + _cb = cb; + } + + bool operator()(const std::shared_ptr &stream, const std::string &arg) { + return _cb ? _cb(stream, arg) : true; + } + +private: + friend class OptionParser; + bool _must_exist = false; + char _short_opt; + enum ArgType _type; + std::string _des; + std::string _long_opt; + OptionHandler _cb; + std::shared_ptr _default_value; +}; + +class OptionParser { +public: + using OptionCompleted = std::function &, mINI &)>; + + OptionParser(const OptionCompleted &cb = nullptr, bool enable_empty_args = true) { + _on_completed = cb; + _enable_empty_args = enable_empty_args; + _helper = Option('h', "help", Option::ArgNone, nullptr, false, "打印此信息", + [this](const std::shared_ptr &stream,const std::string &arg)->bool { + static const char *argsType[] = {"无参", "有参", "选参"}; + static const char *mustExist[] = {"选填", "必填"}; + static std::string defaultPrefix = "默认:"; + static std::string defaultNull = "null"; + + std::stringstream printer; + size_t maxLen_longOpt = 0; + auto maxLen_default = defaultNull.size(); + + for (auto &pr : _map_options) { + auto &opt = pr.second; + if (opt._long_opt.size() > maxLen_longOpt) { + maxLen_longOpt = opt._long_opt.size(); + } + if (opt._default_value) { + if (opt._default_value->size() > maxLen_default) { + maxLen_default = opt._default_value->size(); + } + } + } + for (auto &pr : _map_options) { + auto &opt = pr.second; + //打印短参和长参名 + if (opt._short_opt) { + printer << " -" << opt._short_opt << " --" << opt._long_opt; + } else { + printer << " " << " " << " --" << opt._long_opt; + } + for (size_t i = 0; i < maxLen_longOpt - opt._long_opt.size(); ++i) { + printer << " "; + } + //打印是否有参 + printer << " " << argsType[opt._type]; + //打印默认参数 + std::string defaultValue = defaultNull; + if (opt._default_value) { + defaultValue = *opt._default_value; + } + printer << " " << defaultPrefix << defaultValue; + for (size_t i = 0; i < maxLen_default - defaultValue.size(); ++i) { + printer << " "; + } + //打印是否必填参数 + printer << " " << mustExist[opt._must_exist]; + //打印描述 + printer << " " << opt._des << std::endl; + } + throw std::invalid_argument(printer.str()); + }); + (*this) << _helper; + } + + OptionParser &operator<<(Option &&option) { + int index = 0xFF + (int) _map_options.size(); + if (option._short_opt) { + _map_char_index.emplace(option._short_opt, index); + } + _map_options.emplace(index, std::forward