复现已有算法
This commit is contained in:
+151
@@ -0,0 +1,151 @@
|
||||
---
|
||||
Language: Cpp
|
||||
# BasedOnStyle: Google
|
||||
AccessModifierOffset: -2
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignConsecutiveAssignments: false
|
||||
AlignConsecutiveDeclarations: false
|
||||
AlignEscapedNewlines: Left
|
||||
AlignOperands: true
|
||||
AlignTrailingComments: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
AllowShortBlocksOnASingleLine: false
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: None
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: true
|
||||
AlwaysBreakTemplateDeclarations: true
|
||||
BinPackArguments: false
|
||||
BinPackParameters: false
|
||||
BraceWrapping:
|
||||
AfterClass: true
|
||||
AfterControlStatement: Always
|
||||
AfterEnum: true
|
||||
AfterFunction: true
|
||||
AfterNamespace: true
|
||||
AfterObjCDeclaration: false
|
||||
AfterStruct: true
|
||||
AfterUnion: true
|
||||
AfterExternBlock: true
|
||||
BeforeCatch: true
|
||||
BeforeElse: true
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: true
|
||||
SplitEmptyRecord: true
|
||||
SplitEmptyNamespace: true
|
||||
AfterCaseLabel: true
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeBraces: Custom
|
||||
BreakBeforeInheritanceComma: false
|
||||
BreakInheritanceList: BeforeColon
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializersBeforeComma: false
|
||||
BreakConstructorInitializers: BeforeColon
|
||||
BreakAfterJavaFieldAnnotations: false
|
||||
BreakStringLiterals: true
|
||||
ColumnLimit: 80
|
||||
CommentPragmas: '^ IWYU pragma:'
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: true
|
||||
DerivePointerAlignment: true
|
||||
DisableFormat: false
|
||||
FixNamespaceComments: true
|
||||
ForEachMacros:
|
||||
- foreach
|
||||
- Q_FOREACH
|
||||
- BOOST_FOREACH
|
||||
IncludeBlocks: Preserve
|
||||
IncludeCategories:
|
||||
- Regex: '^<ext/.*\.h>'
|
||||
Priority: 2
|
||||
- Regex: '^<.*\.h>'
|
||||
Priority: 1
|
||||
- Regex: '^<.*'
|
||||
Priority: 2
|
||||
- Regex: '.*'
|
||||
Priority: 3
|
||||
IncludeIsMainRegex: '([-_](test|unittest))?$'
|
||||
IndentCaseLabels: true
|
||||
IndentPPDirectives: None
|
||||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: false
|
||||
JavaScriptQuotes: Leave
|
||||
JavaScriptWrapImports: true
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
MacroBlockBegin: ''
|
||||
MacroBlockEnd: ''
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
ObjCBinPackProtocolList: Never
|
||||
ObjCBlockIndentWidth: 2
|
||||
ObjCSpaceAfterProperty: false
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
PenaltyBreakAssignment: 2
|
||||
PenaltyBreakBeforeFirstCallParameter: 100
|
||||
PenaltyBreakComment: 300
|
||||
PenaltyBreakFirstLessLess: 120
|
||||
PenaltyBreakString: 1000
|
||||
PenaltyBreakTemplateDeclaration: 10
|
||||
PenaltyExcessCharacter: 1000000
|
||||
PenaltyReturnTypeOnItsOwnLine: 2000
|
||||
PointerAlignment: Left
|
||||
RawStringFormats:
|
||||
- Language: Cpp
|
||||
Delimiters:
|
||||
- cc
|
||||
- CC
|
||||
- cpp
|
||||
- Cpp
|
||||
- CPP
|
||||
- 'c++'
|
||||
- 'C++'
|
||||
CanonicalDelimiter: ''
|
||||
BasedOnStyle: google
|
||||
- Language: TextProto
|
||||
Delimiters:
|
||||
- pb
|
||||
- PB
|
||||
- proto
|
||||
- PROTO
|
||||
EnclosingFunctions:
|
||||
- EqualsProto
|
||||
- EquivToProto
|
||||
- PARSE_PARTIAL_TEXT_PROTO
|
||||
- PARSE_TEST_PROTO
|
||||
- PARSE_TEXT_PROTO
|
||||
- ParseTextOrDie
|
||||
- ParseTextProtoOrDie
|
||||
CanonicalDelimiter: ''
|
||||
BasedOnStyle: google
|
||||
ReflowComments: true
|
||||
SortIncludes: false
|
||||
SortUsingDeclarations: true
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterTemplateKeyword: true
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCpp11BracedList: false
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 2
|
||||
SpacesInAngles: false
|
||||
SpacesInContainerLiterals: true
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: Auto
|
||||
StatementMacros:
|
||||
- Q_UNUSED
|
||||
- QT_REQUIRE_VERSION
|
||||
TabWidth: 8
|
||||
UseTab: Never
|
||||
...
|
||||
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
# notes for additional commands
|
||||
#
|
||||
# nargs: '*' to allow multiple arguments
|
||||
# kwargs: &fookwargs to definite keyword arguments
|
||||
# kwargs: *fookwargs to use the same keyword arguments as fookwargs
|
||||
# NAME: 1 to allow single keyword arguments
|
||||
# NAME: + to allow multiple keyword arguments
|
||||
# NAME: * to allow multiple keyword arguments
|
||||
# spelling: FOO to use foo to FOO spelling
|
||||
|
||||
parse:
|
||||
additional_commands:
|
||||
FetchContent_Declare:
|
||||
pargs:
|
||||
nargs: '*'
|
||||
flags: []
|
||||
kwargs:
|
||||
GIT_TAG: 1
|
||||
GITHUB_REPOSITORY: 1
|
||||
GITLAB_REPOSITORY: 1
|
||||
GIT_REPOSITORY: 1
|
||||
SVN_REPOSITORY: 1
|
||||
SVN_REVISION: 1
|
||||
URL: 1
|
||||
URL_HASH: 1
|
||||
URL_MD5: 1
|
||||
FIND_PACKAGE_ARGS: +
|
||||
FetchContent_MakeAvailable:
|
||||
pargs:
|
||||
nargs: '*'
|
||||
flags: []
|
||||
execute_process:
|
||||
pargs:
|
||||
nargs: '*'
|
||||
flags: []
|
||||
kwargs:
|
||||
COMMAND: +
|
||||
WORKING_DIRECTORY: 1
|
||||
set_target_properties:
|
||||
pargs:
|
||||
nargs: '*'
|
||||
flags: []
|
||||
kwargs:
|
||||
PROPERTIES: +
|
||||
IMPORTED_LOCATION: 1
|
||||
INTERFACE_INCLUDE_DIRECTORIES: 1
|
||||
format:
|
||||
tab_size: 2
|
||||
line_width: 120
|
||||
autosort: true
|
||||
dangle_parens: true
|
||||
max_subgroups_hwrap: 2
|
||||
max_pargs_hwrap: 3
|
||||
@@ -0,0 +1,31 @@
|
||||
name: Lint source code
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
unix:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
steps:
|
||||
- name: Checkout Trantor source code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
fetch-depth: 0
|
||||
|
||||
- name: (Linux) Install dependencies
|
||||
run: |
|
||||
# Installing packages might fail as the github image becomes outdated
|
||||
sudo apt update
|
||||
sudo apt install dos2unix clang-format
|
||||
pip install cmake-format
|
||||
|
||||
- name: Lint
|
||||
run: ./format.sh && git diff --exit-code
|
||||
@@ -0,0 +1,71 @@
|
||||
name: Build macos-clang
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: "${{matrix.link}}-${{matrix.build-type}}-${{matrix.tls-provider}}"
|
||||
runs-on: macos-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
link: ["STATIC", "SHARED"]
|
||||
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
|
||||
build-type: ["Debug", "Release"]
|
||||
tls-provider: ['', 'openssl', 'botan']
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
# botan v3
|
||||
run: |
|
||||
brew install botan spdlog
|
||||
|
||||
- name: Install gtest
|
||||
run: |
|
||||
wget https://github.com/google/googletest/archive/refs/tags/v1.13.0.tar.gz
|
||||
tar xf v1.13.0.tar.gz
|
||||
cd googletest-1.13.0
|
||||
cmake .
|
||||
make && sudo make install
|
||||
|
||||
- name: Checkout Trantor source code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Create build directory
|
||||
run: |
|
||||
mkdir build
|
||||
|
||||
- name: Create Build Environment & Configure Cmake
|
||||
shell: bash
|
||||
working-directory: ./build
|
||||
run: |
|
||||
[[ ${{ matrix.link }} == "SHARED" ]] && shared="ON" || shared="OFF"
|
||||
cmake .. \
|
||||
-DTRANTOR_USE_TLS=${{matrix.tls-provider}} \
|
||||
-DCMAKE_BUILD_TYPE=${{matrix.build-type}} \
|
||||
-DBUILD_SHARED_LIBS=$shared \
|
||||
-DCMAKE_INSTALL_PREFIX=../install \
|
||||
-DUSE_SPDLOG=ON \
|
||||
-DBUILD_TESTING=ON \
|
||||
|
||||
- name: Build
|
||||
shell: bash
|
||||
working-directory: ./build
|
||||
# Execute the build. You can specify a specific target with "--target <NAME>"
|
||||
run: |
|
||||
sudo make && sudo make install
|
||||
|
||||
- name: Test
|
||||
working-directory: ./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: |
|
||||
make test
|
||||
@@ -0,0 +1,88 @@
|
||||
name: Build rockylinux-gcc
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: '${{matrix.link}}-${{matrix.build-type}}-${{matrix.tls-provider}}'
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: rockylinux:9.3
|
||||
options: --user root
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
link: [ 'STATIC', 'SHARED' ]
|
||||
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
|
||||
build-type: ['Debug', 'Release']
|
||||
# TODO: ubuntu botan is v2, v2 support is removed
|
||||
# tls-provider: ['', 'openssl', 'botan']
|
||||
tls-provider: ['', 'openssl']
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
dnf install gcc-c++ cmake git wget -y
|
||||
|
||||
- name: Install dependencies - spdlog
|
||||
run: |
|
||||
git clone https://github.com/gabime/spdlog.git
|
||||
cd spdlog && mkdir build && cd build
|
||||
cmake .. && make -j
|
||||
|
||||
- name: Install dependencies - OpenSSL
|
||||
if: matrix.tls-provider == 'openssl'
|
||||
run: |
|
||||
dnf install openssl-devel -y
|
||||
|
||||
- name: Install gtest
|
||||
run: |
|
||||
wget https://github.com/google/googletest/archive/refs/tags/v1.13.0.tar.gz
|
||||
tar xf v1.13.0.tar.gz
|
||||
cd googletest-1.13.0
|
||||
cmake .
|
||||
make -j && make install
|
||||
|
||||
- name: Checkout Trantor source code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Create build directory
|
||||
run: |
|
||||
mkdir build
|
||||
|
||||
- name: Create Build Environment & Configure Cmake
|
||||
shell: bash
|
||||
working-directory: ./build
|
||||
if: ${{matrix.link}} == "SHARED"
|
||||
run: |
|
||||
[[ ${{ matrix.link }} == "SHARED" ]] && shared="ON" || shared="OFF"
|
||||
cmake .. \
|
||||
-DTRANTOR_USE_TLS=${{matrix.tls-provider}} \
|
||||
-DCMAKE_BUILD_TYPE=${{matrix.build-type}} \
|
||||
-DBUILD_SHARED_LIBS=$shared \
|
||||
-DCMAKE_INSTALL_PREFIX=../install \
|
||||
-DUSE_SPDLOG=ON \
|
||||
-DBUILD_TESTING=ON
|
||||
|
||||
- name: Build
|
||||
shell: bash
|
||||
working-directory: ./build
|
||||
# Execute the build. You can specify a specific target with "--target <NAME>"
|
||||
run: |
|
||||
make && make install
|
||||
|
||||
- name: Test
|
||||
working-directory: ./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: |
|
||||
make test
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
name: Build ubuntu-gcc
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: '${{matrix.link}}-${{matrix.build-type}}-${{matrix.tls-provider}}'
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
link: [ 'STATIC', 'SHARED' ]
|
||||
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
|
||||
build-type: ['Debug', 'Release']
|
||||
# TODO: ubuntu botan is v2, v2 support is removed
|
||||
# tls-provider: ['', 'openssl', 'botan']
|
||||
tls-provider: ['', 'openssl']
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
# Installing packages might fail as the github image becomes outdated
|
||||
sudo apt update
|
||||
sudo apt install libspdlog-dev libfmt-dev
|
||||
|
||||
- name: Install dependencies - OpenSSL
|
||||
if: matrix.tls-provider == 'openssl'
|
||||
run: |
|
||||
sudo apt install openssl libssl-dev
|
||||
|
||||
- name: Install gtest
|
||||
run: |
|
||||
wget https://github.com/google/googletest/archive/refs/tags/v1.13.0.tar.gz
|
||||
tar xf v1.13.0.tar.gz
|
||||
cd googletest-1.13.0
|
||||
cmake .
|
||||
make -j && sudo make install
|
||||
|
||||
- name: Checkout Trantor source code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Create build directory
|
||||
run: |
|
||||
mkdir build
|
||||
|
||||
- name: Create Build Environment & Configure Cmake
|
||||
shell: bash
|
||||
working-directory: ./build
|
||||
if: ${{matrix.link}} == "SHARED"
|
||||
run: |
|
||||
[[ ${{ matrix.link }} == "SHARED" ]] && shared="ON" || shared="OFF"
|
||||
cmake .. \
|
||||
-DTRANTOR_USE_TLS=${{matrix.tls-provider}} \
|
||||
-DCMAKE_BUILD_TYPE=${{matrix.build-type}} \
|
||||
-DBUILD_SHARED_LIBS=$shared \
|
||||
-DCMAKE_INSTALL_PREFIX=../install \
|
||||
-DUSE_SPDLOG=ON \
|
||||
-DBUILD_TESTING=ON
|
||||
|
||||
- name: Build
|
||||
shell: bash
|
||||
working-directory: ./build
|
||||
# Execute the build. You can specify a specific target with "--target <NAME>"
|
||||
run: |
|
||||
sudo make && sudo make install
|
||||
|
||||
- name: Test
|
||||
working-directory: ./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: |
|
||||
make test
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
name: Build windows-msvc
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: '${{matrix.link}}-${{matrix.build-type}}-${{matrix.tls-provider}}'
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
link: [ 'STATIC', 'SHARED' ]
|
||||
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
|
||||
build-type: ['Debug', 'Release']
|
||||
# TODO: conan botan is v2, v2 support is removed
|
||||
# tls-provider: ['', 'openssl', 'botan']
|
||||
tls-provider: ['', 'openssl']
|
||||
|
||||
steps:
|
||||
- name: Checkout Trantor source code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: false
|
||||
|
||||
- name: Create build directory
|
||||
working-directory: ${{env.GITHUB_WORKSPACE}}
|
||||
run: |
|
||||
mkdir build
|
||||
|
||||
- name: Install conan packages
|
||||
shell: bash
|
||||
working-directory: ./build
|
||||
run: |
|
||||
pip install conan
|
||||
conan profile detect --force
|
||||
conan install .. --output-folder=. --build=missing --settings=build_type=${{matrix.build-type}} --settings=compiler="msvc"
|
||||
|
||||
- name: Create Build Environment & Configure Cmake
|
||||
shell: bash
|
||||
working-directory: ./build
|
||||
# -DBUILD_TESTING=ON Removed,
|
||||
# Due to unittest by GTest in windows runner will comes out 'error MSB3073'
|
||||
run: |
|
||||
[[ ${{ matrix.link }} == "SHARED" ]] && shared="ON" || shared="OFF"
|
||||
cmake .. -G "Visual Studio 17 2022" -T host=x64 -A x64 \
|
||||
-DTRANTOR_USE_TLS=${{matrix.tls-provider}} \
|
||||
-DCMAKE_BUILD_TYPE=${{matrix.build-type}} \
|
||||
-DBUILD_SHARED_LIBS=$shared \
|
||||
-DCMAKE_INSTALL_PREFIX=../install \
|
||||
-DUSE_SPDLOG=ON \
|
||||
-DCMAKE_POLICY_DEFAULT_CMP0091=NEW
|
||||
|
||||
- name: Build
|
||||
working-directory: ./build
|
||||
shell: bash
|
||||
# multi config build using --config to switch Release|Debug
|
||||
run: |
|
||||
cmake --build . --config ${{matrix.build-type}} --parallel
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
.idea
|
||||
build
|
||||
cmake-build-debug
|
||||
.vscode
|
||||
.vs
|
||||
CMakeSettings.json
|
||||
.cache
|
||||
+436
@@ -0,0 +1,436 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
project(trantor)
|
||||
|
||||
option(BUILD_DOC "Build Doxygen documentation" OFF)
|
||||
option(BUILD_C-ARES "Build C-ARES" ON)
|
||||
option(BUILD_TESTING "Build tests" OFF)
|
||||
option(BUILD_SHARED_LIBS "Build trantor as a shared lib" OFF)
|
||||
option(TRANTOR_USE_TLS
|
||||
"TLS provider for trantor. Valid options are 'openssl', 'botan' or '' (let the build scripr decide)" ""
|
||||
)
|
||||
option(USE_SPDLOG "Allow using the spdlog logging library" OFF)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake_modules/)
|
||||
|
||||
set(TRANTOR_MAJOR_VERSION 1)
|
||||
set(TRANTOR_MINOR_VERSION 5)
|
||||
set(TRANTOR_PATCH_VERSION 26)
|
||||
set(TRANTOR_VERSION ${TRANTOR_MAJOR_VERSION}.${TRANTOR_MINOR_VERSION}.${TRANTOR_PATCH_VERSION})
|
||||
|
||||
include(GNUInstallDirs)
|
||||
# Offer the user the choice of overriding the installation directories
|
||||
set(INSTALL_BIN_DIR
|
||||
${CMAKE_INSTALL_BINDIR}
|
||||
CACHE PATH "Installation directory for binaries"
|
||||
)
|
||||
set(INSTALL_LIB_DIR
|
||||
${CMAKE_INSTALL_LIBDIR}
|
||||
CACHE PATH "Installation directory for libraries"
|
||||
)
|
||||
set(INSTALL_INCLUDE_DIR
|
||||
${CMAKE_INSTALL_INCLUDEDIR}
|
||||
CACHE PATH "Installation directory for header files"
|
||||
)
|
||||
set(DEF_INSTALL_TRANTOR_CMAKE_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/Trantor)
|
||||
set(INSTALL_TRANTOR_CMAKE_DIR
|
||||
${DEF_INSTALL_TRANTOR_CMAKE_DIR}
|
||||
CACHE PATH "Installation directory for cmake files"
|
||||
)
|
||||
|
||||
add_library(${PROJECT_NAME})
|
||||
if(BUILD_SHARED_LIBS)
|
||||
list(
|
||||
FIND
|
||||
CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES
|
||||
"${CMAKE_INSTALL_PREFIX}/${INSTALL_LIB_DIR}"
|
||||
isSystemDir
|
||||
)
|
||||
if("${isSystemDir}" STREQUAL "-1")
|
||||
set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${INSTALL_LIB_DIR}")
|
||||
endif("${isSystemDir}" STREQUAL "-1")
|
||||
set_target_properties(
|
||||
${PROJECT_NAME}
|
||||
PROPERTIES VERSION
|
||||
${TRANTOR_VERSION}
|
||||
SOVERSION
|
||||
${TRANTOR_MAJOR_VERSION}
|
||||
)
|
||||
if(CMAKE_CXX_COMPILER_ID MATCHES MSVC)
|
||||
# Ignore MSVC C4251 and C4275 warning of exporting std objects with no dll export We export class to facilitate
|
||||
# maintenance, thus if you compile drogon on windows as a shared library, you will need to use exact same compiler
|
||||
# for drogon and your app.
|
||||
target_compile_options(${PROJECT_NAME} PUBLIC /wd4251 /wd4275)
|
||||
endif()
|
||||
endif(BUILD_SHARED_LIBS)
|
||||
|
||||
# Tells Visual Studio 2017 (15.7+) and newer to correctly set the value of the standard __cplusplus macro, instead of
|
||||
# leaving it to 199711L and settings the effective c++ version in _MSVC_LANG Dropping support for older versions of VS
|
||||
# would allow to only rely on __cplusplus
|
||||
if(MSVC AND MSVC_VERSION GREATER_EQUAL 1914)
|
||||
add_compile_options(/Zc:__cplusplus)
|
||||
endif(MSVC AND MSVC_VERSION GREATER_EQUAL 1914)
|
||||
|
||||
if(NOT
|
||||
${CMAKE_SYSTEM_NAME}
|
||||
STREQUAL
|
||||
"Windows"
|
||||
AND CMAKE_CXX_COMPILER_ID MATCHES Clang|GNU
|
||||
)
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -Werror)
|
||||
endif()
|
||||
|
||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Haiku")
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE network)
|
||||
endif()
|
||||
|
||||
include(GenerateExportHeader)
|
||||
generate_export_header(${PROJECT_NAME} EXPORT_FILE_NAME ${CMAKE_CURRENT_BINARY_DIR}/exports/trantor/exports.h)
|
||||
|
||||
# include directories
|
||||
target_include_directories(
|
||||
${PROJECT_NAME}
|
||||
PUBLIC $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}> $<INSTALL_INTERFACE:${INSTALL_INCLUDE_DIR}>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/exports>
|
||||
PRIVATE ${PROJECT_SOURCE_DIR}
|
||||
${PROJECT_SOURCE_DIR}/trantor/utils
|
||||
${PROJECT_SOURCE_DIR}/trantor/net
|
||||
${PROJECT_SOURCE_DIR}/trantor/net/inner
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/third_party/wepoll>
|
||||
)
|
||||
|
||||
if(MINGW)
|
||||
target_compile_definitions(${PROJECT_NAME} PUBLIC -D_WIN32_WINNT=0x0601)
|
||||
endif(MINGW)
|
||||
|
||||
set(TRANTOR_SOURCES
|
||||
trantor/utils/AsyncFileLogger.cc
|
||||
trantor/utils/ConcurrentTaskQueue.cc
|
||||
trantor/utils/Date.cc
|
||||
trantor/utils/LogStream.cc
|
||||
trantor/utils/Logger.cc
|
||||
trantor/utils/MsgBuffer.cc
|
||||
trantor/utils/SerialTaskQueue.cc
|
||||
trantor/utils/TimingWheel.cc
|
||||
trantor/utils/Utilities.cc
|
||||
trantor/net/EventLoop.cc
|
||||
trantor/net/EventLoopThread.cc
|
||||
trantor/net/EventLoopThreadPool.cc
|
||||
trantor/net/InetAddress.cc
|
||||
trantor/net/TcpClient.cc
|
||||
trantor/net/TcpServer.cc
|
||||
trantor/net/Channel.cc
|
||||
trantor/net/inner/Acceptor.cc
|
||||
trantor/net/inner/Connector.cc
|
||||
trantor/net/inner/Poller.cc
|
||||
trantor/net/inner/Socket.cc
|
||||
trantor/net/inner/MemBufferNode.cc
|
||||
trantor/net/inner/StreamBufferNode.cc
|
||||
trantor/net/inner/AsyncStreamBufferNode.cc
|
||||
trantor/net/inner/TcpConnectionImpl.cc
|
||||
trantor/net/inner/Timer.cc
|
||||
trantor/net/inner/TimerQueue.cc
|
||||
trantor/net/inner/poller/EpollPoller.cc
|
||||
trantor/net/inner/poller/KQueue.cc
|
||||
trantor/net/inner/poller/PollPoller.cc
|
||||
)
|
||||
set(private_headers
|
||||
trantor/net/inner/Acceptor.h
|
||||
trantor/net/inner/Connector.h
|
||||
trantor/net/inner/Poller.h
|
||||
trantor/net/inner/Socket.h
|
||||
trantor/net/inner/TcpConnectionImpl.h
|
||||
trantor/net/inner/Timer.h
|
||||
trantor/net/inner/TimerQueue.h
|
||||
trantor/net/inner/poller/EpollPoller.h
|
||||
trantor/net/inner/poller/KQueue.h
|
||||
trantor/net/inner/poller/PollPoller.h
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
set(TRANTOR_SOURCES
|
||||
${TRANTOR_SOURCES}
|
||||
third_party/wepoll/Wepoll.c
|
||||
trantor/utils/WindowsSupport.cc
|
||||
trantor/net/inner/FileBufferNodeWin.cc
|
||||
)
|
||||
set(private_headers ${private_headers} third_party/wepoll/Wepoll.h trantor/utils/WindowsSupport.h)
|
||||
else(WIN32)
|
||||
set(TRANTOR_SOURCES ${TRANTOR_SOURCES} trantor/net/inner/FileBufferNodeUnix.cc)
|
||||
endif(WIN32)
|
||||
|
||||
# Somehow the default value of TRANTOR_USE_TLS is OFF
|
||||
if(TRANTOR_USE_TLS STREQUAL OFF)
|
||||
set(TRANTOR_USE_TLS "")
|
||||
endif()
|
||||
set(VALID_TLS_PROVIDERS "openssl" "botan" "none")
|
||||
list(
|
||||
FIND
|
||||
VALID_TLS_PROVIDERS
|
||||
"${TRANTOR_USE_TLS}"
|
||||
PREFERED_TLS_IDX
|
||||
)
|
||||
if(PREFERED_TLS_IDX EQUAL -1
|
||||
AND NOT
|
||||
TRANTOR_USE_TLS
|
||||
STREQUAL
|
||||
""
|
||||
)
|
||||
message(FATAL_ERROR "Invalid TLS provider: ${TRANTOR_USE_TLS}\n" "Valid TLS providers are: ${VALID_TLS_PROVIDERS}")
|
||||
endif()
|
||||
|
||||
set(TRANTOR_TLS_PROVIDER "None")
|
||||
if(TRANTOR_USE_TLS STREQUAL "openssl" OR TRANTOR_USE_TLS STREQUAL "")
|
||||
find_package(OpenSSL)
|
||||
if(OpenSSL_FOUND)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE OpenSSL::SSL OpenSSL::Crypto)
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE USE_OPENSSL)
|
||||
set(TRANTOR_TLS_PROVIDER "OpenSSL")
|
||||
|
||||
set(TRANTOR_SOURCES ${TRANTOR_SOURCES} trantor/net/inner/tlsprovider/OpenSSLProvider.cc
|
||||
trantor/utils/crypto/openssl.cc
|
||||
)
|
||||
elseif(TRANTOR_USE_TLS STREQUAL "openssl")
|
||||
message(FATAL_ERROR "Requested OpenSSL TLS provider but OpenSSL was not found")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(TRANTOR_TLS_PROVIDER STREQUAL "None" AND (TRANTOR_USE_TLS STREQUAL "botan" OR TRANTOR_USE_TLS STREQUAL ""))
|
||||
find_package(Botan)
|
||||
if(Botan_FOUND)
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE USE_BOTAN)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE Botan::Botan)
|
||||
if(CMAKE_CXX_COMPILER_ID MATCHES Clang|GNU)
|
||||
# Trantor uses some features that are deprecated in C++20 but Botan3 needs C++20
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE -Wno-deprecated)
|
||||
endif()
|
||||
set(TRANTOR_TLS_PROVIDER "Botan")
|
||||
|
||||
set(TRANTOR_SOURCES ${TRANTOR_SOURCES} trantor/net/inner/tlsprovider/BotanTLSProvider.cc
|
||||
trantor/utils/crypto/botan.cc
|
||||
)
|
||||
elseif(TRANTOR_USE_TLS STREQUAL "botan")
|
||||
message(FATAL_ERROR "Requested Botan TLS provider but Botan was not found")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(TRANTOR_TLS_PROVIDER STREQUAL "None")
|
||||
set(TRANTOR_SOURCES
|
||||
${TRANTOR_SOURCES}
|
||||
trantor/utils/crypto/sha3.cc
|
||||
trantor/utils/crypto/md5.cc
|
||||
trantor/utils/crypto/sha1.cc
|
||||
trantor/utils/crypto/sha256.cc
|
||||
trantor/utils/crypto/blake2.cc
|
||||
)
|
||||
set(private_headers
|
||||
${private_headers}
|
||||
trantor/utils/crypto/sha3.h
|
||||
trantor/utils/crypto/md5.h
|
||||
trantor/utils/crypto/sha1.h
|
||||
trantor/utils/crypto/sha256.h
|
||||
)
|
||||
endif()
|
||||
|
||||
message(STATUS "Trantor using SSL library: ${TRANTOR_TLS_PROVIDER}")
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE TRANTOR_TLS_PROVIDER=${TRANTOR_TLS_PROVIDER})
|
||||
|
||||
set(HAVE_SPDLOG NO)
|
||||
if(USE_SPDLOG)
|
||||
find_package(spdlog CONFIG)
|
||||
if(spdlog_FOUND)
|
||||
message(STATUS "spdlog found!")
|
||||
set(HAVE_SPDLOG TRUE)
|
||||
endif(spdlog_FOUND)
|
||||
endif(USE_SPDLOG)
|
||||
if(HAVE_SPDLOG)
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC spdlog::spdlog_header_only)
|
||||
target_compile_definitions(${PROJECT_NAME} PUBLIC TRANTOR_SPDLOG_SUPPORT SPDLOG_FMT_EXTERNAL_HO FMT_HEADER_ONLY)
|
||||
endif(HAVE_SPDLOG)
|
||||
|
||||
set(HAVE_C-ARES NO)
|
||||
if(BUILD_C-ARES)
|
||||
find_package(c-ares)
|
||||
if(c-ares_FOUND)
|
||||
message(STATUS "c-ares found!")
|
||||
set(HAVE_C-ARES TRUE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(HAVE_C-ARES)
|
||||
if(NOT BUILD_SHARED_LIBS)
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE CARES_STATICLIB)
|
||||
endif()
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE c-ares_lib)
|
||||
set(TRANTOR_SOURCES ${TRANTOR_SOURCES} trantor/net/inner/AresResolver.cc)
|
||||
set(private_headers ${private_headers} trantor/net/inner/AresResolver.h)
|
||||
if(APPLE)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE resolv)
|
||||
elseif(WIN32)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE iphlpapi)
|
||||
endif()
|
||||
else()
|
||||
set(TRANTOR_SOURCES ${TRANTOR_SOURCES} trantor/net/inner/NormalResolver.cc)
|
||||
set(private_headers ${private_headers} trantor/net/inner/NormalResolver.h)
|
||||
endif()
|
||||
|
||||
find_package(Threads)
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC Threads::Threads)
|
||||
if(WIN32)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE ws2_32 rpcrt4)
|
||||
if(OpenSSL_FOUND)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE crypt32 secur32)
|
||||
endif(OpenSSL_FOUND)
|
||||
elseif(NOT ANDROID)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE pthread $<$<PLATFORM_ID:SunOS>:socket>)
|
||||
endif(WIN32)
|
||||
|
||||
file(WRITE ${CMAKE_BINARY_DIR}/test_atomic.cpp "#include <atomic>\n"
|
||||
"int main() { std::atomic<int64_t> i(0); i++; return 0; }\n"
|
||||
)
|
||||
try_compile(ATOMIC_WITHOUT_LINKING ${CMAKE_BINARY_DIR} ${CMAKE_BINARY_DIR}/test_atomic.cpp)
|
||||
if(NOT ATOMIC_WITHOUT_LINKING)
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC atomic)
|
||||
endif()
|
||||
file(REMOVE ${CMAKE_BINARY_DIR}/test_atomic.cpp)
|
||||
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD 14)
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD_REQUIRED ON)
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES CXX_EXTENSIONS OFF)
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES EXPORT_NAME Trantor)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_subdirectory(trantor/tests)
|
||||
find_package(GTest)
|
||||
if(GTest_FOUND)
|
||||
enable_testing()
|
||||
add_subdirectory(trantor/unittests)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(public_net_headers
|
||||
trantor/net/EventLoop.h
|
||||
trantor/net/EventLoopThread.h
|
||||
trantor/net/EventLoopThreadPool.h
|
||||
trantor/net/InetAddress.h
|
||||
trantor/net/TcpClient.h
|
||||
trantor/net/TcpConnection.h
|
||||
trantor/net/TcpServer.h
|
||||
trantor/net/AsyncStream.h
|
||||
trantor/net/callbacks.h
|
||||
trantor/net/Resolver.h
|
||||
trantor/net/Channel.h
|
||||
trantor/net/Certificate.h
|
||||
trantor/net/TLSPolicy.h
|
||||
)
|
||||
|
||||
set(public_utils_headers
|
||||
trantor/utils/AsyncFileLogger.h
|
||||
trantor/utils/ConcurrentTaskQueue.h
|
||||
trantor/utils/Date.h
|
||||
trantor/utils/Funcs.h
|
||||
trantor/utils/LockFreeQueue.h
|
||||
trantor/utils/LogStream.h
|
||||
trantor/utils/Logger.h
|
||||
trantor/utils/MsgBuffer.h
|
||||
trantor/utils/NonCopyable.h
|
||||
trantor/utils/ObjectPool.h
|
||||
trantor/utils/SerialTaskQueue.h
|
||||
trantor/utils/TaskQueue.h
|
||||
trantor/utils/TimingWheel.h
|
||||
trantor/utils/Utilities.h
|
||||
)
|
||||
|
||||
target_sources(
|
||||
${PROJECT_NAME}
|
||||
PRIVATE ${TRANTOR_SOURCES}
|
||||
${CMAKE_CURRENT_BINARY_DIR}/exports/trantor/exports.h
|
||||
${public_net_headers}
|
||||
${public_utils_headers}
|
||||
${private_headers}
|
||||
)
|
||||
|
||||
source_group(
|
||||
"Public API" FILES ${CMAKE_CURRENT_BINARY_DIR}/exports/trantor/exports.h ${public_net_headers}
|
||||
${public_utils_headers}
|
||||
)
|
||||
|
||||
source_group("Private Headers" FILES ${private_headers})
|
||||
|
||||
install(
|
||||
TARGETS trantor
|
||||
# IMPORTANT: Add the trantor library to the "export-set"
|
||||
EXPORT TrantorTargets
|
||||
RUNTIME DESTINATION "${INSTALL_BIN_DIR}" COMPONENT bin
|
||||
ARCHIVE DESTINATION "${INSTALL_LIB_DIR}" COMPONENT lib
|
||||
LIBRARY DESTINATION "${INSTALL_LIB_DIR}" COMPONENT lib
|
||||
)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/exports/trantor/exports.h DESTINATION ${INSTALL_INCLUDE_DIR}/trantor)
|
||||
install(FILES ${public_net_headers} DESTINATION ${INSTALL_INCLUDE_DIR}/trantor/net)
|
||||
install(FILES ${public_utils_headers} DESTINATION ${INSTALL_INCLUDE_DIR}/trantor/utils)
|
||||
|
||||
include(CMakePackageConfigHelpers)
|
||||
# ... for the install tree
|
||||
configure_package_config_file(
|
||||
cmake/templates/TrantorConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/TrantorConfig.cmake
|
||||
INSTALL_DESTINATION ${INSTALL_TRANTOR_CMAKE_DIR}
|
||||
)
|
||||
|
||||
# version
|
||||
write_basic_package_version_file(
|
||||
${CMAKE_CURRENT_BINARY_DIR}/TrantorConfigVersion.cmake
|
||||
VERSION ${TRANTOR_VERSION}
|
||||
COMPATIBILITY SameMajorVersion
|
||||
)
|
||||
|
||||
# Install the TrantorConfig.cmake and TrantorConfigVersion.cmake
|
||||
install(
|
||||
FILES "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/TrantorConfig.cmake"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/TrantorConfigVersion.cmake"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules/Findc-ares.cmake"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules/FindBotan.cmake"
|
||||
DESTINATION "${INSTALL_TRANTOR_CMAKE_DIR}"
|
||||
COMPONENT dev
|
||||
)
|
||||
|
||||
# Install the export set for use with the install-tree
|
||||
install(
|
||||
EXPORT TrantorTargets
|
||||
DESTINATION "${INSTALL_TRANTOR_CMAKE_DIR}"
|
||||
NAMESPACE Trantor::
|
||||
COMPONENT dev
|
||||
)
|
||||
|
||||
# Doxygen documentation
|
||||
find_package(Doxygen OPTIONAL_COMPONENTS dot dia)
|
||||
if(DOXYGEN_FOUND)
|
||||
set(DOXYGEN_PROJECT_BRIEF "Non-blocking I/O cross-platform TCP network library, using C++14")
|
||||
set(DOXYGEN_OUTPUT_DIRECTORY docs/${PROJECT_NAME})
|
||||
set(DOXYGEN_GENERATE_LATEX NO)
|
||||
set(DOXYGEN_BUILTIN_STL_SUPPORT YES)
|
||||
set(DOXYGEN_USE_MDFILE_AS_MAINPAGE README.md)
|
||||
set(DOXYGEN_STRIP_FROM_INC_PATH ${PROJECT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}/exports)
|
||||
if(WIN32)
|
||||
set(DOXYGEN_PREDEFINED _WIN32)
|
||||
endif(WIN32)
|
||||
doxygen_add_docs(
|
||||
doc_${PROJECT_NAME}
|
||||
README.md
|
||||
ChangeLog.md
|
||||
${public_net_headers}
|
||||
${public_utils_headers}
|
||||
COMMENT "Generate documentation"
|
||||
)
|
||||
if(NOT TARGET doc)
|
||||
add_custom_target(doc)
|
||||
endif()
|
||||
add_dependencies(doc doc_${PROJECT_NAME})
|
||||
if(BUILD_DOC)
|
||||
add_dependencies(${PROJECT_NAME} doc_${PROJECT_NAME})
|
||||
# Don't install twice, so limit to Debug (assume developer)
|
||||
install(
|
||||
DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/docs/${PROJECT_NAME}
|
||||
TYPE DOC
|
||||
CONFIGURATIONS Debug
|
||||
)
|
||||
endif(BUILD_DOC)
|
||||
endif(DOXYGEN_FOUND)
|
||||
+845
@@ -0,0 +1,845 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [1.5.26] - 2026-01-26
|
||||
|
||||
### Changed
|
||||
|
||||
- Enable Botan3 on MacOS CI.
|
||||
|
||||
- remove TU specific variables.
|
||||
|
||||
- Support parsing datetime with timezone.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix building error for -Werror=unused-value.
|
||||
|
||||
- Fix SPDLOG_FMT_EXTERNAL definition in CMakeLists.
|
||||
|
||||
- Fix range sendFile() for MinGW.
|
||||
|
||||
- Fix fread check for reading randomness.
|
||||
|
||||
- Fixes to TLS backend.
|
||||
|
||||
- Fix iptos.
|
||||
|
||||
- Fix the error handling of kBufferOffset bytes when adjusting the buffer.
|
||||
|
||||
## [1.5.25] - 2025-10-29
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix Date::fromDbStringLocal() when YYYY-MM-DD is given.
|
||||
|
||||
- Fix typo MICRO_SECONDS_PER_SEC.
|
||||
|
||||
## [1.5.24] - 2025-06-20
|
||||
|
||||
### Changed
|
||||
|
||||
- refactor: replace atomic counter with plain int in RunInLoopTest2.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix compile errors in some case.
|
||||
|
||||
- Fix a bug when sending streams.
|
||||
|
||||
## [1.5.23] - 2025-02-20
|
||||
|
||||
### Changed
|
||||
|
||||
- Replace ipv4 inet_ntop with a handrolled function.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix some typos.
|
||||
|
||||
## [1.5.22] - 2024-10-27
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix a bug in the dtor of EventLoop.
|
||||
|
||||
- Free leaked memory in ares resolver.
|
||||
|
||||
## [1.5.21] - 2024-09-10
|
||||
|
||||
### API changes list
|
||||
|
||||
- Add a method to reload the SSL certificate and private key on the fly.
|
||||
|
||||
### Changed
|
||||
|
||||
- Keep log level consistency.
|
||||
|
||||
## [1.5.20] - 2024-07-20
|
||||
|
||||
### Changed
|
||||
|
||||
- Add byte order detection for internal SHA1 implementation for OSX, POWER, RISC-V and s390.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix Windows CI build fail by using the latest MSVC.
|
||||
|
||||
- Fix the Botan TLS provider build on Linux.
|
||||
|
||||
- Fix "pthread not found" build error when using Android NDK.
|
||||
|
||||
## [1.5.19] - 2024-06-08
|
||||
|
||||
### changed
|
||||
|
||||
- show forked repository build status.
|
||||
|
||||
- Add cmake-format.
|
||||
|
||||
- Some spelling corrections.
|
||||
|
||||
## [1.5.18] - 2024-05-04
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix data type conflict.
|
||||
|
||||
- Fix build on latest c-ares.
|
||||
|
||||
## [1.5.17] - 2024-02-09
|
||||
|
||||
### Changed
|
||||
|
||||
- Make FileBufferNodeWin aware of UWP Win32 API.
|
||||
|
||||
- Use ssize_t declared by toolchain when available.
|
||||
|
||||
## [1.5.16] - 2024-01-18
|
||||
|
||||
### Changed
|
||||
|
||||
- Add build badge for individual OS.
|
||||
|
||||
- deinit libressl.
|
||||
|
||||
- Remove mutex.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Pile of fix for h2.
|
||||
|
||||
- Fix a bug when sending data.
|
||||
|
||||
- Fix c-ares CARES_EXTERN for static builds.
|
||||
|
||||
- Fix header file name issue when cross-compiling on Windows.
|
||||
|
||||
- Fix name issue when cross-compiling.
|
||||
|
||||
## [1.5.15] - 2023-11-27
|
||||
|
||||
### Changed
|
||||
|
||||
- Feature: Integrate spdlog as logging backend for Trantor Logger.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix the botan backend always validating certificate and OpenSSL allowing empty ALPN.
|
||||
|
||||
- Fix build error on OpenBSD.
|
||||
|
||||
- Fix Botan leaking memory if connection force closed.
|
||||
|
||||
- Fix a cmake warning.
|
||||
|
||||
- Workaround botan backend init failure on MacOS.
|
||||
|
||||
- Fix failing wstr conversion if locale is set to C.
|
||||
|
||||
## [1.5.14] - 2023-09-19
|
||||
|
||||
### [Fixed]
|
||||
|
||||
- Fix OpenSSL: read can be incomplete.
|
||||
|
||||
- Fix botan provider.
|
||||
|
||||
- Fix botan3 not triggering handshake finish event.
|
||||
|
||||
- Fix an compilation error when no STL lib is found.
|
||||
|
||||
## [1.5.13] - 2023-08-23
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix an error when sending files.
|
||||
|
||||
- Include <memory> header in TcpConnectionImpl.cc.
|
||||
|
||||
## [1.5.12] - 2023-08-20
|
||||
|
||||
### API changes list
|
||||
|
||||
- Add NetEndian versions of toIp and toIpPort.
|
||||
|
||||
- Add setsockopt to TcpClient and TcpServer.
|
||||
|
||||
- Support setting max files in AsyncFileLogger.
|
||||
|
||||
- Support returning multiple results for dns parsing.
|
||||
|
||||
### Changed
|
||||
|
||||
- Refactor SSL handling.
|
||||
|
||||
- Add ability to use one log file until the size-limit.
|
||||
|
||||
- Make the std::string_view work on windows.
|
||||
|
||||
- Drop Botan 2 support and support Botan 3.
|
||||
|
||||
- Make the getNextLoop method multi-thread safe.
|
||||
|
||||
- Add fallback when OpenSSL not providing BLAKE2b.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix override mark.
|
||||
|
||||
- Add missing <cstdint> header with GCC 13.
|
||||
|
||||
- Fix AresResolver.
|
||||
|
||||
- Fix building built-in hashes on Windows.
|
||||
|
||||
- Fix MSYS2/Cygwin compatibility issues.
|
||||
|
||||
- Fix more build errors on win32/mingw.
|
||||
|
||||
- Fix off_t(on windows off_t defined with long, not longlong).
|
||||
|
||||
- Fix bug with Trantor::Date timeZoneOffset calculation.
|
||||
|
||||
- Fix wrong usage of shared pointer in TcpClient ctor.
|
||||
|
||||
## [1.5.11] - 2023-03-17
|
||||
|
||||
### API Changes list
|
||||
|
||||
- Add a method to the Logger class to enable local time displaying.
|
||||
|
||||
- TRNANTOR_LOG_COMPACT - compact logs without source code details.
|
||||
|
||||
### Changed
|
||||
|
||||
- Refactor TcpServer I/O loop logic.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix a conan issue.
|
||||
|
||||
## [1.5.10] - 2023-01-23
|
||||
|
||||
### API Changes list
|
||||
|
||||
### Changed
|
||||
|
||||
- Use gtest 1.13 in github actions
|
||||
|
||||
### Fixed
|
||||
|
||||
## [1.5.9] - 2023-01-23
|
||||
|
||||
### API Changes list
|
||||
|
||||
### Changed
|
||||
|
||||
- Search for \ if under msvc
|
||||
|
||||
### Fixed
|
||||
|
||||
## [1.5.8] - 2022-11-11
|
||||
|
||||
### API Changes list
|
||||
|
||||
### Changed
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix Date::timezoneOffset().
|
||||
|
||||
- Fix socket fd leak if Connector destruct before connection callback is made.
|
||||
|
||||
## [1.5.7] - 2022-09-25
|
||||
|
||||
### API changes list
|
||||
|
||||
- Add utc methods for trantor::Date.
|
||||
|
||||
### Changed
|
||||
|
||||
- Remove an unnecessary semi-colon.
|
||||
|
||||
- Added support for Solaris.
|
||||
|
||||
- Define ssize_t as std::intptr_t on Windows.
|
||||
|
||||
- Add an environment without openssl to github actions.
|
||||
|
||||
- Added SSL Error Trace Log and mTLS Samples.
|
||||
|
||||
- Use LOG_TRACE instead of LOG_DEBUG.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix a race condition.
|
||||
|
||||
- Fix iterator invalidation bug when stopping TCP server.
|
||||
|
||||
- Partial fix of exception safety in the event loop.
|
||||
|
||||
## [1.5.6] - 2022-07-09
|
||||
|
||||
### API changes list
|
||||
|
||||
- Add support for sending data streams via callback.
|
||||
|
||||
- Added mTLS support.
|
||||
|
||||
### Changed
|
||||
|
||||
- Make MsgBuffer constructor explicit.
|
||||
|
||||
- Always queue connectDestroyed() in loop.
|
||||
|
||||
- Stop calling abort() in runtime.
|
||||
|
||||
- Give EventLoopThread::loop_ static lifetime.
|
||||
|
||||
- Optimization SSL name matching.
|
||||
|
||||
- Clarify SSL error message.
|
||||
|
||||
- Rename BUILD_TRANTOR_SHARED to BUILD_SHARED_LIBS.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix tolower with sanitizer cfi
|
||||
|
||||
- include <pthread_np.h> unconditionally on freebsd
|
||||
|
||||
- Fix thread sanitizer.
|
||||
|
||||
## [1.5.5] - 2022-02-19
|
||||
|
||||
### API changes list
|
||||
|
||||
### Changed
|
||||
|
||||
- Move EventLoop::runAfter to a template.
|
||||
|
||||
- Remove an assertion when removing channels.
|
||||
|
||||
- Prevent TcpClient::removeConnection call on deleted TcpClient instance.
|
||||
|
||||
- Wait for loop to exit in EventLoop destructor.
|
||||
|
||||
- Add r-reference version of set-callback methods to TcpConnectionImpl.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix a bug when closing connections on Windows/MacOS.
|
||||
|
||||
- Fix logger causes if statement mismatch.
|
||||
|
||||
## [1.5.4] - 2021-12-10
|
||||
|
||||
### API changes list
|
||||
|
||||
### Changed
|
||||
|
||||
- Correctly handle the error of the getaddrinfo function
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix the error when sending partial files
|
||||
|
||||
## [1.5.3] - 2021-11-28
|
||||
|
||||
### API changes list
|
||||
|
||||
- TcpClientImpl support SSL client certificate
|
||||
|
||||
### Changed
|
||||
|
||||
- Allow RVO in fromDbStringLocal
|
||||
|
||||
### Fixed
|
||||
|
||||
- Make sure resolvers are added when C-Ares is manually disabled
|
||||
|
||||
## [1.5.2] - 2021-10-17
|
||||
|
||||
### API changes list
|
||||
|
||||
### Changed
|
||||
|
||||
- Disable setting SSL Configs when using LibreSSL
|
||||
|
||||
- cmake: Use GNUInstallDirs to figure out install dirs
|
||||
|
||||
- support HaikuOS
|
||||
|
||||
- Improve Error handling for certificates/private keys
|
||||
|
||||
- Make c-ares support optional when building
|
||||
|
||||
- Use locale.h
|
||||
|
||||
- Assert fd >= 0 in updateChannel()
|
||||
|
||||
- Add Clang support for -Wall -Wextra -Werror; fix -Wunused-parameter
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix a small memory leak
|
||||
|
||||
- Fix errors in log macros
|
||||
|
||||
- Fix a race condition when TcpClient destroyed before connected
|
||||
|
||||
- Fix the error of calling removeAndResetChannel twice
|
||||
|
||||
- Fix a bug when EAGAIN on reading sockets
|
||||
|
||||
- Fix compilation warnings
|
||||
|
||||
- Fix a potential race condition
|
||||
|
||||
## [1.5.1] - 2021-08-08
|
||||
|
||||
### API changes list
|
||||
|
||||
### Changed
|
||||
|
||||
- Fix warning C4244 in MSVC
|
||||
|
||||
- Disable strict compiler check on Windows with GCC
|
||||
|
||||
- Add support for paths containing unicode characters on Windows
|
||||
|
||||
- Add BUILD_DOC cmake option (doxygen)
|
||||
|
||||
- Use make_shared instead of shared_ptr(new)
|
||||
|
||||
- Detect and handle MinGW
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix the destructor of AresResolver
|
||||
|
||||
- Fix memory leak in NormalResolver
|
||||
|
||||
## [1.5.0] - 2021-06-18
|
||||
|
||||
### API changes list
|
||||
|
||||
- Enable multiple log files or streams.
|
||||
|
||||
- Add SSL_CONF_cmd support.
|
||||
|
||||
- Add runOnQuit to the EventLoop class.
|
||||
|
||||
### Changed
|
||||
|
||||
- Export the FixedBuffer.
|
||||
|
||||
- Added a try_compile block to detect if we need to link against atomic.
|
||||
|
||||
## [1.4.1] - 2021-05-15
|
||||
|
||||
### Changed
|
||||
|
||||
- Add github actions of Windows.
|
||||
|
||||
- Modify the way the log file is opened.
|
||||
|
||||
- Add version/soversion to shared library.
|
||||
|
||||
- Use double instead of long double as the type for timer durations.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix a bug in the TcpConnectionImpl class.
|
||||
|
||||
- Fix constructing Date in a daylight saving timezone.
|
||||
|
||||
- GNU: -Wall -Wextra -Werror; fix related warnings.
|
||||
|
||||
- Add wincrypt.h include for Windows.
|
||||
|
||||
## [1.4.0] - 2021-04-09
|
||||
|
||||
### API changes list
|
||||
|
||||
- Add isUnspecified() to indicate if IP parsing failed.
|
||||
|
||||
- Add exports macro to allow Shared Library with hidden symbols by default.
|
||||
|
||||
### Changed
|
||||
|
||||
- Modify the AsyncFileLogger destructor.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Recycle TimerID in the TimerQueue.
|
||||
|
||||
## [1.3.0] - 2021-03-05
|
||||
|
||||
### API changes list
|
||||
|
||||
- Add secondsSinceEpoch to trantor::Date.
|
||||
|
||||
- Rename the 'bzero' method of the FixedBuffer class to 'zeroBuffer'.
|
||||
|
||||
- Add SNI support to TcpClient.
|
||||
|
||||
- Add SSL certificate validation.
|
||||
|
||||
### Changed
|
||||
|
||||
- Change README.md.
|
||||
|
||||
## [1.2.0] - 2021-01-16
|
||||
|
||||
### API changes list
|
||||
|
||||
- Add LOG_IF and DLOG like glog lib.
|
||||
|
||||
### Changed
|
||||
|
||||
- Enable github actions.
|
||||
|
||||
- Add support for VS2019.
|
||||
|
||||
- Modify the LockFreeQueue.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix MinGW error with inet_ntop and inet_pton.
|
||||
|
||||
- Fix a macro regression when using MSVC.
|
||||
|
||||
## [1.1.1] - 2020-12-12
|
||||
|
||||
### Changed
|
||||
|
||||
- Add Openbsd support.
|
||||
|
||||
## [1.1.0] - 2020-10-24
|
||||
|
||||
### Changed
|
||||
|
||||
- Disable TLS 1.0 and 1.1 by default.
|
||||
|
||||
- Use explicit lambda capture lists.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix a bug in the Date::fromDbStringLocal() method.
|
||||
|
||||
## [1.0.0] - 2020-9-27
|
||||
|
||||
### API changes list
|
||||
|
||||
- Add the address() method to the TcpServer class.
|
||||
|
||||
- Change some internal methods from public to private in the Channel class.
|
||||
|
||||
### Changed
|
||||
|
||||
- Update the wepoll library.
|
||||
|
||||
- Add comments in public header files.
|
||||
|
||||
## [1.0.0-rc16] - 2020-8-15
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix a bug when sending big files on Windows.
|
||||
|
||||
### API changes list
|
||||
|
||||
- Add updateEvents() method to the Channel class.
|
||||
|
||||
## [1.0.0-rc15] - 2020-7-16
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix installation errors of shared library.
|
||||
|
||||
## [1.0.0-rc14] - 2020-6-14
|
||||
|
||||
### API changes list
|
||||
|
||||
- Add the moveToCurrentThread() method to EventLoop.
|
||||
|
||||
### Changed
|
||||
|
||||
- Optimized LockFreeQueue by Reducing Object Construction.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix a bug when sending a file.
|
||||
|
||||
## [1.0.0-rc13] - 2020-5-23
|
||||
|
||||
### API changes list
|
||||
|
||||
- Make the Channel class as a part of the public API.
|
||||
|
||||
## [1.0.0-rc12] - 2020-5-22
|
||||
|
||||
### API changes list
|
||||
|
||||
- Add a method to show if the c-ares library is used
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix a bug in SSL mode (#85)
|
||||
|
||||
- Use SOCKET type in windows for x86-windows compilation
|
||||
|
||||
- Use env to find bash in build.sh script to support FreeBSD
|
||||
|
||||
## [1.0.0-rc11] - 2020-4-27
|
||||
|
||||
### API changes list
|
||||
|
||||
- Add fromDbStringLocal() method to the Date class
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix a race condition of TimingWheel class
|
||||
|
||||
- Fix localhost resolving on windows
|
||||
|
||||
## [1.0.0-rc10] - 2020-3-28
|
||||
|
||||
### API changes list
|
||||
|
||||
- Add the send(const void *, size_t) method to the TcpConnection class
|
||||
|
||||
- Add the send(const MsgBufferPtr &) method to TcpConnection class
|
||||
|
||||
- Add stop() method to the TcpServer class
|
||||
|
||||
### Changed
|
||||
|
||||
- Compile wepoll directly into trantor (Windows)
|
||||
|
||||
- Add CI for Windows
|
||||
|
||||
- Make CMake install files relocatable
|
||||
|
||||
- Modify the Resolver class
|
||||
|
||||
## [1.0.0-rc9] - 2020-2-17
|
||||
|
||||
### API changes list
|
||||
|
||||
- Add support for a delayed SSL handshake
|
||||
|
||||
- Change a method name of EventLoopThreadPool(getLoopNum() -> size())
|
||||
|
||||
### Changed
|
||||
|
||||
- Port Trantor to Windows
|
||||
|
||||
- Use SSL_CTX_use_certificate_chain_file instead of SSL_CTX_use_certificate_file()
|
||||
|
||||
## [1.0.0-rc8] - 2019-11-30
|
||||
|
||||
### API changes list
|
||||
|
||||
- Add the isSSLConnection() method to the TcpConnection class
|
||||
|
||||
### Changed
|
||||
|
||||
- Use the std::chrono::steady_clock for timers
|
||||
|
||||
## [1.0.0-rc7] - 2019-11-21
|
||||
|
||||
### Changed
|
||||
|
||||
- Modify some code styles
|
||||
|
||||
## [1.0.0-rc6] - 2019-10-4
|
||||
|
||||
### API changes list
|
||||
|
||||
- Add index() interface to the EventLoop class.
|
||||
|
||||
### Changed
|
||||
|
||||
- Fix some compilation warnings.
|
||||
|
||||
- Modify the CMakeLists.txt
|
||||
|
||||
## [1.0.0-rc5] - 2019-08-24
|
||||
|
||||
### API changes list
|
||||
|
||||
- Remove the resolve method from the InetAddress class.
|
||||
|
||||
### Added
|
||||
|
||||
- Add the Resolver class that provides high-performance DNS functionality(with c-ares library)
|
||||
- Add some unit tests.
|
||||
|
||||
## [1.0.0-rc4] - 2019-08-08
|
||||
|
||||
### API changes list
|
||||
|
||||
- None
|
||||
|
||||
### Changed
|
||||
|
||||
- Add TrantorConfig.cmake so that users can use trantor with the `find_package(Trantor)` command.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix an SSL error (occurs when sending large data via SSL).
|
||||
|
||||
## [1.0.0-rc3] - 2019-07-30
|
||||
|
||||
### API changes list
|
||||
|
||||
- TcpConnection::setContext, TcpConnection::getContext, etc.
|
||||
- Remove the config.h from public API.
|
||||
|
||||
### Changed
|
||||
|
||||
- Modify the CMakeLists.txt.
|
||||
- Modify some log output.
|
||||
- Remove some unnecessary `std::dynamic_pointer_cast` calls.
|
||||
|
||||
## [1.0.0-rc2] - 2019-07-11
|
||||
|
||||
### Added
|
||||
|
||||
- Add bytes statistics methods to the TcpConnection class.
|
||||
- Add the setIoLoopThreadPool method to the TcpServer class.
|
||||
|
||||
### Changed
|
||||
|
||||
- Ignore SIGPIPE signal when using the TcpClient class.
|
||||
- Enable TCP_NODELAY by default (for higher performance).
|
||||
|
||||
## [1.0.0-rc1] - 2019-06-11
|
||||
|
||||
[Unreleased]: https://github.com/an-tao/trantor/compare/v1.5.26...HEAD
|
||||
|
||||
[1.5.26]: https://github.com/an-tao/trantor/compare/v1.5.25...v1.5.26
|
||||
|
||||
[1.5.25]: https://github.com/an-tao/trantor/compare/v1.5.24...v1.5.25
|
||||
|
||||
[1.5.24]: https://github.com/an-tao/trantor/compare/v1.5.23...v1.5.24
|
||||
|
||||
[1.5.23]: https://github.com/an-tao/trantor/compare/v1.5.22...v1.5.23
|
||||
|
||||
[1.5.22]: https://github.com/an-tao/trantor/compare/v1.5.21...v1.5.22
|
||||
|
||||
[1.5.21]: https://github.com/an-tao/trantor/compare/v1.5.20...v1.5.21
|
||||
|
||||
[1.5.20]: https://github.com/an-tao/trantor/compare/v1.5.19...v1.5.20
|
||||
|
||||
[1.5.19]: https://github.com/an-tao/trantor/compare/v1.5.18...v1.5.19
|
||||
|
||||
[1.5.18]: https://github.com/an-tao/trantor/compare/v1.5.17...v1.5.18
|
||||
|
||||
[1.5.17]: https://github.com/an-tao/trantor/compare/v1.5.16...v1.5.17
|
||||
|
||||
[1.5.16]: https://github.com/an-tao/trantor/compare/v1.5.15...v1.5.16
|
||||
|
||||
[1.5.15]: https://github.com/an-tao/trantor/compare/v1.5.14...v1.5.15
|
||||
|
||||
[1.5.14]: https://github.com/an-tao/trantor/compare/v1.5.13...v1.5.14
|
||||
|
||||
[1.5.13]: https://github.com/an-tao/trantor/compare/v1.5.12...v1.5.13
|
||||
|
||||
[1.5.12]: https://github.com/an-tao/trantor/compare/v1.5.11...v1.5.12
|
||||
|
||||
[1.5.11]: https://github.com/an-tao/trantor/compare/v1.5.10...v1.5.11
|
||||
|
||||
[1.5.10]: https://github.com/an-tao/trantor/compare/v1.5.9...v1.5.10
|
||||
|
||||
[1.5.9]: https://github.com/an-tao/trantor/compare/v1.5.8...v1.5.9
|
||||
|
||||
[1.5.8]: https://github.com/an-tao/trantor/compare/v1.5.7...v1.5.8
|
||||
|
||||
[1.5.7]: https://github.com/an-tao/trantor/compare/v1.5.6...v1.5.7
|
||||
|
||||
[1.5.6]: https://github.com/an-tao/trantor/compare/v1.5.5...v1.5.6
|
||||
|
||||
[1.5.5]: https://github.com/an-tao/trantor/compare/v1.5.4...v1.5.5
|
||||
|
||||
[1.5.4]: https://github.com/an-tao/trantor/compare/v1.5.3...v1.5.4
|
||||
|
||||
[1.5.3]: https://github.com/an-tao/trantor/compare/v1.5.2...v1.5.3
|
||||
|
||||
[1.5.2]: https://github.com/an-tao/trantor/compare/v1.5.1...v1.5.2
|
||||
|
||||
[1.5.1]: https://github.com/an-tao/trantor/compare/v1.5.0...v1.5.1
|
||||
|
||||
[1.5.0]: https://github.com/an-tao/trantor/compare/v1.4.1...v1.5.0
|
||||
|
||||
[1.4.1]: https://github.com/an-tao/trantor/compare/v1.4.0...v1.4.1
|
||||
|
||||
[1.4.0]: https://github.com/an-tao/trantor/compare/v1.3.0...v1.4.0
|
||||
|
||||
[1.3.0]: https://github.com/an-tao/trantor/compare/v1.2.0...v1.3.0
|
||||
|
||||
[1.2.0]: https://github.com/an-tao/trantor/compare/v1.1.1...v1.2.0
|
||||
|
||||
[1.1.1]: https://github.com/an-tao/trantor/compare/v1.1.0...v1.1.1
|
||||
|
||||
[1.1.0]: https://github.com/an-tao/trantor/compare/v1.0.0...v1.1.0
|
||||
|
||||
[1.0.0]: https://github.com/an-tao/trantor/compare/v1.0.0-rc16...v1.0.0
|
||||
|
||||
[1.0.0-rc16]: https://github.com/an-tao/trantor/compare/v1.0.0-rc15...v1.0.0-rc16
|
||||
|
||||
[1.0.0-rc15]: https://github.com/an-tao/trantor/compare/v1.0.0-rc14...v1.0.0-rc15
|
||||
|
||||
[1.0.0-rc14]: https://github.com/an-tao/trantor/compare/v1.0.0-rc13...v1.0.0-rc14
|
||||
|
||||
[1.0.0-rc13]: https://github.com/an-tao/trantor/compare/v1.0.0-rc12...v1.0.0-rc13
|
||||
|
||||
[1.0.0-rc12]: https://github.com/an-tao/trantor/compare/v1.0.0-rc11...v1.0.0-rc12
|
||||
|
||||
[1.0.0-rc11]: https://github.com/an-tao/trantor/compare/v1.0.0-rc10...v1.0.0-rc11
|
||||
|
||||
[1.0.0-rc10]: https://github.com/an-tao/trantor/compare/v1.0.0-rc9...v1.0.0-rc10
|
||||
|
||||
[1.0.0-rc9]: https://github.com/an-tao/trantor/compare/v1.0.0-rc8...v1.0.0-rc9
|
||||
|
||||
[1.0.0-rc8]: https://github.com/an-tao/trantor/compare/v1.0.0-rc7...v1.0.0-rc8
|
||||
|
||||
[1.0.0-rc7]: https://github.com/an-tao/trantor/compare/v1.0.0-rc6...v1.0.0-rc7
|
||||
|
||||
[1.0.0-rc6]: https://github.com/an-tao/trantor/compare/v1.0.0-rc5...v1.0.0-rc6
|
||||
|
||||
[1.0.0-rc5]: https://github.com/an-tao/trantor/compare/v1.0.0-rc4...v1.0.0-rc5
|
||||
|
||||
[1.0.0-rc4]: https://github.com/an-tao/trantor/compare/v1.0.0-rc3...v1.0.0-rc4
|
||||
|
||||
[1.0.0-rc3]: https://github.com/an-tao/trantor/compare/v1.0.0-rc2...v1.0.0-rc3
|
||||
|
||||
[1.0.0-rc2]: https://github.com/an-tao/trantor/compare/v1.0.0-rc1...v1.0.0-rc2
|
||||
|
||||
[1.0.0-rc1]: https://github.com/an-tao/trantor/releases/tag/v1.0.0-rc1
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
// Trantor - A non-blocking I/O based TCP network library, using C++14/17,
|
||||
// Copyright (c) 2016-2021, Tao An. All rights reserved.
|
||||
// https://github.com/an-tao/trantor
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * 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.
|
||||
// * Neither the name of Tao An nor the names of other 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
|
||||
// OWNER 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.
|
||||
|
||||
// Muduo - A reactor-based C++ network library for Linux
|
||||
// Copyright (c) 2010, Shuo Chen. All rights reserved.
|
||||
// http://code.google.com/p/muduo/
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * 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.
|
||||
// * Neither the name of Shuo Chen nor the names of other 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
|
||||
// OWNER 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.
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
# TRANTOR
|
||||
[](../../actions/workflows/ubuntu-gcc.yml/badge.svg)
|
||||
[](../../actions/workflows/macos-clang.yml/badge.svg)
|
||||
[](../../actions/workflows/rockylinux-gcc.yml/badge.svg)
|
||||
[](../../actions/workflows/windows-msvc.yml/badge.svg)
|
||||
|
||||
## Overview
|
||||
A non-blocking I/O cross-platform TCP network library, using C++14.
|
||||
Drawing on the design of Muduo Library
|
||||
|
||||
## Supported platforms
|
||||
- Linux
|
||||
- MacOS
|
||||
- UNIX(BSD)
|
||||
- Windows
|
||||
|
||||
## Feature highlights
|
||||
- Non-blocking I/O
|
||||
- cross-platform
|
||||
- Thread pool
|
||||
- Lock free design
|
||||
- Support SSL
|
||||
- Server and Client
|
||||
|
||||
|
||||
## Build
|
||||
```shell
|
||||
git clone https://github.com/an-tao/trantor.git
|
||||
cd trantor
|
||||
cmake -B build -H.
|
||||
cd build
|
||||
make -j
|
||||
```
|
||||
|
||||
## Licensing
|
||||
Trantor - A non-blocking I/O based TCP network library, using C++14.
|
||||
|
||||
Copyright (c) 2016-2021, Tao An. All rights reserved.
|
||||
|
||||
https://github.com/an-tao/trantor
|
||||
|
||||
For more information see [License](License)
|
||||
|
||||
## Community
|
||||
[Gitter](https://gitter.im/drogon-web/community)
|
||||
|
||||
## Documentation
|
||||
[DocsForge](https://trantor.docsforge.com/)
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
#building trantor
|
||||
function build_trantor() {
|
||||
|
||||
#Saving current directory
|
||||
current_dir="${PWD}"
|
||||
|
||||
#The folder we will build
|
||||
build_dir='./build'
|
||||
if [ -d $build_dir ]; then
|
||||
echo "Deleted folder: ${build_dir}"
|
||||
rm -rf $build_dir
|
||||
fi
|
||||
|
||||
#Creating building folder
|
||||
echo "Created building folder: ${build_dir}"
|
||||
mkdir $build_dir
|
||||
|
||||
echo "Entering folder: ${build_dir}"
|
||||
cd $build_dir
|
||||
|
||||
echo "Start building trantor ..."
|
||||
if [ $1 -eq 1 ]; then
|
||||
cmake .. -DBUILD_TESTING=on
|
||||
else
|
||||
cmake ..
|
||||
fi
|
||||
|
||||
#If errors then exit
|
||||
if [ "$?" != "0" ]; then
|
||||
exit -1
|
||||
fi
|
||||
|
||||
make
|
||||
|
||||
#If errors then exit
|
||||
if [ "$?" != "0" ]; then
|
||||
exit -1
|
||||
fi
|
||||
|
||||
echo "Installing ..."
|
||||
sudo make install
|
||||
|
||||
#Reback current directory
|
||||
cd $current_dir
|
||||
exit 0
|
||||
#Ok!
|
||||
}
|
||||
|
||||
if [ "$1" = "-t" ]; then
|
||||
build_trantor 1
|
||||
else
|
||||
build_trantor 0
|
||||
fi
|
||||
@@ -0,0 +1,37 @@
|
||||
#[[
|
||||
# - Config file for the Trantor package
|
||||
# It defines the following variables
|
||||
# TRANTOR_INCLUDE_DIRS - include directories for Trantor
|
||||
# TRANTOR_LIBRARIES - libraries to link against
|
||||
# Trantor_FOUND
|
||||
# This module defines the following IMPORTED target:
|
||||
# Trantor::Trantor
|
||||
#]]
|
||||
|
||||
@PACKAGE_INIT@
|
||||
|
||||
include(CMakeFindDependencyMacro)
|
||||
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR})
|
||||
if(@OpenSSL_FOUND@)
|
||||
find_dependency(OpenSSL)
|
||||
endif()
|
||||
if(@Botan_FOUND@)
|
||||
find_dependency(Botan)
|
||||
endif()
|
||||
if(@c-ares_FOUND@)
|
||||
find_dependency(c-ares)
|
||||
endif()
|
||||
find_dependency(Threads)
|
||||
if(@spdlog_FOUND@)
|
||||
find_dependency(spdlog)
|
||||
endif()
|
||||
# Compute paths
|
||||
|
||||
# Our library dependencies (contains definitions for IMPORTED targets)
|
||||
get_filename_component(TRANTOR_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH)
|
||||
if(NOT TARGET Trantor::Trantor)
|
||||
include("${TRANTOR_CMAKE_DIR}/TrantorTargets.cmake")
|
||||
endif()
|
||||
|
||||
get_target_property(TRANTOR_INCLUDE_DIRS Trantor::Trantor INTERFACE_INCLUDE_DIRECTORIES)
|
||||
set(TRANTOR_LIBRARIES Trantor::Trantor)
|
||||
@@ -0,0 +1,68 @@
|
||||
function(find_botan_pkgconfig package_name botan_ver)
|
||||
if(TARGET Botan::Botan)
|
||||
return()
|
||||
endif()
|
||||
|
||||
pkg_check_modules(
|
||||
Botan
|
||||
QUIET
|
||||
IMPORTED_TARGET
|
||||
${package_name}
|
||||
)
|
||||
if(TARGET PkgConfig::Botan)
|
||||
add_library(Botan::Botan ALIAS PkgConfig::Botan)
|
||||
|
||||
if(botan_ver EQUAL 3)
|
||||
target_compile_features(PkgConfig::Botan INTERFACE cxx_std_20)
|
||||
endif()
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(find_botan_search package_name botan_ver)
|
||||
if(TARGET Botan::Botan)
|
||||
return()
|
||||
endif()
|
||||
find_path(
|
||||
Botan_INCLUDE_DIRS
|
||||
NAMES botan/botan.h
|
||||
PATH_SUFFIXES ${package_name}
|
||||
DOC "The Botan include directory"
|
||||
)
|
||||
|
||||
find_library(
|
||||
Botan_LIBRARIES
|
||||
NAMES botan ${package_name}
|
||||
DOC "The Botan library"
|
||||
)
|
||||
|
||||
mark_as_advanced(Botan_INCLUDE_DIRS Botan_LIBRARIES)
|
||||
|
||||
add_library(Botan::Botan IMPORTED UNKNOWN)
|
||||
set_target_properties(
|
||||
Botan::Botan
|
||||
PROPERTIES
|
||||
IMPORTED_LOCATION "${Botan_LIBRARIES}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${Botan_INCLUDE_DIRS}"
|
||||
)
|
||||
if(botan_ver EQUAL 3)
|
||||
target_compile_features(Botan::Botan INTERFACE cxx_std_20)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
target_compile_definitions(Botan::Botan INTERFACE -DNOMINMAX=1)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
find_package(PkgConfig)
|
||||
if(NOT WIN32 AND PKG_CONFIG_FOUND)
|
||||
# find_botan_pkgconfig(botan-2 2)
|
||||
find_botan_pkgconfig(botan-3 3)
|
||||
endif()
|
||||
|
||||
if(NOT TARGET Botan::Botan)
|
||||
# find_botan_search(botan-2 2)
|
||||
find_botan_search(botan-3 3)
|
||||
endif()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(Botan REQUIRED_VARS Botan_LIBRARIES Botan_INCLUDE_DIRS)
|
||||
@@ -0,0 +1,30 @@
|
||||
#[[
|
||||
# Try to find c-ares library Once done this will define
|
||||
#
|
||||
# c-ares_FOUND - system has c-ares
|
||||
# C-ARES_INCLUDE_DIRS - The c-ares include directory
|
||||
# C-ARES_LIBRARIES - Link these to use c-ares
|
||||
# c-ares_lib - Imported Targets
|
||||
#
|
||||
# Copyright (c) 2020 antao <antao2002@gmail.com>
|
||||
#]]
|
||||
|
||||
find_path(C-ARES_INCLUDE_DIRS ares.h)
|
||||
find_library(C-ARES_LIBRARIES NAMES cares)
|
||||
if(C-ARES_INCLUDE_DIRS AND C-ARES_LIBRARIES)
|
||||
add_library(c-ares_lib INTERFACE IMPORTED)
|
||||
set_target_properties(
|
||||
c-ares_lib
|
||||
PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${C-ARES_INCLUDE_DIRS}" INTERFACE_LINK_LIBRARIES "${C-ARES_LIBRARIES}"
|
||||
)
|
||||
endif()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(
|
||||
c-ares
|
||||
DEFAULT_MSG
|
||||
C-ARES_INCLUDE_DIRS
|
||||
C-ARES_LIBRARIES
|
||||
)
|
||||
mark_as_advanced(C-ARES_INCLUDE_DIRS C-ARES_LIBRARIES)
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
[requires]
|
||||
gtest/1.10.0
|
||||
openssl/1.1.1t
|
||||
#c-ares/1.17.1
|
||||
spdlog/1.12.0
|
||||
|
||||
[generators]
|
||||
CMakeToolchain
|
||||
|
||||
[options]
|
||||
|
||||
[imports]
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
|
||||
clang-format --version
|
||||
|
||||
find trantor -name *.h -o -name *.cc -exec dos2unix {} \;
|
||||
find trantor -name *.h -o -name *.cc|xargs clang-format -i -style=file
|
||||
|
||||
cmake-format --version
|
||||
find . -maxdepth 1 -name CMakeLists.txt|xargs cmake-format -i
|
||||
find trantor -name CMakeLists.txt|xargs cmake-format -i
|
||||
find cmake -name *.cmake -o -name *.cmake.in|xargs cmake-format -i
|
||||
find cmake_modules -name *.cmake -o -name *.cmake.in|xargs cmake-format -i
|
||||
@@ -0,0 +1,28 @@
|
||||
wepoll - epoll for Windows
|
||||
https://github.com/piscisaureus/wepoll
|
||||
|
||||
Copyright 2012-2020, Bert Belder <bertbelder@gmail.com>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* 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.
|
||||
|
||||
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
|
||||
OWNER 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.
|
||||
@@ -0,0 +1,41 @@
|
||||
## wepoll
|
||||
|
||||
This library is based on [wepoll v1.5.8](https://github.com/piscisaureus/wepoll/commit/0598a791bf9cbbf480793d778930fc635b044980).
|
||||
|
||||
An eventfd-like mechanism is added to it. After making the changes, we can wake up `trantor::EventLoop` from the epoll_wait() function.
|
||||
|
||||
## Modifications
|
||||
|
||||
```shell
|
||||
diff wepoll.h Wepoll.h
|
||||
53a54
|
||||
> EPOLLEVENT = (int)(1U << 14),
|
||||
67a69
|
||||
> #define EPOLLEVENT (1U << 14)
|
||||
111a114
|
||||
> WEPOLL_EXPORT void epoll_post_signal(HANDLE ephnd, uint64_t event);
|
||||
```
|
||||
|
||||
```shell
|
||||
diff wepoll.c Wepoll.c
|
||||
50a51
|
||||
> EPOLLEVENT = (int)(1U << 14),
|
||||
64a66
|
||||
> #define EPOLLEVENT (1U << 14)
|
||||
1262a1265,1271
|
||||
> if (iocp_events[i].lpCompletionKey)
|
||||
> {
|
||||
> struct epoll_event* ev = &epoll_events[epoll_event_count++];
|
||||
> ev->data.u64 = (uint64_t)iocp_events[i].lpCompletionKey;
|
||||
> ev->events = EPOLLEVENT;
|
||||
> continue;
|
||||
> }
|
||||
2441a2451,2457
|
||||
> void epoll_post_signal(HANDLE port_handle, uint64_t event)
|
||||
> {
|
||||
> ULONG_PTR ev;
|
||||
> ev = (ULONG_PTR)event;
|
||||
> PostQueuedCompletionStatus(port_handle, 1, ev, NULL);
|
||||
> }
|
||||
>
|
||||
```
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* wepoll - epoll for Windows
|
||||
* https://github.com/piscisaureus/wepoll
|
||||
*
|
||||
* Copyright 2012-2020, Bert Belder <bertbelder@gmail.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * 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.
|
||||
*
|
||||
* 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
|
||||
* OWNER 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.
|
||||
*/
|
||||
|
||||
#ifndef WEPOLL_H_
|
||||
#define WEPOLL_H_
|
||||
|
||||
#ifndef WEPOLL_EXPORT
|
||||
#define WEPOLL_EXPORT
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
enum EPOLL_EVENTS
|
||||
{
|
||||
EPOLLIN = (int)(1U << 0),
|
||||
EPOLLPRI = (int)(1U << 1),
|
||||
EPOLLOUT = (int)(1U << 2),
|
||||
EPOLLERR = (int)(1U << 3),
|
||||
EPOLLHUP = (int)(1U << 4),
|
||||
EPOLLRDNORM = (int)(1U << 6),
|
||||
EPOLLRDBAND = (int)(1U << 7),
|
||||
EPOLLWRNORM = (int)(1U << 8),
|
||||
EPOLLWRBAND = (int)(1U << 9),
|
||||
EPOLLMSG = (int)(1U << 10), /* Never reported. */
|
||||
EPOLLRDHUP = (int)(1U << 13),
|
||||
EPOLLEVENT = (int)(1U << 14),
|
||||
EPOLLONESHOT = (int)(1U << 31)
|
||||
};
|
||||
|
||||
#define EPOLLIN (1U << 0)
|
||||
#define EPOLLPRI (1U << 1)
|
||||
#define EPOLLOUT (1U << 2)
|
||||
#define EPOLLERR (1U << 3)
|
||||
#define EPOLLHUP (1U << 4)
|
||||
#define EPOLLRDNORM (1U << 6)
|
||||
#define EPOLLRDBAND (1U << 7)
|
||||
#define EPOLLWRNORM (1U << 8)
|
||||
#define EPOLLWRBAND (1U << 9)
|
||||
#define EPOLLMSG (1U << 10)
|
||||
#define EPOLLRDHUP (1U << 13)
|
||||
#define EPOLLEVENT (1U << 14)
|
||||
#define EPOLLONESHOT (1U << 31)
|
||||
|
||||
#define EPOLL_CTL_ADD 1
|
||||
#define EPOLL_CTL_MOD 2
|
||||
#define EPOLL_CTL_DEL 3
|
||||
|
||||
typedef void* HANDLE;
|
||||
typedef uintptr_t SOCKET;
|
||||
|
||||
typedef union epoll_data
|
||||
{
|
||||
void* ptr;
|
||||
int fd;
|
||||
uint32_t u32;
|
||||
uint64_t u64;
|
||||
SOCKET sock; /* Windows specific */
|
||||
HANDLE hnd; /* Windows specific */
|
||||
} epoll_data_t;
|
||||
|
||||
struct epoll_event
|
||||
{
|
||||
uint32_t events; /* Epoll events and flags */
|
||||
epoll_data_t data; /* User data variable */
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
WEPOLL_EXPORT HANDLE epoll_create(int size);
|
||||
WEPOLL_EXPORT HANDLE epoll_create1(int flags);
|
||||
|
||||
WEPOLL_EXPORT int epoll_close(HANDLE ephnd);
|
||||
|
||||
WEPOLL_EXPORT int epoll_ctl(HANDLE ephnd,
|
||||
int op,
|
||||
SOCKET sock,
|
||||
struct epoll_event* event);
|
||||
|
||||
WEPOLL_EXPORT int epoll_wait(HANDLE ephnd,
|
||||
struct epoll_event* events,
|
||||
int maxevents,
|
||||
int timeout);
|
||||
WEPOLL_EXPORT void epoll_post_signal(HANDLE ephnd, uint64_t event);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#endif /* WEPOLL_H_ */
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
*
|
||||
* @file AsyncStream.h
|
||||
* @author An Tao
|
||||
*
|
||||
* Public header file in trantor lib.
|
||||
*
|
||||
* Copyright 2023, An Tao. All rights reserved.
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <trantor/utils/NonCopyable.h>
|
||||
#include <memory>
|
||||
|
||||
namespace trantor
|
||||
{
|
||||
/**
|
||||
* @brief This class represents a data stream that can be sent asynchronously.
|
||||
* The data is sent in chunks, and the chunks are sent in order, and all the
|
||||
* chunks are sent continuously.
|
||||
*/
|
||||
class TRANTOR_EXPORT AsyncStream : public NonCopyable
|
||||
{
|
||||
public:
|
||||
virtual ~AsyncStream() = default;
|
||||
/**
|
||||
* @brief Send data asynchronously.
|
||||
*
|
||||
* @param data The data to be sent
|
||||
* @param len The length of the data
|
||||
* @return true if the data is sent successfully or at least is put in the
|
||||
* send buffer.
|
||||
* @return false if the connection is closed.
|
||||
*/
|
||||
virtual bool send(const char *data, size_t len) = 0;
|
||||
bool send(const std::string &data)
|
||||
{
|
||||
return send(data.data(), data.length());
|
||||
}
|
||||
/**
|
||||
* @brief Terminate the stream.
|
||||
*/
|
||||
virtual void close() = 0;
|
||||
};
|
||||
using AsyncStreamPtr = std::unique_ptr<AsyncStream>;
|
||||
} // namespace trantor
|
||||
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
namespace trantor
|
||||
{
|
||||
struct Certificate
|
||||
{
|
||||
virtual ~Certificate() = default;
|
||||
virtual std::string sha1Fingerprint() const = 0;
|
||||
virtual std::string sha256Fingerprint() const = 0;
|
||||
virtual std::string pem() const = 0;
|
||||
};
|
||||
using CertificatePtr = std::shared_ptr<Certificate>;
|
||||
|
||||
} // namespace trantor
|
||||
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
*
|
||||
* Channel.cc
|
||||
* An Tao
|
||||
*
|
||||
* Public header file in trantor lib.
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#include "Channel.h"
|
||||
#include <trantor/net/EventLoop.h>
|
||||
#ifdef _WIN32
|
||||
#include "Wepoll.h"
|
||||
#define POLLIN EPOLLIN
|
||||
#define POLLPRI EPOLLPRI
|
||||
#define POLLOUT EPOLLOUT
|
||||
#define POLLHUP EPOLLHUP
|
||||
#define POLLNVAL 0
|
||||
#define POLLERR EPOLLERR
|
||||
#else
|
||||
#include <poll.h>
|
||||
#endif
|
||||
#include <iostream>
|
||||
namespace trantor
|
||||
{
|
||||
const int Channel::kNoneEvent = 0;
|
||||
|
||||
const int Channel::kReadEvent = POLLIN | POLLPRI;
|
||||
const int Channel::kWriteEvent = POLLOUT;
|
||||
|
||||
Channel::Channel(EventLoop *loop, int fd)
|
||||
: loop_(loop), fd_(fd), events_(0), revents_(0), index_(-1), tied_(false)
|
||||
{
|
||||
}
|
||||
|
||||
void Channel::remove()
|
||||
{
|
||||
assert(events_ == kNoneEvent);
|
||||
addedToLoop_ = false;
|
||||
loop_->removeChannel(this);
|
||||
}
|
||||
|
||||
void Channel::update()
|
||||
{
|
||||
loop_->updateChannel(this);
|
||||
}
|
||||
|
||||
void Channel::handleEvent()
|
||||
{
|
||||
// LOG_TRACE<<"revents_="<<revents_;
|
||||
if (events_ == kNoneEvent)
|
||||
return;
|
||||
if (tied_)
|
||||
{
|
||||
std::shared_ptr<void> guard = tie_.lock();
|
||||
if (guard)
|
||||
{
|
||||
handleEventSafely();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
handleEventSafely();
|
||||
}
|
||||
}
|
||||
void Channel::handleEventSafely()
|
||||
{
|
||||
if (eventCallback_)
|
||||
{
|
||||
eventCallback_();
|
||||
return;
|
||||
}
|
||||
if ((revents_ & POLLHUP) && !(revents_ & POLLIN))
|
||||
{
|
||||
// LOG_TRACE<<"handle close";
|
||||
if (closeCallback_)
|
||||
closeCallback_();
|
||||
}
|
||||
if (revents_ & (POLLNVAL | POLLERR))
|
||||
{
|
||||
// LOG_TRACE<<"handle error";
|
||||
if (errorCallback_)
|
||||
errorCallback_();
|
||||
}
|
||||
#ifdef __linux__
|
||||
if (revents_ & (POLLIN | POLLPRI | POLLRDHUP))
|
||||
#else
|
||||
if (revents_ & (POLLIN | POLLPRI))
|
||||
#endif
|
||||
{
|
||||
// LOG_TRACE<<"handle read";
|
||||
if (readCallback_)
|
||||
readCallback_();
|
||||
}
|
||||
#ifdef _WIN32
|
||||
if ((revents_ & POLLOUT) && !(revents_ & POLLHUP))
|
||||
#else
|
||||
if (revents_ & POLLOUT)
|
||||
#endif
|
||||
{
|
||||
// LOG_TRACE<<"handle write";
|
||||
if (writeCallback_)
|
||||
writeCallback_();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace trantor
|
||||
@@ -0,0 +1,315 @@
|
||||
/**
|
||||
*
|
||||
* @file Channel.h
|
||||
* @author An Tao
|
||||
*
|
||||
* Public header file in trantor lib.
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <trantor/utils/Logger.h>
|
||||
#include <trantor/utils/NonCopyable.h>
|
||||
#include <trantor/exports.h>
|
||||
#include <functional>
|
||||
#include <assert.h>
|
||||
#include <memory>
|
||||
namespace trantor
|
||||
{
|
||||
class EventLoop;
|
||||
/**
|
||||
* @brief This class is used to implement reactor pattern. A Channel object
|
||||
* manages a socket fd. Users use a Channel object to receive write or read
|
||||
* events on the socket it manages.
|
||||
*
|
||||
*/
|
||||
class TRANTOR_EXPORT Channel : NonCopyable
|
||||
{
|
||||
public:
|
||||
using EventCallback = std::function<void()>;
|
||||
/**
|
||||
* @brief Construct a new Channel instance.
|
||||
*
|
||||
* @param loop The event loop in which the channel works.
|
||||
* @param fd The socket fd.
|
||||
*/
|
||||
Channel(EventLoop *loop, int fd);
|
||||
|
||||
/**
|
||||
* @brief Set the read callback.
|
||||
*
|
||||
* @param cb The callback is called when read event occurs on the socket.
|
||||
* @note One should call the enableReading() method to ensure that the
|
||||
* callback would be called when some data is received on the socket.
|
||||
*/
|
||||
void setReadCallback(const EventCallback &cb)
|
||||
{
|
||||
readCallback_ = cb;
|
||||
};
|
||||
void setReadCallback(EventCallback &&cb)
|
||||
{
|
||||
readCallback_ = std::move(cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the write callback.
|
||||
*
|
||||
* @param cb The callback is called when write event occurs on the socket.
|
||||
* @note One should call the enableWriting() method to ensure that the
|
||||
* callback would be called when the socket can be written.
|
||||
*/
|
||||
void setWriteCallback(const EventCallback &cb)
|
||||
{
|
||||
writeCallback_ = cb;
|
||||
};
|
||||
void setWriteCallback(EventCallback &&cb)
|
||||
{
|
||||
writeCallback_ = std::move(cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the close callback.
|
||||
*
|
||||
* @param cb The callback is called when the socket is closed.
|
||||
*/
|
||||
void setCloseCallback(const EventCallback &cb)
|
||||
{
|
||||
closeCallback_ = cb;
|
||||
}
|
||||
void setCloseCallback(EventCallback &&cb)
|
||||
{
|
||||
closeCallback_ = std::move(cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the error callback.
|
||||
*
|
||||
* @param cb The callback is called when an error occurs on the socket.
|
||||
*/
|
||||
void setErrorCallback(const EventCallback &cb)
|
||||
{
|
||||
errorCallback_ = cb;
|
||||
}
|
||||
void setErrorCallback(EventCallback &&cb)
|
||||
{
|
||||
errorCallback_ = std::move(cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the event callback.
|
||||
*
|
||||
* @param cb The callback is called when any event occurs on the socket.
|
||||
* @note If the event callback is set to the channel, any other callback
|
||||
* wouldn't be called again.
|
||||
*/
|
||||
void setEventCallback(const EventCallback &cb)
|
||||
{
|
||||
eventCallback_ = cb;
|
||||
}
|
||||
void setEventCallback(EventCallback &&cb)
|
||||
{
|
||||
eventCallback_ = std::move(cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the fd of the socket.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
int fd() const
|
||||
{
|
||||
return fd_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the events enabled on the socket.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
int events() const
|
||||
{
|
||||
return events_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the events that occurred on the socket.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
int revents() const
|
||||
{
|
||||
return revents_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check whether there is no event enabled on the socket.
|
||||
*
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool isNoneEvent() const
|
||||
{
|
||||
return events_ == kNoneEvent;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Disable all events on the socket.
|
||||
*
|
||||
*/
|
||||
void disableAll()
|
||||
{
|
||||
events_ = kNoneEvent;
|
||||
update();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Remove the socket from the poller in the event loop.
|
||||
*
|
||||
*/
|
||||
void remove();
|
||||
|
||||
/**
|
||||
* @brief Return the event loop.
|
||||
*
|
||||
* @return EventLoop*
|
||||
*/
|
||||
EventLoop *ownerLoop()
|
||||
{
|
||||
return loop_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Enable the read event on the socket.
|
||||
*
|
||||
*/
|
||||
void enableReading()
|
||||
{
|
||||
events_ |= kReadEvent;
|
||||
update();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disable the read event on the socket.
|
||||
*
|
||||
*/
|
||||
void disableReading()
|
||||
{
|
||||
events_ &= ~kReadEvent;
|
||||
update();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Enable the write event on the socket.
|
||||
*
|
||||
*/
|
||||
void enableWriting()
|
||||
{
|
||||
events_ |= kWriteEvent;
|
||||
update();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disable the write event on the socket.
|
||||
*
|
||||
*/
|
||||
void disableWriting()
|
||||
{
|
||||
events_ &= ~kWriteEvent;
|
||||
update();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check whether the write event is enabled on the socket.
|
||||
*
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool isWriting() const
|
||||
{
|
||||
return events_ & kWriteEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check whether the read event is enabled on the socket.
|
||||
*
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool isReading() const
|
||||
{
|
||||
return events_ & kReadEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set and update the events enabled.
|
||||
*
|
||||
* @param events
|
||||
*/
|
||||
void updateEvents(int events)
|
||||
{
|
||||
events_ = events;
|
||||
update();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief This method is used to ensure that the callback owner is valid
|
||||
* when a callback is called.
|
||||
*
|
||||
* @param obj The callback owner. Usually, the owner is also the owner of
|
||||
* the channel.
|
||||
* @note The 'obj' is kept in a weak_ptr object, so this method does not
|
||||
* cause a circular reference problem.
|
||||
*/
|
||||
void tie(const std::shared_ptr<void> &obj)
|
||||
{
|
||||
tie_ = obj;
|
||||
tied_ = true;
|
||||
}
|
||||
|
||||
static const int kNoneEvent;
|
||||
static const int kReadEvent;
|
||||
static const int kWriteEvent;
|
||||
|
||||
private:
|
||||
friend class EventLoop;
|
||||
friend class EpollPoller;
|
||||
friend class KQueue;
|
||||
friend class PollPoller;
|
||||
void update();
|
||||
void handleEvent();
|
||||
void handleEventSafely();
|
||||
int setRevents(int revt)
|
||||
{
|
||||
// LOG_TRACE<<"revents="<<revt;
|
||||
revents_ = revt;
|
||||
return revt;
|
||||
};
|
||||
int index()
|
||||
{
|
||||
return index_;
|
||||
};
|
||||
void setIndex(int index)
|
||||
{
|
||||
index_ = index;
|
||||
};
|
||||
EventLoop *loop_;
|
||||
const int fd_;
|
||||
int events_;
|
||||
int revents_;
|
||||
int index_;
|
||||
bool addedToLoop_{false};
|
||||
EventCallback readCallback_;
|
||||
EventCallback writeCallback_;
|
||||
EventCallback errorCallback_;
|
||||
EventCallback closeCallback_;
|
||||
EventCallback eventCallback_;
|
||||
std::weak_ptr<void> tie_;
|
||||
bool tied_;
|
||||
};
|
||||
} // namespace trantor
|
||||
@@ -0,0 +1,423 @@
|
||||
// Copyright 2010, Shuo Chen. All rights reserved.
|
||||
// http://code.google.com/p/muduo/
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license
|
||||
// that can be found in the License file.
|
||||
|
||||
// Author: Shuo Chen (chenshuo at chenshuo dot com)
|
||||
|
||||
// Taken from Muduo and modified
|
||||
// Copyright 2016, Tao An. All rights reserved.
|
||||
// https://github.com/an-tao/trantor
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license
|
||||
// that can be found in the License file.
|
||||
|
||||
// Author: Tao An
|
||||
|
||||
#include <trantor/net/EventLoop.h>
|
||||
#include <trantor/utils/Logger.h>
|
||||
|
||||
#include "Poller.h"
|
||||
#include "TimerQueue.h"
|
||||
#include "Channel.h"
|
||||
|
||||
#include <thread>
|
||||
#include <assert.h>
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <io.h>
|
||||
#include <synchapi.h>
|
||||
#ifndef _SSIZE_T_DEFINED
|
||||
using ssize_t = long long;
|
||||
#endif
|
||||
#else
|
||||
#include <poll.h>
|
||||
#endif
|
||||
#include <iostream>
|
||||
#ifdef __linux__
|
||||
#include <sys/eventfd.h>
|
||||
#endif
|
||||
#include <functional>
|
||||
#ifndef _WIN32
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include <algorithm>
|
||||
#include <signal.h>
|
||||
#include <fcntl.h>
|
||||
#include <exception>
|
||||
|
||||
namespace trantor
|
||||
{
|
||||
#ifdef __linux__
|
||||
int createEventfd()
|
||||
{
|
||||
int evtfd = ::eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
|
||||
if (evtfd < 0)
|
||||
{
|
||||
std::cout << "Failed in eventfd" << std::endl;
|
||||
abort();
|
||||
}
|
||||
|
||||
return evtfd;
|
||||
}
|
||||
const int kPollTimeMs = 10000;
|
||||
#endif
|
||||
thread_local EventLoop *t_loopInThisThread = nullptr;
|
||||
|
||||
EventLoop::EventLoop()
|
||||
: looping_(false),
|
||||
threadId_(std::this_thread::get_id()),
|
||||
quit_(false),
|
||||
poller_(Poller::newPoller(this)),
|
||||
currentActiveChannel_(nullptr),
|
||||
eventHandling_(false),
|
||||
timerQueue_(new TimerQueue(this)),
|
||||
#ifdef __linux__
|
||||
wakeupFd_(createEventfd()),
|
||||
wakeupChannelPtr_(new Channel(this, wakeupFd_)),
|
||||
#endif
|
||||
threadLocalLoopPtr_(&t_loopInThisThread)
|
||||
{
|
||||
if (t_loopInThisThread)
|
||||
{
|
||||
LOG_FATAL << "There is already an EventLoop in this thread";
|
||||
exit(-1);
|
||||
}
|
||||
t_loopInThisThread = this;
|
||||
#ifdef __linux__
|
||||
wakeupChannelPtr_->setReadCallback(std::bind(&EventLoop::wakeupRead, this));
|
||||
wakeupChannelPtr_->enableReading();
|
||||
#elif !defined _WIN32
|
||||
auto r = pipe(wakeupFd_);
|
||||
(void)r;
|
||||
assert(!r);
|
||||
fcntl(wakeupFd_[0], F_SETFL, O_NONBLOCK | O_CLOEXEC);
|
||||
fcntl(wakeupFd_[1], F_SETFL, O_NONBLOCK | O_CLOEXEC);
|
||||
wakeupChannelPtr_ =
|
||||
std::unique_ptr<Channel>(new Channel(this, wakeupFd_[0]));
|
||||
wakeupChannelPtr_->setReadCallback(std::bind(&EventLoop::wakeupRead, this));
|
||||
wakeupChannelPtr_->enableReading();
|
||||
#else
|
||||
poller_->setEventCallback([](uint64_t event) { assert(event == 1); });
|
||||
#endif
|
||||
}
|
||||
#ifdef __linux__
|
||||
void EventLoop::resetTimerQueue()
|
||||
{
|
||||
assertInLoopThread();
|
||||
assert(!looping_.load(std::memory_order_acquire));
|
||||
timerQueue_->reset();
|
||||
}
|
||||
#endif
|
||||
void EventLoop::resetAfterFork()
|
||||
{
|
||||
poller_->resetAfterFork();
|
||||
}
|
||||
EventLoop::~EventLoop()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
DWORD delay = 1; /* 1 msec */
|
||||
#else
|
||||
struct timespec delay = {0, 1000000}; /* 1 msec */
|
||||
#endif
|
||||
|
||||
quit();
|
||||
|
||||
// Spin waiting for the loop to exit because
|
||||
// this may take some time to complete. We
|
||||
// assume the loop thread will *always* exit.
|
||||
// If this cannot be guaranteed then one option
|
||||
// might be to abort waiting and
|
||||
// assert(!looping_) after some delay;
|
||||
while (looping_.load(std::memory_order_acquire))
|
||||
{
|
||||
#ifdef _WIN32
|
||||
Sleep(delay);
|
||||
#else
|
||||
nanosleep(&delay, nullptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
close(wakeupFd_);
|
||||
#elif defined _WIN32
|
||||
#else
|
||||
close(wakeupFd_[0]);
|
||||
close(wakeupFd_[1]);
|
||||
#endif
|
||||
}
|
||||
EventLoop *EventLoop::getEventLoopOfCurrentThread()
|
||||
{
|
||||
return t_loopInThisThread;
|
||||
}
|
||||
void EventLoop::updateChannel(Channel *channel)
|
||||
{
|
||||
assert(channel->ownerLoop() == this);
|
||||
assertInLoopThread();
|
||||
poller_->updateChannel(channel);
|
||||
}
|
||||
void EventLoop::removeChannel(Channel *channel)
|
||||
{
|
||||
assert(channel->ownerLoop() == this);
|
||||
assertInLoopThread();
|
||||
poller_->removeChannel(channel);
|
||||
}
|
||||
void EventLoop::quit()
|
||||
{
|
||||
quit_.store(true, std::memory_order_release);
|
||||
|
||||
if (!isInLoopThread())
|
||||
{
|
||||
wakeup();
|
||||
}
|
||||
}
|
||||
|
||||
// The event loop needs a scope exit, so here's the simplest most limited
|
||||
// C++14 scope exit available (from
|
||||
// https://stackoverflow.com/a/42506763/3173540)
|
||||
//
|
||||
// TODO: If this is needed anywhere else, introduce a proper on_exit from, for
|
||||
// example, the GSL library
|
||||
namespace
|
||||
{
|
||||
template <typename F>
|
||||
struct ScopeExit
|
||||
{
|
||||
ScopeExit(F &&f) : f_(std::forward<F>(f))
|
||||
{
|
||||
}
|
||||
~ScopeExit()
|
||||
{
|
||||
f_();
|
||||
}
|
||||
F f_;
|
||||
};
|
||||
|
||||
template <typename F>
|
||||
ScopeExit<F> makeScopeExit(F &&f)
|
||||
{
|
||||
return ScopeExit<F>(std::forward<F>(f));
|
||||
};
|
||||
} // namespace
|
||||
|
||||
void EventLoop::loop()
|
||||
{
|
||||
assert(!looping_);
|
||||
assertInLoopThread();
|
||||
looping_.store(true, std::memory_order_release);
|
||||
quit_.store(false, std::memory_order_release);
|
||||
|
||||
std::exception_ptr loopException;
|
||||
try
|
||||
{ // Scope where the loop flag is set
|
||||
|
||||
auto loopFlagCleaner = makeScopeExit(
|
||||
[this]() { looping_.store(false, std::memory_order_release); });
|
||||
while (!quit_.load(std::memory_order_acquire))
|
||||
{
|
||||
activeChannels_.clear();
|
||||
#ifdef __linux__
|
||||
poller_->poll(kPollTimeMs, &activeChannels_);
|
||||
#else
|
||||
poller_->poll(static_cast<int>(timerQueue_->getTimeout()),
|
||||
&activeChannels_);
|
||||
timerQueue_->processTimers();
|
||||
#endif
|
||||
// TODO sort channel by priority
|
||||
// std::cout<<"after ->poll()"<<std::endl;
|
||||
eventHandling_ = true;
|
||||
for (auto it = activeChannels_.begin(); it != activeChannels_.end();
|
||||
++it)
|
||||
{
|
||||
currentActiveChannel_ = *it;
|
||||
currentActiveChannel_->handleEvent();
|
||||
}
|
||||
currentActiveChannel_ = nullptr;
|
||||
eventHandling_ = false;
|
||||
// std::cout << "looping" << endl;
|
||||
doRunInLoopFuncs();
|
||||
}
|
||||
// loopFlagCleaner clears the loop flag here
|
||||
}
|
||||
catch (std::exception &e)
|
||||
{
|
||||
LOG_WARN << "Exception thrown from event loop, rethrowing after "
|
||||
"running functions on quit: "
|
||||
<< e.what();
|
||||
loopException = std::current_exception();
|
||||
}
|
||||
|
||||
// Run the quit functions even if exceptions were thrown
|
||||
// TODO: if more exceptions are thrown in the quit functions, some are left
|
||||
// un-run. Can this be made exception safe?
|
||||
Func f;
|
||||
while (funcsOnQuit_.dequeue(f))
|
||||
{
|
||||
f();
|
||||
}
|
||||
t_loopInThisThread = nullptr;
|
||||
// Throw the exception from the end
|
||||
if (loopException)
|
||||
{
|
||||
LOG_WARN << "Rethrowing exception from event loop";
|
||||
std::rethrow_exception(loopException);
|
||||
}
|
||||
}
|
||||
void EventLoop::abortNotInLoopThread()
|
||||
{
|
||||
LOG_FATAL << "It is forbidden to run loop on threads other than event-loop "
|
||||
"thread";
|
||||
exit(1);
|
||||
}
|
||||
void EventLoop::queueInLoop(const Func &cb)
|
||||
{
|
||||
funcs_.enqueue(cb);
|
||||
if (!isInLoopThread() || !looping_.load(std::memory_order_acquire))
|
||||
{
|
||||
wakeup();
|
||||
}
|
||||
}
|
||||
void EventLoop::queueInLoop(Func &&cb)
|
||||
{
|
||||
funcs_.enqueue(std::move(cb));
|
||||
if (!isInLoopThread() || !looping_.load(std::memory_order_acquire))
|
||||
{
|
||||
wakeup();
|
||||
}
|
||||
}
|
||||
|
||||
TimerId EventLoop::runAt(const Date &time, const Func &cb)
|
||||
{
|
||||
auto microSeconds =
|
||||
time.microSecondsSinceEpoch() - Date::now().microSecondsSinceEpoch();
|
||||
std::chrono::steady_clock::time_point tp =
|
||||
std::chrono::steady_clock::now() +
|
||||
std::chrono::microseconds(microSeconds);
|
||||
return timerQueue_->addTimer(cb, tp, std::chrono::microseconds(0));
|
||||
}
|
||||
TimerId EventLoop::runAt(const Date &time, Func &&cb)
|
||||
{
|
||||
auto microSeconds =
|
||||
time.microSecondsSinceEpoch() - Date::now().microSecondsSinceEpoch();
|
||||
std::chrono::steady_clock::time_point tp =
|
||||
std::chrono::steady_clock::now() +
|
||||
std::chrono::microseconds(microSeconds);
|
||||
return timerQueue_->addTimer(std::move(cb),
|
||||
tp,
|
||||
std::chrono::microseconds(0));
|
||||
}
|
||||
TimerId EventLoop::runAfter(double delay, const Func &cb)
|
||||
{
|
||||
return runAt(Date::date().after(delay), cb);
|
||||
}
|
||||
TimerId EventLoop::runAfter(double delay, Func &&cb)
|
||||
{
|
||||
return runAt(Date::date().after(delay), std::move(cb));
|
||||
}
|
||||
TimerId EventLoop::runEvery(double interval, const Func &cb)
|
||||
{
|
||||
std::chrono::microseconds dur(
|
||||
static_cast<std::chrono::microseconds::rep>(interval * 1000000));
|
||||
auto tp = std::chrono::steady_clock::now() + dur;
|
||||
return timerQueue_->addTimer(cb, tp, dur);
|
||||
}
|
||||
TimerId EventLoop::runEvery(double interval, Func &&cb)
|
||||
{
|
||||
std::chrono::microseconds dur(
|
||||
static_cast<std::chrono::microseconds::rep>(interval * 1000000));
|
||||
auto tp = std::chrono::steady_clock::now() + dur;
|
||||
return timerQueue_->addTimer(std::move(cb), tp, dur);
|
||||
}
|
||||
void EventLoop::invalidateTimer(TimerId id)
|
||||
{
|
||||
if (isRunning() && timerQueue_)
|
||||
timerQueue_->invalidateTimer(id);
|
||||
}
|
||||
void EventLoop::doRunInLoopFuncs()
|
||||
{
|
||||
callingFuncs_ = true;
|
||||
{
|
||||
// Assure the flag is cleared even if func throws
|
||||
auto callingFlagCleaner =
|
||||
makeScopeExit([this]() { callingFuncs_ = false; });
|
||||
// the destructor for the Func may itself insert a new entry into the
|
||||
// queue
|
||||
// TODO: The following is exception-unsafe. If one of the funcs throws,
|
||||
// the remaining ones will not get run. The simplest fix is to catch any
|
||||
// exceptions and rethrow them later, but somehow that seems fishy...
|
||||
while (!funcs_.empty())
|
||||
{
|
||||
Func func;
|
||||
while (funcs_.dequeue(func))
|
||||
{
|
||||
func();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
void EventLoop::wakeup()
|
||||
{
|
||||
// if (!looping_)
|
||||
// return;
|
||||
uint64_t tmp = 1;
|
||||
#ifdef __linux__
|
||||
int ret = write(wakeupFd_, &tmp, sizeof(tmp));
|
||||
(void)ret;
|
||||
#elif defined _WIN32
|
||||
poller_->postEvent(1);
|
||||
#else
|
||||
int ret = write(wakeupFd_[1], &tmp, sizeof(tmp));
|
||||
(void)ret;
|
||||
#endif
|
||||
}
|
||||
void EventLoop::wakeupRead()
|
||||
{
|
||||
ssize_t ret = 0;
|
||||
#ifdef __linux__
|
||||
uint64_t tmp;
|
||||
ret = read(wakeupFd_, &tmp, sizeof(tmp));
|
||||
#elif defined _WIN32
|
||||
#else
|
||||
uint64_t tmp;
|
||||
ret = read(wakeupFd_[0], &tmp, sizeof(tmp));
|
||||
#endif
|
||||
if (ret < 0)
|
||||
LOG_SYSERR << "wakeup read error";
|
||||
}
|
||||
|
||||
void EventLoop::moveToCurrentThread()
|
||||
{
|
||||
if (isRunning())
|
||||
{
|
||||
LOG_FATAL << "EventLoop cannot be moved when running";
|
||||
exit(-1);
|
||||
}
|
||||
if (isInLoopThread())
|
||||
{
|
||||
LOG_WARN << "This EventLoop is already in the current thread";
|
||||
return;
|
||||
}
|
||||
if (t_loopInThisThread)
|
||||
{
|
||||
LOG_FATAL << "There is already an EventLoop in this thread, you cannot "
|
||||
"move another in";
|
||||
exit(-1);
|
||||
}
|
||||
*threadLocalLoopPtr_ = nullptr;
|
||||
t_loopInThisThread = this;
|
||||
threadLocalLoopPtr_ = &t_loopInThisThread;
|
||||
threadId_ = std::this_thread::get_id();
|
||||
}
|
||||
|
||||
void EventLoop::runOnQuit(Func &&cb)
|
||||
{
|
||||
funcsOnQuit_.enqueue(std::move(cb));
|
||||
}
|
||||
|
||||
void EventLoop::runOnQuit(const Func &cb)
|
||||
{
|
||||
funcsOnQuit_.enqueue(cb);
|
||||
}
|
||||
|
||||
} // namespace trantor
|
||||
@@ -0,0 +1,333 @@
|
||||
// Copyright 2010, Shuo Chen. All rights reserved.
|
||||
// http://code.google.com/p/muduo/
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license
|
||||
// that can be found in the License file.
|
||||
|
||||
// Author: Shuo Chen (chenshuo at chenshuo dot com)
|
||||
|
||||
// Taken from Muduo and modified
|
||||
// Copyright 2016, Tao An. All rights reserved.
|
||||
// https://github.com/an-tao/trantor
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license
|
||||
// that can be found in the License file.
|
||||
|
||||
// Author: Tao An
|
||||
|
||||
#pragma once
|
||||
#include <trantor/utils/NonCopyable.h>
|
||||
#include <trantor/utils/Date.h>
|
||||
#include <trantor/utils/LockFreeQueue.h>
|
||||
#include <trantor/exports.h>
|
||||
#include <thread>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <functional>
|
||||
#include <chrono>
|
||||
#include <limits>
|
||||
#include <atomic>
|
||||
|
||||
namespace trantor
|
||||
{
|
||||
class Poller;
|
||||
class TimerQueue;
|
||||
class Channel;
|
||||
using ChannelList = std::vector<Channel *>;
|
||||
using Func = std::function<void()>;
|
||||
using TimerId = uint64_t;
|
||||
enum
|
||||
{
|
||||
InvalidTimerId = 0
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief As the name implies, this class represents an event loop that runs in
|
||||
* a particular thread. The event loop can handle network I/O events and timers
|
||||
* in asynchronous mode.
|
||||
* @note An event loop object always belongs to a separate thread, and there is
|
||||
* one event loop object at most in a thread. We can call an event loop object
|
||||
* the event loop of the thread it belongs to, or call that thread the thread of
|
||||
* the event loop.
|
||||
*/
|
||||
class TRANTOR_EXPORT EventLoop : NonCopyable
|
||||
{
|
||||
public:
|
||||
EventLoop();
|
||||
~EventLoop();
|
||||
|
||||
/**
|
||||
* @brief Run the event loop. This method will be blocked until the event
|
||||
* loop exits.
|
||||
*
|
||||
*/
|
||||
void loop();
|
||||
|
||||
/**
|
||||
* @brief Let the event loop quit.
|
||||
*
|
||||
*/
|
||||
void quit();
|
||||
|
||||
/**
|
||||
* @brief Assertion that the current thread is the thread to which the event
|
||||
* loop belongs. If the assertion fails, the program aborts.
|
||||
*/
|
||||
void assertInLoopThread()
|
||||
{
|
||||
if (!isInLoopThread())
|
||||
{
|
||||
abortNotInLoopThread();
|
||||
}
|
||||
};
|
||||
#ifdef __linux__
|
||||
/**
|
||||
* @brief Make the timer queue works after calling the fork() function.
|
||||
*
|
||||
*/
|
||||
void resetTimerQueue();
|
||||
#endif
|
||||
/**
|
||||
* @brief Make the event loop works after calling the fork() function.
|
||||
*
|
||||
*/
|
||||
void resetAfterFork();
|
||||
|
||||
/**
|
||||
* @brief Return true if the current thread is the thread to which the event
|
||||
* loop belongs.
|
||||
*
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool isInLoopThread() const
|
||||
{
|
||||
return threadId_ == std::this_thread::get_id();
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Get the event loop of the current thread. Return nullptr if there
|
||||
* is no event loop in the current thread.
|
||||
*
|
||||
* @return EventLoop*
|
||||
*/
|
||||
static EventLoop *getEventLoopOfCurrentThread();
|
||||
|
||||
/**
|
||||
* @brief Run the function f in the thread of the event loop.
|
||||
*
|
||||
* @param f
|
||||
* @note If the current thread is the thread of the event loop, the function
|
||||
* f is executed directly before the method exiting.
|
||||
*/
|
||||
template <typename Functor>
|
||||
inline void runInLoop(Functor &&f)
|
||||
{
|
||||
if (isInLoopThread())
|
||||
{
|
||||
f();
|
||||
}
|
||||
else
|
||||
{
|
||||
queueInLoop(std::forward<Functor>(f));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Run the function f in the thread of the event loop.
|
||||
*
|
||||
* @param f
|
||||
* @note The difference between this method and the runInLoop() method is
|
||||
* that the function f is executed after the method exiting no matter if the
|
||||
* current thread is the thread of the event loop.
|
||||
*/
|
||||
void queueInLoop(const Func &f);
|
||||
void queueInLoop(Func &&f);
|
||||
|
||||
/**
|
||||
* @brief Run a function at a time point.
|
||||
*
|
||||
* @param time The time to run the function.
|
||||
* @param cb The function to run.
|
||||
* @return TimerId The ID of the timer.
|
||||
*/
|
||||
TimerId runAt(const Date &time, const Func &cb);
|
||||
TimerId runAt(const Date &time, Func &&cb);
|
||||
|
||||
/**
|
||||
* @brief Run a function after a period of time.
|
||||
*
|
||||
* @param delay Represent the period of time in seconds.
|
||||
* @param cb The function to run.
|
||||
* @return TimerId The ID of the timer.
|
||||
*/
|
||||
TimerId runAfter(double delay, const Func &cb);
|
||||
TimerId runAfter(double delay, Func &&cb);
|
||||
|
||||
/**
|
||||
* @brief Run a function after a period of time.
|
||||
* @note Users could use chrono literals to represent a time duration
|
||||
* For example:
|
||||
* @code
|
||||
runAfter(5s, task);
|
||||
runAfter(10min, task);
|
||||
@endcode
|
||||
*/
|
||||
TimerId runAfter(const std::chrono::duration<double> &delay, const Func &cb)
|
||||
{
|
||||
return runAfter(delay.count(), cb);
|
||||
}
|
||||
TimerId runAfter(const std::chrono::duration<double> &delay, Func &&cb)
|
||||
{
|
||||
return runAfter(delay.count(), std::move(cb));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Repeatedly run a function every period of time.
|
||||
*
|
||||
* @param interval The duration in seconds.
|
||||
* @param cb The function to run.
|
||||
* @return TimerId The ID of the timer.
|
||||
*/
|
||||
TimerId runEvery(double interval, const Func &cb);
|
||||
TimerId runEvery(double interval, Func &&cb);
|
||||
|
||||
/**
|
||||
* @brief Repeatedly run a function every period of time.
|
||||
* Users could use chrono literals to represent a time duration
|
||||
* For example:
|
||||
* @code
|
||||
runEvery(5s, task);
|
||||
runEvery(10min, task);
|
||||
runEvery(0.1h, task);
|
||||
@endcode
|
||||
*/
|
||||
TimerId runEvery(const std::chrono::duration<double> &interval,
|
||||
const Func &cb)
|
||||
{
|
||||
return runEvery(interval.count(), cb);
|
||||
}
|
||||
TimerId runEvery(const std::chrono::duration<double> &interval, Func &&cb)
|
||||
{
|
||||
return runEvery(interval.count(), std::move(cb));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Invalidate the timer identified by the given ID.
|
||||
*
|
||||
* @param id The ID of the timer.
|
||||
*/
|
||||
void invalidateTimer(TimerId id);
|
||||
|
||||
/**
|
||||
* @brief Move the EventLoop to the current thread, this method must be
|
||||
* called before the loop is running.
|
||||
*
|
||||
*/
|
||||
void moveToCurrentThread();
|
||||
|
||||
/**
|
||||
* @brief Update channel status. This method is usually used internally.
|
||||
*
|
||||
* @param chl
|
||||
*/
|
||||
void updateChannel(Channel *chl);
|
||||
|
||||
/**
|
||||
* @brief Remove a channel from the event loop. This method is usually used
|
||||
* internally.
|
||||
*
|
||||
* @param chl
|
||||
*/
|
||||
void removeChannel(Channel *chl);
|
||||
|
||||
/**
|
||||
* @brief Return the index of the event loop.
|
||||
*
|
||||
* @return size_t
|
||||
*/
|
||||
size_t index()
|
||||
{
|
||||
return index_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the index of the event loop.
|
||||
*
|
||||
* @param index
|
||||
*/
|
||||
void setIndex(size_t index)
|
||||
{
|
||||
index_ = index;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return true if the event loop is running.
|
||||
*
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool isRunning()
|
||||
{
|
||||
return looping_.load(std::memory_order_acquire) &&
|
||||
(!quit_.load(std::memory_order_acquire));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if the event loop is calling a function.
|
||||
*
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool isCallingFunctions()
|
||||
{
|
||||
return callingFuncs_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Run functions when the event loop quits
|
||||
*
|
||||
* @param cb the function to run
|
||||
* @note the function runs on the thread that quits the EventLoop
|
||||
*/
|
||||
void runOnQuit(Func &&cb);
|
||||
void runOnQuit(const Func &cb);
|
||||
|
||||
private:
|
||||
void abortNotInLoopThread();
|
||||
void wakeup();
|
||||
void wakeupRead();
|
||||
std::atomic<bool> looping_;
|
||||
std::thread::id threadId_;
|
||||
std::atomic<bool> quit_;
|
||||
std::unique_ptr<Poller> poller_;
|
||||
|
||||
ChannelList activeChannels_;
|
||||
Channel *currentActiveChannel_;
|
||||
|
||||
bool eventHandling_;
|
||||
MpscQueue<Func> funcs_;
|
||||
std::unique_ptr<TimerQueue> timerQueue_;
|
||||
MpscQueue<Func> funcsOnQuit_;
|
||||
bool callingFuncs_{false};
|
||||
#ifdef __linux__
|
||||
int wakeupFd_;
|
||||
std::unique_ptr<Channel> wakeupChannelPtr_;
|
||||
#elif defined _WIN32
|
||||
#else
|
||||
int wakeupFd_[2];
|
||||
std::unique_ptr<Channel> wakeupChannelPtr_;
|
||||
#endif
|
||||
|
||||
void doRunInLoopFuncs();
|
||||
#ifdef _WIN32
|
||||
size_t index_{size_t(-1)};
|
||||
#else
|
||||
size_t index_{std::numeric_limits<size_t>::max()};
|
||||
#endif
|
||||
EventLoop **threadLocalLoopPtr_;
|
||||
};
|
||||
|
||||
} // namespace trantor
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
*
|
||||
* @file EventLoopThread.cc
|
||||
* @author An Tao
|
||||
*
|
||||
* Public header file in trantor lib.
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#include <trantor/net/EventLoopThread.h>
|
||||
#include <trantor/utils/Logger.h>
|
||||
#ifdef __linux__
|
||||
#include <sys/prctl.h>
|
||||
#endif
|
||||
|
||||
using namespace trantor;
|
||||
EventLoopThread::EventLoopThread(const std::string &threadName)
|
||||
: loop_(nullptr),
|
||||
loopThreadName_(threadName),
|
||||
thread_([this]() { loopFuncs(); })
|
||||
{
|
||||
auto f = promiseForLoopPointer_.get_future();
|
||||
loop_ = f.get();
|
||||
}
|
||||
|
||||
EventLoopThread::~EventLoopThread()
|
||||
{
|
||||
run();
|
||||
std::shared_ptr<EventLoop> loop;
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(loopMutex_);
|
||||
loop = loop_;
|
||||
}
|
||||
if (loop)
|
||||
{
|
||||
loop->quit();
|
||||
}
|
||||
if (thread_.joinable())
|
||||
{
|
||||
thread_.join();
|
||||
}
|
||||
}
|
||||
|
||||
void EventLoopThread::wait()
|
||||
{
|
||||
thread_.join();
|
||||
}
|
||||
|
||||
void EventLoopThread::loopFuncs()
|
||||
{
|
||||
#ifdef __linux__
|
||||
::prctl(PR_SET_NAME, loopThreadName_.c_str());
|
||||
#endif
|
||||
thread_local static std::shared_ptr<EventLoop> loop =
|
||||
std::make_shared<EventLoop>();
|
||||
loop->queueInLoop([this]() { promiseForLoop_.set_value(1); });
|
||||
promiseForLoopPointer_.set_value(loop);
|
||||
auto f = promiseForRun_.get_future();
|
||||
(void)f.get();
|
||||
loop->loop();
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(loopMutex_);
|
||||
loop_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void EventLoopThread::run()
|
||||
{
|
||||
std::call_once(once_, [this]() {
|
||||
auto f = promiseForLoop_.get_future();
|
||||
promiseForRun_.set_value(1);
|
||||
// Make sure the event loop loops before returning.
|
||||
(void)f.get();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
*
|
||||
* @file EventLoopThread.h
|
||||
* @author An Tao
|
||||
*
|
||||
* Public header file in trantor lib.
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <trantor/net/EventLoop.h>
|
||||
#include <trantor/utils/NonCopyable.h>
|
||||
#include <trantor/exports.h>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <memory>
|
||||
#include <condition_variable>
|
||||
#include <future>
|
||||
|
||||
namespace trantor
|
||||
{
|
||||
/**
|
||||
* @brief This class represents an event loop thread.
|
||||
*
|
||||
*/
|
||||
class TRANTOR_EXPORT EventLoopThread : NonCopyable
|
||||
{
|
||||
public:
|
||||
explicit EventLoopThread(const std::string &threadName = "EventLoopThread");
|
||||
~EventLoopThread();
|
||||
|
||||
/**
|
||||
* @brief Wait for the event loop to exit.
|
||||
* @note This method blocks the current thread until the event loop exits.
|
||||
*/
|
||||
void wait();
|
||||
|
||||
/**
|
||||
* @brief Get the pointer of the event loop of the thread.
|
||||
*
|
||||
* @return EventLoop*
|
||||
*/
|
||||
EventLoop *getLoop() const
|
||||
{
|
||||
return loop_.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Run the event loop of the thread. This method doesn't block the
|
||||
* current thread.
|
||||
*
|
||||
*/
|
||||
void run();
|
||||
|
||||
private:
|
||||
// With C++20, use std::atomic<std::shared_ptr<EventLoop>>
|
||||
std::shared_ptr<EventLoop> loop_;
|
||||
std::mutex loopMutex_;
|
||||
|
||||
std::string loopThreadName_;
|
||||
void loopFuncs();
|
||||
std::promise<std::shared_ptr<EventLoop>> promiseForLoopPointer_;
|
||||
std::promise<int> promiseForRun_;
|
||||
std::promise<int> promiseForLoop_;
|
||||
std::once_flag once_;
|
||||
std::thread thread_;
|
||||
};
|
||||
|
||||
} // namespace trantor
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
*
|
||||
* EventLoopThreadPool.cc
|
||||
* An Tao
|
||||
*
|
||||
* Public header file in trantor lib.
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#include <trantor/net/EventLoopThreadPool.h>
|
||||
using namespace trantor;
|
||||
EventLoopThreadPool::EventLoopThreadPool(size_t threadNum,
|
||||
const std::string &name)
|
||||
: loopIndex_(0)
|
||||
{
|
||||
for (size_t i = 0; i < threadNum; ++i)
|
||||
{
|
||||
loopThreadVector_.emplace_back(std::make_shared<EventLoopThread>(name));
|
||||
}
|
||||
}
|
||||
void EventLoopThreadPool::start()
|
||||
{
|
||||
for (unsigned int i = 0; i < loopThreadVector_.size(); ++i)
|
||||
{
|
||||
loopThreadVector_[i]->run();
|
||||
}
|
||||
}
|
||||
// void EventLoopThreadPool::stop(){
|
||||
// for(unsigned int i=0;i<loopThreadVector_.size();i++)
|
||||
// {
|
||||
// loopThreadVector_[i].stop();
|
||||
// }
|
||||
//}
|
||||
void EventLoopThreadPool::wait()
|
||||
{
|
||||
for (unsigned int i = 0; i < loopThreadVector_.size(); ++i)
|
||||
{
|
||||
loopThreadVector_[i]->wait();
|
||||
}
|
||||
}
|
||||
EventLoop *EventLoopThreadPool::getNextLoop()
|
||||
{
|
||||
if (loopThreadVector_.size() > 0)
|
||||
{
|
||||
size_t index = loopIndex_.fetch_add(1, std::memory_order_relaxed);
|
||||
EventLoop *loop =
|
||||
loopThreadVector_[index % loopThreadVector_.size()]->getLoop();
|
||||
return loop;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
EventLoop *EventLoopThreadPool::getLoop(size_t id)
|
||||
{
|
||||
if (id < loopThreadVector_.size())
|
||||
return loopThreadVector_[id]->getLoop();
|
||||
return nullptr;
|
||||
}
|
||||
std::vector<EventLoop *> EventLoopThreadPool::getLoops() const
|
||||
{
|
||||
std::vector<EventLoop *> ret;
|
||||
for (auto &loopThread : loopThreadVector_)
|
||||
{
|
||||
ret.push_back(loopThread->getLoop());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
*
|
||||
* @file EventLoopThreadPool.h
|
||||
* @author An Tao
|
||||
*
|
||||
* Public header file in trantor lib.
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <trantor/net/EventLoopThread.h>
|
||||
#include <trantor/exports.h>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <atomic>
|
||||
|
||||
namespace trantor
|
||||
{
|
||||
/**
|
||||
* @brief This class represents a pool of EventLoopThread objects
|
||||
*
|
||||
*/
|
||||
class TRANTOR_EXPORT EventLoopThreadPool : NonCopyable
|
||||
{
|
||||
public:
|
||||
EventLoopThreadPool() = delete;
|
||||
|
||||
/**
|
||||
* @brief Construct a new event loop thread pool instance.
|
||||
*
|
||||
* @param threadNum The number of threads
|
||||
* @param name The name of the EventLoopThreadPool object.
|
||||
*/
|
||||
EventLoopThreadPool(size_t threadNum,
|
||||
const std::string &name = "EventLoopThreadPool");
|
||||
|
||||
/**
|
||||
* @brief Run all event loops in the pool.
|
||||
* @note This function doesn't block the current thread.
|
||||
*/
|
||||
void start();
|
||||
|
||||
/**
|
||||
* @brief Wait for all event loops in the pool to quit.
|
||||
*
|
||||
* @note This function blocks the current thread.
|
||||
*/
|
||||
void wait();
|
||||
|
||||
/**
|
||||
* @brief Return the number of the event loop.
|
||||
*
|
||||
* @return size_t
|
||||
*/
|
||||
size_t size()
|
||||
{
|
||||
return loopThreadVector_.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the next event loop in the pool.
|
||||
*
|
||||
* @return EventLoop*
|
||||
*/
|
||||
EventLoop *getNextLoop();
|
||||
|
||||
/**
|
||||
* @brief Get the event loop in the `id` position in the pool.
|
||||
*
|
||||
* @param id The id of the first event loop is zero. If the id >= the number
|
||||
* of event loops, nullptr is returned.
|
||||
* @return EventLoop*
|
||||
*/
|
||||
EventLoop *getLoop(size_t id);
|
||||
|
||||
/**
|
||||
* @brief Get all event loops in the pool.
|
||||
*
|
||||
* @return std::vector<EventLoop *>
|
||||
*/
|
||||
std::vector<EventLoop *> getLoops() const;
|
||||
|
||||
private:
|
||||
std::vector<std::shared_ptr<EventLoopThread>> loopThreadVector_;
|
||||
std::atomic<size_t> loopIndex_{0};
|
||||
};
|
||||
} // namespace trantor
|
||||
@@ -0,0 +1,301 @@
|
||||
// Copyright 2010, Shuo Chen. All rights reserved.
|
||||
// http://code.google.com/p/muduo/
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license
|
||||
// that can be found in the License file.
|
||||
|
||||
// Author: Shuo Chen (chenshuo at chenshuo dot com)
|
||||
|
||||
#include <trantor/net/InetAddress.h>
|
||||
|
||||
#include <trantor/utils/Logger.h>
|
||||
#include <cstring>
|
||||
// #include <muduo/net/Endian.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
struct in6_addr_uint
|
||||
{
|
||||
union
|
||||
{
|
||||
u_char Byte[16];
|
||||
u_short Word[8];
|
||||
uint32_t __s6_addr32[4];
|
||||
} uext;
|
||||
};
|
||||
#else
|
||||
#include <strings.h> // memset
|
||||
#include <netinet/tcp.h>
|
||||
#include <netdb.h>
|
||||
#endif
|
||||
|
||||
// INADDR_ANY use (type)value casting.
|
||||
static const in_addr_t kInaddrAny = INADDR_ANY;
|
||||
static const in_addr_t kInaddrLoopback = INADDR_LOOPBACK;
|
||||
|
||||
// /* Structure describing an Internet socket address. */
|
||||
// struct sockaddr_in {
|
||||
// sa_family_t sin_family; /* address family: AF_INET */
|
||||
// uint16_t sin_port; /* port in network byte order */
|
||||
// struct in_addr sin_addr; /* internet address */
|
||||
// };
|
||||
|
||||
// /* Internet address. */
|
||||
// typedef uint32_t in_addr_t;
|
||||
// struct in_addr {
|
||||
// in_addr_t s_addr; /* address in network byte order */
|
||||
// };
|
||||
|
||||
// struct sockaddr_in6 {
|
||||
// sa_family_t sin6_family; /* address family: AF_INET6 */
|
||||
// uint16_t sin6_port; /* port in network byte order */
|
||||
// uint32_t sin6_flowinfo; /* IPv6 flow information */
|
||||
// struct in6_addr sin6_addr; /* IPv6 address */
|
||||
// uint32_t sin6_scope_id; /* IPv6 scope-id */
|
||||
// };
|
||||
|
||||
using namespace trantor;
|
||||
|
||||
/*
|
||||
#ifdef __linux__
|
||||
#if !(__GNUC_PREREQ(4, 6))
|
||||
#pragma GCC diagnostic ignored "-Winvalid-offsetof"
|
||||
#endif
|
||||
#endif
|
||||
*/
|
||||
|
||||
InetAddress::InetAddress(uint16_t port, bool loopbackOnly, bool ipv6)
|
||||
: isIpV6_(ipv6)
|
||||
{
|
||||
if (ipv6)
|
||||
{
|
||||
memset(&addr6_, 0, sizeof(addr6_));
|
||||
addr6_.sin6_family = AF_INET6;
|
||||
in6_addr ip = loopbackOnly ? in6addr_loopback : in6addr_any;
|
||||
addr6_.sin6_addr = ip;
|
||||
addr6_.sin6_port = htons(port);
|
||||
}
|
||||
else
|
||||
{
|
||||
memset(&addr_, 0, sizeof(addr_));
|
||||
addr_.sin_family = AF_INET;
|
||||
in_addr_t ip = loopbackOnly ? kInaddrLoopback : kInaddrAny;
|
||||
addr_.sin_addr.s_addr = htonl(ip);
|
||||
addr_.sin_port = htons(port);
|
||||
}
|
||||
isUnspecified_ = false;
|
||||
}
|
||||
|
||||
InetAddress::InetAddress(const std::string &ip, uint16_t port, bool ipv6)
|
||||
: isIpV6_(ipv6)
|
||||
{
|
||||
if (ipv6)
|
||||
{
|
||||
memset(&addr6_, 0, sizeof(addr6_));
|
||||
addr6_.sin6_family = AF_INET6;
|
||||
addr6_.sin6_port = htons(port);
|
||||
if (::inet_pton(AF_INET6, ip.c_str(), &addr6_.sin6_addr) <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
memset(&addr_, 0, sizeof(addr_));
|
||||
addr_.sin_family = AF_INET;
|
||||
addr_.sin_port = htons(port);
|
||||
if (::inet_pton(AF_INET, ip.c_str(), &addr_.sin_addr) <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
isUnspecified_ = false;
|
||||
}
|
||||
|
||||
std::string InetAddress::toIpPort() const
|
||||
{
|
||||
char buf[64] = "";
|
||||
uint16_t port = ntohs(addr_.sin_port);
|
||||
snprintf(buf, sizeof(buf), ":%u", port);
|
||||
return toIp() + std::string(buf);
|
||||
}
|
||||
std::string InetAddress::toIpPortNetEndian() const
|
||||
{
|
||||
std::string buf;
|
||||
static constexpr auto bytes = sizeof(addr_.sin_port);
|
||||
buf.resize(bytes);
|
||||
#if defined _WIN32
|
||||
std::memcpy((PVOID)&buf[0], (PVOID)&addr_.sin_port, bytes);
|
||||
#else
|
||||
std::memcpy(&buf[0], &addr_.sin_port, bytes);
|
||||
#endif
|
||||
return toIpNetEndian() + buf;
|
||||
}
|
||||
bool InetAddress::isIntranetIp() const
|
||||
{
|
||||
if (addr_.sin_family == AF_INET)
|
||||
{
|
||||
uint32_t ip_addr = ntohl(addr_.sin_addr.s_addr);
|
||||
if ((ip_addr >= 0x0A000000 && ip_addr <= 0x0AFFFFFF) ||
|
||||
(ip_addr >= 0xAC100000 && ip_addr <= 0xAC1FFFFF) ||
|
||||
(ip_addr >= 0xC0A80000 && ip_addr <= 0xC0A8FFFF) ||
|
||||
ip_addr == 0x7f000001)
|
||||
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto addrP = ip6NetEndian();
|
||||
// Loopback ip
|
||||
if (*addrP == 0 && *(addrP + 1) == 0 && *(addrP + 2) == 0 &&
|
||||
ntohl(*(addrP + 3)) == 1)
|
||||
return true;
|
||||
// Privated ip is prefixed by FEC0::/10 or FE80::/10, need testing
|
||||
auto i32 = (ntohl(*addrP) & 0xffc00000);
|
||||
if (i32 == 0xfec00000 || i32 == 0xfe800000)
|
||||
return true;
|
||||
if (*addrP == 0 && *(addrP + 1) == 0 && ntohl(*(addrP + 2)) == 0xffff)
|
||||
{
|
||||
// the IPv6 version of an IPv4 IP address
|
||||
uint32_t ip_addr = ntohl(*(addrP + 3));
|
||||
if ((ip_addr >= 0x0A000000 && ip_addr <= 0x0AFFFFFF) ||
|
||||
(ip_addr >= 0xAC100000 && ip_addr <= 0xAC1FFFFF) ||
|
||||
(ip_addr >= 0xC0A80000 && ip_addr <= 0xC0A8FFFF) ||
|
||||
ip_addr == 0x7f000001)
|
||||
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool InetAddress::isLoopbackIp() const
|
||||
{
|
||||
if (!isIpV6())
|
||||
{
|
||||
uint32_t ip_addr = ntohl(addr_.sin_addr.s_addr);
|
||||
if (ip_addr == 0x7f000001)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto addrP = ip6NetEndian();
|
||||
if (*addrP == 0 && *(addrP + 1) == 0 && *(addrP + 2) == 0 &&
|
||||
ntohl(*(addrP + 3)) == 1)
|
||||
return true;
|
||||
// the IPv6 version of an IPv4 loopback address
|
||||
if (*addrP == 0 && *(addrP + 1) == 0 && ntohl(*(addrP + 2)) == 0xffff &&
|
||||
ntohl(*(addrP + 3)) == 0x7f000001)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void byteToChars(std::string::iterator &dst, unsigned char byte)
|
||||
{
|
||||
*dst = byte / 100 + '0';
|
||||
dst += byte >= 100;
|
||||
*dst = byte % 100 / 10 + '0';
|
||||
dst += byte >= 10;
|
||||
*dst = byte % 10 + '0';
|
||||
++dst;
|
||||
}
|
||||
|
||||
static std::string iptos(unsigned inet_addr)
|
||||
{
|
||||
// Convert to host byte order
|
||||
inet_addr = ntohl(inet_addr);
|
||||
|
||||
// Initialize with a static buffer to force the constructor of string to get
|
||||
// fully inlined
|
||||
constexpr char stringInitBuffer[15]{};
|
||||
std::string out(stringInitBuffer, 15);
|
||||
std::string::iterator dst = out.begin();
|
||||
byteToChars(dst, (inet_addr >> 24) & 0xff);
|
||||
*(dst++) = '.';
|
||||
byteToChars(dst, (inet_addr >> 16) & 0xff);
|
||||
*(dst++) = '.';
|
||||
byteToChars(dst, (inet_addr >> 8) & 0xff);
|
||||
*(dst++) = '.';
|
||||
byteToChars(dst, inet_addr & 0xff);
|
||||
out.erase(dst, out.end());
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string InetAddress::toIp() const
|
||||
{
|
||||
char buf[INET6_ADDRSTRLEN]{};
|
||||
if (addr_.sin_family == AF_INET)
|
||||
{
|
||||
return iptos(addr_.sin_addr.s_addr);
|
||||
}
|
||||
else if (addr_.sin_family == AF_INET6)
|
||||
{
|
||||
#if defined _WIN32
|
||||
::inet_ntop(AF_INET6, (PVOID)&addr6_.sin6_addr, buf, sizeof(buf));
|
||||
#else
|
||||
::inet_ntop(AF_INET6, &addr6_.sin6_addr, buf, sizeof(buf));
|
||||
#endif
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
std::string InetAddress::toIpNetEndian() const
|
||||
{
|
||||
std::string buf;
|
||||
if (addr_.sin_family == AF_INET)
|
||||
{
|
||||
static constexpr auto bytes = sizeof(addr_.sin_addr.s_addr);
|
||||
buf.resize(bytes);
|
||||
#if defined _WIN32
|
||||
std::memcpy((PVOID)&buf[0], (PVOID)&addr_.sin_addr.s_addr, bytes);
|
||||
#else
|
||||
std::memcpy(&buf[0], &addr_.sin_addr.s_addr, bytes);
|
||||
#endif
|
||||
}
|
||||
else if (addr_.sin_family == AF_INET6)
|
||||
{
|
||||
static constexpr auto bytes = sizeof(addr6_.sin6_addr);
|
||||
buf.resize(bytes);
|
||||
#if defined _WIN32
|
||||
std::memcpy((PVOID)&buf[0], (PVOID)ip6NetEndian(), bytes);
|
||||
#else
|
||||
std::memcpy(&buf[0], ip6NetEndian(), bytes);
|
||||
#endif
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
uint32_t InetAddress::ipNetEndian() const
|
||||
{
|
||||
// assert(family() == AF_INET);
|
||||
return addr_.sin_addr.s_addr;
|
||||
}
|
||||
|
||||
const uint32_t *InetAddress::ip6NetEndian() const
|
||||
{
|
||||
// assert(family() == AF_INET6);
|
||||
#if defined __linux__ || defined __HAIKU__
|
||||
return addr6_.sin6_addr.s6_addr32;
|
||||
#elif defined __sun
|
||||
return addr6_.sin6_addr._S6_un._S6_u32;
|
||||
#elif defined _WIN32
|
||||
// TODO is this OK ?
|
||||
const struct in6_addr_uint *addr_temp =
|
||||
reinterpret_cast<const struct in6_addr_uint *>(&addr6_.sin6_addr);
|
||||
return (*addr_temp).uext.__s6_addr32;
|
||||
#else
|
||||
return addr6_.sin6_addr.__u6_addr.__u6_addr32;
|
||||
#endif
|
||||
}
|
||||
uint16_t InetAddress::toPort() const
|
||||
{
|
||||
return ntohs(portNetEndian());
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
// Copyright 2010, Shuo Chen. All rights reserved.
|
||||
// http://code.google.com/p/muduo/
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license
|
||||
// that can be found in the License file.
|
||||
|
||||
// Author: Shuo Chen (chenshuo at chenshuo dot com)
|
||||
//
|
||||
// This is a public header file, it must only include public header files.
|
||||
|
||||
// Taken from Muduo and modified
|
||||
// Copyright 2016, Tao An. All rights reserved.
|
||||
// https://github.com/an-tao/trantor
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license
|
||||
// that can be found in the License file.
|
||||
|
||||
// Author: Tao An
|
||||
|
||||
#ifndef MUDUO_NET_INETADDRESS_H
|
||||
#define MUDUO_NET_INETADDRESS_H
|
||||
|
||||
#include <trantor/utils/Date.h>
|
||||
#include <trantor/exports.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <ws2tcpip.h>
|
||||
using sa_family_t = unsigned short;
|
||||
using in_addr_t = uint32_t;
|
||||
using uint16_t = unsigned short;
|
||||
#else
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <mutex>
|
||||
namespace trantor
|
||||
{
|
||||
/**
|
||||
* @brief Wrapper of sockaddr_in. This is an POD interface class.
|
||||
*
|
||||
*/
|
||||
class TRANTOR_EXPORT InetAddress
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs an endpoint with given port number. Mostly used in
|
||||
* TcpServer listening.
|
||||
*
|
||||
* @param port
|
||||
* @param loopbackOnly
|
||||
* @param ipv6
|
||||
*/
|
||||
InetAddress(uint16_t port = 0,
|
||||
bool loopbackOnly = false,
|
||||
bool ipv6 = false);
|
||||
|
||||
/**
|
||||
* @brief Constructs an endpoint with given ip and port.
|
||||
*
|
||||
* @param ip A IPv4 or IPv6 address.
|
||||
* @param port
|
||||
* @param ipv6
|
||||
*/
|
||||
InetAddress(const std::string &ip, uint16_t port, bool ipv6 = false);
|
||||
|
||||
/**
|
||||
* @brief Constructs an endpoint with given struct `sockaddr_in`. Mostly
|
||||
* used when accepting new connections
|
||||
*
|
||||
* @param addr
|
||||
*/
|
||||
explicit InetAddress(const struct sockaddr_in &addr)
|
||||
: addr_(addr), isUnspecified_(false)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Constructs an IPv6 endpoint with given struct `sockaddr_in6`.
|
||||
* Mostly used when accepting new connections
|
||||
*
|
||||
* @param addr
|
||||
*/
|
||||
explicit InetAddress(const struct sockaddr_in6 &addr)
|
||||
: addr6_(addr), isIpV6_(true), isUnspecified_(false)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the sin_family of the endpoint.
|
||||
*
|
||||
* @return sa_family_t
|
||||
*/
|
||||
sa_family_t family() const
|
||||
{
|
||||
return addr_.sin_family;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the IP string of the endpoint.
|
||||
*
|
||||
* @return std::string
|
||||
*/
|
||||
std::string toIp() const;
|
||||
|
||||
/**
|
||||
* @brief Return the IP and port string of the endpoint.
|
||||
*
|
||||
* @return std::string
|
||||
*/
|
||||
std::string toIpPort() const;
|
||||
|
||||
/**
|
||||
* @brief Return the IP bytes of the endpoint in net endian byte order
|
||||
*
|
||||
* @return std::string
|
||||
*/
|
||||
std::string toIpNetEndian() const;
|
||||
|
||||
/**
|
||||
* @brief Return the IP and port bytes of the endpoint in net endian byte
|
||||
* order
|
||||
*
|
||||
* @return std::string
|
||||
*/
|
||||
std::string toIpPortNetEndian() const;
|
||||
|
||||
/**
|
||||
* @brief Return the port number of the endpoint.
|
||||
*
|
||||
* @return uint16_t
|
||||
*/
|
||||
uint16_t toPort() const;
|
||||
|
||||
/**
|
||||
* @brief Check if the endpoint is IPv4 or IPv6.
|
||||
*
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool isIpV6() const
|
||||
{
|
||||
return isIpV6_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return true if the endpoint is an intranet endpoint.
|
||||
*
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool isIntranetIp() const;
|
||||
|
||||
/**
|
||||
* @brief Return true if the endpoint is a loopback endpoint.
|
||||
*
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool isLoopbackIp() const;
|
||||
|
||||
/**
|
||||
* @brief Get the pointer to the sockaddr struct.
|
||||
*
|
||||
* @return const struct sockaddr*
|
||||
*/
|
||||
const struct sockaddr *getSockAddr() const
|
||||
{
|
||||
return static_cast<const struct sockaddr *>((void *)(&addr6_));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the sockaddr_in6 struct in the endpoint.
|
||||
*
|
||||
* @param addr6
|
||||
*/
|
||||
void setSockAddrInet6(const struct sockaddr_in6 &addr6)
|
||||
{
|
||||
addr6_ = addr6;
|
||||
isIpV6_ = (addr6_.sin6_family == AF_INET6);
|
||||
isUnspecified_ = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the integer value of the IP(v4) in net endian byte order.
|
||||
*
|
||||
* @return uint32_t
|
||||
*/
|
||||
uint32_t ipNetEndian() const;
|
||||
|
||||
/**
|
||||
* @brief Return the pointer to the integer value of the IP(v6) in net
|
||||
* endian byte order.
|
||||
*
|
||||
* @return const uint32_t*
|
||||
*/
|
||||
const uint32_t *ip6NetEndian() const;
|
||||
|
||||
/**
|
||||
* @brief Return the port number in net endian byte order.
|
||||
*
|
||||
* @return uint16_t
|
||||
*/
|
||||
uint16_t portNetEndian() const
|
||||
{
|
||||
return addr_.sin_port;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the port number in net endian byte order.
|
||||
*
|
||||
* @param port
|
||||
*/
|
||||
void setPortNetEndian(uint16_t port)
|
||||
{
|
||||
addr_.sin_port = port;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return true if the address is not initialized.
|
||||
*/
|
||||
inline bool isUnspecified() const
|
||||
{
|
||||
return isUnspecified_;
|
||||
}
|
||||
|
||||
private:
|
||||
union
|
||||
{
|
||||
struct sockaddr_in addr_;
|
||||
struct sockaddr_in6 addr6_;
|
||||
};
|
||||
bool isIpV6_{false};
|
||||
bool isUnspecified_{true};
|
||||
};
|
||||
|
||||
} // namespace trantor
|
||||
|
||||
#endif // MUDUO_NET_INETADDRESS_H
|
||||
@@ -0,0 +1,68 @@
|
||||
// Copyright 2016, Tao An. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license
|
||||
// that can be found in the License file.
|
||||
|
||||
// Author: Tao An
|
||||
|
||||
#pragma once
|
||||
#include <trantor/exports.h>
|
||||
#include <memory>
|
||||
#include <trantor/net/EventLoop.h>
|
||||
#include <trantor/net/InetAddress.h>
|
||||
|
||||
namespace trantor
|
||||
{
|
||||
/**
|
||||
* @brief This class represents an asynchronous DNS resolver.
|
||||
* @note Although the c-ares library is not essential, it is recommended to
|
||||
* install it for higher performance
|
||||
*/
|
||||
class TRANTOR_EXPORT Resolver
|
||||
{
|
||||
public:
|
||||
using Callback = std::function<void(const trantor::InetAddress&)>;
|
||||
using ResolverResultsCallback =
|
||||
std::function<void(const std::vector<trantor::InetAddress>&)>;
|
||||
|
||||
/**
|
||||
* @brief Create a new DNS resolver.
|
||||
*
|
||||
* @param loop The event loop in which the DNS resolver runs.
|
||||
* @param timeout The timeout in seconds for DNS.
|
||||
* @return std::shared_ptr<Resolver>
|
||||
*/
|
||||
static std::shared_ptr<Resolver> newResolver(EventLoop* loop = nullptr,
|
||||
size_t timeout = 60);
|
||||
|
||||
/**
|
||||
* @brief Resolve an address asynchronously.
|
||||
*
|
||||
* @param hostname
|
||||
* @param callback
|
||||
*/
|
||||
virtual void resolve(const std::string& hostname,
|
||||
const Callback& callback) = 0;
|
||||
|
||||
/**
|
||||
* @brief Resolve an address array asynchronously.
|
||||
*
|
||||
* @param hostname
|
||||
* @param callback
|
||||
*/
|
||||
virtual void resolve(const std::string& hostname,
|
||||
const ResolverResultsCallback& callback) = 0;
|
||||
|
||||
virtual ~Resolver()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check whether the c-ares library is used.
|
||||
*
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
static bool isCAresUsed();
|
||||
};
|
||||
} // namespace trantor
|
||||
@@ -0,0 +1,221 @@
|
||||
#pragma once
|
||||
#include <trantor/exports.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace trantor
|
||||
{
|
||||
struct TRANTOR_EXPORT TLSPolicy final
|
||||
{
|
||||
/**
|
||||
* @brief set the ssl configuration commands. The commands will be passed
|
||||
* to the ssl library. The commands are in the form of {{key, value}}.
|
||||
* for example, {"SSL_OP_NO_SSLv2", "1"}. Not all TLS providers support
|
||||
* this feature AND the meaning of the commands may vary between TLS
|
||||
* providers.
|
||||
*
|
||||
* As of 2023-03 Only OpenSSL supports this feature. LibreSSL does not
|
||||
* nor Botan.
|
||||
*/
|
||||
TLSPolicy &setConfCmds(
|
||||
const std::vector<std::pair<std::string, std::string>> &sslConfCmds)
|
||||
{
|
||||
sslConfCmds_ = sslConfCmds;
|
||||
return *this;
|
||||
}
|
||||
/**
|
||||
* @brief set the hostname to be used for SNI and certificate validation.
|
||||
*/
|
||||
TLSPolicy &setHostname(const std::string &hostname)
|
||||
{
|
||||
hostname_ = hostname;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief set the path to the certificate file. The file must be in PEM
|
||||
* format.
|
||||
*/
|
||||
TLSPolicy &setCertPath(const std::string &certPath)
|
||||
{
|
||||
certPath_ = certPath;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief set the path to the private key file. The file must be in PEM
|
||||
* format.
|
||||
*/
|
||||
TLSPolicy &setKeyPath(const std::string &keyPath)
|
||||
{
|
||||
keyPath_ = keyPath;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief set the path to the CA file or directory. The file must be in
|
||||
* PEM format.
|
||||
*/
|
||||
TLSPolicy &setCaPath(const std::string &caPath)
|
||||
{
|
||||
caPath_ = caPath;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief enables the use of the old TLS protocol (old meaning < TLS 1.2).
|
||||
* TLS providers may not support old protocols even if this option is set
|
||||
*/
|
||||
TLSPolicy &setUseOldTLS(bool useOldTLS)
|
||||
{
|
||||
useOldTLS_ = useOldTLS;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief set the list of protocols to be used for ALPN.
|
||||
*
|
||||
* @note for servers, it selects matching protocol against the client's
|
||||
* list. And the first matching protocol supplied in the parameter will be
|
||||
* selected. If no matching protocol is found, the connection will be
|
||||
* closed.
|
||||
*
|
||||
* @note for clients, it sends the list of protocols to the server.
|
||||
*/
|
||||
TLSPolicy &setAlpnProtocols(const std::vector<std::string> &alpnProtocols)
|
||||
{
|
||||
alpnProtocols_ = alpnProtocols;
|
||||
return *this;
|
||||
}
|
||||
TLSPolicy &setAlpnProtocols(std::vector<std::string> &&alpnProtocols)
|
||||
{
|
||||
alpnProtocols_ = std::move(alpnProtocols);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Weather to use the system's certificate store.
|
||||
*
|
||||
* @note setting both not to use the system's certificate store and to
|
||||
* supply a CA path WILL LEAD TO NO CERTIFICATE VALIDATION AT ALL.
|
||||
*/
|
||||
TLSPolicy &setUseSystemCertStore(bool useSystemCertStore)
|
||||
{
|
||||
useSystemCertStore_ = useSystemCertStore;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Enable certificate validation.
|
||||
*/
|
||||
TLSPolicy &setValidate(bool enable)
|
||||
{
|
||||
validate_ = enable;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Allow broken chain (self-signed certificate, root CA not in
|
||||
* allowed list, etc..) but still validate the domain name and date. This
|
||||
* option has no effect if validate is false.
|
||||
*
|
||||
* @note IMPORTANT: This option makes more then self signed certificates
|
||||
* valid. It also allows certificates that are not signed by a trusted CA,
|
||||
* the CA gets revoked. But the underlying implementation may still check
|
||||
* for the type of certificate, date and hostname, etc.. To disable all
|
||||
* certificate validation, use setValidate(false).
|
||||
*/
|
||||
TLSPolicy &setAllowBrokenChain(bool allow)
|
||||
{
|
||||
allowBrokenChain_ = allow;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// The getters
|
||||
const std::vector<std::pair<std::string, std::string>> &getConfCmds() const
|
||||
{
|
||||
return sslConfCmds_;
|
||||
}
|
||||
const std::string &getHostname() const
|
||||
{
|
||||
return hostname_;
|
||||
}
|
||||
const std::string &getCertPath() const
|
||||
{
|
||||
return certPath_;
|
||||
}
|
||||
const std::string &getKeyPath() const
|
||||
{
|
||||
return keyPath_;
|
||||
}
|
||||
const std::string &getCaPath() const
|
||||
{
|
||||
return caPath_;
|
||||
}
|
||||
bool getUseOldTLS() const
|
||||
{
|
||||
return useOldTLS_;
|
||||
}
|
||||
bool getValidate() const
|
||||
{
|
||||
return validate_;
|
||||
}
|
||||
bool getAllowBrokenChain() const
|
||||
{
|
||||
return allowBrokenChain_;
|
||||
}
|
||||
const std::vector<std::string> &getAlpnProtocols() const
|
||||
{
|
||||
return alpnProtocols_;
|
||||
}
|
||||
const std::vector<std::string> &getAlpnProtocols()
|
||||
{
|
||||
return alpnProtocols_;
|
||||
}
|
||||
|
||||
bool getUseSystemCertStore() const
|
||||
{
|
||||
return useSystemCertStore_;
|
||||
}
|
||||
|
||||
static std::shared_ptr<TLSPolicy> defaultServerPolicy(
|
||||
const std::string &certPath,
|
||||
const std::string &keyPath)
|
||||
{
|
||||
auto policy = std::make_shared<TLSPolicy>();
|
||||
policy->setValidate(false)
|
||||
.setUseOldTLS(false)
|
||||
.setUseSystemCertStore(false)
|
||||
.setCertPath(certPath)
|
||||
.setKeyPath(keyPath);
|
||||
return policy;
|
||||
}
|
||||
|
||||
static std::shared_ptr<TLSPolicy> defaultClientPolicy(
|
||||
const std::string &hostname = "")
|
||||
{
|
||||
auto policy = std::make_shared<TLSPolicy>();
|
||||
policy->setValidate(true)
|
||||
.setUseOldTLS(false)
|
||||
.setUseSystemCertStore(true)
|
||||
.setHostname(hostname);
|
||||
return policy;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::vector<std::pair<std::string, std::string>> sslConfCmds_ = {};
|
||||
std::string hostname_ = "";
|
||||
std::string certPath_ = "";
|
||||
std::string keyPath_ = "";
|
||||
std::string caPath_ = "";
|
||||
std::vector<std::string> alpnProtocols_ = {};
|
||||
bool useOldTLS_ = false; // turn into specific version
|
||||
bool validate_ = true;
|
||||
bool allowBrokenChain_ = false;
|
||||
bool useSystemCertStore_ = true;
|
||||
};
|
||||
using TLSPolicyPtr = std::shared_ptr<TLSPolicy>;
|
||||
} // namespace trantor
|
||||
@@ -0,0 +1,249 @@
|
||||
// Copyright 2010, Shuo Chen. All rights reserved.
|
||||
// http://code.google.com/p/muduo/
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license
|
||||
// that can be found in the License file.
|
||||
|
||||
// Author: Shuo Chen (chenshuo at chenshuo dot com)
|
||||
//
|
||||
|
||||
// Taken from muduo and modified by an tao
|
||||
|
||||
#include <trantor/net/TcpClient.h>
|
||||
#include <trantor/net/inner/TLSProvider.h>
|
||||
|
||||
#include <trantor/utils/Logger.h>
|
||||
#include "Connector.h"
|
||||
#include "inner/TcpConnectionImpl.h"
|
||||
#include <trantor/net/EventLoop.h>
|
||||
|
||||
#include <functional>
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
|
||||
#include "Socket.h"
|
||||
|
||||
#include <stdio.h> // snprintf
|
||||
|
||||
using namespace trantor;
|
||||
using namespace std::placeholders;
|
||||
|
||||
namespace trantor
|
||||
{
|
||||
// void removeConnector(const ConnectorPtr &)
|
||||
// {
|
||||
// // connector->
|
||||
// }
|
||||
#ifndef _WIN32
|
||||
TcpClient::IgnoreSigPipe TcpClient::initObj;
|
||||
#endif
|
||||
|
||||
static void defaultConnectionCallback(const TcpConnectionPtr &conn)
|
||||
{
|
||||
LOG_TRACE << conn->localAddr().toIpPort() << " -> "
|
||||
<< conn->peerAddr().toIpPort() << " is "
|
||||
<< (conn->connected() ? "UP" : "DOWN");
|
||||
// do not call conn->forceClose(), because some users want to register
|
||||
// message callback only.
|
||||
}
|
||||
|
||||
static void defaultMessageCallback(const TcpConnectionPtr &, MsgBuffer *buf)
|
||||
{
|
||||
buf->retrieveAll();
|
||||
}
|
||||
|
||||
} // namespace trantor
|
||||
|
||||
TcpClient::TcpClient(EventLoop *loop,
|
||||
const InetAddress &serverAddr,
|
||||
const std::string &nameArg)
|
||||
: loop_(loop),
|
||||
connector_(new Connector(loop, serverAddr, false)),
|
||||
name_(nameArg),
|
||||
connectionCallback_(defaultConnectionCallback),
|
||||
messageCallback_(defaultMessageCallback),
|
||||
retry_(false),
|
||||
connect_(true)
|
||||
{
|
||||
(void)validateCert_;
|
||||
LOG_TRACE << "TcpClient::TcpClient[" << name_ << "] - connector ";
|
||||
}
|
||||
|
||||
TcpClient::~TcpClient()
|
||||
{
|
||||
LOG_TRACE << "TcpClient::~TcpClient[" << name_ << "] - connector ";
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (connection_ == nullptr)
|
||||
{
|
||||
connector_->stop();
|
||||
return;
|
||||
}
|
||||
assert(loop_ == connection_->getLoop());
|
||||
auto conn =
|
||||
std::atomic_load_explicit(&connection_, std::memory_order_relaxed);
|
||||
loop_->runInLoop([conn = std::move(conn)]() {
|
||||
conn->setCloseCallback([](const TcpConnectionPtr &connPtr) mutable {
|
||||
connPtr->getLoop()->queueInLoop(
|
||||
[connPtr] { connPtr->connectDestroyed(); });
|
||||
});
|
||||
});
|
||||
connection_->forceClose();
|
||||
}
|
||||
|
||||
void TcpClient::connect()
|
||||
{
|
||||
// TODO: check state
|
||||
LOG_TRACE << "TcpClient::connect[" << name_ << "] - connecting to "
|
||||
<< connector_->serverAddress().toIpPort();
|
||||
|
||||
auto weakPtr = std::weak_ptr<TcpClient>(shared_from_this());
|
||||
connector_->setNewConnectionCallback([weakPtr](int sockfd) {
|
||||
auto ptr = weakPtr.lock();
|
||||
if (ptr)
|
||||
{
|
||||
ptr->newConnection(sockfd);
|
||||
}
|
||||
});
|
||||
// WORKAROUND: somehow we got use-after-free error
|
||||
connector_->setErrorCallback([weakPtr]() {
|
||||
auto ptr = weakPtr.lock();
|
||||
if (ptr && ptr->connectionErrorCallback_)
|
||||
{
|
||||
ptr->connectionErrorCallback_();
|
||||
}
|
||||
});
|
||||
connect_ = true;
|
||||
connector_->start();
|
||||
}
|
||||
|
||||
void TcpClient::disconnect()
|
||||
{
|
||||
connect_ = false;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (connection_)
|
||||
{
|
||||
connection_->shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TcpClient::stop()
|
||||
{
|
||||
connect_ = false;
|
||||
connector_->stop();
|
||||
}
|
||||
|
||||
void TcpClient::setSockOptCallback(SockOptCallback &&cb)
|
||||
{
|
||||
connector_->setSockOptCallback(std::move(cb));
|
||||
}
|
||||
|
||||
void TcpClient::setSockOptCallback(const SockOptCallback &cb)
|
||||
{
|
||||
connector_->setSockOptCallback(cb);
|
||||
}
|
||||
|
||||
void TcpClient::newConnection(int sockfd)
|
||||
{
|
||||
loop_->assertInLoopThread();
|
||||
InetAddress peerAddr(Socket::getPeerAddr(sockfd));
|
||||
InetAddress localAddr(Socket::getLocalAddr(sockfd));
|
||||
// TODO poll with zero timeout to double confirm the new connection
|
||||
// TODO use make_shared if necessary
|
||||
TcpConnectionPtr conn;
|
||||
LOG_TRACE << "SSL enabled: " << (tlsPolicyPtr_ ? "true" : "false");
|
||||
if (tlsPolicyPtr_)
|
||||
{
|
||||
assert(sslContextPtr_);
|
||||
conn = std::make_shared<TcpConnectionImpl>(
|
||||
loop_, sockfd, localAddr, peerAddr, tlsPolicyPtr_, sslContextPtr_);
|
||||
}
|
||||
else
|
||||
{
|
||||
conn = std::make_shared<TcpConnectionImpl>(loop_,
|
||||
sockfd,
|
||||
localAddr,
|
||||
peerAddr);
|
||||
}
|
||||
conn->setConnectionCallback(connectionCallback_);
|
||||
conn->setRecvMsgCallback(messageCallback_);
|
||||
conn->setWriteCompleteCallback(writeCompleteCallback_);
|
||||
|
||||
std::weak_ptr<TcpClient> weakSelf(shared_from_this());
|
||||
auto closeCb = std::function<void(const TcpConnectionPtr &)>(
|
||||
[weakSelf](const TcpConnectionPtr &c) {
|
||||
if (auto self = weakSelf.lock())
|
||||
{
|
||||
self->removeConnection(c);
|
||||
}
|
||||
// Else the TcpClient instance has already been destroyed
|
||||
else
|
||||
{
|
||||
LOG_TRACE << "TcpClient::removeConnection was skipped because "
|
||||
"TcpClient instanced already freed";
|
||||
c->getLoop()->queueInLoop([c] { c->connectDestroyed(); });
|
||||
}
|
||||
});
|
||||
conn->setCloseCallback(std::move(closeCb));
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
connection_ = conn;
|
||||
}
|
||||
conn->setSSLErrorCallback([weakSelf = std::move(weakSelf)](SSLError err) {
|
||||
auto self = weakSelf.lock();
|
||||
if (self && self->sslErrorCallback_)
|
||||
self->sslErrorCallback_(err);
|
||||
});
|
||||
conn->connectEstablished();
|
||||
}
|
||||
|
||||
void TcpClient::removeConnection(const TcpConnectionPtr &conn)
|
||||
{
|
||||
loop_->assertInLoopThread();
|
||||
assert(loop_ == conn->getLoop());
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
assert(connection_ == conn);
|
||||
connection_.reset();
|
||||
}
|
||||
|
||||
loop_->queueInLoop([conn]() { conn->connectDestroyed(); });
|
||||
if (retry_ && connect_)
|
||||
{
|
||||
LOG_TRACE << "TcpClient::connect[" << name_ << "] - Reconnecting to "
|
||||
<< connector_->serverAddress().toIpPort();
|
||||
connector_->restart();
|
||||
}
|
||||
}
|
||||
|
||||
void TcpClient::enableSSL(
|
||||
bool useOldTLS,
|
||||
bool validateCert,
|
||||
std::string hostname,
|
||||
const std::vector<std::pair<std::string, std::string>> &sslConfCmds,
|
||||
const std::string &certPath,
|
||||
const std::string &keyPath,
|
||||
const std::string &caPath)
|
||||
{
|
||||
if (!hostname.empty())
|
||||
{
|
||||
std::transform(hostname.begin(),
|
||||
hostname.end(),
|
||||
hostname.begin(),
|
||||
[](unsigned char c) { return tolower(c); });
|
||||
}
|
||||
|
||||
tlsPolicyPtr_ = TLSPolicy::defaultClientPolicy();
|
||||
tlsPolicyPtr_->setValidate(validateCert)
|
||||
.setUseOldTLS(useOldTLS)
|
||||
.setConfCmds(sslConfCmds)
|
||||
.setCertPath(certPath)
|
||||
.setKeyPath(keyPath)
|
||||
.setHostname(hostname)
|
||||
.setCaPath(caPath);
|
||||
sslContextPtr_ = newSSLContext(*tlsPolicyPtr_, false);
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
// taken from muduo
|
||||
// Copyright 2010, Shuo Chen. All rights reserved.
|
||||
// http://code.google.com/p/muduo/
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license
|
||||
// that can be found in the License file.
|
||||
|
||||
// Author: Shuo Chen (chenshuo at chenshuo dot com)
|
||||
//
|
||||
// This is a public header file, it must only include public header files.
|
||||
|
||||
// Copyright 2016, Tao An. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license
|
||||
// that can be found in the License file.
|
||||
|
||||
// Author: Tao An
|
||||
|
||||
#pragma once
|
||||
#include <trantor/net/EventLoop.h>
|
||||
#include <trantor/net/InetAddress.h>
|
||||
#include <trantor/net/TcpConnection.h>
|
||||
#include <trantor/utils/Logger.h>
|
||||
#include <trantor/exports.h>
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <signal.h>
|
||||
namespace trantor
|
||||
{
|
||||
class Connector;
|
||||
using ConnectorPtr = std::shared_ptr<Connector>;
|
||||
/**
|
||||
* @brief This class represents a TCP client.
|
||||
*
|
||||
*/
|
||||
class TRANTOR_EXPORT TcpClient : NonCopyable,
|
||||
public std::enable_shared_from_this<TcpClient>
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new TCP client instance.
|
||||
*
|
||||
* @param loop The event loop in which the client runs.
|
||||
* @param serverAddr The address of the server.
|
||||
* @param nameArg The name of the client.
|
||||
*/
|
||||
TcpClient(EventLoop *loop,
|
||||
const InetAddress &serverAddr,
|
||||
const std::string &nameArg);
|
||||
~TcpClient();
|
||||
|
||||
/**
|
||||
* @brief Connect to the server.
|
||||
*
|
||||
*/
|
||||
void connect();
|
||||
|
||||
/**
|
||||
* @brief Disconnect from the server.
|
||||
*
|
||||
*/
|
||||
void disconnect();
|
||||
|
||||
/**
|
||||
* @brief Stop connecting to the server.
|
||||
*
|
||||
*/
|
||||
void stop();
|
||||
|
||||
/**
|
||||
* @brief Get the TCP connection to the server.
|
||||
*
|
||||
* @return TcpConnectionPtr
|
||||
*/
|
||||
TcpConnectionPtr connection() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return connection_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the event loop.
|
||||
*
|
||||
* @return EventLoop*
|
||||
*/
|
||||
EventLoop *getLoop() const
|
||||
{
|
||||
return loop_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check whether the client re-connect to the server.
|
||||
*
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool retry() const
|
||||
{
|
||||
return retry_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Enable retrying.
|
||||
*
|
||||
*/
|
||||
void enableRetry()
|
||||
{
|
||||
retry_ = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the name of the client.
|
||||
*
|
||||
* @return const std::string&
|
||||
*/
|
||||
const std::string &name() const
|
||||
{
|
||||
return name_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the connection callback.
|
||||
*
|
||||
* @param cb The callback is called when the connection to the server is
|
||||
* established or closed.
|
||||
*/
|
||||
void setConnectionCallback(const ConnectionCallback &cb)
|
||||
{
|
||||
connectionCallback_ = cb;
|
||||
}
|
||||
void setConnectionCallback(ConnectionCallback &&cb)
|
||||
{
|
||||
connectionCallback_ = std::move(cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the connection error callback.
|
||||
*
|
||||
* @param cb The callback is called when an error occurs during connecting
|
||||
* to the server.
|
||||
*/
|
||||
void setConnectionErrorCallback(const ConnectionErrorCallback &cb)
|
||||
{
|
||||
connectionErrorCallback_ = cb;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the message callback.
|
||||
*
|
||||
* @param cb The callback is called when some data is received from the
|
||||
* server.
|
||||
*/
|
||||
void setMessageCallback(const RecvMessageCallback &cb)
|
||||
{
|
||||
messageCallback_ = cb;
|
||||
}
|
||||
void setMessageCallback(RecvMessageCallback &&cb)
|
||||
{
|
||||
messageCallback_ = std::move(cb);
|
||||
}
|
||||
/// Set write complete callback.
|
||||
/// Not thread safe.
|
||||
|
||||
/**
|
||||
* @brief Set the write complete callback.
|
||||
*
|
||||
* @param cb The callback is called when data to send is written to the
|
||||
* socket.
|
||||
*/
|
||||
void setWriteCompleteCallback(const WriteCompleteCallback &cb)
|
||||
{
|
||||
writeCompleteCallback_ = cb;
|
||||
}
|
||||
void setWriteCompleteCallback(WriteCompleteCallback &&cb)
|
||||
{
|
||||
writeCompleteCallback_ = std::move(cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the callback for errors of SSL
|
||||
* @param cb The callback is called when an SSL error occurs.
|
||||
*/
|
||||
void setSSLErrorCallback(const SSLErrorCallback &cb)
|
||||
{
|
||||
sslErrorCallback_ = cb;
|
||||
}
|
||||
void setSSLErrorCallback(SSLErrorCallback &&cb)
|
||||
{
|
||||
sslErrorCallback_ = std::move(cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the callback for set socket option
|
||||
* @param cb The callback is called, before connect
|
||||
*/
|
||||
void setSockOptCallback(const SockOptCallback &cb);
|
||||
void setSockOptCallback(SockOptCallback &&cb);
|
||||
|
||||
/**
|
||||
* @brief Enable SSL encryption.
|
||||
* @param useOldTLS If true, the TLS 1.0 and 1.1 are supported by the
|
||||
* client.
|
||||
* @param validateCert If true, we try to validate if the peer's SSL cert
|
||||
* is valid.
|
||||
* @param hostname The server hostname for SNI. If it is empty, the SNI is
|
||||
* not used.
|
||||
* @param sslConfCmds The commands used to call the SSL_CONF_cmd function in
|
||||
* OpenSSL.
|
||||
* @param certPath The path of the certificate file.
|
||||
* @param keyPath The path of the private key file.
|
||||
* @param caPath The path of the certificate authority file.
|
||||
* @note It's well known that TLS 1.0 and 1.1 are not considered secure in
|
||||
* 2020. And it's a good practice to only use TLS 1.2 and above.
|
||||
*/
|
||||
[[deprecated("Use enableSSL(TLSPolicyPtr policy) instead")]] void enableSSL(
|
||||
bool useOldTLS = false,
|
||||
bool validateCert = true,
|
||||
std::string hostname = "",
|
||||
const std::vector<std::pair<std::string, std::string>> &sslConfCmds =
|
||||
{},
|
||||
const std::string &certPath = "",
|
||||
const std::string &keyPath = "",
|
||||
const std::string &caPath = "");
|
||||
/**
|
||||
* @brief Enable SSL encryption.
|
||||
*/
|
||||
void enableSSL(TLSPolicyPtr policy)
|
||||
{
|
||||
tlsPolicyPtr_ = std::move(policy);
|
||||
sslContextPtr_ = newSSLContext(*tlsPolicyPtr_, false);
|
||||
}
|
||||
|
||||
private:
|
||||
/// Not thread safe, but in loop
|
||||
void newConnection(int sockfd);
|
||||
/// Not thread safe, but in loop
|
||||
void removeConnection(const TcpConnectionPtr &conn);
|
||||
|
||||
EventLoop *loop_;
|
||||
ConnectorPtr connector_; // avoid revealing Connector
|
||||
const std::string name_;
|
||||
ConnectionCallback connectionCallback_;
|
||||
ConnectionErrorCallback connectionErrorCallback_;
|
||||
RecvMessageCallback messageCallback_;
|
||||
WriteCompleteCallback writeCompleteCallback_;
|
||||
SSLErrorCallback sslErrorCallback_;
|
||||
std::atomic_bool retry_; // atomic
|
||||
std::atomic_bool connect_; // atomic
|
||||
// always in loop thread
|
||||
mutable std::mutex mutex_;
|
||||
TcpConnectionPtr connection_; // @GuardedBy mutex_
|
||||
TLSPolicyPtr tlsPolicyPtr_;
|
||||
SSLContextPtr sslContextPtr_;
|
||||
bool validateCert_{false};
|
||||
|
||||
#ifndef _WIN32
|
||||
class IgnoreSigPipe
|
||||
{
|
||||
public:
|
||||
IgnoreSigPipe()
|
||||
{
|
||||
::signal(SIGPIPE, SIG_IGN);
|
||||
}
|
||||
};
|
||||
|
||||
static IgnoreSigPipe initObj;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace trantor
|
||||
@@ -0,0 +1,386 @@
|
||||
/**
|
||||
*
|
||||
* @file TcpConnection.h
|
||||
* @author An Tao
|
||||
*
|
||||
* Public header file in trantor lib.
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <trantor/exports.h>
|
||||
#include <trantor/net/EventLoop.h>
|
||||
#include <trantor/net/InetAddress.h>
|
||||
#include <trantor/utils/NonCopyable.h>
|
||||
#include <trantor/utils/MsgBuffer.h>
|
||||
#include <trantor/net/callbacks.h>
|
||||
#include <trantor/net/Certificate.h>
|
||||
#include <trantor/net/TLSPolicy.h>
|
||||
#include <trantor/net/AsyncStream.h>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
namespace trantor
|
||||
{
|
||||
class TimingWheel;
|
||||
|
||||
struct SSLContext;
|
||||
using SSLContextPtr = std::shared_ptr<SSLContext>;
|
||||
|
||||
/**
|
||||
* @brief This class represents a TCP connection.
|
||||
*
|
||||
*/
|
||||
class TRANTOR_EXPORT TcpConnection
|
||||
{
|
||||
public:
|
||||
friend class TcpServer;
|
||||
friend class TcpConnectionImpl;
|
||||
friend class TcpClient;
|
||||
|
||||
TcpConnection() = default;
|
||||
virtual ~TcpConnection(){};
|
||||
|
||||
/**
|
||||
* @brief Send some data to the peer.
|
||||
*
|
||||
* @param msg
|
||||
* @param len
|
||||
*/
|
||||
virtual void send(const char *msg, size_t len) = 0;
|
||||
virtual void send(const void *msg, size_t len) = 0;
|
||||
virtual void send(const std::string &msg) = 0;
|
||||
virtual void send(std::string &&msg) = 0;
|
||||
virtual void send(const MsgBuffer &buffer) = 0;
|
||||
virtual void send(MsgBuffer &&buffer) = 0;
|
||||
virtual void send(const std::shared_ptr<std::string> &msgPtr) = 0;
|
||||
virtual void send(const std::shared_ptr<MsgBuffer> &msgPtr) = 0;
|
||||
|
||||
/**
|
||||
* @brief Send a file to the peer.
|
||||
*
|
||||
* @param fileName in UTF-8
|
||||
* @param offset
|
||||
* @param length
|
||||
*/
|
||||
virtual void sendFile(const char *fileName,
|
||||
long long offset = 0,
|
||||
long long length = 0) = 0;
|
||||
/**
|
||||
* @brief Send a file to the peer.
|
||||
*
|
||||
* @param fileName in wide string (eg. windows native UCS-2)
|
||||
* @param offset
|
||||
* @param length
|
||||
*/
|
||||
virtual void sendFile(const wchar_t *fileName,
|
||||
long long offset = 0,
|
||||
long long length = 0) = 0;
|
||||
/**
|
||||
* @brief Send a stream to the peer.
|
||||
*
|
||||
* @param callback function to retrieve the stream data (stream ends when a
|
||||
* zero size is returned) the callback will be called with nullptr when the
|
||||
* send is finished/interrupted, so that it cleans up any internal data (ex:
|
||||
* close file).
|
||||
* @warning The buffer size should be >= 10 to allow http chunked-encoding
|
||||
* data stream
|
||||
*/
|
||||
virtual void sendStream(std::function<std::size_t(char *, std::size_t)>
|
||||
callback) = 0; // (buffer, buffer size) -> size
|
||||
// of data put in buffer
|
||||
|
||||
/**
|
||||
* @brief Send a stream to the peer asynchronously.
|
||||
* @param disableKickoff Disable the kickoff mechanism. If this parameter is
|
||||
* enabled, the connection will not be closed after the inactive timeout.
|
||||
* @note The subsequent data sent after the async stream will be sent after
|
||||
* the stream is closed.
|
||||
*/
|
||||
virtual AsyncStreamPtr sendAsyncStream(bool disableKickoff = false) = 0;
|
||||
/**
|
||||
* @brief Get the local address of the connection.
|
||||
*
|
||||
* @return const InetAddress&
|
||||
*/
|
||||
|
||||
virtual const InetAddress &localAddr() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Get the remote address of the connection.
|
||||
*
|
||||
* @return const InetAddress&
|
||||
*/
|
||||
virtual const InetAddress &peerAddr() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Return true if the connection is established.
|
||||
*
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
virtual bool connected() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Return false if the connection is established.
|
||||
*
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
virtual bool disconnected() const = 0;
|
||||
|
||||
/* *
|
||||
* @brief Get the buffer in which the received data stored.
|
||||
*
|
||||
* @return MsgBuffer*
|
||||
*/
|
||||
// virtual MsgBuffer *getRecvBuffer() = 0;
|
||||
|
||||
/**
|
||||
* @brief Set the high water mark callback
|
||||
*
|
||||
* @param cb The callback is called when the data in sending buffer is
|
||||
* larger than the water mark.
|
||||
* @param markLen The water mark in bytes.
|
||||
*/
|
||||
virtual void setHighWaterMarkCallback(const HighWaterMarkCallback &cb,
|
||||
size_t markLen) = 0;
|
||||
|
||||
/**
|
||||
* @brief Set the TCP_NODELAY option to the socket.
|
||||
*
|
||||
* @param on
|
||||
*/
|
||||
virtual void setTcpNoDelay(bool on) = 0;
|
||||
|
||||
/**
|
||||
* @brief Shutdown the connection.
|
||||
* @note This method only closes the writing direction.
|
||||
*/
|
||||
virtual void shutdown() = 0;
|
||||
|
||||
/**
|
||||
* @brief Close the connection forcefully.
|
||||
*
|
||||
*/
|
||||
virtual void forceClose() = 0;
|
||||
|
||||
/**
|
||||
* @brief Get the event loop in which the connection I/O is handled.
|
||||
*
|
||||
* @return EventLoop*
|
||||
*/
|
||||
virtual EventLoop *getLoop() = 0;
|
||||
|
||||
/**
|
||||
* @brief Set the custom data on the connection.
|
||||
*
|
||||
* @param context
|
||||
*/
|
||||
void setContext(const std::shared_ptr<void> &context)
|
||||
{
|
||||
contextPtr_ = context;
|
||||
}
|
||||
void setContext(std::shared_ptr<void> &&context)
|
||||
{
|
||||
contextPtr_ = std::move(context);
|
||||
}
|
||||
virtual std::string applicationProtocol() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Get the custom data from the connection.
|
||||
*
|
||||
* @tparam T
|
||||
* @return std::shared_ptr<T>
|
||||
*/
|
||||
template <typename T>
|
||||
std::shared_ptr<T> getContext() const
|
||||
{
|
||||
return std::static_pointer_cast<T>(contextPtr_);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return true if the custom data is set by user.
|
||||
*
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool hasContext() const
|
||||
{
|
||||
return (bool)contextPtr_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clear the custom data.
|
||||
*
|
||||
*/
|
||||
void clearContext()
|
||||
{
|
||||
contextPtr_.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Call this method to avoid being kicked off by TcpServer, refer to
|
||||
* the kickoffIdleConnections method in the TcpServer class.
|
||||
*
|
||||
*/
|
||||
virtual void keepAlive() = 0;
|
||||
|
||||
/**
|
||||
* @brief Return true if the keepAlive() method is called.
|
||||
*
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
virtual bool isKeepAlive() = 0;
|
||||
|
||||
/**
|
||||
* @brief Return the number of bytes sent
|
||||
*
|
||||
* @return size_t
|
||||
*/
|
||||
virtual size_t bytesSent() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Return the number of bytes received.
|
||||
*
|
||||
* @return size_t
|
||||
*/
|
||||
virtual size_t bytesReceived() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Check whether the connection is SSL encrypted.
|
||||
*
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
virtual bool isSSLConnection() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Get buffer of unprompted data.
|
||||
*/
|
||||
virtual MsgBuffer *getRecvBuffer() = 0;
|
||||
|
||||
/**
|
||||
* @brief Get peer certificate (if any).
|
||||
*
|
||||
* @return pointer to Certificate object or nullptr if no certificate was
|
||||
* provided
|
||||
*/
|
||||
virtual CertificatePtr peerCertificate() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Get the SNI name (for server connections only)
|
||||
*
|
||||
* @return Empty string if no SNI name was provided (not an SSL connection
|
||||
* or peer did not provide SNI)
|
||||
*/
|
||||
virtual std::string sniName() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Start TLS. If the connection is specified as a server, the
|
||||
* connection will be upgraded to a TLS server connection. If the connection
|
||||
* is specified as a client, the connection will be upgraded to a TLS client
|
||||
* @note This method is only available for non-SSL connections.
|
||||
*/
|
||||
virtual void startEncryption(TLSPolicyPtr policy,
|
||||
bool isServer,
|
||||
std::function<void(const TcpConnectionPtr &)>
|
||||
upgradeCallback = nullptr) = 0;
|
||||
/**
|
||||
* @brief Start TLS as a client.
|
||||
* @note This method is only available for non-SSL connections.
|
||||
*/
|
||||
[[deprecated("Use startEncryption(TLSPolicyPtr) instead")]] void
|
||||
startClientEncryption(
|
||||
std::function<void(const TcpConnectionPtr &)> &&callback,
|
||||
bool useOldTLS = false,
|
||||
bool validateCert = true,
|
||||
const std::string &hostname = "",
|
||||
const std::vector<std::pair<std::string, std::string>> &sslConfCmds =
|
||||
{})
|
||||
{
|
||||
auto policy = TLSPolicy::defaultClientPolicy();
|
||||
policy->setUseOldTLS(useOldTLS)
|
||||
.setValidate(validateCert)
|
||||
.setHostname(hostname)
|
||||
.setConfCmds(sslConfCmds);
|
||||
startEncryption(std::move(policy), false, std::move(callback));
|
||||
}
|
||||
|
||||
void setValidationPolicy(TLSPolicy &&policy)
|
||||
{
|
||||
tlsPolicy_ = std::move(policy);
|
||||
}
|
||||
|
||||
void setRecvMsgCallback(const RecvMessageCallback &cb)
|
||||
{
|
||||
recvMsgCallback_ = cb;
|
||||
}
|
||||
void setRecvMsgCallback(RecvMessageCallback &&cb)
|
||||
{
|
||||
recvMsgCallback_ = std::move(cb);
|
||||
}
|
||||
void setConnectionCallback(const ConnectionCallback &cb)
|
||||
{
|
||||
connectionCallback_ = cb;
|
||||
}
|
||||
void setConnectionCallback(ConnectionCallback &&cb)
|
||||
{
|
||||
connectionCallback_ = std::move(cb);
|
||||
}
|
||||
void setWriteCompleteCallback(const WriteCompleteCallback &cb)
|
||||
{
|
||||
writeCompleteCallback_ = cb;
|
||||
}
|
||||
void setWriteCompleteCallback(WriteCompleteCallback &&cb)
|
||||
{
|
||||
writeCompleteCallback_ = std::move(cb);
|
||||
}
|
||||
void setCloseCallback(const CloseCallback &cb)
|
||||
{
|
||||
closeCallback_ = cb;
|
||||
}
|
||||
void setCloseCallback(CloseCallback &&cb)
|
||||
{
|
||||
closeCallback_ = std::move(cb);
|
||||
}
|
||||
void setSSLErrorCallback(const SSLErrorCallback &cb)
|
||||
{
|
||||
sslErrorCallback_ = cb;
|
||||
}
|
||||
void setSSLErrorCallback(SSLErrorCallback &&cb)
|
||||
{
|
||||
sslErrorCallback_ = std::move(cb);
|
||||
}
|
||||
|
||||
// TODO: These should be internal APIs
|
||||
virtual void connectEstablished() = 0;
|
||||
virtual void connectDestroyed() = 0;
|
||||
virtual void enableKickingOff(
|
||||
size_t timeout,
|
||||
const std::shared_ptr<TimingWheel> &timingWheel) = 0;
|
||||
|
||||
protected:
|
||||
// callbacks
|
||||
RecvMessageCallback recvMsgCallback_;
|
||||
ConnectionCallback connectionCallback_;
|
||||
CloseCallback closeCallback_;
|
||||
WriteCompleteCallback writeCompleteCallback_;
|
||||
HighWaterMarkCallback highWaterMarkCallback_;
|
||||
SSLErrorCallback sslErrorCallback_;
|
||||
TLSPolicy tlsPolicy_;
|
||||
|
||||
private:
|
||||
std::shared_ptr<void> contextPtr_;
|
||||
};
|
||||
TRANTOR_EXPORT SSLContextPtr newSSLContext(const TLSPolicy &policy,
|
||||
bool server);
|
||||
|
||||
} // namespace trantor
|
||||
@@ -0,0 +1,256 @@
|
||||
/**
|
||||
*
|
||||
* @file TcpServer.cc
|
||||
* @author An Tao
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* https://github.com/an-tao/trantor
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Trantor
|
||||
*
|
||||
*/
|
||||
|
||||
#include <trantor/net/TcpServer.h>
|
||||
#include <trantor/utils/Logger.h>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include "Acceptor.h"
|
||||
#include "inner/TcpConnectionImpl.h"
|
||||
using namespace trantor;
|
||||
using namespace std::placeholders;
|
||||
|
||||
TcpServer::TcpServer(EventLoop *loop,
|
||||
const InetAddress &address,
|
||||
std::string name,
|
||||
bool reUseAddr,
|
||||
bool reUsePort)
|
||||
: loop_(loop),
|
||||
acceptorPtr_(new Acceptor(loop, address, reUseAddr, reUsePort)),
|
||||
serverName_(std::move(name)),
|
||||
recvMessageCallback_([](const TcpConnectionPtr &, MsgBuffer *buffer) {
|
||||
LOG_ERROR << "unhandled recv message [" << buffer->readableBytes()
|
||||
<< " bytes]";
|
||||
buffer->retrieveAll();
|
||||
}),
|
||||
ioLoops_({loop}),
|
||||
numIoLoops_(1)
|
||||
{
|
||||
acceptorPtr_->setNewConnectionCallback(
|
||||
[this](int fd, const InetAddress &peer) { newConnection(fd, peer); });
|
||||
}
|
||||
|
||||
TcpServer::~TcpServer()
|
||||
{
|
||||
// loop_->assertInLoopThread();
|
||||
LOG_TRACE << "TcpServer::~TcpServer [" << serverName_ << "] destructing";
|
||||
}
|
||||
|
||||
void TcpServer::setBeforeListenSockOptCallback(SockOptCallback cb)
|
||||
{
|
||||
acceptorPtr_->setBeforeListenSockOptCallback(std::move(cb));
|
||||
}
|
||||
|
||||
void TcpServer::setAfterAcceptSockOptCallback(SockOptCallback cb)
|
||||
{
|
||||
acceptorPtr_->setAfterAcceptSockOptCallback(std::move(cb));
|
||||
}
|
||||
|
||||
void TcpServer::newConnection(int sockfd, const InetAddress &peer)
|
||||
{
|
||||
LOG_TRACE << "new connection:fd=" << sockfd
|
||||
<< " address=" << peer.toIpPort();
|
||||
loop_->assertInLoopThread();
|
||||
EventLoop *ioLoop = ioLoops_[nextLoopIdx_];
|
||||
if (++nextLoopIdx_ >= numIoLoops_)
|
||||
{
|
||||
nextLoopIdx_ = 0;
|
||||
}
|
||||
TcpConnectionPtr newPtr;
|
||||
if (policyPtr_)
|
||||
{
|
||||
assert(sslContextPtr_);
|
||||
newPtr = std::make_shared<TcpConnectionImpl>(
|
||||
ioLoop,
|
||||
sockfd,
|
||||
InetAddress(Socket::getLocalAddr(sockfd)),
|
||||
peer,
|
||||
policyPtr_,
|
||||
sslContextPtr_);
|
||||
}
|
||||
else
|
||||
{
|
||||
newPtr = std::make_shared<TcpConnectionImpl>(
|
||||
ioLoop, sockfd, InetAddress(Socket::getLocalAddr(sockfd)), peer);
|
||||
}
|
||||
|
||||
if (idleTimeout_ > 0)
|
||||
{
|
||||
assert(timingWheelMap_[ioLoop]);
|
||||
newPtr->enableKickingOff(idleTimeout_, timingWheelMap_[ioLoop]);
|
||||
}
|
||||
newPtr->setRecvMsgCallback(recvMessageCallback_);
|
||||
|
||||
newPtr->setConnectionCallback(
|
||||
[this](const TcpConnectionPtr &connectionPtr) {
|
||||
if (connectionCallback_)
|
||||
connectionCallback_(connectionPtr);
|
||||
});
|
||||
newPtr->setWriteCompleteCallback(
|
||||
[this](const TcpConnectionPtr &connectionPtr) {
|
||||
if (writeCompleteCallback_)
|
||||
writeCompleteCallback_(connectionPtr);
|
||||
});
|
||||
|
||||
newPtr->setCloseCallback([this](const TcpConnectionPtr &closeConnPtr) {
|
||||
connectionClosed(closeConnPtr);
|
||||
});
|
||||
connSet_.insert(newPtr);
|
||||
newPtr->connectEstablished();
|
||||
}
|
||||
|
||||
void TcpServer::start()
|
||||
{
|
||||
loop_->runInLoop([this]() {
|
||||
assert(!started_);
|
||||
started_ = true;
|
||||
if (idleTimeout_ > 0)
|
||||
{
|
||||
for (EventLoop *loop : ioLoops_)
|
||||
{
|
||||
timingWheelMap_[loop] =
|
||||
std::make_shared<TimingWheel>(loop,
|
||||
idleTimeout_,
|
||||
1.0F,
|
||||
idleTimeout_ < 500
|
||||
? idleTimeout_ + 1
|
||||
: 100);
|
||||
}
|
||||
}
|
||||
LOG_TRACE << "map size=" << timingWheelMap_.size();
|
||||
acceptorPtr_->listen();
|
||||
});
|
||||
}
|
||||
void TcpServer::stop()
|
||||
{
|
||||
if (loop_->isInLoopThread())
|
||||
{
|
||||
acceptorPtr_.reset();
|
||||
// copy the connSet_ to a vector, use the vector to close the
|
||||
// connections to avoid the iterator invalidation.
|
||||
std::vector<TcpConnectionPtr> connPtrs;
|
||||
connPtrs.reserve(connSet_.size());
|
||||
for (auto &conn : connSet_)
|
||||
{
|
||||
connPtrs.push_back(conn);
|
||||
}
|
||||
for (auto &connection : connPtrs)
|
||||
{
|
||||
connection->forceClose();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::promise<void> pro;
|
||||
auto f = pro.get_future();
|
||||
loop_->queueInLoop([this, &pro]() {
|
||||
acceptorPtr_.reset();
|
||||
std::vector<TcpConnectionPtr> connPtrs;
|
||||
connPtrs.reserve(connSet_.size());
|
||||
for (auto &conn : connSet_)
|
||||
{
|
||||
connPtrs.push_back(conn);
|
||||
}
|
||||
for (auto &connection : connPtrs)
|
||||
{
|
||||
connection->forceClose();
|
||||
}
|
||||
pro.set_value();
|
||||
});
|
||||
f.get();
|
||||
}
|
||||
loopPoolPtr_.reset();
|
||||
for (auto &iter : timingWheelMap_)
|
||||
{
|
||||
std::promise<void> pro;
|
||||
auto f = pro.get_future();
|
||||
iter.second->getLoop()->runInLoop([&iter, &pro]() mutable {
|
||||
iter.second.reset();
|
||||
pro.set_value();
|
||||
});
|
||||
f.get();
|
||||
}
|
||||
}
|
||||
void TcpServer::handleCloseInLoop(const TcpConnectionPtr &connectionPtr)
|
||||
{
|
||||
size_t n = connSet_.erase(connectionPtr);
|
||||
(void)n;
|
||||
assert(n == 1);
|
||||
auto connLoop = connectionPtr->getLoop();
|
||||
|
||||
// NOTE: always queue this operation in connLoop, because this connection
|
||||
// may be in loop_'s current active channels, waiting to be processed.
|
||||
// If `connectDestroyed()` is called here, we will be using an wild pointer
|
||||
// later.
|
||||
connLoop->queueInLoop(
|
||||
[connectionPtr]() { connectionPtr->connectDestroyed(); });
|
||||
}
|
||||
void TcpServer::connectionClosed(const TcpConnectionPtr &connectionPtr)
|
||||
{
|
||||
LOG_TRACE << "connectionClosed";
|
||||
if (loop_->isInLoopThread())
|
||||
{
|
||||
handleCloseInLoop(connectionPtr);
|
||||
}
|
||||
else
|
||||
{
|
||||
loop_->queueInLoop(
|
||||
[this, connectionPtr]() { handleCloseInLoop(connectionPtr); });
|
||||
}
|
||||
}
|
||||
|
||||
std::string TcpServer::ipPort() const
|
||||
{
|
||||
return acceptorPtr_->addr().toIpPort();
|
||||
}
|
||||
|
||||
const trantor::InetAddress &TcpServer::address() const
|
||||
{
|
||||
return acceptorPtr_->addr();
|
||||
}
|
||||
|
||||
void TcpServer::enableSSL(
|
||||
const std::string &certPath,
|
||||
const std::string &keyPath,
|
||||
bool useOldTLS,
|
||||
const std::vector<std::pair<std::string, std::string>> &sslConfCmds,
|
||||
const std::string &caPath)
|
||||
{
|
||||
policyPtr_ = TLSPolicy::defaultServerPolicy(certPath, keyPath);
|
||||
policyPtr_->setUseOldTLS(useOldTLS)
|
||||
.setConfCmds(sslConfCmds)
|
||||
.setCaPath(caPath)
|
||||
.setValidate(caPath.empty() ? false : true);
|
||||
sslContextPtr_ = newSSLContext(*policyPtr_, true);
|
||||
}
|
||||
|
||||
void TcpServer::reloadSSL()
|
||||
{
|
||||
if (loop_->isInLoopThread())
|
||||
{
|
||||
if (policyPtr_)
|
||||
{
|
||||
sslContextPtr_ = newSSLContext(*policyPtr_, true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
loop_->queueInLoop([this]() {
|
||||
if (policyPtr_)
|
||||
{
|
||||
sslContextPtr_ = newSSLContext(*policyPtr_, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,317 @@
|
||||
/**
|
||||
*
|
||||
* @file TcpServer.h
|
||||
* @author An Tao
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* https://github.com/an-tao/trantor
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Trantor
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <trantor/exports.h>
|
||||
#include <trantor/net/EventLoopThreadPool.h>
|
||||
#include <trantor/net/InetAddress.h>
|
||||
#include <trantor/net/TcpConnection.h>
|
||||
#include <trantor/net/callbacks.h>
|
||||
#include <trantor/utils/Logger.h>
|
||||
#include <trantor/utils/NonCopyable.h>
|
||||
#include <trantor/utils/TimingWheel.h>
|
||||
#include <csignal>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
namespace trantor
|
||||
{
|
||||
class Acceptor;
|
||||
/**
|
||||
* @brief This class represents a TCP server.
|
||||
*
|
||||
*/
|
||||
class TRANTOR_EXPORT TcpServer : NonCopyable
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a new TCP server instance.
|
||||
*
|
||||
* @param loop The event loop in which the acceptor of the server is
|
||||
* handled.
|
||||
* @param address The address of the server.
|
||||
* @param name The name of the server.
|
||||
* @param reUseAddr The SO_REUSEADDR option.
|
||||
* @param reUsePort The SO_REUSEPORT option.
|
||||
*/
|
||||
TcpServer(EventLoop *loop,
|
||||
const InetAddress &address,
|
||||
std::string name,
|
||||
bool reUseAddr = true,
|
||||
bool reUsePort = true);
|
||||
~TcpServer();
|
||||
|
||||
/**
|
||||
* @brief Start the server.
|
||||
*
|
||||
*/
|
||||
void start();
|
||||
|
||||
/**
|
||||
* @brief Stop the server.
|
||||
*
|
||||
*/
|
||||
void stop();
|
||||
|
||||
/**
|
||||
* @brief Set the number of event loops in which the I/O of connections to
|
||||
* the server is handled.
|
||||
* An EventLoopThreadPool is created and managed by TcpServer.
|
||||
*
|
||||
* @param num
|
||||
*/
|
||||
void setIoLoopNum(size_t num)
|
||||
{
|
||||
assert(!started_);
|
||||
loopPoolPtr_ = std::make_shared<EventLoopThreadPool>(num);
|
||||
loopPoolPtr_->start();
|
||||
ioLoops_ = loopPoolPtr_->getLoops();
|
||||
numIoLoops_ = ioLoops_.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the event loops pool in which the I/O of connections to
|
||||
* the server is handled.
|
||||
* A shared_ptr of EventLoopThreadPool is copied.
|
||||
*
|
||||
* @param pool
|
||||
*/
|
||||
void setIoLoopThreadPool(const std::shared_ptr<EventLoopThreadPool> &pool)
|
||||
{
|
||||
assert(pool->size() > 0);
|
||||
assert(!started_);
|
||||
loopPoolPtr_ = pool;
|
||||
loopPoolPtr_->start(); // TODO: should not start by TcpServer
|
||||
ioLoops_ = loopPoolPtr_->getLoops();
|
||||
numIoLoops_ = ioLoops_.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the event loops in which the I/O of connections to
|
||||
* the server is handled.
|
||||
* The loops are managed by caller. Caller should ensure that ioLoops
|
||||
* lives longer than TcpServer.
|
||||
*
|
||||
* @param ioLoops
|
||||
*/
|
||||
void setIoLoops(const std::vector<trantor::EventLoop *> &ioLoops)
|
||||
{
|
||||
assert(!ioLoops.empty());
|
||||
assert(!started_);
|
||||
ioLoops_ = ioLoops;
|
||||
numIoLoops_ = ioLoops_.size();
|
||||
loopPoolPtr_.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the message callback.
|
||||
*
|
||||
* @param cb The callback is called when some data is received on a
|
||||
* connection to the server.
|
||||
*/
|
||||
void setRecvMessageCallback(const RecvMessageCallback &cb)
|
||||
{
|
||||
recvMessageCallback_ = cb;
|
||||
}
|
||||
void setRecvMessageCallback(RecvMessageCallback &&cb)
|
||||
{
|
||||
recvMessageCallback_ = std::move(cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the connection callback.
|
||||
*
|
||||
* @param cb The callback is called when a connection is established or
|
||||
* closed.
|
||||
*/
|
||||
void setConnectionCallback(const ConnectionCallback &cb)
|
||||
{
|
||||
connectionCallback_ = cb;
|
||||
}
|
||||
void setConnectionCallback(ConnectionCallback &&cb)
|
||||
{
|
||||
connectionCallback_ = std::move(cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the write complete callback.
|
||||
*
|
||||
* @param cb The callback is called when data to send is written to the
|
||||
* socket of a connection.
|
||||
*/
|
||||
void setWriteCompleteCallback(const WriteCompleteCallback &cb)
|
||||
{
|
||||
writeCompleteCallback_ = cb;
|
||||
}
|
||||
void setWriteCompleteCallback(WriteCompleteCallback &&cb)
|
||||
{
|
||||
writeCompleteCallback_ = std::move(cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the before listen setsockopt callback.
|
||||
*
|
||||
* @param cb This callback will be called before the listen
|
||||
*/
|
||||
void setBeforeListenSockOptCallback(SockOptCallback cb);
|
||||
/**
|
||||
* @brief Set the after accept setsockopt callback.
|
||||
*
|
||||
* @param cb This callback will be called after accept
|
||||
*/
|
||||
void setAfterAcceptSockOptCallback(SockOptCallback cb);
|
||||
|
||||
/**
|
||||
* @brief Get the name of the server.
|
||||
*
|
||||
* @return const std::string&
|
||||
*/
|
||||
const std::string &name() const
|
||||
{
|
||||
return serverName_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the IP and port string of the server.
|
||||
*
|
||||
* @return const std::string
|
||||
*/
|
||||
std::string ipPort() const;
|
||||
|
||||
/**
|
||||
* @brief Get the address of the server.
|
||||
*
|
||||
* @return const trantor::InetAddress&
|
||||
*/
|
||||
const trantor::InetAddress &address() const;
|
||||
|
||||
/**
|
||||
* @brief Get the event loop of the server.
|
||||
*
|
||||
* @return EventLoop*
|
||||
*/
|
||||
EventLoop *getLoop() const
|
||||
{
|
||||
return loop_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the I/O event loops of the server.
|
||||
*
|
||||
* @return std::vector<EventLoop *>
|
||||
*/
|
||||
std::vector<EventLoop *> getIoLoops() const
|
||||
{
|
||||
return ioLoops_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief An idle connection is a connection that has no read or write, kick
|
||||
* off it after timeout seconds.
|
||||
*
|
||||
* @param timeout
|
||||
*/
|
||||
void kickoffIdleConnections(size_t timeout)
|
||||
{
|
||||
loop_->runInLoop([this, timeout]() {
|
||||
assert(!started_);
|
||||
idleTimeout_ = timeout;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Enable SSL encryption.
|
||||
*
|
||||
* @param certPath The path of the certificate file.
|
||||
* @param keyPath The path of the private key file.
|
||||
* @param useOldTLS If true, the TLS 1.0 and 1.1 are supported by the
|
||||
* server.
|
||||
* @param sslConfCmds The commands used to call the SSL_CONF_cmd function in
|
||||
* OpenSSL.
|
||||
* @param caPath The path of the certificate authority file.
|
||||
* @note It's well known that TLS 1.0 and 1.1 are not considered secure in
|
||||
* 2020. And it's a good practice to only use TLS 1.2 and above.
|
||||
*/
|
||||
[[deprecated("Use enableSSL(TLSPolicyPtr) instead")]] void enableSSL(
|
||||
const std::string &certPath,
|
||||
const std::string &keyPath,
|
||||
bool useOldTLS = false,
|
||||
const std::vector<std::pair<std::string, std::string>> &sslConfCmds =
|
||||
{},
|
||||
const std::string &caPath = "");
|
||||
/**
|
||||
* @brief Enable SSL encryption.
|
||||
*/
|
||||
void enableSSL(TLSPolicyPtr policy)
|
||||
{
|
||||
policyPtr_ = std::move(policy);
|
||||
sslContextPtr_ = newSSLContext(*policyPtr_, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reload the SSL context.
|
||||
* @note Call this function when the certificate or private key is updated.
|
||||
* The server will reload the SSL context and use the new certificate and
|
||||
* private key. new connections will use the new SSL context.
|
||||
*/
|
||||
void reloadSSL();
|
||||
|
||||
private:
|
||||
void handleCloseInLoop(const TcpConnectionPtr &connectionPtr);
|
||||
void newConnection(int fd, const InetAddress &peer);
|
||||
void connectionClosed(const TcpConnectionPtr &connectionPtr);
|
||||
|
||||
EventLoop *loop_;
|
||||
std::unique_ptr<Acceptor> acceptorPtr_;
|
||||
std::string serverName_;
|
||||
std::set<TcpConnectionPtr> connSet_;
|
||||
|
||||
RecvMessageCallback recvMessageCallback_;
|
||||
ConnectionCallback connectionCallback_;
|
||||
WriteCompleteCallback writeCompleteCallback_;
|
||||
|
||||
size_t idleTimeout_{0};
|
||||
std::map<EventLoop *, std::shared_ptr<TimingWheel>> timingWheelMap_;
|
||||
|
||||
// `loopPoolPtr_` may and may not hold the internal thread pool.
|
||||
// We should not access it directly in codes.
|
||||
// Instead, we should use its delegation variable `ioLoops_`.
|
||||
std::shared_ptr<EventLoopThreadPool> loopPoolPtr_;
|
||||
// If one of `setIoLoopNum()`, `setIoLoopThreadPool()` and `setIoLoops()` is
|
||||
// called, `ioLoops_` will hold the loops passed in.
|
||||
// Otherwise, it should contain only one element, which is `loop_`.
|
||||
std::vector<EventLoop *> ioLoops_;
|
||||
size_t nextLoopIdx_{0};
|
||||
size_t numIoLoops_{0};
|
||||
|
||||
#ifndef _WIN32
|
||||
class IgnoreSigPipe
|
||||
{
|
||||
public:
|
||||
IgnoreSigPipe()
|
||||
{
|
||||
::signal(SIGPIPE, SIG_IGN);
|
||||
LOG_TRACE << "Ignore SIGPIPE";
|
||||
}
|
||||
};
|
||||
|
||||
IgnoreSigPipe initObj;
|
||||
#endif
|
||||
bool started_{false};
|
||||
TLSPolicyPtr policyPtr_{nullptr};
|
||||
SSLContextPtr sslContextPtr_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace trantor
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
*
|
||||
* callbacks.h
|
||||
* An Tao
|
||||
*
|
||||
* Public header file in trantor lib.
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
namespace trantor
|
||||
{
|
||||
enum class SSLError
|
||||
{
|
||||
kSSLHandshakeError,
|
||||
kSSLInvalidCertificate,
|
||||
kSSLProtocolError
|
||||
};
|
||||
using TimerCallback = std::function<void()>;
|
||||
|
||||
// the data has been read to (buf, len)
|
||||
class TcpConnection;
|
||||
class MsgBuffer;
|
||||
using TcpConnectionPtr = std::shared_ptr<TcpConnection>;
|
||||
// tcp server and connection callback
|
||||
using RecvMessageCallback =
|
||||
std::function<void(const TcpConnectionPtr &, MsgBuffer *)>;
|
||||
using ConnectionErrorCallback = std::function<void()>;
|
||||
using ConnectionCallback = std::function<void(const TcpConnectionPtr &)>;
|
||||
using CloseCallback = std::function<void(const TcpConnectionPtr &)>;
|
||||
using WriteCompleteCallback = std::function<void(const TcpConnectionPtr &)>;
|
||||
using HighWaterMarkCallback =
|
||||
std::function<void(const TcpConnectionPtr &, const size_t)>;
|
||||
using SSLErrorCallback = std::function<void(SSLError)>;
|
||||
using SockOptCallback = std::function<void(int)>;
|
||||
|
||||
} // namespace trantor
|
||||
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
*
|
||||
* Acceptor.cc
|
||||
* An Tao
|
||||
*
|
||||
* Public header file in trantor lib.
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#include "Acceptor.h"
|
||||
using namespace trantor;
|
||||
|
||||
#ifndef O_CLOEXEC
|
||||
#define O_CLOEXEC O_NOINHERIT
|
||||
#endif
|
||||
|
||||
Acceptor::Acceptor(EventLoop *loop,
|
||||
const InetAddress &addr,
|
||||
bool reUseAddr,
|
||||
bool reUsePort)
|
||||
|
||||
:
|
||||
#ifndef _WIN32
|
||||
idleFd_(::open("/dev/null", O_RDONLY | O_CLOEXEC)),
|
||||
#endif
|
||||
sock_(
|
||||
Socket::createNonblockingSocketOrDie(addr.getSockAddr()->sa_family)),
|
||||
addr_(addr),
|
||||
loop_(loop),
|
||||
acceptChannel_(loop, sock_.fd())
|
||||
{
|
||||
sock_.setReuseAddr(reUseAddr);
|
||||
sock_.setReusePort(reUsePort);
|
||||
sock_.bindAddress(addr_);
|
||||
acceptChannel_.setReadCallback(std::bind(&Acceptor::readCallback, this));
|
||||
if (addr_.toPort() == 0)
|
||||
{
|
||||
addr_ = InetAddress{Socket::getLocalAddr(sock_.fd())};
|
||||
}
|
||||
}
|
||||
Acceptor::~Acceptor()
|
||||
{
|
||||
acceptChannel_.disableAll();
|
||||
acceptChannel_.remove();
|
||||
#ifndef _WIN32
|
||||
::close(idleFd_);
|
||||
#endif
|
||||
}
|
||||
void Acceptor::listen()
|
||||
{
|
||||
loop_->assertInLoopThread();
|
||||
if (beforeListenSetSockOptCallback_)
|
||||
beforeListenSetSockOptCallback_(sock_.fd());
|
||||
sock_.listen();
|
||||
acceptChannel_.enableReading();
|
||||
}
|
||||
|
||||
void Acceptor::readCallback()
|
||||
{
|
||||
InetAddress peer;
|
||||
int newsock = sock_.accept(&peer);
|
||||
if (newsock >= 0)
|
||||
{
|
||||
if (afterAcceptSetSockOptCallback_)
|
||||
afterAcceptSetSockOptCallback_(newsock);
|
||||
if (newConnectionCallback_)
|
||||
{
|
||||
newConnectionCallback_(newsock, peer);
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifndef _WIN32
|
||||
::close(newsock);
|
||||
#else
|
||||
closesocket(newsock);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_SYSERR << "Acceptor::readCallback";
|
||||
// Read the section named "The special problem of
|
||||
// accept()ing when you can't" in libev's doc.
|
||||
// By Marc Lehmann, author of libev.
|
||||
/// errno is thread safe
|
||||
#ifndef _WIN32
|
||||
if (errno == EMFILE)
|
||||
{
|
||||
::close(idleFd_);
|
||||
idleFd_ = sock_.accept(&peer);
|
||||
::close(idleFd_);
|
||||
idleFd_ = ::open("/dev/null", O_RDONLY | O_CLOEXEC);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
*
|
||||
* Acceptor.h
|
||||
* An Tao
|
||||
*
|
||||
* Public header file in trantor lib.
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <trantor/net/EventLoop.h>
|
||||
#include <trantor/utils/NonCopyable.h>
|
||||
#include "Socket.h"
|
||||
#include <trantor/net/InetAddress.h>
|
||||
#include "Channel.h"
|
||||
#include <functional>
|
||||
|
||||
namespace trantor
|
||||
{
|
||||
using NewConnectionCallback = std::function<void(int fd, const InetAddress &)>;
|
||||
using AcceptorSockOptCallback = std::function<void(int)>;
|
||||
class Acceptor : NonCopyable
|
||||
{
|
||||
public:
|
||||
Acceptor(EventLoop *loop,
|
||||
const InetAddress &addr,
|
||||
bool reUseAddr = true,
|
||||
bool reUsePort = true);
|
||||
~Acceptor();
|
||||
const InetAddress &addr() const
|
||||
{
|
||||
return addr_;
|
||||
}
|
||||
void setNewConnectionCallback(const NewConnectionCallback &cb)
|
||||
{
|
||||
newConnectionCallback_ = cb;
|
||||
};
|
||||
void listen();
|
||||
|
||||
void setBeforeListenSockOptCallback(AcceptorSockOptCallback cb)
|
||||
{
|
||||
beforeListenSetSockOptCallback_ = std::move(cb);
|
||||
}
|
||||
|
||||
void setAfterAcceptSockOptCallback(AcceptorSockOptCallback cb)
|
||||
{
|
||||
afterAcceptSetSockOptCallback_ = std::move(cb);
|
||||
}
|
||||
|
||||
protected:
|
||||
#ifndef _WIN32
|
||||
int idleFd_;
|
||||
#endif
|
||||
Socket sock_;
|
||||
InetAddress addr_;
|
||||
EventLoop *loop_;
|
||||
NewConnectionCallback newConnectionCallback_;
|
||||
Channel acceptChannel_;
|
||||
void readCallback();
|
||||
AcceptorSockOptCallback beforeListenSetSockOptCallback_;
|
||||
AcceptorSockOptCallback afterAcceptSetSockOptCallback_;
|
||||
};
|
||||
} // namespace trantor
|
||||
@@ -0,0 +1,293 @@
|
||||
// Copyright 2016, Tao An. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license
|
||||
// that can be found in the License file.
|
||||
|
||||
// Author: Tao An
|
||||
|
||||
#include "AresResolver.h"
|
||||
#include <trantor/net/Channel.h>
|
||||
#include <ares.h>
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#else
|
||||
#include <netdb.h>
|
||||
#include <arpa/inet.h> // inet_ntop
|
||||
#include <netinet/in.h>
|
||||
#endif
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
|
||||
using namespace trantor;
|
||||
using namespace std::placeholders;
|
||||
|
||||
namespace
|
||||
{
|
||||
double getSeconds(struct timeval* tv)
|
||||
{
|
||||
if (tv)
|
||||
return double(tv->tv_sec) + double(tv->tv_usec) / 1000000.0;
|
||||
else
|
||||
return -1.0;
|
||||
}
|
||||
|
||||
const char* getSocketType(int type)
|
||||
{
|
||||
if (type == SOCK_DGRAM)
|
||||
return "UDP";
|
||||
else if (type == SOCK_STREAM)
|
||||
return "TCP";
|
||||
else
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool Resolver::isCAresUsed()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
AresResolver::LibraryInitializer::LibraryInitializer()
|
||||
{
|
||||
ares_library_init(ARES_LIB_INIT_ALL);
|
||||
|
||||
hints_ = new ares_addrinfo_hints;
|
||||
hints_->ai_flags = 0;
|
||||
hints_->ai_family = AF_INET;
|
||||
hints_->ai_socktype = 0;
|
||||
hints_->ai_protocol = 0;
|
||||
}
|
||||
AresResolver::LibraryInitializer::~LibraryInitializer()
|
||||
{
|
||||
ares_library_cleanup();
|
||||
delete hints_;
|
||||
}
|
||||
|
||||
AresResolver::LibraryInitializer AresResolver::libraryInitializer_;
|
||||
|
||||
std::shared_ptr<Resolver> Resolver::newResolver(trantor::EventLoop* loop,
|
||||
size_t timeout)
|
||||
{
|
||||
return std::make_shared<AresResolver>(loop, timeout);
|
||||
}
|
||||
|
||||
AresResolver::AresResolver(EventLoop* loop, size_t timeout)
|
||||
: loop_(loop), timeout_(timeout)
|
||||
{
|
||||
if (!loop)
|
||||
{
|
||||
loop_ = getLoop();
|
||||
}
|
||||
loopValid_ = std::make_shared<bool>(true);
|
||||
loop_->runOnQuit([loopValid = loopValid_]() { *loopValid = false; });
|
||||
}
|
||||
void AresResolver::init()
|
||||
{
|
||||
if (!ctx_)
|
||||
{
|
||||
struct ares_options options;
|
||||
int optmask = ARES_OPT_FLAGS;
|
||||
options.flags = ARES_FLAG_NOCHECKRESP;
|
||||
options.flags |= ARES_FLAG_STAYOPEN;
|
||||
options.flags |= ARES_FLAG_IGNTC; // UDP only
|
||||
optmask |= ARES_OPT_SOCK_STATE_CB;
|
||||
options.sock_state_cb = &AresResolver::ares_sock_statecallback_;
|
||||
options.sock_state_cb_data = this;
|
||||
optmask |= ARES_OPT_TIMEOUT;
|
||||
options.timeout = 2;
|
||||
// optmask |= ARES_OPT_LOOKUPS;
|
||||
// options.lookups = lookups;
|
||||
|
||||
int status = ares_init_options(&ctx_, &options, optmask);
|
||||
if (status != ARES_SUCCESS)
|
||||
{
|
||||
assert(0);
|
||||
}
|
||||
ares_set_socket_callback(ctx_,
|
||||
&AresResolver::ares_sock_createcallback_,
|
||||
this);
|
||||
}
|
||||
}
|
||||
AresResolver::~AresResolver()
|
||||
{
|
||||
if (ctx_)
|
||||
ares_destroy(ctx_);
|
||||
}
|
||||
|
||||
void AresResolver::resolveInLoop(const std::string& hostname,
|
||||
const ResolverResultsCallback& cb)
|
||||
{
|
||||
loop_->assertInLoopThread();
|
||||
#ifdef _WIN32
|
||||
if (hostname == "localhost")
|
||||
{
|
||||
const static std::vector<trantor::InetAddress> localhost_{
|
||||
trantor::InetAddress{"127.0.0.1", 0}};
|
||||
cb(localhost_);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
init();
|
||||
QueryData* queryData = new QueryData(this, cb, hostname);
|
||||
ares_getaddrinfo(ctx_,
|
||||
hostname.c_str(),
|
||||
NULL,
|
||||
libraryInitializer_.hints_,
|
||||
&AresResolver::ares_hostcallback_,
|
||||
queryData);
|
||||
struct timeval tv;
|
||||
struct timeval* tvp = ares_timeout(ctx_, NULL, &tv);
|
||||
double timeout = getSeconds(tvp);
|
||||
if (!timerActive_ && timeout >= 0.0)
|
||||
{
|
||||
loop_->runAfter(timeout,
|
||||
std::bind(&AresResolver::onTimer, shared_from_this()));
|
||||
timerActive_ = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
void AresResolver::onRead(int sockfd)
|
||||
{
|
||||
ares_process_fd(ctx_, sockfd, ARES_SOCKET_BAD);
|
||||
}
|
||||
|
||||
void AresResolver::onTimer()
|
||||
{
|
||||
assert(timerActive_ == true);
|
||||
ares_process_fd(ctx_, ARES_SOCKET_BAD, ARES_SOCKET_BAD);
|
||||
struct timeval tv;
|
||||
struct timeval* tvp = ares_timeout(ctx_, NULL, &tv);
|
||||
double timeout = getSeconds(tvp);
|
||||
|
||||
if (timeout < 0)
|
||||
{
|
||||
timerActive_ = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
loop_->runAfter(timeout,
|
||||
std::bind(&AresResolver::onTimer, shared_from_this()));
|
||||
}
|
||||
}
|
||||
|
||||
void AresResolver::onQueryResult(int status,
|
||||
struct ares_addrinfo* result,
|
||||
const std::string& hostname,
|
||||
const ResolverResultsCallback& callback)
|
||||
{
|
||||
LOG_TRACE << "onQueryResult " << status;
|
||||
auto inets_ptr = std::make_shared<std::vector<trantor::InetAddress>>();
|
||||
if (result)
|
||||
{
|
||||
auto pptr = (struct ares_addrinfo_node*)result->nodes;
|
||||
for (; pptr != NULL; pptr = pptr->ai_next)
|
||||
{
|
||||
trantor::InetAddress inet;
|
||||
if (pptr->ai_family == AF_INET)
|
||||
{
|
||||
struct sockaddr_in* addr4 = (struct sockaddr_in*)pptr->ai_addr;
|
||||
inets_ptr->emplace_back(trantor::InetAddress{*addr4});
|
||||
}
|
||||
else if (pptr->ai_family == AF_INET6)
|
||||
{
|
||||
struct sockaddr_in6* addr6 =
|
||||
(struct sockaddr_in6*)pptr->ai_addr;
|
||||
inets_ptr->emplace_back(trantor::InetAddress{*addr6});
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Handle unknown family?
|
||||
}
|
||||
}
|
||||
ares_freeaddrinfo(result);
|
||||
}
|
||||
if (inets_ptr->empty())
|
||||
{
|
||||
struct sockaddr_in addr;
|
||||
memset(&addr, 0, sizeof addr);
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = 0;
|
||||
InetAddress inet(addr);
|
||||
inets_ptr->emplace_back(std::move(inet));
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(globalMutex());
|
||||
auto& addrItem = globalCache()[hostname];
|
||||
addrItem.first = inets_ptr;
|
||||
addrItem.second = trantor::Date::date();
|
||||
}
|
||||
callback(*inets_ptr);
|
||||
}
|
||||
|
||||
void AresResolver::onSockCreate(int sockfd, int type)
|
||||
{
|
||||
(void)type;
|
||||
loop_->assertInLoopThread();
|
||||
assert(channels_.find(sockfd) == channels_.end());
|
||||
Channel* channel = new Channel(loop_, sockfd);
|
||||
channel->setReadCallback(std::bind(&AresResolver::onRead, this, sockfd));
|
||||
channel->enableReading();
|
||||
channels_[sockfd].reset(channel);
|
||||
}
|
||||
|
||||
void AresResolver::onSockStateChange(int sockfd, bool read, bool write)
|
||||
{
|
||||
(void)write;
|
||||
if (read)
|
||||
{
|
||||
// update
|
||||
// if (write) { } else { }
|
||||
}
|
||||
else if (*loopValid_)
|
||||
{
|
||||
loop_->assertInLoopThread();
|
||||
ChannelList::iterator it = channels_.find(sockfd);
|
||||
assert(it != channels_.end());
|
||||
// remove
|
||||
it->second->disableAll();
|
||||
it->second->remove();
|
||||
channels_.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void AresResolver::ares_hostcallback_(void* data,
|
||||
int status,
|
||||
int timeouts,
|
||||
struct ares_addrinfo* hostent)
|
||||
{
|
||||
(void)timeouts;
|
||||
QueryData* query = static_cast<QueryData*>(data);
|
||||
|
||||
query->owner_->onQueryResult(status,
|
||||
hostent,
|
||||
query->hostname_,
|
||||
query->callback_);
|
||||
delete query;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
int AresResolver::ares_sock_createcallback_(SOCKET sockfd, int type, void* data)
|
||||
#else
|
||||
int AresResolver::ares_sock_createcallback_(int sockfd, int type, void* data)
|
||||
#endif
|
||||
{
|
||||
LOG_TRACE << "sockfd=" << sockfd << " type=" << getSocketType(type);
|
||||
static_cast<AresResolver*>(data)->onSockCreate(sockfd, type);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void AresResolver::ares_sock_statecallback_(void* data,
|
||||
#ifdef _WIN32
|
||||
SOCKET sockfd,
|
||||
#else
|
||||
int sockfd,
|
||||
#endif
|
||||
int read,
|
||||
int write)
|
||||
{
|
||||
LOG_TRACE << "sockfd=" << sockfd << " read=" << read << " write=" << write;
|
||||
static_cast<AresResolver*>(data)->onSockStateChange(sockfd, read, write);
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
// Copyright 2016, Tao An. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license
|
||||
// that can be found in the License file.
|
||||
|
||||
// Author: Tao An
|
||||
|
||||
#pragma once
|
||||
#include <trantor/net/Resolver.h>
|
||||
#include <trantor/utils/NonCopyable.h>
|
||||
#include <trantor/net/EventLoopThread.h>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string.h>
|
||||
|
||||
extern "C"
|
||||
{
|
||||
struct ares_addrinfo;
|
||||
struct ares_channeldata;
|
||||
struct ares_addrinfo_hints;
|
||||
using ares_channel = struct ares_channeldata*;
|
||||
}
|
||||
namespace trantor
|
||||
{
|
||||
class AresResolver : public Resolver,
|
||||
public NonCopyable,
|
||||
public std::enable_shared_from_this<AresResolver>
|
||||
{
|
||||
public:
|
||||
AresResolver(trantor::EventLoop* loop, size_t timeout);
|
||||
~AresResolver();
|
||||
|
||||
virtual void resolve(const std::string& hostname,
|
||||
const Callback& cb) override
|
||||
{
|
||||
bool cached = false;
|
||||
InetAddress inet;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(globalMutex());
|
||||
auto iter = globalCache().find(hostname);
|
||||
if (iter != globalCache().end())
|
||||
{
|
||||
auto& cachedAddr = iter->second;
|
||||
if (timeout_ == 0 ||
|
||||
cachedAddr.second.after(timeout_) > trantor::Date::date())
|
||||
{
|
||||
inet = (*cachedAddr.first)[0];
|
||||
cached = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cached)
|
||||
{
|
||||
cb(inet);
|
||||
return;
|
||||
}
|
||||
if (loop_->isInLoopThread())
|
||||
{
|
||||
resolveInLoop(hostname,
|
||||
[cb](const std::vector<trantor::InetAddress>& inets) {
|
||||
cb(inets[0]);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
loop_->queueInLoop([thisPtr = shared_from_this(), hostname, cb]() {
|
||||
thisPtr->resolveInLoop(
|
||||
hostname,
|
||||
[cb](const std::vector<trantor::InetAddress>& inets) {
|
||||
cb(inets[0]);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
virtual void resolve(const std::string& hostname,
|
||||
const ResolverResultsCallback& cb) override
|
||||
{
|
||||
std::shared_ptr<std::vector<trantor::InetAddress>> inets_ptr{nullptr};
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(globalMutex());
|
||||
auto iter = globalCache().find(hostname);
|
||||
if (iter != globalCache().end())
|
||||
{
|
||||
auto& cachedAddr = iter->second;
|
||||
if (timeout_ == 0 ||
|
||||
cachedAddr.second.after(timeout_) > trantor::Date::date())
|
||||
{
|
||||
inets_ptr = cachedAddr.first;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (inets_ptr)
|
||||
{
|
||||
cb(*inets_ptr);
|
||||
return;
|
||||
}
|
||||
if (loop_->isInLoopThread())
|
||||
{
|
||||
resolveInLoop(hostname, cb);
|
||||
}
|
||||
else
|
||||
{
|
||||
loop_->queueInLoop([thisPtr = shared_from_this(), hostname, cb]() {
|
||||
thisPtr->resolveInLoop(hostname, cb);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
struct QueryData
|
||||
{
|
||||
AresResolver* owner_;
|
||||
ResolverResultsCallback callback_;
|
||||
std::string hostname_;
|
||||
QueryData(AresResolver* o,
|
||||
const ResolverResultsCallback& cb,
|
||||
const std::string& hostname)
|
||||
: owner_(o), callback_(cb), hostname_(hostname)
|
||||
{
|
||||
}
|
||||
};
|
||||
void resolveInLoop(const std::string& hostname,
|
||||
const ResolverResultsCallback& cb);
|
||||
void init();
|
||||
trantor::EventLoop* loop_;
|
||||
std::shared_ptr<bool> loopValid_;
|
||||
ares_channel ctx_{nullptr};
|
||||
bool timerActive_{false};
|
||||
using ChannelList = std::map<int, std::unique_ptr<trantor::Channel>>;
|
||||
ChannelList channels_;
|
||||
static std::unordered_map<
|
||||
std::string,
|
||||
std::pair<std::shared_ptr<std::vector<trantor::InetAddress>>,
|
||||
trantor::Date>>&
|
||||
globalCache()
|
||||
{
|
||||
static std::unordered_map<
|
||||
std::string,
|
||||
std::pair<std::shared_ptr<std::vector<trantor::InetAddress>>,
|
||||
trantor::Date>>
|
||||
dnsCache;
|
||||
return dnsCache;
|
||||
}
|
||||
static std::mutex& globalMutex()
|
||||
{
|
||||
static std::mutex mutex_;
|
||||
return mutex_;
|
||||
}
|
||||
static EventLoop* getLoop()
|
||||
{
|
||||
static EventLoopThread loopThread;
|
||||
loopThread.run();
|
||||
return loopThread.getLoop();
|
||||
}
|
||||
const size_t timeout_{60};
|
||||
|
||||
void onRead(int sockfd);
|
||||
void onTimer();
|
||||
void onQueryResult(int status,
|
||||
struct ares_addrinfo* result,
|
||||
const std::string& hostname,
|
||||
const ResolverResultsCallback& callback);
|
||||
void onSockCreate(int sockfd, int type);
|
||||
void onSockStateChange(int sockfd, bool read, bool write);
|
||||
|
||||
static void ares_hostcallback_(void* data,
|
||||
int status,
|
||||
int timeouts,
|
||||
struct ares_addrinfo* hostent);
|
||||
#ifdef _WIN32
|
||||
static int ares_sock_createcallback_(SOCKET sockfd, int type, void* data);
|
||||
#else
|
||||
static int ares_sock_createcallback_(int sockfd, int type, void* data);
|
||||
#endif
|
||||
static void ares_sock_statecallback_(void* data,
|
||||
#ifdef _WIN32
|
||||
SOCKET sockfd,
|
||||
#else
|
||||
int sockfd,
|
||||
#endif
|
||||
int read,
|
||||
int write);
|
||||
struct LibraryInitializer
|
||||
{
|
||||
LibraryInitializer();
|
||||
~LibraryInitializer();
|
||||
ares_addrinfo_hints* hints_;
|
||||
};
|
||||
static LibraryInitializer libraryInitializer_;
|
||||
};
|
||||
} // namespace trantor
|
||||
@@ -0,0 +1,65 @@
|
||||
#include <trantor/net/inner/BufferNode.h>
|
||||
|
||||
namespace trantor
|
||||
{
|
||||
class AsyncBufferNode : public BufferNode
|
||||
{
|
||||
public:
|
||||
AsyncBufferNode() = default;
|
||||
~AsyncBufferNode() override = default;
|
||||
bool isAsync() const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
bool isStream() const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
long long remainingBytes() const override
|
||||
{
|
||||
if (msgBufferPtr_)
|
||||
return static_cast<long long>(msgBufferPtr_->readableBytes());
|
||||
return 0;
|
||||
}
|
||||
bool available() const override
|
||||
{
|
||||
return !isDone_;
|
||||
}
|
||||
void getData(const char *&data, size_t &len) override
|
||||
{
|
||||
if (msgBufferPtr_)
|
||||
{
|
||||
data = msgBufferPtr_->peek();
|
||||
len = msgBufferPtr_->readableBytes();
|
||||
}
|
||||
else
|
||||
{
|
||||
data = nullptr;
|
||||
len = 0;
|
||||
}
|
||||
}
|
||||
void retrieve(size_t len) override
|
||||
{
|
||||
assert(msgBufferPtr_);
|
||||
if (msgBufferPtr_)
|
||||
{
|
||||
msgBufferPtr_->retrieve(len);
|
||||
}
|
||||
}
|
||||
void append(const char *data, size_t len) override
|
||||
{
|
||||
if (!msgBufferPtr_)
|
||||
{
|
||||
msgBufferPtr_ = std::make_unique<MsgBuffer>(len);
|
||||
}
|
||||
msgBufferPtr_->append(data, len);
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<MsgBuffer> msgBufferPtr_;
|
||||
};
|
||||
BufferNodePtr BufferNode::newAsyncStreamBufferNode()
|
||||
{
|
||||
return std::make_shared<AsyncBufferNode>();
|
||||
}
|
||||
} // namespace trantor
|
||||
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
*
|
||||
* @file BufferNode.h
|
||||
* @author An Tao
|
||||
*
|
||||
* Public header file in trantor lib.
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifdef _WIN32
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
#include <trantor/utils/MsgBuffer.h>
|
||||
#include <trantor/utils/NonCopyable.h>
|
||||
#include <trantor/utils/Logger.h>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace trantor
|
||||
{
|
||||
class BufferNode;
|
||||
using BufferNodePtr = std::shared_ptr<BufferNode>;
|
||||
using StreamCallback = std::function<std::size_t(char *, std::size_t)>;
|
||||
class BufferNode : public NonCopyable
|
||||
{
|
||||
public:
|
||||
virtual bool isFile() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
virtual ~BufferNode() = default;
|
||||
virtual bool isStream() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
virtual void getData(const char *&data, size_t &len) = 0;
|
||||
virtual void append(const char *, size_t)
|
||||
{
|
||||
LOG_FATAL << "Not a memory buffer node";
|
||||
}
|
||||
virtual void retrieve(size_t len) = 0;
|
||||
virtual long long remainingBytes() const = 0;
|
||||
virtual int getFd() const
|
||||
{
|
||||
LOG_FATAL << "Not a file buffer node";
|
||||
return -1;
|
||||
}
|
||||
virtual bool available() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
virtual bool isAsync() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void done()
|
||||
{
|
||||
isDone_ = true;
|
||||
}
|
||||
static BufferNodePtr newMemBufferNode();
|
||||
|
||||
static BufferNodePtr newStreamBufferNode(StreamCallback &&cb);
|
||||
#ifdef _WIN32
|
||||
static BufferNodePtr newFileBufferNode(const wchar_t *fileName,
|
||||
long long offset,
|
||||
long long length);
|
||||
#else
|
||||
static BufferNodePtr newFileBufferNode(const char *fileName,
|
||||
long long offset,
|
||||
long long length);
|
||||
#endif
|
||||
static BufferNodePtr newAsyncStreamBufferNode();
|
||||
|
||||
protected:
|
||||
bool isDone_{false};
|
||||
};
|
||||
|
||||
} // namespace trantor
|
||||
@@ -0,0 +1,299 @@
|
||||
/**
|
||||
*
|
||||
* @file Connector.cc
|
||||
* @author An Tao
|
||||
*
|
||||
* Public header file in trantor lib.
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#include "Connector.h"
|
||||
#include "Channel.h"
|
||||
#include "Socket.h"
|
||||
|
||||
using namespace trantor;
|
||||
|
||||
Connector::Connector(EventLoop *loop, const InetAddress &addr, bool retry)
|
||||
: loop_(loop), serverAddr_(addr), retry_(retry)
|
||||
{
|
||||
}
|
||||
Connector::Connector(EventLoop *loop, InetAddress &&addr, bool retry)
|
||||
: loop_(loop), serverAddr_(std::move(addr)), retry_(retry)
|
||||
{
|
||||
}
|
||||
|
||||
Connector::~Connector()
|
||||
{
|
||||
if (socketHanded_ == false && fd_ != -1)
|
||||
{
|
||||
#ifndef _WIN32
|
||||
::close(fd_);
|
||||
#else
|
||||
closesocket(fd_);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void Connector::start()
|
||||
{
|
||||
connect_ = true;
|
||||
loop_->runInLoop([this]() { startInLoop(); });
|
||||
}
|
||||
void Connector::restart()
|
||||
{
|
||||
}
|
||||
void Connector::stop()
|
||||
{
|
||||
status_ = Status::Disconnected;
|
||||
if (loop_->isInLoopThread())
|
||||
{
|
||||
removeAndResetChannel();
|
||||
}
|
||||
else
|
||||
{
|
||||
loop_->queueInLoop([thisPtr = shared_from_this()]() {
|
||||
thisPtr->removeAndResetChannel();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void Connector::startInLoop()
|
||||
{
|
||||
loop_->assertInLoopThread();
|
||||
assert(status_ == Status::Disconnected);
|
||||
if (connect_)
|
||||
{
|
||||
connect();
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_TRACE << "do not connect";
|
||||
}
|
||||
}
|
||||
void Connector::connect()
|
||||
{
|
||||
socketHanded_ = false;
|
||||
fd_ = Socket::createNonblockingSocketOrDie(serverAddr_.family());
|
||||
if (sockOptCallback_)
|
||||
sockOptCallback_(fd_);
|
||||
errno = 0;
|
||||
int ret = Socket::connect(fd_, serverAddr_);
|
||||
int savedErrno = (ret == 0) ? 0 : errno;
|
||||
switch (savedErrno)
|
||||
{
|
||||
case 0:
|
||||
case EINPROGRESS:
|
||||
case EINTR:
|
||||
case EISCONN:
|
||||
LOG_TRACE << "connecting";
|
||||
connecting(fd_);
|
||||
break;
|
||||
|
||||
case EAGAIN:
|
||||
case EADDRINUSE:
|
||||
case EADDRNOTAVAIL:
|
||||
case ECONNREFUSED:
|
||||
case ENETUNREACH:
|
||||
if (retry_)
|
||||
{
|
||||
retry(fd_);
|
||||
}
|
||||
break;
|
||||
|
||||
case EACCES:
|
||||
case EPERM:
|
||||
case EAFNOSUPPORT:
|
||||
case EALREADY:
|
||||
case EBADF:
|
||||
case EFAULT:
|
||||
case ENOTSOCK:
|
||||
LOG_SYSERR << "connect error in Connector::startInLoop "
|
||||
<< savedErrno;
|
||||
socketHanded_ = true;
|
||||
#ifndef _WIN32
|
||||
::close(fd_);
|
||||
#else
|
||||
closesocket(fd_);
|
||||
#endif
|
||||
if (errorCallback_)
|
||||
errorCallback_();
|
||||
break;
|
||||
|
||||
default:
|
||||
LOG_SYSERR << "Unexpected error in Connector::startInLoop "
|
||||
<< savedErrno;
|
||||
socketHanded_ = true;
|
||||
#ifndef _WIN32
|
||||
::close(fd_);
|
||||
#else
|
||||
closesocket(fd_);
|
||||
#endif
|
||||
if (errorCallback_)
|
||||
errorCallback_();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Connector::connecting(int sockfd)
|
||||
{
|
||||
status_ = Status::Connecting;
|
||||
assert(!channelPtr_);
|
||||
channelPtr_.reset(new Channel(loop_, sockfd));
|
||||
channelPtr_->setWriteCallback(
|
||||
std::bind(&Connector::handleWrite, shared_from_this()));
|
||||
channelPtr_->setErrorCallback(
|
||||
std::bind(&Connector::handleError, shared_from_this()));
|
||||
channelPtr_->setCloseCallback(
|
||||
std::bind(&Connector::handleError, shared_from_this()));
|
||||
LOG_TRACE << "connecting:" << sockfd;
|
||||
channelPtr_->enableWriting();
|
||||
}
|
||||
|
||||
int Connector::removeAndResetChannel()
|
||||
{
|
||||
if (!channelPtr_)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
channelPtr_->disableAll();
|
||||
channelPtr_->remove();
|
||||
int sockfd = channelPtr_->fd();
|
||||
// Can't reset channel_ here, because we are inside Channel::handleEvent
|
||||
loop_->queueInLoop([channelPtr = channelPtr_]() {});
|
||||
channelPtr_.reset();
|
||||
return sockfd;
|
||||
}
|
||||
|
||||
void Connector::handleWrite()
|
||||
{
|
||||
socketHanded_ = true;
|
||||
if (status_ == Status::Connecting)
|
||||
{
|
||||
int sockfd = removeAndResetChannel();
|
||||
int err = Socket::getSocketError(sockfd);
|
||||
if (err)
|
||||
{
|
||||
LOG_WARN << "Connector::handleWrite - SO_ERROR = " << err << " "
|
||||
<< strerror_tl(err);
|
||||
if (retry_)
|
||||
{
|
||||
retry(sockfd);
|
||||
}
|
||||
else
|
||||
{
|
||||
socketHanded_ = true;
|
||||
#ifndef _WIN32
|
||||
::close(sockfd);
|
||||
#else
|
||||
closesocket(sockfd);
|
||||
#endif
|
||||
}
|
||||
if (errorCallback_)
|
||||
{
|
||||
errorCallback_();
|
||||
}
|
||||
}
|
||||
else if (Socket::isSelfConnect(sockfd))
|
||||
{
|
||||
LOG_WARN << "Connector::handleWrite - Self connect";
|
||||
if (retry_)
|
||||
{
|
||||
retry(sockfd);
|
||||
}
|
||||
else
|
||||
{
|
||||
socketHanded_ = true;
|
||||
#ifndef _WIN32
|
||||
::close(sockfd);
|
||||
#else
|
||||
closesocket(sockfd);
|
||||
#endif
|
||||
}
|
||||
if (errorCallback_)
|
||||
{
|
||||
errorCallback_();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
status_ = Status::Connected;
|
||||
if (connect_)
|
||||
{
|
||||
newConnectionCallback_(sockfd);
|
||||
}
|
||||
else
|
||||
{
|
||||
socketHanded_ = true;
|
||||
#ifndef _WIN32
|
||||
::close(sockfd);
|
||||
#else
|
||||
closesocket(sockfd);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// has been stopped
|
||||
assert(status_ == Status::Disconnected);
|
||||
}
|
||||
}
|
||||
|
||||
void Connector::handleError()
|
||||
{
|
||||
socketHanded_ = true;
|
||||
if (status_ == Status::Connecting)
|
||||
{
|
||||
status_ = Status::Disconnected;
|
||||
int sockfd = removeAndResetChannel();
|
||||
int err = Socket::getSocketError(sockfd);
|
||||
LOG_TRACE << "SO_ERROR = " << err << " " << strerror_tl(err);
|
||||
if (retry_)
|
||||
{
|
||||
retry(sockfd);
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifndef _WIN32
|
||||
::close(sockfd);
|
||||
#else
|
||||
closesocket(sockfd);
|
||||
#endif
|
||||
}
|
||||
if (errorCallback_)
|
||||
{
|
||||
errorCallback_();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Connector::retry(int sockfd)
|
||||
{
|
||||
assert(retry_);
|
||||
#ifndef _WIN32
|
||||
::close(sockfd);
|
||||
#else
|
||||
closesocket(sockfd);
|
||||
#endif
|
||||
status_ = Status::Disconnected;
|
||||
if (connect_)
|
||||
{
|
||||
LOG_INFO << "Connector::retry - Retry connecting to "
|
||||
<< serverAddr_.toIpPort() << " in " << retryInterval_
|
||||
<< " milliseconds. ";
|
||||
loop_->runAfter(retryInterval_ / 1000.0,
|
||||
std::bind(&Connector::startInLoop, shared_from_this()));
|
||||
retryInterval_ = retryInterval_ * 2;
|
||||
if (retryInterval_ > maxRetryInterval_)
|
||||
retryInterval_ = maxRetryInterval_;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_TRACE << "do not connect";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
*
|
||||
* Connector.h
|
||||
* An Tao
|
||||
*
|
||||
* Public header file in trantor lib.
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <trantor/net/EventLoop.h>
|
||||
#include <trantor/net/InetAddress.h>
|
||||
#include <trantor/utils/Logger.h>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
|
||||
namespace trantor
|
||||
{
|
||||
class Connector : public NonCopyable,
|
||||
public std::enable_shared_from_this<Connector>
|
||||
{
|
||||
public:
|
||||
using NewConnectionCallback = std::function<void(int sockfd)>;
|
||||
using ConnectionErrorCallback = std::function<void()>;
|
||||
using SockOptCallback = std::function<void(int sockfd)>;
|
||||
Connector(EventLoop *loop, const InetAddress &addr, bool retry = true);
|
||||
Connector(EventLoop *loop, InetAddress &&addr, bool retry = true);
|
||||
~Connector();
|
||||
void setNewConnectionCallback(const NewConnectionCallback &cb)
|
||||
{
|
||||
newConnectionCallback_ = cb;
|
||||
}
|
||||
void setNewConnectionCallback(NewConnectionCallback &&cb)
|
||||
{
|
||||
newConnectionCallback_ = std::move(cb);
|
||||
}
|
||||
void setErrorCallback(const ConnectionErrorCallback &cb)
|
||||
{
|
||||
errorCallback_ = cb;
|
||||
}
|
||||
void setErrorCallback(ConnectionErrorCallback &&cb)
|
||||
{
|
||||
errorCallback_ = std::move(cb);
|
||||
}
|
||||
void setSockOptCallback(const SockOptCallback &cb)
|
||||
{
|
||||
sockOptCallback_ = cb;
|
||||
}
|
||||
void setSockOptCallback(SockOptCallback &&cb)
|
||||
{
|
||||
sockOptCallback_ = std::move(cb);
|
||||
}
|
||||
const InetAddress &serverAddress() const
|
||||
{
|
||||
return serverAddr_;
|
||||
}
|
||||
void start();
|
||||
void restart();
|
||||
void stop();
|
||||
|
||||
private:
|
||||
NewConnectionCallback newConnectionCallback_;
|
||||
ConnectionErrorCallback errorCallback_;
|
||||
SockOptCallback sockOptCallback_;
|
||||
enum class Status
|
||||
{
|
||||
Disconnected,
|
||||
Connecting,
|
||||
Connected
|
||||
};
|
||||
static constexpr int kMaxRetryDelayMs = 30 * 1000;
|
||||
static constexpr int kInitRetryDelayMs = 500;
|
||||
std::shared_ptr<Channel> channelPtr_;
|
||||
EventLoop *loop_;
|
||||
InetAddress serverAddr_;
|
||||
|
||||
std::atomic_bool connect_{false};
|
||||
std::atomic<Status> status_{Status::Disconnected};
|
||||
|
||||
int retryInterval_{kInitRetryDelayMs};
|
||||
int maxRetryInterval_{kMaxRetryDelayMs};
|
||||
|
||||
bool retry_;
|
||||
bool socketHanded_{false};
|
||||
int fd_{-1};
|
||||
|
||||
void startInLoop();
|
||||
void connect();
|
||||
void connecting(int sockfd);
|
||||
int removeAndResetChannel();
|
||||
void handleWrite();
|
||||
void handleError();
|
||||
void retry(int sockfd);
|
||||
};
|
||||
|
||||
} // namespace trantor
|
||||
@@ -0,0 +1,150 @@
|
||||
#include <trantor/net/inner/BufferNode.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <algorithm>
|
||||
|
||||
namespace trantor
|
||||
{
|
||||
static const size_t kMaxSendFileBufferSize = 16 * 1024;
|
||||
class FileBufferNode : public BufferNode
|
||||
{
|
||||
public:
|
||||
FileBufferNode(const char *fileName, long long offset, long long length)
|
||||
{
|
||||
assert(offset >= 0);
|
||||
if (offset < 0)
|
||||
{
|
||||
LOG_ERROR << "offset must be greater than or equal to 0";
|
||||
isDone_ = true;
|
||||
return;
|
||||
}
|
||||
sendFd_ = open(fileName, O_RDONLY);
|
||||
|
||||
if (sendFd_ < 0)
|
||||
{
|
||||
LOG_SYSERR << fileName << " open error";
|
||||
isDone_ = true;
|
||||
return;
|
||||
}
|
||||
struct stat filestat;
|
||||
if (stat(fileName, &filestat) < 0)
|
||||
{
|
||||
LOG_SYSERR << fileName << " stat error";
|
||||
close(sendFd_);
|
||||
sendFd_ = -1;
|
||||
isDone_ = true;
|
||||
return;
|
||||
}
|
||||
if (length == 0)
|
||||
{
|
||||
if (offset >= filestat.st_size)
|
||||
{
|
||||
LOG_ERROR << "The file size is " << filestat.st_size
|
||||
<< " bytes, but the offset is " << offset
|
||||
<< " bytes and the length is " << length << " bytes";
|
||||
close(sendFd_);
|
||||
sendFd_ = -1;
|
||||
isDone_ = true;
|
||||
return;
|
||||
}
|
||||
fileBytesToSend_ = filestat.st_size - offset;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (length > filestat.st_size - offset)
|
||||
{
|
||||
LOG_ERROR << "The file size is " << filestat.st_size
|
||||
<< " bytes, but the offset is " << offset
|
||||
<< " bytes and the length is " << length << " bytes";
|
||||
close(sendFd_);
|
||||
sendFd_ = -1;
|
||||
isDone_ = true;
|
||||
return;
|
||||
}
|
||||
fileBytesToSend_ = length;
|
||||
}
|
||||
lseek(sendFd_, offset, SEEK_SET);
|
||||
}
|
||||
bool isFile() const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
int getFd() const override
|
||||
{
|
||||
return sendFd_;
|
||||
}
|
||||
void getData(const char *&data, size_t &len) override
|
||||
{
|
||||
if (msgBufferPtr_ == nullptr)
|
||||
{
|
||||
msgBufferPtr_ = std::make_unique<MsgBuffer>(
|
||||
(std::min)(kMaxSendFileBufferSize,
|
||||
static_cast<size_t>(fileBytesToSend_)));
|
||||
}
|
||||
if (msgBufferPtr_->readableBytes() == 0 && fileBytesToSend_ > 0 &&
|
||||
sendFd_ >= 0)
|
||||
{
|
||||
msgBufferPtr_->ensureWritableBytes(
|
||||
(std::min)(kMaxSendFileBufferSize,
|
||||
static_cast<size_t>(fileBytesToSend_)));
|
||||
auto n = read(sendFd_,
|
||||
msgBufferPtr_->beginWrite(),
|
||||
msgBufferPtr_->writableBytes());
|
||||
if (n > 0)
|
||||
{
|
||||
msgBufferPtr_->hasWritten(n);
|
||||
}
|
||||
else if (n == 0)
|
||||
{
|
||||
LOG_TRACE << "Read the end of file.";
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_SYSERR << "FileBufferNode::getData()";
|
||||
}
|
||||
}
|
||||
data = msgBufferPtr_->peek();
|
||||
len = msgBufferPtr_->readableBytes();
|
||||
}
|
||||
void retrieve(size_t len) override
|
||||
{
|
||||
if (msgBufferPtr_)
|
||||
{
|
||||
msgBufferPtr_->retrieve(len);
|
||||
}
|
||||
fileBytesToSend_ -= static_cast<long long>(len);
|
||||
if (fileBytesToSend_ < 0)
|
||||
fileBytesToSend_ = 0;
|
||||
}
|
||||
long long remainingBytes() const override
|
||||
{
|
||||
if (isDone_)
|
||||
return 0;
|
||||
return fileBytesToSend_;
|
||||
}
|
||||
~FileBufferNode() override
|
||||
{
|
||||
if (sendFd_ >= 0)
|
||||
{
|
||||
close(sendFd_);
|
||||
}
|
||||
}
|
||||
bool available() const override
|
||||
{
|
||||
return sendFd_ >= 0;
|
||||
}
|
||||
|
||||
private:
|
||||
int sendFd_{-1};
|
||||
long long fileBytesToSend_{0};
|
||||
std::unique_ptr<MsgBuffer> msgBufferPtr_;
|
||||
};
|
||||
|
||||
BufferNodePtr BufferNode::newFileBufferNode(const char *fileName,
|
||||
long long offset,
|
||||
long long length)
|
||||
{
|
||||
return std::make_shared<FileBufferNode>(fileName, offset, length);
|
||||
}
|
||||
} // namespace trantor
|
||||
@@ -0,0 +1,171 @@
|
||||
#include <trantor/net/inner/BufferNode.h>
|
||||
#include <windows.h>
|
||||
#include <fileapi.h>
|
||||
#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP)
|
||||
#define UWP 1
|
||||
#else
|
||||
#define UWP 0
|
||||
#endif
|
||||
|
||||
namespace trantor
|
||||
{
|
||||
static const size_t kMaxSendFileBufferSize = 16 * 1024;
|
||||
class FileBufferNode : public BufferNode
|
||||
{
|
||||
public:
|
||||
FileBufferNode(const wchar_t *fileName, long long offset, long long length)
|
||||
{
|
||||
#if UWP
|
||||
sendHandle_ = CreateFile2(
|
||||
fileName, GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, nullptr);
|
||||
|
||||
#else
|
||||
sendHandle_ = CreateFileW(fileName,
|
||||
GENERIC_READ,
|
||||
FILE_SHARE_READ,
|
||||
nullptr,
|
||||
OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL,
|
||||
nullptr);
|
||||
#endif
|
||||
if (sendHandle_ == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
LOG_SYSERR << fileName << " open error";
|
||||
isDone_ = true;
|
||||
return;
|
||||
}
|
||||
LARGE_INTEGER fileSize;
|
||||
if (!GetFileSizeEx(sendHandle_, &fileSize))
|
||||
{
|
||||
LOG_SYSERR << fileName << " stat error";
|
||||
CloseHandle(sendHandle_);
|
||||
sendHandle_ = INVALID_HANDLE_VALUE;
|
||||
isDone_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (length == 0)
|
||||
{
|
||||
if (offset >= fileSize.QuadPart)
|
||||
{
|
||||
LOG_ERROR << "The file size is " << fileSize.QuadPart
|
||||
<< " bytes, but the offset is " << offset
|
||||
<< " bytes and the length is " << length << " bytes";
|
||||
CloseHandle(sendHandle_);
|
||||
sendHandle_ = INVALID_HANDLE_VALUE;
|
||||
isDone_ = true;
|
||||
return;
|
||||
}
|
||||
fileBytesToSend_ = fileSize.QuadPart - offset;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (length + offset > fileSize.QuadPart)
|
||||
{
|
||||
LOG_ERROR << "The file size is " << fileSize.QuadPart
|
||||
<< " bytes, but the offset is " << offset
|
||||
<< " bytes and the length is " << length << " bytes";
|
||||
CloseHandle(sendHandle_);
|
||||
sendHandle_ = INVALID_HANDLE_VALUE;
|
||||
isDone_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
fileBytesToSend_ = length;
|
||||
}
|
||||
LARGE_INTEGER li;
|
||||
li.QuadPart = offset;
|
||||
if (!SetFilePointerEx(sendHandle_, li, nullptr, FILE_BEGIN))
|
||||
{
|
||||
LOG_SYSERR << fileName << " seek error";
|
||||
CloseHandle(sendHandle_);
|
||||
sendHandle_ = INVALID_HANDLE_VALUE;
|
||||
isDone_ = true;
|
||||
return;
|
||||
}
|
||||
msgBufferPtr_ = std::make_unique<MsgBuffer>(
|
||||
kMaxSendFileBufferSize < fileBytesToSend_ ? kMaxSendFileBufferSize
|
||||
: fileBytesToSend_);
|
||||
}
|
||||
|
||||
bool isFile() const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void getData(const char *&data, size_t &len) override
|
||||
{
|
||||
if (msgBufferPtr_->readableBytes() == 0 && fileBytesToSend_ > 0 &&
|
||||
sendHandle_ != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
msgBufferPtr_->ensureWritableBytes(kMaxSendFileBufferSize <
|
||||
fileBytesToSend_
|
||||
? kMaxSendFileBufferSize
|
||||
: fileBytesToSend_);
|
||||
DWORD n = 0;
|
||||
if (!ReadFile(sendHandle_,
|
||||
msgBufferPtr_->beginWrite(),
|
||||
(uint32_t)msgBufferPtr_->writableBytes(),
|
||||
&n,
|
||||
nullptr))
|
||||
{
|
||||
LOG_SYSERR << "FileBufferNode::getData()";
|
||||
}
|
||||
if (n > 0)
|
||||
{
|
||||
msgBufferPtr_->hasWritten(n);
|
||||
}
|
||||
else if (n == 0)
|
||||
{
|
||||
LOG_TRACE << "Read the end of file.";
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_SYSERR << "FileBufferNode::getData()";
|
||||
}
|
||||
}
|
||||
data = msgBufferPtr_->peek();
|
||||
len = msgBufferPtr_->readableBytes();
|
||||
}
|
||||
void retrieve(size_t len) override
|
||||
{
|
||||
msgBufferPtr_->retrieve(len);
|
||||
fileBytesToSend_ -= static_cast<long long>(len);
|
||||
if (fileBytesToSend_ < 0)
|
||||
fileBytesToSend_ = 0;
|
||||
}
|
||||
long long remainingBytes() const override
|
||||
{
|
||||
if (isDone_)
|
||||
return 0;
|
||||
return fileBytesToSend_;
|
||||
}
|
||||
~FileBufferNode() override
|
||||
{
|
||||
if (sendHandle_ != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
CloseHandle(sendHandle_);
|
||||
}
|
||||
}
|
||||
int getFd() const override
|
||||
{
|
||||
LOG_ERROR << "getFd() is not supported on Windows";
|
||||
return 0;
|
||||
}
|
||||
bool available() const override
|
||||
{
|
||||
return sendHandle_ != INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
private:
|
||||
HANDLE sendHandle_{INVALID_HANDLE_VALUE};
|
||||
long long fileBytesToSend_{0};
|
||||
std::unique_ptr<MsgBuffer> msgBufferPtr_;
|
||||
};
|
||||
BufferNodePtr BufferNode::newFileBufferNode(const wchar_t *fileName,
|
||||
long long offset,
|
||||
long long length)
|
||||
{
|
||||
return std::make_shared<FileBufferNode>(fileName, offset, length);
|
||||
}
|
||||
} // namespace trantor
|
||||
@@ -0,0 +1,36 @@
|
||||
#include <trantor/net/inner/BufferNode.h>
|
||||
namespace trantor
|
||||
{
|
||||
class MemBufferNode : public BufferNode
|
||||
{
|
||||
public:
|
||||
MemBufferNode() = default;
|
||||
|
||||
void getData(const char *&data, size_t &len) override
|
||||
{
|
||||
data = buffer_.peek();
|
||||
len = buffer_.readableBytes();
|
||||
}
|
||||
void retrieve(size_t len) override
|
||||
{
|
||||
buffer_.retrieve(len);
|
||||
}
|
||||
long long remainingBytes() const override
|
||||
{
|
||||
if (isDone_)
|
||||
return 0;
|
||||
return static_cast<long long>(buffer_.readableBytes());
|
||||
}
|
||||
void append(const char *data, size_t len) override
|
||||
{
|
||||
buffer_.append(data, len);
|
||||
}
|
||||
|
||||
private:
|
||||
trantor::MsgBuffer buffer_;
|
||||
};
|
||||
BufferNodePtr BufferNode::newMemBufferNode()
|
||||
{
|
||||
return std::make_shared<MemBufferNode>();
|
||||
}
|
||||
} // namespace trantor
|
||||
@@ -0,0 +1,100 @@
|
||||
#include "NormalResolver.h"
|
||||
#include <trantor/utils/Logger.h>
|
||||
#ifdef _WIN32
|
||||
#include <ws2tcpip.h>
|
||||
#else
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <strings.h> // memset
|
||||
#endif
|
||||
|
||||
using namespace trantor;
|
||||
|
||||
std::shared_ptr<Resolver> Resolver::newResolver(trantor::EventLoop *,
|
||||
size_t timeout)
|
||||
{
|
||||
return std::make_shared<NormalResolver>(timeout);
|
||||
}
|
||||
bool Resolver::isCAresUsed()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
void NormalResolver::resolve(const std::string &hostname,
|
||||
const Callback &callback)
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(globalMutex());
|
||||
auto iter = globalCache().find(hostname);
|
||||
if (iter != globalCache().end())
|
||||
{
|
||||
auto &cachedAddr = iter->second;
|
||||
if (timeout_ == 0 || cachedAddr.second.after(static_cast<double>(
|
||||
timeout_)) > trantor::Date::date())
|
||||
{
|
||||
callback(cachedAddr.first);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
concurrentTaskQueue().runTaskInQueue(
|
||||
[thisPtr = shared_from_this(), callback, hostname]() {
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(thisPtr->globalMutex());
|
||||
auto iter = thisPtr->globalCache().find(hostname);
|
||||
if (iter != thisPtr->globalCache().end())
|
||||
{
|
||||
auto &cachedAddr = iter->second;
|
||||
if (thisPtr->timeout_ == 0 ||
|
||||
cachedAddr.second.after(static_cast<double>(
|
||||
thisPtr->timeout_)) > trantor::Date::date())
|
||||
{
|
||||
callback(cachedAddr.first);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
struct addrinfo hints, *res = nullptr;
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = PF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
hints.ai_flags = AI_PASSIVE;
|
||||
auto error = getaddrinfo(hostname.data(), nullptr, &hints, &res);
|
||||
if (error != 0 || res == nullptr)
|
||||
{
|
||||
LOG_SYSERR << "InetAddress::resolve";
|
||||
if (res != nullptr)
|
||||
{
|
||||
freeaddrinfo(res);
|
||||
}
|
||||
callback(InetAddress{});
|
||||
return;
|
||||
}
|
||||
InetAddress inet;
|
||||
if (res->ai_family == AF_INET)
|
||||
{
|
||||
struct sockaddr_in addr;
|
||||
memset(&addr, 0, sizeof addr);
|
||||
addr = *reinterpret_cast<struct sockaddr_in *>(res->ai_addr);
|
||||
inet = InetAddress(addr);
|
||||
}
|
||||
else if (res->ai_family == AF_INET6)
|
||||
{
|
||||
struct sockaddr_in6 addr;
|
||||
memset(&addr, 0, sizeof addr);
|
||||
addr = *reinterpret_cast<struct sockaddr_in6 *>(res->ai_addr);
|
||||
inet = InetAddress(addr);
|
||||
}
|
||||
freeaddrinfo(res);
|
||||
callback(inet);
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(thisPtr->globalMutex());
|
||||
auto &addrItem = thisPtr->globalCache()[hostname];
|
||||
addrItem.first = inet;
|
||||
addrItem.second = trantor::Date::date();
|
||||
}
|
||||
return;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// Copyright 2016, Tao An. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license
|
||||
// that can be found in the License file.
|
||||
|
||||
// Author: Tao An
|
||||
|
||||
#pragma once
|
||||
#include <trantor/net/Resolver.h>
|
||||
#include <trantor/utils/NonCopyable.h>
|
||||
#include <trantor/utils/ConcurrentTaskQueue.h>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <thread>
|
||||
|
||||
namespace trantor
|
||||
{
|
||||
constexpr size_t kResolveBufferLength{16 * 1024};
|
||||
class NormalResolver : public Resolver,
|
||||
public NonCopyable,
|
||||
public std::enable_shared_from_this<NormalResolver>
|
||||
{
|
||||
public:
|
||||
virtual void resolve(const std::string& hostname,
|
||||
const Callback& callback) override;
|
||||
virtual void resolve(const std::string& hostname,
|
||||
const ResolverResultsCallback& callback) override
|
||||
{
|
||||
resolve(hostname, [callback](const trantor::InetAddress& inet) {
|
||||
callback(std::vector<trantor::InetAddress>{inet});
|
||||
});
|
||||
}
|
||||
explicit NormalResolver(size_t timeout)
|
||||
: timeout_(timeout), resolveBuffer_(kResolveBufferLength)
|
||||
{
|
||||
}
|
||||
virtual ~NormalResolver()
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
static std::unordered_map<std::string,
|
||||
std::pair<trantor::InetAddress, trantor::Date>>&
|
||||
globalCache()
|
||||
{
|
||||
static std::unordered_map<
|
||||
std::string,
|
||||
std::pair<trantor::InetAddress, trantor::Date>>
|
||||
dnsCache_;
|
||||
return dnsCache_;
|
||||
}
|
||||
static std::mutex& globalMutex()
|
||||
{
|
||||
static std::mutex mutex_;
|
||||
return mutex_;
|
||||
}
|
||||
static trantor::ConcurrentTaskQueue& concurrentTaskQueue()
|
||||
{
|
||||
static trantor::ConcurrentTaskQueue queue(
|
||||
std::thread::hardware_concurrency() < 8
|
||||
? 8
|
||||
: std::thread::hardware_concurrency(),
|
||||
"Dns Queue");
|
||||
return queue;
|
||||
}
|
||||
const size_t timeout_;
|
||||
std::vector<char> resolveBuffer_;
|
||||
};
|
||||
} // namespace trantor
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
*
|
||||
* Poller.cc
|
||||
* An Tao
|
||||
*
|
||||
* Public header file in trantor lib.
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#include "Poller.h"
|
||||
#ifdef __linux__
|
||||
#include "poller/EpollPoller.h"
|
||||
#elif defined _WIN32
|
||||
#include "Wepoll.h"
|
||||
#include "poller/EpollPoller.h"
|
||||
#elif defined __FreeBSD__ || defined __OpenBSD__ || defined __APPLE__
|
||||
#include "poller/KQueue.h"
|
||||
#else
|
||||
#include "poller/PollPoller.h"
|
||||
#endif
|
||||
using namespace trantor;
|
||||
Poller *Poller::newPoller(EventLoop *loop)
|
||||
{
|
||||
#if defined __linux__ || defined _WIN32
|
||||
return new EpollPoller(loop);
|
||||
#elif defined __FreeBSD__ || defined __OpenBSD__ || defined __APPLE__
|
||||
return new KQueue(loop);
|
||||
#else
|
||||
return new PollPoller(loop);
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
*
|
||||
* Poller.h
|
||||
* An Tao
|
||||
*
|
||||
* Public header file in trantor lib.
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "NonCopyable.h"
|
||||
#include "EventLoop.h"
|
||||
|
||||
#include <memory>
|
||||
#include <map>
|
||||
|
||||
namespace trantor
|
||||
{
|
||||
class Channel;
|
||||
#ifdef _WIN32
|
||||
using EventCallback = std::function<void(uint64_t)>;
|
||||
#endif
|
||||
class Poller : NonCopyable
|
||||
{
|
||||
public:
|
||||
explicit Poller(EventLoop *loop) : ownerLoop_(loop){};
|
||||
virtual ~Poller()
|
||||
{
|
||||
}
|
||||
void assertInLoopThread()
|
||||
{
|
||||
ownerLoop_->assertInLoopThread();
|
||||
}
|
||||
virtual void poll(int timeoutMs, ChannelList *activeChannels) = 0;
|
||||
virtual void updateChannel(Channel *channel) = 0;
|
||||
virtual void removeChannel(Channel *channel) = 0;
|
||||
#ifdef _WIN32
|
||||
virtual void postEvent(uint64_t event) = 0;
|
||||
virtual void setEventCallback(const EventCallback &cb) = 0;
|
||||
#endif
|
||||
virtual void resetAfterFork()
|
||||
{
|
||||
}
|
||||
static Poller *newPoller(EventLoop *loop);
|
||||
|
||||
private:
|
||||
EventLoop *ownerLoop_;
|
||||
};
|
||||
} // namespace trantor
|
||||
+252
@@ -0,0 +1,252 @@
|
||||
/**
|
||||
*
|
||||
* Socket.cc
|
||||
* An Tao
|
||||
*
|
||||
* Public header file in trantor lib.
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#include <trantor/utils/Logger.h>
|
||||
#include "Socket.h"
|
||||
#include <assert.h>
|
||||
#include <sys/types.h>
|
||||
#ifdef _WIN32
|
||||
#include <ws2tcpip.h>
|
||||
#else
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/tcp.h>
|
||||
#endif
|
||||
|
||||
using namespace trantor;
|
||||
|
||||
bool Socket::isSelfConnect(int sockfd)
|
||||
{
|
||||
struct sockaddr_in6 localaddr = getLocalAddr(sockfd);
|
||||
struct sockaddr_in6 peeraddr = getPeerAddr(sockfd);
|
||||
if (localaddr.sin6_family == AF_INET)
|
||||
{
|
||||
const struct sockaddr_in *laddr4 =
|
||||
reinterpret_cast<struct sockaddr_in *>(&localaddr);
|
||||
const struct sockaddr_in *raddr4 =
|
||||
reinterpret_cast<struct sockaddr_in *>(&peeraddr);
|
||||
return laddr4->sin_port == raddr4->sin_port &&
|
||||
laddr4->sin_addr.s_addr == raddr4->sin_addr.s_addr;
|
||||
}
|
||||
else if (localaddr.sin6_family == AF_INET6)
|
||||
{
|
||||
return localaddr.sin6_port == peeraddr.sin6_port &&
|
||||
memcmp(&localaddr.sin6_addr,
|
||||
&peeraddr.sin6_addr,
|
||||
sizeof localaddr.sin6_addr) == 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void Socket::bindAddress(const InetAddress &localaddr)
|
||||
{
|
||||
assert(sockFd_ > 0);
|
||||
int ret;
|
||||
if (localaddr.isIpV6())
|
||||
ret = ::bind(sockFd_, localaddr.getSockAddr(), sizeof(sockaddr_in6));
|
||||
else
|
||||
ret = ::bind(sockFd_, localaddr.getSockAddr(), sizeof(sockaddr_in));
|
||||
|
||||
if (ret == 0)
|
||||
return;
|
||||
else
|
||||
{
|
||||
LOG_SYSERR << ", Bind address failed at " << localaddr.toIpPort();
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
void Socket::listen()
|
||||
{
|
||||
assert(sockFd_ > 0);
|
||||
int ret = ::listen(sockFd_, SOMAXCONN);
|
||||
if (ret < 0)
|
||||
{
|
||||
LOG_SYSERR << "listen failed";
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
int Socket::accept(InetAddress *peeraddr)
|
||||
{
|
||||
struct sockaddr_in6 addr6;
|
||||
memset(&addr6, 0, sizeof(addr6));
|
||||
socklen_t size = sizeof(addr6);
|
||||
#ifdef __linux__
|
||||
int connfd = ::accept4(sockFd_,
|
||||
(struct sockaddr *)&addr6,
|
||||
&size,
|
||||
SOCK_NONBLOCK | SOCK_CLOEXEC);
|
||||
#else
|
||||
int connfd =
|
||||
static_cast<int>(::accept(sockFd_, (struct sockaddr *)&addr6, &size));
|
||||
setNonBlockAndCloseOnExec(connfd);
|
||||
#endif
|
||||
if (connfd >= 0)
|
||||
{
|
||||
peeraddr->setSockAddrInet6(addr6);
|
||||
}
|
||||
return connfd;
|
||||
}
|
||||
void Socket::closeWrite()
|
||||
{
|
||||
#ifndef _WIN32
|
||||
if (::shutdown(sockFd_, SHUT_WR) < 0)
|
||||
#else
|
||||
if (::shutdown(sockFd_, SD_SEND) < 0)
|
||||
#endif
|
||||
{
|
||||
LOG_SYSERR << "sockets::shutdownWrite";
|
||||
}
|
||||
}
|
||||
int Socket::read(char *buffer, uint64_t len)
|
||||
{
|
||||
#ifndef _WIN32
|
||||
return ::read(sockFd_, buffer, len);
|
||||
#else
|
||||
return recv(sockFd_, buffer, static_cast<int>(len), 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
struct sockaddr_in6 Socket::getLocalAddr(int sockfd)
|
||||
{
|
||||
struct sockaddr_in6 localaddr;
|
||||
memset(&localaddr, 0, sizeof(localaddr));
|
||||
socklen_t addrlen = static_cast<socklen_t>(sizeof localaddr);
|
||||
if (::getsockname(sockfd,
|
||||
static_cast<struct sockaddr *>((void *)(&localaddr)),
|
||||
&addrlen) < 0)
|
||||
{
|
||||
LOG_SYSERR << "sockets::getLocalAddr";
|
||||
}
|
||||
return localaddr;
|
||||
}
|
||||
|
||||
struct sockaddr_in6 Socket::getPeerAddr(int sockfd)
|
||||
{
|
||||
struct sockaddr_in6 peeraddr;
|
||||
memset(&peeraddr, 0, sizeof(peeraddr));
|
||||
socklen_t addrlen = static_cast<socklen_t>(sizeof peeraddr);
|
||||
if (::getpeername(sockfd,
|
||||
static_cast<struct sockaddr *>((void *)(&peeraddr)),
|
||||
&addrlen) < 0)
|
||||
{
|
||||
LOG_SYSERR << "sockets::getPeerAddr";
|
||||
}
|
||||
return peeraddr;
|
||||
}
|
||||
|
||||
void Socket::setTcpNoDelay(bool on)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
char optval = on ? 1 : 0;
|
||||
#else
|
||||
int optval = on ? 1 : 0;
|
||||
#endif
|
||||
::setsockopt(sockFd_,
|
||||
IPPROTO_TCP,
|
||||
TCP_NODELAY,
|
||||
&optval,
|
||||
static_cast<socklen_t>(sizeof optval));
|
||||
// TODO CHECK
|
||||
}
|
||||
|
||||
void Socket::setReuseAddr(bool on)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
char optval = on ? 1 : 0;
|
||||
#else
|
||||
int optval = on ? 1 : 0;
|
||||
#endif
|
||||
::setsockopt(sockFd_,
|
||||
SOL_SOCKET,
|
||||
SO_REUSEADDR,
|
||||
&optval,
|
||||
static_cast<socklen_t>(sizeof optval));
|
||||
// TODO CHECK
|
||||
}
|
||||
|
||||
void Socket::setReusePort(bool on)
|
||||
{
|
||||
#ifdef SO_REUSEPORT
|
||||
#ifdef _WIN32
|
||||
char optval = on ? 1 : 0;
|
||||
#else
|
||||
int optval = on ? 1 : 0;
|
||||
#endif
|
||||
int ret = ::setsockopt(sockFd_,
|
||||
SOL_SOCKET,
|
||||
SO_REUSEPORT,
|
||||
&optval,
|
||||
static_cast<socklen_t>(sizeof optval));
|
||||
if (ret < 0 && on)
|
||||
{
|
||||
LOG_SYSERR << "SO_REUSEPORT failed.";
|
||||
}
|
||||
#else
|
||||
if (on)
|
||||
{
|
||||
LOG_ERROR << "SO_REUSEPORT is not supported.";
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void Socket::setKeepAlive(bool on)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
char optval = on ? 1 : 0;
|
||||
#else
|
||||
int optval = on ? 1 : 0;
|
||||
#endif
|
||||
::setsockopt(sockFd_,
|
||||
SOL_SOCKET,
|
||||
SO_KEEPALIVE,
|
||||
&optval,
|
||||
static_cast<socklen_t>(sizeof optval));
|
||||
// TODO CHECK
|
||||
}
|
||||
|
||||
int Socket::getSocketError()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
char optval;
|
||||
#else
|
||||
int optval;
|
||||
#endif
|
||||
socklen_t optlen = static_cast<socklen_t>(sizeof optval);
|
||||
|
||||
if (::getsockopt(sockFd_, SOL_SOCKET, SO_ERROR, &optval, &optlen) < 0)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return ::WSAGetLastError();
|
||||
#else
|
||||
return errno;
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
return optval;
|
||||
}
|
||||
}
|
||||
|
||||
Socket::~Socket()
|
||||
{
|
||||
LOG_TRACE << "Socket deconstructed:" << sockFd_;
|
||||
if (sockFd_ >= 0)
|
||||
#ifndef _WIN32
|
||||
close(sockFd_);
|
||||
#else
|
||||
closesocket(sockFd_);
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
*
|
||||
* Socket.h
|
||||
* An Tao
|
||||
*
|
||||
* Public header file in trantor lib.
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <trantor/utils/NonCopyable.h>
|
||||
#include <trantor/net/InetAddress.h>
|
||||
#include <trantor/utils/Logger.h>
|
||||
#include <string>
|
||||
#ifndef _WIN32
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include <fcntl.h>
|
||||
|
||||
namespace trantor
|
||||
{
|
||||
class Socket : NonCopyable
|
||||
{
|
||||
public:
|
||||
static int createNonblockingSocketOrDie(int family)
|
||||
{
|
||||
#ifdef __linux__
|
||||
int sock = ::socket(family,
|
||||
SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC,
|
||||
IPPROTO_TCP);
|
||||
#else
|
||||
int sock = static_cast<int>(::socket(family, SOCK_STREAM, IPPROTO_TCP));
|
||||
setNonBlockAndCloseOnExec(sock);
|
||||
#endif
|
||||
if (sock < 0)
|
||||
{
|
||||
LOG_SYSERR << "sockets::createNonblockingOrDie";
|
||||
exit(1);
|
||||
}
|
||||
LOG_TRACE << "sock=" << sock;
|
||||
return sock;
|
||||
}
|
||||
|
||||
static int getSocketError(int sockfd)
|
||||
{
|
||||
int optval;
|
||||
socklen_t optlen = static_cast<socklen_t>(sizeof optval);
|
||||
#ifdef _WIN32
|
||||
if (::getsockopt(
|
||||
sockfd, SOL_SOCKET, SO_ERROR, (char *)&optval, &optlen) < 0)
|
||||
#else
|
||||
if (::getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &optval, &optlen) < 0)
|
||||
#endif
|
||||
{
|
||||
return errno;
|
||||
}
|
||||
else
|
||||
{
|
||||
return optval;
|
||||
}
|
||||
}
|
||||
|
||||
static int connect(int sockfd, const InetAddress &addr)
|
||||
{
|
||||
if (addr.isIpV6())
|
||||
return ::connect(sockfd,
|
||||
addr.getSockAddr(),
|
||||
static_cast<socklen_t>(
|
||||
sizeof(struct sockaddr_in6)));
|
||||
else
|
||||
return ::connect(sockfd,
|
||||
addr.getSockAddr(),
|
||||
static_cast<socklen_t>(
|
||||
sizeof(struct sockaddr_in)));
|
||||
}
|
||||
|
||||
static bool isSelfConnect(int sockfd);
|
||||
|
||||
explicit Socket(int sockfd) : sockFd_(sockfd)
|
||||
{
|
||||
}
|
||||
~Socket();
|
||||
/// abort if address in use
|
||||
void bindAddress(const InetAddress &localaddr);
|
||||
/// abort if address in use
|
||||
void listen();
|
||||
int accept(InetAddress *peeraddr);
|
||||
void closeWrite();
|
||||
int read(char *buffer, uint64_t len);
|
||||
int fd()
|
||||
{
|
||||
return sockFd_;
|
||||
}
|
||||
static struct sockaddr_in6 getLocalAddr(int sockfd);
|
||||
static struct sockaddr_in6 getPeerAddr(int sockfd);
|
||||
|
||||
///
|
||||
/// Enable/disable TCP_NODELAY (disable/enable Nagle's algorithm).
|
||||
///
|
||||
void setTcpNoDelay(bool on);
|
||||
|
||||
///
|
||||
/// Enable/disable SO_REUSEADDR
|
||||
///
|
||||
void setReuseAddr(bool on);
|
||||
|
||||
///
|
||||
/// Enable/disable SO_REUSEPORT
|
||||
///
|
||||
void setReusePort(bool on);
|
||||
|
||||
///
|
||||
/// Enable/disable SO_KEEPALIVE
|
||||
///
|
||||
void setKeepAlive(bool on);
|
||||
int getSocketError();
|
||||
|
||||
protected:
|
||||
int sockFd_;
|
||||
|
||||
public:
|
||||
// taken from muduo
|
||||
static void setNonBlockAndCloseOnExec(int sockfd)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
// TODO how to set FD_CLOEXEC on windows? is it necessary?
|
||||
u_long arg = 1;
|
||||
auto ret = ioctlsocket(sockfd, (long)FIONBIO, &arg);
|
||||
if (ret)
|
||||
{
|
||||
LOG_ERROR << "ioctlsocket error";
|
||||
}
|
||||
#else
|
||||
// non-block
|
||||
int flags = ::fcntl(sockfd, F_GETFL, 0);
|
||||
flags |= O_NONBLOCK;
|
||||
int ret = ::fcntl(sockfd, F_SETFL, flags);
|
||||
// TODO check
|
||||
|
||||
// close-on-exec
|
||||
flags = ::fcntl(sockfd, F_GETFD, 0);
|
||||
flags |= FD_CLOEXEC;
|
||||
ret = ::fcntl(sockfd, F_SETFD, flags);
|
||||
// TODO check
|
||||
|
||||
(void)ret;
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace trantor
|
||||
@@ -0,0 +1,67 @@
|
||||
#include <trantor/net/inner/BufferNode.h>
|
||||
namespace trantor
|
||||
{
|
||||
static const size_t kMaxSendFileBufferSize = 16 * 1024;
|
||||
class StreamBufferNode : public BufferNode
|
||||
{
|
||||
public:
|
||||
StreamBufferNode(std::function<std::size_t(char *, std::size_t)> &&callback)
|
||||
: streamCallback_(std::move(callback))
|
||||
{
|
||||
}
|
||||
bool isStream() const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
void getData(const char *&data, size_t &len) override
|
||||
{
|
||||
if (msgBuffer_.readableBytes() == 0)
|
||||
{
|
||||
msgBuffer_.ensureWritableBytes(kMaxSendFileBufferSize);
|
||||
auto n = streamCallback_(msgBuffer_.beginWrite(),
|
||||
msgBuffer_.writableBytes());
|
||||
if (n > 0)
|
||||
{
|
||||
msgBuffer_.hasWritten(n);
|
||||
}
|
||||
else
|
||||
{
|
||||
isDone_ = true;
|
||||
}
|
||||
}
|
||||
data = msgBuffer_.peek();
|
||||
len = msgBuffer_.readableBytes();
|
||||
}
|
||||
void retrieve(size_t len) override
|
||||
{
|
||||
msgBuffer_.retrieve(len);
|
||||
#ifndef NDEBUG
|
||||
dataWritten_ += len;
|
||||
LOG_TRACE << "send stream in loop: bytes written: " << dataWritten_
|
||||
<< " / total bytes written: " << dataWritten_;
|
||||
#endif
|
||||
}
|
||||
long long remainingBytes() const override
|
||||
{
|
||||
if (isDone_)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
~StreamBufferNode() override
|
||||
{
|
||||
if (streamCallback_)
|
||||
streamCallback_(nullptr, 0); // cleanup callback internals
|
||||
}
|
||||
|
||||
private:
|
||||
std::function<std::size_t(char *, std::size_t)> streamCallback_;
|
||||
#ifndef NDEBUG // defined by CMake for release build
|
||||
std::size_t dataWritten_{0};
|
||||
#endif
|
||||
MsgBuffer msgBuffer_;
|
||||
};
|
||||
BufferNodePtr BufferNode::newStreamBufferNode(StreamCallback &&callback)
|
||||
{
|
||||
return std::make_shared<StreamBufferNode>(std::move(callback));
|
||||
}
|
||||
} // namespace trantor
|
||||
@@ -0,0 +1,173 @@
|
||||
#pragma once
|
||||
|
||||
#include <trantor/utils/NonCopyable.h>
|
||||
#include <trantor/utils/MsgBuffer.h>
|
||||
#include <trantor/utils/Logger.h>
|
||||
#include <trantor/net/callbacks.h>
|
||||
#include <trantor/net/TcpConnection.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace trantor
|
||||
{
|
||||
struct TLSProvider
|
||||
{
|
||||
TLSProvider(TcpConnection* conn, TLSPolicyPtr policy, SSLContextPtr ctx)
|
||||
: conn_(conn),
|
||||
policyPtr_(std::move(policy)),
|
||||
contextPtr_(std::move(ctx)),
|
||||
loop_(conn_->getLoop())
|
||||
{
|
||||
}
|
||||
virtual ~TLSProvider() = default;
|
||||
using WriteCallback = ssize_t (*)(TcpConnection*,
|
||||
const void* data,
|
||||
size_t len);
|
||||
using ErrorCallback = void (*)(TcpConnection*, SSLError err);
|
||||
using HandshakeCallback = void (*)(TcpConnection*);
|
||||
using MessageCallback = void (*)(TcpConnection*, MsgBuffer* buffer);
|
||||
using CloseCallback = void (*)(TcpConnection*);
|
||||
|
||||
/**
|
||||
* @brief Sends data to the TLSProvider to process handshake and decrypt
|
||||
* data
|
||||
*/
|
||||
virtual void recvData(MsgBuffer* buffer) = 0;
|
||||
|
||||
/**
|
||||
* @brief Encrypt and send data via TLS
|
||||
* @return the number of bytes sent, or -1 on error, or 0 if EAGAIN or
|
||||
* EWOULDBLOCK.
|
||||
*/
|
||||
virtual ssize_t sendData(const char* ptr, size_t size) = 0;
|
||||
|
||||
/**
|
||||
* @brief Close the TLS connection
|
||||
*/
|
||||
virtual void close() = 0;
|
||||
|
||||
virtual void startEncryption() = 0;
|
||||
|
||||
bool sendBufferedData()
|
||||
{
|
||||
if (writeBuffer_.readableBytes() == 0)
|
||||
return true;
|
||||
|
||||
auto n = writeCallback_(conn_,
|
||||
writeBuffer_.peek(),
|
||||
writeBuffer_.readableBytes());
|
||||
if (n == -1)
|
||||
{
|
||||
LOG_ERROR << "WTF! Failed to send buffered data. Error: "
|
||||
<< strerror(errno);
|
||||
return false;
|
||||
}
|
||||
else if ((size_t)n != writeBuffer_.readableBytes())
|
||||
{
|
||||
writeBuffer_.retrieve(n);
|
||||
return false;
|
||||
}
|
||||
|
||||
writeBuffer_.retrieveAll();
|
||||
return true;
|
||||
}
|
||||
|
||||
MsgBuffer& getBufferedData()
|
||||
{
|
||||
return writeBuffer_;
|
||||
}
|
||||
|
||||
void appendToWriteBuffer(const char* ptr, size_t size)
|
||||
{
|
||||
writeBuffer_.ensureWritableBytes(size);
|
||||
writeBuffer_.append(ptr, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set a function to be called when the TLSProvider wants to send
|
||||
* data
|
||||
*
|
||||
* @note The caller MUST guarantee that it will not make the TLSProvider
|
||||
* send data after caller is destroyed. std::function used due to
|
||||
* performance reasons.
|
||||
*/
|
||||
void setWriteCallback(WriteCallback cb)
|
||||
{
|
||||
writeCallback_ = cb;
|
||||
}
|
||||
|
||||
void setErrorCallback(ErrorCallback cb)
|
||||
{
|
||||
errorCallback_ = cb;
|
||||
}
|
||||
|
||||
void setHandshakeCallback(HandshakeCallback cb)
|
||||
{
|
||||
handshakeCallback_ = cb;
|
||||
}
|
||||
|
||||
void setMessageCallback(MessageCallback cb)
|
||||
{
|
||||
messageCallback_ = cb;
|
||||
}
|
||||
void setCloseCallback(CloseCallback cb)
|
||||
{
|
||||
closeCallback_ = cb;
|
||||
}
|
||||
|
||||
MsgBuffer& getRecvBuffer()
|
||||
{
|
||||
return recvBuffer_;
|
||||
}
|
||||
|
||||
const CertificatePtr& peerCertificate() const
|
||||
{
|
||||
return peerCertificate_;
|
||||
}
|
||||
|
||||
const std::string& applicationProtocol() const
|
||||
{
|
||||
return applicationProtocol_;
|
||||
}
|
||||
|
||||
const std::string& sniName() const
|
||||
{
|
||||
return sniName_;
|
||||
}
|
||||
|
||||
protected:
|
||||
void setPeerCertificate(CertificatePtr cert)
|
||||
{
|
||||
peerCertificate_ = std::move(cert);
|
||||
}
|
||||
|
||||
void setApplicationProtocol(std::string protocol)
|
||||
{
|
||||
applicationProtocol_ = std::move(protocol);
|
||||
}
|
||||
|
||||
void setSniName(std::string name)
|
||||
{
|
||||
sniName_ = std::move(name);
|
||||
}
|
||||
|
||||
WriteCallback writeCallback_ = nullptr;
|
||||
ErrorCallback errorCallback_ = nullptr;
|
||||
HandshakeCallback handshakeCallback_ = nullptr;
|
||||
MessageCallback messageCallback_ = nullptr;
|
||||
CloseCallback closeCallback_ = nullptr;
|
||||
TcpConnection* conn_ = nullptr;
|
||||
const TLSPolicyPtr policyPtr_;
|
||||
const SSLContextPtr contextPtr_;
|
||||
MsgBuffer recvBuffer_;
|
||||
EventLoop* loop_ = nullptr;
|
||||
CertificatePtr peerCertificate_;
|
||||
std::string applicationProtocol_;
|
||||
std::string sniName_;
|
||||
MsgBuffer writeBuffer_;
|
||||
};
|
||||
|
||||
std::shared_ptr<TLSProvider> newTLSProvider(TcpConnection* conn,
|
||||
TLSPolicyPtr policy,
|
||||
SSLContextPtr ctx);
|
||||
} // namespace trantor
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,276 @@
|
||||
/**
|
||||
*
|
||||
* @file TcpConnectionImpl.h
|
||||
* @author An Tao
|
||||
*
|
||||
* Public header file in trantor lib.
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <trantor/net/TcpConnection.h>
|
||||
#include <trantor/utils/TimingWheel.h>
|
||||
#include <trantor/net/inner/TLSProvider.h>
|
||||
#include <trantor/net/inner/BufferNode.h>
|
||||
#include <list>
|
||||
#include <mutex>
|
||||
#ifndef _WIN32
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include <thread>
|
||||
#include <array>
|
||||
|
||||
namespace trantor
|
||||
{
|
||||
class Channel;
|
||||
class Socket;
|
||||
class TcpServer;
|
||||
class TcpConnectionImpl : public TcpConnection,
|
||||
public NonCopyable,
|
||||
public std::enable_shared_from_this<TcpConnectionImpl>
|
||||
{
|
||||
friend class TcpServer;
|
||||
friend class TcpClient;
|
||||
|
||||
public:
|
||||
class KickoffEntry
|
||||
{
|
||||
public:
|
||||
explicit KickoffEntry(const std::weak_ptr<TcpConnection> &conn)
|
||||
: conn_(conn)
|
||||
{
|
||||
}
|
||||
void reset()
|
||||
{
|
||||
conn_.reset();
|
||||
}
|
||||
~KickoffEntry()
|
||||
{
|
||||
auto conn = conn_.lock();
|
||||
if (conn)
|
||||
{
|
||||
conn->forceClose();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::weak_ptr<TcpConnection> conn_;
|
||||
};
|
||||
|
||||
TcpConnectionImpl(EventLoop *loop,
|
||||
int socketfd,
|
||||
const InetAddress &localAddr,
|
||||
const InetAddress &peerAddr,
|
||||
TLSPolicyPtr policy = nullptr,
|
||||
SSLContextPtr ctx = nullptr);
|
||||
~TcpConnectionImpl() override;
|
||||
void send(const char *msg, size_t len) override;
|
||||
void send(const void *msg, size_t len) override;
|
||||
void send(const std::string &msg) override;
|
||||
void send(std::string &&msg) override;
|
||||
void send(const MsgBuffer &buffer) override;
|
||||
void send(MsgBuffer &&buffer) override;
|
||||
void send(const std::shared_ptr<std::string> &msgPtr) override;
|
||||
void send(const std::shared_ptr<MsgBuffer> &msgPtr) override;
|
||||
void sendFile(const char *fileName,
|
||||
long long offset,
|
||||
long long length) override;
|
||||
void sendFile(const wchar_t *fileName,
|
||||
long long offset,
|
||||
long long length) override;
|
||||
void sendStream(
|
||||
std::function<std::size_t(char *, std::size_t)> callback) override;
|
||||
|
||||
const InetAddress &localAddr() const override
|
||||
{
|
||||
return localAddr_;
|
||||
}
|
||||
const InetAddress &peerAddr() const override
|
||||
{
|
||||
return peerAddr_;
|
||||
}
|
||||
|
||||
bool connected() const override
|
||||
{
|
||||
return status_ == ConnStatus::Connected;
|
||||
}
|
||||
bool disconnected() const override
|
||||
{
|
||||
return status_ == ConnStatus::Disconnected;
|
||||
}
|
||||
|
||||
// virtual MsgBuffer* getSendBuffer() override{ return &writeBuffer_;}
|
||||
// virtual MsgBuffer *getRecvBuffer() override
|
||||
// {
|
||||
// return &readBuffer_;
|
||||
// }
|
||||
// set callbacks
|
||||
void setHighWaterMarkCallback(const HighWaterMarkCallback &cb,
|
||||
size_t markLen) override
|
||||
{
|
||||
highWaterMarkCallback_ = cb;
|
||||
highWaterMarkLen_ = markLen;
|
||||
}
|
||||
|
||||
void keepAlive() override
|
||||
{
|
||||
idleTimeout_ = 0;
|
||||
auto entry = kickoffEntry_.lock();
|
||||
if (entry)
|
||||
{
|
||||
entry->reset();
|
||||
}
|
||||
}
|
||||
bool isKeepAlive() override
|
||||
{
|
||||
return idleTimeout_ == 0;
|
||||
}
|
||||
void setTcpNoDelay(bool on) override;
|
||||
void shutdown() override;
|
||||
void forceClose() override;
|
||||
EventLoop *getLoop() override
|
||||
{
|
||||
return loop_;
|
||||
}
|
||||
|
||||
size_t bytesSent() const override
|
||||
{
|
||||
return bytesSent_;
|
||||
}
|
||||
size_t bytesReceived() const override
|
||||
{
|
||||
return bytesReceived_;
|
||||
}
|
||||
|
||||
bool isSSLConnection() const override
|
||||
{
|
||||
return tlsProviderPtr_ != nullptr;
|
||||
}
|
||||
void connectEstablished() override;
|
||||
void connectDestroyed() override;
|
||||
|
||||
MsgBuffer *getRecvBuffer() override
|
||||
{
|
||||
if (tlsProviderPtr_)
|
||||
return &tlsProviderPtr_->getRecvBuffer();
|
||||
return &readBuffer_;
|
||||
}
|
||||
|
||||
std::string applicationProtocol() const override
|
||||
{
|
||||
if (tlsProviderPtr_)
|
||||
return tlsProviderPtr_->applicationProtocol();
|
||||
return "";
|
||||
}
|
||||
|
||||
CertificatePtr peerCertificate() const override
|
||||
{
|
||||
if (tlsProviderPtr_)
|
||||
return tlsProviderPtr_->peerCertificate();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string sniName() const override
|
||||
{
|
||||
if (tlsProviderPtr_)
|
||||
return tlsProviderPtr_->sniName();
|
||||
return "";
|
||||
}
|
||||
|
||||
void startEncryption(
|
||||
TLSPolicyPtr policy,
|
||||
bool isServer,
|
||||
std::function<void(const TcpConnectionPtr &)> upgradeCallback) override;
|
||||
AsyncStreamPtr sendAsyncStream(bool disableKickoff) override;
|
||||
|
||||
void enableKickingOff(
|
||||
size_t timeout,
|
||||
const std::shared_ptr<TimingWheel> &timingWheel) override
|
||||
{
|
||||
assert(timingWheel);
|
||||
assert(timingWheel->getLoop() == loop_);
|
||||
assert(timeout > 0);
|
||||
auto entry = std::make_shared<KickoffEntry>(shared_from_this());
|
||||
kickoffEntry_ = entry;
|
||||
timingWheelWeakPtr_ = timingWheel;
|
||||
idleTimeout_ = timeout;
|
||||
timingWheel->insertEntry(timeout, entry);
|
||||
}
|
||||
|
||||
private:
|
||||
/// Internal use only.
|
||||
|
||||
std::weak_ptr<KickoffEntry> kickoffEntry_;
|
||||
std::weak_ptr<TimingWheel> timingWheelWeakPtr_;
|
||||
size_t idleTimeout_{0};
|
||||
size_t idleTimeoutBackup_{0};
|
||||
Date lastTimingWheelUpdateTime_;
|
||||
void extendLife();
|
||||
void sendFile(BufferNodePtr &&fileNode);
|
||||
|
||||
protected:
|
||||
enum class ConnStatus
|
||||
{
|
||||
Disconnected,
|
||||
Connecting,
|
||||
Connected,
|
||||
Disconnecting
|
||||
};
|
||||
EventLoop *loop_;
|
||||
std::unique_ptr<Channel> ioChannelPtr_;
|
||||
std::unique_ptr<Socket> socketPtr_;
|
||||
MsgBuffer readBuffer_;
|
||||
std::list<BufferNodePtr> writeBufferList_;
|
||||
void readCallback();
|
||||
void writeCallback();
|
||||
InetAddress localAddr_, peerAddr_;
|
||||
ConnStatus status_{ConnStatus::Connecting};
|
||||
void handleClose();
|
||||
void handleError();
|
||||
// virtual void sendInLoop(const std::string &msg);
|
||||
void sendAsyncDataInLoop(const BufferNodePtr &node,
|
||||
const char *data,
|
||||
size_t len);
|
||||
// -1: error, 0: EAGAIN, >0: bytes sent
|
||||
ssize_t sendNodeInLoop(const BufferNodePtr &node);
|
||||
#ifndef _WIN32
|
||||
void sendInLoop(const void *buffer, size_t length);
|
||||
ssize_t writeRaw(const void *buffer, size_t length);
|
||||
ssize_t writeInLoop(const void *buffer, size_t length);
|
||||
#else
|
||||
void sendInLoop(const char *buffer, size_t length);
|
||||
// -1: error, 0: EAGAIN, >0: bytes sent
|
||||
ssize_t writeRaw(const char *buffer, size_t length);
|
||||
// -1: error, 0: EAGAIN, >0: bytes sent
|
||||
ssize_t writeInLoop(const char *buffer, size_t length);
|
||||
#endif
|
||||
size_t highWaterMarkLen_{0};
|
||||
std::string name_;
|
||||
|
||||
size_t bytesSent_{0};
|
||||
size_t bytesReceived_{0};
|
||||
|
||||
// std::unique_ptr<std::vector<char>> fileBufferPtr_;
|
||||
std::shared_ptr<TLSProvider> tlsProviderPtr_;
|
||||
std::function<void(const TcpConnectionPtr &)> upgradeCallback_;
|
||||
|
||||
bool closeOnEmpty_{false};
|
||||
|
||||
static void onSslError(TcpConnection *self, SSLError err);
|
||||
static void onHandshakeFinished(TcpConnection *self);
|
||||
static void onSslMessage(TcpConnection *self, MsgBuffer *buffer);
|
||||
static ssize_t onSslWrite(TcpConnection *self,
|
||||
const void *data,
|
||||
size_t len);
|
||||
static void onSslCloseAlert(TcpConnection *self);
|
||||
};
|
||||
|
||||
using TcpConnectionImplPtr = std::shared_ptr<TcpConnectionImpl>;
|
||||
|
||||
} // namespace trantor
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
*
|
||||
* Timer.cc
|
||||
* An Tao
|
||||
*
|
||||
* Public header file in trantor lib.
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#include "Timer.h"
|
||||
#include <trantor/utils/Logger.h>
|
||||
#include <trantor/net/EventLoop.h>
|
||||
|
||||
namespace trantor
|
||||
{
|
||||
std::atomic<TimerId> Timer::timersCreated_ = ATOMIC_VAR_INIT(InvalidTimerId);
|
||||
Timer::Timer(const TimerCallback &cb,
|
||||
const TimePoint &when,
|
||||
const TimeInterval &interval)
|
||||
: callback_(cb),
|
||||
when_(when),
|
||||
interval_(interval),
|
||||
repeat_(interval.count() > 0),
|
||||
id_(++timersCreated_)
|
||||
{
|
||||
}
|
||||
Timer::Timer(TimerCallback &&cb,
|
||||
const TimePoint &when,
|
||||
const TimeInterval &interval)
|
||||
: callback_(std::move(cb)),
|
||||
when_(when),
|
||||
interval_(interval),
|
||||
repeat_(interval.count() > 0),
|
||||
id_(++timersCreated_)
|
||||
{
|
||||
// LOG_TRACE<<"Timer move contrustor";
|
||||
}
|
||||
void Timer::run() const
|
||||
{
|
||||
callback_();
|
||||
}
|
||||
void Timer::restart(const TimePoint &now)
|
||||
{
|
||||
if (repeat_)
|
||||
{
|
||||
when_ = now + interval_;
|
||||
}
|
||||
else
|
||||
when_ = std::chrono::steady_clock::now();
|
||||
}
|
||||
bool Timer::operator<(const Timer &t) const
|
||||
{
|
||||
return when_ < t.when_;
|
||||
}
|
||||
bool Timer::operator>(const Timer &t) const
|
||||
{
|
||||
return when_ > t.when_;
|
||||
}
|
||||
} // namespace trantor
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
*
|
||||
* Timer.h
|
||||
* An Tao
|
||||
*
|
||||
* Public header file in trantor lib.
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <trantor/utils/NonCopyable.h>
|
||||
#include <trantor/net/callbacks.h>
|
||||
#include <functional>
|
||||
#include <atomic>
|
||||
#include <iostream>
|
||||
#include <chrono>
|
||||
|
||||
namespace trantor
|
||||
{
|
||||
using TimerId = uint64_t;
|
||||
using TimePoint = std::chrono::steady_clock::time_point;
|
||||
using TimeInterval = std::chrono::microseconds;
|
||||
class Timer : public NonCopyable
|
||||
{
|
||||
public:
|
||||
Timer(const TimerCallback &cb,
|
||||
const TimePoint &when,
|
||||
const TimeInterval &interval);
|
||||
Timer(TimerCallback &&cb,
|
||||
const TimePoint &when,
|
||||
const TimeInterval &interval);
|
||||
~Timer()
|
||||
{
|
||||
// std::cout<<"Timer unconstract!"<<std::endl;
|
||||
}
|
||||
void run() const;
|
||||
void restart(const TimePoint &now);
|
||||
bool operator<(const Timer &t) const;
|
||||
bool operator>(const Timer &t) const;
|
||||
const TimePoint &when() const
|
||||
{
|
||||
return when_;
|
||||
}
|
||||
bool isRepeat()
|
||||
{
|
||||
return repeat_;
|
||||
}
|
||||
TimerId id()
|
||||
{
|
||||
return id_;
|
||||
}
|
||||
|
||||
private:
|
||||
TimerCallback callback_;
|
||||
TimePoint when_;
|
||||
const TimeInterval interval_;
|
||||
const bool repeat_;
|
||||
const TimerId id_;
|
||||
static std::atomic<TimerId> timersCreated_;
|
||||
};
|
||||
|
||||
} // namespace trantor
|
||||
@@ -0,0 +1,294 @@
|
||||
/**
|
||||
*
|
||||
* @file TimerQueue.cc
|
||||
* @author An Tao
|
||||
*
|
||||
* Public header file in trantor lib.
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#include <trantor/net/EventLoop.h>
|
||||
|
||||
#include "TimerQueue.h"
|
||||
#include "Channel.h"
|
||||
#ifdef __linux__
|
||||
#include <sys/timerfd.h>
|
||||
#endif
|
||||
#include <string.h>
|
||||
#include <iostream>
|
||||
#ifndef _WIN32
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
using namespace trantor;
|
||||
#ifdef __linux__
|
||||
static int createTimerfd()
|
||||
{
|
||||
int timerfd = ::timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
|
||||
if (timerfd < 0)
|
||||
{
|
||||
std::cerr << "create timerfd failed!" << std::endl;
|
||||
}
|
||||
return timerfd;
|
||||
}
|
||||
|
||||
static struct timespec howMuchTimeFromNow(const TimePoint &when)
|
||||
{
|
||||
auto microSeconds = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
when - std::chrono::steady_clock::now())
|
||||
.count();
|
||||
if (microSeconds < 100)
|
||||
{
|
||||
microSeconds = 100;
|
||||
}
|
||||
struct timespec ts;
|
||||
ts.tv_sec = static_cast<time_t>(microSeconds / 1000000);
|
||||
ts.tv_nsec = static_cast<long>((microSeconds % 1000000) * 1000);
|
||||
return ts;
|
||||
}
|
||||
static void resetTimerfd(int timerfd, const TimePoint &expiration)
|
||||
{
|
||||
// wake up loop by timerfd_settime()
|
||||
struct itimerspec newValue;
|
||||
struct itimerspec oldValue;
|
||||
memset(&newValue, 0, sizeof(newValue));
|
||||
memset(&oldValue, 0, sizeof(oldValue));
|
||||
newValue.it_value = howMuchTimeFromNow(expiration);
|
||||
int ret = ::timerfd_settime(timerfd, 0, &newValue, &oldValue);
|
||||
if (ret)
|
||||
{
|
||||
// LOG_SYSERR << "timerfd_settime()";
|
||||
}
|
||||
}
|
||||
static void readTimerfd(int timerfd, const TimePoint &)
|
||||
{
|
||||
uint64_t howmany;
|
||||
ssize_t n = ::read(timerfd, &howmany, sizeof howmany);
|
||||
if (n != sizeof howmany)
|
||||
{
|
||||
LOG_ERROR << "TimerQueue::handleRead() reads " << n
|
||||
<< " bytes instead of 8";
|
||||
}
|
||||
}
|
||||
|
||||
void TimerQueue::handleRead()
|
||||
{
|
||||
loop_->assertInLoopThread();
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
readTimerfd(timerfd_, now);
|
||||
|
||||
std::vector<TimerPtr> expired = getExpired(now);
|
||||
|
||||
callingExpiredTimers_ = true;
|
||||
// cancelingTimers_.clear();
|
||||
// safe to callback outside critical section
|
||||
for (auto const &timerPtr : expired)
|
||||
{
|
||||
if (timerIdSet_.find(timerPtr->id()) != timerIdSet_.end())
|
||||
{
|
||||
timerPtr->run();
|
||||
}
|
||||
}
|
||||
callingExpiredTimers_ = false;
|
||||
|
||||
reset(expired, now);
|
||||
}
|
||||
#else
|
||||
static int64_t howMuchTimeFromNow(const TimePoint &when)
|
||||
{
|
||||
auto microSeconds = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
when - std::chrono::steady_clock::now())
|
||||
.count();
|
||||
if (microSeconds < 1000)
|
||||
{
|
||||
microSeconds = 1000;
|
||||
}
|
||||
return microSeconds / 1000;
|
||||
}
|
||||
void TimerQueue::processTimers()
|
||||
{
|
||||
loop_->assertInLoopThread();
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
|
||||
std::vector<TimerPtr> expired = getExpired(now);
|
||||
|
||||
callingExpiredTimers_ = true;
|
||||
// cancelingTimers_.clear();
|
||||
// safe to callback outside critical section
|
||||
for (auto const &timerPtr : expired)
|
||||
{
|
||||
if (timerIdSet_.find(timerPtr->id()) != timerIdSet_.end())
|
||||
{
|
||||
timerPtr->run();
|
||||
}
|
||||
}
|
||||
callingExpiredTimers_ = false;
|
||||
|
||||
reset(expired, now);
|
||||
}
|
||||
#endif
|
||||
///////////////////////////////////////
|
||||
TimerQueue::TimerQueue(EventLoop *loop)
|
||||
: loop_(loop),
|
||||
#ifdef __linux__
|
||||
timerfd_(createTimerfd()),
|
||||
timerfdChannelPtr_(new Channel(loop, timerfd_)),
|
||||
#endif
|
||||
timers_(),
|
||||
callingExpiredTimers_(false)
|
||||
{
|
||||
#ifdef __linux__
|
||||
timerfdChannelPtr_->setReadCallback(
|
||||
std::bind(&TimerQueue::handleRead, this));
|
||||
// we are always reading the timerfd, we disarm it with timerfd_settime.
|
||||
timerfdChannelPtr_->enableReading();
|
||||
#endif
|
||||
}
|
||||
#ifdef __linux__
|
||||
void TimerQueue::reset()
|
||||
{
|
||||
loop_->runInLoop([this]() {
|
||||
timerfdChannelPtr_->disableAll();
|
||||
timerfdChannelPtr_->remove();
|
||||
close(timerfd_);
|
||||
timerfd_ = createTimerfd();
|
||||
timerfdChannelPtr_ = std::make_shared<Channel>(loop_, timerfd_);
|
||||
timerfdChannelPtr_->setReadCallback(
|
||||
std::bind(&TimerQueue::handleRead, this));
|
||||
// we are always reading the timerfd, we disarm it with timerfd_settime.
|
||||
timerfdChannelPtr_->enableReading();
|
||||
if (!timers_.empty())
|
||||
{
|
||||
const auto nextExpire = timers_.top()->when();
|
||||
resetTimerfd(timerfd_, nextExpire);
|
||||
}
|
||||
});
|
||||
}
|
||||
#endif
|
||||
TimerQueue::~TimerQueue()
|
||||
{
|
||||
#ifdef __linux__
|
||||
auto chlPtr = timerfdChannelPtr_;
|
||||
auto fd = timerfd_;
|
||||
loop_->runInLoop([chlPtr, fd]() {
|
||||
chlPtr->disableAll();
|
||||
chlPtr->remove();
|
||||
::close(fd);
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
TimerId TimerQueue::addTimer(const TimerCallback &cb,
|
||||
const TimePoint &when,
|
||||
const TimeInterval &interval)
|
||||
{
|
||||
std::shared_ptr<Timer> timerPtr =
|
||||
std::make_shared<Timer>(cb, when, interval);
|
||||
|
||||
loop_->runInLoop([this, timerPtr]() { addTimerInLoop(timerPtr); });
|
||||
return timerPtr->id();
|
||||
}
|
||||
TimerId TimerQueue::addTimer(TimerCallback &&cb,
|
||||
const TimePoint &when,
|
||||
const TimeInterval &interval)
|
||||
{
|
||||
std::shared_ptr<Timer> timerPtr =
|
||||
std::make_shared<Timer>(std::move(cb), when, interval);
|
||||
|
||||
loop_->runInLoop([this, timerPtr]() { addTimerInLoop(timerPtr); });
|
||||
return timerPtr->id();
|
||||
}
|
||||
void TimerQueue::addTimerInLoop(const TimerPtr &timer)
|
||||
{
|
||||
loop_->assertInLoopThread();
|
||||
timerIdSet_.insert(timer->id());
|
||||
if (insert(timer))
|
||||
{
|
||||
// the earliest timer changed
|
||||
#ifdef __linux__
|
||||
resetTimerfd(timerfd_, timer->when());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void TimerQueue::invalidateTimer(TimerId id)
|
||||
{
|
||||
loop_->runInLoop([this, id]() { timerIdSet_.erase(id); });
|
||||
}
|
||||
|
||||
bool TimerQueue::insert(const TimerPtr &timerPtr)
|
||||
{
|
||||
loop_->assertInLoopThread();
|
||||
bool earliestChanged = false;
|
||||
if (timers_.size() == 0 || *timerPtr < *timers_.top())
|
||||
{
|
||||
earliestChanged = true;
|
||||
}
|
||||
timers_.push(timerPtr);
|
||||
// std::cout<<"after push new
|
||||
// timer:"<<timerPtr->when().microSecondsSinceEpoch()/1000000<<std::endl;
|
||||
return earliestChanged;
|
||||
}
|
||||
#ifndef __linux__
|
||||
int64_t TimerQueue::getTimeout() const
|
||||
{
|
||||
loop_->assertInLoopThread();
|
||||
if (timers_.empty())
|
||||
{
|
||||
return 10000;
|
||||
}
|
||||
else
|
||||
{
|
||||
return howMuchTimeFromNow(timers_.top()->when());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
std::vector<TimerPtr> TimerQueue::getExpired(const TimePoint &now)
|
||||
{
|
||||
std::vector<TimerPtr> expired;
|
||||
while (!timers_.empty())
|
||||
{
|
||||
if (timers_.top()->when() < now)
|
||||
{
|
||||
expired.push_back(timers_.top());
|
||||
timers_.pop();
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
return expired;
|
||||
}
|
||||
void TimerQueue::reset(const std::vector<TimerPtr> &expired,
|
||||
const TimePoint &now)
|
||||
{
|
||||
loop_->assertInLoopThread();
|
||||
for (auto const &timerPtr : expired)
|
||||
{
|
||||
auto iter = timerIdSet_.find(timerPtr->id());
|
||||
if (iter != timerIdSet_.end())
|
||||
{
|
||||
if (timerPtr->isRepeat())
|
||||
{
|
||||
timerPtr->restart(now);
|
||||
insert(timerPtr);
|
||||
}
|
||||
else
|
||||
{
|
||||
timerIdSet_.erase(iter);
|
||||
}
|
||||
}
|
||||
}
|
||||
#ifdef __linux__
|
||||
if (!timers_.empty())
|
||||
{
|
||||
const auto nextExpire = timers_.top()->when();
|
||||
resetTimerfd(timerfd_, nextExpire);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
*
|
||||
* TimerQueue.h
|
||||
* An Tao
|
||||
*
|
||||
* Public header file in trantor lib.
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <trantor/utils/NonCopyable.h>
|
||||
#include <trantor/net/callbacks.h>
|
||||
#include "Timer.h"
|
||||
#include <queue>
|
||||
#include <memory>
|
||||
#include <atomic>
|
||||
#include <unordered_set>
|
||||
namespace trantor
|
||||
{
|
||||
// class Timer;
|
||||
class EventLoop;
|
||||
class Channel;
|
||||
using TimerPtr = std::shared_ptr<Timer>;
|
||||
struct TimerPtrComparer
|
||||
{
|
||||
bool operator()(const TimerPtr &x, const TimerPtr &y) const
|
||||
{
|
||||
return *x > *y;
|
||||
}
|
||||
};
|
||||
|
||||
class TimerQueue : NonCopyable
|
||||
{
|
||||
public:
|
||||
explicit TimerQueue(EventLoop *loop);
|
||||
~TimerQueue();
|
||||
TimerId addTimer(const TimerCallback &cb,
|
||||
const TimePoint &when,
|
||||
const TimeInterval &interval);
|
||||
TimerId addTimer(TimerCallback &&cb,
|
||||
const TimePoint &when,
|
||||
const TimeInterval &interval);
|
||||
void addTimerInLoop(const TimerPtr &timer);
|
||||
void invalidateTimer(TimerId id);
|
||||
#ifdef __linux__
|
||||
void reset();
|
||||
#else
|
||||
int64_t getTimeout() const;
|
||||
void processTimers();
|
||||
#endif
|
||||
protected:
|
||||
EventLoop *loop_;
|
||||
#ifdef __linux__
|
||||
int timerfd_;
|
||||
std::shared_ptr<Channel> timerfdChannelPtr_;
|
||||
void handleRead();
|
||||
#endif
|
||||
std::priority_queue<TimerPtr, std::vector<TimerPtr>, TimerPtrComparer>
|
||||
timers_;
|
||||
|
||||
bool callingExpiredTimers_;
|
||||
bool insert(const TimerPtr &timePtr);
|
||||
std::vector<TimerPtr> getExpired();
|
||||
void reset(const std::vector<TimerPtr> &expired, const TimePoint &now);
|
||||
std::vector<TimerPtr> getExpired(const TimePoint &now);
|
||||
|
||||
private:
|
||||
std::unordered_set<uint64_t> timerIdSet_;
|
||||
};
|
||||
} // namespace trantor
|
||||
@@ -0,0 +1,243 @@
|
||||
/**
|
||||
*
|
||||
* EpollPoller.cc
|
||||
* An Tao
|
||||
*
|
||||
* Public header file in trantor lib.
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#include <trantor/utils/Logger.h>
|
||||
#include "Channel.h"
|
||||
#include "EpollPoller.h"
|
||||
#ifdef __linux__
|
||||
#include <poll.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
#include <strings.h>
|
||||
#include <iostream>
|
||||
#elif defined _WIN32
|
||||
#include "Wepoll.h"
|
||||
#include <assert.h>
|
||||
#include <iostream>
|
||||
#include <winsock2.h>
|
||||
#include <fcntl.h>
|
||||
#define EPOLL_CLOEXEC _O_NOINHERIT
|
||||
#endif
|
||||
namespace trantor
|
||||
{
|
||||
#if defined __linux__ || defined _WIN32
|
||||
|
||||
#if defined __linux__
|
||||
static_assert(EPOLLIN == POLLIN, "EPOLLIN != POLLIN");
|
||||
static_assert(EPOLLPRI == POLLPRI, "EPOLLPRI != POLLPRI");
|
||||
static_assert(EPOLLOUT == POLLOUT, "EPOLLOUT != POLLOUT");
|
||||
static_assert(EPOLLRDHUP == POLLRDHUP, "EPOLLRDHUP != POLLRDHUP");
|
||||
static_assert(EPOLLERR == POLLERR, "EPOLLERR != POLLERR");
|
||||
static_assert(EPOLLHUP == POLLHUP, "EPOLLHUP != POLLHUP");
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
const int kNew = -1;
|
||||
const int kAdded = 1;
|
||||
const int kDeleted = 2;
|
||||
} // namespace
|
||||
|
||||
EpollPoller::EpollPoller(EventLoop *loop)
|
||||
: Poller(loop),
|
||||
#ifdef _WIN32
|
||||
// wepoll does not support flags
|
||||
epollfd_(::epoll_create1(0)),
|
||||
#else
|
||||
epollfd_(::epoll_create1(EPOLL_CLOEXEC)),
|
||||
#endif
|
||||
events_(kInitEventListSize)
|
||||
{
|
||||
}
|
||||
EpollPoller::~EpollPoller()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
epoll_close(epollfd_);
|
||||
#else
|
||||
close(epollfd_);
|
||||
#endif
|
||||
}
|
||||
#ifdef _WIN32
|
||||
void EpollPoller::postEvent(uint64_t event)
|
||||
{
|
||||
epoll_post_signal(epollfd_, event);
|
||||
}
|
||||
#endif
|
||||
void EpollPoller::poll(int timeoutMs, ChannelList *activeChannels)
|
||||
{
|
||||
int numEvents = ::epoll_wait(epollfd_,
|
||||
&*events_.begin(),
|
||||
static_cast<int>(events_.size()),
|
||||
timeoutMs);
|
||||
int savedErrno = errno;
|
||||
// Timestamp now(Timestamp::now());
|
||||
if (numEvents > 0)
|
||||
{
|
||||
// LOG_TRACE << numEvents << " events happened";
|
||||
fillActiveChannels(numEvents, activeChannels);
|
||||
if (static_cast<size_t>(numEvents) == events_.size())
|
||||
{
|
||||
events_.resize(events_.size() * 2);
|
||||
}
|
||||
}
|
||||
else if (numEvents == 0)
|
||||
{
|
||||
// std::cout << "nothing happened" << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
// error happens, log uncommon ones
|
||||
if (savedErrno != EINTR)
|
||||
{
|
||||
errno = savedErrno;
|
||||
LOG_SYSERR << "EPollEpollPoller::poll()";
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
void EpollPoller::fillActiveChannels(int numEvents,
|
||||
ChannelList *activeChannels) const
|
||||
{
|
||||
assert(static_cast<size_t>(numEvents) <= events_.size());
|
||||
for (int i = 0; i < numEvents; ++i)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (events_[i].events == EPOLLEVENT)
|
||||
{
|
||||
eventCallback_(events_[i].data.u64);
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
Channel *channel = static_cast<Channel *>(events_[i].data.ptr);
|
||||
#ifndef NDEBUG
|
||||
int fd = channel->fd();
|
||||
ChannelMap::const_iterator it = channels_.find(fd);
|
||||
assert(it != channels_.end());
|
||||
assert(it->second == channel);
|
||||
#endif
|
||||
channel->setRevents(events_[i].events);
|
||||
activeChannels->push_back(channel);
|
||||
}
|
||||
// LOG_TRACE<<"active Channels num:"<<activeChannels->size();
|
||||
}
|
||||
void EpollPoller::updateChannel(Channel *channel)
|
||||
{
|
||||
assertInLoopThread();
|
||||
assert(channel->fd() >= 0);
|
||||
|
||||
const int index = channel->index();
|
||||
// LOG_TRACE << "fd = " << channel->fd()
|
||||
// << " events = " << channel->events() << " index = " << index;
|
||||
if (index == kNew || index == kDeleted)
|
||||
{
|
||||
// a new one, add with EPOLL_CTL_ADD
|
||||
#ifndef NDEBUG
|
||||
int fd = channel->fd();
|
||||
if (index == kNew)
|
||||
{
|
||||
assert(channels_.find(fd) == channels_.end());
|
||||
channels_[fd] = channel;
|
||||
}
|
||||
else
|
||||
{ // index == kDeleted
|
||||
assert(channels_.find(fd) != channels_.end());
|
||||
assert(channels_[fd] == channel);
|
||||
}
|
||||
#endif
|
||||
channel->setIndex(kAdded);
|
||||
update(EPOLL_CTL_ADD, channel);
|
||||
}
|
||||
else
|
||||
{
|
||||
// update existing one with EPOLL_CTL_MOD/DEL
|
||||
#ifndef NDEBUG
|
||||
int fd = channel->fd();
|
||||
(void)fd;
|
||||
assert(channels_.find(fd) != channels_.end());
|
||||
assert(channels_[fd] == channel);
|
||||
#endif
|
||||
assert(index == kAdded);
|
||||
if (channel->isNoneEvent())
|
||||
{
|
||||
update(EPOLL_CTL_DEL, channel);
|
||||
channel->setIndex(kDeleted);
|
||||
}
|
||||
else
|
||||
{
|
||||
update(EPOLL_CTL_MOD, channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
void EpollPoller::removeChannel(Channel *channel)
|
||||
{
|
||||
EpollPoller::assertInLoopThread();
|
||||
#ifndef NDEBUG
|
||||
int fd = channel->fd();
|
||||
assert(channels_.find(fd) != channels_.end());
|
||||
assert(channels_[fd] == channel);
|
||||
size_t n = channels_.erase(fd);
|
||||
(void)n;
|
||||
assert(n == 1);
|
||||
#endif
|
||||
assert(channel->isNoneEvent());
|
||||
int index = channel->index();
|
||||
assert(index == kAdded || index == kDeleted);
|
||||
if (index == kAdded)
|
||||
{
|
||||
update(EPOLL_CTL_DEL, channel);
|
||||
}
|
||||
channel->setIndex(kNew);
|
||||
}
|
||||
void EpollPoller::update(int operation, Channel *channel)
|
||||
{
|
||||
struct epoll_event event;
|
||||
memset(&event, 0, sizeof(event));
|
||||
event.events = channel->events();
|
||||
event.data.ptr = channel;
|
||||
int fd = channel->fd();
|
||||
if (::epoll_ctl(epollfd_, operation, fd, &event) < 0)
|
||||
{
|
||||
if (operation == EPOLL_CTL_DEL)
|
||||
{
|
||||
// LOG_SYSERR << "epoll_ctl op =" << operationToString(operation) <<
|
||||
// " fd =" << fd;
|
||||
}
|
||||
else
|
||||
{
|
||||
// LOG_SYSFATAL << "epoll_ctl op =" << operationToString(operation)
|
||||
// << " fd =" << fd;
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
EpollPoller::EpollPoller(EventLoop *loop) : Poller(loop)
|
||||
{
|
||||
assert(false);
|
||||
}
|
||||
EpollPoller::~EpollPoller()
|
||||
{
|
||||
}
|
||||
void EpollPoller::poll(int, ChannelList *)
|
||||
{
|
||||
}
|
||||
void EpollPoller::updateChannel(Channel *)
|
||||
{
|
||||
}
|
||||
void EpollPoller::removeChannel(Channel *)
|
||||
{
|
||||
}
|
||||
|
||||
#endif
|
||||
} // namespace trantor
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
*
|
||||
* EpollPoller.h
|
||||
* An Tao
|
||||
*
|
||||
* Public header file in trantor lib.
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../Poller.h"
|
||||
#include <trantor/utils/NonCopyable.h>
|
||||
#include <trantor/net/EventLoop.h>
|
||||
|
||||
#if defined __linux__ || defined _WIN32
|
||||
#include <memory>
|
||||
#include <map>
|
||||
using EventList = std::vector<struct epoll_event>;
|
||||
#endif
|
||||
namespace trantor
|
||||
{
|
||||
class Channel;
|
||||
|
||||
class EpollPoller : public Poller
|
||||
{
|
||||
public:
|
||||
explicit EpollPoller(EventLoop *loop);
|
||||
virtual ~EpollPoller();
|
||||
virtual void poll(int timeoutMs, ChannelList *activeChannels) override;
|
||||
virtual void updateChannel(Channel *channel) override;
|
||||
virtual void removeChannel(Channel *channel) override;
|
||||
#ifdef _WIN32
|
||||
virtual void postEvent(uint64_t event) override;
|
||||
virtual void setEventCallback(const EventCallback &cb) override
|
||||
{
|
||||
eventCallback_ = cb;
|
||||
}
|
||||
#endif
|
||||
|
||||
private:
|
||||
#if defined __linux__ || defined _WIN32
|
||||
static const int kInitEventListSize = 16;
|
||||
#ifdef _WIN32
|
||||
void *epollfd_;
|
||||
EventCallback eventCallback_{[](uint64_t event) {}};
|
||||
#else
|
||||
int epollfd_;
|
||||
#endif
|
||||
EventList events_;
|
||||
void update(int operation, Channel *channel);
|
||||
#ifndef NDEBUG
|
||||
using ChannelMap = std::map<int, Channel *>;
|
||||
ChannelMap channels_;
|
||||
#endif
|
||||
void fillActiveChannels(int numEvents, ChannelList *activeChannels) const;
|
||||
#endif
|
||||
};
|
||||
} // namespace trantor
|
||||
@@ -0,0 +1,251 @@
|
||||
#include "KQueue.h"
|
||||
#include "Channel.h"
|
||||
#ifdef USE_KQUEUE
|
||||
#include <trantor/utils/Logger.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/event.h>
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
#include <poll.h>
|
||||
#endif
|
||||
namespace trantor
|
||||
{
|
||||
#ifdef USE_KQUEUE
|
||||
namespace
|
||||
{
|
||||
const int kNew = -1;
|
||||
const int kAdded = 1;
|
||||
const int kDeleted = 2;
|
||||
} // namespace
|
||||
|
||||
KQueue::KQueue(EventLoop *loop)
|
||||
: Poller(loop), kqfd_(kqueue()), events_(kInitEventListSize)
|
||||
{
|
||||
assert(kqfd_ >= 0);
|
||||
}
|
||||
|
||||
KQueue::~KQueue()
|
||||
{
|
||||
close(kqfd_);
|
||||
}
|
||||
|
||||
void KQueue::resetAfterFork()
|
||||
{
|
||||
close(kqfd_);
|
||||
kqfd_ = kqueue();
|
||||
for (auto &ch : channels_)
|
||||
{
|
||||
ch.second.first = 0;
|
||||
if (ch.second.second->isReading() || ch.second.second->isWriting())
|
||||
{
|
||||
update(ch.second.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KQueue::poll(int timeoutMs, ChannelList *activeChannels)
|
||||
{
|
||||
struct timespec timeout;
|
||||
timeout.tv_sec = timeoutMs / 1000;
|
||||
timeout.tv_nsec = (timeoutMs % 1000) * 1000000;
|
||||
|
||||
int numEvents = kevent(kqfd_,
|
||||
NULL,
|
||||
0,
|
||||
events_.data(),
|
||||
static_cast<int>(events_.size()),
|
||||
&timeout);
|
||||
int savedErrno = errno;
|
||||
// Timestamp now(Timestamp::now());
|
||||
if (numEvents > 0)
|
||||
{
|
||||
// LOG_TRACE << numEvents << " events happened";
|
||||
fillActiveChannels(numEvents, activeChannels);
|
||||
if (static_cast<size_t>(numEvents) == events_.size())
|
||||
{
|
||||
events_.resize(events_.size() * 2);
|
||||
}
|
||||
}
|
||||
else if (numEvents == 0)
|
||||
{
|
||||
// std::cout << "nothing happened" << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
// error happens, log uncommon ones
|
||||
if (savedErrno != EINTR)
|
||||
{
|
||||
errno = savedErrno;
|
||||
LOG_SYSERR << "KQueue::poll()";
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
void KQueue::fillActiveChannels(int numEvents,
|
||||
ChannelList *activeChannels) const
|
||||
{
|
||||
assert(static_cast<size_t>(numEvents) <= events_.size());
|
||||
for (int i = 0; i < numEvents; ++i)
|
||||
{
|
||||
Channel *channel = static_cast<Channel *>(events_[i].udata);
|
||||
assert(channels_.find(channel->fd()) != channels_.end());
|
||||
int events = events_[i].filter;
|
||||
if (events == EVFILT_READ)
|
||||
{
|
||||
channel->setRevents(POLLIN);
|
||||
}
|
||||
else if (events == EVFILT_WRITE)
|
||||
{
|
||||
channel->setRevents(POLLOUT);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_ERROR << "events=" << events;
|
||||
continue;
|
||||
}
|
||||
activeChannels->push_back(channel);
|
||||
}
|
||||
}
|
||||
|
||||
void KQueue::updateChannel(Channel *channel)
|
||||
{
|
||||
assertInLoopThread();
|
||||
assert(channel->fd() >= 0);
|
||||
|
||||
const int index = channel->index();
|
||||
// LOG_TRACE << "fd = " << channel->fd()
|
||||
// << " events = " << channel->events() << " index = " << index;
|
||||
if (index == kNew || index == kDeleted)
|
||||
{
|
||||
if (index == kNew)
|
||||
{
|
||||
assert(channels_.find(channel->fd()) == channels_.end());
|
||||
}
|
||||
else
|
||||
{ // index == kDeleted
|
||||
assert(channels_.find(channel->fd()) != channels_.end());
|
||||
assert(channels_[channel->fd()].second == channel);
|
||||
}
|
||||
update(channel);
|
||||
channel->setIndex(kAdded);
|
||||
}
|
||||
else
|
||||
{
|
||||
// update existing one
|
||||
int fd = channel->fd();
|
||||
(void)fd;
|
||||
assert(channels_.find(fd) != channels_.end());
|
||||
assert(index == kAdded);
|
||||
if (channel->isNoneEvent())
|
||||
{
|
||||
update(channel);
|
||||
channel->setIndex(kDeleted);
|
||||
}
|
||||
else
|
||||
{
|
||||
update(channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
void KQueue::removeChannel(Channel *channel)
|
||||
{
|
||||
assertInLoopThread();
|
||||
int fd = channel->fd();
|
||||
assert(channels_.find(fd) != channels_.end());
|
||||
assert(channel->isNoneEvent());
|
||||
int index = channel->index();
|
||||
assert(index == kAdded || index == kDeleted);
|
||||
|
||||
if (index == kAdded)
|
||||
{
|
||||
update(channel);
|
||||
}
|
||||
|
||||
size_t n = channels_.erase(fd);
|
||||
(void)n;
|
||||
assert(n == 1);
|
||||
channel->setIndex(kNew);
|
||||
}
|
||||
|
||||
void KQueue::update(Channel *channel)
|
||||
{
|
||||
struct kevent ev[2];
|
||||
int n = 0;
|
||||
auto events = channel->events();
|
||||
int oldEvents = 0;
|
||||
if (channels_.find(channel->fd()) != channels_.end())
|
||||
{
|
||||
oldEvents = channels_[channel->fd()].first;
|
||||
}
|
||||
|
||||
auto fd = channel->fd();
|
||||
channels_[fd] = {events, channel};
|
||||
|
||||
if ((events & Channel::kReadEvent) && (!(oldEvents & Channel::kReadEvent)))
|
||||
{
|
||||
EV_SET(&ev[n++],
|
||||
fd,
|
||||
EVFILT_READ,
|
||||
EV_ADD | EV_ENABLE,
|
||||
0,
|
||||
0,
|
||||
(void *)(intptr_t)channel);
|
||||
}
|
||||
else if ((!(events & Channel::kReadEvent)) &&
|
||||
(oldEvents & Channel::kReadEvent))
|
||||
{
|
||||
EV_SET(&ev[n++],
|
||||
fd,
|
||||
EVFILT_READ,
|
||||
EV_DELETE,
|
||||
0,
|
||||
0,
|
||||
(void *)(intptr_t)channel);
|
||||
}
|
||||
if ((events & Channel::kWriteEvent) &&
|
||||
(!(oldEvents & Channel::kWriteEvent)))
|
||||
{
|
||||
EV_SET(&ev[n++],
|
||||
fd,
|
||||
EVFILT_WRITE,
|
||||
EV_ADD | EV_ENABLE,
|
||||
0,
|
||||
0,
|
||||
(void *)(intptr_t)channel);
|
||||
}
|
||||
else if ((!(events & Channel::kWriteEvent)) &&
|
||||
(oldEvents & Channel::kWriteEvent))
|
||||
{
|
||||
EV_SET(&ev[n++],
|
||||
fd,
|
||||
EVFILT_WRITE,
|
||||
EV_DELETE,
|
||||
0,
|
||||
0,
|
||||
(void *)(intptr_t)channel);
|
||||
}
|
||||
kevent(kqfd_, ev, n, NULL, 0, NULL);
|
||||
}
|
||||
#else
|
||||
KQueue::KQueue(EventLoop *loop) : Poller(loop)
|
||||
{
|
||||
assert(false);
|
||||
}
|
||||
KQueue::~KQueue()
|
||||
{
|
||||
}
|
||||
void KQueue::poll(int, ChannelList *)
|
||||
{
|
||||
}
|
||||
void KQueue::updateChannel(Channel *)
|
||||
{
|
||||
}
|
||||
void KQueue::removeChannel(Channel *)
|
||||
{
|
||||
}
|
||||
void KQueue::resetAfterFork()
|
||||
{
|
||||
}
|
||||
#endif
|
||||
} // namespace trantor
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
*
|
||||
* KQueue.h
|
||||
* An Tao
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* https://github.com/an-tao/trantor
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Trantor
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../Poller.h"
|
||||
#include <trantor/utils/NonCopyable.h>
|
||||
#include <trantor/net/EventLoop.h>
|
||||
|
||||
#if (defined(__unix__) && !defined(__linux__)) || \
|
||||
(defined(__APPLE__) && defined(__MACH__))
|
||||
#define USE_KQUEUE
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
using EventList = std::vector<struct kevent>;
|
||||
#endif
|
||||
namespace trantor
|
||||
{
|
||||
class Channel;
|
||||
|
||||
class KQueue : public Poller
|
||||
{
|
||||
public:
|
||||
explicit KQueue(EventLoop *loop);
|
||||
virtual ~KQueue();
|
||||
virtual void poll(int timeoutMs, ChannelList *activeChannels) override;
|
||||
virtual void updateChannel(Channel *channel) override;
|
||||
virtual void removeChannel(Channel *channel) override;
|
||||
virtual void resetAfterFork() override;
|
||||
|
||||
private:
|
||||
#ifdef USE_KQUEUE
|
||||
static const int kInitEventListSize = 16;
|
||||
int kqfd_;
|
||||
EventList events_;
|
||||
using ChannelMap = std::unordered_map<int, std::pair<int, Channel *>>;
|
||||
ChannelMap channels_;
|
||||
|
||||
void fillActiveChannels(int numEvents, ChannelList *activeChannels) const;
|
||||
void update(Channel *channel);
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace trantor
|
||||
@@ -0,0 +1,177 @@
|
||||
/**
|
||||
*
|
||||
* PollPoller.h
|
||||
* Martin Chang
|
||||
*
|
||||
* Copyright 2021, An Tao. All rights reserved.
|
||||
* https://github.com/an-tao/trantor
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Trantor
|
||||
*
|
||||
*/
|
||||
#include "PollPoller.h"
|
||||
|
||||
#include <trantor/utils/Logger.h>
|
||||
#include "trantor/net/Channel.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
using namespace trantor;
|
||||
|
||||
#if defined __unix__ || defined __HAIKU__
|
||||
|
||||
#include <errno.h>
|
||||
#include <poll.h>
|
||||
static std::once_flag warning_flag;
|
||||
|
||||
PollPoller::PollPoller(EventLoop* loop) : Poller(loop)
|
||||
{
|
||||
std::call_once(warning_flag, []() {
|
||||
LOG_WARN << "Creating a PollPoller. This poller is slow and should "
|
||||
"only be used when no other pollers are available";
|
||||
});
|
||||
}
|
||||
|
||||
PollPoller::~PollPoller()
|
||||
{
|
||||
}
|
||||
|
||||
void PollPoller::poll(int timeoutMs, ChannelList* activeChannels)
|
||||
{
|
||||
// XXX pollfds_ shouldn't change
|
||||
int numEvents = ::poll(pollfds_.data(), pollfds_.size(), timeoutMs);
|
||||
int savedErrno = errno;
|
||||
if (numEvents > 0)
|
||||
{
|
||||
// LOG_TRACE << numEvents << " events happened";
|
||||
fillActiveChannels(numEvents, activeChannels);
|
||||
}
|
||||
else if (numEvents == 0)
|
||||
{
|
||||
// LOG_TRACE << " nothing happened";
|
||||
}
|
||||
else
|
||||
{
|
||||
if (savedErrno != EINTR)
|
||||
{
|
||||
errno = savedErrno;
|
||||
LOG_SYSERR << "PollPoller::poll()";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PollPoller::fillActiveChannels(int numEvents,
|
||||
ChannelList* activeChannels) const
|
||||
{
|
||||
int processedEvents = 0;
|
||||
for (auto pfd : pollfds_)
|
||||
{
|
||||
if (pfd.revents > 0)
|
||||
{
|
||||
auto ch = channels_.find(pfd.fd);
|
||||
assert(ch != channels_.end());
|
||||
Channel* channel = ch->second;
|
||||
assert(channel->fd() == pfd.fd);
|
||||
channel->setRevents(pfd.revents);
|
||||
// pfd.revents = 0;
|
||||
activeChannels->push_back(channel);
|
||||
|
||||
processedEvents++;
|
||||
if (processedEvents == numEvents)
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(processedEvents == numEvents);
|
||||
}
|
||||
|
||||
void PollPoller::updateChannel(Channel* channel)
|
||||
{
|
||||
Poller::assertInLoopThread();
|
||||
assert(channel->fd() >= 0);
|
||||
|
||||
// LOG_TRACE << "fd = " << channel->fd() << " events = " <<
|
||||
// channel->events();
|
||||
if (channel->index() < 0)
|
||||
{
|
||||
// a new one, add to pollfds_
|
||||
assert(channels_.find(channel->fd()) == channels_.end());
|
||||
pollfd pfd;
|
||||
pfd.fd = channel->fd();
|
||||
pfd.events = static_cast<short>(channel->events());
|
||||
pfd.revents = 0;
|
||||
pollfds_.push_back(pfd);
|
||||
int idx = static_cast<int>(pollfds_.size()) - 1;
|
||||
channel->setIndex(idx);
|
||||
channels_[pfd.fd] = channel;
|
||||
}
|
||||
else
|
||||
{
|
||||
// update existing one
|
||||
assert(channels_.find(channel->fd()) != channels_.end());
|
||||
assert(channels_[channel->fd()] == channel);
|
||||
int idx = channel->index();
|
||||
assert(0 <= idx && idx < static_cast<int>(pollfds_.size()));
|
||||
pollfd& pfd = pollfds_[idx];
|
||||
assert(pfd.fd == channel->fd() || pfd.fd == -channel->fd() - 1);
|
||||
pfd.fd = channel->fd();
|
||||
pfd.events = static_cast<short>(channel->events());
|
||||
pfd.revents = 0;
|
||||
if (channel->isNoneEvent())
|
||||
{
|
||||
// ignore this pollfd
|
||||
pfd.fd = -channel->fd() - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PollPoller::removeChannel(Channel* channel)
|
||||
{
|
||||
Poller::assertInLoopThread();
|
||||
// LOG_TRACE << "fd = " << channel->fd();
|
||||
assert(channels_.find(channel->fd()) != channels_.end());
|
||||
assert(channels_[channel->fd()] == channel);
|
||||
assert(channel->isNoneEvent());
|
||||
int idx = channel->index();
|
||||
assert(0 <= idx && idx < static_cast<int>(pollfds_.size()));
|
||||
const pollfd& pfd = pollfds_[idx];
|
||||
(void)pfd;
|
||||
assert(pfd.fd == -channel->fd() - 1 && pfd.events == channel->events());
|
||||
size_t n = channels_.erase(channel->fd());
|
||||
assert(n == 1);
|
||||
(void)n;
|
||||
if (size_t(idx) == pollfds_.size() - 1)
|
||||
{
|
||||
pollfds_.pop_back();
|
||||
}
|
||||
else
|
||||
{
|
||||
int channelAtEnd = pollfds_.back().fd;
|
||||
iter_swap(pollfds_.begin() + idx, pollfds_.end() - 1);
|
||||
if (channelAtEnd < 0)
|
||||
{
|
||||
channelAtEnd = -channelAtEnd - 1;
|
||||
}
|
||||
channels_[channelAtEnd]->setIndex(idx);
|
||||
pollfds_.pop_back();
|
||||
}
|
||||
}
|
||||
#else
|
||||
PollPoller::PollPoller(EventLoop *loop) : Poller(loop)
|
||||
{
|
||||
assert(false);
|
||||
}
|
||||
PollPoller::~PollPoller()
|
||||
{
|
||||
}
|
||||
void PollPoller::poll(int, ChannelList *)
|
||||
{
|
||||
}
|
||||
void PollPoller::updateChannel(Channel *)
|
||||
{
|
||||
}
|
||||
void PollPoller::removeChannel(Channel *)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
*
|
||||
* PollPoller.h
|
||||
* Martin Chang
|
||||
*
|
||||
* Copyright 2021, An Tao. All rights reserved.
|
||||
* https://github.com/an-tao/trantor
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Trantor
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "../Poller.h"
|
||||
#include <vector>
|
||||
|
||||
#if defined __unix__ || defined __HAIKU__
|
||||
#include <poll.h>
|
||||
#endif
|
||||
|
||||
namespace trantor
|
||||
{
|
||||
class PollPoller : public Poller
|
||||
{
|
||||
public:
|
||||
PollPoller(EventLoop* loop);
|
||||
~PollPoller() override;
|
||||
|
||||
void poll(int timeoutMs, ChannelList* activeChannels) override;
|
||||
void updateChannel(Channel* channel) override;
|
||||
void removeChannel(Channel* channel) override;
|
||||
|
||||
private:
|
||||
void fillActiveChannels(int numEvents, ChannelList* activeChannels) const;
|
||||
|
||||
#if defined __unix__ || defined __HAIKU__
|
||||
std::vector<struct pollfd> pollfds_;
|
||||
std::map<int, Channel*> channels_;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace trantor
|
||||
+492
@@ -0,0 +1,492 @@
|
||||
#include <trantor/net/inner/TLSProvider.h>
|
||||
#include <trantor/net/Certificate.h>
|
||||
#include <trantor/net/TcpConnection.h>
|
||||
#include <trantor/net/callbacks.h>
|
||||
#include <trantor/utils/Logger.h>
|
||||
|
||||
#include <botan/tls_server.h>
|
||||
#include <botan/tls_client.h>
|
||||
#include <botan/tls_callbacks.h>
|
||||
#include <botan/tls_policy.h>
|
||||
#include <botan/auto_rng.h>
|
||||
#include <botan/certstor.h>
|
||||
#include <botan/certstor_system.h>
|
||||
#include <botan/data_src.h>
|
||||
#include <botan/pkcs8.h>
|
||||
#include <botan/tls_exceptn.h>
|
||||
#include <botan/pkix_types.h>
|
||||
#include <botan/certstor_flatfile.h>
|
||||
#include <botan/x509path.h>
|
||||
#include <botan/tls_session_manager_memory.h>
|
||||
#include <memory>
|
||||
|
||||
using namespace trantor;
|
||||
using namespace std::placeholders;
|
||||
|
||||
static std::once_flag sessionManagerInitFlag;
|
||||
static std::shared_ptr<Botan::AutoSeeded_RNG> sessionManagerRng;
|
||||
static std::shared_ptr<Botan::TLS::Session_Manager_In_Memory> sessionManager;
|
||||
static thread_local std::shared_ptr<Botan::AutoSeeded_RNG> rng;
|
||||
static std::unique_ptr<Botan::System_Certificate_Store> systemCertStore;
|
||||
static std::once_flag systemCertStoreInitFlag;
|
||||
|
||||
using namespace trantor;
|
||||
|
||||
static std::string join(const std::vector<std::string> &vec,
|
||||
const std::string &delim)
|
||||
{
|
||||
std::string ret;
|
||||
for (auto const &str : vec)
|
||||
{
|
||||
if (ret.empty() == false)
|
||||
ret += delim;
|
||||
ret += str;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
class Credentials : public Botan::Credentials_Manager
|
||||
{
|
||||
public:
|
||||
Credentials(std::shared_ptr<Botan::Private_Key> key,
|
||||
Botan::X509_Certificate *cert,
|
||||
Botan::Certificate_Store *certStore)
|
||||
: certStore_(certStore), cert_(cert), key_(key)
|
||||
{
|
||||
}
|
||||
std::vector<Botan::Certificate_Store *> trusted_certificate_authorities(
|
||||
const std::string &type,
|
||||
const std::string &context) override
|
||||
{
|
||||
(void)type;
|
||||
(void)context;
|
||||
if (certStore_ == nullptr)
|
||||
return {};
|
||||
return {certStore_};
|
||||
}
|
||||
|
||||
std::vector<Botan::X509_Certificate> find_cert_chain(
|
||||
const std::vector<std::string> &cert_key_types,
|
||||
const std::vector<Botan::AlgorithmIdentifier> &cert_signature_schemes,
|
||||
const std::vector<Botan::X509_DN> &acceptable_CAs,
|
||||
const std::string &type,
|
||||
const std::string &context) override
|
||||
{
|
||||
(void)type;
|
||||
(void)context;
|
||||
(void)cert_signature_schemes;
|
||||
(void)acceptable_CAs;
|
||||
if (cert_ == nullptr)
|
||||
return {};
|
||||
|
||||
auto key_algo =
|
||||
cert_->subject_public_key_algo().oid().to_formatted_string();
|
||||
auto it =
|
||||
std::find(cert_key_types.begin(), cert_key_types.end(), key_algo);
|
||||
if (it == cert_key_types.end())
|
||||
return {};
|
||||
return {*cert_};
|
||||
}
|
||||
|
||||
std::shared_ptr<Botan::Private_Key> private_key_for(
|
||||
const Botan::X509_Certificate &cert,
|
||||
const std::string &type,
|
||||
const std::string &context) override
|
||||
{
|
||||
(void)cert;
|
||||
(void)type;
|
||||
(void)context;
|
||||
return key_;
|
||||
}
|
||||
Botan::Certificate_Store *certStore_ = nullptr;
|
||||
Botan::X509_Certificate *cert_ = nullptr;
|
||||
std::shared_ptr<Botan::Private_Key> key_ = nullptr;
|
||||
};
|
||||
|
||||
struct BotanCertificate : public Certificate
|
||||
{
|
||||
BotanCertificate(const Botan::X509_Certificate &cert) : cert_(cert)
|
||||
{
|
||||
}
|
||||
|
||||
virtual std::string sha1Fingerprint() const override
|
||||
{
|
||||
return cert_.fingerprint("SHA-1");
|
||||
}
|
||||
|
||||
virtual std::string sha256Fingerprint() const override
|
||||
{
|
||||
return cert_.fingerprint("SHA-256");
|
||||
}
|
||||
|
||||
virtual std::string pem() const override
|
||||
{
|
||||
return cert_.PEM_encode();
|
||||
}
|
||||
Botan::X509_Certificate cert_;
|
||||
};
|
||||
|
||||
namespace trantor
|
||||
{
|
||||
struct SSLContext
|
||||
{
|
||||
std::shared_ptr<Botan::Private_Key> key;
|
||||
std::unique_ptr<Botan::X509_Certificate> cert;
|
||||
std::shared_ptr<Botan::Certificate_Store> certStore;
|
||||
bool isServer = false;
|
||||
bool requireClientCert = false;
|
||||
};
|
||||
} // namespace trantor
|
||||
|
||||
class TrantorPolicy : public Botan::TLS::Policy
|
||||
{
|
||||
virtual bool require_cert_revocation_info() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual bool require_client_certificate_authentication() const override
|
||||
{
|
||||
return requireClientCert_;
|
||||
}
|
||||
|
||||
public:
|
||||
bool requireClientCert_ = false;
|
||||
};
|
||||
|
||||
struct BotanTLSProvider : public TLSProvider,
|
||||
public NonCopyable,
|
||||
public Botan::TLS::Callbacks,
|
||||
public std::enable_shared_from_this<BotanTLSProvider>
|
||||
{
|
||||
public:
|
||||
BotanTLSProvider(TcpConnection *conn,
|
||||
TLSPolicyPtr policy,
|
||||
SSLContextPtr ctx)
|
||||
: TLSProvider(conn, std::move(policy), std::move(ctx))
|
||||
{
|
||||
validationPolicy_ = std::make_shared<TrantorPolicy>();
|
||||
}
|
||||
|
||||
virtual void recvData(MsgBuffer *buffer) override
|
||||
{
|
||||
LOG_TRACE << "Low level connection received " << buffer->readableBytes()
|
||||
<< " bytes.";
|
||||
try
|
||||
{
|
||||
assert(channel_ != nullptr);
|
||||
channel_->received_data((const uint8_t *)buffer->peek(),
|
||||
buffer->readableBytes());
|
||||
}
|
||||
catch (const Botan::TLS::TLS_Exception &e)
|
||||
{
|
||||
LOG_ERROR << "Unexpected TLS Exception: " << e.what();
|
||||
conn_->shutdown();
|
||||
|
||||
if (tlsConnected_ == false)
|
||||
{
|
||||
if (e.type() == Botan::TLS::Alert::BadCertificate)
|
||||
handleSSLError(SSLError::kSSLInvalidCertificate);
|
||||
else
|
||||
handleSSLError(SSLError::kSSLHandshakeError);
|
||||
}
|
||||
else
|
||||
handleSSLError(SSLError::kSSLProtocolError);
|
||||
}
|
||||
catch (const Botan::Exception &e)
|
||||
{
|
||||
LOG_ERROR << "Unexpected Botan Exception: " << e.what();
|
||||
conn_->shutdown();
|
||||
if (tlsConnected_ == false)
|
||||
handleSSLError(SSLError::kSSLHandshakeError);
|
||||
else
|
||||
handleSSLError(SSLError::kSSLProtocolError);
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
LOG_ERROR << "Unexpected Generic Exception: " << e.what();
|
||||
conn_->shutdown();
|
||||
if (tlsConnected_ == false)
|
||||
handleSSLError(SSLError::kSSLHandshakeError);
|
||||
else
|
||||
handleSSLError(SSLError::kSSLProtocolError);
|
||||
}
|
||||
buffer->retrieveAll();
|
||||
}
|
||||
|
||||
virtual ssize_t sendData(const char *ptr, size_t size) override
|
||||
{
|
||||
if (getBufferedData().readableBytes() != 0)
|
||||
{
|
||||
errno = EAGAIN;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Limit the size of the data we send in one go to avoid holding massive
|
||||
// buffers in memory.
|
||||
constexpr size_t maxSend = 64 * 1024;
|
||||
size_t hasSent = 0;
|
||||
while (hasSent < size && getBufferedData().readableBytes() == 0)
|
||||
{
|
||||
auto trunkLen = size - hasSent;
|
||||
if (trunkLen > maxSend)
|
||||
trunkLen = maxSend;
|
||||
channel_->send((const uint8_t *)ptr + hasSent, trunkLen);
|
||||
// HACK: Botan doesn't provide a way to know how much raw data has
|
||||
// been written to the underlying transport. So we have to assume
|
||||
// that all data has been written. And cache the unwritten data in
|
||||
// writeBuffer_. Then "fake" the consumed size in sendData() to make
|
||||
// the caller think that all data has been written. Then return -1
|
||||
// if the underlying socket is not writable at all (i.e. write is
|
||||
// all or nothing)
|
||||
if (lastWriteSize_ == -1)
|
||||
return -1;
|
||||
hasSent += trunkLen;
|
||||
}
|
||||
return static_cast<ssize_t>(hasSent);
|
||||
}
|
||||
|
||||
virtual void close() override
|
||||
{
|
||||
if (channel_ && channel_->is_active())
|
||||
{
|
||||
channel_->close();
|
||||
}
|
||||
}
|
||||
|
||||
virtual void startEncryption() override
|
||||
{
|
||||
auto certStorePtr = contextPtr_->certStore.get();
|
||||
if (certStorePtr == nullptr)
|
||||
{
|
||||
std::call_once(systemCertStoreInitFlag, []() {
|
||||
systemCertStore =
|
||||
std::make_unique<Botan::System_Certificate_Store>();
|
||||
});
|
||||
certStorePtr = systemCertStore.get();
|
||||
}
|
||||
credsPtr_ = std::make_shared<Credentials>(contextPtr_->key,
|
||||
contextPtr_->cert.get(),
|
||||
certStorePtr);
|
||||
if (policyPtr_->getConfCmds().empty() == false)
|
||||
LOG_WARN << "BotanTLSConnectionImpl does not support sslConfCmds.";
|
||||
|
||||
// initialize rng and session manager if we haven't already
|
||||
std::call_once(sessionManagerInitFlag, []() {
|
||||
sessionManagerRng = std::make_shared<Botan::AutoSeeded_RNG>();
|
||||
sessionManager =
|
||||
std::make_shared<Botan::TLS::Session_Manager_In_Memory>(
|
||||
sessionManagerRng);
|
||||
});
|
||||
if (rng == nullptr)
|
||||
rng = std::make_shared<Botan::AutoSeeded_RNG>();
|
||||
|
||||
auto fakeThis = std::shared_ptr<BotanTLSProvider>(this, [](auto) {});
|
||||
if (contextPtr_->isServer)
|
||||
{
|
||||
// TODO: Need a more scalable way to manage session validation rules
|
||||
validationPolicy_->requireClientCert_ =
|
||||
contextPtr_->requireClientCert;
|
||||
channel_ = std::make_unique<Botan::TLS::Server>(std::move(fakeThis),
|
||||
sessionManager,
|
||||
credsPtr_,
|
||||
validationPolicy_,
|
||||
rng);
|
||||
}
|
||||
else
|
||||
{
|
||||
validationPolicy_->requireClientCert_ =
|
||||
contextPtr_->requireClientCert;
|
||||
// technically Botan2 does support TLS 1.0 and 1.1, but Botan3 does
|
||||
// not. So we just disable them to keep compatibility.
|
||||
if (policyPtr_->getUseOldTLS())
|
||||
LOG_WARN << "Old TLS not supported by Botan (only >= TLS 1.2)";
|
||||
channel_ = std::make_unique<Botan::TLS::Client>(
|
||||
std::move(fakeThis),
|
||||
sessionManager,
|
||||
credsPtr_,
|
||||
validationPolicy_,
|
||||
rng,
|
||||
Botan::TLS::Server_Information(policyPtr_->getHostname(),
|
||||
conn_->peerAddr().toPort()),
|
||||
Botan::TLS::Protocol_Version::TLS_V12,
|
||||
policyPtr_->getAlpnProtocols());
|
||||
setSniName(policyPtr_->getHostname());
|
||||
}
|
||||
}
|
||||
|
||||
void handleSSLError(SSLError err)
|
||||
{
|
||||
if (!errorCallback_)
|
||||
return;
|
||||
loop_->queueInLoop([this, err]() { errorCallback_(conn_, err); });
|
||||
}
|
||||
|
||||
virtual ~BotanTLSProvider() override = default;
|
||||
|
||||
void tls_emit_data(std::span<const uint8_t> data) override
|
||||
{
|
||||
auto n = writeCallback_(conn_, data.data(), data.size_bytes());
|
||||
lastWriteSize_ = n;
|
||||
|
||||
// store the unsent data and send it later
|
||||
if (n == ssize_t(data.size_bytes()))
|
||||
return;
|
||||
if (n == -1)
|
||||
n = 0;
|
||||
appendToWriteBuffer((const char *)data.data() + n,
|
||||
data.size_bytes() - n);
|
||||
}
|
||||
|
||||
void tls_record_received(uint64_t seq_no,
|
||||
std::span<const uint8_t> data) override
|
||||
{
|
||||
(void)seq_no;
|
||||
recvBuffer_.append((const char *)data.data(), data.size_bytes());
|
||||
if (messageCallback_)
|
||||
messageCallback_(conn_, &recvBuffer_);
|
||||
}
|
||||
|
||||
std::string tls_server_choose_app_protocol(
|
||||
const std::vector<std::string> &client_protos) override
|
||||
{
|
||||
assert(contextPtr_->isServer);
|
||||
if (policyPtr_->getAlpnProtocols().empty() || client_protos.empty())
|
||||
return "";
|
||||
|
||||
for (auto const &proto : client_protos)
|
||||
{
|
||||
if (std::find(policyPtr_->getAlpnProtocols().begin(),
|
||||
policyPtr_->getAlpnProtocols().end(),
|
||||
proto) != policyPtr_->getAlpnProtocols().end())
|
||||
return proto;
|
||||
}
|
||||
|
||||
throw Botan::TLS::TLS_Exception(
|
||||
Botan::TLS::Alert::NoApplicationProtocol,
|
||||
"No supported application protocol found. Client offered: " +
|
||||
join(client_protos, ", ") + " but we support: " +
|
||||
join(policyPtr_->getAlpnProtocols(), ", "));
|
||||
}
|
||||
|
||||
void tls_alert(Botan::TLS::Alert alert) override
|
||||
{
|
||||
if (alert.type() == Botan::TLS::Alert::CloseNotify)
|
||||
{
|
||||
LOG_TRACE << "TLS close notify received";
|
||||
if (closeCallback_)
|
||||
closeCallback_(conn_);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (errorCallback_)
|
||||
errorCallback_(conn_, SSLError::kSSLProtocolError);
|
||||
}
|
||||
}
|
||||
|
||||
void tls_session_activated() override
|
||||
{
|
||||
LOG_TRACE << "tls_session_activated";
|
||||
tlsConnected_ = true;
|
||||
setApplicationProtocol(channel_->application_protocol());
|
||||
if (handshakeCallback_)
|
||||
handshakeCallback_(conn_);
|
||||
}
|
||||
|
||||
void tls_verify_cert_chain(
|
||||
const std::vector<Botan::X509_Certificate> &certs,
|
||||
const std::vector<std::optional<Botan::OCSP::Response>> &ocsp,
|
||||
const std::vector<Botan::Certificate_Store *> &trusted_roots,
|
||||
Botan::Usage_Type usage,
|
||||
std::string_view hostname,
|
||||
const Botan::TLS::Policy &policy) override
|
||||
{
|
||||
setSniName(std::string(hostname));
|
||||
if (policyPtr_->getValidate() && !policyPtr_->getAllowBrokenChain())
|
||||
Botan::TLS::Callbacks::tls_verify_cert_chain(
|
||||
certs, ocsp, trusted_roots, usage, hostname, policy);
|
||||
else if (policyPtr_->getValidate())
|
||||
{
|
||||
if (certs.size() == 0)
|
||||
throw Botan::TLS::TLS_Exception(
|
||||
Botan::TLS::Alert::NoCertificate,
|
||||
"Certificate validation failed: no certificate");
|
||||
// handle self-signed certificate
|
||||
std::vector<Botan::X509_Certificate> selfSigned = {certs[0]};
|
||||
|
||||
Botan::Path_Validation_Restrictions restrictions(
|
||||
false, // require revocation
|
||||
validationPolicy_->minimum_signature_strength());
|
||||
|
||||
auto now = std::chrono::system_clock::now();
|
||||
const auto status = Botan::PKIX::check_chain(
|
||||
selfSigned, now, hostname, usage, restrictions);
|
||||
|
||||
const auto result = Botan::PKIX::overall_status(status);
|
||||
|
||||
if (result != Botan::Certificate_Status_Code::OK)
|
||||
throw Botan::TLS::TLS_Exception(
|
||||
Botan::TLS::Alert::BadCertificate,
|
||||
std::string("Certificate validation failed: ") +
|
||||
Botan::to_string(result));
|
||||
}
|
||||
|
||||
if (certs.size() > 0)
|
||||
setPeerCertificate(std::make_shared<BotanCertificate>(certs[0]));
|
||||
}
|
||||
|
||||
std::shared_ptr<TrantorPolicy> validationPolicy_;
|
||||
std::shared_ptr<Botan::Credentials_Manager> credsPtr_;
|
||||
std::unique_ptr<Botan::TLS::Channel> channel_;
|
||||
bool tlsConnected_ = false;
|
||||
ssize_t lastWriteSize_ = 0;
|
||||
};
|
||||
|
||||
std::shared_ptr<TLSProvider> trantor::newTLSProvider(TcpConnection *conn,
|
||||
TLSPolicyPtr policy,
|
||||
SSLContextPtr ctx)
|
||||
{
|
||||
return std::make_shared<BotanTLSProvider>(conn,
|
||||
std::move(policy),
|
||||
std::move(ctx));
|
||||
}
|
||||
|
||||
SSLContextPtr trantor::newSSLContext(const TLSPolicy &policy, bool server)
|
||||
{
|
||||
auto ctx = std::make_shared<SSLContext>();
|
||||
ctx->isServer = server;
|
||||
if (!policy.getKeyPath().empty())
|
||||
{
|
||||
Botan::DataSource_Stream in(policy.getKeyPath());
|
||||
ctx->key = Botan::PKCS8::load_key(in);
|
||||
}
|
||||
|
||||
if (!policy.getCertPath().empty())
|
||||
{
|
||||
ctx->cert =
|
||||
std::make_unique<Botan::X509_Certificate>(policy.getCertPath());
|
||||
}
|
||||
|
||||
if (policy.getValidate() && policy.getAllowBrokenChain())
|
||||
{
|
||||
if (!policy.getCaPath().empty())
|
||||
{
|
||||
ctx->certStore =
|
||||
std::make_shared<Botan::Flatfile_Certificate_Store>(
|
||||
policy.getCaPath());
|
||||
if (server)
|
||||
ctx->requireClientCert = true;
|
||||
}
|
||||
else if (policy.getUseSystemCertStore())
|
||||
{
|
||||
static auto systemCertStore =
|
||||
std::make_shared<Botan::System_Certificate_Store>();
|
||||
ctx->certStore = systemCertStore;
|
||||
}
|
||||
}
|
||||
|
||||
if (policy.getUseOldTLS())
|
||||
LOG_WARN << "SSLPloicy have set useOldTLS to true. BUt Botan does not "
|
||||
"support TLS/SSL below TLS 1.2. Ignoring this option.";
|
||||
return ctx;
|
||||
}
|
||||
+917
@@ -0,0 +1,917 @@
|
||||
#include <trantor/utils/Logger.h>
|
||||
#include <trantor/utils/Utilities.h>
|
||||
#include <trantor/net/TcpConnection.h>
|
||||
#include <trantor/net/inner/TLSProvider.h>
|
||||
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/x509v3.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <list>
|
||||
#include <unordered_map>
|
||||
#include <array>
|
||||
#include <limits>
|
||||
#include "callbacks.h"
|
||||
|
||||
using namespace trantor;
|
||||
|
||||
// Force OpenSSL to initialize before main() is called
|
||||
static bool sslInitFlag = []() {
|
||||
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||
SSL_library_init();
|
||||
OpenSSL_add_all_algorithms();
|
||||
SSL_load_error_strings();
|
||||
ERR_load_BIO_strings();
|
||||
ERR_load_crypto_strings();
|
||||
#elif defined(LIBRESSL_VERSION_NUMBER)
|
||||
// LibreSSL needs explicit de-init
|
||||
atexit(OPENSSL_cleanup);
|
||||
#endif
|
||||
return true;
|
||||
}();
|
||||
|
||||
namespace internal
|
||||
{
|
||||
#ifdef _WIN32
|
||||
// Code yanked from stackoverflow
|
||||
// https://stackoverflow.com/questions/9507184/can-openssl-on-windows-use-the-system-certificate-store
|
||||
inline bool loadWindowsSystemCert(X509_STORE *store)
|
||||
{
|
||||
auto hStore = CertOpenSystemStoreW((HCRYPTPROV_LEGACY)NULL, L"ROOT");
|
||||
|
||||
if (!hStore)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
PCCERT_CONTEXT pContext = NULL;
|
||||
while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) !=
|
||||
nullptr)
|
||||
{
|
||||
auto encoded_cert =
|
||||
static_cast<const unsigned char *>(pContext->pbCertEncoded);
|
||||
|
||||
auto x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded);
|
||||
if (x509)
|
||||
{
|
||||
X509_STORE_add_cert(store, x509);
|
||||
X509_free(x509);
|
||||
}
|
||||
}
|
||||
|
||||
CertFreeCertificateContext(pContext);
|
||||
CertCloseStore(hStore, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
inline bool verifyCommonName(X509 *cert, const std::string &hostname)
|
||||
{
|
||||
X509_NAME *subjectName = X509_get_subject_name(cert);
|
||||
|
||||
if (subjectName != nullptr)
|
||||
{
|
||||
std::array<char, BUFSIZ> name;
|
||||
auto length = X509_NAME_get_text_by_NID(subjectName,
|
||||
NID_commonName,
|
||||
name.data(),
|
||||
(int)name.size());
|
||||
if (length == -1)
|
||||
return false;
|
||||
|
||||
return utils::verifySslName(std::string(name.begin(),
|
||||
name.begin() + length),
|
||||
hostname);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool verifyAltName(X509 *cert, const std::string &hostname)
|
||||
{
|
||||
bool good = false;
|
||||
auto altNames = static_cast<const struct stack_st_GENERAL_NAME *>(
|
||||
X509_get_ext_d2i(cert, NID_subject_alt_name, nullptr, nullptr));
|
||||
|
||||
if (altNames)
|
||||
{
|
||||
int numNames = sk_GENERAL_NAME_num(altNames);
|
||||
|
||||
for (int i = 0; i < numNames && !good; i++)
|
||||
{
|
||||
auto val = sk_GENERAL_NAME_value(altNames, i);
|
||||
if (val->type != GEN_DNS)
|
||||
{
|
||||
LOG_WARN << "Name using IP addresses are not supported. Open "
|
||||
"an issue if you need that feature";
|
||||
continue;
|
||||
}
|
||||
#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
|
||||
auto name = (const char *)ASN1_STRING_get0_data(val->d.ia5);
|
||||
#else
|
||||
auto name = (const char *)ASN1_STRING_data(val->d.ia5);
|
||||
#endif
|
||||
auto name_len = (size_t)ASN1_STRING_length(val->d.ia5);
|
||||
good = utils::verifySslName(std::string(name, name + name_len),
|
||||
hostname);
|
||||
}
|
||||
}
|
||||
|
||||
GENERAL_NAMES_free((STACK_OF(GENERAL_NAME) *)altNames);
|
||||
return good;
|
||||
}
|
||||
|
||||
static bool validatePeerCertificate(SSL *ssl,
|
||||
X509 *cert,
|
||||
const std::string &hostname,
|
||||
bool allowBrokenChain,
|
||||
bool isServer)
|
||||
{
|
||||
assert(ssl != nullptr);
|
||||
assert(cert != nullptr);
|
||||
LOG_TRACE << "Validating peer certificate";
|
||||
|
||||
if (isServer)
|
||||
{
|
||||
bool domainIsValid =
|
||||
verifyCommonName(cert, hostname) || verifyAltName(cert, hostname);
|
||||
if (!domainIsValid)
|
||||
return false;
|
||||
}
|
||||
|
||||
auto result = SSL_get_verify_result(ssl);
|
||||
if (result == X509_V_ERR_CERT_NOT_YET_VALID ||
|
||||
result == X509_V_ERR_CERT_HAS_EXPIRED)
|
||||
{
|
||||
// What happens if cert is self-signed and expired?
|
||||
LOG_TRACE << "cert error code: " << result
|
||||
<< ", date validation failed";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (result != X509_V_OK && !allowBrokenChain)
|
||||
{
|
||||
LOG_TRACE << "cert error code: " << result;
|
||||
LOG_ERROR << "Peer certificate is not valid";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int serverSelectProtocol(SSL *ssl,
|
||||
const unsigned char **out,
|
||||
unsigned char *outlen,
|
||||
const unsigned char *in,
|
||||
unsigned int inlen,
|
||||
void *arg)
|
||||
{
|
||||
(void)ssl;
|
||||
auto protocols = static_cast<std::vector<std::string> *>(arg);
|
||||
if (protocols->empty())
|
||||
return SSL_TLSEXT_ERR_NOACK;
|
||||
|
||||
for (auto &protocol : *protocols)
|
||||
{
|
||||
const unsigned char *cur = in;
|
||||
const unsigned char *end = in + inlen;
|
||||
while (cur < end)
|
||||
{
|
||||
unsigned int len = *cur++;
|
||||
if (cur + len > end)
|
||||
{
|
||||
LOG_ERROR << "Client provided invalid protocol list in APLN";
|
||||
return SSL_TLSEXT_ERR_NOACK;
|
||||
}
|
||||
if (protocol.size() == len &&
|
||||
memcmp(cur, protocol.data(), len) == 0)
|
||||
{
|
||||
*out = cur;
|
||||
*outlen = len;
|
||||
LOG_TRACE << "Selected protocol: " << protocol;
|
||||
return SSL_TLSEXT_ERR_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return SSL_TLSEXT_ERR_NOACK;
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
||||
namespace trantor
|
||||
{
|
||||
struct SSLContext
|
||||
{
|
||||
SSLContext(
|
||||
bool useOldTLS,
|
||||
const std::vector<std::pair<std::string, std::string>> &sslConfCmds,
|
||||
bool server)
|
||||
: isServer(server)
|
||||
{
|
||||
// Ungodly amount of preprocessor macros to support older versions of
|
||||
// OpenSSL and LibreSSL
|
||||
#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
|
||||
#define SSL_METHOD SSLv23_method
|
||||
#else
|
||||
#define SSL_METHOD TLS_method
|
||||
#endif
|
||||
|
||||
#ifdef LIBRESSL_VERSION_NUMBER
|
||||
ctx_ = SSL_CTX_new(SSL_METHOD());
|
||||
if (ctx_ == nullptr)
|
||||
throw std::runtime_error("Failed to create SSL context");
|
||||
if (sslConfCmds.size() != 0)
|
||||
LOG_WARN << "LibreSSL does not support SSL configuration commands";
|
||||
|
||||
if (!useOldTLS)
|
||||
SSL_CTX_set_min_proto_version(ctx_, TLS1_2_VERSION);
|
||||
#else
|
||||
ctx_ = SSL_CTX_new(SSL_METHOD());
|
||||
if (ctx_ == nullptr)
|
||||
throw std::runtime_error("Failed to create SSL context");
|
||||
SSL_CONF_CTX *cctx = SSL_CONF_CTX_new();
|
||||
SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_SERVER);
|
||||
SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_CLIENT);
|
||||
SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_CERTIFICATE);
|
||||
SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_FILE);
|
||||
SSL_CONF_CTX_set_ssl_ctx(cctx, ctx_);
|
||||
for (const auto &cmd : sslConfCmds)
|
||||
SSL_CONF_cmd(cctx, cmd.first.data(), cmd.second.data());
|
||||
SSL_CONF_CTX_finish(cctx);
|
||||
SSL_CONF_CTX_free(cctx);
|
||||
if (useOldTLS == false)
|
||||
{
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x10101000L
|
||||
SSL_CTX_set_min_proto_version(ctx_, TLS1_2_VERSION);
|
||||
#else
|
||||
const auto opt = SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1 |
|
||||
SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;
|
||||
SSL_CTX_set_options(ctx_, opt);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_WARN << "TLS 1.1 and below enabled. They are considered "
|
||||
"obsolete, insecure standards and should only be "
|
||||
"used for legacy purpose.";
|
||||
}
|
||||
#endif
|
||||
}
|
||||
~SSLContext()
|
||||
{
|
||||
if (ctx_)
|
||||
SSL_CTX_free(ctx_);
|
||||
}
|
||||
SSL_CTX *ctx_ = nullptr;
|
||||
|
||||
SSL_CTX *ctx() const
|
||||
{
|
||||
return ctx_;
|
||||
}
|
||||
|
||||
bool isServer{false};
|
||||
};
|
||||
|
||||
struct OpenSSLCertificate : public Certificate
|
||||
{
|
||||
OpenSSLCertificate(X509 *cert) : cert_(cert)
|
||||
{
|
||||
assert(cert_);
|
||||
}
|
||||
~OpenSSLCertificate()
|
||||
{
|
||||
X509_free(cert_);
|
||||
}
|
||||
virtual std::string sha1Fingerprint() const override
|
||||
{
|
||||
std::string sha1;
|
||||
unsigned char md[EVP_MAX_MD_SIZE];
|
||||
unsigned int n = 0;
|
||||
if (X509_digest(cert_, EVP_sha1(), md, &n))
|
||||
{
|
||||
sha1.resize(n * 3);
|
||||
for (unsigned int i = 0; i < n; i++)
|
||||
{
|
||||
snprintf(&sha1[i * 3], 4, "%02X:", md[i]);
|
||||
}
|
||||
sha1.resize(sha1.size() - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// handle error
|
||||
// LOG_ERROR << "X509_digest failed";
|
||||
}
|
||||
return sha1;
|
||||
}
|
||||
|
||||
virtual std::string sha256Fingerprint() const override
|
||||
{
|
||||
std::string sha256;
|
||||
unsigned char md[EVP_MAX_MD_SIZE];
|
||||
unsigned int n = 0;
|
||||
if (X509_digest(cert_, EVP_sha256(), md, &n))
|
||||
{
|
||||
sha256.resize(n * 3);
|
||||
for (unsigned int i = 0; i < n; i++)
|
||||
{
|
||||
snprintf(&sha256[i * 3], 4, "%02X:", md[i]);
|
||||
}
|
||||
sha256.resize(sha256.size() - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// handle error
|
||||
// LOG_ERROR << "X509_digest failed";
|
||||
}
|
||||
return sha256;
|
||||
}
|
||||
|
||||
virtual std::string pem() const override
|
||||
{
|
||||
std::string pem;
|
||||
BIO *bio = BIO_new(BIO_s_mem());
|
||||
if (bio)
|
||||
{
|
||||
PEM_write_bio_X509(bio, cert_);
|
||||
char *data = nullptr;
|
||||
long len = BIO_get_mem_data(bio, &data);
|
||||
if (len > 0)
|
||||
{
|
||||
pem.assign(data, len);
|
||||
}
|
||||
else
|
||||
{
|
||||
// handle error
|
||||
// LOG_ERROR << "BIO_get_mem_data failed";
|
||||
}
|
||||
BIO_free(bio);
|
||||
}
|
||||
else
|
||||
{
|
||||
// handle error
|
||||
// LOG_ERROR << "BIO_new failed";
|
||||
}
|
||||
return pem;
|
||||
}
|
||||
X509 *cert_ = nullptr;
|
||||
};
|
||||
|
||||
class SessionManager
|
||||
{
|
||||
struct SessionData
|
||||
{
|
||||
SSL_SESSION *session = nullptr;
|
||||
std::string key;
|
||||
TimerId timerId = 0;
|
||||
EventLoop *loop = nullptr;
|
||||
};
|
||||
|
||||
public:
|
||||
~SessionManager()
|
||||
{
|
||||
for (auto &session : sessions_)
|
||||
{
|
||||
SSL_SESSION_free(session.session);
|
||||
}
|
||||
}
|
||||
|
||||
void store(const std::string &hostname,
|
||||
InetAddress peerAddr,
|
||||
SSL_SESSION *session,
|
||||
EventLoop *loop)
|
||||
{
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
auto key = toKey(hostname, peerAddr);
|
||||
auto it = sessionMap_.find(key);
|
||||
if (it != sessionMap_.end())
|
||||
{
|
||||
SSL_SESSION_free(it->second->session);
|
||||
it->second->loop->invalidateTimer(it->second->timerId);
|
||||
sessions_.erase(it->second);
|
||||
sessionMap_.erase(it);
|
||||
}
|
||||
|
||||
SSL_SESSION_up_ref(session);
|
||||
TimerId tid = loop->runAfter(sessionTimeout_, [this, key]() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
auto it = sessionMap_.find(key);
|
||||
if (it != sessionMap_.end())
|
||||
{
|
||||
SSL_SESSION_free(it->second->session);
|
||||
sessions_.erase(it->second);
|
||||
sessionMap_.erase(it);
|
||||
}
|
||||
});
|
||||
sessions_.push_front(SessionData{session, key, tid, loop});
|
||||
sessionMap_[key] = sessions_.begin();
|
||||
}
|
||||
removeExcessSession();
|
||||
#else
|
||||
(void)hostname;
|
||||
(void)peerAddr;
|
||||
(void)session;
|
||||
(void)loop;
|
||||
assert(false && "not support under ancient openssl");
|
||||
#endif
|
||||
}
|
||||
|
||||
SSL_SESSION *get(const std::string &hostname, InetAddress peerAddr)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
auto key = toKey(hostname, peerAddr);
|
||||
auto it = sessionMap_.find(key);
|
||||
if (it != sessionMap_.end())
|
||||
{
|
||||
return it->second->session;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void removeExcessSession()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
assert(maxSessions_ > 0);
|
||||
assert(mexExtendSize_ > 0);
|
||||
if (sessions_.size() < size_t(maxSessions_ + mexExtendSize_))
|
||||
return;
|
||||
while (sessions_.size() > size_t(maxSessions_))
|
||||
{
|
||||
auto it = sessions_.end();
|
||||
it--;
|
||||
SSL_SESSION_free(it->session);
|
||||
it->loop->invalidateTimer(it->timerId);
|
||||
sessionMap_.erase(it->key);
|
||||
sessions_.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
std::string toKey(const std::string &hostname, InetAddress peerAddr)
|
||||
{
|
||||
return hostname + peerAddr.toIpPort();
|
||||
}
|
||||
|
||||
std::mutex mutex_;
|
||||
int maxSessions_ = 150;
|
||||
int mexExtendSize_ = 20;
|
||||
int sessionTimeout_ = 3600;
|
||||
std::list<SessionData> sessions_;
|
||||
std::unordered_map<std::string, std::list<SessionData>::iterator>
|
||||
sessionMap_;
|
||||
};
|
||||
|
||||
} // namespace trantor
|
||||
|
||||
static SessionManager sessionManager;
|
||||
|
||||
struct OpenSSLProvider : public TLSProvider, public NonCopyable
|
||||
{
|
||||
OpenSSLProvider(TcpConnection *conn, TLSPolicyPtr policy, SSLContextPtr ctx)
|
||||
: TLSProvider(conn, std::move(policy), std::move(ctx))
|
||||
{
|
||||
rbio_ = BIO_new(BIO_s_mem());
|
||||
wbio_ = BIO_new(BIO_s_mem());
|
||||
ssl_ = SSL_new(contextPtr_->ctx());
|
||||
assert(ssl_);
|
||||
assert(rbio_);
|
||||
assert(wbio_);
|
||||
SSL_set_bio(ssl_, rbio_, wbio_);
|
||||
if (!policyPtr_->getHostname().empty())
|
||||
SSL_set_tlsext_host_name(ssl_, policyPtr_->getHostname().c_str());
|
||||
}
|
||||
|
||||
virtual ~OpenSSLProvider()
|
||||
{
|
||||
SSL_free(ssl_);
|
||||
}
|
||||
|
||||
virtual void startEncryption() override
|
||||
{
|
||||
if (contextPtr_->isServer)
|
||||
{
|
||||
assert(ssl_);
|
||||
SSL_set_accept_state(ssl_);
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(ssl_);
|
||||
|
||||
const auto &protocols = policyPtr_->getAlpnProtocols();
|
||||
if (!protocols.empty())
|
||||
{
|
||||
std::string alpnList;
|
||||
alpnList.reserve(24); // some reasonable size
|
||||
for (const auto &proto : policyPtr_->getAlpnProtocols())
|
||||
{
|
||||
char ch = static_cast<char>(proto.size());
|
||||
alpnList.push_back(ch);
|
||||
alpnList.append(proto);
|
||||
}
|
||||
if (SSL_set_alpn_protos(
|
||||
ssl_,
|
||||
(const unsigned char *)(alpnList.data()),
|
||||
(unsigned int)alpnList.size()) != 0)
|
||||
{
|
||||
LOG_TRACE << "Failed to set ALPN";
|
||||
handleSSLError(SSLError::kSSLHandshakeError);
|
||||
}
|
||||
}
|
||||
|
||||
SSL_SESSION *cachedSession =
|
||||
sessionManager.get(policyPtr_->getHostname(),
|
||||
conn_->peerAddr());
|
||||
if (cachedSession)
|
||||
{
|
||||
SSL_set_session(ssl_, cachedSession);
|
||||
}
|
||||
SSL_set_connect_state(ssl_);
|
||||
}
|
||||
|
||||
processHandshake();
|
||||
}
|
||||
|
||||
virtual void recvData(MsgBuffer *buffer) override
|
||||
{
|
||||
LOG_TRACE << "Received " << buffer->readableBytes()
|
||||
<< " bytes from lower layer";
|
||||
if (buffer->readableBytes() == 0)
|
||||
return;
|
||||
while (buffer->readableBytes() > 0)
|
||||
{
|
||||
int n =
|
||||
BIO_write(rbio_, buffer->peek(), (int)buffer->readableBytes());
|
||||
if (n <= 0)
|
||||
{
|
||||
// TODO: make the status code more specific
|
||||
handleSSLError(SSLError::kSSLHandshakeError);
|
||||
return;
|
||||
}
|
||||
|
||||
buffer->retrieve(n);
|
||||
|
||||
if (!SSL_is_init_finished(ssl_))
|
||||
{
|
||||
bool handshakeDone = processHandshake();
|
||||
if (handshakeDone)
|
||||
processApplicationData();
|
||||
}
|
||||
else
|
||||
{
|
||||
processApplicationData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual void close() override
|
||||
{
|
||||
if (!SSL_is_init_finished(ssl_))
|
||||
return;
|
||||
SSL_shutdown(ssl_);
|
||||
sendTLSData();
|
||||
}
|
||||
|
||||
virtual ssize_t sendData(const char *data, size_t len) override
|
||||
{
|
||||
if (getBufferedData().readableBytes() != 0)
|
||||
{
|
||||
errno = EAGAIN;
|
||||
return 0;
|
||||
}
|
||||
// Limit the size of the data we send in one go to avoid holding massive
|
||||
// buffers in memory.
|
||||
constexpr size_t maxSend = 64 * 1024;
|
||||
size_t hasSent = 0;
|
||||
while (hasSent < len && getBufferedData().readableBytes() == 0)
|
||||
{
|
||||
auto trunkLen = len - hasSent;
|
||||
if (trunkLen > maxSend)
|
||||
trunkLen = maxSend;
|
||||
int n = SSL_write(ssl_, data + hasSent, (int)trunkLen);
|
||||
if (n <= 0 && len != 0)
|
||||
{
|
||||
handleSSLError(SSLError::kSSLProtocolError);
|
||||
return -1;
|
||||
}
|
||||
auto num = sendTLSData();
|
||||
if (num == -1)
|
||||
return -1;
|
||||
hasSent += trunkLen;
|
||||
}
|
||||
return static_cast<ssize_t>(hasSent);
|
||||
}
|
||||
|
||||
bool processHandshake()
|
||||
{
|
||||
int ret = SSL_do_handshake(ssl_);
|
||||
if (ret == 1)
|
||||
{
|
||||
LOG_TRACE << "SSL handshake finished";
|
||||
if (contextPtr_->isServer)
|
||||
{
|
||||
const char *sniName =
|
||||
SSL_get_servername(ssl_, TLSEXT_NAMETYPE_host_name);
|
||||
if (sniName)
|
||||
setSniName(sniName);
|
||||
|
||||
const unsigned char *alpn = nullptr;
|
||||
unsigned int alpnlen = 0;
|
||||
SSL_get0_alpn_selected(ssl_, &alpn, &alpnlen);
|
||||
if (alpn)
|
||||
setApplicationProtocol(std::string((char *)alpn, alpnlen));
|
||||
}
|
||||
else
|
||||
{
|
||||
setSniName(policyPtr_->getHostname());
|
||||
if (policyPtr_->getAlpnProtocols().size() > 0)
|
||||
{
|
||||
const unsigned char *alpn = nullptr;
|
||||
unsigned int alpnlen = 0;
|
||||
SSL_get0_alpn_selected(ssl_, &alpn, &alpnlen);
|
||||
if (alpn)
|
||||
{
|
||||
assert(alpnlen > 0);
|
||||
setApplicationProtocol(
|
||||
std::string((char *)alpn, alpnlen));
|
||||
}
|
||||
}
|
||||
|
||||
#if OPENSSL_VERSION_NUMBER >= 0x10101000L
|
||||
SSL_SESSION *session = SSL_get0_session(ssl_);
|
||||
assert(session);
|
||||
if (SSL_SESSION_is_resumable(session))
|
||||
{
|
||||
auto reused = SSL_session_reused(ssl_);
|
||||
if (reused == 0)
|
||||
sessionManager.store(sniName_,
|
||||
conn_->peerAddr(),
|
||||
session,
|
||||
loop_);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
auto cert = SSL_get_peer_certificate(ssl_);
|
||||
bool needCert = policyPtr_->getValidate();
|
||||
if (cert)
|
||||
setPeerCertificate(std::make_shared<OpenSSLCertificate>(cert));
|
||||
|
||||
if (needCert)
|
||||
{
|
||||
if (cert)
|
||||
{
|
||||
bool valid = internal::validatePeerCertificate(
|
||||
ssl_,
|
||||
cert,
|
||||
policyPtr_->getHostname(),
|
||||
policyPtr_->getAllowBrokenChain(),
|
||||
contextPtr_->isServer);
|
||||
if (!valid)
|
||||
{
|
||||
LOG_TRACE
|
||||
<< "SSL handshake error: invalid peer certificate";
|
||||
SSL_shutdown(ssl_);
|
||||
handleSSLError(SSLError::kSSLInvalidCertificate);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_TRACE
|
||||
<< "SSL handshake error: no peer certificate. Cannot "
|
||||
"perform validation";
|
||||
SSL_shutdown(ssl_);
|
||||
handleSSLError(SSLError::kSSLInvalidCertificate);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (handshakeCallback_)
|
||||
handshakeCallback_(conn_);
|
||||
sendTLSData(); // Needed to send ChangeCipherSpec
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
int err = SSL_get_error(ssl_, ret);
|
||||
if (err == SSL_ERROR_WANT_READ)
|
||||
{
|
||||
LOG_TRACE << "SSL handshake wants to read";
|
||||
sendTLSData();
|
||||
}
|
||||
else if (err == SSL_ERROR_WANT_WRITE)
|
||||
{
|
||||
LOG_TRACE << "SSL handshake wants to write";
|
||||
sendTLSData();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!processedHandshakeError_)
|
||||
processedHandshakeError_ = true;
|
||||
else
|
||||
return false;
|
||||
LOG_TRACE << "SSL handshake error: "
|
||||
<< ERR_error_string(ERR_get_error(), NULL);
|
||||
conn_->shutdown();
|
||||
handleSSLError(SSLError::kSSLHandshakeError);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void processApplicationData()
|
||||
{
|
||||
constexpr size_t maxSingleRead = 128 * 1024;
|
||||
constexpr size_t maxWritibleBytes = (std::numeric_limits<int>::max)();
|
||||
while (true)
|
||||
{
|
||||
auto pending = BIO_pending(rbio_);
|
||||
// horrible syntax, because MSVC
|
||||
pending = (std::max)(1024, pending);
|
||||
recvBuffer_.ensureWritableBytes(
|
||||
(std::min)(maxSingleRead, (size_t)pending));
|
||||
// clamp to int, because that's what SSL_read accepts
|
||||
const size_t wrtibleSize =
|
||||
(std::min)(maxWritibleBytes, recvBuffer_.writableBytes());
|
||||
int n = SSL_read(ssl_, recvBuffer_.beginWrite(), (int)wrtibleSize);
|
||||
int shutdownState = SSL_get_shutdown(ssl_);
|
||||
if (n == 0 && (shutdownState & SSL_RECEIVED_SHUTDOWN))
|
||||
{
|
||||
LOG_TRACE << "SSL connection closed by peer";
|
||||
conn_->shutdown();
|
||||
return;
|
||||
}
|
||||
else if (n > 0)
|
||||
{
|
||||
recvBuffer_.hasWritten(n);
|
||||
LOG_TRACE << "Received " << n << " bytes from SSL";
|
||||
if (messageCallback_)
|
||||
messageCallback_(conn_, &recvBuffer_);
|
||||
}
|
||||
else if (n <= 0)
|
||||
{
|
||||
int err = SSL_get_error(ssl_, n);
|
||||
if (err == SSL_ERROR_ZERO_RETURN)
|
||||
{
|
||||
// Clean shutdown
|
||||
LOG_TRACE << "SSL connection closed cleanly";
|
||||
conn_->shutdown();
|
||||
return;
|
||||
}
|
||||
if (err == SSL_ERROR_SSL || err == SSL_ERROR_SYSCALL)
|
||||
{
|
||||
handleSSLError(SSLError::kSSLProtocolError);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ssize_t sendTLSData()
|
||||
{
|
||||
void *data = nullptr;
|
||||
int len = BIO_get_mem_data(wbio_, &data);
|
||||
if (len < 0 || data == nullptr)
|
||||
return -1;
|
||||
if (len == 0)
|
||||
return 0;
|
||||
int n = writeCallback_(conn_, data, len);
|
||||
|
||||
if (n >= 0)
|
||||
{
|
||||
appendToWriteBuffer((char *)data + n, len - n);
|
||||
}
|
||||
(void)BIO_reset(wbio_);
|
||||
if (n < 0)
|
||||
return -1;
|
||||
return len;
|
||||
}
|
||||
|
||||
void handleSSLError(SSLError error)
|
||||
{
|
||||
sendTLSData();
|
||||
|
||||
if (!processedSslError_)
|
||||
processedSslError_ = true;
|
||||
else
|
||||
return;
|
||||
if (errorCallback_)
|
||||
errorCallback_(conn_, error);
|
||||
}
|
||||
|
||||
SSL *ssl_;
|
||||
BIO *rbio_;
|
||||
BIO *wbio_;
|
||||
bool processedHandshakeError_{false};
|
||||
bool processedSslError_{false};
|
||||
};
|
||||
|
||||
std::shared_ptr<TLSProvider> trantor::newTLSProvider(TcpConnection *conn,
|
||||
TLSPolicyPtr policy,
|
||||
SSLContextPtr ctx)
|
||||
{
|
||||
return std::make_shared<OpenSSLProvider>(conn,
|
||||
std::move(policy),
|
||||
std::move(ctx));
|
||||
}
|
||||
|
||||
SSLContextPtr trantor::newSSLContext(const TLSPolicy &policy, bool isServer)
|
||||
{
|
||||
auto ctx = std::make_shared<SSLContext>(policy.getUseOldTLS(),
|
||||
policy.getConfCmds(),
|
||||
isServer);
|
||||
if (!policy.getCertPath().empty() && !policy.getKeyPath().empty())
|
||||
{
|
||||
if (SSL_CTX_use_certificate_chain_file(ctx->ctx(),
|
||||
policy.getCertPath().data()) <=
|
||||
0)
|
||||
{
|
||||
throw std::runtime_error("Failed to load certificate " +
|
||||
policy.getCertPath());
|
||||
}
|
||||
if (SSL_CTX_use_PrivateKey_file(ctx->ctx(),
|
||||
policy.getKeyPath().data(),
|
||||
SSL_FILETYPE_PEM) <= 0)
|
||||
{
|
||||
throw std::runtime_error("Failed to load private key");
|
||||
}
|
||||
if (SSL_CTX_check_private_key(ctx->ctx()) == 0)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
"Private key does not match the "
|
||||
"certificate public key");
|
||||
}
|
||||
}
|
||||
if (policy.getValidate() && policy.getUseSystemCertStore())
|
||||
{
|
||||
#ifdef _WIN32
|
||||
internal::loadWindowsSystemCert(SSL_CTX_get_cert_store(ctx->ctx()));
|
||||
#else
|
||||
SSL_CTX_set_default_verify_paths(ctx->ctx());
|
||||
#endif
|
||||
}
|
||||
|
||||
if (!policy.getCaPath().empty())
|
||||
{
|
||||
if (isServer)
|
||||
{
|
||||
if (SSL_CTX_load_verify_locations(ctx->ctx(),
|
||||
policy.getCaPath().data(),
|
||||
nullptr) <= 0)
|
||||
{
|
||||
throw std::runtime_error("Failed to load CA certificate");
|
||||
}
|
||||
|
||||
STACK_OF(X509_NAME) *cert_names =
|
||||
SSL_load_client_CA_file(policy.getCaPath().data());
|
||||
if (cert_names == nullptr)
|
||||
{
|
||||
throw std::runtime_error("Not CA names found in file");
|
||||
}
|
||||
SSL_CTX_set_client_CA_list(ctx->ctx(), cert_names);
|
||||
SSL_CTX_set_verify(ctx->ctx(),
|
||||
SSL_VERIFY_PEER |
|
||||
SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
|
||||
nullptr);
|
||||
LOG_TRACE << "Finished loading custom CA";
|
||||
}
|
||||
else
|
||||
{
|
||||
auto *store = X509_STORE_new();
|
||||
if (!X509_STORE_load_locations(store,
|
||||
policy.getCaPath().data(),
|
||||
nullptr))
|
||||
{
|
||||
throw std::runtime_error("Failed to load CA certificate");
|
||||
}
|
||||
SSL_CTX_set_cert_store(ctx->ctx(), store);
|
||||
}
|
||||
}
|
||||
|
||||
if (!policy.getAlpnProtocols().empty() && isServer)
|
||||
{
|
||||
SSL_CTX_set_alpn_select_cb(ctx->ctx(),
|
||||
internal::serverSelectProtocol,
|
||||
(void *)&policy.getAlpnProtocols());
|
||||
}
|
||||
|
||||
if (!isServer)
|
||||
{
|
||||
// We have our own session cache, so disable OpenSSL's
|
||||
SSL_CTX_set_session_cache_mode(ctx->ctx(), SSL_SESS_CACHE_OFF);
|
||||
}
|
||||
|
||||
// Disable weak ciphers. Weak hash and ciphers can die in a fire.
|
||||
int status = SSL_CTX_set_cipher_list(ctx->ctx(),
|
||||
"MEDIUM:HIGH:!aNULL:!MD5:!RC4:!3DES");
|
||||
if (status != 1)
|
||||
throw std::runtime_error("Failed to select secure ciphers");
|
||||
|
||||
return ctx;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#include <trantor/utils/Logger.h>
|
||||
#include <trantor/utils/AsyncFileLogger.h>
|
||||
#include <stdlib.h>
|
||||
#include <thread>
|
||||
int main()
|
||||
{
|
||||
trantor::AsyncFileLogger asyncFileLogger;
|
||||
asyncFileLogger.setFileName("async_test");
|
||||
asyncFileLogger.startLogging();
|
||||
trantor::Logger::setOutputFunction(
|
||||
[&](const char *msg, const uint64_t len) {
|
||||
asyncFileLogger.output(msg, len);
|
||||
},
|
||||
[&]() { asyncFileLogger.flush(); });
|
||||
asyncFileLogger.setFileSizeLimit(100000000);
|
||||
// LOG_DEBUG<<"debug log!"<<1;
|
||||
// LOG_TRACE<<"trace log!"<<2;
|
||||
// LOG_INFO<<"info log!"<<3;
|
||||
// LOG_WARN<<"warning log!"<<4;
|
||||
// if(1)
|
||||
// LOG_ERROR<<"error log!"<<5;
|
||||
// std::thread thread_([](){
|
||||
// LOG_FATAL<<"fatal log!"<<6;
|
||||
// });
|
||||
//
|
||||
// FILE *fp=fopen("/notexistfile","rb");
|
||||
// if(fp==NULL)
|
||||
// {
|
||||
// LOG_SYSERR<<"syserr log!"<<7;
|
||||
// }
|
||||
|
||||
int i = 0;
|
||||
while (i < 1000000)
|
||||
{
|
||||
++i;
|
||||
if (i % 100 == 0)
|
||||
{
|
||||
LOG_ERROR << "this is the " << i << "th log";
|
||||
continue;
|
||||
}
|
||||
LOG_INFO << "this is the " << i << "th log";
|
||||
++i;
|
||||
LOG_DEBUG << "this is the " << i << "th log";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
#include <trantor/utils/Logger.h>
|
||||
#include <trantor/utils/AsyncFileLogger.h>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <stdlib.h>
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
int main()
|
||||
{
|
||||
trantor::AsyncFileLogger asyncFileLogger;
|
||||
asyncFileLogger.setFileName("async_test");
|
||||
asyncFileLogger.startLogging();
|
||||
trantor::Logger::setOutputFunction(
|
||||
[&](const char *msg, const uint64_t len) {
|
||||
asyncFileLogger.output(msg, len);
|
||||
},
|
||||
[&]() { asyncFileLogger.flush(); });
|
||||
asyncFileLogger.setFileSizeLimit(100000000);
|
||||
int i = 0;
|
||||
while (i < 1000000)
|
||||
{
|
||||
++i;
|
||||
if (i % 100 == 0)
|
||||
{
|
||||
LOG_ERROR << "this is the " << i << "th log";
|
||||
continue;
|
||||
}
|
||||
LOG_INFO << "this is the " << i << "th log";
|
||||
++i;
|
||||
LOG_DEBUG << "this is the " << i << "th log";
|
||||
std::this_thread::sleep_for(1s);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
add_executable(ssl_server_test SSLServerTest.cc)
|
||||
add_executable(ssl_client_test SSLClientTest.cc)
|
||||
add_executable(serial_task_queue_test1 SerialTaskQueueTest1.cc)
|
||||
add_executable(serial_task_queue_test2 SerialTaskQueueTest2.cc)
|
||||
add_executable(timer_test TimerTest.cc)
|
||||
add_executable(timer_test1 TimerTest1.cc)
|
||||
add_executable(run_in_loop_test1 RunInLoopTest1.cc)
|
||||
add_executable(run_in_loop_test2 RunInLoopTest2.cc)
|
||||
add_executable(logger_test LoggerTest.cc)
|
||||
add_executable(async_file_logger_test AsyncFileLoggerTest.cc)
|
||||
add_executable(tcp_server_test TcpServerTest.cc)
|
||||
add_executable(concurrent_task_queue_test ConcurrentTaskQueueTest.cc)
|
||||
add_executable(tcp_client_test TcpClientTest.cc)
|
||||
add_executable(async_file_logger_test1 AsyncFileLoggerTest1.cc)
|
||||
add_executable(sendfile_test SendfileTest.cc)
|
||||
add_executable(sendstream_test SendstreamTest.cc)
|
||||
add_executable(timing_wheel_test TimingWheelTest.cc)
|
||||
add_executable(kickoff_test KickoffTest.cc)
|
||||
add_executable(dns_test DnsTest.cc)
|
||||
add_executable(run_on_quit_test RunOnQuitTest.cc)
|
||||
add_executable(path_conversion_test PathConversionTest.cc)
|
||||
add_executable(logger_macro_test LoggerMacroTest.cc)
|
||||
add_executable(delayed_ssl_server_test DelayedSSLServerTest.cc)
|
||||
add_executable(delayed_ssl_client_test DelayedSSLClientTest.cc)
|
||||
add_executable(tcp_asyncstream_server_test TcpAsyncStreamServerTest.cc)
|
||||
set(targets_list
|
||||
ssl_server_test
|
||||
ssl_client_test
|
||||
serial_task_queue_test1
|
||||
serial_task_queue_test2
|
||||
timer_test
|
||||
timer_test1
|
||||
run_in_loop_test1
|
||||
run_in_loop_test2
|
||||
logger_test
|
||||
async_file_logger_test
|
||||
tcp_server_test
|
||||
concurrent_task_queue_test
|
||||
tcp_client_test
|
||||
async_file_logger_test1
|
||||
sendfile_test
|
||||
sendstream_test
|
||||
timing_wheel_test
|
||||
kickoff_test
|
||||
dns_test
|
||||
run_on_quit_test
|
||||
path_conversion_test
|
||||
logger_macro_test
|
||||
delayed_ssl_server_test
|
||||
delayed_ssl_client_test
|
||||
tcp_asyncstream_server_test
|
||||
)
|
||||
|
||||
if(HAVE_SPDLOG)
|
||||
add_executable(spdlogger_test SpdLoggerTest.cc)
|
||||
list(APPEND targets_list spdlogger_test)
|
||||
endif(HAVE_SPDLOG)
|
||||
|
||||
set_property(TARGET ${targets_list} PROPERTY CXX_STANDARD 14)
|
||||
set_property(TARGET ${targets_list} PROPERTY CXX_STANDARD_REQUIRED ON)
|
||||
set_property(TARGET ${targets_list} PROPERTY CXX_EXTENSIONS OFF)
|
||||
|
||||
foreach(T ${targets_list})
|
||||
target_link_libraries(${T} PRIVATE trantor)
|
||||
endforeach()
|
||||
@@ -0,0 +1,37 @@
|
||||
#include <trantor/utils/ConcurrentTaskQueue.h>
|
||||
#include <trantor/utils/Logger.h>
|
||||
#include <iostream>
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <time.h>
|
||||
#include <stdio.h>
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
int main()
|
||||
{
|
||||
trantor::ConcurrentTaskQueue queue(5, "concurrT");
|
||||
std::atomic_int sum;
|
||||
sum = 0;
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
queue.runTaskInQueue([&sum]() {
|
||||
LOG_DEBUG << "add sum";
|
||||
for (int i = 0; i < 10000; ++i)
|
||||
{
|
||||
++sum;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
queue.runTaskInQueue([&sum]() {
|
||||
for (int i = 0; i < 20; ++i)
|
||||
{
|
||||
LOG_DEBUG << "sum=" << sum;
|
||||
std::this_thread::sleep_for(100us);
|
||||
}
|
||||
});
|
||||
|
||||
getc(stdin);
|
||||
LOG_DEBUG << "sum=" << sum;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
#include <trantor/net/TcpClient.h>
|
||||
#include <trantor/utils/Logger.h>
|
||||
#include <trantor/net/EventLoopThread.h>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <atomic>
|
||||
using namespace trantor;
|
||||
#define USE_IPV6 0
|
||||
int main()
|
||||
{
|
||||
trantor::Logger::setLogLevel(trantor::Logger::kTrace);
|
||||
LOG_DEBUG << "TcpClient class test!";
|
||||
EventLoop loop;
|
||||
#if USE_IPV6
|
||||
InetAddress serverAddr("::1", 8888, true);
|
||||
#else
|
||||
InetAddress serverAddr("127.0.0.1", 8888);
|
||||
#endif
|
||||
std::shared_ptr<trantor::TcpClient> client[10];
|
||||
std::atomic_int connCount;
|
||||
connCount = 1;
|
||||
for (int i = 0; i < 1; ++i)
|
||||
{
|
||||
client[i] = std::make_shared<trantor::TcpClient>(&loop,
|
||||
serverAddr,
|
||||
"tcpclienttest");
|
||||
client[i]->setConnectionCallback(
|
||||
[i, &loop, &connCount](const TcpConnectionPtr &conn) {
|
||||
if (conn->connected())
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_DEBUG << i << " disconnected";
|
||||
--connCount;
|
||||
if (connCount == 0)
|
||||
loop.quit();
|
||||
}
|
||||
});
|
||||
client[i]->setMessageCallback([](const TcpConnectionPtr &conn,
|
||||
MsgBuffer *buf) {
|
||||
auto msg = std::string(buf->peek(), buf->readableBytes());
|
||||
|
||||
LOG_INFO << msg;
|
||||
if (msg == "hello")
|
||||
{
|
||||
buf->retrieveAll();
|
||||
auto policy = TLSPolicy::defaultClientPolicy();
|
||||
policy->setValidate(false);
|
||||
conn->startEncryption(
|
||||
policy, false, [](const TcpConnectionPtr &encryptedConn) {
|
||||
LOG_INFO << "SSL established";
|
||||
encryptedConn->send("Hello");
|
||||
});
|
||||
}
|
||||
if (conn->isSSLConnection())
|
||||
{
|
||||
buf->retrieveAll();
|
||||
}
|
||||
});
|
||||
client[i]->connect();
|
||||
}
|
||||
loop.loop();
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
#include <trantor/net/TcpServer.h>
|
||||
#include <trantor/utils/Logger.h>
|
||||
#include <trantor/net/EventLoopThread.h>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
using namespace trantor;
|
||||
#define USE_IPV6 0
|
||||
int main()
|
||||
{
|
||||
LOG_DEBUG << "test start";
|
||||
Logger::setLogLevel(Logger::kTrace);
|
||||
EventLoopThread loopThread;
|
||||
loopThread.run();
|
||||
#if USE_IPV6
|
||||
InetAddress addr(8888, true, true);
|
||||
#else
|
||||
InetAddress addr(8888);
|
||||
#endif
|
||||
TcpServer server(loopThread.getLoop(), addr, "test");
|
||||
// auto ctx = newSSLServerContext("server.pem", "server.pem", {});
|
||||
LOG_INFO << "start";
|
||||
server.setRecvMessageCallback(
|
||||
[](const TcpConnectionPtr &connectionPtr, MsgBuffer *buffer) {
|
||||
LOG_DEBUG << std::string{buffer->peek(), buffer->readableBytes()};
|
||||
connectionPtr->send(*buffer);
|
||||
buffer->retrieveAll();
|
||||
connectionPtr->shutdown();
|
||||
});
|
||||
server.setConnectionCallback([](const TcpConnectionPtr &connPtr) {
|
||||
if (connPtr->connected())
|
||||
{
|
||||
LOG_DEBUG << "New connection";
|
||||
connPtr->send("hello");
|
||||
auto policy =
|
||||
TLSPolicy::defaultServerPolicy("server.crt", "server.key");
|
||||
connPtr->startEncryption(policy, true);
|
||||
}
|
||||
else if (connPtr->disconnected())
|
||||
{
|
||||
LOG_DEBUG << "connection disconnected";
|
||||
}
|
||||
});
|
||||
server.setIoLoopNum(3);
|
||||
server.start();
|
||||
loopThread.wait();
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
#include <trantor/net/Resolver.h>
|
||||
#include <iostream>
|
||||
void dns(const std::shared_ptr<trantor::Resolver> &resolver)
|
||||
{
|
||||
auto now = trantor::Date::now();
|
||||
resolver->resolve("www.baidu.com", [now](const trantor::InetAddress &addr) {
|
||||
auto interval = trantor::Date::now().microSecondsSinceEpoch() -
|
||||
now.microSecondsSinceEpoch();
|
||||
std::cout << "baidu:" << addr.toIp() << " " << interval / 1000 << "ms"
|
||||
<< std::endl;
|
||||
});
|
||||
resolver->resolve("www.baidu.com",
|
||||
[now](const std::vector<trantor::InetAddress> &addrs) {
|
||||
auto interval =
|
||||
trantor::Date::now().microSecondsSinceEpoch() -
|
||||
now.microSecondsSinceEpoch();
|
||||
for (auto &addr : addrs)
|
||||
std::cout << "baidu:" << addr.toIp() << " "
|
||||
<< interval / 1000 << "ms" << std::endl;
|
||||
});
|
||||
resolver->resolve("www.google.com",
|
||||
[now](const trantor::InetAddress &addr) {
|
||||
auto interval =
|
||||
trantor::Date::now().microSecondsSinceEpoch() -
|
||||
now.microSecondsSinceEpoch();
|
||||
std::cout << "google:" << addr.toIp() << " "
|
||||
<< interval / 1000 << "ms" << std::endl;
|
||||
});
|
||||
resolver->resolve("www.sina.com", [now](const trantor::InetAddress &addr) {
|
||||
auto interval = trantor::Date::now().microSecondsSinceEpoch() -
|
||||
now.microSecondsSinceEpoch();
|
||||
std::cout << "sina:" << addr.toIp() << " " << interval / 1000 << "ms"
|
||||
<< std::endl;
|
||||
});
|
||||
resolver->resolve("www.xjfisfjaskfeiakdjfg.com",
|
||||
[now](const trantor::InetAddress &addr) {
|
||||
auto interval =
|
||||
trantor::Date::now().microSecondsSinceEpoch() -
|
||||
now.microSecondsSinceEpoch();
|
||||
std::cout << "bad address:" << addr.toIp() << " "
|
||||
<< interval / 1000 << "ms" << std::endl;
|
||||
});
|
||||
resolver->resolve("localhost", [now](const trantor::InetAddress &addr) {
|
||||
auto interval = trantor::Date::now().microSecondsSinceEpoch() -
|
||||
now.microSecondsSinceEpoch();
|
||||
std::cout << "localhost:" << addr.toIp() << " " << interval / 1000
|
||||
<< "ms" << std::endl;
|
||||
});
|
||||
}
|
||||
int main()
|
||||
{
|
||||
trantor::EventLoop loop;
|
||||
auto resolver = trantor::Resolver::newResolver(&loop);
|
||||
dns(resolver);
|
||||
loop.runAfter(1.0, [resolver]() { dns(resolver); });
|
||||
loop.loop();
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
#include <trantor/net/TcpServer.h>
|
||||
#include <trantor/utils/Logger.h>
|
||||
#include <trantor/net/EventLoopThread.h>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
using namespace trantor;
|
||||
#define USE_IPV6 0
|
||||
int main()
|
||||
{
|
||||
LOG_DEBUG << "test start";
|
||||
Logger::setLogLevel(Logger::kTrace);
|
||||
EventLoop loop;
|
||||
#if USE_IPV6
|
||||
InetAddress addr(8888, true, true);
|
||||
#else
|
||||
InetAddress addr(8888);
|
||||
#endif
|
||||
TcpServer server(&loop, addr, "test");
|
||||
server.kickoffIdleConnections(10);
|
||||
server.setRecvMessageCallback(
|
||||
[](const TcpConnectionPtr &connectionPtr, MsgBuffer *buffer) {
|
||||
// LOG_DEBUG<<"recv callback!";
|
||||
std::cout << std::string(buffer->peek(), buffer->readableBytes());
|
||||
connectionPtr->send(buffer->peek(), buffer->readableBytes());
|
||||
buffer->retrieveAll();
|
||||
});
|
||||
int n = 0;
|
||||
server.setConnectionCallback([&n](const TcpConnectionPtr &connPtr) {
|
||||
if (connPtr->connected())
|
||||
{
|
||||
++n;
|
||||
if (n % 2 == 0)
|
||||
{
|
||||
connPtr->keepAlive();
|
||||
}
|
||||
LOG_DEBUG << "New connection";
|
||||
}
|
||||
else if (connPtr->disconnected())
|
||||
{
|
||||
LOG_DEBUG << "connection disconnected";
|
||||
}
|
||||
});
|
||||
server.setIoLoopNum(3);
|
||||
server.start();
|
||||
loop.loop();
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#include <trantor/utils/Logger.h>
|
||||
|
||||
using namespace trantor;
|
||||
|
||||
int main()
|
||||
{
|
||||
trantor::Logger::setLogLevel(trantor::Logger::kInfo);
|
||||
if (0)
|
||||
LOG_INFO << "dummy";
|
||||
else
|
||||
LOG_WARN << "it works";
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
*
|
||||
* # Generate CA file
|
||||
* openssl req -new -x509 -days 365 -keyout ca-key.pem -out ca-crt.pem
|
||||
*
|
||||
* # Generate Key File (ie: same for client and server, but you can create one
|
||||
* for each one) openssl genrsa -out server-key.pem 4096
|
||||
*
|
||||
* # Generate Server certificate:
|
||||
* openssl req -new -sha256 -key server-key.pem -out ca-csr.pem
|
||||
* openssl x509 -req -days 365 -in ca-csr.pem -CA ca-crt.pem -CAkey ca-key.pem
|
||||
* -CAcreateserial -out server-crt.pem openssl verify -CAfile ca-crt.pem
|
||||
* server-crt.pem
|
||||
*
|
||||
*
|
||||
* # For client (to specify a certificate client mode only - no domain):
|
||||
* # Create file client_cert_ext.cnf:
|
||||
* cat client_cert_ext.cnf
|
||||
*
|
||||
* keyUsage = critical, digitalSignature, keyEncipherment
|
||||
* extendedKeyUsage = clientAuth
|
||||
* basicConstraints = critical, CA:FALSE
|
||||
* authorityKeyIdentifier = keyid,issuer
|
||||
* subjectAltName = DNS:Client
|
||||
*
|
||||
* Create client cert (using the same serve key and CA)
|
||||
* openssl x509 -req -in ca-csr.pem -days 1000 -CA ca-crt.pem -CAkey ca-key.pem
|
||||
* -set_serial 01 -extfile client_cert_ext.cnf > client-crt.pem
|
||||
*
|
||||
* openssl verify -CAfile ca-crt.pem client-crt.pem
|
||||
* openssl x509 -in client-crt.pem -text -noout -purpose
|
||||
*
|
||||
* # Compile sample:
|
||||
*
|
||||
* g++ -o MTLSClient MTLSClient.cc -ltrantor -lssl -lcrypto -lpthread
|
||||
*
|
||||
* # Tests
|
||||
*
|
||||
* # Listen generic SSL server
|
||||
* openssl s_server -accept 8888 -CAfile ./ca-crt.pem -cert ./server-crt.pem
|
||||
* -key ./server-key.pem -state
|
||||
*
|
||||
* # Listen generic SSL server with mTLS verification
|
||||
* openssl s_server -accept 8888 -CAfile ./ca-crt.pem -cert ./server-crt.pem
|
||||
* -key ./server-key.pem -state -verify_return_error -Verify 1
|
||||
*
|
||||
* # Test the mTLS client bin
|
||||
* ./MTLSClient
|
||||
*
|
||||
* **/
|
||||
|
||||
#include <trantor/net/TcpClient.h>
|
||||
#include <trantor/utils/Logger.h>
|
||||
#include <trantor/net/EventLoopThread.h>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <atomic>
|
||||
using namespace trantor;
|
||||
#define USE_IPV6 0
|
||||
int main()
|
||||
{
|
||||
trantor::Logger::setLogLevel(trantor::Logger::kTrace);
|
||||
LOG_DEBUG << "TcpClient class test!";
|
||||
EventLoop loop;
|
||||
#if USE_IPV6
|
||||
InetAddress serverAddr("::1", 8888, true);
|
||||
#else
|
||||
InetAddress serverAddr("127.0.0.1", 8888);
|
||||
#endif
|
||||
std::shared_ptr<trantor::TcpClient> client[10];
|
||||
std::atomic_int connCount;
|
||||
connCount = 1;
|
||||
for (int i = 0; i < connCount; ++i)
|
||||
{
|
||||
client[i] = std::make_shared<trantor::TcpClient>(&loop,
|
||||
serverAddr,
|
||||
"tcpclienttest");
|
||||
std::vector<std::pair<std::string, std::string>> sslcmd = {};
|
||||
// That key is common for client and server
|
||||
// The CA file must be the client CA, for this sample the CA is common
|
||||
// for both
|
||||
auto policy = TLSPolicy::defaultClientPolicy();
|
||||
policy->setCertPath("./client-crt.pem")
|
||||
.setKeyPath("./server-key.pem")
|
||||
.setCaPath("./ca-crt.pem")
|
||||
.setHostname("localhost");
|
||||
client[i]->enableSSL(policy);
|
||||
client[i]->setConnectionCallback(
|
||||
[i, &loop, &connCount](const TcpConnectionPtr &conn) {
|
||||
if (conn->connected())
|
||||
{
|
||||
LOG_DEBUG << i << " connected!";
|
||||
char tmp[20];
|
||||
sprintf(tmp, "%d client!!", i);
|
||||
conn->send(tmp);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_DEBUG << i << " disconnected";
|
||||
--connCount;
|
||||
if (connCount == 0)
|
||||
loop.quit();
|
||||
}
|
||||
});
|
||||
client[i]->setMessageCallback(
|
||||
[](const TcpConnectionPtr &conn, MsgBuffer *buf) {
|
||||
LOG_DEBUG << std::string(buf->peek(), buf->readableBytes());
|
||||
buf->retrieveAll();
|
||||
conn->shutdown();
|
||||
});
|
||||
client[i]->connect();
|
||||
}
|
||||
loop.loop();
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
*
|
||||
* # Generate CA file
|
||||
* openssl req -new -x509 -days 365 -keyout ca-key.pem -out ca-crt.pem
|
||||
*
|
||||
* # Generate Key File (ie: same for client and server, but you can create one
|
||||
* for each one) openssl genrsa -out server-key.pem 4096
|
||||
*
|
||||
* # Generate Server certificate:
|
||||
* openssl req -new -sha256 -key server-key.pem -out ca-csr.pem
|
||||
* openssl x509 -req -days 365 -in ca-csr.pem -CA ca-crt.pem -CAkey ca-key.pem
|
||||
* -CAcreateserial -out server-crt.pem openssl verify -CAfile ca-crt.pem
|
||||
* server-crt.pem
|
||||
*
|
||||
*
|
||||
* # For client (to specify a certificate client mode only - no domain):
|
||||
* # Create file client_cert_ext.cnf:
|
||||
* cat client_cert_ext.cnf
|
||||
*
|
||||
* keyUsage = critical, digitalSignature, keyEncipherment
|
||||
* extendedKeyUsage = clientAuth
|
||||
* basicConstraints = critical, CA:FALSE
|
||||
* authorityKeyIdentifier = keyid,issuer
|
||||
* subjectAltName = DNS:Client
|
||||
*
|
||||
* Create client cert (using the same serve key and CA)
|
||||
* openssl x509 -req -in ca-csr.pem -days 1000 -CA ca-crt.pem -CAkey ca-key.pem
|
||||
* -set_serial 01 -extfile client_cert_ext.cnf > client-crt.pem
|
||||
*
|
||||
* openssl verify -CAfile ca-crt.pem client-crt.pem
|
||||
* openssl x509 -in client-crt.pem -text -noout -purpose
|
||||
*
|
||||
* # Compile sample:
|
||||
*
|
||||
* g++ -o MTLSServer MTLSServer.cc -ltrantor -lssl -lcrypto -lpthread
|
||||
*
|
||||
* # Tests
|
||||
*
|
||||
* # Should Fail
|
||||
* openssl s_client -connect 0.0.0.0:8888 -state
|
||||
*
|
||||
* # Should Connect (the CA file must be the server CA), for this sample the CA
|
||||
* is common for both openssl s_client -connect 0.0.0.0:8888 -key
|
||||
* ./server-key.pem -cert ./server-crt.pem -CAfile ./ca-crt.pem -state
|
||||
*
|
||||
* **/
|
||||
|
||||
#include <trantor/net/TcpServer.h>
|
||||
#include <trantor/utils/Logger.h>
|
||||
#include <trantor/net/EventLoopThread.h>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
using namespace trantor;
|
||||
#define USE_IPV6 0
|
||||
int main()
|
||||
{
|
||||
LOG_DEBUG << "test start";
|
||||
Logger::setLogLevel(Logger::kTrace);
|
||||
EventLoopThread loopThread;
|
||||
loopThread.run();
|
||||
#if USE_IPV6
|
||||
InetAddress addr(8888, true, true);
|
||||
#else
|
||||
InetAddress addr(8888);
|
||||
#endif
|
||||
TcpServer server(loopThread.getLoop(), addr, "test");
|
||||
std::vector<std::pair<std::string, std::string>> sslcmd = {};
|
||||
|
||||
// the CA file must be the client CA, for this sample the CA is common for
|
||||
// both
|
||||
auto policy =
|
||||
TLSPolicy::defaultServerPolicy("server-crt.pem", "server-key.pem");
|
||||
policy->setCaPath("ca-crt.pem")
|
||||
.setValidateChain(true)
|
||||
.setValidateDate(true)
|
||||
.setValidateDomain(false); // client's don't have a domain name
|
||||
server.enableSSL(policy);
|
||||
server.setRecvMessageCallback(
|
||||
[](const TcpConnectionPtr &connectionPtr, MsgBuffer *buffer) {
|
||||
// LOG_DEBUG<<"recv callback!";
|
||||
std::cout << std::string(buffer->peek(), buffer->readableBytes());
|
||||
connectionPtr->send(buffer->peek(), buffer->readableBytes());
|
||||
buffer->retrieveAll();
|
||||
connectionPtr->forceClose();
|
||||
});
|
||||
server.setConnectionCallback([](const TcpConnectionPtr &connPtr) {
|
||||
if (connPtr->connected())
|
||||
{
|
||||
LOG_DEBUG << "New connection";
|
||||
connPtr->send("Hello world\r\n");
|
||||
}
|
||||
else if (connPtr->disconnected())
|
||||
{
|
||||
LOG_DEBUG << "connection disconnected";
|
||||
}
|
||||
});
|
||||
server.setIoLoopNum(3);
|
||||
server.start();
|
||||
loopThread.wait();
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
#include <trantor/utils/Utilities.h>
|
||||
#include <trantor/utils/Logger.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int main()
|
||||
{
|
||||
trantor::Logger::setLogLevel(trantor::Logger::kTrace);
|
||||
LOG_DEBUG << "PathConversion utils test!";
|
||||
|
||||
#ifdef _WIN32
|
||||
std::string utf8PathStandard("C:/Temp/\xE4\xB8\xAD\xE6\x96\x87");
|
||||
std::string utf8PathAlt("C:\\Temp\\\xE4\xB8\xAD\xE6\x96\x87");
|
||||
std::wstring widePathStandard(L"C:\\Temp\\\u4E2D\u6587");
|
||||
std::wstring widePathAlt(L"C:/Temp/\u4E2D\u6587");
|
||||
std::string utf8WidePathStandard{utf8PathAlt};
|
||||
std::string utf8WidePathAlt{utf8PathStandard};
|
||||
#else // _WIN32
|
||||
std::string utf8PathStandard("/tmp/\xE4\xB8\xAD\xE6\x96\x87");
|
||||
std::string utf8PathAlt(
|
||||
"\\tmp\\\xE4\xB8\xAD\xE6\x96\x87"); // Invalid, won't be changed
|
||||
std::wstring widePathStandard(L"/tmp/\u4E2D\u6587");
|
||||
std::wstring widePathAlt(L"\\tmp\\\u4E2D\u6587");
|
||||
std::string utf8WidePathStandard{utf8PathStandard};
|
||||
std::string utf8WidePathAlt{utf8PathAlt};
|
||||
#endif // _WIN32
|
||||
|
||||
// 1. Check from/to UTF-8
|
||||
#ifdef _WIN32
|
||||
if (utf8PathAlt != trantor::utils::toUtf8(widePathStandard))
|
||||
#else // _WIN32
|
||||
if (utf8PathStandard != trantor::utils::toUtf8(widePathStandard))
|
||||
#endif // _WIN32
|
||||
LOG_ERROR << "Error converting " << utf8WidePathStandard
|
||||
<< " from wide string to utf-8";
|
||||
#ifdef _WIN32
|
||||
if (utf8PathStandard != trantor::utils::toUtf8(widePathAlt))
|
||||
#else // _WIN32
|
||||
if (utf8PathAlt != trantor::utils::toUtf8(widePathAlt))
|
||||
#endif // _WIN32
|
||||
LOG_ERROR << "Error converting " << utf8WidePathAlt
|
||||
<< " from wide string to utf-8";
|
||||
#ifdef _WIN32
|
||||
if (widePathAlt != trantor::utils::fromUtf8(utf8PathStandard))
|
||||
#else // _WIN32
|
||||
if (widePathStandard != trantor::utils::fromUtf8(utf8PathStandard))
|
||||
#endif // _WIN32
|
||||
LOG_ERROR << "Error converting " << utf8PathStandard
|
||||
<< " from utf-8 to wide string";
|
||||
#ifdef _WIN32
|
||||
if (widePathStandard != trantor::utils::fromUtf8(utf8PathAlt))
|
||||
#else // _WIN32
|
||||
if (widePathAlt != trantor::utils::fromUtf8(utf8PathAlt))
|
||||
#endif // _WIN32
|
||||
LOG_ERROR << "Error converting " << utf8PathAlt
|
||||
<< " from utf-8 to wide string";
|
||||
|
||||
// 2. Check path conversion. Note: The directory separator should be changed
|
||||
// on Windows only
|
||||
if (utf8PathStandard != trantor::utils::fromWidePath(widePathStandard))
|
||||
LOG_ERROR << "Error converting " << utf8WidePathStandard
|
||||
<< " from wide path to utf-8";
|
||||
#ifdef _WIN32
|
||||
if (utf8PathStandard != trantor::utils::fromWidePath(widePathAlt))
|
||||
#else // _WIN32
|
||||
if (utf8PathAlt != trantor::utils::fromWidePath(widePathAlt))
|
||||
#endif // _WIN32
|
||||
LOG_ERROR << "Error converting " << utf8WidePathAlt
|
||||
<< " from wide path to utf-8";
|
||||
if (widePathStandard != trantor::utils::toWidePath(utf8PathStandard))
|
||||
LOG_ERROR << "Error converting " << utf8WidePathStandard
|
||||
<< " from utf-8 to wide path";
|
||||
#ifdef _WIN32
|
||||
if (widePathStandard != trantor::utils::toWidePath(utf8PathAlt))
|
||||
#else // _WIN32
|
||||
if (widePathAlt != trantor::utils::toWidePath(utf8PathAlt))
|
||||
#endif // _WIN32
|
||||
LOG_ERROR << "Error converting " << utf8PathAlt
|
||||
<< " from utf-8 to wide path";
|
||||
|
||||
// 3. From/to native path
|
||||
auto nativePath1 = trantor::utils::toNativePath(widePathStandard);
|
||||
auto nativePath2 = trantor::utils::toNativePath(utf8PathStandard);
|
||||
if (nativePath1 != nativePath2)
|
||||
LOG_ERROR << "Error converting " << utf8PathStandard
|
||||
<< " to native path";
|
||||
if (utf8PathStandard != trantor::utils::fromNativePath(nativePath1))
|
||||
LOG_ERROR << "Error converting " << utf8PathStandard
|
||||
<< " from native to utf-8 path";
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// Created by antao on 1/14/17.
|
||||
//
|
||||
|
||||
#include <trantor/net/EventLoop.h>
|
||||
#ifndef _WIN32
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
using namespace std::chrono_literals;
|
||||
int main()
|
||||
{
|
||||
trantor::EventLoop loop;
|
||||
std::thread thread([&loop]() {
|
||||
std::this_thread::sleep_for(3s);
|
||||
loop.runInLoop([&loop]() {
|
||||
std::cout << "runInLoop called in other thread" << std::endl;
|
||||
loop.queueInLoop(
|
||||
[]() { std::cout << "queueInLoop in runInLoop" << std::endl; });
|
||||
});
|
||||
});
|
||||
loop.runInLoop([]() { std::cout << "runInLoop 1" << std::endl; });
|
||||
loop.runInLoop([]() { std::cout << "runInLoop 2" << std::endl; });
|
||||
loop.queueInLoop([]() { std::cout << "queueInLoop 1" << std::endl; });
|
||||
loop.runAfter(1.5, []() { std::cout << "run after 1.5" << std::endl; });
|
||||
loop.loop();
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
#include <trantor/net/EventLoopThread.h>
|
||||
#include <iostream>
|
||||
#include <atomic>
|
||||
#include <future>
|
||||
#ifndef _WIN32
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
int main()
|
||||
{
|
||||
// Local variable to be used within the loopThread
|
||||
uint64_t counter = 0;
|
||||
std::promise<int> pro;
|
||||
auto ft = pro.get_future();
|
||||
trantor::EventLoopThread loopThread;
|
||||
|
||||
auto loop = loopThread.getLoop();
|
||||
loop->runInLoop([&counter, &pro, loop]() {
|
||||
for (int i = 0; i < 10000; ++i)
|
||||
{
|
||||
loop->queueInLoop([&counter, &pro]() {
|
||||
++counter;
|
||||
if (counter == 110000)
|
||||
pro.set_value(1);
|
||||
});
|
||||
}
|
||||
});
|
||||
for (int i = 0; i < 10; ++i)
|
||||
{
|
||||
std::thread([&counter, loop, &pro]() {
|
||||
for (int i = 0; i < 10000; ++i)
|
||||
{
|
||||
loop->runInLoop([&counter, &pro]() {
|
||||
++counter;
|
||||
if (counter == 110000)
|
||||
pro.set_value(1);
|
||||
});
|
||||
}
|
||||
}).detach();
|
||||
}
|
||||
loopThread.run();
|
||||
ft.get();
|
||||
std::cout << "counter=" << counter << std::endl;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#include <trantor/net/EventLoopThread.h>
|
||||
#include <iostream>
|
||||
#include <atomic>
|
||||
#include <future>
|
||||
#ifndef _WIN32
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
int main()
|
||||
{
|
||||
std::atomic<bool> flag(false);
|
||||
{
|
||||
trantor::EventLoopThread thr;
|
||||
thr.getLoop()->runOnQuit([&]() { flag = true; });
|
||||
thr.run();
|
||||
thr.getLoop()->quit();
|
||||
}
|
||||
|
||||
if (flag == false)
|
||||
{
|
||||
std::cerr << "Test failed\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << "Success\n";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
#include <trantor/net/TcpClient.h>
|
||||
#include <trantor/utils/Logger.h>
|
||||
#include <trantor/net/EventLoopThread.h>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <atomic>
|
||||
using namespace trantor;
|
||||
#define USE_IPV6 0
|
||||
int main()
|
||||
{
|
||||
trantor::Logger::setLogLevel(trantor::Logger::kTrace);
|
||||
LOG_DEBUG << "TcpClient class test!";
|
||||
EventLoop loop;
|
||||
#if USE_IPV6
|
||||
InetAddress serverAddr("::1", 8888, true);
|
||||
#else
|
||||
InetAddress serverAddr("127.0.0.1", 8888);
|
||||
#endif
|
||||
std::shared_ptr<trantor::TcpClient> client[10];
|
||||
std::atomic_int connCount;
|
||||
connCount = 1;
|
||||
for (int i = 0; i < connCount; ++i)
|
||||
{
|
||||
client[i] = std::make_shared<trantor::TcpClient>(&loop,
|
||||
serverAddr,
|
||||
"tcpclienttest");
|
||||
auto policy = TLSPolicy::defaultClientPolicy();
|
||||
policy->setValidate(false);
|
||||
client[i]->enableSSL(std::move(policy));
|
||||
client[i]->setConnectionCallback(
|
||||
[i, &loop, &connCount](const TcpConnectionPtr &conn) {
|
||||
if (conn->connected())
|
||||
{
|
||||
LOG_DEBUG << i << " connected!";
|
||||
conn->send(std::to_string(i) + " client!!");
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_DEBUG << i << " disconnected";
|
||||
--connCount;
|
||||
if (connCount == 0)
|
||||
loop.quit();
|
||||
}
|
||||
});
|
||||
client[i]->setMessageCallback(
|
||||
[](const TcpConnectionPtr &conn, MsgBuffer *buf) {
|
||||
LOG_DEBUG << std::string(buf->peek(), buf->readableBytes());
|
||||
buf->retrieveAll();
|
||||
conn->shutdown();
|
||||
});
|
||||
client[i]->connect();
|
||||
}
|
||||
loop.loop();
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
#include <trantor/net/TcpServer.h>
|
||||
#include <trantor/utils/Logger.h>
|
||||
#include <trantor/net/EventLoopThread.h>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
using namespace trantor;
|
||||
#define USE_IPV6 0
|
||||
int main()
|
||||
{
|
||||
LOG_DEBUG << "test start";
|
||||
Logger::setLogLevel(Logger::kTrace);
|
||||
EventLoopThread loopThread;
|
||||
loopThread.run();
|
||||
#if USE_IPV6
|
||||
InetAddress addr(8888, true, true);
|
||||
#else
|
||||
InetAddress addr(8888);
|
||||
#endif
|
||||
TcpServer server(loopThread.getLoop(), addr, "test");
|
||||
auto policy = TLSPolicy::defaultServerPolicy("server.crt", "server.key");
|
||||
server.enableSSL(std::move(policy));
|
||||
server.setRecvMessageCallback(
|
||||
[](const TcpConnectionPtr &connectionPtr, MsgBuffer *buffer) {
|
||||
// LOG_DEBUG<<"recv callback!";
|
||||
std::cout << std::string(buffer->peek(), buffer->readableBytes());
|
||||
connectionPtr->send(buffer->peek(), buffer->readableBytes());
|
||||
buffer->retrieveAll();
|
||||
// connectionPtr->forceClose();
|
||||
});
|
||||
server.setConnectionCallback([](const TcpConnectionPtr &connPtr) {
|
||||
if (connPtr->connected())
|
||||
{
|
||||
LOG_DEBUG << "New connection";
|
||||
connPtr->send("Hello world\r\n");
|
||||
}
|
||||
else if (connPtr->disconnected())
|
||||
{
|
||||
LOG_DEBUG << "connection disconnected";
|
||||
}
|
||||
});
|
||||
server.setIoLoopNum(3);
|
||||
server.start();
|
||||
loopThread.wait();
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
#include <trantor/net/TcpServer.h>
|
||||
#include <trantor/utils/Logger.h>
|
||||
#include <trantor/net/EventLoopThread.h>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#ifndef _WIN32
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
using namespace trantor;
|
||||
#define USE_IPV6 0
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
if (argc < 2)
|
||||
{
|
||||
std::cout << "usage:" << argv[0] << " filename" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
std::cout << "filename:" << argv[1] << std::endl;
|
||||
struct stat filestat;
|
||||
if (stat(argv[1], &filestat) < 0)
|
||||
{
|
||||
perror("");
|
||||
exit(1);
|
||||
}
|
||||
std::cout << "file len=" << filestat.st_size << std::endl;
|
||||
|
||||
auto fp = fopen(argv[1], "rb");
|
||||
|
||||
if (fp == nullptr)
|
||||
{
|
||||
perror("");
|
||||
exit(1);
|
||||
}
|
||||
fclose(fp);
|
||||
|
||||
LOG_DEBUG << "test start";
|
||||
|
||||
Logger::setLogLevel(Logger::kTrace);
|
||||
EventLoopThread loopThread;
|
||||
loopThread.run();
|
||||
|
||||
#if USE_IPV6
|
||||
InetAddress addr(1207, true, true);
|
||||
#else
|
||||
InetAddress addr(1207);
|
||||
#endif
|
||||
TcpServer server(loopThread.getLoop(), addr, "test");
|
||||
server.setRecvMessageCallback(
|
||||
[](const TcpConnectionPtr &connectionPtr, MsgBuffer *buffer) {
|
||||
// LOG_DEBUG<<"recv callback!";
|
||||
});
|
||||
int counter = 0;
|
||||
server.setConnectionCallback(
|
||||
[argv, &counter](const TcpConnectionPtr &connPtr) {
|
||||
if (connPtr->connected())
|
||||
{
|
||||
LOG_DEBUG << "New connection";
|
||||
std::thread t([connPtr, argv, &counter]() {
|
||||
for (int i = 0; i < 5; ++i)
|
||||
{
|
||||
connPtr->sendFile(argv[1]);
|
||||
++counter;
|
||||
std::string str =
|
||||
"\n" + std::to_string(counter) + " files sent!\n";
|
||||
connPtr->send(std::move(str));
|
||||
}
|
||||
});
|
||||
t.detach();
|
||||
|
||||
for (int i = 0; i < 3; ++i)
|
||||
{
|
||||
connPtr->sendFile(argv[1]);
|
||||
++counter;
|
||||
std::string str =
|
||||
"\n" + std::to_string(counter) + " files sent!\n";
|
||||
connPtr->send(std::move(str));
|
||||
}
|
||||
}
|
||||
else if (connPtr->disconnected())
|
||||
{
|
||||
LOG_DEBUG << "connection disconnected";
|
||||
}
|
||||
});
|
||||
server.setIoLoopNum(3);
|
||||
server.start();
|
||||
loopThread.wait();
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
#include <trantor/net/TcpServer.h>
|
||||
#include <trantor/utils/Logger.h>
|
||||
#include <trantor/net/EventLoopThread.h>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#ifndef _WIN32
|
||||
#include <unistd.h>
|
||||
#else
|
||||
#include <io.h>
|
||||
#endif
|
||||
|
||||
std::size_t fileCallback(const std::string &, int, char *, std::size_t);
|
||||
|
||||
using namespace trantor;
|
||||
#define USE_IPV6 0
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
if (argc < 2)
|
||||
{
|
||||
std::cout << "usage:" << argv[0] << " filename" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
std::cout << "filename:" << argv[1] << std::endl;
|
||||
struct stat filestat;
|
||||
if (stat(argv[1], &filestat) < 0)
|
||||
{
|
||||
perror("");
|
||||
exit(1);
|
||||
}
|
||||
std::cout << "file len=" << filestat.st_size << std::endl;
|
||||
|
||||
auto fp = fopen(argv[1], "rb");
|
||||
|
||||
if (fp == nullptr)
|
||||
{
|
||||
perror("");
|
||||
exit(1);
|
||||
}
|
||||
fclose(fp);
|
||||
|
||||
LOG_DEBUG << "test start";
|
||||
|
||||
Logger::setLogLevel(Logger::kTrace);
|
||||
EventLoopThread loopThread;
|
||||
loopThread.run();
|
||||
|
||||
#if USE_IPV6
|
||||
InetAddress addr(1207, true, true);
|
||||
#else
|
||||
InetAddress addr(1207);
|
||||
#endif
|
||||
TcpServer server(loopThread.getLoop(), addr, "test");
|
||||
server.setRecvMessageCallback(
|
||||
[](const TcpConnectionPtr &connectionPtr, MsgBuffer *buffer) {
|
||||
// LOG_DEBUG<<"recv callback!";
|
||||
});
|
||||
int counter = 0;
|
||||
server.setConnectionCallback([argv,
|
||||
&counter](const TcpConnectionPtr &connPtr) {
|
||||
if (connPtr->connected())
|
||||
{
|
||||
LOG_DEBUG << "New connection";
|
||||
std::thread t([connPtr, argv, &counter]() {
|
||||
for (int i = 0; i < 5; ++i)
|
||||
{
|
||||
int fd;
|
||||
#ifdef _WIN32
|
||||
_sopen_s(
|
||||
&fd, argv[1], _O_BINARY | _O_RDONLY, _SH_DENYNO, 0);
|
||||
#else
|
||||
fd = open(argv[1], O_RDONLY);
|
||||
#endif
|
||||
auto callback = std::bind(fileCallback,
|
||||
argv[1],
|
||||
fd,
|
||||
std::placeholders::_1,
|
||||
std::placeholders::_2);
|
||||
connPtr->sendStream(callback);
|
||||
++counter;
|
||||
std::string str =
|
||||
"\n" + std::to_string(counter) + " streams sent!\n";
|
||||
connPtr->send(std::move(str));
|
||||
}
|
||||
});
|
||||
t.detach();
|
||||
|
||||
for (int i = 0; i < 3; ++i)
|
||||
{
|
||||
int fd;
|
||||
#ifdef _WIN32
|
||||
_sopen_s(&fd, argv[1], _O_BINARY | _O_RDONLY, _SH_DENYNO, 0);
|
||||
#else
|
||||
fd = open(argv[1], O_RDONLY);
|
||||
#endif
|
||||
auto callback = std::bind(fileCallback,
|
||||
argv[1],
|
||||
fd,
|
||||
std::placeholders::_1,
|
||||
std::placeholders::_2);
|
||||
connPtr->sendStream(callback);
|
||||
++counter;
|
||||
std::string str =
|
||||
"\n" + std::to_string(counter) + " streams sent!\n";
|
||||
connPtr->send(std::move(str));
|
||||
}
|
||||
}
|
||||
else if (connPtr->disconnected())
|
||||
{
|
||||
LOG_DEBUG << "connection disconnected";
|
||||
}
|
||||
});
|
||||
server.setIoLoopNum(3);
|
||||
server.start();
|
||||
loopThread.wait();
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::size_t fileCallback(const std::string &strFile,
|
||||
int nFd,
|
||||
char *pBuffer,
|
||||
std::size_t nBuffSize)
|
||||
{
|
||||
if (nFd < 0)
|
||||
return 0;
|
||||
if (pBuffer == nullptr)
|
||||
{
|
||||
LOG_DEBUG << strFile.c_str() << " closed.";
|
||||
#ifdef _WIN32
|
||||
_close(nFd);
|
||||
#else
|
||||
close(nFd);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
int nRead = _read(nFd, pBuffer, (unsigned int)nBuffSize);
|
||||
#else
|
||||
ssize_t nRead = read(nFd, pBuffer, nBuffSize);
|
||||
#endif
|
||||
if (nRead < 0)
|
||||
return 0;
|
||||
return std::size_t(nRead);
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
#include <trantor/utils/SerialTaskQueue.h>
|
||||
#include <trantor/utils/Logger.h>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include <stdio.h>
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
int main()
|
||||
{
|
||||
trantor::Logger::setLogLevel(trantor::Logger::kTrace);
|
||||
trantor::SerialTaskQueue queue1("test queue1");
|
||||
trantor::SerialTaskQueue queue2("");
|
||||
queue1.runTaskInQueue([&]() {
|
||||
for (int i = 0; i < 5; ++i)
|
||||
{
|
||||
std::this_thread::sleep_for(1s);
|
||||
printf("task(%s) i=%d\n", queue1.getName().c_str(), i);
|
||||
}
|
||||
});
|
||||
queue2.runTaskInQueue([&]() {
|
||||
for (int i = 0; i < 5; ++i)
|
||||
{
|
||||
std::this_thread::sleep_for(1s);
|
||||
printf("task(%s) i=%d\n", queue2.getName().c_str(), i);
|
||||
}
|
||||
});
|
||||
queue1.waitAllTasksFinished();
|
||||
queue2.waitAllTasksFinished();
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
#include <trantor/utils/SerialTaskQueue.h>
|
||||
#include <iostream>
|
||||
#include <atomic>
|
||||
#include <future>
|
||||
#ifndef _WIN32
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
int main()
|
||||
{
|
||||
std::atomic<uint64_t> counter;
|
||||
counter = 0;
|
||||
std::promise<int> pro;
|
||||
auto ft = pro.get_future();
|
||||
trantor::SerialTaskQueue queue("");
|
||||
queue.runTaskInQueue([&counter, &pro, &queue]() {
|
||||
for (int i = 0; i < 10000; ++i)
|
||||
{
|
||||
queue.runTaskInQueue([&counter, &pro]() {
|
||||
++counter;
|
||||
if (counter.load() == 110000)
|
||||
pro.set_value(1);
|
||||
});
|
||||
}
|
||||
});
|
||||
for (int i = 0; i < 10; ++i)
|
||||
{
|
||||
std::thread([&counter, &queue, &pro]() {
|
||||
for (int i = 0; i < 10000; ++i)
|
||||
{
|
||||
queue.runTaskInQueue([&counter, &pro]() {
|
||||
++counter;
|
||||
if (counter.load() == 110000)
|
||||
pro.set_value(1);
|
||||
});
|
||||
}
|
||||
}).detach();
|
||||
}
|
||||
|
||||
ft.get();
|
||||
std::cout << "counter=" << counter.load() << std::endl;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,53 @@
|
||||
#include <trantor/net/TcpServer.h>
|
||||
#include <trantor/utils/Logger.h>
|
||||
#include <trantor/net/EventLoopThread.h>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
using namespace trantor;
|
||||
#define USE_IPV6 0
|
||||
int main()
|
||||
{
|
||||
LOG_DEBUG << "test start";
|
||||
Logger::setLogLevel(Logger::kTrace);
|
||||
EventLoopThread loopThread;
|
||||
loopThread.run();
|
||||
#if USE_IPV6
|
||||
InetAddress addr(8888, true, true);
|
||||
#else
|
||||
InetAddress addr(8888);
|
||||
#endif
|
||||
TcpServer server(loopThread.getLoop(), addr, "test");
|
||||
|
||||
server.setRecvMessageCallback(
|
||||
[](const TcpConnectionPtr &connectionPtr, MsgBuffer *buffer) {
|
||||
// LOG_DEBUG<<"recv callback!";
|
||||
std::cout << std::string(buffer->peek(), buffer->readableBytes());
|
||||
connectionPtr->send(buffer->peek(), buffer->readableBytes());
|
||||
buffer->retrieveAll();
|
||||
// connectionPtr->forceClose();
|
||||
});
|
||||
server.setConnectionCallback([](const TcpConnectionPtr &connPtr) {
|
||||
if (connPtr->connected())
|
||||
{
|
||||
LOG_DEBUG << "New connection";
|
||||
auto stream = connPtr->sendAsyncStream();
|
||||
stream->send("hello world 1...");
|
||||
std::thread([stream = std::move(stream)] {
|
||||
for (int i = 2; i < 10; i++)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
stream->send("hello world " + std::to_string(i) + "...");
|
||||
}
|
||||
stream->close();
|
||||
}).detach();
|
||||
connPtr->send("hello world");
|
||||
}
|
||||
else if (connPtr->disconnected())
|
||||
{
|
||||
LOG_DEBUG << "connection disconnected";
|
||||
}
|
||||
});
|
||||
server.setIoLoopNum(3);
|
||||
server.start();
|
||||
loopThread.wait();
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
#include <trantor/net/TcpClient.h>
|
||||
#include <trantor/utils/Logger.h>
|
||||
#include <trantor/net/EventLoopThread.h>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <atomic>
|
||||
#ifdef _WIN32
|
||||
#include <ws2tcpip.h>
|
||||
#else
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/tcp.h>
|
||||
#endif
|
||||
|
||||
using namespace trantor;
|
||||
#define USE_IPV6 0
|
||||
int main()
|
||||
{
|
||||
trantor::Logger::setLogLevel(trantor::Logger::kTrace);
|
||||
LOG_DEBUG << "TcpClient class test!";
|
||||
EventLoop loop;
|
||||
#if USE_IPV6
|
||||
InetAddress serverAddr("::1", 8888, true);
|
||||
#else
|
||||
InetAddress serverAddr("127.0.0.1", 8888);
|
||||
#endif
|
||||
std::shared_ptr<trantor::TcpClient> client[10];
|
||||
std::atomic_int connCount;
|
||||
connCount = 10;
|
||||
for (int i = 0; i < 10; ++i)
|
||||
{
|
||||
client[i] = std::make_shared<trantor::TcpClient>(&loop,
|
||||
serverAddr,
|
||||
"tcpclienttest");
|
||||
client[i]->setSockOptCallback([](int fd) {
|
||||
LOG_DEBUG << "setSockOptCallback!";
|
||||
#ifdef _WIN32
|
||||
#elif __linux__
|
||||
int optval = 10;
|
||||
::setsockopt(fd,
|
||||
SOL_TCP,
|
||||
TCP_KEEPCNT,
|
||||
&optval,
|
||||
static_cast<socklen_t>(sizeof optval));
|
||||
::setsockopt(fd,
|
||||
SOL_TCP,
|
||||
TCP_KEEPIDLE,
|
||||
&optval,
|
||||
static_cast<socklen_t>(sizeof optval));
|
||||
::setsockopt(fd,
|
||||
SOL_TCP,
|
||||
TCP_KEEPINTVL,
|
||||
&optval,
|
||||
static_cast<socklen_t>(sizeof optval));
|
||||
#else
|
||||
#endif
|
||||
});
|
||||
client[i]->setConnectionCallback(
|
||||
[i, &loop, &connCount](const TcpConnectionPtr &conn) {
|
||||
if (conn->connected())
|
||||
{
|
||||
LOG_DEBUG << i << " connected!";
|
||||
std::string tmp = std::to_string(i) + " client!!";
|
||||
conn->send(tmp);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_DEBUG << i << " disconnected";
|
||||
--connCount;
|
||||
if (connCount == 0)
|
||||
loop.quit();
|
||||
}
|
||||
});
|
||||
client[i]->setMessageCallback(
|
||||
[](const TcpConnectionPtr &conn, MsgBuffer *buf) {
|
||||
LOG_DEBUG << std::string(buf->peek(), buf->readableBytes());
|
||||
buf->retrieveAll();
|
||||
conn->shutdown();
|
||||
});
|
||||
client[i]->connect();
|
||||
}
|
||||
loop.loop();
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user