@@ -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 | |||
... |
@@ -0,0 +1,2 @@ | |||
*.h linguist-language=cpp | |||
*.c linguist-language=cpp |
@@ -0,0 +1,85 @@ | |||
--- | |||
name: bug 反馈 | |||
about: 反馈 ZLMediaKit 代码本身的 bug | |||
title: "[BUG]: BUG 现象描述" | |||
labels: bug | |||
assignees: '' | |||
--- | |||
<!-- | |||
请仔细阅读相关注释提示, 请务必根据提示填写相关信息. | |||
1. 信息不完整会影响问题的解决速度. | |||
1. 乱七八糟的渲染格式也会影响开发者心情, 同样会影响问题的解决. 提交前请务必点击 Preview/预览下反馈的显示效果. | |||
1. 不要删除模版内容, 模版的注释部分的内容不会显示,不需要删除,直接在各部分注释外面补充相关信息即可. | |||
--> | |||
<!-- | |||
markdown 语法参考: | |||
* https://docs.github.com/cn/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax | |||
* https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax | |||
--> | |||
### 现象描述 | |||
<!-- | |||
在使用什么功能产生的问题? 其异常表现是什么? | |||
如: 在测试 WebRTC 功能时, 使用 Chrome 浏览器访问 ZLMediait 自带网页播放 FFmpeg 以 RTSP 协议推送的图像有卡顿/花屏. | |||
--> | |||
### 如何复现? | |||
<!-- | |||
明确的复现步骤对快速解决问题极有帮助. | |||
格式参考: | |||
1. 首先 ... | |||
1. 然后 ... | |||
1. 期望 ..., 结果 ... | |||
--> | |||
### 相关日志或截图 | |||
<!-- | |||
由于日志通长较长, 建议将日志信息填写到下面的 "日志内容..." | |||
如果是程序异常崩溃/终止, 相关调用栈信息也极为有用, 可复制下面的格式, 添加相关调用栈信息. | |||
替换下面的 "日志内容..." 为实际日志内容. | |||
--> | |||
<details> | |||
<summary>展开查看详细日志</summary> | |||
<pre> | |||
日志内容... | |||
</pre> | |||
</details> | |||
### 配置 | |||
<!-- | |||
部分常见问题是由于配置错误导致的, 建议仔细阅读配置文件中的注释信息 | |||
替换下面的 "配置内容..." 为实际配置内容. | |||
--> | |||
<details> | |||
<summary>展开查看详细配置</summary> | |||
<pre> | |||
配置内容... | |||
</pre> | |||
</details> | |||
### 各种环境信息 | |||
<!-- | |||
请填写相关环境信息, 详细的环境信息有助于快速复现定位问题. | |||
* 代码提交记录, 可使用命令 `git rev-parse HEAD` 进行查看. | |||
* 操作系统及版本, 如: Windows 10, CentOS 7, ... | |||
* 硬件信息, 如: Intel, AMD, ARM, 飞腾, 龙芯, ... | |||
--> | |||
* **代码提交记录/git commit hash**: | |||
* **操作系统及版本**: | |||
* **硬件信息**: | |||
* **其他需要补充的信息**: |
@@ -0,0 +1,57 @@ | |||
--- | |||
name: 编译问题反馈 | |||
about: 反馈 ZLMediaKit 编译相关的问题 | |||
title: "[编译问题]: " | |||
labels: 编译问题 | |||
assignees: '' | |||
--- | |||
<!-- | |||
请仔细阅读相关注释提示, 请务必根据提示填写相关信息. | |||
1. 信息不完整会影响问题的解决速度. | |||
1. 乱七八糟的渲染格式也会影响开发者心情, 同样会影响问题的解决. 提交前请务必点击 Preview/预览下反馈的显示效果. | |||
1. 不要删除模版内容, 模版的注释部分的内容不会显示,不需要删除,直接在各部分注释外面补充相关信息即可. | |||
--> | |||
<!-- | |||
markdown 语法参考: | |||
* https://docs.github.com/cn/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax | |||
* https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax | |||
--> | |||
### 相关日志及环境信息 | |||
<!-- | |||
由于编译日志通长较长, 建议将日志信息填写到下面 `````` block 内,或者上传日志文件 | |||
--> | |||
**清除编译缓存后,完整执行 cmake && make 命令的输出** | |||
<details> | |||
<summary>展开查看详细编译日志</summary> | |||
<pre> | |||
``` | |||
详细日志粘在这里! | |||
``` | |||
</pre> | |||
</details> | |||
编译目录下的 `CMakeCache.txt` 文件内容,请直接上传为附件。 | |||
### 各种环境信息 | |||
<!-- | |||
请填写相关环境信息, 详细的环境信息有助于快速复现定位问题. | |||
* 代码提交记录, 可使用命令 `git rev-parse HEAD` 进行查看. | |||
* 操作系统及版本, 如: Windows 10, CentOS 7, ... | |||
* 硬件信息, 如: Intel, AMD, ARM, 飞腾, 龙芯, ... | |||
--> | |||
* **代码提交记录/git commit hash**: | |||
* **操作系统及版本**: | |||
* **硬件信息**: | |||
* **其他需要补充的信息**: |
@@ -0,0 +1,14 @@ | |||
--- | |||
name: 新增功能请求 | |||
about: 请求新增某些新功能或新特性,或者对已有功能的改进 | |||
title: "[功能请求]" | |||
labels: 意见建议 | |||
assignees: '' | |||
--- | |||
**描述该功能的用处,可以提供相关资料描述该功能** | |||
**该功能是否用于改进项目缺陷,如果是,请描述现有缺陷** | |||
**描述你期望实现该功能的方式和最终效果** |
@@ -0,0 +1,19 @@ | |||
--- | |||
name: 技术咨询 | |||
about: 使用咨询、技术咨询等 | |||
title: "[技术咨询]" | |||
labels: 技术咨询 | |||
assignees: '' | |||
--- | |||
**咨询的功能模块** | |||
- 请描述您想咨询zlmediakit的哪部分功能 | |||
**咨询的具体内容和问题** | |||
- 此处展开您咨询内容的描述 | |||
**注意事项** | |||
- 技术咨询前请先认真阅读readme, [wiki](https://github.com/xia-chu/ZLMediaKit/wiki),如有必要,您也可以同时搜索已经答复的issue,如果没找到答案才在此提issue | |||
- 技术咨询不属于bug缺陷,建议先star本项目,否则可能会降低答复优先级 |
@@ -0,0 +1,8 @@ | |||
--- | |||
name: issue创建要求 | |||
about: 不符合模板要求不便定位问题,可能会被管理员直接关闭 | |||
title: "" | |||
labels: '' | |||
assignees: '' | |||
--- |
@@ -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 |
@@ -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 | |||
@@ -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 }} |
@@ -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 & | |||
@@ -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 & | |||
@@ -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} |
@@ -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' |
@@ -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/ |
@@ -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 |
@@ -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 |
@@ -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 | |||
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>/jsoncpp/include" | |||
PUBLIC | |||
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>/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 | |||
"$<BUILD_INTERFACE:${MediaServer_MOV_ROOT}/include>" | |||
PUBLIC | |||
"$<BUILD_INTERFACE:${MediaServer_MOV_ROOT}/include>") | |||
# 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 | |||
"$<BUILD_INTERFACE:${MediaServer_FLV_ROOT}/include>" | |||
PUBLIC | |||
"$<BUILD_INTERFACE:${MediaServer_FLV_ROOT}/include>") | |||
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 | |||
"$<BUILD_INTERFACE:${MediaServer_MPEG_ROOT}/include>" | |||
PUBLIC | |||
"$<BUILD_INTERFACE:${MediaServer_MPEG_ROOT}/include>") | |||
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 | |||
"$<BUILD_INTERFACE:${ToolKit_ROOT}/src>" | |||
PUBLIC | |||
"$<BUILD_INTERFACE:${ToolKit_ROOT}>/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() |
@@ -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 <NAME>" | |||
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 |
@@ -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 <NAME>" | |||
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 |
@@ -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} |
@@ -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' |
@@ -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/ |
@@ -0,0 +1,13 @@ | |||
language: cpp | |||
sudo: required | |||
dist: trusty | |||
compiler: | |||
- gcc | |||
os: | |||
- linux | |||
before_install: | |||
script: | |||
- ./build_for_linux.sh | |||
@@ -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) |
@@ -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() |
@@ -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. |
@@ -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 | |||
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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() |
@@ -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() |
@@ -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 <staniek@kde.org> | |||
# | |||
# 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 <mysql.h>\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) |
@@ -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) |
@@ -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 <cstdlib> | |||
#include "Buffer.h" | |||
#include "Util/onceToken.h" | |||
namespace toolkit { | |||
StatisticImp(Buffer) | |||
StatisticImp(BufferRaw) | |||
StatisticImp(BufferLikeString) | |||
BufferRaw::Ptr BufferRaw::create() { | |||
#if 0 | |||
static ResourcePool<BufferRaw> 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 |
@@ -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 <cassert> | |||
#include <memory> | |||
#include <string> | |||
#include <vector> | |||
#include <type_traits> | |||
#include <functional> | |||
#include "Util/util.h" | |||
#include "Util/ResourcePool.h" | |||
namespace toolkit { | |||
template <typename T> struct is_pointer : public std::false_type {}; | |||
template <typename T> struct is_pointer<std::shared_ptr<T> > : public std::true_type {}; | |||
template <typename T> struct is_pointer<std::shared_ptr<T const> > : public std::true_type {}; | |||
template <typename T> struct is_pointer<T*> : public std::true_type {}; | |||
template <typename T> struct is_pointer<const T*> : public std::true_type {}; | |||
//缓存基类 | |||
class Buffer : public noncopyable { | |||
public: | |||
using Ptr = std::shared_ptr<Buffer>; | |||
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<Buffer> _statistic; | |||
}; | |||
template <typename C> | |||
class BufferOffset : public Buffer { | |||
public: | |||
using Ptr = std::shared_ptr<BufferOffset>; | |||
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<char *>(getPointer<C>(_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<C>(_data)->size(); | |||
assert(offset + size <= max_size); | |||
if (!size) { | |||
size = max_size - offset; | |||
} | |||
_size = size; | |||
_offset = offset; | |||
} | |||
template<typename T> | |||
static typename std::enable_if<::toolkit::is_pointer<T>::value, const T &>::type | |||
getPointer(const T &data) { | |||
return data; | |||
} | |||
template<typename T> | |||
static typename std::enable_if<!::toolkit::is_pointer<T>::value, const T *>::type | |||
getPointer(const T &data) { | |||
return &data; | |||
} | |||
private: | |||
C _data; | |||
size_t _size; | |||
size_t _offset; | |||
}; | |||
using BufferString = BufferOffset<std::string>; | |||
//指针式缓存对象, | |||
class BufferRaw : public Buffer { | |||
public: | |||
using Ptr = std::shared_ptr<BufferRaw>; | |||
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>; | |||
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<BufferRaw> _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<BufferLikeString *>(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<BufferLikeString> _statistic; | |||
}; | |||
}//namespace toolkit | |||
#endif //ZLTOOLKIT_BUFFER_H |
@@ -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 <assert.h> | |||
#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 <unistd.h> | |||
#include <sys/syscall.h> | |||
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 <unistd.h> | |||
#include <sys/syscall.h> | |||
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<std::pair<Buffer::Ptr, bool> > 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<std::pair<Buffer::Ptr, bool> > _pkt_list; | |||
}; | |||
/////////////////////////////////////// BufferSendMsg /////////////////////////////////////// | |||
#if !defined(_WIN32) | |||
class BufferSendMsg : public BufferList, public BufferCallBack { | |||
public: | |||
BufferSendMsg(List<std::pair<Buffer::Ptr, bool> > 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<struct iovec> _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<std::pair<Buffer::Ptr, bool>> list, SendResult cb) | |||
: BufferCallBack(std::move(list), std::move(cb)) | |||
, _iovec(_pkt_list.size()) { | |||
auto it = _iovec.begin(); | |||
_pkt_list.for_each([&](std::pair<Buffer::Ptr, bool> &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<std::pair<Buffer::Ptr, bool> > 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<std::pair<Buffer::Ptr, bool>> 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<Buffer::Ptr, bool> &pr) { | |||
if (!pr.second) { | |||
return nullptr; | |||
} | |||
return static_cast<BufferSock *>(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<std::pair<Buffer::Ptr, bool> > 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<struct iovec> _iovec; | |||
std::vector<struct mmsghdr> _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<std::pair<Buffer::Ptr, bool>> 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<Buffer::Ptr, bool> &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<std::pair<Buffer::Ptr, bool> > list, SendResult cb, bool is_udp) { | |||
#if defined(_WIN32) | |||
//win32目前未做网络发送性能优化 | |||
return std::make_shared<BufferSendTo>(std::move(list), std::move(cb), is_udp); | |||
#elif defined(__linux__) || defined(__linux) | |||
if (is_udp) { | |||
return std::make_shared<BufferSendMMsg>(std::move(list), std::move(cb)); | |||
} | |||
return std::make_shared<BufferSendMsg>(std::move(list), std::move(cb)); | |||
#else | |||
if (is_udp) { | |||
return std::make_shared<BufferSendTo>(std::move(list), std::move(cb), is_udp); | |||
} | |||
return std::make_shared<BufferSendMsg>(std::move(list), std::move(cb)); | |||
#endif | |||
} | |||
} //toolkit |
@@ -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 <sys/uio.h> | |||
#include <limits.h> | |||
#endif | |||
#include <cassert> | |||
#include <memory> | |||
#include <string> | |||
#include <vector> | |||
#include <type_traits> | |||
#include <functional> | |||
#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>; | |||
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<BufferList>; | |||
using SendResult = std::function<void(const Buffer::Ptr &buffer, bool send_success)>; | |||
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<std::pair<Buffer::Ptr, bool> > list, SendResult cb, bool is_udp); | |||
private: | |||
//对象个数统计 | |||
ObjectStatistic<BufferList> _statistic; | |||
}; | |||
} | |||
#endif //ZLTOOLKIT_BUFFERSOCK_H |
@@ -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> &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<mutex> lck(_mtx_session); | |||
return _map_session.emplace(tag, session).second; | |||
} | |||
bool SessionMap::del(const string &tag) { | |||
lock_guard<mutex> lck(_mtx_session); | |||
return _map_session.erase(tag); | |||
} | |||
Session::Ptr SessionMap::get(const string &tag) { | |||
lock_guard<mutex> 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<void(const string &id, const Session::Ptr &session)> &cb) { | |||
lock_guard<mutex> 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 |
@@ -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 <unordered_map> | |||
#include "Util/mini.h" | |||
#include "Session.h" | |||
namespace toolkit { | |||
// 全局的 Session 记录对象, 方便后面管理 | |||
// 线程安全的 | |||
class SessionMap : public std::enable_shared_from_this<SessionMap> { | |||
public: | |||
friend class SessionHelper; | |||
using Ptr = std::shared_ptr<SessionMap>; | |||
//单例 | |||
static SessionMap &Instance(); | |||
~SessionMap() = default; | |||
//获取Session | |||
Session::Ptr get(const std::string &tag); | |||
void for_each_session(const std::function<void(const std::string &id, const Session::Ptr &session)> &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<std::string, std::weak_ptr<Session> > _map_session; | |||
}; | |||
class Server; | |||
class SessionHelper { | |||
public: | |||
using Ptr = std::shared_ptr<SessionHelper>; | |||
SessionHelper(const std::weak_ptr<Server> &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; | |||
}; | |||
// server 基类, 暂时仅用于剥离 SessionHelper 对 TcpServer 的依赖 | |||
// 后续将 TCP 与 UDP 服务通用部分加到这里. | |||
class Server : public std::enable_shared_from_this<Server>, public mINI { | |||
public: | |||
using Ptr = std::shared_ptr<Server>; | |||
explicit Server(EventPoller::Ptr poller = nullptr); | |||
virtual ~Server() = default; | |||
protected: | |||
EventPoller::Ptr _poller; | |||
}; | |||
} // namespace toolkit | |||
#endif // ZLTOOLKIT_SERVER_H |
@@ -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 <atomic> | |||
#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<TcpSession>); | |||
} else { | |||
_statistic_udp.reset(new ObjectStatistic<UdpSession>); | |||
} | |||
} | |||
static atomic<uint64_t> 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<Session> weakSelf = shared_from_this(); | |||
async_first([weakSelf,ex](){ | |||
auto strongSelf = weakSelf.lock(); | |||
if (strongSelf) { | |||
strongSelf->shutdown(ex); | |||
} | |||
}); | |||
} | |||
} // namespace toolkit |
@@ -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 <memory> | |||
#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<Session>, public SocketHelper { | |||
public: | |||
using Ptr = std::shared_ptr<Session>; | |||
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<toolkit::ObjectStatistic<toolkit::TcpSession> > _statistic_tcp; | |||
std::unique_ptr<toolkit::ObjectStatistic<toolkit::UdpSession> > _statistic_udp; | |||
}; | |||
// 通过该模板可以让TCP服务器快速支持TLS | |||
template <typename SessionType> | |||
class SessionWithSSL : public SessionType { | |||
public: | |||
template <typename... ArgsType> | |||
SessionWithSSL(ArgsType &&...args) | |||
: SessionType(std::forward<ArgsType>(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<Buffer::Ptr &>(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 |
@@ -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 <memory> | |||
#include <string> | |||
#include <mutex> | |||
#include <atomic> | |||
#include <sstream> | |||
#include <functional> | |||
#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<SockNum>; | |||
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<SockFD>; | |||
/** | |||
* 创建一个fd对象 | |||
* @param num 文件描述符,int数字 | |||
* @param poller 事件监听器 | |||
*/ | |||
SockFD(int num, SockNum::SockType type, const EventPoller::Ptr &poller) { | |||
_num = std::make_shared<SockNum>(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 Mtx = std::recursive_mutex> | |||
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<Socket>, public noncopyable, public SockInfo { | |||
public: | |||
using Ptr = std::shared_ptr<Socket>; | |||
//接收数据回调 | |||
using onReadCB = std::function<void(const Buffer::Ptr &buf, struct sockaddr *addr, int addr_len)>; | |||
//发生错误回调 | |||
using onErrCB = std::function<void(const SockException &err)>; | |||
//tcp监听接收到连接请求 | |||
using onAcceptCB = std::function<void(Socket::Ptr &sock, std::shared_ptr<void> &complete)>; | |||
//socket发送缓存清空事件,返回true代表下次继续监听该事件,否则停止 | |||
using onFlush = std::function<bool()>; | |||
//在接收到连接请求前,拦截Socket默认生成方式 | |||
using onCreateSocket = std::function<Ptr(const EventPoller::Ptr &poller)>; | |||
//发送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<bool> _enable_recv {true}; | |||
//标记该socket是否可写,socket写缓存满了就不可写 | |||
std::atomic<bool> _sendable {true}; | |||
//tcp连接超时定时器 | |||
Timer::Ptr _con_timer; | |||
//tcp连接结果回调对象 | |||
std::shared_ptr<std::function<void(int)> > _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<std::recursive_mutex> _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<std::recursive_mutex> _mtx_event; | |||
//一级发送缓存, socket可写时,会把一级缓存批量送入到二级缓存 | |||
List<std::pair<Buffer::Ptr, bool> > _send_buf_waiting; | |||
//一级发送缓存锁 | |||
MutexWrapper<std::recursive_mutex> _mtx_send_buf_waiting; | |||
//二级发送缓存, socket可写时,会把二级缓存批量写入到socket | |||
List<BufferList::Ptr> _send_buf_sending; | |||
//二级发送缓存锁 | |||
MutexWrapper<std::recursive_mutex> _mtx_send_buf_sending; | |||
//发送buffer结果回调 | |||
BufferList::SendResult _send_result; | |||
//对象个数统计 | |||
ObjectStatistic<Socket> _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<typename T> | |||
SockSender &operator << (T &&buf) { | |||
std::ostringstream ss; | |||
ss << std::forward<T>(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 */ |
@@ -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 <Foundation/Foundation.h> | |||
#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 |
@@ -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<TcpClient> weak_self = shared_from_this(); | |||
_timer = std::make_shared<Timer>(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<TcpClient> 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 */ |
@@ -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 <memory> | |||
#include "Socket.h" | |||
#include "Util/SSLBox.h" | |||
namespace toolkit { | |||
//Tcp客户端,Socket对象默认开始互斥锁 | |||
class TcpClient : public std::enable_shared_from_this<TcpClient>, public SocketHelper { | |||
public: | |||
using Ptr = std::shared_ptr<TcpClient>; | |||
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> _timer; | |||
//对象个数统计 | |||
ObjectStatistic<TcpClient> _statistic; | |||
}; | |||
//用于实现TLS客户端的模板对象 | |||
template<typename TcpClientType> | |||
class TcpClientWithSSL : public TcpClientType { | |||
public: | |||
using Ptr = std::shared_ptr<TcpClientWithSSL>; | |||
template<typename ...ArgsType> | |||
TcpClientWithSSL(ArgsType &&...args):TcpClientType(std::forward<ArgsType>(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<Buffer::Ptr &>(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<SSL_Box>(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> _ssl_box; | |||
}; | |||
} /* namespace toolkit */ | |||
#endif /* NETWORK_TCPCLIENT_H */ |
@@ -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<void> &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<TcpServer>(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<TcpServer> weak_self = std::dynamic_pointer_cast<TcpServer>(shared_from_this()); | |||
_timer = std::make_shared<Timer>(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<TcpServer> weak_self = std::dynamic_pointer_cast<TcpServer>(shared_from_this()); | |||
//创建一个Session;这里实现创建不同的服务会话实例 | |||
auto helper = _session_alloc(std::dynamic_pointer_cast<TcpServer>(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<Session> 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<TcpServer> weak_self = std::dynamic_pointer_cast<TcpServer>(shared_from_this()); | |||
_timer = std::make_shared<Timer>(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<EventPoller>(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<TcpServer>(_parent ? const_cast<TcpServer *>(_parent)->shared_from_this() : | |||
const_cast<TcpServer *>(this)->shared_from_this()); | |||
} | |||
Session::Ptr TcpServer::createSession(const Socket::Ptr &sock) { | |||
return getServer(sock->getPoller().get())->onAcceptConnection(sock); | |||
} | |||
} /* namespace toolkit */ | |||
@@ -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 <memory> | |||
#include <functional> | |||
#include <unordered_map> | |||
#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<TcpServer>; | |||
/** | |||
* 创建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<typename SessionType> | |||
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<SessionType>(sock); | |||
session->setOnCreateSocket(server->_on_create_socket); | |||
return std::make_shared<SessionHelper>(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> _timer; | |||
Socket::onCreateSocket _on_create_socket; | |||
std::unordered_map<SessionHelper *, SessionHelper::Ptr> _session_map; | |||
std::function<SessionHelper::Ptr(const TcpServer::Ptr &server, const Socket::Ptr &)> _session_alloc; | |||
std::unordered_map<const EventPoller *, Ptr> _cloned_server; | |||
//对象个数统计 | |||
ObjectStatistic<TcpServer> _statistic; | |||
}; | |||
} /* namespace toolkit */ | |||
#endif /* TCPSERVER_TCPSERVER_H */ |
@@ -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<std::recursive_mutex> 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<std::recursive_mutex>(); | |||
_session_map = std::make_shared<std::unordered_map<PeerIdType, SessionHelper::Ptr> >(); | |||
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<UdpServer> weak_self = std::dynamic_pointer_cast<UdpServer>(shared_from_this()); | |||
_timer = std::make_shared<Timer>(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<EventPoller>(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<UdpServer>(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<Session> weak_session = session; | |||
//由于socket读buffer是该线程上所有socket共享复用的,所以不能跨线程使用,必须先拷贝一下 | |||
auto cacheable_buf = std::make_shared<BufferString>(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<std::recursive_mutex> lock(*_session_mutex); | |||
//拷贝map,防止遍历时移除对象 | |||
copy_map = std::make_shared<std::unordered_map<PeerIdType, SessionHelper::Ptr> >(*_session_map); | |||
} | |||
EventPollerPool::Instance().for_each([copy_map](const TaskExecutor::Ptr &executor) { | |||
auto poller = std::dynamic_pointer_cast<EventPoller>(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<std::recursive_mutex> 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<UdpServer> weak_self = std::dynamic_pointer_cast<UdpServer>(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<std::recursive_mutex> 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<Session> 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<std::recursive_mutex> 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<BufferString>(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 |
@@ -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<UdpServer>; | |||
using PeerIdType = std::string; | |||
using onCreateSocket = std::function<Socket::Ptr(const EventPoller::Ptr &, const Buffer::Ptr &, struct sockaddr *, int)>; | |||
explicit UdpServer(const EventPoller::Ptr &poller = nullptr); | |||
~UdpServer() override; | |||
/** | |||
* @brief 开始监听服务器 | |||
*/ | |||
template<typename SessionType> | |||
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<SessionType>(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<SessionHelper>(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> _timer; | |||
onCreateSocket _on_create_socket; | |||
//cloned server共享主server的session map,防止数据在不同server间漂移 | |||
std::shared_ptr<std::recursive_mutex> _session_mutex; | |||
std::shared_ptr<std::unordered_map<PeerIdType, SessionHelper::Ptr> > _session_map; | |||
//主server持有cloned server的引用 | |||
std::unordered_map<EventPoller *, Ptr> _cloned_server; | |||
std::function<SessionHelper::Ptr(const UdpServer::Ptr &, const Socket::Ptr &)> _session_alloc; | |||
// 对象个数统计 | |||
ObjectStatistic<UdpServer> _statistic; | |||
}; | |||
} // namespace toolkit | |||
#endif // TOOLKIT_NETWORK_UDPSERVER_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 <winsock2.h> | |||
#include <ws2tcpip.h> | |||
#include <iphlpapi.h> | |||
#pragma comment (lib, "Ws2_32.lib") | |||
#pragma comment(lib,"Iphlpapi.lib") | |||
#else | |||
#include <netdb.h> | |||
#include <arpa/inet.h> | |||
#include <sys/ioctl.h> | |||
#include <sys/socket.h> | |||
#include <net/if.h> | |||
#include <netinet/in.h> | |||
#include <netinet/tcp.h> | |||
#endif // defined(_WIN32) | |||
#include <cstring> | |||
#include <cstdint> | |||
#include <map> | |||
#include <vector> | |||
#include <string> | |||
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<map<ip:name> > | |||
*/ | |||
static std::vector<std::map<std::string, std::string>> 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 |
@@ -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 <sys/epoll.h> | |||
#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<PollEventCB>(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<Poll_Record>(); | |||
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<PollEventCB &>(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<PollDelCB &>(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<Task>(std::move(task)); | |||
{ | |||
lock_guard<mutex> 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<mutex> 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<mutex> 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<EventPoller> 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<mutex> 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<Poll_Record::Ptr> 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<uint64_t()> task) { | |||
DelayTask::Ptr ret = std::make_shared<DelayTask>(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<EventPoller>(_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<EventPoller>(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 | |||
@@ -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 <mutex> | |||
#include <thread> | |||
#include <string> | |||
#include <functional> | |||
#include <memory> | |||
#include <unordered_map> | |||
#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<EventPoller> { | |||
public: | |||
friend class TaskExecutorGetterImp; | |||
using Ptr = std::shared_ptr<EventPoller>; | |||
using PollEventCB = std::function<void(int event)>; | |||
using PollDelCB = std::function<void(bool success)>; | |||
using DelayTask = TaskCancelableImp<uint64_t(void)>; | |||
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<uint64_t()> 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<BufferRaw> _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<Task::Ptr> _list_task; | |||
//保持日志可用 | |||
Logger::Ptr _logger; | |||
#if defined(HAS_EPOLL) | |||
//epoll相关 | |||
int _epoll_fd = -1; | |||
unordered_map<int, std::shared_ptr<PollEventCB> > _event_map; | |||
#else | |||
//select相关 | |||
struct Poll_Record { | |||
using Ptr = std::shared_ptr<Poll_Record>; | |||
int event; | |||
int attach; | |||
PollEventCB call_back; | |||
}; | |||
unordered_map<int, Poll_Record::Ptr> _event_map; | |||
#endif //HAS_EPOLL | |||
//定时器相关 | |||
std::multimap<uint64_t, DelayTask::Ptr> _delay_task_map; | |||
}; | |||
class EventPollerPool : public std::enable_shared_from_this<EventPollerPool>, public TaskExecutorGetterImp { | |||
public: | |||
using Ptr = std::shared_ptr<EventPollerPool>; | |||
~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 */ |
@@ -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 <fcntl.h> | |||
#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<PipeWrap>(); | |||
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<char> 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 |
@@ -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 <functional> | |||
#include "PipeWrap.h" | |||
#include "EventPoller.h" | |||
namespace toolkit { | |||
class Pipe { | |||
public: | |||
using onRead = std::function<void(int size, const char *buf)>; | |||
Pipe(const onRead &cb = nullptr, const EventPoller::Ptr &poller = nullptr); | |||
~Pipe(); | |||
void send(const char *send, int size = 0); | |||
private: | |||
std::shared_ptr<PipeWrap> _pipe; | |||
EventPoller::Ptr _poller; | |||
}; | |||
} // namespace toolkit | |||
#endif /* Pipe_h */ |
@@ -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 <stdexcept> | |||
#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*/ |
@@ -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 | |||
@@ -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 */ | |||
@@ -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_ */ |
@@ -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<bool()> &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 |
@@ -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 <functional> | |||
#include "EventPoller.h" | |||
namespace toolkit { | |||
class Timer { | |||
public: | |||
using Ptr = std::shared_ptr<Timer>; | |||
/** | |||
* 构造定时器 | |||
* @param second 定时器重复秒数 | |||
* @param cb 定时器任务,返回true表示重复下次任务,否则不重复,如果任务中抛异常,则默认重复下次任务 | |||
* @param poller EventPoller对象,可以为nullptr | |||
*/ | |||
Timer(float second, const std::function<bool()> &cb, const EventPoller::Ptr &poller); | |||
~Timer(); | |||
private: | |||
std::weak_ptr<EventPoller::DelayTask> _tag; | |||
//定时器保持EventPoller的强引用 | |||
EventPoller::Ptr _poller; | |||
}; | |||
} // namespace toolkit | |||
#endif /* Timer_h */ |
@@ -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 | |||
``` | |||
@@ -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 <memory> | |||
#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<mutex> 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<mutex> 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<mutex> 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<int> TaskExecutorGetterImp::getExecutorLoad() { | |||
vector<int> vec(_threads.size()); | |||
int i = 0; | |||
for (auto &executor : _threads) { | |||
vec[i++] = executor->load(); | |||
} | |||
return vec; | |||
} | |||
void TaskExecutorGetterImp::getExecutorDelay(const function<void(const vector<int> &)> &callback) { | |||
std::shared_ptr<vector<int> > delay_vec = std::make_shared<vector<int>>(_threads.size()); | |||
shared_ptr<void> finished(nullptr, [callback, delay_vec](void *) { | |||
//此析构回调触发时,说明已执行完毕所有async任务 | |||
callback((*delay_vec)); | |||
}); | |||
int index = 0; | |||
for (auto &th : _threads) { | |||
std::shared_ptr<Ticker> delay_ticker = std::make_shared<Ticker>(); | |||
th->async([finished, delay_vec, index, delay_ticker]() { | |||
(*delay_vec)[index] = (int) delay_ticker->elapsedTime(); | |||
}, false); | |||
++index; | |||
} | |||
} | |||
void TaskExecutorGetterImp::for_each(const function<void(const TaskExecutor::Ptr &)> &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 |
@@ -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 <mutex> | |||
#include <memory> | |||
#include <functional> | |||
#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<TimeRecord> _time_list; | |||
}; | |||
class TaskCancelable : public noncopyable { | |||
public: | |||
TaskCancelable() = default; | |||
virtual ~TaskCancelable() = default; | |||
virtual void cancel() = 0; | |||
}; | |||
template<class R, class... ArgTypes> | |||
class TaskCancelableImp; | |||
template<class R, class... ArgTypes> | |||
class TaskCancelableImp<R(ArgTypes...)> : public TaskCancelable { | |||
public: | |||
using Ptr = std::shared_ptr<TaskCancelableImp>; | |||
using func_type = std::function<R(ArgTypes...)>; | |||
~TaskCancelableImp() = default; | |||
template<typename FUNC> | |||
TaskCancelableImp(FUNC &&task) { | |||
_strongTask = std::make_shared<func_type>(std::forward<FUNC>(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<ArgTypes>(args)...); | |||
} | |||
return defaultValue<R>(); | |||
} | |||
template<typename T> | |||
static typename std::enable_if<std::is_void<T>::value, void>::type | |||
defaultValue() {} | |||
template<typename T> | |||
static typename std::enable_if<std::is_pointer<T>::value, T>::type | |||
defaultValue() { | |||
return nullptr; | |||
} | |||
template<typename T> | |||
static typename std::enable_if<std::is_integral<T>::value, T>::type | |||
defaultValue() { | |||
return 0; | |||
} | |||
protected: | |||
std::weak_ptr<func_type> _weakTask; | |||
std::shared_ptr<func_type> _strongTask; | |||
}; | |||
using TaskIn = std::function<void()>; | |||
using Task = TaskCancelableImp<void()>; | |||
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<TaskExecutor>; | |||
/** | |||
* 构造函数 | |||
* @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<TaskExecutorGetter>; | |||
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<int> getExecutorLoad(); | |||
/** | |||
* 获取所有线程任务执行延时,单位毫秒 | |||
* 通过此函数也可以大概知道线程负载情况 | |||
* @return | |||
*/ | |||
void getExecutorDelay(const std::function<void(const std::vector<int> &)> &callback); | |||
/** | |||
* 遍历所有线程 | |||
*/ | |||
void for_each(const std::function<void(const TaskExecutor::Ptr &)> &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<TaskExecutor::Ptr> _threads; | |||
}; | |||
}//toolkit | |||
#endif //ZLTOOLKIT_TASKEXECUTOR_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 <mutex> | |||
#include "Util/List.h" | |||
#include "semaphore.h" | |||
namespace toolkit { | |||
//实现了一个基于函数对象的任务列队,该列队是线程安全的,任务列队任务数由信号量控制 | |||
template<typename T> | |||
class TaskQueue { | |||
public: | |||
//打入任务至列队 | |||
template<typename C> | |||
void push_task(C &&task_func) { | |||
{ | |||
std::lock_guard<decltype(_mutex)> lock(_mutex); | |||
_queue.emplace_back(std::forward<C>(task_func)); | |||
} | |||
_sem.post(); | |||
} | |||
template<typename C> | |||
void push_task_first(C &&task_func) { | |||
{ | |||
std::lock_guard<decltype(_mutex)> lock(_mutex); | |||
_queue.emplace_front(std::forward<C>(task_func)); | |||
} | |||
_sem.post(); | |||
} | |||
//清空任务列队 | |||
void push_exit(size_t n) { | |||
_sem.post(n); | |||
} | |||
//从列队获取一个任务,由执行线程执行 | |||
bool get_task(T &tsk) { | |||
_sem.wait(); | |||
std::lock_guard<decltype(_mutex)> lock(_mutex); | |||
if (_queue.empty()) { | |||
return false; | |||
} | |||
tsk = std::move(_queue.front()); | |||
_queue.pop_front(); | |||
return true; | |||
} | |||
size_t size() const { | |||
std::lock_guard<decltype(_mutex)> lock(_mutex); | |||
return _queue.size(); | |||
} | |||
private: | |||
List <T> _queue; | |||
mutable std::mutex _mutex; | |||
semaphore _sem; | |||
}; | |||
} /* namespace toolkit */ | |||
#endif /* TASKQUEUE_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<Task>(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<Task>(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<Task::Ptr> _queue; | |||
thread_group _thread_group; | |||
Priority _priority; | |||
Logger::Ptr _logger; | |||
}; | |||
} /* namespace toolkit */ | |||
#endif /* THREADPOOL_H_ */ |
@@ -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<EventPoller>(_threads.front()); | |||
} | |||
EventPoller::Ptr WorkThreadPool::getPoller() { | |||
return std::dynamic_pointer_cast<EventPoller>(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 */ | |||
@@ -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 <memory> | |||
#include "Poller/EventPoller.h" | |||
namespace toolkit { | |||
class WorkThreadPool : public std::enable_shared_from_this<WorkThreadPool>, public TaskExecutorGetterImp { | |||
public: | |||
using Ptr = std::shared_ptr<WorkThreadPool>; | |||
~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_ */ |
@@ -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 <semaphore.h> | |||
#define HAVE_SEM | |||
#endif //HAVE_SEM | |||
*/ | |||
#include <mutex> | |||
#include <condition_variable> | |||
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<std::recursive_mutex> 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<std::recursive_mutex> 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_ */ |
@@ -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 <stdexcept> | |||
#include <thread> | |||
#include <unordered_map> | |||
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<typename F> | |||
std::thread *create_thread(F &&threadfunc) { | |||
auto thread_new = std::make_shared<std::thread>(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<std::thread::id, std::shared_ptr<std::thread>> _threads; | |||
}; | |||
} /* namespace toolkit */ | |||
#endif /* THREADGROUP_H_ */ |
@@ -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 <getopt.h> | |||
#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<ostream> &stream) { | |||
vector<struct option> 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<mutex> 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 |
@@ -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 <map> | |||
#include <mutex> | |||
#include <string> | |||
#include <memory> | |||
#include <vector> | |||
#include <iostream> | |||
#include <functional> | |||
#include "mini.h" | |||
namespace toolkit{ | |||
class Option { | |||
public: | |||
using OptionHandler = std::function<bool(const std::shared_ptr<std::ostream> &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<std::string>(default_value); | |||
} | |||
if (!_default_value && must_exist) { | |||
_must_exist = true; | |||
} | |||
} | |||
_des = des; | |||
_cb = cb; | |||
} | |||
bool operator()(const std::shared_ptr<std::ostream> &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<std::string> _default_value; | |||
}; | |||
class OptionParser { | |||
public: | |||
using OptionCompleted = std::function<void(const std::shared_ptr<std::ostream> &, 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<std::ostream> &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<Option>(option)); | |||
return *this; | |||
} | |||
OptionParser &operator<<(const 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, option); | |||
return *this; | |||
} | |||
void delOption(const char *key) { | |||
for (auto &pr : _map_options) { | |||
if (pr.second._long_opt == key) { | |||
if (pr.second._short_opt) { | |||
_map_char_index.erase(pr.second._short_opt); | |||
} | |||
_map_options.erase(pr.first); | |||
break; | |||
} | |||
} | |||
} | |||
void operator ()(mINI &all_args, int argc, char *argv[], const std::shared_ptr<std::ostream> &stream); | |||
private: | |||
bool _enable_empty_args; | |||
Option _helper; | |||
std::map<char, int> _map_char_index; | |||
std::map<int, Option> _map_options; | |||
OptionCompleted _on_completed; | |||
}; | |||
class CMD : public mINI { | |||
public: | |||
virtual ~CMD() = default; | |||
virtual const char *description() const { | |||
return "description"; | |||
} | |||
void operator()(int argc, char *argv[], const std::shared_ptr<std::ostream> &stream = nullptr) { | |||
this->clear(); | |||
std::shared_ptr<std::ostream> coutPtr(&std::cout, [](std::ostream *) {}); | |||
(*_parser)(*this, argc, argv, stream ? stream : coutPtr); | |||
} | |||
bool hasKey(const char *key) { | |||
return this->find(key) != this->end(); | |||
} | |||
std::vector<variant> splitedVal(const char *key, const char *delim = ":") { | |||
std::vector<variant> ret; | |||
auto &val = (*this)[key]; | |||
split(val, delim, ret); | |||
return ret; | |||
} | |||
void delOption(const char *key) { | |||
if (_parser) { | |||
_parser->delOption(key); | |||
} | |||
} | |||
protected: | |||
std::shared_ptr<OptionParser> _parser; | |||
private: | |||
void split(const std::string &s, const char *delim, std::vector<variant> &ret) { | |||
size_t last = 0; | |||
auto index = s.find(delim, last); | |||
while (index != std::string::npos) { | |||
if (index - last > 0) { | |||
ret.push_back(s.substr(last, index - last)); | |||
} | |||
last = index + strlen(delim); | |||
index = s.find(delim, last); | |||
} | |||
if (s.size() - last > 0) { | |||
ret.push_back(s.substr(last)); | |||
} | |||
} | |||
}; | |||
class CMDRegister { | |||
public: | |||
static CMDRegister &Instance(); | |||
void clear() { | |||
std::lock_guard<std::recursive_mutex> lck(_mtx); | |||
_cmd_map.clear(); | |||
} | |||
void registCMD(const char *name, const std::shared_ptr<CMD> &cmd) { | |||
std::lock_guard<std::recursive_mutex> lck(_mtx); | |||
_cmd_map.emplace(name, cmd); | |||
} | |||
void unregistCMD(const char *name) { | |||
std::lock_guard<std::recursive_mutex> lck(_mtx); | |||
_cmd_map.erase(name); | |||
} | |||
std::shared_ptr<CMD> operator[](const char *name) { | |||
std::lock_guard<std::recursive_mutex> lck(_mtx); | |||
auto it = _cmd_map.find(name); | |||
if (it == _cmd_map.end()) { | |||
throw std::invalid_argument(std::string("CMD not existed: ") + name); | |||
} | |||
return it->second; | |||
} | |||
void operator()(const char *name, int argc, char *argv[], const std::shared_ptr<std::ostream> &stream = nullptr) { | |||
auto cmd = (*this)[name]; | |||
if (!cmd) { | |||
throw std::invalid_argument(std::string("CMD not existed: ") + name); | |||
} | |||
(*cmd)(argc, argv, stream); | |||
} | |||
void printHelp(const std::shared_ptr<std::ostream> &streamTmp = nullptr) { | |||
auto stream = streamTmp; | |||
if (!stream) { | |||
stream.reset(&std::cout, [](std::ostream *) {}); | |||
} | |||
std::lock_guard<std::recursive_mutex> lck(_mtx); | |||
size_t maxLen = 0; | |||
for (auto &pr : _cmd_map) { | |||
if (pr.first.size() > maxLen) { | |||
maxLen = pr.first.size(); | |||
} | |||
} | |||
for (auto &pr : _cmd_map) { | |||
(*stream) << " " << pr.first; | |||
for (size_t i = 0; i < maxLen - pr.first.size(); ++i) { | |||
(*stream) << " "; | |||
} | |||
(*stream) << " " << pr.second->description() << std::endl; | |||
} | |||
} | |||
void operator()(const std::string &line, const std::shared_ptr<std::ostream> &stream = nullptr) { | |||
if (line.empty()) { | |||
return; | |||
} | |||
std::vector<char *> argv; | |||
size_t argc = getArgs((char *) line.data(), argv); | |||
if (argc == 0) { | |||
return; | |||
} | |||
std::string cmd = argv[0]; | |||
std::lock_guard<std::recursive_mutex> lck(_mtx); | |||
auto it = _cmd_map.find(cmd); | |||
if (it == _cmd_map.end()) { | |||
std::stringstream ss; | |||
ss << " 未识别的命令\"" << cmd << "\",输入 \"help\" 获取帮助."; | |||
throw std::invalid_argument(ss.str()); | |||
} | |||
(*it->second)((int) argc, &argv[0], stream); | |||
} | |||
private: | |||
size_t getArgs(char *buf, std::vector<char *> &argv) { | |||
size_t argc = 0; | |||
bool start = false; | |||
auto len = strlen(buf); | |||
for (size_t i = 0; i < len; ++i) { | |||
if (buf[i] != ' ' && buf[i] != '\t' && buf[i] != '\r' && buf[i] != '\n') { | |||
if (!start) { | |||
start = true; | |||
if (argv.size() < argc + 1) { | |||
argv.resize(argc + 1); | |||
} | |||
argv[argc++] = buf + i; | |||
} | |||
} else { | |||
buf[i] = '\0'; | |||
start = false; | |||
} | |||
} | |||
return argc; | |||
} | |||
private: | |||
std::recursive_mutex _mtx; | |||
std::map<std::string, std::shared_ptr<CMD> > _cmd_map; | |||
}; | |||
//帮助命令(help),该命令默认已注册 | |||
class CMD_help : public CMD { | |||
public: | |||
CMD_help() { | |||
_parser = std::make_shared<OptionParser>([](const std::shared_ptr<std::ostream> &stream, mINI &) { | |||
CMDRegister::Instance().printHelp(stream); | |||
}); | |||
} | |||
const char *description() const override { | |||
return "打印帮助信息"; | |||
} | |||
}; | |||
class ExitException : public std::exception {}; | |||
//退出程序命令(exit),该命令默认已注册 | |||
class CMD_exit : public CMD { | |||
public: | |||
CMD_exit() { | |||
_parser = std::make_shared<OptionParser>([](const std::shared_ptr<std::ostream> &, mINI &) { | |||
throw ExitException(); | |||
}); | |||
} | |||
const char *description() const override { | |||
return "退出shell"; | |||
} | |||
}; | |||
//退出程序命令(quit),该命令默认已注册 | |||
#define CMD_quit CMD_exit | |||
//清空屏幕信息命令(clear),该命令默认已注册 | |||
class CMD_clear : public CMD { | |||
public: | |||
CMD_clear() { | |||
_parser = std::make_shared<OptionParser>([this](const std::shared_ptr<std::ostream> &stream, mINI &args) { | |||
clear(stream); | |||
}); | |||
} | |||
const char *description() const { | |||
return "清空屏幕输出"; | |||
} | |||
private: | |||
void clear(const std::shared_ptr<std::ostream> &stream) { | |||
(*stream) << "\x1b[2J\x1b[H"; | |||
stream->flush(); | |||
} | |||
}; | |||
#define GET_CMD(name) (*(CMDRegister::Instance()[name])) | |||
#define CMD_DO(name,...) (*(CMDRegister::Instance()[name]))(__VA_ARGS__) | |||
#define REGIST_CMD(name) CMDRegister::Instance().registCMD(#name,std::make_shared<CMD_##name>()); | |||
}//namespace toolkit | |||
#endif /* SRC_UTIL_CMD_H_ */ |
@@ -0,0 +1,343 @@ | |||
/* | |||
* 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. | |||
*/ | |||
#if defined(_WIN32) | |||
#include <io.h> | |||
#include <direct.h> | |||
#else | |||
#include <dirent.h> | |||
#include <limits.h> | |||
#endif // WIN32 | |||
#include <sys/stat.h> | |||
#include "File.h" | |||
#include "util.h" | |||
#include "logger.h" | |||
#include "uv_errno.h" | |||
using namespace std; | |||
using namespace toolkit; | |||
#if !defined(_WIN32) | |||
#define _unlink unlink | |||
#define _rmdir rmdir | |||
#define _access access | |||
#endif | |||
#if defined(_WIN32) | |||
int mkdir(const char *path, int mode) { | |||
return _mkdir(path); | |||
} | |||
DIR *opendir(const char *name) { | |||
char namebuf[512]; | |||
snprintf(namebuf, sizeof(namebuf), "%s\\*.*", name); | |||
WIN32_FIND_DATAA FindData; | |||
auto hFind = FindFirstFileA(namebuf, &FindData); | |||
if (hFind == INVALID_HANDLE_VALUE) { | |||
return nullptr; | |||
} | |||
DIR *dir = (DIR *)malloc(sizeof(DIR)); | |||
memset(dir, 0, sizeof(DIR)); | |||
dir->dd_fd = 0; // simulate return | |||
dir->handle = hFind; | |||
return dir; | |||
} | |||
struct dirent *readdir(DIR *d) { | |||
HANDLE hFind = d->handle; | |||
WIN32_FIND_DATAA FileData; | |||
//fail or end | |||
if (!FindNextFileA(hFind, &FileData)) { | |||
return nullptr; | |||
} | |||
struct dirent *dir = (struct dirent *)malloc(sizeof(struct dirent) + sizeof(FileData.cFileName)); | |||
strcpy(dir->d_name, (char *)FileData.cFileName); | |||
dir->d_reclen = (uint16_t)strlen(dir->d_name); | |||
//check there is file or directory | |||
if (FileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { | |||
dir->d_type = 2; | |||
} | |||
else { | |||
dir->d_type = 1; | |||
} | |||
if (d->index) { | |||
//覆盖前释放内存 | |||
free(d->index); | |||
d->index = nullptr; | |||
} | |||
d->index = dir; | |||
return dir; | |||
} | |||
int closedir(DIR *d) { | |||
if (!d) { | |||
return -1; | |||
} | |||
//关闭句柄 | |||
if (d->handle != INVALID_HANDLE_VALUE) { | |||
FindClose(d->handle); | |||
d->handle = INVALID_HANDLE_VALUE; | |||
} | |||
//释放内存 | |||
if (d->index) { | |||
free(d->index); | |||
d->index = nullptr; | |||
} | |||
free(d); | |||
return 0; | |||
} | |||
#endif // defined(_WIN32) | |||
namespace toolkit { | |||
FILE *File::create_file(const char *file, const char *mode) { | |||
std::string path = file; | |||
std::string dir; | |||
size_t index = 1; | |||
FILE *ret = nullptr; | |||
while (true) { | |||
index = path.find('/', index) + 1; | |||
dir = path.substr(0, index); | |||
if (dir.length() == 0) { | |||
break; | |||
} | |||
if (_access(dir.c_str(), 0) == -1) { //access函数是查看是不是存在 | |||
if (mkdir(dir.c_str(), 0777) == -1) { //如果不存在就用mkdir函数来创建 | |||
WarnL << "mkdir " << dir << " failed: " << get_uv_errmsg(); | |||
return nullptr; | |||
} | |||
} | |||
} | |||
if (path[path.size() - 1] != '/') { | |||
ret = fopen(file, mode); | |||
} | |||
return ret; | |||
} | |||
bool File::create_path(const char *file, unsigned int mod) { | |||
std::string path = file; | |||
std::string dir; | |||
size_t index = 1; | |||
while (1) { | |||
index = path.find('/', index) + 1; | |||
dir = path.substr(0, index); | |||
if (dir.length() == 0) { | |||
break; | |||
} | |||
if (_access(dir.c_str(), 0) == -1) { //access函数是查看是不是存在 | |||
if (mkdir(dir.c_str(), mod) == -1) { //如果不存在就用mkdir函数来创建 | |||
WarnL << "mkdir " << dir << " failed: " << get_uv_errmsg(); | |||
return false; | |||
} | |||
} | |||
} | |||
return true; | |||
} | |||
//判断是否为目录 | |||
bool File::is_dir(const char *path) { | |||
auto dir = opendir(path); | |||
if (!dir) { | |||
return false; | |||
} | |||
closedir(dir); | |||
return true; | |||
} | |||
//判断是否为常规文件 | |||
bool File::fileExist(const char *path) { | |||
auto fp = fopen(path, "rb"); | |||
if (!fp) { | |||
return false; | |||
} | |||
fclose(fp); | |||
return true; | |||
} | |||
//判断是否是特殊目录 | |||
bool File::is_special_dir(const char *path) { | |||
return strcmp(path, ".") == 0 || strcmp(path, "..") == 0; | |||
} | |||
//生成完整的文件路径 | |||
void get_file_path(const char *path, const char *file_name, char *file_path) { | |||
strcpy(file_path, path); | |||
if (file_path[strlen(file_path) - 1] != '/') { | |||
strcat(file_path, "/"); | |||
} | |||
strcat(file_path, file_name); | |||
} | |||
int File::delete_file(const char *path) { | |||
DIR *dir; | |||
dirent *dir_info; | |||
char file_path[PATH_MAX]; | |||
if (is_dir(path)) { | |||
if ((dir = opendir(path)) == nullptr) { | |||
return _rmdir(path); | |||
} | |||
while ((dir_info = readdir(dir)) != nullptr) { | |||
if (is_special_dir(dir_info->d_name)) { | |||
continue; | |||
} | |||
get_file_path(path, dir_info->d_name, file_path); | |||
delete_file(file_path); | |||
} | |||
auto ret = _rmdir(path); | |||
closedir(dir); | |||
return ret; | |||
} | |||
return remove(path) ? _unlink(path) : 0; | |||
} | |||
string File::loadFile(const char *path) { | |||
FILE *fp = fopen(path, "rb"); | |||
if (!fp) { | |||
return ""; | |||
} | |||
fseek(fp, 0, SEEK_END); | |||
auto len = ftell(fp); | |||
fseek(fp, 0, SEEK_SET); | |||
string str(len, '\0'); | |||
if (len != fread((char *)str.data(), 1, str.size(), fp)) { | |||
WarnL << "fread " << path << " failed: " << get_uv_errmsg(); | |||
} | |||
fclose(fp); | |||
return str; | |||
} | |||
bool File::saveFile(const string &data, const char *path) { | |||
FILE *fp = fopen(path, "wb"); | |||
if (!fp) { | |||
return false; | |||
} | |||
fwrite(data.data(), data.size(), 1, fp); | |||
fclose(fp); | |||
return true; | |||
} | |||
string File::parentDir(const string &path) { | |||
auto parent_dir = path; | |||
if (parent_dir.back() == '/') { | |||
parent_dir.pop_back(); | |||
} | |||
auto pos = parent_dir.rfind('/'); | |||
if (pos != string::npos) { | |||
parent_dir = parent_dir.substr(0, pos + 1); | |||
} | |||
return parent_dir; | |||
} | |||
string File::absolutePath(const string &path, const string ¤t_path, bool can_access_parent) { | |||
string currentPath = current_path; | |||
if (!currentPath.empty()) { | |||
//当前目录不为空 | |||
if (currentPath.front() == '.') { | |||
//如果当前目录是相对路径,那么先转换成绝对路径 | |||
currentPath = absolutePath(current_path, exeDir(), true); | |||
} | |||
} else { | |||
currentPath = exeDir(); | |||
} | |||
if (path.empty()) { | |||
//相对路径为空,那么返回当前目录 | |||
return currentPath; | |||
} | |||
if (currentPath.back() != '/') { | |||
//确保当前目录最后字节为'/' | |||
currentPath.push_back('/'); | |||
} | |||
auto rootPath = currentPath; | |||
auto dir_vec = split(path, "/"); | |||
for (auto &dir : dir_vec) { | |||
if (dir.empty() || dir == ".") { | |||
//忽略空或本文件夹 | |||
continue; | |||
} | |||
if (dir == "..") { | |||
//访问上级目录 | |||
if (!can_access_parent && currentPath.size() <= rootPath.size()) { | |||
//不能访问根目录之外的目录 | |||
return ""; | |||
} | |||
currentPath = parentDir(currentPath); | |||
continue; | |||
} | |||
currentPath.append(dir); | |||
currentPath.append("/"); | |||
} | |||
if (path.back() != '/' && currentPath.back() == '/') { | |||
//在路径是文件的情况下,防止转换成目录 | |||
currentPath.pop_back(); | |||
} | |||
return currentPath; | |||
} | |||
void File::scanDir(const string &path_in, const function<bool(const string &path, bool is_dir)> &cb, bool enter_subdirectory) { | |||
string path = path_in; | |||
if (path.back() == '/') { | |||
path.pop_back(); | |||
} | |||
DIR *pDir; | |||
dirent *pDirent; | |||
if ((pDir = opendir(path.data())) == nullptr) { | |||
//文件夹无效 | |||
return; | |||
} | |||
while ((pDirent = readdir(pDir)) != nullptr) { | |||
if (is_special_dir(pDirent->d_name)) { | |||
continue; | |||
} | |||
if (pDirent->d_name[0] == '.') { | |||
//隐藏的文件 | |||
continue; | |||
} | |||
string strAbsolutePath = path + "/" + pDirent->d_name; | |||
bool isDir = is_dir(strAbsolutePath.data()); | |||
if (!cb(strAbsolutePath, isDir)) { | |||
//不再继续扫描 | |||
break; | |||
} | |||
if (isDir && enter_subdirectory) { | |||
//如果是文件夹并且扫描子文件夹,那么递归扫描 | |||
scanDir(strAbsolutePath, cb, enter_subdirectory); | |||
} | |||
} | |||
closedir(pDir); | |||
} | |||
uint64_t File::fileSize(FILE *fp, bool remain_size) { | |||
if (!fp) { | |||
return 0; | |||
} | |||
auto current = ftell64(fp); | |||
fseek64(fp, 0L, SEEK_END); /* 定位到文件末尾 */ | |||
auto end = ftell64(fp); /* 得到文件大小 */ | |||
fseek64(fp, current, SEEK_SET); | |||
return end - (remain_size ? current : 0); | |||
} | |||
uint64_t File::fileSize(const char *path) { | |||
if (!path) { | |||
return 0; | |||
} | |||
auto fp = std::unique_ptr<FILE, decltype(&fclose)>(fopen(path, "rb"), fclose); | |||
return fileSize(fp.get()); | |||
} | |||
} /* namespace toolkit */ |
@@ -0,0 +1,145 @@ | |||
/* | |||
* 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_FILE_H_ | |||
#define SRC_UTIL_FILE_H_ | |||
#include <cstdio> | |||
#include <cstdlib> | |||
#include <string> | |||
#include "util.h" | |||
#include <functional> | |||
#if defined(__linux__) | |||
#include <limits.h> | |||
#endif | |||
#if defined(_WIN32) | |||
#ifndef PATH_MAX | |||
#define PATH_MAX 1024 | |||
#endif // !PATH_MAX | |||
struct dirent{ | |||
long d_ino; /* inode number*/ | |||
off_t d_off; /* offset to this dirent*/ | |||
unsigned short d_reclen; /* length of this d_name*/ | |||
unsigned char d_type; /* the type of d_name*/ | |||
char d_name[1]; /* file name (null-terminated)*/ | |||
}; | |||
typedef struct _dirdesc { | |||
int dd_fd; /** file descriptor associated with directory */ | |||
long dd_loc; /** offset in current buffer */ | |||
long dd_size; /** amount of data returned by getdirentries */ | |||
char *dd_buf; /** data buffer */ | |||
int dd_len; /** size of data buffer */ | |||
long dd_seek; /** magic cookie returned by getdirentries */ | |||
HANDLE handle; | |||
struct dirent *index; | |||
} DIR; | |||
# define __dirfd(dp) ((dp)->dd_fd) | |||
int mkdir(const char *path, int mode); | |||
DIR *opendir(const char *); | |||
int closedir(DIR *); | |||
struct dirent *readdir(DIR *); | |||
#endif // defined(_WIN32) | |||
#if defined(_WIN32) || defined(_WIN64) | |||
#define fseek64 _fseeki64 | |||
#define ftell64 _ftelli64 | |||
#else | |||
#define fseek64 fseek | |||
#define ftell64 ftell | |||
#endif | |||
namespace toolkit { | |||
class File { | |||
public: | |||
//创建路径 | |||
static bool create_path(const char *file, unsigned int mod); | |||
//新建文件,目录文件夹自动生成 | |||
static FILE *create_file(const char *file, const char *mode); | |||
//判断是否为目录 | |||
static bool is_dir(const char *path); | |||
//判断是否是特殊目录(. or ..) | |||
static bool is_special_dir(const char *path); | |||
//删除目录或文件 | |||
static int delete_file(const char *path); | |||
//判断文件是否存在 | |||
static bool fileExist(const char *path); | |||
/** | |||
* 加载文件内容至string | |||
* @param path 加载的文件路径 | |||
* @return 文件内容 | |||
*/ | |||
static std::string loadFile(const char *path); | |||
/** | |||
* 保存内容至文件 | |||
* @param data 文件内容 | |||
* @param path 保存的文件路径 | |||
* @return 是否保存成功 | |||
*/ | |||
static bool saveFile(const std::string &data, const char *path); | |||
/** | |||
* 获取父文件夹 | |||
* @param path 路径 | |||
* @return 文件夹 | |||
*/ | |||
static std::string parentDir(const std::string &path); | |||
/** | |||
* 替换"../",获取绝对路径 | |||
* @param path 相对路径,里面可能包含 "../" | |||
* @param current_path 当前目录 | |||
* @param can_access_parent 能否访问父目录之外的目录 | |||
* @return 替换"../"之后的路径 | |||
*/ | |||
static std::string absolutePath(const std::string &path, const std::string ¤t_path, bool can_access_parent = false); | |||
/** | |||
* 遍历文件夹下的所有文件 | |||
* @param path 文件夹路径 | |||
* @param cb 回调对象 ,path为绝对路径,isDir为该路径是否为文件夹,返回true代表继续扫描,否则中断 | |||
* @param enter_subdirectory 是否进入子目录扫描 | |||
*/ | |||
static void scanDir(const std::string &path, const std::function<bool(const std::string &path, bool isDir)> &cb, bool enter_subdirectory = false); | |||
/** | |||
* 获取文件大小 | |||
* @param fp 文件句柄 | |||
* @param remain_size true:获取文件剩余未读数据大小,false:获取文件总大小 | |||
*/ | |||
static uint64_t fileSize(FILE *fp, bool remain_size = false); | |||
/** | |||
* 获取文件大小 | |||
* @param path 文件路径 | |||
* @return 文件大小 | |||
* @warning 调用者应确保文件存在 | |||
*/ | |||
static uint64_t fileSize(const char *path); | |||
private: | |||
File(); | |||
~File(); | |||
}; | |||
} /* namespace toolkit */ | |||
#endif /* SRC_UTIL_FILE_H_ */ |
@@ -0,0 +1,218 @@ | |||
/* | |||
* 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_LIST_H | |||
#define ZLTOOLKIT_LIST_H | |||
#include <list> | |||
#include <type_traits> | |||
namespace toolkit { | |||
#if 0 | |||
template<typename T> | |||
class List; | |||
template<typename T> | |||
class ListNode | |||
{ | |||
public: | |||
friend class List<T>; | |||
~ListNode(){} | |||
template <class... Args> | |||
ListNode(Args&&... args):_data(std::forward<Args>(args)...){} | |||
private: | |||
T _data; | |||
ListNode *next = nullptr; | |||
}; | |||
template<typename T> | |||
class List { | |||
public: | |||
using NodeType = ListNode<T>; | |||
List(){} | |||
List(List &&that){ | |||
swap(that); | |||
} | |||
~List(){ | |||
clear(); | |||
} | |||
void clear(){ | |||
auto ptr = _front; | |||
auto last = ptr; | |||
while(ptr){ | |||
last = ptr; | |||
ptr = ptr->next; | |||
delete last; | |||
} | |||
_size = 0; | |||
_front = nullptr; | |||
_back = nullptr; | |||
} | |||
template<typename FUN> | |||
void for_each(FUN &&fun) const { | |||
auto ptr = _front; | |||
while (ptr) { | |||
fun(ptr->_data); | |||
ptr = ptr->next; | |||
} | |||
} | |||
size_t size() const{ | |||
return _size; | |||
} | |||
bool empty() const{ | |||
return _size == 0; | |||
} | |||
template <class... Args> | |||
void emplace_front(Args&&... args){ | |||
NodeType *node = new NodeType(std::forward<Args>(args)...); | |||
if(!_front){ | |||
_front = node; | |||
_back = node; | |||
_size = 1; | |||
}else{ | |||
node->next = _front; | |||
_front = node; | |||
++_size; | |||
} | |||
} | |||
template <class...Args> | |||
void emplace_back(Args&&... args){ | |||
NodeType *node = new NodeType(std::forward<Args>(args)...); | |||
if(!_back){ | |||
_back = node; | |||
_front = node; | |||
_size = 1; | |||
}else{ | |||
_back->next = node; | |||
_back = node; | |||
++_size; | |||
} | |||
} | |||
T &front() const{ | |||
return _front->_data; | |||
} | |||
T &back() const{ | |||
return _back->_data; | |||
} | |||
T &operator[](size_t pos){ | |||
NodeType *front = _front ; | |||
while(pos--){ | |||
front = front->next; | |||
} | |||
return front->_data; | |||
} | |||
void pop_front(){ | |||
if(!_front){ | |||
return; | |||
} | |||
auto ptr = _front; | |||
_front = _front->next; | |||
delete ptr; | |||
if(!_front){ | |||
_back = nullptr; | |||
} | |||
--_size; | |||
} | |||
void swap(List &other){ | |||
NodeType *tmp_node; | |||
tmp_node = _front; | |||
_front = other._front; | |||
other._front = tmp_node; | |||
tmp_node = _back; | |||
_back = other._back; | |||
other._back = tmp_node; | |||
size_t tmp_size = _size; | |||
_size = other._size; | |||
other._size = tmp_size; | |||
} | |||
void append(List<T> &other){ | |||
if(other.empty()){ | |||
return; | |||
} | |||
if(_back){ | |||
_back->next = other._front; | |||
_back = other._back; | |||
}else{ | |||
_front = other._front; | |||
_back = other._back; | |||
} | |||
_size += other._size; | |||
other._front = other._back = nullptr; | |||
other._size = 0; | |||
} | |||
List &operator=(const List &that) { | |||
that.for_each([&](const T &t) { | |||
emplace_back(t); | |||
}); | |||
return *this; | |||
} | |||
private: | |||
NodeType *_front = nullptr; | |||
NodeType *_back = nullptr; | |||
size_t _size = 0; | |||
}; | |||
#else | |||
template<typename T> | |||
class List : public std::list<T> { | |||
public: | |||
template<typename ... ARGS> | |||
List(ARGS &&...args) : std::list<T>(std::forward<ARGS>(args)...) {}; | |||
~List() = default; | |||
void append(List<T> &other) { | |||
if (other.empty()) { | |||
return; | |||
} | |||
this->insert(this->end(), other.begin(), other.end()); | |||
other.clear(); | |||
} | |||
template<typename FUNC> | |||
void for_each(FUNC &&func) { | |||
for (auto &t : *this) { | |||
func(t); | |||
} | |||
} | |||
template<typename FUNC> | |||
void for_each(FUNC &&func) const { | |||
for (auto &t : *this) { | |||
func(t); | |||
} | |||
} | |||
}; | |||
#endif | |||
} /* namespace toolkit */ | |||
#endif //ZLTOOLKIT_LIST_H |
@@ -0,0 +1,372 @@ | |||
//MD5.cpp | |||
/* MD5 | |||
converted to C++ class by Frank Thilo (thilo@unix-ag.org) | |||
for bzflag (http://www.bzflag.org) | |||
based on: | |||
md5.h and md5.c | |||
reference implemantion of RFC 1321 | |||
Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All | |||
rights reserved. | |||
Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> | |||
License to copy and use this software is granted provided that it | |||
is identified as the "RSA Data Security, Inc. MD5 Message-Digest | |||
Algorithm" in all material mentioning or referencing this software | |||
or this function. | |||
License is also granted to make and use derivative works provided | |||
that such works are identified as "derived from the RSA Data | |||
Security, Inc. MD5 Message-Digest Algorithm" in all material | |||
mentioning or referencing the derived work. | |||
RSA Data Security, Inc. makes no representations concerning either | |||
the merchantability of this software or the suitability of this | |||
software for any particular purpose. It is provided "as is" | |||
without express or implied warranty of any kind. | |||
These notices must be retained in any copies of any part of this | |||
documentation and/or software. | |||
*/ | |||
/* interface header */ | |||
#include "MD5.h" | |||
/* system implementation headers */ | |||
#include <cstdio> | |||
#include <cstring> | |||
namespace toolkit { | |||
// Constants for MD5Transform routine. | |||
#define S11 7 | |||
#define S12 12 | |||
#define S13 17 | |||
#define S14 22 | |||
#define S21 5 | |||
#define S22 9 | |||
#define S23 14 | |||
#define S24 20 | |||
#define S31 4 | |||
#define S32 11 | |||
#define S33 16 | |||
#define S34 23 | |||
#define S41 6 | |||
#define S42 10 | |||
#define S43 15 | |||
#define S44 21 | |||
/////////////////////////////////////////////// | |||
// F, G, H and I are basic MD5 functions. | |||
inline MD5::uint4 MD5::F(uint4 x, uint4 y, uint4 z) { | |||
return (x&y) | (~x&z); | |||
} | |||
inline MD5::uint4 MD5::G(uint4 x, uint4 y, uint4 z) { | |||
return (x&z) | (y&~z); | |||
} | |||
inline MD5::uint4 MD5::H(uint4 x, uint4 y, uint4 z) { | |||
return x^y^z; | |||
} | |||
inline MD5::uint4 MD5::I(uint4 x, uint4 y, uint4 z) { | |||
return y ^ (x | ~z); | |||
} | |||
// rotate_left rotates x left n bits. | |||
inline MD5::uint4 MD5::rotate_left(uint4 x, int n) { | |||
return (x << n) | (x >> (32-n)); | |||
} | |||
// FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4. | |||
// Rotation is separate from addition to prevent recomputation. | |||
inline void MD5::FF(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac) { | |||
a = rotate_left(a+ F(b,c,d) + x + ac, s) + b; | |||
} | |||
inline void MD5::GG(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac) { | |||
a = rotate_left(a + G(b,c,d) + x + ac, s) + b; | |||
} | |||
inline void MD5::HH(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac) { | |||
a = rotate_left(a + H(b,c,d) + x + ac, s) + b; | |||
} | |||
inline void MD5::II(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac) { | |||
a = rotate_left(a + I(b,c,d) + x + ac, s) + b; | |||
} | |||
////////////////////////////////////////////// | |||
// default ctor, just initailize | |||
MD5::MD5() | |||
{ | |||
init(); | |||
} | |||
////////////////////////////////////////////// | |||
// nifty shortcut ctor, compute MD5 for string and finalize it right away | |||
MD5::MD5(const std::string &text) | |||
{ | |||
init(); | |||
update(text.c_str(), text.length()); | |||
finalize(); | |||
} | |||
////////////////////////////// | |||
void MD5::init() | |||
{ | |||
finalized=false; | |||
count[0] = 0; | |||
count[1] = 0; | |||
// load magic initialization constants. | |||
state[0] = 0x67452301; | |||
state[1] = 0xefcdab89; | |||
state[2] = 0x98badcfe; | |||
state[3] = 0x10325476; | |||
} | |||
////////////////////////////// | |||
// decodes input (unsigned char) into output (uint4). Assumes len is a multiple of 4. | |||
void MD5::decode(uint4 output[], const uint1 input[], size_type len) | |||
{ | |||
for (unsigned int i = 0, j = 0; j < len; i++, j += 4) | |||
output[i] = ((uint4)input[j]) | (((uint4)input[j+1]) << 8) | | |||
(((uint4)input[j+2]) << 16) | (((uint4)input[j+3]) << 24); | |||
} | |||
////////////////////////////// | |||
// encodes input (uint4) into output (unsigned char). Assumes len is | |||
// a multiple of 4. | |||
void MD5::encode(uint1 output[], const uint4 input[], size_type len) | |||
{ | |||
for (size_type i = 0, j = 0; j < len; i++, j += 4) { | |||
output[j] = input[i] & 0xff; | |||
output[j+1] = (input[i] >> 8) & 0xff; | |||
output[j+2] = (input[i] >> 16) & 0xff; | |||
output[j+3] = (input[i] >> 24) & 0xff; | |||
} | |||
} | |||
////////////////////////////// | |||
// apply MD5 algo on a block | |||
void MD5::transform(const uint1 block[blocksize]) | |||
{ | |||
uint4 a = state[0], b = state[1], c = state[2], d = state[3], x[16]; | |||
decode (x, block, blocksize); | |||
/* Round 1 */ | |||
FF (a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */ | |||
FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */ | |||
FF (c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */ | |||
FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */ | |||
FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */ | |||
FF (d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */ | |||
FF (c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */ | |||
FF (b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */ | |||
FF (a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */ | |||
FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */ | |||
FF (c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */ | |||
FF (b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */ | |||
FF (a, b, c, d, x[12], S11, 0x6b901122); /* 13 */ | |||
FF (d, a, b, c, x[13], S12, 0xfd987193); /* 14 */ | |||
FF (c, d, a, b, x[14], S13, 0xa679438e); /* 15 */ | |||
FF (b, c, d, a, x[15], S14, 0x49b40821); /* 16 */ | |||
/* Round 2 */ | |||
GG (a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */ | |||
GG (d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */ | |||
GG (c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */ | |||
GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */ | |||
GG (a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */ | |||
GG (d, a, b, c, x[10], S22, 0x2441453); /* 22 */ | |||
GG (c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */ | |||
GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */ | |||
GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */ | |||
GG (d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */ | |||
GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */ | |||
GG (b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */ | |||
GG (a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */ | |||
GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */ | |||
GG (c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */ | |||
GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */ | |||
/* Round 3 */ | |||
HH (a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */ | |||
HH (d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */ | |||
HH (c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */ | |||
HH (b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */ | |||
HH (a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */ | |||
HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */ | |||
HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */ | |||
HH (b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */ | |||
HH (a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */ | |||
HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */ | |||
HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */ | |||
HH (b, c, d, a, x[ 6], S34, 0x4881d05); /* 44 */ | |||
HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */ | |||
HH (d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */ | |||
HH (c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */ | |||
HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */ | |||
/* Round 4 */ | |||
II (a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */ | |||
II (d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */ | |||
II (c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */ | |||
II (b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */ | |||
II (a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */ | |||
II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */ | |||
II (c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */ | |||
II (b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */ | |||
II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */ | |||
II (d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */ | |||
II (c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */ | |||
II (b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */ | |||
II (a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */ | |||
II (d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */ | |||
II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */ | |||
II (b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */ | |||
state[0] += a; | |||
state[1] += b; | |||
state[2] += c; | |||
state[3] += d; | |||
// Zeroize sensitive information. | |||
memset(x, 0, sizeof x); | |||
} | |||
////////////////////////////// | |||
// MD5 block update operation. Continues an MD5 message-digest | |||
// operation, processing another message block | |||
void MD5::update(const unsigned char input[], size_type length) | |||
{ | |||
// compute number of bytes mod 64 | |||
size_type index = count[0] / 8 % blocksize; | |||
// Update number of bits | |||
if ((count[0] += (length << 3)) < (length << 3)) | |||
count[1]++; | |||
count[1] += (length >> 29); | |||
// number of bytes we need to fill in buffer | |||
size_type firstpart = 64 - index; | |||
size_type i; | |||
// transform as many times as possible. | |||
if (length >= firstpart) | |||
{ | |||
// fill buffer first, transform | |||
memcpy(&buffer[index], input, firstpart); | |||
transform(buffer); | |||
// transform chunks of blocksize (64 bytes) | |||
for (i = firstpart; i + blocksize <= length; i += blocksize) | |||
transform(&input[i]); | |||
index = 0; | |||
} | |||
else | |||
i = 0; | |||
// buffer remaining input | |||
memcpy(&buffer[index], &input[i], length-i); | |||
} | |||
////////////////////////////// | |||
// for convenience provide a verson with signed char | |||
void MD5::update(const char input[], size_type length) | |||
{ | |||
update((const unsigned char*)input, length); | |||
} | |||
////////////////////////////// | |||
// MD5 finalization. Ends an MD5 message-digest operation, writing the | |||
// the message digest and zeroizing the context. | |||
MD5& MD5::finalize() | |||
{ | |||
static unsigned char padding[64] = { | |||
0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 | |||
}; | |||
if (!finalized) { | |||
// Save number of bits | |||
unsigned char bits[8]; | |||
encode(bits, count, 8); | |||
// pad out to 56 mod 64. | |||
size_type index = count[0] / 8 % 64; | |||
size_type padLen = (index < 56) ? (56 - index) : (120 - index); | |||
update(padding, padLen); | |||
// Append length (before padding) | |||
update(bits, 8); | |||
// Store state in digest | |||
encode(digest, state, 16); | |||
// Zeroize sensitive information. | |||
memset(buffer, 0, sizeof buffer); | |||
memset(count, 0, sizeof count); | |||
finalized=true; | |||
} | |||
return *this; | |||
} | |||
////////////////////////////// | |||
// return hex representation of digest as string | |||
std::string MD5::hexdigest() const | |||
{ | |||
if (!finalized) | |||
return ""; | |||
char buf[33]; | |||
for (int i=0; i<16; i++) | |||
sprintf(buf+i*2, "%02x", digest[i]); | |||
buf[32]=0; | |||
return std::string(buf); | |||
} | |||
std::string MD5::rawdigest() const{ | |||
return std::string((char *)digest, sizeof(digest)); | |||
} | |||
////////////////////////////// | |||
std::ostream& operator<<(std::ostream& out, MD5 md5) | |||
{ | |||
return out << md5.hexdigest(); | |||
} | |||
////////////////////////////// | |||
std::string md5(const std::string str) | |||
{ | |||
MD5 md5 = MD5(str); | |||
return md5.hexdigest(); | |||
} | |||
} /* namespace toolkit */ |
@@ -0,0 +1,96 @@ | |||
//MD5.cpp | |||
/* MD5 | |||
converted to C++ class by Frank Thilo (thilo@unix-ag.org) | |||
for bzflag (http://www.bzflag.org) | |||
based on: | |||
md5.h and md5.c | |||
reference implemantion of RFC 1321 | |||
Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All | |||
rights reserved. | |||
License to copy and use this software is granted provided that it | |||
is identified as the "RSA Data Security, Inc. MD5 Message-Digest | |||
Algorithm" in all material mentioning or referencing this software | |||
or this function. | |||
License is also granted to make and use derivative works provided | |||
that such works are identified as "derived from the RSA Data | |||
Security, Inc. MD5 Message-Digest Algorithm" in all material | |||
mentioning or referencing the derived work. | |||
RSA Data Security, Inc. makes no representations concerning either | |||
the merchantability of this software or the suitability of this | |||
software for any particular purpose. It is provided "as is" | |||
without express or implied warranty of any kind. | |||
These notices must be retained in any copies of any part of this | |||
documentation and/or software. | |||
*/ | |||
#ifndef SRC_UTIL_MD5_H_ | |||
#define SRC_UTIL_MD5_H_ | |||
#include <string> | |||
#include <iostream> | |||
namespace toolkit { | |||
// a small class for calculating MD5 hashes of strings or byte arrays | |||
// it is not meant to be fast or secure | |||
// | |||
// usage: 1) feed it blocks of uchars with update() | |||
// 2) finalize() | |||
// 3) get hexdigest() string | |||
// or | |||
// MD5(std::string).hexdigest() | |||
// | |||
// assumes that char is 8 bit and int is 32 bit | |||
class MD5 | |||
{ | |||
public: | |||
typedef unsigned int size_type; // must be 32bit | |||
MD5(); | |||
MD5(const std::string& text); | |||
void update(const unsigned char *buf, size_type length); | |||
void update(const char *buf, size_type length); | |||
MD5& finalize(); | |||
std::string hexdigest() const; | |||
std::string rawdigest() const; | |||
friend std::ostream& operator<<(std::ostream&, MD5 md5); | |||
private: | |||
void init(); | |||
typedef uint8_t uint1; // 8bit | |||
typedef uint32_t uint4; // 32bit | |||
enum {blocksize = 64}; // VC6 won't eat a const static int here | |||
void transform(const uint1 block[blocksize]); | |||
static void decode(uint4 output[], const uint1 input[], size_type len); | |||
static void encode(uint1 output[], const uint4 input[], size_type len); | |||
bool finalized; | |||
uint1 buffer[blocksize]; // bytes that didn't fit in last 64 byte chunk | |||
uint4 count[2]; // 64bit counter for number of bits (lo, hi) | |||
uint4 state[4]; // digest so far | |||
uint1 digest[16]; // the result | |||
// low level logic operations | |||
static inline uint4 F(uint4 x, uint4 y, uint4 z); | |||
static inline uint4 G(uint4 x, uint4 y, uint4 z); | |||
static inline uint4 H(uint4 x, uint4 y, uint4 z); | |||
static inline uint4 I(uint4 x, uint4 y, uint4 z); | |||
static inline uint4 rotate_left(uint4 x, int n); | |||
static inline void FF(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac); | |||
static inline void GG(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac); | |||
static inline void HH(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac); | |||
static inline void II(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac); | |||
}; | |||
} /* namespace toolkit */ | |||
#endif /* SRC_UTIL_MD5_H_ */ |
@@ -0,0 +1,19 @@ | |||
/* | |||
* 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 "util.h" | |||
#include "NoticeCenter.h" | |||
namespace toolkit { | |||
INSTANCE_IMP(NoticeCenter) | |||
} /* namespace toolkit */ | |||
@@ -0,0 +1,173 @@ | |||
/* | |||
* 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_NOTICECENTER_H_ | |||
#define SRC_UTIL_NOTICECENTER_H_ | |||
#include <mutex> | |||
#include <memory> | |||
#include <string> | |||
#include <exception> | |||
#include <functional> | |||
#include <unordered_map> | |||
#include "function_traits.h" | |||
namespace toolkit { | |||
class EventDispatcher { | |||
public: | |||
friend class NoticeCenter; | |||
using Ptr = std::shared_ptr<EventDispatcher>; | |||
~EventDispatcher() = default; | |||
private: | |||
using MapType = std::unordered_multimap<void *, std::shared_ptr<void> >; | |||
EventDispatcher() = default; | |||
class InterruptException : public std::runtime_error { | |||
public: | |||
InterruptException() : std::runtime_error("InterruptException") {} | |||
~InterruptException() {} | |||
}; | |||
template<typename ...ArgsType> | |||
int emitEvent(ArgsType &&...args) { | |||
using funType = std::function<void(decltype(std::forward<ArgsType>(args))...)>; | |||
decltype(_mapListener) copy; | |||
{ | |||
//先拷贝(开销比较小),目的是防止在触发回调时还是上锁状态从而导致交叉互锁 | |||
std::lock_guard<std::recursive_mutex> lck(_mtxListener); | |||
copy = _mapListener; | |||
} | |||
int ret = 0; | |||
for (auto &pr : copy) { | |||
funType *obj = (funType *) (pr.second.get()); | |||
try { | |||
(*obj)(std::forward<ArgsType>(args)...); | |||
++ret; | |||
} catch (InterruptException &) { | |||
++ret; | |||
break; | |||
} | |||
} | |||
return ret; | |||
} | |||
template<typename FUNC> | |||
void addListener(void *tag, FUNC &&func) { | |||
using funType = typename function_traits<typename std::remove_reference<FUNC>::type>::stl_function_type; | |||
std::shared_ptr<void> pListener(new funType(std::forward<FUNC>(func)), [](void *ptr) { | |||
funType *obj = (funType *) ptr; | |||
delete obj; | |||
}); | |||
std::lock_guard<std::recursive_mutex> lck(_mtxListener); | |||
_mapListener.emplace(tag, pListener); | |||
} | |||
void delListener(void *tag, bool &empty) { | |||
std::lock_guard<std::recursive_mutex> lck(_mtxListener); | |||
_mapListener.erase(tag); | |||
empty = _mapListener.empty(); | |||
} | |||
private: | |||
std::recursive_mutex _mtxListener; | |||
MapType _mapListener; | |||
}; | |||
class NoticeCenter : public std::enable_shared_from_this<NoticeCenter> { | |||
public: | |||
using Ptr = std::shared_ptr<NoticeCenter>; | |||
static NoticeCenter &Instance(); | |||
template<typename ...ArgsType> | |||
int emitEvent(const std::string &strEvent, ArgsType &&...args) { | |||
auto dispatcher = getDispatcher(strEvent); | |||
if (!dispatcher) { | |||
//该事件无人监听 | |||
return 0; | |||
} | |||
return dispatcher->emitEvent(std::forward<ArgsType>(args)...); | |||
} | |||
template<typename FUNC> | |||
void addListener(void *tag, const std::string &event, FUNC &&func) { | |||
getDispatcher(event, true)->addListener(tag, std::forward<FUNC>(func)); | |||
} | |||
void delListener(void *tag, const std::string &event) { | |||
auto dispatcher = getDispatcher(event); | |||
if (!dispatcher) { | |||
//不存在该事件 | |||
return; | |||
} | |||
bool empty; | |||
dispatcher->delListener(tag, empty); | |||
if (empty) { | |||
delDispatcher(event, dispatcher); | |||
} | |||
} | |||
//这个方法性能比较差 | |||
void delListener(void *tag) { | |||
std::lock_guard<std::recursive_mutex> lck(_mtxListener); | |||
bool empty; | |||
for (auto it = _mapListener.begin(); it != _mapListener.end();) { | |||
it->second->delListener(tag, empty); | |||
if (empty) { | |||
it = _mapListener.erase(it); | |||
continue; | |||
} | |||
++it; | |||
} | |||
} | |||
void clearAll() { | |||
std::lock_guard<std::recursive_mutex> lck(_mtxListener); | |||
_mapListener.clear(); | |||
} | |||
private: | |||
EventDispatcher::Ptr getDispatcher(const std::string &event, bool create = false) { | |||
std::lock_guard<std::recursive_mutex> lck(_mtxListener); | |||
auto it = _mapListener.find(event); | |||
if (it != _mapListener.end()) { | |||
return it->second; | |||
} | |||
if (create) { | |||
//如果为空则创建一个 | |||
EventDispatcher::Ptr dispatcher(new EventDispatcher()); | |||
_mapListener.emplace(event, dispatcher); | |||
return dispatcher; | |||
} | |||
return nullptr; | |||
} | |||
void delDispatcher(const std::string &event, const EventDispatcher::Ptr &dispatcher) { | |||
std::lock_guard<std::recursive_mutex> lck(_mtxListener); | |||
auto it = _mapListener.find(event); | |||
if (it != _mapListener.end() && dispatcher == it->second) { | |||
//两者相同则删除 | |||
_mapListener.erase(it); | |||
} | |||
} | |||
private: | |||
std::recursive_mutex _mtxListener; | |||
std::unordered_map<std::string, EventDispatcher::Ptr> _mapListener; | |||
}; | |||
} /* namespace toolkit */ | |||
#endif /* SRC_UTIL_NOTICECENTER_H_ */ |
@@ -0,0 +1,205 @@ | |||
/* | |||
* 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_RECYCLEPOOL_H_ | |||
#define UTIL_RECYCLEPOOL_H_ | |||
#include "List.h" | |||
#include <atomic> | |||
#include <deque> | |||
#include <functional> | |||
#include <memory> | |||
#include <mutex> | |||
#include <unordered_set> | |||
namespace toolkit { | |||
#if (defined(__GNUC__) && (__GNUC__ >= 5 || (__GNUC__ >= 4 && __GNUC_MINOR__ >= 9))) || defined(__clang__) \ | |||
|| !defined(__GNUC__) | |||
#define SUPPORT_DYNAMIC_TEMPLATE | |||
#endif | |||
template <typename C> | |||
class ResourcePool_l; | |||
template <typename C> | |||
class ResourcePool; | |||
template <typename C> | |||
class shared_ptr_imp : public std::shared_ptr<C> { | |||
public: | |||
shared_ptr_imp() {} | |||
/** | |||
* 构造智能指针 | |||
* @param ptr 裸指针 | |||
* @param weakPool 管理本指针的循环池 | |||
* @param quit 对接是否放弃循环使用 | |||
*/ | |||
shared_ptr_imp( | |||
C *ptr, const std::weak_ptr<ResourcePool_l<C>> &weakPool, std::shared_ptr<std::atomic_bool> quit, | |||
const std::function<void(C *)> &on_recycle); | |||
/** | |||
* 放弃或恢复回到循环池继续使用 | |||
* @param flag | |||
*/ | |||
void quit(bool flag = true) { | |||
if (_quit) { | |||
*_quit = flag; | |||
} | |||
} | |||
private: | |||
std::shared_ptr<std::atomic_bool> _quit; | |||
}; | |||
template <typename C> | |||
class ResourcePool_l : public std::enable_shared_from_this<ResourcePool_l<C>> { | |||
public: | |||
using ValuePtr = shared_ptr_imp<C>; | |||
friend class shared_ptr_imp<C>; | |||
friend class ResourcePool<C>; | |||
ResourcePool_l() { | |||
_alloc = []() -> C * { return new C(); }; | |||
} | |||
#if defined(SUPPORT_DYNAMIC_TEMPLATE) | |||
template <typename... ArgTypes> | |||
ResourcePool_l(ArgTypes &&...args) { | |||
_alloc = [args...]() -> C * { return new C(args...); }; | |||
} | |||
#endif // defined(SUPPORT_DYNAMIC_TEMPLATE) | |||
~ResourcePool_l() { | |||
for (auto ptr : _objs) { | |||
delete ptr; | |||
} | |||
} | |||
void setSize(size_t size) { | |||
_pool_size = size; | |||
_objs.reserve(size); | |||
} | |||
ValuePtr obtain(const std::function<void(C *)> &on_recycle = nullptr) { | |||
return ValuePtr(getPtr(), _weak_self, std::make_shared<std::atomic_bool>(false), on_recycle); | |||
} | |||
std::shared_ptr<C> obtain2() { | |||
auto weak_self = _weak_self; | |||
return std::shared_ptr<C>(getPtr(), [weak_self](C *ptr) { | |||
auto strongPool = weak_self.lock(); | |||
if (strongPool) { | |||
//放入循环池 | |||
strongPool->recycle(ptr); | |||
} else { | |||
delete ptr; | |||
} | |||
}); | |||
} | |||
private: | |||
void recycle(C *obj) { | |||
auto is_busy = _busy.test_and_set(); | |||
if (!is_busy) { | |||
//获取到锁 | |||
if (_objs.size() >= _pool_size) { | |||
delete obj; | |||
} else { | |||
_objs.emplace_back(obj); | |||
} | |||
_busy.clear(); | |||
} else { | |||
//未获取到锁 | |||
delete obj; | |||
} | |||
} | |||
C *getPtr() { | |||
C *ptr; | |||
auto is_busy = _busy.test_and_set(); | |||
if (!is_busy) { | |||
//获取到锁 | |||
if (_objs.size() == 0) { | |||
ptr = _alloc(); | |||
} else { | |||
ptr = _objs.back(); | |||
_objs.pop_back(); | |||
} | |||
_busy.clear(); | |||
} else { | |||
//未获取到锁 | |||
ptr = _alloc(); | |||
} | |||
return ptr; | |||
} | |||
void setup() { _weak_self = this->shared_from_this(); } | |||
private: | |||
size_t _pool_size = 8; | |||
std::vector<C *> _objs; | |||
std::function<C *(void)> _alloc; | |||
std::atomic_flag _busy { false }; | |||
std::weak_ptr<ResourcePool_l> _weak_self; | |||
}; | |||
/** | |||
* 循环池,注意,循环池里面的对象不能继承enable_shared_from_this! | |||
* @tparam C | |||
*/ | |||
template <typename C> | |||
class ResourcePool { | |||
public: | |||
using ValuePtr = shared_ptr_imp<C>; | |||
ResourcePool() { | |||
pool.reset(new ResourcePool_l<C>()); | |||
pool->setup(); | |||
} | |||
#if defined(SUPPORT_DYNAMIC_TEMPLATE) | |||
template <typename... ArgTypes> | |||
ResourcePool(ArgTypes &&...args) { | |||
pool = std::make_shared<ResourcePool_l<C>>(std::forward<ArgTypes>(args)...); | |||
pool->setup(); | |||
} | |||
#endif // defined(SUPPORT_DYNAMIC_TEMPLATE) | |||
void setSize(size_t size) { pool->setSize(size); } | |||
//获取一个对象,性能差些,但是功能丰富些 | |||
ValuePtr obtain(const std::function<void(C *)> &on_recycle = nullptr) { return pool->obtain(on_recycle); } | |||
//获取一个对象,性能好些 | |||
std::shared_ptr<C> obtain2() { return pool->obtain2(); } | |||
private: | |||
std::shared_ptr<ResourcePool_l<C>> pool; | |||
}; | |||
template<typename C> | |||
shared_ptr_imp<C>::shared_ptr_imp(C *ptr, | |||
const std::weak_ptr<ResourcePool_l<C> > &weakPool, | |||
std::shared_ptr<std::atomic_bool> quit, | |||
const std::function<void(C *)> &on_recycle) : | |||
std::shared_ptr<C>(ptr, [weakPool, quit, on_recycle](C *ptr) { | |||
if (on_recycle) { | |||
on_recycle(ptr); | |||
} | |||
auto strongPool = weakPool.lock(); | |||
if (strongPool && !(*quit)) { | |||
//循环池还在并且不放弃放入循环池 | |||
strongPool->recycle(ptr); | |||
} else { | |||
delete ptr; | |||
} | |||
}), _quit(std::move(quit)) {} | |||
} /* namespace toolkit */ | |||
#endif /* UTIL_RECYCLEPOOL_H_ */ |
@@ -0,0 +1,438 @@ | |||
/* | |||
* 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_RINGBUFFER_H_ | |||
#define UTIL_RINGBUFFER_H_ | |||
#include <assert.h> | |||
#include <atomic> | |||
#include <memory> | |||
#include <mutex> | |||
#include <unordered_map> | |||
#include <condition_variable> | |||
#include <functional> | |||
#include "List.h" | |||
#include "Poller/EventPoller.h" | |||
// GOP缓存最大长度下限值 | |||
#define RING_MIN_SIZE 32 | |||
#define LOCK_GUARD(mtx) std::lock_guard<decltype(mtx)> lck(mtx) | |||
namespace toolkit { | |||
using ReaderInfo = std::shared_ptr<void>; | |||
template <typename T> | |||
class RingDelegate { | |||
public: | |||
using Ptr = std::shared_ptr<RingDelegate>; | |||
RingDelegate() = default; | |||
virtual ~RingDelegate() = default; | |||
virtual void onWrite(T in, bool is_key = true) = 0; | |||
}; | |||
template <typename T> | |||
class _RingStorage; | |||
template <typename T> | |||
class _RingReaderDispatcher; | |||
/** | |||
* 环形缓存读取器 | |||
* 该对象的事件触发都会在绑定的poller线程中执行 | |||
* 所以把锁去掉了 | |||
* 对该对象的一切操作都应该在poller线程中执行 | |||
*/ | |||
template <typename T> | |||
class _RingReader { | |||
public: | |||
using Ptr = std::shared_ptr<_RingReader>; | |||
friend class _RingReaderDispatcher<T>; | |||
_RingReader(std::shared_ptr<_RingStorage<T>> storage) { _storage = std::move(storage); } | |||
~_RingReader() = default; | |||
void setReadCB(std::function<void(const T &)> cb) { | |||
if (!cb) { | |||
_read_cb = [](const T &) {}; | |||
} else { | |||
_read_cb = std::move(cb); | |||
flushGop(); | |||
} | |||
} | |||
void setDetachCB(std::function<void()> cb) { | |||
_detach_cb = cb ? std::move(cb) : []() {}; | |||
} | |||
void setGetInfoCB(std::function<ReaderInfo()> cb) { | |||
_get_info = cb ? std::move(cb) : []() { return ReaderInfo(); }; | |||
} | |||
private: | |||
void onRead(const T &data, bool /*is_key*/) { _read_cb(data); } | |||
void onDetach() const { _detach_cb(); } | |||
void flushGop() { | |||
if (!_storage) { | |||
return; | |||
} | |||
_storage->getCache().for_each([this](const List<std::pair<bool, T>> &lst) { | |||
lst.for_each([this](const std::pair<bool, T> &pr) { onRead(pr.second, pr.first); }); | |||
}); | |||
} | |||
ReaderInfo getInfo() { return _get_info(); } | |||
private: | |||
std::shared_ptr<_RingStorage<T>> _storage; | |||
std::function<void(void)> _detach_cb = []() {}; | |||
std::function<void(const T &)> _read_cb = [](const T &) {}; | |||
std::function<ReaderInfo()> _get_info = []() { return ReaderInfo(); }; | |||
}; | |||
template <typename T> | |||
class _RingStorage { | |||
public: | |||
using Ptr = std::shared_ptr<_RingStorage>; | |||
using GopType = List<List<std::pair<bool, T>>>; | |||
_RingStorage(size_t max_size, size_t max_gop_size) { | |||
// gop缓存个数不能小于32 | |||
if (max_size < RING_MIN_SIZE) { | |||
max_size = RING_MIN_SIZE; | |||
} | |||
_max_size = max_size; | |||
_max_gop_size = max_gop_size; | |||
clearCache(); | |||
} | |||
~_RingStorage() = default; | |||
/** | |||
* 写入环形缓存数据 | |||
* @param in 数据 | |||
* @param is_key 是否为关键帧 | |||
* @return 是否触发重置环形缓存大小 | |||
*/ | |||
void write(T in, bool is_key = true) { | |||
if (is_key) { | |||
_have_idr = true; | |||
_started = true; | |||
if (!_data_cache.back().empty()) { | |||
//当前gop列队还没收到任意缓存 | |||
_data_cache.emplace_back(); | |||
} | |||
if (_data_cache.size() > _max_gop_size) { | |||
// GOP个数超过限制,那么移除最早的GOP | |||
popFrontGop(); | |||
} | |||
} | |||
if (!_have_idr && _started) { | |||
//缓存中没有关键帧,那么gop缓存无效 | |||
return; | |||
} | |||
_data_cache.back().emplace_back(std::make_pair(is_key, std::move(in))); | |||
if (++_size > _max_size) { | |||
// GOP缓存溢出 | |||
while (_data_cache.size() > 1) { | |||
//先尝试清除老的GOP缓存 | |||
popFrontGop(); | |||
} | |||
if (_size > _max_size) { | |||
//还是大于最大缓冲限制,那么清空所有GOP | |||
clearCache(); | |||
} | |||
} | |||
} | |||
Ptr clone() const { | |||
Ptr ret(new _RingStorage()); | |||
ret->_size = _size; | |||
ret->_have_idr = _have_idr; | |||
ret->_started = _started; | |||
ret->_max_size = _max_size; | |||
ret->_max_gop_size = _max_gop_size; | |||
ret->_data_cache = _data_cache; | |||
return ret; | |||
} | |||
const GopType &getCache() const { return _data_cache; } | |||
void clearCache() { | |||
_size = 0; | |||
_have_idr = false; | |||
_data_cache.clear(); | |||
_data_cache.emplace_back(); | |||
} | |||
private: | |||
_RingStorage() = default; | |||
void popFrontGop() { | |||
if (!_data_cache.empty()) { | |||
_size -= _data_cache.front().size(); | |||
_data_cache.pop_front(); | |||
if (_data_cache.empty()) { | |||
_data_cache.emplace_back(); | |||
} | |||
} | |||
} | |||
private: | |||
bool _started = false; | |||
bool _have_idr; | |||
size_t _size; | |||
size_t _max_size; | |||
size_t _max_gop_size; | |||
GopType _data_cache; | |||
}; | |||
template <typename T> | |||
class RingBuffer; | |||
/** | |||
* 环形缓存事件派发器,只能一个poller线程操作它 | |||
* @tparam T | |||
*/ | |||
template <typename T> | |||
class _RingReaderDispatcher : public std::enable_shared_from_this<_RingReaderDispatcher<T>> { | |||
public: | |||
using Ptr = std::shared_ptr<_RingReaderDispatcher>; | |||
using RingReader = _RingReader<T>; | |||
using RingStorage = _RingStorage<T>; | |||
using onChangeInfoCB = std::function<ReaderInfo(ReaderInfo &&info)>; | |||
friend class RingBuffer<T>; | |||
~_RingReaderDispatcher() { | |||
decltype(_reader_map) reader_map; | |||
reader_map.swap(_reader_map); | |||
for (auto &pr : reader_map) { | |||
auto reader = pr.second.lock(); | |||
if (reader) { | |||
reader->onDetach(); | |||
} | |||
} | |||
} | |||
private: | |||
_RingReaderDispatcher( | |||
const typename RingStorage::Ptr &storage, std::function<void(int, bool)> onSizeChanged) { | |||
_reader_size = 0; | |||
_storage = storage; | |||
_on_size_changed = std::move(onSizeChanged); | |||
assert(_on_size_changed); | |||
} | |||
void write(T in, bool is_key = true) { | |||
for (auto it = _reader_map.begin(); it != _reader_map.end();) { | |||
auto reader = it->second.lock(); | |||
if (!reader) { | |||
it = _reader_map.erase(it); | |||
--_reader_size; | |||
onSizeChanged(false); | |||
continue; | |||
} | |||
reader->onRead(in, is_key); | |||
++it; | |||
} | |||
_storage->write(std::move(in), is_key); | |||
} | |||
std::shared_ptr<RingReader> attach(const EventPoller::Ptr &poller, bool use_cache) { | |||
if (!poller->isCurrentThread()) { | |||
throw std::runtime_error("You can attach RingBuffer only in it's poller thread"); | |||
} | |||
std::weak_ptr<_RingReaderDispatcher> weak_self = this->shared_from_this(); | |||
auto on_dealloc = [weak_self, poller](RingReader *ptr) { | |||
poller->async([weak_self, ptr]() { | |||
auto strong_self = weak_self.lock(); | |||
if (strong_self && strong_self->_reader_map.erase(ptr)) { | |||
--strong_self->_reader_size; | |||
strong_self->onSizeChanged(false); | |||
} | |||
delete ptr; | |||
}); | |||
}; | |||
std::shared_ptr<RingReader> reader(new RingReader(use_cache ? _storage : nullptr), on_dealloc); | |||
_reader_map[reader.get()] = reader; | |||
++_reader_size; | |||
onSizeChanged(true); | |||
return reader; | |||
} | |||
void onSizeChanged(bool add_flag) { _on_size_changed(_reader_size, add_flag); } | |||
void clearCache() { | |||
if (_reader_size == 0) { | |||
_storage->clearCache(); | |||
} | |||
} | |||
std::list<ReaderInfo> getInfoList(const onChangeInfoCB &on_change) { | |||
std::list<ReaderInfo> ret; | |||
for (auto &pr : _reader_map) { | |||
auto reader = pr.second.lock(); | |||
if (!reader) { | |||
continue; | |||
} | |||
auto info = reader->getInfo(); | |||
if (!info) { | |||
continue; | |||
} | |||
ret.emplace_back(on_change(std::move(info))); | |||
} | |||
return ret; | |||
} | |||
private: | |||
std::atomic_int _reader_size; | |||
std::function<void(int, bool)> _on_size_changed; | |||
typename RingStorage::Ptr _storage; | |||
std::unordered_map<void *, std::weak_ptr<RingReader>> _reader_map; | |||
}; | |||
template <typename T> | |||
class RingBuffer : public std::enable_shared_from_this<RingBuffer<T>> { | |||
public: | |||
using Ptr = std::shared_ptr<RingBuffer>; | |||
using RingReader = _RingReader<T>; | |||
using RingStorage = _RingStorage<T>; | |||
using RingReaderDispatcher = _RingReaderDispatcher<T>; | |||
using onReaderChanged = std::function<void(int size)>; | |||
using onGetInfoCB = std::function<void(std::list<ReaderInfo> &info_list)>; | |||
RingBuffer(size_t max_size = 1024, onReaderChanged cb = nullptr, size_t max_gop_size = 1) { | |||
_storage = std::make_shared<RingStorage>(max_size, max_gop_size); | |||
_on_reader_changed = cb ? std::move(cb) : [](int size) {}; | |||
//先触发无人观看 | |||
_on_reader_changed(0); | |||
} | |||
~RingBuffer() = default; | |||
void write(T in, bool is_key = true) { | |||
if (_delegate) { | |||
_delegate->onWrite(std::move(in), is_key); | |||
return; | |||
} | |||
LOCK_GUARD(_mtx_map); | |||
for (auto &pr : _dispatcher_map) { | |||
auto &second = pr.second; | |||
//切换线程后触发onRead事件 | |||
pr.first->async([second, in, is_key]() { second->write(std::move(const_cast<T &>(in)), is_key); }, false); | |||
} | |||
_storage->write(std::move(in), is_key); | |||
} | |||
void setDelegate(const typename RingDelegate<T>::Ptr &delegate) { _delegate = delegate; } | |||
std::shared_ptr<RingReader> attach(const EventPoller::Ptr &poller, bool use_cache = true) { | |||
typename RingReaderDispatcher::Ptr dispatcher; | |||
{ | |||
LOCK_GUARD(_mtx_map); | |||
auto &ref = _dispatcher_map[poller]; | |||
if (!ref) { | |||
std::weak_ptr<RingBuffer> weak_self = this->shared_from_this(); | |||
auto onSizeChanged = [weak_self, poller](int size, bool add_flag) { | |||
if (auto strong_self = weak_self.lock()) { | |||
strong_self->onSizeChanged(poller, size, add_flag); | |||
} | |||
}; | |||
auto onDealloc = [poller](RingReaderDispatcher *ptr) { poller->async([ptr]() { delete ptr; }); }; | |||
ref.reset(new RingReaderDispatcher(_storage->clone(), std::move(onSizeChanged)), std::move(onDealloc)); | |||
} | |||
dispatcher = ref; | |||
} | |||
return dispatcher->attach(poller, use_cache); | |||
} | |||
int readerCount() { return _total_count; } | |||
void clearCache() { | |||
LOCK_GUARD(_mtx_map); | |||
_storage->clearCache(); | |||
for (auto &pr : _dispatcher_map) { | |||
auto &second = pr.second; | |||
//切换线程后清空缓存 | |||
pr.first->async([second]() { second->clearCache(); }, false); | |||
} | |||
} | |||
void getInfoList(const onGetInfoCB &cb, const typename RingReaderDispatcher::onChangeInfoCB &on_change = nullptr) { | |||
if (!cb) { | |||
return; | |||
} | |||
if (!on_change) { | |||
const_cast<typename RingReaderDispatcher::onChangeInfoCB &>(on_change) = [](ReaderInfo &&info) { return std::move(info); }; | |||
} | |||
LOCK_GUARD(_mtx_map); | |||
auto info_vec = std::make_shared<std::vector<std::list<ReaderInfo>>>(); | |||
// 1、最少确保一个元素 | |||
info_vec->resize(_dispatcher_map.empty() ? 1 : _dispatcher_map.size()); | |||
std::shared_ptr<void> on_finished(nullptr, [cb, info_vec](void *) mutable { | |||
// 2、防止这里为空 | |||
auto &lst = *info_vec->begin(); | |||
for (auto &item : *info_vec) { | |||
if (&lst != &item) { | |||
lst.insert(lst.end(), item.begin(), item.end()); | |||
} | |||
} | |||
cb(lst); | |||
}); | |||
auto i = 0U; | |||
for (auto &pr : _dispatcher_map) { | |||
auto &second = pr.second; | |||
pr.first->async([second, info_vec, on_finished, i, on_change]() { (*info_vec)[i] = second->getInfoList(on_change); }); | |||
++i; | |||
} | |||
} | |||
private: | |||
void onSizeChanged(const EventPoller::Ptr &poller, int size, bool add_flag) { | |||
if (size == 0) { | |||
LOCK_GUARD(_mtx_map); | |||
_dispatcher_map.erase(poller); | |||
} | |||
if (add_flag) { | |||
++_total_count; | |||
} else { | |||
--_total_count; | |||
} | |||
_on_reader_changed(_total_count); | |||
} | |||
private: | |||
struct HashOfPtr { | |||
std::size_t operator()(const EventPoller::Ptr &key) const { return (std::size_t)key.get(); } | |||
}; | |||
private: | |||
std::mutex _mtx_map; | |||
std::atomic_int _total_count { 0 }; | |||
typename RingStorage::Ptr _storage; | |||
typename RingDelegate<T>::Ptr _delegate; | |||
onReaderChanged _on_reader_changed; | |||
std::unordered_map<EventPoller::Ptr, typename RingReaderDispatcher::Ptr, HashOfPtr> _dispatcher_map; | |||
}; | |||
} /* namespace toolkit */ | |||
#endif /* UTIL_RINGBUFFER_H_ */ |
@@ -0,0 +1,341 @@ | |||
// 100% Public Domain. | |||
// | |||
// Original C Code | |||
// -- Steve Reid <steve@edmweb.com> | |||
// Small changes to fit into bglibs | |||
// -- Bruce Guenter <bruce@untroubled.org> | |||
// Translation to simpler C++ Code | |||
// -- Volker Grabsch <vog@notjusthosting.com> | |||
// Safety fixes | |||
// -- Eugene Hopkinson <slowriot at voxelstorm dot com> | |||
// Adapt for project | |||
// Dmitriy Khaustov <khaustov.dm@gmail.com> | |||
// | |||
// File created on: 2017.02.25 | |||
// SHA1.cpp | |||
#include "SHA1.h" | |||
#include <sstream> | |||
#include <iomanip> | |||
#include <fstream> | |||
namespace toolkit { | |||
static const size_t BLOCK_INTS = 16; /* number of 32bit integers per SHA1 block */ | |||
static const size_t BLOCK_BYTES = BLOCK_INTS * 4; | |||
static void reset(uint32_t digest[], std::string &buffer, uint64_t &transforms) | |||
{ | |||
/* SHA1 initialization constants */ | |||
digest[0] = 0x67452301; | |||
digest[1] = 0xefcdab89; | |||
digest[2] = 0x98badcfe; | |||
digest[3] = 0x10325476; | |||
digest[4] = 0xc3d2e1f0; | |||
/* Reset counters */ | |||
buffer = ""; | |||
transforms = 0; | |||
} | |||
static uint32_t rol(const uint32_t value, const size_t bits) | |||
{ | |||
return (value << bits) | (value >> (32 - bits)); | |||
} | |||
static uint32_t blk(const uint32_t block[BLOCK_INTS], const size_t i) | |||
{ | |||
return rol(block[(i+13)&15] ^ block[(i+8)&15] ^ block[(i+2)&15] ^ block[i], 1); | |||
} | |||
/* | |||
* (R0+R1), R2, R3, R4 are the different operations used in SHA1 | |||
*/ | |||
static void R0(const uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i) | |||
{ | |||
z += ((w&(x^y))^y) + block[i] + 0x5a827999 + rol(v, 5); | |||
w = rol(w, 30); | |||
} | |||
static void R1(uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i) | |||
{ | |||
block[i] = blk(block, i); | |||
z += ((w&(x^y))^y) + block[i] + 0x5a827999 + rol(v, 5); | |||
w = rol(w, 30); | |||
} | |||
static void R2(uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i) | |||
{ | |||
block[i] = blk(block, i); | |||
z += (w^x^y) + block[i] + 0x6ed9eba1 + rol(v, 5); | |||
w = rol(w, 30); | |||
} | |||
static void R3(uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i) | |||
{ | |||
block[i] = blk(block, i); | |||
z += (((w|x)&y)|(w&x)) + block[i] + 0x8f1bbcdc + rol(v, 5); | |||
w = rol(w, 30); | |||
} | |||
static void R4(uint32_t block[BLOCK_INTS], const uint32_t v, uint32_t &w, const uint32_t x, const uint32_t y, uint32_t &z, const size_t i) | |||
{ | |||
block[i] = blk(block, i); | |||
z += (w^x^y) + block[i] + 0xca62c1d6 + rol(v, 5); | |||
w = rol(w, 30); | |||
} | |||
/* | |||
* Hash a single 512-bit block. This is the core of the algorithm. | |||
*/ | |||
static void transform(uint32_t digest[], uint32_t block[BLOCK_INTS], uint64_t &transforms) | |||
{ | |||
/* Copy digest[] to working vars */ | |||
uint32_t a = digest[0]; | |||
uint32_t b = digest[1]; | |||
uint32_t c = digest[2]; | |||
uint32_t d = digest[3]; | |||
uint32_t e = digest[4]; | |||
/* 4 rounds of 20 operations each. Loop unrolled. */ | |||
R0(block, a, b, c, d, e, 0); | |||
R0(block, e, a, b, c, d, 1); | |||
R0(block, d, e, a, b, c, 2); | |||
R0(block, c, d, e, a, b, 3); | |||
R0(block, b, c, d, e, a, 4); | |||
R0(block, a, b, c, d, e, 5); | |||
R0(block, e, a, b, c, d, 6); | |||
R0(block, d, e, a, b, c, 7); | |||
R0(block, c, d, e, a, b, 8); | |||
R0(block, b, c, d, e, a, 9); | |||
R0(block, a, b, c, d, e, 10); | |||
R0(block, e, a, b, c, d, 11); | |||
R0(block, d, e, a, b, c, 12); | |||
R0(block, c, d, e, a, b, 13); | |||
R0(block, b, c, d, e, a, 14); | |||
R0(block, a, b, c, d, e, 15); | |||
R1(block, e, a, b, c, d, 0); | |||
R1(block, d, e, a, b, c, 1); | |||
R1(block, c, d, e, a, b, 2); | |||
R1(block, b, c, d, e, a, 3); | |||
R2(block, a, b, c, d, e, 4); | |||
R2(block, e, a, b, c, d, 5); | |||
R2(block, d, e, a, b, c, 6); | |||
R2(block, c, d, e, a, b, 7); | |||
R2(block, b, c, d, e, a, 8); | |||
R2(block, a, b, c, d, e, 9); | |||
R2(block, e, a, b, c, d, 10); | |||
R2(block, d, e, a, b, c, 11); | |||
R2(block, c, d, e, a, b, 12); | |||
R2(block, b, c, d, e, a, 13); | |||
R2(block, a, b, c, d, e, 14); | |||
R2(block, e, a, b, c, d, 15); | |||
R2(block, d, e, a, b, c, 0); | |||
R2(block, c, d, e, a, b, 1); | |||
R2(block, b, c, d, e, a, 2); | |||
R2(block, a, b, c, d, e, 3); | |||
R2(block, e, a, b, c, d, 4); | |||
R2(block, d, e, a, b, c, 5); | |||
R2(block, c, d, e, a, b, 6); | |||
R2(block, b, c, d, e, a, 7); | |||
R3(block, a, b, c, d, e, 8); | |||
R3(block, e, a, b, c, d, 9); | |||
R3(block, d, e, a, b, c, 10); | |||
R3(block, c, d, e, a, b, 11); | |||
R3(block, b, c, d, e, a, 12); | |||
R3(block, a, b, c, d, e, 13); | |||
R3(block, e, a, b, c, d, 14); | |||
R3(block, d, e, a, b, c, 15); | |||
R3(block, c, d, e, a, b, 0); | |||
R3(block, b, c, d, e, a, 1); | |||
R3(block, a, b, c, d, e, 2); | |||
R3(block, e, a, b, c, d, 3); | |||
R3(block, d, e, a, b, c, 4); | |||
R3(block, c, d, e, a, b, 5); | |||
R3(block, b, c, d, e, a, 6); | |||
R3(block, a, b, c, d, e, 7); | |||
R3(block, e, a, b, c, d, 8); | |||
R3(block, d, e, a, b, c, 9); | |||
R3(block, c, d, e, a, b, 10); | |||
R3(block, b, c, d, e, a, 11); | |||
R4(block, a, b, c, d, e, 12); | |||
R4(block, e, a, b, c, d, 13); | |||
R4(block, d, e, a, b, c, 14); | |||
R4(block, c, d, e, a, b, 15); | |||
R4(block, b, c, d, e, a, 0); | |||
R4(block, a, b, c, d, e, 1); | |||
R4(block, e, a, b, c, d, 2); | |||
R4(block, d, e, a, b, c, 3); | |||
R4(block, c, d, e, a, b, 4); | |||
R4(block, b, c, d, e, a, 5); | |||
R4(block, a, b, c, d, e, 6); | |||
R4(block, e, a, b, c, d, 7); | |||
R4(block, d, e, a, b, c, 8); | |||
R4(block, c, d, e, a, b, 9); | |||
R4(block, b, c, d, e, a, 10); | |||
R4(block, a, b, c, d, e, 11); | |||
R4(block, e, a, b, c, d, 12); | |||
R4(block, d, e, a, b, c, 13); | |||
R4(block, c, d, e, a, b, 14); | |||
R4(block, b, c, d, e, a, 15); | |||
/* Add the working vars back into digest[] */ | |||
digest[0] += a; | |||
digest[1] += b; | |||
digest[2] += c; | |||
digest[3] += d; | |||
digest[4] += e; | |||
/* Count the number of transformations */ | |||
transforms++; | |||
} | |||
static void buffer_to_block(const std::string &buffer, uint32_t block[BLOCK_INTS]) | |||
{ | |||
/* Convert the std::string (byte buffer) to a uint32_t array (MSB) */ | |||
for (size_t i = 0; i < BLOCK_INTS; i++) | |||
{ | |||
block[i] = | |||
(buffer[4*i+3] & 0xFF) | |||
| (buffer[4*i+2] & 0xFF) << 8 | |||
| (buffer[4*i+1] & 0xff) << 16 | |||
| (buffer[4*i+0] & 0xff) << 24; | |||
} | |||
} | |||
SHA1::SHA1() | |||
{ | |||
reset(digest, buffer, transforms); | |||
} | |||
void SHA1::update(const std::string &s) | |||
{ | |||
std::istringstream is(s); | |||
update(is); | |||
} | |||
void SHA1::update(std::istream &is) | |||
{ | |||
while (true) | |||
{ | |||
char sbuf[BLOCK_BYTES]; | |||
is.read(sbuf, BLOCK_BYTES - buffer.size()); | |||
buffer.append(sbuf, is.gcount()); | |||
if (buffer.size() != BLOCK_BYTES) | |||
{ | |||
return; | |||
} | |||
uint32_t block[BLOCK_INTS]; | |||
buffer_to_block(buffer, block); | |||
transform(digest, block, transforms); | |||
buffer.clear(); | |||
} | |||
} | |||
/* | |||
* Add padding and return the message digest. | |||
*/ | |||
std::string SHA1::final() | |||
{ | |||
auto str = final_bin(); | |||
std::ostringstream result; | |||
for (size_t i = 0; i < str.size(); i++) | |||
{ | |||
char b[3]; | |||
sprintf(b, "%02x", static_cast<unsigned char>(str[i])); | |||
result << b; | |||
} | |||
return result.str(); | |||
} | |||
std::string SHA1::final_bin() | |||
{ | |||
/* Total number of hashed bits */ | |||
uint64_t total_bits = (transforms*BLOCK_BYTES + buffer.size()) * 8; | |||
/* Padding */ | |||
buffer += 0x80; | |||
size_t orig_size = buffer.size(); | |||
while (buffer.size() < BLOCK_BYTES) | |||
{ | |||
buffer += (char)0x00; | |||
} | |||
uint32_t block[BLOCK_INTS]; | |||
buffer_to_block(buffer, block); | |||
if (orig_size > BLOCK_BYTES - 8) | |||
{ | |||
transform(digest, block, transforms); | |||
for (size_t i = 0; i < BLOCK_INTS - 2; i++) | |||
{ | |||
block[i] = 0; | |||
} | |||
} | |||
/* Append total_bits, split this uint64_t into two uint32_t */ | |||
block[BLOCK_INTS - 1] = total_bits; | |||
block[BLOCK_INTS - 2] = (total_bits >> 32); | |||
transform(digest, block, transforms); | |||
/* Hex std::string */ | |||
std::string result; | |||
for (size_t i = 0; i < sizeof(digest) / sizeof(digest[0]); i++) | |||
{ | |||
for (size_t b = 0; b < sizeof(digest[0])/sizeof(uint8_t); b++) | |||
{ | |||
result.push_back((digest[i] >> (8 * (sizeof(digest[0]) / sizeof(uint8_t) - 1 - b))) & 0xFF); | |||
} | |||
} | |||
/* Reset for next run */ | |||
reset(digest, buffer, transforms); | |||
return result; | |||
} | |||
std::string SHA1::from_file(const std::string &filename) | |||
{ | |||
std::ifstream stream(filename.c_str(), std::ios::binary); | |||
SHA1 checksum; | |||
checksum.update(stream); | |||
return checksum.final(); | |||
} | |||
std::string SHA1::encode(const std::string &s) | |||
{ | |||
SHA1 sha1; | |||
sha1.update(s); | |||
return sha1.final(); | |||
} | |||
std::string SHA1::encode_bin(const std::string &s) | |||
{ | |||
SHA1 sha1; | |||
sha1.update(s); | |||
return sha1.final_bin(); | |||
} | |||
} //namespace toolkit |
@@ -0,0 +1,47 @@ | |||
// 100% Public Domain. | |||
// | |||
// Original C Code | |||
// -- Steve Reid <steve@edmweb.com> | |||
// Small changes to fit into bglibs | |||
// -- Bruce Guenter <bruce@untroubled.org> | |||
// Translation to simpler C++ Code | |||
// -- Volker Grabsch <vog@notjusthosting.com> | |||
// Safety fixes | |||
// -- Eugene Hopkinson <slowriot at voxelstorm dot com> | |||
// Adapt for project | |||
// Dmitriy Khaustov <khaustov.dm@gmail.com> | |||
// | |||
// File created on: 2017.02.25 | |||
// SHA1.h | |||
#pragma once | |||
#include <cstdint> | |||
#include <iostream> | |||
#include <string> | |||
namespace toolkit { | |||
class SHA1 final | |||
{ | |||
public: | |||
SHA1(); | |||
void update(const std::string &s); | |||
void update(std::istream &is); | |||
std::string final(); | |||
std::string final_bin(); | |||
static std::string from_file(const std::string &filename); | |||
static std::string encode(const std::string &s); | |||
static std::string encode_bin(const std::string &s); | |||
private: | |||
uint32_t digest[5]; | |||
std::string buffer; | |||
uint64_t transforms; | |||
}; | |||
}//namespace toolkit |
@@ -0,0 +1,502 @@ | |||
/* | |||
* 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 "SSLBox.h" | |||
#include "onceToken.h" | |||
#include "SSLUtil.h" | |||
#if defined(ENABLE_OPENSSL) | |||
#include <openssl/ssl.h> | |||
#include <openssl/rand.h> | |||
#include <openssl/crypto.h> | |||
#include <openssl/err.h> | |||
#include <openssl/conf.h> | |||
#include <openssl/bio.h> | |||
#include <openssl/ossl_typ.h> | |||
#endif //defined(ENABLE_OPENSSL) | |||
#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME | |||
//openssl版本是否支持sni | |||
#define SSL_ENABLE_SNI | |||
#endif | |||
using namespace std; | |||
namespace toolkit { | |||
static bool s_ignore_invalid_cer = true; | |||
SSL_Initor &SSL_Initor::Instance() { | |||
static SSL_Initor obj; | |||
return obj; | |||
} | |||
void SSL_Initor::ignoreInvalidCertificate(bool ignore) { | |||
s_ignore_invalid_cer = ignore; | |||
} | |||
SSL_Initor::SSL_Initor() { | |||
#if defined(ENABLE_OPENSSL) | |||
SSL_library_init(); | |||
SSL_load_error_strings(); | |||
OpenSSL_add_all_digests(); | |||
OpenSSL_add_all_ciphers(); | |||
OpenSSL_add_all_algorithms(); | |||
CRYPTO_set_locking_callback([](int mode, int n, const char *file, int line) { | |||
static mutex *s_mutexes = new mutex[CRYPTO_num_locks()]; | |||
static onceToken token(nullptr, []() { | |||
delete[] s_mutexes; | |||
}); | |||
if (mode & CRYPTO_LOCK) { | |||
s_mutexes[n].lock(); | |||
} else { | |||
s_mutexes[n].unlock(); | |||
} | |||
}); | |||
CRYPTO_set_id_callback([]() -> unsigned long { | |||
#if !defined(_WIN32) | |||
return (unsigned long) pthread_self(); | |||
#else | |||
return (unsigned long) GetCurrentThreadId(); | |||
#endif | |||
}); | |||
setContext("", SSLUtil::makeSSLContext(vector<shared_ptr<X509> >(), nullptr, false), false); | |||
setContext("", SSLUtil::makeSSLContext(vector<shared_ptr<X509> >(), nullptr, true), true); | |||
#endif //defined(ENABLE_OPENSSL) | |||
} | |||
SSL_Initor::~SSL_Initor() { | |||
#if defined(ENABLE_OPENSSL) | |||
EVP_cleanup(); | |||
ERR_free_strings(); | |||
ERR_clear_error(); | |||
#if OPENSSL_VERSION_NUMBER >= 0x10000000L && OPENSSL_VERSION_NUMBER < 0x10100000L | |||
ERR_remove_thread_state(nullptr); | |||
#elif OPENSSL_VERSION_NUMBER < 0x10000000L | |||
ERR_remove_state(0); | |||
#endif | |||
CRYPTO_set_locking_callback(nullptr); | |||
//sk_SSL_COMP_free(SSL_COMP_get_compression_methods()); | |||
CRYPTO_cleanup_all_ex_data(); | |||
CONF_modules_unload(1); | |||
CONF_modules_free(); | |||
#endif //defined(ENABLE_OPENSSL) | |||
} | |||
bool SSL_Initor::loadCertificate(const string &pem_or_p12, bool server_mode, const string &password, bool is_file, | |||
bool is_default) { | |||
auto cers = SSLUtil::loadPublicKey(pem_or_p12, password, is_file); | |||
auto key = SSLUtil::loadPrivateKey(pem_or_p12, password, is_file); | |||
auto ssl_ctx = SSLUtil::makeSSLContext(cers, key, server_mode, true); | |||
if (!ssl_ctx) { | |||
return false; | |||
} | |||
for (auto &cer : cers) { | |||
auto server_name = SSLUtil::getServerName(cer.get()); | |||
setContext(server_name, ssl_ctx, server_mode, is_default); | |||
break; | |||
} | |||
return true; | |||
} | |||
int SSL_Initor::findCertificate(SSL *ssl, int *, void *arg) { | |||
#if !defined(ENABLE_OPENSSL) || !defined(SSL_ENABLE_SNI) | |||
return 0; | |||
#else | |||
if (!ssl) { | |||
return SSL_TLSEXT_ERR_ALERT_FATAL; | |||
} | |||
SSL_CTX *ctx = nullptr; | |||
static auto &ref = SSL_Initor::Instance(); | |||
const char *vhost = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); | |||
if (vhost && vhost[0] != '\0') { | |||
//根据域名找到证书 | |||
ctx = ref.getSSLCtx(vhost, (bool) (arg)).get(); | |||
if (!ctx) { | |||
//未找到对应的证书 | |||
WarnL << "Can not find any certificate of host: " << vhost | |||
<< ", select default certificate of: " << ref._default_vhost[(bool) (arg)]; | |||
} | |||
} | |||
if (!ctx) { | |||
//客户端未指定域名或者指定的证书不存在,那么选择一个默认的证书 | |||
ctx = ref.getSSLCtx("", (bool) (arg)).get(); | |||
} | |||
if (!ctx) { | |||
//未有任何有效的证书 | |||
WarnL << "Can not find any available certificate of host: " << (vhost ? vhost : "default host") | |||
<< ", tls handshake failed"; | |||
return SSL_TLSEXT_ERR_ALERT_FATAL; | |||
} | |||
SSL_set_SSL_CTX(ssl, ctx); | |||
return SSL_TLSEXT_ERR_OK; | |||
#endif | |||
} | |||
bool SSL_Initor::setContext(const string &vhost, const shared_ptr<SSL_CTX> &ctx, bool server_mode, bool is_default) { | |||
if (!ctx) { | |||
return false; | |||
} | |||
setupCtx(ctx.get()); | |||
#if defined(ENABLE_OPENSSL) | |||
if (vhost.empty()) { | |||
_ctx_empty[server_mode] = ctx; | |||
#ifdef SSL_ENABLE_SNI | |||
if (server_mode) { | |||
SSL_CTX_set_tlsext_servername_callback(ctx.get(), findCertificate); | |||
SSL_CTX_set_tlsext_servername_arg(ctx.get(), (void *) server_mode); | |||
} | |||
#endif // SSL_ENABLE_SNI | |||
} else { | |||
_ctxs[server_mode][vhost] = ctx; | |||
if (is_default) { | |||
_default_vhost[server_mode] = vhost; | |||
} | |||
if (vhost.find("*.") == 0) { | |||
//通配符证书 | |||
_ctxs_wildcards[server_mode][vhost.substr(1)] = ctx; | |||
} | |||
DebugL << "Add certificate of: " << vhost; | |||
} | |||
return true; | |||
#else | |||
WarnL << "ENABLE_OPENSSL disabled, you can not use any features based on openssl"; | |||
return false; | |||
#endif //defined(ENABLE_OPENSSL) | |||
} | |||
void SSL_Initor::setupCtx(SSL_CTX *ctx) { | |||
#if defined(ENABLE_OPENSSL) | |||
//加载默认信任证书 | |||
SSLUtil::loadDefaultCAs(ctx); | |||
SSL_CTX_set_cipher_list(ctx, "ALL:!ADH:!LOW:!EXP:!MD5:!3DES:@STRENGTH"); | |||
SSL_CTX_set_verify_depth(ctx, 9); | |||
SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY); | |||
SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF); | |||
SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, [](int ok, X509_STORE_CTX *pStore) { | |||
if (!ok) { | |||
int depth = X509_STORE_CTX_get_error_depth(pStore); | |||
int err = X509_STORE_CTX_get_error(pStore); | |||
WarnL << "SSL_CTX_set_verify callback, depth: " << depth << " ,err: " << X509_verify_cert_error_string(err); | |||
} | |||
return s_ignore_invalid_cer ? 1 : ok; | |||
}); | |||
#ifndef SSL_OP_NO_COMPRESSION | |||
#define SSL_OP_NO_COMPRESSION 0 | |||
#endif | |||
#ifndef SSL_MODE_RELEASE_BUFFERS /* OpenSSL >= 1.0.0 */ | |||
#define SSL_MODE_RELEASE_BUFFERS 0 | |||
#endif | |||
unsigned long ssloptions = SSL_OP_ALL | |||
| SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION | |||
| SSL_OP_NO_COMPRESSION; | |||
#ifdef SSL_OP_NO_RENEGOTIATION /* openssl 1.1.0 */ | |||
ssloptions |= SSL_OP_NO_RENEGOTIATION; | |||
#endif | |||
SSL_CTX_set_options(ctx, ssloptions); | |||
#endif //defined(ENABLE_OPENSSL) | |||
} | |||
shared_ptr<SSL> SSL_Initor::makeSSL(bool server_mode) { | |||
#if defined(ENABLE_OPENSSL) | |||
#ifdef SSL_ENABLE_SNI | |||
//openssl 版本支持SNI | |||
return SSLUtil::makeSSL(_ctx_empty[server_mode].get()); | |||
#else | |||
//openssl 版本不支持SNI,选择默认证书 | |||
return SSLUtil::makeSSL(getSSLCtx("",server_mode).get()); | |||
#endif//SSL_CTRL_SET_TLSEXT_HOSTNAME | |||
#else | |||
return nullptr; | |||
#endif //defined(ENABLE_OPENSSL) | |||
} | |||
bool SSL_Initor::trustCertificate(X509 *cer, bool server_mode) { | |||
return SSLUtil::trustCertificate(_ctx_empty[server_mode].get(), cer); | |||
} | |||
bool SSL_Initor::trustCertificate(const string &pem_p12_cer, bool server_mode, const string &password, bool is_file) { | |||
auto cers = SSLUtil::loadPublicKey(pem_p12_cer, password, is_file); | |||
for (auto &cer : cers) { | |||
trustCertificate(cer.get(), server_mode); | |||
} | |||
return true; | |||
} | |||
std::shared_ptr<SSL_CTX> SSL_Initor::getSSLCtx(const string &vhost, bool server_mode) { | |||
auto ret = getSSLCtx_l(vhost, server_mode); | |||
if (ret) { | |||
return ret; | |||
} | |||
return getSSLCtxWildcards(vhost, server_mode); | |||
} | |||
std::shared_ptr<SSL_CTX> SSL_Initor::getSSLCtxWildcards(const string &vhost, bool server_mode) { | |||
for (auto &pr : _ctxs_wildcards[server_mode]) { | |||
auto pos = strcasestr(vhost.data(), pr.first.data()); | |||
if (pos && pos + pr.first.size() == &vhost.back() + 1) { | |||
return pr.second; | |||
} | |||
} | |||
return nullptr; | |||
} | |||
std::shared_ptr<SSL_CTX> SSL_Initor::getSSLCtx_l(const string &vhost_in, bool server_mode) { | |||
auto vhost = vhost_in; | |||
if (vhost.empty()) { | |||
if (!_default_vhost[server_mode].empty()) { | |||
vhost = _default_vhost[server_mode]; | |||
} else { | |||
//没默认主机,选择空主机 | |||
if (server_mode) { | |||
WarnL << "Server with ssl must have certification and key"; | |||
} | |||
return _ctx_empty[server_mode]; | |||
} | |||
} | |||
//根据主机名查找证书 | |||
auto it = _ctxs[server_mode].find(vhost); | |||
if (it == _ctxs[server_mode].end()) { | |||
return nullptr; | |||
} | |||
return it->second; | |||
} | |||
string SSL_Initor::defaultVhost(bool server_mode) { | |||
return _default_vhost[server_mode]; | |||
} | |||
////////////////////////////////////////////////////SSL_Box//////////////////////////////////////////////////////////// | |||
SSL_Box::~SSL_Box() {} | |||
SSL_Box::SSL_Box(bool server_mode, bool enable, int buff_size) { | |||
#if defined(ENABLE_OPENSSL) | |||
_read_bio = BIO_new(BIO_s_mem()); | |||
_server_mode = server_mode; | |||
if (enable) { | |||
_ssl = SSL_Initor::Instance().makeSSL(server_mode); | |||
} | |||
if (_ssl) { | |||
_write_bio = BIO_new(BIO_s_mem()); | |||
SSL_set_bio(_ssl.get(), _read_bio, _write_bio); | |||
_server_mode ? SSL_set_accept_state(_ssl.get()) : SSL_set_connect_state(_ssl.get()); | |||
} else { | |||
WarnL << "makeSSL failed"; | |||
} | |||
_send_handshake = false; | |||
_buff_size = buff_size; | |||
#endif //defined(ENABLE_OPENSSL) | |||
} | |||
void SSL_Box::shutdown() { | |||
#if defined(ENABLE_OPENSSL) | |||
_buffer_send.clear(); | |||
int ret = SSL_shutdown(_ssl.get()); | |||
if (ret != 1) { | |||
ErrorL << "SSL_shutdown failed: " << SSLUtil::getLastError(); | |||
} else { | |||
flush(); | |||
} | |||
#endif //defined(ENABLE_OPENSSL) | |||
} | |||
void SSL_Box::onRecv(const Buffer::Ptr &buffer) { | |||
if (!buffer->size()) { | |||
return; | |||
} | |||
if (!_ssl) { | |||
if (_on_dec) { | |||
_on_dec(buffer); | |||
} | |||
return; | |||
} | |||
#if defined(ENABLE_OPENSSL) | |||
uint32_t offset = 0; | |||
while (offset < buffer->size()) { | |||
auto nwrite = BIO_write(_read_bio, buffer->data() + offset, buffer->size() - offset); | |||
if (nwrite > 0) { | |||
//部分或全部写入bio完毕 | |||
offset += nwrite; | |||
flush(); | |||
continue; | |||
} | |||
//nwrite <= 0,出现异常 | |||
ErrorL << "Ssl error on BIO_write: " << SSLUtil::getLastError(); | |||
shutdown(); | |||
break; | |||
} | |||
#endif //defined(ENABLE_OPENSSL) | |||
} | |||
void SSL_Box::onSend(Buffer::Ptr buffer) { | |||
if (!buffer->size()) { | |||
return; | |||
} | |||
if (!_ssl) { | |||
if (_on_enc) { | |||
_on_enc(buffer); | |||
} | |||
return; | |||
} | |||
#if defined(ENABLE_OPENSSL) | |||
if (!_server_mode && !_send_handshake) { | |||
_send_handshake = true; | |||
SSL_do_handshake(_ssl.get()); | |||
} | |||
_buffer_send.emplace_back(std::move(buffer)); | |||
flush(); | |||
#endif //defined(ENABLE_OPENSSL) | |||
} | |||
void SSL_Box::setOnDecData(const function<void(const Buffer::Ptr &)> &cb) { | |||
_on_dec = cb; | |||
} | |||
void SSL_Box::setOnEncData(const function<void(const Buffer::Ptr &)> &cb) { | |||
_on_enc = cb; | |||
} | |||
void SSL_Box::flushWriteBio() { | |||
#if defined(ENABLE_OPENSSL) | |||
int total = 0; | |||
int nread = 0; | |||
auto buffer_bio = _buffer_pool.obtain2(); | |||
buffer_bio->setCapacity(_buff_size); | |||
auto buf_size = buffer_bio->getCapacity() - 1; | |||
do { | |||
nread = BIO_read(_write_bio, buffer_bio->data() + total, buf_size - total); | |||
if (nread > 0) { | |||
total += nread; | |||
} | |||
} while (nread > 0 && buf_size - total > 0); | |||
if (!total) { | |||
//未有数据 | |||
return; | |||
} | |||
//触发此次回调 | |||
buffer_bio->data()[total] = '\0'; | |||
buffer_bio->setSize(total); | |||
if (_on_enc) { | |||
_on_enc(buffer_bio); | |||
} | |||
if (nread > 0) { | |||
//还有剩余数据,读取剩余数据 | |||
flushWriteBio(); | |||
} | |||
#endif //defined(ENABLE_OPENSSL) | |||
} | |||
void SSL_Box::flushReadBio() { | |||
#if defined(ENABLE_OPENSSL) | |||
int total = 0; | |||
int nread = 0; | |||
auto buffer_bio = _buffer_pool.obtain2(); | |||
buffer_bio->setCapacity(_buff_size); | |||
auto buf_size = buffer_bio->getCapacity() - 1; | |||
do { | |||
nread = SSL_read(_ssl.get(), buffer_bio->data() + total, buf_size - total); | |||
if (nread > 0) { | |||
total += nread; | |||
} | |||
} while (nread > 0 && buf_size - total > 0); | |||
if (!total) { | |||
//未有数据 | |||
return; | |||
} | |||
//触发此次回调 | |||
buffer_bio->data()[total] = '\0'; | |||
buffer_bio->setSize(total); | |||
if (_on_dec) { | |||
_on_dec(buffer_bio); | |||
} | |||
if (nread > 0) { | |||
//还有剩余数据,读取剩余数据 | |||
flushReadBio(); | |||
} | |||
#endif //defined(ENABLE_OPENSSL) | |||
} | |||
void SSL_Box::flush() { | |||
#if defined(ENABLE_OPENSSL) | |||
if (_is_flush) { | |||
return; | |||
} | |||
onceToken token([&] { | |||
_is_flush = true; | |||
}, [&]() { | |||
_is_flush = false; | |||
}); | |||
flushReadBio(); | |||
if (!SSL_is_init_finished(_ssl.get()) || _buffer_send.empty()) { | |||
//ssl未握手结束或没有需要发送的数据 | |||
flushWriteBio(); | |||
return; | |||
} | |||
//加密数据并发送 | |||
while (!_buffer_send.empty()) { | |||
auto &front = _buffer_send.front(); | |||
uint32_t offset = 0; | |||
while (offset < front->size()) { | |||
auto nwrite = SSL_write(_ssl.get(), front->data() + offset, front->size() - offset); | |||
if (nwrite > 0) { | |||
//部分或全部写入完毕 | |||
offset += nwrite; | |||
flushWriteBio(); | |||
continue; | |||
} | |||
//nwrite <= 0,出现异常 | |||
break; | |||
} | |||
if (offset != front->size()) { | |||
//这个包未消费完毕,出现了异常,清空数据并断开ssl | |||
ErrorL << "Ssl error on SSL_write: " << SSLUtil::getLastError(); | |||
shutdown(); | |||
break; | |||
} | |||
//这个包消费完毕,开始消费下一个包 | |||
_buffer_send.pop_front(); | |||
} | |||
#endif //defined(ENABLE_OPENSSL) | |||
} | |||
bool SSL_Box::setHost(const char *host) { | |||
if (!_ssl) { | |||
return false; | |||
} | |||
#ifdef SSL_ENABLE_SNI | |||
return 0 != SSL_set_tlsext_host_name(_ssl.get(), host); | |||
#else | |||
return false; | |||
#endif//SSL_ENABLE_SNI | |||
} | |||
} /* namespace toolkit */ |
@@ -0,0 +1,206 @@ | |||
/* | |||
* 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 CRYPTO_SSLBOX_H_ | |||
#define CRYPTO_SSLBOX_H_ | |||
#include <mutex> | |||
#include <string> | |||
#include <functional> | |||
#include "logger.h" | |||
#include "List.h" | |||
#include "util.h" | |||
#include "Network/Buffer.h" | |||
#include "ResourcePool.h" | |||
typedef struct x509_st X509; | |||
typedef struct evp_pkey_st EVP_PKEY; | |||
typedef struct ssl_ctx_st SSL_CTX; | |||
typedef struct ssl_st SSL; | |||
typedef struct bio_st BIO; | |||
namespace toolkit { | |||
class SSL_Initor { | |||
public: | |||
friend class SSL_Box; | |||
static SSL_Initor &Instance(); | |||
/** | |||
* 从文件或字符串中加载公钥和私钥 | |||
* 该证书文件必须同时包含公钥和私钥(cer格式的证书只包括公钥,请使用后面的方法加载) | |||
* 客户端默认可以不加载证书(除非服务器要求客户端提供证书) | |||
* @param pem_or_p12 pem或p12文件路径或者文件内容字符串 | |||
* @param server_mode 是否为服务器模式 | |||
* @param password 私钥加密密码 | |||
* @param is_file 参数pem_or_p12是否为文件路径 | |||
* @param is_default 是否为默认证书 | |||
*/ | |||
bool loadCertificate(const std::string &pem_or_p12, bool server_mode = true, const std::string &password = "", | |||
bool is_file = true, bool is_default = true); | |||
/** | |||
* 是否忽略无效的证书 | |||
* 默认忽略,强烈建议不要忽略! | |||
* @param ignore 标记 | |||
*/ | |||
void ignoreInvalidCertificate(bool ignore = true); | |||
/** | |||
* 信任某证书,一般用于客户端信任自签名的证书或自签名CA签署的证书使用 | |||
* 比如说我的客户端要信任我自己签发的证书,那么我们可以只信任这个证书 | |||
* @param pem_p12_cer pem文件或p12文件或cer文件路径或内容 | |||
* @param server_mode 是否为服务器模式 | |||
* @param password pem或p12证书的密码 | |||
* @param is_file 是否为文件路径 | |||
* @return 是否加载成功 | |||
*/ | |||
bool trustCertificate(const std::string &pem_p12_cer, bool server_mode = false, const std::string &password = "", | |||
bool is_file = true); | |||
/** | |||
* 信任某证书 | |||
* @param cer 证书公钥 | |||
* @param server_mode 是否为服务模式 | |||
* @return 是否加载成功 | |||
*/ | |||
bool trustCertificate(X509 *cer, bool server_mode = false); | |||
private: | |||
SSL_Initor(); | |||
~SSL_Initor(); | |||
/** | |||
* 创建SSL对象 | |||
*/ | |||
std::shared_ptr<SSL> makeSSL(bool server_mode); | |||
/** | |||
* 设置ssl context | |||
* @param vhost 虚拟主机名 | |||
* @param ctx ssl context | |||
* @param server_mode ssl context | |||
* @param is_default 是否为默认证书 | |||
*/ | |||
bool setContext(const std::string &vhost, const std::shared_ptr<SSL_CTX> &ctx, bool server_mode, bool is_default = true); | |||
/** | |||
* 设置SSL_CTX的默认配置 | |||
* @param ctx 对象指针 | |||
*/ | |||
void setupCtx(SSL_CTX *ctx); | |||
/** | |||
* 根据虚拟主机获取SSL_CTX对象 | |||
* @param vhost 虚拟主机名 | |||
* @param server_mode 是否为服务器模式 | |||
* @return SSL_CTX对象 | |||
*/ | |||
std::shared_ptr<SSL_CTX> getSSLCtx(const std::string &vhost, bool server_mode); | |||
std::shared_ptr<SSL_CTX> getSSLCtx_l(const std::string &vhost, bool server_mode); | |||
std::shared_ptr<SSL_CTX> getSSLCtxWildcards(const std::string &vhost, bool server_mode); | |||
/** | |||
* 获取默认的虚拟主机 | |||
*/ | |||
std::string defaultVhost(bool server_mode); | |||
/** | |||
* 完成vhost name 匹配的回调函数 | |||
*/ | |||
static int findCertificate(SSL *ssl, int *ad, void *arg); | |||
private: | |||
struct less_nocase { | |||
bool operator()(const std::string &x, const std::string &y) const { | |||
return strcasecmp(x.data(), y.data()) < 0; | |||
} | |||
}; | |||
private: | |||
std::string _default_vhost[2]; | |||
std::shared_ptr<SSL_CTX> _ctx_empty[2]; | |||
std::map<std::string, std::shared_ptr<SSL_CTX>, less_nocase> _ctxs[2]; | |||
std::map<std::string, std::shared_ptr<SSL_CTX>, less_nocase> _ctxs_wildcards[2]; | |||
}; | |||
//////////////////////////////////////////////////////////////////////////////////// | |||
class SSL_Box { | |||
public: | |||
SSL_Box(bool server_mode = true, bool enable = true, int buff_size = 32 * 1024); | |||
~SSL_Box(); | |||
/** | |||
* 收到密文后,调用此函数解密 | |||
* @param buffer 收到的密文数据 | |||
*/ | |||
void onRecv(const Buffer::Ptr &buffer); | |||
/** | |||
* 需要加密明文调用此函数 | |||
* @param buffer 需要加密的明文数据 | |||
*/ | |||
void onSend(Buffer::Ptr buffer); | |||
/** | |||
* 设置解密后获取明文的回调 | |||
* @param cb 回调对象 | |||
*/ | |||
void setOnDecData(const std::function<void(const Buffer::Ptr &)> &cb); | |||
/** | |||
* 设置加密后获取密文的回调 | |||
* @param cb 回调对象 | |||
*/ | |||
void setOnEncData(const std::function<void(const Buffer::Ptr &)> &cb); | |||
/** | |||
* 终结ssl | |||
*/ | |||
void shutdown(); | |||
/** | |||
* 清空数据 | |||
*/ | |||
void flush(); | |||
/** | |||
* 设置虚拟主机名 | |||
* @param host 虚拟主机名 | |||
* @return 是否成功 | |||
*/ | |||
bool setHost(const char *host); | |||
private: | |||
void flushWriteBio(); | |||
void flushReadBio(); | |||
private: | |||
bool _server_mode; | |||
bool _send_handshake; | |||
bool _is_flush = false; | |||
int _buff_size; | |||
BIO *_read_bio; | |||
BIO *_write_bio; | |||
std::shared_ptr<SSL> _ssl; | |||
List <Buffer::Ptr> _buffer_send; | |||
ResourcePool <BufferRaw> _buffer_pool; | |||
std::function<void(const Buffer::Ptr &)> _on_dec; | |||
std::function<void(const Buffer::Ptr &)> _on_enc; | |||
}; | |||
} /* namespace toolkit */ | |||
#endif /* CRYPTO_SSLBOX_H_ */ |
@@ -0,0 +1,386 @@ | |||
/* | |||
* 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 "SSLUtil.h" | |||
#include "onceToken.h" | |||
#include "logger.h" | |||
#if defined(ENABLE_OPENSSL) | |||
#include <openssl/bio.h> | |||
#include <openssl/ossl_typ.h> | |||
#include <openssl/pkcs12.h> | |||
#include <openssl/ssl.h> | |||
#include <openssl/rand.h> | |||
#include <openssl/crypto.h> | |||
#include <openssl/err.h> | |||
#include <openssl/conf.h> | |||
#endif //defined(ENABLE_OPENSSL) | |||
using namespace std; | |||
namespace toolkit { | |||
std::string SSLUtil::getLastError() { | |||
#if defined(ENABLE_OPENSSL) | |||
unsigned long errCode = ERR_get_error(); | |||
if (errCode != 0) { | |||
char buffer[256]; | |||
ERR_error_string_n(errCode, buffer, sizeof(buffer)); | |||
return buffer; | |||
} else | |||
#endif //defined(ENABLE_OPENSSL) | |||
{ | |||
return "No error"; | |||
} | |||
} | |||
#if defined(ENABLE_OPENSSL) | |||
static int getCerType(BIO *bio, const char *passwd, X509 **x509, int type) { | |||
//尝试pem格式 | |||
if (type == 1 || type == 0) { | |||
if (type == 0) { | |||
BIO_reset(bio); | |||
} | |||
// 尝试PEM格式 | |||
*x509 = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr); | |||
if (*x509) { | |||
return 1; | |||
} | |||
} | |||
if (type == 2 || type == 0) { | |||
if (type == 0) { | |||
BIO_reset(bio); | |||
} | |||
//尝试DER格式 | |||
*x509 = d2i_X509_bio(bio, nullptr); | |||
if (*x509) { | |||
return 2; | |||
} | |||
} | |||
if (type == 3 || type == 0) { | |||
if (type == 0) { | |||
BIO_reset(bio); | |||
} | |||
//尝试p12格式 | |||
PKCS12 *p12 = d2i_PKCS12_bio(bio, nullptr); | |||
if (p12) { | |||
EVP_PKEY *pkey = nullptr; | |||
PKCS12_parse(p12, passwd, &pkey, x509, nullptr); | |||
PKCS12_free(p12); | |||
if (pkey) { | |||
EVP_PKEY_free(pkey); | |||
} | |||
if (*x509) { | |||
return 3; | |||
} | |||
} | |||
} | |||
return 0; | |||
} | |||
#endif //defined(ENABLE_OPENSSL) | |||
vector<shared_ptr<X509> > SSLUtil::loadPublicKey(const string &file_path_or_data, const string &passwd, bool isFile) { | |||
vector<shared_ptr<X509> > ret; | |||
#if defined(ENABLE_OPENSSL) | |||
BIO *bio = isFile ? BIO_new_file((char *) file_path_or_data.data(), "r") : | |||
BIO_new_mem_buf((char *) file_path_or_data.data(), file_path_or_data.size()); | |||
if (!bio) { | |||
WarnL << (isFile ? "BIO_new_file" : "BIO_new_mem_buf") << " failed: " << getLastError(); | |||
return ret; | |||
} | |||
onceToken token0(nullptr, [&]() { | |||
BIO_free(bio); | |||
}); | |||
int cer_type = 0; | |||
X509 *x509 = nullptr; | |||
do { | |||
cer_type = getCerType(bio, passwd.data(), &x509, cer_type); | |||
if (cer_type) { | |||
ret.push_back(shared_ptr<X509>(x509, [](X509 *ptr) { X509_free(ptr); })); | |||
} | |||
} while (cer_type != 0); | |||
return ret; | |||
#else | |||
return ret; | |||
#endif //defined(ENABLE_OPENSSL) | |||
} | |||
shared_ptr<EVP_PKEY> SSLUtil::loadPrivateKey(const string &file_path_or_data, const string &passwd, bool isFile) { | |||
#if defined(ENABLE_OPENSSL) | |||
BIO *bio = isFile ? | |||
BIO_new_file((char *) file_path_or_data.data(), "r") : | |||
BIO_new_mem_buf((char *) file_path_or_data.data(), file_path_or_data.size()); | |||
if (!bio) { | |||
WarnL << (isFile ? "BIO_new_file" : "BIO_new_mem_buf") << " failed: " << getLastError(); | |||
return nullptr; | |||
} | |||
pem_password_cb *cb = [](char *buf, int size, int rwflag, void *userdata) -> int { | |||
const string *passwd = (const string *) userdata; | |||
size = size < (int) passwd->size() ? size : (int) passwd->size(); | |||
memcpy(buf, passwd->data(), size); | |||
return size; | |||
}; | |||
onceToken token0(nullptr, [&]() { | |||
BIO_free(bio); | |||
}); | |||
//尝试pem格式 | |||
EVP_PKEY *evp_key = PEM_read_bio_PrivateKey(bio, nullptr, cb, (void *) &passwd); | |||
if (!evp_key) { | |||
//尝试p12格式 | |||
BIO_reset(bio); | |||
PKCS12 *p12 = d2i_PKCS12_bio(bio, nullptr); | |||
if (!p12) { | |||
return nullptr; | |||
} | |||
X509 *x509 = nullptr; | |||
PKCS12_parse(p12, passwd.data(), &evp_key, &x509, nullptr); | |||
PKCS12_free(p12); | |||
if (x509) { | |||
X509_free(x509); | |||
} | |||
if (!evp_key) { | |||
return nullptr; | |||
} | |||
} | |||
return shared_ptr<EVP_PKEY>(evp_key, [](EVP_PKEY *ptr) { | |||
EVP_PKEY_free(ptr); | |||
}); | |||
#else | |||
return nullptr; | |||
#endif //defined(ENABLE_OPENSSL) | |||
} | |||
shared_ptr<SSL_CTX> SSLUtil::makeSSLContext(const vector<shared_ptr<X509> > &cers, const shared_ptr<EVP_PKEY> &key, bool serverMode, bool checkKey) { | |||
#if defined(ENABLE_OPENSSL) | |||
SSL_CTX *ctx = SSL_CTX_new(serverMode ? SSLv23_server_method() : SSLv23_client_method()); | |||
if (!ctx) { | |||
WarnL << "SSL_CTX_new " << (serverMode ? "SSLv23_server_method" : "SSLv23_client_method") << " failed: " << getLastError(); | |||
return nullptr; | |||
} | |||
int i = 0; | |||
for (auto &cer : cers) { | |||
//加载公钥 | |||
if (i++ == 0) { | |||
//SSL_CTX_use_certificate内部会调用X509_up_ref,所以这里不用X509_dup | |||
SSL_CTX_use_certificate(ctx, cer.get()); | |||
} else { | |||
//需要先拷贝X509对象,否则指针会失效 | |||
SSL_CTX_add_extra_chain_cert(ctx, X509_dup(cer.get())); | |||
} | |||
} | |||
if (key) { | |||
//提供了私钥 | |||
if (SSL_CTX_use_PrivateKey(ctx, key.get()) != 1) { | |||
WarnL << "SSL_CTX_use_PrivateKey failed: " << getLastError(); | |||
SSL_CTX_free(ctx); | |||
return nullptr; | |||
} | |||
} | |||
if (key || checkKey) { | |||
//加载私钥成功 | |||
if (SSL_CTX_check_private_key(ctx) != 1) { | |||
WarnL << "SSL_CTX_check_private_key failed: " << getLastError(); | |||
SSL_CTX_free(ctx); | |||
return nullptr; | |||
} | |||
} | |||
//公钥私钥匹配或者没有公私钥 | |||
return shared_ptr<SSL_CTX>(ctx, [](SSL_CTX *ptr) { SSL_CTX_free(ptr); }); | |||
#else | |||
return nullptr; | |||
#endif //defined(ENABLE_OPENSSL) | |||
} | |||
shared_ptr<SSL> SSLUtil::makeSSL(SSL_CTX *ctx) { | |||
#if defined(ENABLE_OPENSSL) | |||
auto *ssl = SSL_new(ctx); | |||
if (!ssl) { | |||
return nullptr; | |||
} | |||
return shared_ptr<SSL>(ssl, [](SSL *ptr) { | |||
SSL_free(ptr); | |||
}); | |||
#else | |||
return nullptr; | |||
#endif //defined(ENABLE_OPENSSL) | |||
} | |||
bool SSLUtil::loadDefaultCAs(SSL_CTX *ctx) { | |||
#if defined(ENABLE_OPENSSL) | |||
if (!ctx) { | |||
return false; | |||
} | |||
if (SSL_CTX_set_default_verify_paths(ctx) != 1) { | |||
WarnL << "SSL_CTX_set_default_verify_paths failed: " << getLastError(); | |||
return false; | |||
} | |||
return true; | |||
#else | |||
return false; | |||
#endif //defined(ENABLE_OPENSSL) | |||
} | |||
bool SSLUtil::trustCertificate(SSL_CTX *ctx, X509 *cer) { | |||
#if defined(ENABLE_OPENSSL) | |||
X509_STORE *store = SSL_CTX_get_cert_store(ctx); | |||
if (store && cer) { | |||
if (X509_STORE_add_cert(store, cer) != 1) { | |||
WarnL << "X509_STORE_add_cert failed: " << getLastError(); | |||
return false; | |||
} | |||
return true; | |||
} | |||
#endif //defined(ENABLE_OPENSSL) | |||
return false; | |||
} | |||
bool SSLUtil::verifyX509(X509 *cer, ...) { | |||
#if defined(ENABLE_OPENSSL) | |||
va_list args; | |||
va_start(args, cer); | |||
X509_STORE *store = X509_STORE_new(); | |||
do { | |||
X509 *ca; | |||
if ((ca = va_arg(args, X509*)) == nullptr) { | |||
break; | |||
} | |||
X509_STORE_add_cert(store, ca); | |||
} while (true); | |||
va_end(args); | |||
X509_STORE_CTX *store_ctx = X509_STORE_CTX_new(); | |||
X509_STORE_CTX_init(store_ctx, store, cer, nullptr); | |||
auto ret = X509_verify_cert(store_ctx); | |||
if (ret != 1) { | |||
int depth = X509_STORE_CTX_get_error_depth(store_ctx); | |||
int err = X509_STORE_CTX_get_error(store_ctx); | |||
WarnL << "X509_verify_cert failed, depth: " << depth << ", err: " << X509_verify_cert_error_string(err); | |||
} | |||
X509_STORE_CTX_free(store_ctx); | |||
X509_STORE_free(store); | |||
return ret == 1; | |||
#else | |||
WarnL << "ENABLE_OPENSSL disabled, you can not use any features based on openssl"; | |||
return false; | |||
#endif //defined(ENABLE_OPENSSL) | |||
} | |||
#ifdef ENABLE_OPENSSL | |||
#ifndef X509_F_X509_PUBKEY_GET0 | |||
EVP_PKEY *X509_get0_pubkey(X509 *x){ | |||
EVP_PKEY *ret = X509_get_pubkey(x); | |||
if(ret){ | |||
EVP_PKEY_free(ret); | |||
} | |||
return ret; | |||
} | |||
#endif //X509_F_X509_PUBKEY_GET0 | |||
#ifndef EVP_F_EVP_PKEY_GET0_RSA | |||
RSA *EVP_PKEY_get0_RSA(EVP_PKEY *pkey){ | |||
RSA *ret = EVP_PKEY_get1_RSA(pkey); | |||
if(ret){ | |||
RSA_free(ret); | |||
} | |||
return ret; | |||
} | |||
#endif //EVP_F_EVP_PKEY_GET0_RSA | |||
#endif //ENABLE_OPENSSL | |||
string SSLUtil::cryptWithRsaPublicKey(X509 *cer, const string &in_str, bool enc_or_dec) { | |||
#if defined(ENABLE_OPENSSL) | |||
EVP_PKEY *public_key = X509_get0_pubkey(cer); | |||
if (!public_key) { | |||
return ""; | |||
} | |||
auto rsa = EVP_PKEY_get1_RSA(public_key); | |||
if (!rsa) { | |||
return ""; | |||
} | |||
string out_str(RSA_size(rsa), '\0'); | |||
int ret = 0; | |||
if (enc_or_dec) { | |||
ret = RSA_public_encrypt(in_str.size(), (uint8_t *) in_str.data(), (uint8_t *) out_str.data(), rsa, | |||
RSA_PKCS1_PADDING); | |||
} else { | |||
ret = RSA_public_decrypt(in_str.size(), (uint8_t *) in_str.data(), (uint8_t *) out_str.data(), rsa, | |||
RSA_PKCS1_PADDING); | |||
} | |||
if (ret > 0) { | |||
out_str.resize(ret); | |||
return out_str; | |||
} | |||
WarnL << (enc_or_dec ? "RSA_public_encrypt" : "RSA_public_decrypt") << " failed: " << getLastError(); | |||
return ""; | |||
#else | |||
WarnL << "ENABLE_OPENSSL disabled, you can not use any features based on openssl"; | |||
return ""; | |||
#endif //defined(ENABLE_OPENSSL) | |||
} | |||
string SSLUtil::cryptWithRsaPrivateKey(EVP_PKEY *private_key, const string &in_str, bool enc_or_dec) { | |||
#if defined(ENABLE_OPENSSL) | |||
auto rsa = EVP_PKEY_get1_RSA(private_key); | |||
if (!rsa) { | |||
return ""; | |||
} | |||
string out_str(RSA_size(rsa), '\0'); | |||
int ret = 0; | |||
if (enc_or_dec) { | |||
ret = RSA_private_encrypt(in_str.size(), (uint8_t *) in_str.data(), (uint8_t *) out_str.data(), rsa, | |||
RSA_PKCS1_PADDING); | |||
} else { | |||
ret = RSA_private_decrypt(in_str.size(), (uint8_t *) in_str.data(), (uint8_t *) out_str.data(), rsa, | |||
RSA_PKCS1_PADDING); | |||
} | |||
if (ret > 0) { | |||
out_str.resize(ret); | |||
return out_str; | |||
} | |||
WarnL << getLastError(); | |||
return ""; | |||
#else | |||
WarnL << "ENABLE_OPENSSL disabled, you can not use any features based on openssl"; | |||
return ""; | |||
#endif //defined(ENABLE_OPENSSL) | |||
} | |||
string SSLUtil::getServerName(X509 *cer) { | |||
#if defined(ENABLE_OPENSSL) && defined(SSL_CTRL_SET_TLSEXT_HOSTNAME) | |||
if (!cer) { | |||
return ""; | |||
} | |||
//获取证书里的域名 | |||
X509_NAME *name = X509_get_subject_name(cer); | |||
char ret[256] = {0}; | |||
X509_NAME_get_text_by_NID(name, NID_commonName, ret, sizeof(ret)); | |||
return ret; | |||
#else | |||
return ""; | |||
#endif | |||
} | |||
}//namespace toolkit |
@@ -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. | |||
*/ | |||
#ifndef ZLTOOLKIT_SSLUTIL_H | |||
#define ZLTOOLKIT_SSLUTIL_H | |||
#include <memory> | |||
#include <string> | |||
#include <vector> | |||
typedef struct x509_st X509; | |||
typedef struct evp_pkey_st EVP_PKEY; | |||
typedef struct ssl_ctx_st SSL_CTX; | |||
typedef struct ssl_st SSL; | |||
typedef struct bio_st BIO; | |||
namespace toolkit { | |||
/** | |||
* ssl证书后缀一般分为以下几种 | |||
* pem:这个是base64的字符编码串,可能存在公钥、私钥或者两者都存在 | |||
* cer:只且只能是公钥,可以与pem的私钥配合使用 | |||
* p12:必须包括私钥和公钥 | |||
*/ | |||
class SSLUtil { | |||
public: | |||
static std::string getLastError(); | |||
/** | |||
* 加载公钥证书,支持pem,p12,cer后缀 | |||
* 由于openssl加载p12证书时会校验公钥和私钥是否匹对,所以加载p12的公钥时可能需要传入证书密码 | |||
* @param file_path_or_data 文件路径或文件内容 | |||
* @param isFile 是否为文件 | |||
* @return 公钥证书列表 | |||
*/ | |||
static std::vector<std::shared_ptr<X509> > loadPublicKey(const std::string &file_path_or_data, const std::string &passwd = "", bool isFile = true); | |||
/** | |||
* 加载私钥证书,支持pem,p12后缀 | |||
* @param file_path_or_data 文件路径或文件内容 | |||
* @param passwd 密码 | |||
* @param isFile 是否为文件 | |||
* @return 私钥证书 | |||
*/ | |||
static std::shared_ptr<EVP_PKEY> loadPrivateKey(const std::string &file_path_or_data, const std::string &passwd = "", bool isFile = true); | |||
/** | |||
* 创建SSL_CTX对象 | |||
* @param cer 公钥数组 | |||
* @param key 私钥 | |||
* @param serverMode 是否为服务器模式或客户端模式 | |||
* @return SSL_CTX对象 | |||
*/ | |||
static std::shared_ptr<SSL_CTX> makeSSLContext(const std::vector<std::shared_ptr<X509> > &cers, const std::shared_ptr<EVP_PKEY> &key, bool serverMode = true, bool checkKey = false); | |||
/** | |||
* 创建ssl对象 | |||
* @param ctx SSL_CTX对象 | |||
*/ | |||
static std::shared_ptr<SSL> makeSSL(SSL_CTX *ctx); | |||
/** | |||
* specifies that the default locations from which CA certificates are loaded should be used. | |||
* There is one default directory and one default file. | |||
* The default CA certificates directory is called "certs" in the default OpenSSL directory. | |||
* Alternatively the SSL_CERT_DIR environment variable can be defined to override this location. | |||
* The default CA certificates file is called "cert.pem" in the default OpenSSL directory. | |||
* Alternatively the SSL_CERT_FILE environment variable can be defined to override this location. | |||
* 信任/usr/local/ssl/certs/目录下的所有证书/usr/local/ssl/cert.pem的证书 | |||
* 环境变量SSL_CERT_FILE将替换/usr/local/ssl/cert.pem的路径 | |||
*/ | |||
static bool loadDefaultCAs(SSL_CTX *ctx); | |||
/** | |||
* 信任某公钥 | |||
*/ | |||
static bool trustCertificate(SSL_CTX *ctx, X509 *cer); | |||
/** | |||
* 验证证书合法性 | |||
* @param cer 待验证的证书 | |||
* @param ... 信任的CA根证书,X509类型,以nullptr结尾 | |||
* @return 是否合法 | |||
*/ | |||
static bool verifyX509(X509 *cer, ...); | |||
/** | |||
* 使用公钥加解密数据 | |||
* @param cer 公钥,必须为ras的公钥 | |||
* @param in_str 加密或解密的原始数据,实测加密最大支持245个字节,加密后数据长度固定为256个字节 | |||
* @param enc_or_dec true:加密,false:解密 | |||
* @return 加密或解密后的数据 | |||
*/ | |||
static std::string cryptWithRsaPublicKey(X509 *cer, const std::string &in_str, bool enc_or_dec); | |||
/** | |||
* 使用私钥加解密数据 | |||
* @param private_key 私钥,必须为ras的私钥 | |||
* @param in_str 加密或解密的原始数据,实测加密最大支持245个字节,加密后数据长度固定为256个字节 | |||
* @param enc_or_dec true:加密,false:解密 | |||
* @return 加密或解密后的数据 | |||
*/ | |||
static std::string cryptWithRsaPrivateKey(EVP_PKEY *private_key, const std::string &in_str, bool enc_or_dec); | |||
/** | |||
* 获取证书域名 | |||
* @param cer 证书公钥 | |||
* @return 证书域名 | |||
*/ | |||
static std::string getServerName(X509 *cer); | |||
}; | |||
}//namespace toolkit | |||
#endif //ZLTOOLKIT_SSLUTIL_H |
@@ -0,0 +1,65 @@ | |||
/* | |||
* 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 SPEED_STATISTIC_H_ | |||
#define SPEED_STATISTIC_H_ | |||
#include "TimeTicker.h" | |||
namespace toolkit { | |||
class BytesSpeed { | |||
public: | |||
BytesSpeed() = default; | |||
~BytesSpeed() = default; | |||
/** | |||
* 添加统计字节 | |||
*/ | |||
BytesSpeed &operator+=(size_t bytes) { | |||
_bytes += bytes; | |||
if (_bytes > 1024 * 1024) { | |||
//数据大于1MB就计算一次网速 | |||
computeSpeed(); | |||
} | |||
return *this; | |||
} | |||
/** | |||
* 获取速度,单位bytes/s | |||
*/ | |||
int getSpeed() { | |||
if (_ticker.elapsedTime() < 1000) { | |||
//获取频率小于1秒,那么返回上次计算结果 | |||
return _speed; | |||
} | |||
return computeSpeed(); | |||
} | |||
private: | |||
int computeSpeed() { | |||
auto elapsed = _ticker.elapsedTime(); | |||
if (!elapsed) { | |||
return _speed; | |||
} | |||
_speed = (int)(_bytes * 1000 / elapsed); | |||
_ticker.resetTime(); | |||
_bytes = 0; | |||
return _speed; | |||
} | |||
private: | |||
int _speed = 0; | |||
size_t _bytes = 0; | |||
Ticker _ticker; | |||
}; | |||
} /* namespace toolkit */ | |||
#endif /* SPEED_STATISTIC_H_ */ |
@@ -0,0 +1,248 @@ | |||
/* | |||
* 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 SQL_SQLCONNECTION_H_ | |||
#define SQL_SQLCONNECTION_H_ | |||
#include <cstdio> | |||
#include <cstdarg> | |||
#include <cstring> | |||
#include <string> | |||
#include <vector> | |||
#include <list> | |||
#include <deque> | |||
#include <sstream> | |||
#include <iostream> | |||
#include <stdexcept> | |||
#include "logger.h" | |||
#include "util.h" | |||
#include <mysql.h> | |||
#if defined(_WIN32) | |||
#pragma comment (lib,"libmysql") | |||
#endif | |||
namespace toolkit { | |||
/** | |||
* 数据库异常类 | |||
*/ | |||
class SqlException : public std::exception { | |||
public: | |||
SqlException(const std::string &sql, const std::string &err) { | |||
_sql = sql; | |||
_err = err; | |||
} | |||
virtual const char *what() const noexcept { | |||
return _err.data(); | |||
} | |||
const std::string &getSql() const { | |||
return _sql; | |||
} | |||
private: | |||
std::string _sql; | |||
std::string _err; | |||
}; | |||
/** | |||
* mysql连接 | |||
*/ | |||
class SqlConnection { | |||
public: | |||
/** | |||
* 构造函数 | |||
* @param url 数据库地址 | |||
* @param port 数据库端口号 | |||
* @param dbname 数据库名 | |||
* @param username 用户名 | |||
* @param password 用户密码 | |||
* @param character 字符集 | |||
*/ | |||
SqlConnection(const std::string &url, unsigned short port, | |||
const std::string &dbname, const std::string &username, | |||
const std::string &password, const std::string &character = "utf8mb4") { | |||
mysql_init(&_sql); | |||
unsigned int timeout = 3; | |||
mysql_options(&_sql, MYSQL_OPT_CONNECT_TIMEOUT, &timeout); | |||
if (!mysql_real_connect(&_sql, url.data(), username.data(), | |||
password.data(), dbname.data(), port, nullptr, 0)) { | |||
mysql_close(&_sql); | |||
throw SqlException("mysql_real_connect", mysql_error(&_sql)); | |||
} | |||
//兼容bool与my_bool | |||
uint32_t reconnect = 0x01010101; | |||
mysql_options(&_sql, MYSQL_OPT_RECONNECT, &reconnect); | |||
mysql_set_character_set(&_sql, character.data()); | |||
} | |||
~SqlConnection() { | |||
mysql_close(&_sql); | |||
} | |||
/** | |||
* 以printf样式执行sql,无数据返回 | |||
* @param rowId insert时的插入rowid | |||
* @param fmt printf类型fmt | |||
* @param arg 可变参数列表 | |||
* @return 影响行数 | |||
*/ | |||
template<typename Fmt, typename ...Args> | |||
int64_t query(int64_t &rowId, Fmt &&fmt, Args &&...arg) { | |||
check(); | |||
auto tmp = queryString(std::forward<Fmt>(fmt), std::forward<Args>(arg)...); | |||
if (doQuery(tmp)) { | |||
throw SqlException(tmp, mysql_error(&_sql)); | |||
} | |||
rowId = mysql_insert_id(&_sql); | |||
return mysql_affected_rows(&_sql); | |||
} | |||
/** | |||
* 以printf样式执行sql,并且返回list类型的结果(不包含数据列名) | |||
* @param rowId insert时的插入rowid | |||
* @param ret 返回数据列表 | |||
* @param fmt printf类型fmt | |||
* @param arg 可变参数列表 | |||
* @return 影响行数 | |||
*/ | |||
template<typename Fmt, typename ...Args> | |||
int64_t query(int64_t &rowId, std::vector<std::vector<std::string> > &ret, Fmt &&fmt, Args &&...arg) { | |||
return queryList(rowId, ret, std::forward<Fmt>(fmt), std::forward<Args>(arg)...); | |||
} | |||
template<typename Fmt, typename... Args> | |||
int64_t query(int64_t &rowId, std::vector<std::list<std::string>> &ret, Fmt &&fmt, Args &&...arg) { | |||
return queryList(rowId, ret, std::forward<Fmt>(fmt), std::forward<Args>(arg)...); | |||
} | |||
template<typename Fmt, typename ...Args> | |||
int64_t query(int64_t &rowId, std::vector<std::deque<std::string> > &ret, Fmt &&fmt, Args &&...arg) { | |||
return queryList(rowId, ret, std::forward<Fmt>(fmt), std::forward<Args>(arg)...); | |||
} | |||
/** | |||
* 以printf样式执行sql,并且返回Map类型的结果(包含数据列名) | |||
* @param rowId insert时的插入rowid | |||
* @param ret 返回数据列表 | |||
* @param fmt printf类型fmt | |||
* @param arg 可变参数列表 | |||
* @return 影响行数 | |||
*/ | |||
template<typename Map, typename Fmt, typename ...Args> | |||
int64_t query(int64_t &rowId, std::vector<Map> &ret, Fmt &&fmt, Args &&...arg) { | |||
check(); | |||
auto tmp = queryString(std::forward<Fmt>(fmt), std::forward<Args>(arg)...); | |||
if (doQuery(tmp)) { | |||
throw SqlException(tmp, mysql_error(&_sql)); | |||
} | |||
ret.clear(); | |||
MYSQL_RES *res = mysql_store_result(&_sql); | |||
if (!res) { | |||
rowId = mysql_insert_id(&_sql); | |||
return mysql_affected_rows(&_sql); | |||
} | |||
MYSQL_ROW row; | |||
unsigned int column = mysql_num_fields(res); | |||
MYSQL_FIELD *fields = mysql_fetch_fields(res); | |||
while ((row = mysql_fetch_row(res)) != nullptr) { | |||
ret.emplace_back(); | |||
auto &back = ret.back(); | |||
for (unsigned int i = 0; i < column; i++) { | |||
back[std::string(fields[i].name, fields[i].name_length)] = (row[i] ? row[i] : ""); | |||
} | |||
} | |||
mysql_free_result(res); | |||
rowId = mysql_insert_id(&_sql); | |||
return mysql_affected_rows(&_sql); | |||
} | |||
std::string escape(const std::string &str) { | |||
char *out = new char[str.length() * 2 + 1]; | |||
mysql_real_escape_string(&_sql, out, str.c_str(), str.size()); | |||
std::string ret(out); | |||
delete[] out; | |||
return ret; | |||
} | |||
template<typename ...Args> | |||
static std::string queryString(const char *fmt, Args &&...arg) { | |||
char *ptr_out = nullptr; | |||
if (asprintf(&ptr_out, fmt, arg...) > 0 && ptr_out) { | |||
std::string ret(ptr_out); | |||
free(ptr_out); | |||
return ret; | |||
} | |||
return ""; | |||
} | |||
template<typename ...Args> | |||
static std::string queryString(const std::string &fmt, Args &&...args) { | |||
return queryString(fmt.data(), std::forward<Args>(args)...); | |||
} | |||
static const char *queryString(const char *fmt) { | |||
return fmt; | |||
} | |||
static const std::string &queryString(const std::string &fmt) { | |||
return fmt; | |||
} | |||
private: | |||
template<typename List, typename Fmt, typename... Args> | |||
int64_t queryList(int64_t &rowId, std::vector<List> &ret, Fmt &&fmt, Args &&...arg) { | |||
check(); | |||
auto tmp = queryString(std::forward<Fmt>(fmt), std::forward<Args>(arg)...); | |||
if (doQuery(tmp)) { | |||
throw SqlException(tmp, mysql_error(&_sql)); | |||
} | |||
ret.clear(); | |||
MYSQL_RES *res = mysql_store_result(&_sql); | |||
if (!res) { | |||
rowId = mysql_insert_id(&_sql); | |||
return mysql_affected_rows(&_sql); | |||
} | |||
MYSQL_ROW row; | |||
unsigned int column = mysql_num_fields(res); | |||
while ((row = mysql_fetch_row(res)) != nullptr) { | |||
ret.emplace_back(); | |||
auto &back = ret.back(); | |||
for (unsigned int i = 0; i < column; i++) { | |||
back.emplace_back(row[i] ? row[i] : ""); | |||
} | |||
} | |||
mysql_free_result(res); | |||
rowId = mysql_insert_id(&_sql); | |||
return mysql_affected_rows(&_sql); | |||
} | |||
inline void check() { | |||
if (mysql_ping(&_sql) != 0) { | |||
throw SqlException("mysql_ping", "Mysql connection ping failed"); | |||
} | |||
} | |||
int doQuery(const std::string &sql) { | |||
return mysql_query(&_sql, sql.data()); | |||
} | |||
int doQuery(const char *sql) { | |||
return mysql_query(&_sql, sql); | |||
} | |||
private: | |||
MYSQL _sql; | |||
}; | |||
} /* namespace toolkit */ | |||
#endif /* SQL_SQLCONNECTION_H_ */ |
@@ -0,0 +1,27 @@ | |||
/* | |||
* 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. | |||
*/ | |||
#if defined(ENABLE_MYSQL) | |||
#include <memory> | |||
#include "util.h" | |||
#include "onceToken.h" | |||
#include "SqlPool.h" | |||
using namespace std; | |||
namespace toolkit { | |||
INSTANCE_IMP(SqlPool) | |||
} /* namespace toolkit */ | |||
#endif// defined(ENABLE_MYSQL) | |||
@@ -0,0 +1,307 @@ | |||
/* | |||
* 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 SQL_SQLPOOL_H_ | |||
#define SQL_SQLPOOL_H_ | |||
#include <deque> | |||
#include <mutex> | |||
#include <memory> | |||
#include <sstream> | |||
#include <functional> | |||
#include "logger.h" | |||
#include "Poller/Timer.h" | |||
#include "SqlConnection.h" | |||
#include "Thread/WorkThreadPool.h" | |||
#include "ResourcePool.h" | |||
namespace toolkit { | |||
class SqlPool : public std::enable_shared_from_this<SqlPool> { | |||
public: | |||
using Ptr = std::shared_ptr<SqlPool>; | |||
using PoolType = ResourcePool<SqlConnection>; | |||
using SqlRetType = std::vector<std::vector<std::string> >; | |||
static SqlPool &Instance(); | |||
~SqlPool() { | |||
_timer.reset(); | |||
flushError(); | |||
_threadPool.reset(); | |||
_pool.reset(); | |||
InfoL; | |||
} | |||
/** | |||
* 设置循环池对象个数 | |||
* @param size | |||
*/ | |||
void setSize(int size) { | |||
checkInited(); | |||
_pool->setSize(size); | |||
} | |||
/** | |||
* 初始化循环池,设置数据库连接参数 | |||
* @tparam Args | |||
* @param arg | |||
*/ | |||
template<typename ...Args> | |||
void Init(Args &&...arg) { | |||
_pool.reset(new PoolType(std::forward<Args>(arg)...)); | |||
_pool->obtain(); | |||
} | |||
/** | |||
* 异步执行sql | |||
* @param str sql语句 | |||
* @param tryCnt 重试次数 | |||
*/ | |||
template<typename ...Args> | |||
void asyncQuery(Args &&...args) { | |||
asyncQuery_l(SqlConnection::queryString(std::forward<Args>(args)...)); | |||
} | |||
/** | |||
* 同步执行sql | |||
* @tparam Args 可变参数类型列表 | |||
* @param arg 可变参数列表 | |||
* @return 影响行数 | |||
*/ | |||
template<typename ...Args> | |||
int64_t syncQuery(Args &&...arg) { | |||
checkInited(); | |||
typename PoolType::ValuePtr mysql; | |||
try { | |||
//捕获执行异常 | |||
mysql = _pool->obtain(); | |||
return mysql->query(std::forward<Args>(arg)...); | |||
} catch (std::exception &e) { | |||
mysql.quit(); | |||
throw; | |||
} | |||
} | |||
/** | |||
* sql转义 | |||
* @param str | |||
* @return | |||
*/ | |||
std::string escape(const std::string &str) { | |||
checkInited(); | |||
return _pool->obtain()->escape(const_cast<std::string &>(str)); | |||
} | |||
private: | |||
SqlPool() { | |||
_threadPool = WorkThreadPool::Instance().getExecutor(); | |||
_timer = std::make_shared<Timer>(30, [this]() { | |||
flushError(); | |||
return true; | |||
}, nullptr); | |||
} | |||
/** | |||
* 异步执行sql | |||
* @param sql sql语句 | |||
* @param tryCnt 重试次数 | |||
*/ | |||
void asyncQuery_l(const std::string &sql, int tryCnt = 3) { | |||
auto lam = [this, sql, tryCnt]() { | |||
int64_t rowID; | |||
auto cnt = tryCnt - 1; | |||
try { | |||
syncQuery(rowID, sql); | |||
} catch (std::exception &ex) { | |||
if (cnt > 0) { | |||
//失败重试 | |||
std::lock_guard<std::mutex> lk(_error_query_mutex); | |||
sqlQuery query(sql, cnt); | |||
_error_query.push_back(query); | |||
} else { | |||
WarnL << "SqlPool::syncQuery failed: " << ex.what(); | |||
} | |||
} | |||
}; | |||
_threadPool->async(lam); | |||
} | |||
/** | |||
* 定时重试失败的sql | |||
*/ | |||
void flushError() { | |||
decltype(_error_query) query_copy; | |||
{ | |||
std::lock_guard<std::mutex> lck(_error_query_mutex); | |||
query_copy.swap(_error_query); | |||
} | |||
for (auto &query : query_copy) { | |||
asyncQuery(query.sql_str, query.tryCnt); | |||
} | |||
} | |||
/** | |||
* 检查数据库连接池是否初始化 | |||
*/ | |||
void checkInited() { | |||
if (!_pool) { | |||
throw SqlException("SqlPool::checkInited", "Mysql connection pool not initialized"); | |||
} | |||
} | |||
private: | |||
struct sqlQuery { | |||
sqlQuery(const std::string &sql, int cnt) : sql_str(sql), tryCnt(cnt) {} | |||
std::string sql_str; | |||
int tryCnt = 0; | |||
}; | |||
private: | |||
std::deque<sqlQuery> _error_query; | |||
TaskExecutor::Ptr _threadPool; | |||
std::mutex _error_query_mutex; | |||
std::shared_ptr<PoolType> _pool; | |||
Timer::Ptr _timer; | |||
}; | |||
/** | |||
* Sql语句生成器,通过占位符'?'的方式生成sql语句 | |||
*/ | |||
class SqlStream { | |||
public: | |||
SqlStream(const char *sql) : _sql(sql) {} | |||
~SqlStream() {} | |||
template<typename T> | |||
SqlStream &operator<<(T &&data) { | |||
auto pos = _sql.find('?', _startPos); | |||
if (pos == std::string::npos) { | |||
return *this; | |||
} | |||
_str_tmp.str(""); | |||
_str_tmp << std::forward<T>(data); | |||
std::string str = SqlPool::Instance().escape(_str_tmp.str()); | |||
_startPos = pos + str.size(); | |||
_sql.replace(pos, 1, str); | |||
return *this; | |||
} | |||
const std::string &operator<<(std::ostream &(*f)(std::ostream &)) const { | |||
return _sql; | |||
} | |||
operator std::string() { | |||
return _sql; | |||
} | |||
private: | |||
std::stringstream _str_tmp; | |||
std::string _sql; | |||
std::string::size_type _startPos = 0; | |||
}; | |||
/** | |||
* sql查询器 | |||
*/ | |||
class SqlWriter { | |||
public: | |||
/** | |||
* 构造函数 | |||
* @param sql 带'?'占位符的sql模板 | |||
* @param throwAble 是否抛异常 | |||
*/ | |||
SqlWriter(const char *sql, bool throwAble = true) : _sqlstream(sql), _throwAble(throwAble) {} | |||
~SqlWriter() {} | |||
/** | |||
* 输入参数替换占位符'?'以便生成sql语句;可能抛异常 | |||
* @tparam T 参数类型 | |||
* @param data 参数 | |||
* @return 本身引用 | |||
*/ | |||
template<typename T> | |||
SqlWriter &operator<<(T &&data) { | |||
try { | |||
_sqlstream << std::forward<T>(data); | |||
} catch (std::exception &ex) { | |||
//在转义sql时可能抛异常 | |||
if (!_throwAble) { | |||
WarnL << "Commit sql failed: " << ex.what(); | |||
} else { | |||
throw; | |||
} | |||
} | |||
return *this; | |||
} | |||
/** | |||
* 异步执行sql,不会抛异常 | |||
* @param f std::endl | |||
*/ | |||
void operator<<(std::ostream &(*f)(std::ostream &)) { | |||
//异步执行sql不会抛异常 | |||
SqlPool::Instance().asyncQuery((std::string) _sqlstream); | |||
} | |||
/** | |||
* 同步执行sql,可能抛异常 | |||
* @tparam Row 数据行类型,可以是vector<string>/list<string>等支持 obj.emplace_back("value")操作的数据类型 | |||
* 也可以是map<string,string>/Json::Value 等支持 obj["key"] = "value"操作的数据类型 | |||
* @param ret 数据存放对象 | |||
* @return 影响行数 | |||
*/ | |||
template<typename Row> | |||
int64_t operator<<(std::vector<Row> &ret) { | |||
try { | |||
_affectedRows = SqlPool::Instance().syncQuery(_rowId, ret, (std::string) _sqlstream); | |||
} catch (std::exception &ex) { | |||
if (!_throwAble) { | |||
WarnL << "SqlPool::syncQuery failed: " << ex.what(); | |||
} else { | |||
throw; | |||
} | |||
} | |||
return _affectedRows; | |||
} | |||
/** | |||
* 在insert数据库时返回插入的rowid | |||
* @return | |||
*/ | |||
int64_t getRowID() const { | |||
return _rowId; | |||
} | |||
/** | |||
* 返回影响数据库数据行数 | |||
* @return | |||
*/ | |||
int64_t getAffectedRows() const { | |||
return _affectedRows; | |||
} | |||
private: | |||
SqlStream _sqlstream; | |||
int64_t _rowId = -1; | |||
int64_t _affectedRows = -1; | |||
bool _throwAble = true; | |||
}; | |||
} /* namespace toolkit */ | |||
#endif /* SQL_SQLPOOL_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 UTIL_TIMETICKER_H_ | |||
#define UTIL_TIMETICKER_H_ | |||
#include <cassert> | |||
#include "logger.h" | |||
namespace toolkit { | |||
class Ticker { | |||
public: | |||
/** | |||
* 此对象可以用于代码执行时间统计,以可以用于一般计时 | |||
* @param min_ms 开启码执行时间统计时,如果代码执行耗时超过该参数,则打印警告日志 | |||
* @param ctx 日志上下文捕获,用于捕获当前日志代码所在位置 | |||
* @param print_log 是否打印代码执行时间 | |||
*/ | |||
Ticker(uint64_t min_ms = 0, | |||
LogContextCapture ctx = LogContextCapture(Logger::Instance(), LWarn, __FILE__, "", __LINE__), | |||
bool print_log = false) : _ctx(std::move(ctx)) { | |||
if (!print_log) { | |||
_ctx.clear(); | |||
} | |||
_created = _begin = getCurrentMillisecond(); | |||
_min_ms = min_ms; | |||
} | |||
~Ticker() { | |||
uint64_t tm = createdTime(); | |||
if (tm > _min_ms) { | |||
_ctx << "take time: " << tm << "ms" << ", thread may be overloaded"; | |||
} else { | |||
_ctx.clear(); | |||
} | |||
} | |||
/** | |||
* 获取上次resetTime后至今的时间,单位毫秒 | |||
*/ | |||
uint64_t elapsedTime() const { | |||
return getCurrentMillisecond() - _begin; | |||
} | |||
/** | |||
* 获取从创建至今的时间,单位毫秒 | |||
*/ | |||
uint64_t createdTime() const { | |||
return getCurrentMillisecond() - _created; | |||
} | |||
/** | |||
* 重置计时器 | |||
*/ | |||
void resetTime() { | |||
_begin = getCurrentMillisecond(); | |||
} | |||
private: | |||
uint64_t _min_ms; | |||
uint64_t _begin; | |||
uint64_t _created; | |||
LogContextCapture _ctx; | |||
}; | |||
class SmoothTicker { | |||
public: | |||
/** | |||
* 此对象用于生成平滑的时间戳 | |||
* @param reset_ms 时间戳重置间隔,没间隔reset_ms毫秒, 生成的时间戳会同步一次系统时间戳 | |||
*/ | |||
SmoothTicker(uint64_t reset_ms = 10000) { | |||
_reset_ms = reset_ms; | |||
_ticker.resetTime(); | |||
} | |||
~SmoothTicker() {} | |||
/** | |||
* 返回平滑的时间戳,防止由于网络抖动导致时间戳不平滑 | |||
*/ | |||
uint64_t elapsedTime() { | |||
auto now_time = _ticker.elapsedTime(); | |||
if (_first_time == 0) { | |||
if (now_time < _last_time) { | |||
auto last_time = _last_time - _time_inc; | |||
double elapse_time = (now_time - last_time); | |||
_time_inc += (elapse_time / ++_pkt_count) / 3; | |||
auto ret_time = last_time + _time_inc; | |||
_last_time = (uint64_t) ret_time; | |||
return (uint64_t) ret_time; | |||
} | |||
_first_time = now_time; | |||
_last_time = now_time; | |||
_pkt_count = 0; | |||
_time_inc = 0; | |||
return now_time; | |||
} | |||
auto elapse_time = (now_time - _first_time); | |||
_time_inc += elapse_time / ++_pkt_count; | |||
auto ret_time = _first_time + _time_inc; | |||
if (elapse_time > _reset_ms) { | |||
_first_time = 0; | |||
} | |||
_last_time = (uint64_t) ret_time; | |||
return (uint64_t) ret_time; | |||
} | |||
/** | |||
* 时间戳重置为0开始 | |||
*/ | |||
void resetTime() { | |||
_first_time = 0; | |||
_pkt_count = 0; | |||
_ticker.resetTime(); | |||
} | |||
private: | |||
double _time_inc = 0; | |||
uint64_t _first_time = 0; | |||
uint64_t _last_time = 0; | |||
uint64_t _pkt_count = 0; | |||
uint64_t _reset_ms; | |||
Ticker _ticker; | |||
}; | |||
#if !defined(NDEBUG) | |||
#define TimeTicker() Ticker __ticker(5,WarnL,true) | |||
#define TimeTicker1(tm) Ticker __ticker1(tm,WarnL,true) | |||
#define TimeTicker2(tm, log) Ticker __ticker2(tm,log,true) | |||
#else | |||
#define TimeTicker() | |||
#define TimeTicker1(tm) | |||
#define TimeTicker2(tm,log) | |||
#endif | |||
} /* namespace toolkit */ | |||
#endif /* UTIL_TIMETICKER_H_ */ |
@@ -0,0 +1,202 @@ | |||
/* | |||
* Copyright (c) 2006 Ryan Martell. (rdm4@martellventures.com) | |||
* | |||
* This file is part of FFmpeg. | |||
* | |||
* FFmpeg is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 2.1 of the License, or (at your option) any later version. | |||
* | |||
* FFmpeg is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public | |||
* License along with FFmpeg; if not, write to the Free Software | |||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |||
*/ | |||
/** | |||
* @file | |||
* @brief Base64 encode/decode | |||
* @author Ryan Martell <rdm4@martellventures.com> (with lots of Michael) | |||
*/ | |||
//#include "common.h" | |||
#include "stdio.h" | |||
#include "base64.h" | |||
#include <memory> | |||
#include <limits.h> | |||
using namespace std; | |||
/* ---------------- private code */ | |||
static const uint8_t map2[] = | |||
{ | |||
0x3e, 0xff, 0xff, 0xff, 0x3f, 0x34, 0x35, 0x36, | |||
0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0xff, | |||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, | |||
0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, | |||
0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, | |||
0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, | |||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1a, 0x1b, | |||
0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, | |||
0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, | |||
0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33 | |||
}; | |||
int av_base64_decode(uint8_t *out, const char *in, int out_size) | |||
{ | |||
int i, v; | |||
uint8_t *dst = out; | |||
v = 0; | |||
for (i = 0; in[i] && in[i] != '='; i++) { | |||
unsigned int index= in[i]-43; | |||
if (index>=FF_ARRAY_ELEMS(map2) || map2[index] == 0xff) | |||
return -1; | |||
v = (v << 6) + map2[index]; | |||
if (i & 3) { | |||
if (dst - out < out_size) { | |||
*dst++ = v >> (6 - 2 * (i & 3)); | |||
} | |||
} | |||
} | |||
return dst - out; | |||
} | |||
/***************************************************************************** | |||
* b64_encode: Stolen from VLC's http.c. | |||
* Simplified by Michael. | |||
* Fixed edge cases and made it work from data (vs. strings) by Ryan. | |||
*****************************************************************************/ | |||
char *av_base64_encode_l(char *out, int *out_size, const uint8_t *in, int in_size) { | |||
static const char b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; | |||
char *ret, *dst; | |||
unsigned i_bits = 0; | |||
int i_shift = 0; | |||
int bytes_remaining = in_size; | |||
if ((size_t)in_size >= UINT_MAX / 4 || *out_size < AV_BASE64_SIZE(in_size)) { | |||
return nullptr; | |||
} | |||
ret = dst = out; | |||
while (bytes_remaining) { | |||
i_bits = (i_bits << 8) + *in++; | |||
bytes_remaining--; | |||
i_shift += 8; | |||
do { | |||
*dst++ = b64[(i_bits << 6 >> i_shift) & 0x3f]; | |||
i_shift -= 6; | |||
} while (i_shift > 6 || (bytes_remaining == 0 && i_shift > 0)); | |||
} | |||
while ((dst - ret) & 3) | |||
*dst++ = '='; | |||
*dst = '\0'; | |||
*out_size = dst - out; | |||
return ret; | |||
} | |||
char *av_base64_encode(char *out, int out_size, const uint8_t *in, int in_size) { | |||
return av_base64_encode_l(out, &out_size, in, in_size); | |||
} | |||
string encodeBase64(const string &txt) { | |||
if (txt.empty()) { | |||
return ""; | |||
} | |||
int size = AV_BASE64_SIZE(txt.size()) + 10; | |||
string ret; | |||
ret.resize(size); | |||
if (!av_base64_encode_l((char *) ret.data(), &size, (const uint8_t *) txt.data(), txt.size())) { | |||
return ""; | |||
} | |||
ret.resize(size); | |||
return ret; | |||
} | |||
string decodeBase64(const string &txt){ | |||
if (txt.empty()) { | |||
return ""; | |||
} | |||
string ret; | |||
ret.resize(txt.size() * 3 / 4 + 10); | |||
auto size = av_base64_decode((uint8_t *) ret.data(), txt.data(), ret.size()); | |||
if (size <= 0) { | |||
return ""; | |||
} | |||
ret.resize(size); | |||
return ret; | |||
} | |||
#ifdef TEST | |||
#undef printf | |||
#define MAX_DATA_SIZE 1024 | |||
#define MAX_ENCODED_SIZE 2048 | |||
static int test_encode_decode(const uint8_t *data, unsigned int data_size, | |||
const char *encoded_ref) | |||
{ | |||
char encoded[MAX_ENCODED_SIZE]; | |||
uint8_t data2[MAX_DATA_SIZE]; | |||
int data2_size, max_data2_size = MAX_DATA_SIZE; | |||
if (!av_base64_encode(encoded, MAX_ENCODED_SIZE, data, data_size)) { | |||
printf("Failed: cannot encode the input data\n"); | |||
return 1; | |||
} | |||
if (encoded_ref && strcmp(encoded, encoded_ref)) { | |||
printf("Failed: encoded string differs from reference\n" | |||
"Encoded:\n%s\nReference:\n%s\n", encoded, encoded_ref); | |||
return 1; | |||
} | |||
if ((data2_size = av_base64_decode(data2, encoded, max_data2_size)) < 0) { | |||
printf("Failed: cannot decode the encoded string\n" | |||
"Encoded:\n%s\n", encoded); | |||
return 1; | |||
} | |||
if (memcmp(data2, data, data_size)) { | |||
printf("Failed: encoded/decoded data differs from original data\n"); | |||
return 1; | |||
} | |||
printf("Passed!\n"); | |||
return 0; | |||
} | |||
int main(void) | |||
{ | |||
int i, error_count = 0; | |||
struct test { | |||
const uint8_t *data; | |||
const char *encoded_ref; | |||
} tests[] = { | |||
{ "", ""}, | |||
{ "1", "MQ=="}, | |||
{ "22", "MjI="}, | |||
{ "333", "MzMz"}, | |||
{ "4444", "NDQ0NA=="}, | |||
{ "55555", "NTU1NTU="}, | |||
{ "666666", "NjY2NjY2"}, | |||
{ "abc:def", "YWJjOmRlZg=="}, | |||
}; | |||
printf("Encoding/decoding tests\n"); | |||
for (i = 0; i < FF_ARRAY_ELEMS(tests); i++) | |||
error_count += test_encode_decode(tests[i].data, strlen(tests[i].data), tests[i].encoded_ref); | |||
return error_count; | |||
} | |||
#endif |
@@ -0,0 +1,71 @@ | |||
/* | |||
* Copyright (c) 2006 Ryan Martell. (rdm4@martellventures.com) | |||
* | |||
* This file is part of FFmpeg. | |||
* | |||
* FFmpeg is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 2.1 of the License, or (at your option) any later version. | |||
* | |||
* FFmpeg is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public | |||
* License along with FFmpeg; if not, write to the Free Software | |||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |||
*/ | |||
#define FF_ARRAY_ELEMS(a) (sizeof(a) / sizeof((a)[0])) | |||
#ifndef AVUTIL_BASE64_H | |||
#define AVUTIL_BASE64_H | |||
#include <cstdint> | |||
#include <string> | |||
/** | |||
* Decode a base64-encoded string. | |||
* | |||
* @param out buffer for decoded data | |||
* @param in null-terminated input string | |||
* @param out_size size in bytes of the out buffer, must be at | |||
* least 3/4 of the length of in | |||
* @return number of bytes written, or a negative value in case of | |||
* invalid input | |||
*/ | |||
int av_base64_decode(uint8_t *out, const char *in, int out_size); | |||
/** | |||
* Encode data to base64 and null-terminate. | |||
* | |||
* @param out buffer for encoded data | |||
* @param out_size size in bytes of the output buffer, must be at | |||
* least AV_BASE64_SIZE(in_size) | |||
* @param in_size size in bytes of the 'in' buffer | |||
* @return 'out' or NULL in case of error | |||
*/ | |||
char *av_base64_encode(char *out, int out_size, const uint8_t *in, int in_size); | |||
/** | |||
* Calculate the output size needed to base64-encode x bytes. | |||
*/ | |||
#define AV_BASE64_SIZE(x) (((x)+2) / 3 * 4 + 1) | |||
/** | |||
* 编码base64 | |||
* @param txt 明文 | |||
* @return 密文 | |||
*/ | |||
std::string encodeBase64(const std::string &txt); | |||
/** | |||
* 解码base64 | |||
* @param txt 密文 | |||
* @return 明文 | |||
*/ | |||
std::string decodeBase64(const std::string &txt); | |||
#endif /* AVUTIL_BASE64_H */ |
@@ -0,0 +1,54 @@ | |||
#ifndef SRC_UTIL_FUNCTION_TRAITS_H_ | |||
#define SRC_UTIL_FUNCTION_TRAITS_H_ | |||
#include <tuple> | |||
namespace toolkit { | |||
template<typename T> | |||
struct function_traits; | |||
//普通函数 | |||
template<typename Ret, typename... Args> | |||
struct function_traits<Ret(Args...)> | |||
{ | |||
public: | |||
enum { arity = sizeof...(Args) }; | |||
typedef Ret function_type(Args...); | |||
typedef Ret return_type; | |||
using stl_function_type = std::function<function_type>; | |||
typedef Ret(*pointer)(Args...); | |||
template<size_t I> | |||
struct args | |||
{ | |||
static_assert(I < arity, "index is out of range, index must less than sizeof Args"); | |||
using type = typename std::tuple_element<I, std::tuple<Args...> >::type; | |||
}; | |||
}; | |||
//函数指针 | |||
template<typename Ret, typename... Args> | |||
struct function_traits<Ret(*)(Args...)> : function_traits<Ret(Args...)>{}; | |||
//std::function | |||
template <typename Ret, typename... Args> | |||
struct function_traits<std::function<Ret(Args...)>> : function_traits<Ret(Args...)>{}; | |||
//member function | |||
#define FUNCTION_TRAITS(...) \ | |||
template <typename ReturnType, typename ClassType, typename... Args>\ | |||
struct function_traits<ReturnType(ClassType::*)(Args...) __VA_ARGS__> : function_traits<ReturnType(Args...)>{}; \ | |||
FUNCTION_TRAITS() | |||
FUNCTION_TRAITS(const) | |||
FUNCTION_TRAITS(volatile) | |||
FUNCTION_TRAITS(const volatile) | |||
//函数对象 | |||
template<typename Callable> | |||
struct function_traits : function_traits<decltype(&Callable::operator())>{}; | |||
} /* namespace toolkit */ | |||
#endif /* SRC_UTIL_FUNCTION_TRAITS_H_ */ |