复现已有算法

This commit is contained in:
cloud
2026-07-14 15:43:18 +08:00
parent abebd2a683
commit 50b8111fd9
860 changed files with 182250 additions and 18 deletions
+151
View File
@@ -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
View File
@@ -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
+7
View File
@@ -0,0 +1,7 @@
.idea
build
cmake-build-debug
.vscode
.vs
CMakeSettings.json
.cache
+436
View File
@@ -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
View File
@@ -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 &lt;memory&gt; 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 &lt;cstdint&gt; 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
View File
@@ -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
View File
@@ -0,0 +1,48 @@
# TRANTOR
[![Build Ubuntu gcc](../../actions/workflows/ubuntu-gcc.yml/badge.svg)](../../actions/workflows/ubuntu-gcc.yml/badge.svg)
[![Build Macos clang](../../actions/workflows/macos-clang.yml/badge.svg)](../../actions/workflows/macos-clang.yml/badge.svg)
[![Build RockyLinux gcc](../../actions/workflows/rockylinux-gcc.yml/badge.svg)](../../actions/workflows/rockylinux-gcc.yml/badge.svg)
[![Build Windows msvc](../../actions/workflows/windows-msvc.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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+112
View File
@@ -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
+315
View File
@@ -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
+423
View File
@@ -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
+333
View File
@@ -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
+68
View File
@@ -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
+221
View File
@@ -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
+249
View File
@@ -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);
}
+271
View File
@@ -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
+256
View File
@@ -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);
}
});
}
}
+317
View File
@@ -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
+45
View File
@@ -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
View File
@@ -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
@@ -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;
}
@@ -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
View File
@@ -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