├── .anvil ├── configs.toml └── project_rules.toml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md ├── build └── pch │ ├── precompile.cpp │ └── precompile.h ├── cmake ├── compiler_msvc.cmake ├── compiler_posix.cmake ├── cotire.cmake └── dependency_manager.cmake ├── examples ├── CMakeLists.txt ├── chat │ ├── CMakeLists.txt │ ├── chat_client.cpp │ ├── chat_client.h │ ├── chat_client_main.cpp │ ├── chat_server.cpp │ ├── chat_server.h │ ├── chat_server_main.cpp │ ├── data_codec.cpp │ └── data_codec.h ├── echo │ ├── CMakeLists.txt │ ├── echo_client.cpp │ └── echo_server.cpp └── socks4a │ ├── CMakeLists.txt │ ├── main.cpp │ ├── socks_proxy.cpp │ ├── socks_proxy.h │ ├── tunnel.cpp │ └── tunnel.h ├── ezio ├── CMakeLists.txt ├── acceptor.cpp ├── acceptor.h ├── acceptor_posix.cpp ├── acceptor_win.cpp ├── buffer.cpp ├── buffer.h ├── buffer_posix.cpp ├── chrono_utils.h ├── common_event_handlers.h ├── connector.h ├── connector_base.cpp ├── connector_base.h ├── connector_posix.cpp ├── connector_posix.h ├── connector_win.cpp ├── connector_win.h ├── endian_utils.h ├── event_loop.cpp ├── event_loop.h ├── event_pump.cpp ├── event_pump.h ├── event_pump_impl_posix.cpp ├── event_pump_impl_posix.h ├── event_pump_impl_win.cpp ├── event_pump_impl_win.h ├── ignore_sigpipe.cpp ├── ignore_sigpipe.h ├── io_context.h ├── io_service_context.cpp ├── io_service_context.h ├── notifier.cpp ├── notifier.h ├── notifier_posix.cpp ├── notifier_win.cpp ├── scoped_socket.h ├── socket_address.cpp ├── socket_address.h ├── socket_utils.cpp ├── socket_utils.h ├── socket_utils_posix.cpp ├── socket_utils_win.cpp ├── tcp_client.cpp ├── tcp_client.h ├── tcp_connection.cpp ├── tcp_connection.h ├── tcp_connection_posix.cpp ├── tcp_connection_win.cpp ├── tcp_server.cpp ├── tcp_server.h ├── this_thread.cpp ├── this_thread.h ├── thread.cpp ├── thread.h ├── timer.cpp ├── timer.h ├── timer_id.h ├── timer_queue.cpp ├── timer_queue.h ├── winsock_context.cpp ├── winsock_context.h ├── worker_pool.cpp └── worker_pool.h └── tests ├── CMakeLists.txt ├── acceptor_unittest.cpp ├── buffer_unittest.cpp ├── connector_and_tcpclient.cpp ├── endian_utils_unittest.cpp ├── ignore_sigpipe_unittest.cpp ├── io_service_context_unittest.cpp ├── loop_and_notifier_unittest.cpp ├── main.cpp ├── scoped_socket_unittest.cpp ├── socket_address_unittest.cpp ├── tcp_server_and_connection.cpp ├── thread_and_worker_pool.cpp └── winsock_context_unittest.cpp /.anvil/configs.toml: -------------------------------------------------------------------------------- 1 | 2 | [msvc-2017] 3 | generator = "Visual Studio 15 2017 Win64" 4 | 5 | [[msvc-2017.gen]] 6 | target = "" 7 | out_dir = "build/msvc-2017" 8 | args = "" 9 | 10 | [[msvc-2017.build]] 11 | mode = "Debug" 12 | args = "--target ALL_BUILD --config Debug" 13 | 14 | [[msvc-2017.build]] 15 | mode = "Release" 16 | args = "--target ALL_BUILD --config Release" 17 | 18 | [msvc-2019] 19 | generator = "Visual Studio 16 2019" 20 | 21 | [[msvc-2019.gen]] 22 | target = "" 23 | out_dir = "build/msvc-2019" 24 | args = "" 25 | 26 | [[msvc-2019.build]] 27 | mode = "Debug" 28 | args = "--target ALL_BUILD --config Debug" 29 | 30 | [[msvc-2019.build]] 31 | mode = "Release" 32 | args = "--target ALL_BUILD --config Release" 33 | 34 | [clang-ninja] 35 | generator = "Ninja" 36 | 37 | [[clang-ninja.gen]] 38 | target = "Debug" 39 | out_dir = "build/Debug" 40 | args = "-DCMAKE_BUILD_TYPE=Debug" 41 | 42 | [[clang-ninja.gen]] 43 | target = "Release" 44 | out_dir = "build/Release" 45 | args = "-DCMAKE_BUILD_TYPE=Release" 46 | 47 | [[clang-ninja.build]] 48 | mode = "" 49 | args = "-- -j 8" 50 | -------------------------------------------------------------------------------- /.anvil/project_rules.toml: -------------------------------------------------------------------------------- 1 | 2 | [cmake] 3 | min_ver = "3.11" 4 | 5 | [project] 6 | name = "ezio" 7 | # The directory to store source of dependency. 8 | # Always relative to project root directory. 9 | deps_source_dir = "build/deps" 10 | # Where sub-build files of the dependency to store. 11 | deps_deploy_dir = "${CMAKE_BINARY_DIR}" 12 | cxx_standard = "14" 13 | 14 | [precompiled_header] 15 | enabled = true 16 | # `pch_file` path is relative the current project directory. 17 | pch_file = "build/pch/precompile.h" 18 | 19 | [platform_support] 20 | windows = true 21 | posix = true 22 | 23 | [main_module] 24 | name = "ezio" 25 | # choose between "library" or "executable" 26 | type = "library" 27 | # effective only if precompiled_header is enable. 28 | use_pch = true 29 | # effective only if windows platform is enabled. 30 | use_msvc_static_analysis = true 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | [Xx]64/ 19 | [Xx]86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | 24 | # Keep track of pch files 25 | [Bb]uild/* 26 | ![Bb]uild/pch/ 27 | 28 | # Visual Studio 2015 cache/options directory 29 | .vs/ 30 | # Uncomment if you have tasks that create the project's static files in wwwroot 31 | #wwwroot/ 32 | 33 | # MSTest test Results 34 | [Tt]est[Rr]esult*/ 35 | [Bb]uild[Ll]og.* 36 | 37 | # NUNIT 38 | *.VisualState.xml 39 | TestResult.xml 40 | 41 | # Build Results of an ATL Project 42 | [Dd]ebugPS/ 43 | [Rr]eleasePS/ 44 | dlldata.c 45 | 46 | # DNX 47 | project.lock.json 48 | artifacts/ 49 | 50 | *_i.c 51 | *_p.c 52 | *_i.h 53 | *.ilk 54 | *.meta 55 | *.obj 56 | *.pch 57 | *.pdb 58 | *.pgc 59 | *.pgd 60 | *.rsp 61 | *.sbr 62 | *.tlb 63 | *.tli 64 | *.tlh 65 | *.tmp 66 | *.tmp_proj 67 | *.log 68 | *.vspscc 69 | *.vssscc 70 | .builds 71 | *.pidb 72 | *.svclog 73 | *.scc 74 | 75 | # Chutzpah Test files 76 | _Chutzpah* 77 | 78 | # Visual C++ cache files 79 | ipch/ 80 | *.aps 81 | *.ncb 82 | *.opendb 83 | *.opensdf 84 | *.sdf 85 | *.cachefile 86 | *.VC.db 87 | 88 | # Visual Studio profiler 89 | *.psess 90 | *.vsp 91 | *.vspx 92 | *.sap 93 | 94 | # TFS 2012 Local Workspace 95 | $tf/ 96 | 97 | # Guidance Automation Toolkit 98 | *.gpState 99 | 100 | # ReSharper is a .NET coding add-in 101 | _ReSharper*/ 102 | *.[Rr]e[Ss]harper 103 | *.DotSettings.user 104 | 105 | # JustCode is a .NET coding add-in 106 | .JustCode 107 | 108 | # TeamCity is a build add-in 109 | _TeamCity* 110 | 111 | # DotCover is a Code Coverage Tool 112 | *.dotCover 113 | 114 | # NCrunch 115 | _NCrunch_* 116 | .*crunch*.local.xml 117 | nCrunchTemp_* 118 | 119 | # MightyMoose 120 | *.mm.* 121 | AutoTest.Net/ 122 | 123 | # Web workbench (sass) 124 | .sass-cache/ 125 | 126 | # Installshield output folder 127 | [Ee]xpress/ 128 | 129 | # DocProject is a documentation generator add-in 130 | DocProject/buildhelp/ 131 | DocProject/Help/*.HxT 132 | DocProject/Help/*.HxC 133 | DocProject/Help/*.hhc 134 | DocProject/Help/*.hhk 135 | DocProject/Help/*.hhp 136 | DocProject/Help/Html2 137 | DocProject/Help/html 138 | 139 | # Click-Once directory 140 | publish/ 141 | 142 | # Publish Web Output 143 | *.[Pp]ublish.xml 144 | *.azurePubxml 145 | 146 | # TODO: Un-comment the next line if you do not want to checkin 147 | # your web deploy settings because they may include unencrypted 148 | # passwords 149 | #*.pubxml 150 | *.publishproj 151 | 152 | # NuGet Packages 153 | *.nupkg 154 | # The packages folder can be ignored because of Package Restore 155 | **/packages/* 156 | # except build/, which is used as an MSBuild target. 157 | !**/packages/build/ 158 | # Uncomment if necessary however generally it will be regenerated when needed 159 | #!**/packages/repositories.config 160 | # NuGet v3's project.json files produces more ignoreable files 161 | *.nuget.props 162 | *.nuget.targets 163 | 164 | # Microsoft Azure Build Output 165 | csx/ 166 | *.build.csdef 167 | 168 | # Microsoft Azure Emulator 169 | ecf/ 170 | rcf/ 171 | 172 | # Microsoft Azure ApplicationInsights config file 173 | ApplicationInsights.config 174 | 175 | # Windows Store app package directory 176 | AppPackages/ 177 | BundleArtifacts/ 178 | 179 | # Visual Studio cache files 180 | # files ending in .cache can be ignored 181 | *.[Cc]ache 182 | # but keep track of directories ending in .cache 183 | !*.[Cc]ache/ 184 | 185 | # Others 186 | ClientBin/ 187 | [Ss]tyle[Cc]op.* 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.pfx 193 | *.publishsettings 194 | node_modules/ 195 | orleans.codegen.cs 196 | 197 | # RIA/Silverlight projects 198 | Generated_Code/ 199 | 200 | # Backup & report files from converting an old project file 201 | # to a newer Visual Studio version. Backup files are not needed, 202 | # because we have git ;-) 203 | _UpgradeReport_Files/ 204 | Backup*/ 205 | UpgradeLog*.XML 206 | UpgradeLog*.htm 207 | 208 | # SQL Server files 209 | *.mdf 210 | *.ldf 211 | 212 | # Business Intelligence projects 213 | *.rdl.data 214 | *.bim.layout 215 | *.bim_*.settings 216 | 217 | # Microsoft Fakes 218 | FakesAssemblies/ 219 | 220 | # GhostDoc plugin setting file 221 | *.GhostDoc.xml 222 | 223 | # Node.js Tools for Visual Studio 224 | .ntvs_analysis.dat 225 | 226 | # Visual Studio 6 build log 227 | *.plg 228 | 229 | # Visual Studio 6 workspace options file 230 | *.opt 231 | 232 | # Visual Studio LightSwitch build output 233 | **/*.HTMLClient/GeneratedArtifacts 234 | **/*.DesktopClient/GeneratedArtifacts 235 | **/*.DesktopClient/ModelManifest.xml 236 | **/*.Server/GeneratedArtifacts 237 | **/*.Server/ModelManifest.xml 238 | _Pvt_Extensions 239 | 240 | # LightSwitch generated files 241 | GeneratedArtifacts/ 242 | ModelManifest.xml 243 | 244 | # Paket dependency manager 245 | .paket/paket.exe 246 | 247 | # FAKE - F# Make 248 | .fake/ 249 | 250 | # Visual Studio Code 251 | .vscode/ 252 | 253 | # Mainly for ycm 254 | .ycm_extra_conf.py 255 | *.pyc 256 | 257 | # KDevelop 258 | *.kdev4/ 259 | *.kdev4 260 | 261 | # CLion 262 | *.idea/ 263 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingsamchen/ezio/f448d1d0d42ffb01ab94228e991d70a7230bfbb1/.gitmodules -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.11) 2 | 3 | # Detect if being bundled via sub-directory. 4 | if(NOT DEFINED PROJECT_NAME) 5 | set(EZIO_NOT_SUBPROJECT ON) 6 | endif() 7 | 8 | project(ezio CXX) 9 | 10 | set_property(GLOBAL PROPERTY USE_FOLDERS ON) 11 | 12 | if(EZIO_NOT_SUBPROJECT) 13 | set(CMAKE_CXX_STANDARD 14) 14 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 15 | set(CMAKE_CXX_EXTENSIONS OFF) 16 | 17 | set(ROOT_DIR ${CMAKE_SOURCE_DIR}) 18 | set(DEPS_SOURCE_DIR ${ROOT_DIR}/build/deps) 19 | set(DEPS_DEPLOY_DIR ${CMAKE_BINARY_DIR}) 20 | endif() 21 | 22 | if(EZIO_NOT_SUBPROJECT) 23 | set(EZIO_CODE_ANALYSIS_DEFAULT ON) 24 | else() 25 | set(EZIO_CODE_ANALYSIS_DEFAULT OFF) 26 | endif() 27 | 28 | option(EZIO_ENABLE_CODE_ANALYSIS "Enable code analysis" ${EZIO_CODE_ANALYSIS_DEFAULT}) 29 | message(STATUS "EZIO_ENABLE_CODE_ANALYSIS = " ${EZIO_ENABLE_CODE_ANALYSIS}) 30 | 31 | if(EZIO_NOT_SUBPROJECT) 32 | option(EZIO_BUILD_UNITTESTS "Build ezio unittest" ON) 33 | message(STATUS "EZIO_BUILD_UNITTEST = " ${EZIO_BUILD_UNITTESTS}) 34 | 35 | option(EZIO_BUILD_EXAMPLES "Build ezio examples" ON) 36 | message(STATUS "EZIO_BUILD_EXAMPLES = " ${EZIO_BUILD_EXAMPLES}) 37 | endif() 38 | 39 | set(EZIO_DIR ${CMAKE_CURRENT_SOURCE_DIR}) 40 | set(EZIO_DEPS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/deps) 41 | set(EZIO_CMAKE_DIR ${EZIO_DIR}/cmake) 42 | 43 | include(${EZIO_CMAKE_DIR}/dependency_manager.cmake) 44 | 45 | include(${EZIO_CMAKE_DIR}/cotire.cmake) 46 | set(EZIO_PCH_HEADER ${EZIO_DIR}/build/pch/precompile.h) 47 | 48 | #Compiler and output configurations. 49 | if(MSVC) 50 | include(${EZIO_CMAKE_DIR}/compiler_msvc.cmake) 51 | foreach(OUTPUTCONFIG_TYPE ${CMAKE_CONFIGURATION_TYPES}) 52 | string(TOUPPER ${OUTPUTCONFIG_TYPE} OUTPUTCONFIG) 53 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${CMAKE_BINARY_DIR}/${OUTPUTCONFIG_TYPE}/bin) 54 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${CMAKE_BINARY_DIR}/${OUTPUTCONFIG_TYPE}/lib) 55 | endforeach() 56 | else() 57 | include(${EZIO_CMAKE_DIR}/compiler_posix.cmake) 58 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) 59 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) 60 | endif() 61 | 62 | add_subdirectory(ezio) 63 | 64 | if (EZIO_NOT_SUBPROJECT AND EZIO_BUILD_UNITTESTS) 65 | add_subdirectory(tests) 66 | endif() 67 | 68 | if (EZIO_NOT_SUBPROJECT AND EZIO_BUILD_EXAMPLES) 69 | add_subdirectory(examples) 70 | endif() 71 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Kingsley Chen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Introduction 2 | === 3 | 4 | ezio is a library that aims to provide an efficient approach to simplify TCP network programming across platforms. 5 | 6 | ## Goals 7 | 8 | ezio comes with following goals: 9 | - Support non-blocking I/O via epoll on Linux 10 | - Support asynchronous I/O via I/O Completion Ports on Windows 11 | - Consistent user codebase 12 | - Easy to use 13 | 14 | ezio currently supports TCP socket only. 15 | 16 | ## Build Instructions 17 | 18 | ### Platform Requirements 19 | 20 | #### Windows 21 | 22 | - Windows 7, or later 23 | - Visual Studio 2015, or later (C++ 14 is required) 24 | - CMake 3.11 or later 25 | - Python 3 26 | 27 | Note: 28 | 29 | - If python 3 was not installed, you should run cmake configuration and build targets manually. 30 | - We will use the latest version of Visual Studio as possible and use x64 as the default target architecture. 31 | 32 | #### Ubuntu 33 | 34 | - 14.04 LTS x64, or later 35 | - Clang 3.8, or G++ as the minimum (C++ 14 is required) 36 | - CMake 3.11, or later 37 | - Python 3 38 | - Ninja (optional) 39 | 40 | Note: 41 | 42 | - If python 3 was not installed, you should run cmake configuration and build targets manually. 43 | - If Ninja was not installed, you can use the traditional Makefile 44 | 45 | ### Generate & Build 46 | 47 | KBase uses [anvil](https://github.com/kingsamchen/anvil) to assist in generating build system files and running builds. 48 | 49 | Please be noted that, building the project on Linux platforms would not install any of its files into your system's include directory. 50 | 51 | Run `anvil --help` to check command flags in details. 52 | 53 | ## Acknowledgements 54 | 55 | ezio is initially inspired by [muduo](https://github.com/chenshuo/muduo), which is an efficient non-blocking network library, but for Linux only. 56 | 57 | Special thanks here to muduo and its author, and also to his remarkable [book](https://book.douban.com/subject/20471211/), which indeed offered great help to me for learning network programming. 58 | 59 | Also thank [oceancx](https://github.com/oceancx), one of contributors of ezio, for reporting several critical issues and sharing his thoughtful insight for resolving these issues. -------------------------------------------------------------------------------- /build/pch/precompile.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | // This file is intended to be blank, since it is used as the precompiled header 6 | // generator for builds. No include in this file is needed as the header include is 7 | // forced via the "Forced Include File" option in project property. -------------------------------------------------------------------------------- /build/pch/precompile.h: -------------------------------------------------------------------------------- 1 | /* 2 | @ Kingsley Chen 3 | */ 4 | 5 | // Add frequently used header files or remove header files that are less frequently 6 | // used during project evolvement. 7 | 8 | #if defined(BUILD_PCH_PRECOMPILE_H_) 9 | #error Include the precompiled header file more than once is prohibited. 10 | #endif 11 | 12 | #define BUILD_PCH_PRECOMPILE_H_ 13 | 14 | // C-Runtime headers. 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | // CPP-Runtime headers. 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | #include 57 | #include 58 | #include 59 | -------------------------------------------------------------------------------- /cmake/compiler_msvc.cmake: -------------------------------------------------------------------------------- 1 | 2 | option(MSVC_ENABLE_PARALLEL_BUILD "If enabled, build multiple files in parallel." ON) 3 | 4 | set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "limited configs" FORCE) 5 | 6 | function(apply_common_compile_properties_to_target TARGET) 7 | target_compile_definitions(${TARGET} 8 | PUBLIC 9 | _UNICODE 10 | UNICODE 11 | NOMINMAX 12 | 13 | $<$: 14 | _DEBUG 15 | > 16 | 17 | $<$>: 18 | NDEBUG 19 | > 20 | ) 21 | 22 | target_compile_options(${TARGET} 23 | PUBLIC 24 | /W4 25 | /WX # Treat warning as error. 26 | 27 | /Zc:referenceBinding # Disallow temporaries from binding to non-const lvalue references. 28 | /Zc:rvalueCast # Enforce the standard rules for explicit type conversion. 29 | /Zc:implicitNoexcept # Enable implicit noexcept specifications where required, such as destructors. 30 | /Zc:strictStrings # Don't allow conversion from a string literal to mutable characters. 31 | /Zc:threadSafeInit # Enable thread-safe function-local statics initialization. 32 | /Zc:throwingNew # Assume operator new throws on failure. 33 | 34 | /permissive- # Be mean, don't allow bad non-standard stuff (C++/CLI, __declspec, etc. are all left intact). 35 | 36 | PRIVATE 37 | /Zc:inline # Have the compiler eliminate unreferenced COMDAT functions and data before emitting the object file. 38 | 39 | $<$:/MP> 40 | ) 41 | 42 | target_compile_options(${TARGET} 43 | PUBLIC 44 | /wd4819 # source characters not in current code page. 45 | ) 46 | endfunction(apply_common_compile_properties_to_target) 47 | 48 | # Enable static analysis for all targets could result in excessive warnings. 49 | # Thus we decided to enable it for targets only we explicitly specify. 50 | # Use /wd args to suppress analysis warnings we cannot resolve. 51 | function(enable_msvc_static_analysis_for_target TARGET) 52 | set(multiValueArgs WDL) 53 | cmake_parse_arguments(ARG "" "" "${multiValueArgs}" ${ARGN}) 54 | 55 | target_compile_options(${TARGET} 56 | PRIVATE 57 | /analyze 58 | 59 | PRIVATE 60 | ${ARG_WDL} 61 | ) 62 | endfunction(enable_msvc_static_analysis_for_target) 63 | -------------------------------------------------------------------------------- /cmake/compiler_posix.cmake: -------------------------------------------------------------------------------- 1 | 2 | if(NOT CMAKE_BUILD_TYPE) 3 | set(CMAKE_BUILD_TYPE "Release") 4 | endif() 5 | 6 | string(TOUPPER ${CMAKE_BUILD_TYPE} BUILD_TYPE) 7 | message(STATUS "BUILD_TYPE = " ${BUILD_TYPE}) 8 | 9 | set(CMAKE_CXX_FLAGS_DEBUG "-O0 -D_DEBUG") 10 | set(CMAKE_CXX_FLAGS_RELEASE "-O2 -DNDEBUG") 11 | 12 | set(CMAKE_EXE_LINKER_FLAGS "-rdynamic") 13 | 14 | function(apply_common_compile_properties_to_target TARGET) 15 | target_compile_options(${TARGET} 16 | PRIVATE 17 | -g 18 | -Wall 19 | -Wextra 20 | -Werror 21 | -Wno-unused-parameter 22 | -Wold-style-cast 23 | -Woverloaded-virtual 24 | -Wpointer-arith 25 | -Wshadow 26 | 27 | $<$:-fno-limit-debug-info> 28 | ) 29 | endfunction(apply_common_compile_properties_to_target) 30 | -------------------------------------------------------------------------------- /cmake/dependency_manager.cmake: -------------------------------------------------------------------------------- 1 | 2 | include(FetchContent) 3 | set(FETCHCONTENT_BASE_DIR ${DEPS_DEPLOY_DIR}) 4 | 5 | function(declare_dep_module MODULE_NAME) 6 | set(options "") 7 | 8 | set(oneValueArgs 9 | VERSION 10 | GIT_REPOSITORY 11 | GIT_TAG 12 | ) 13 | 14 | set(multiValueArgs "") 15 | 16 | cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) 17 | 18 | # Content name is used by the module and in the lowercase. 19 | string(TOLOWER "${MODULE_NAME}-${ARG_VERSION}" MODULE_CONTENT_NAME) 20 | string(TOUPPER "${MODULE_CONTENT_NAME}" UC_MODULE_CONTENT_NAME) 21 | 22 | # Source dir is where we put the source code of the dependency. 23 | # `DEPS_SOURCE_DIR` is defined by the root project. 24 | set(MODULE_SOURCE_DIR "${DEPS_SOURCE_DIR}/${MODULE_CONTENT_NAME}-src") 25 | 26 | FetchContent_Declare( 27 | ${MODULE_CONTENT_NAME} 28 | GIT_REPOSITORY ${ARG_GIT_REPOSITORY} 29 | GIT_TAG ${ARG_GIT_TAG} 30 | GIT_SHALLOW TRUE 31 | SOURCE_DIR ${MODULE_SOURCE_DIR} 32 | ) 33 | 34 | FetchContent_GetProperties(${MODULE_CONTENT_NAME}) 35 | 36 | if(NOT ${MODULE_CONTENT_NAME}_POPULATED) 37 | message(STATUS "Fetching dep: ${MODULE_CONTENT_NAME}") 38 | 39 | if(EXISTS "${MODULE_SOURCE_DIR}/CMakeLists.txt") 40 | set("FETCHCONTENT_SOURCE_DIR_${UC_MODULE_CONTENT_NAME}" ${MODULE_SOURCE_DIR}) 41 | message(STATUS "${MODULE_CONTENT_NAME} source dir is found at: ${FETCHCONTENT_SOURCE_DIR_${UC_MODULE_CONTENT_NAME}}; skip fetching.") 42 | endif() 43 | 44 | FetchContent_Populate(${MODULE_CONTENT_NAME}) 45 | 46 | # Set two module-defined variables. 47 | set("${MODULE_CONTENT_NAME}_CONTENT_SOURCE_DIR_VAR" "${${MODULE_CONTENT_NAME}_SOURCE_DIR}") 48 | set("${MODULE_CONTENT_NAME}_CONTENT_BINARY_DIR_VAR" "${${MODULE_CONTENT_NAME}_BINARY_DIR}") 49 | 50 | message(STATUS "${MODULE_CONTENT_NAME} source dir: ${${MODULE_CONTENT_NAME}_CONTENT_SOURCE_DIR_VAR}") 51 | message(STATUS "${MODULE_CONTENT_NAME} binary dir: ${${MODULE_CONTENT_NAME}_CONTENT_BINARY_DIR_VAR}") 52 | 53 | add_subdirectory("${${MODULE_CONTENT_NAME}_CONTENT_SOURCE_DIR_VAR}" "${${MODULE_CONTENT_NAME}_CONTENT_BINARY_DIR_VAR}") 54 | endif() 55 | 56 | endfunction(declare_dep_module) 57 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | add_subdirectory(echo) 3 | add_subdirectory(chat) 4 | add_subdirectory(socks4a) -------------------------------------------------------------------------------- /examples/chat/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | set(chat_server_SRCS 3 | chat_server_main.cpp 4 | chat_server.cpp 5 | data_codec.cpp 6 | ) 7 | 8 | set(chat_server_HEADERS 9 | chat_server.h 10 | data_codec.h 11 | ) 12 | 13 | add_executable(chat-server ${chat_server_SRCS} ${chat_server_HEADERS}) 14 | 15 | apply_common_compile_properties_to_target(chat-server) 16 | 17 | set_target_properties(chat-server PROPERTIES 18 | FOLDER examples/chat 19 | ) 20 | 21 | target_link_libraries(chat-server 22 | PRIVATE 23 | ezio 24 | kbase 25 | ) 26 | 27 | set(chat_client_SRCS 28 | chat_client_main.cpp 29 | chat_client.cpp 30 | data_codec.cpp 31 | ) 32 | 33 | set(chat_client_HEADERS 34 | chat_client.h 35 | data_codec.h 36 | ) 37 | 38 | add_executable(chat-client ${chat_client_SRCS} ${chat_client_HEADERS}) 39 | 40 | apply_common_compile_properties_to_target(chat-client) 41 | 42 | set_target_properties(chat-client PROPERTIES 43 | FOLDER examples/chat 44 | ) 45 | 46 | target_link_libraries(chat-client 47 | PRIVATE 48 | ezio 49 | kbase 50 | ) 51 | -------------------------------------------------------------------------------- /examples/chat/chat_client.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include "chat_client.h" 6 | 7 | #include 8 | 9 | #include "kbase/error_exception_util.h" 10 | 11 | using namespace std::placeholders; 12 | 13 | ChatClient::ChatClient(const ezio::SocketAddress& sockaddr) 14 | : thread_("client-network"), 15 | client_(thread_.event_loop(), sockaddr, "ChatClient") 16 | { 17 | client_.set_on_connect(std::bind(&ChatClient::OnConnection, this, _1)); 18 | client_.set_on_disconnect(std::bind(&ChatClient::OnConnection, this, _1)); 19 | client_.set_on_connection_destroy([this](const auto&) { 20 | main_loop_.Quit(); 21 | }); 22 | client_.set_on_message(std::bind(&DataCodec::OnDataReceive, &codec_, _1, _2, _3)); 23 | 24 | codec_.set_on_message(std::bind(&ChatClient::OnMessage, this, _1, _2, _3)); 25 | } 26 | 27 | void ChatClient::Run() 28 | { 29 | client_.Connect(); 30 | printf("Wait for connecting..."); 31 | main_loop_.Run(); 32 | } 33 | 34 | void ChatClient::ReadUserInput() 35 | { 36 | ENSURE(CHECK, main_loop_.BelongsToCurrentThread()).Require(); 37 | ENSURE(CHECK, !!conn_).Require(); 38 | 39 | std::string s; 40 | if (!std::getline(std::cin, s, '\n')) { 41 | client_.Disconnect(); 42 | return; 43 | } 44 | 45 | std::string cmd; 46 | if (DataCodec::MatchCommand(s, cmd)) { 47 | conn_->Send(DataCodec::NewCommand(cmd)); 48 | } else { 49 | conn_->Send(DataCodec::NewMessage(s)); 50 | } 51 | 52 | main_loop_.QueueTask(std::bind(&ChatClient::ReadUserInput, this)); 53 | } 54 | 55 | void ChatClient::OnConnection(const ezio::TCPConnectionPtr& conn) 56 | { 57 | if (conn->connected()) { 58 | conn_ = conn; 59 | printf("connected!\n"); 60 | main_loop_.RunTask(std::bind(&ChatClient::ReadUserInput, this)); 61 | } else { 62 | conn_ = nullptr; 63 | printf("Bye!\n"); 64 | } 65 | } 66 | 67 | void ChatClient::OnMessage(const ezio::TCPConnectionPtr&, const std::string& msg, 68 | ezio::TimePoint) const 69 | { 70 | ENSURE(CHECK, thread_.event_loop()->BelongsToCurrentThread()).Require(); 71 | printf("%s\n", msg.c_str()); 72 | } 73 | -------------------------------------------------------------------------------- /examples/chat/chat_client.h: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #ifndef CHAT_CHAT_CLIENT_H_ 6 | #define CHAT_CHAT_CLIENT_H_ 7 | 8 | #include "ezio/event_loop.h" 9 | #include "ezio/socket_address.h" 10 | #include "ezio/tcp_client.h" 11 | #include "ezio/thread.h" 12 | 13 | #include "data_codec.h" 14 | 15 | class ChatClient { 16 | public: 17 | explicit ChatClient(const ezio::SocketAddress& sockaddr); 18 | 19 | ~ChatClient() = default; 20 | 21 | void Run(); 22 | 23 | private: 24 | void ReadUserInput(); 25 | 26 | void OnConnection(const ezio::TCPConnectionPtr& conn); 27 | 28 | void OnMessage(const ezio::TCPConnectionPtr&, const std::string& msg, ezio::TimePoint) const; 29 | 30 | private: 31 | ezio::EventLoop main_loop_; 32 | ezio::Thread thread_; 33 | ezio::TCPClient client_; 34 | ezio::TCPConnectionPtr conn_; 35 | DataCodec codec_; 36 | }; 37 | 38 | #endif // CHAT_CHAT_CLIENT_H_ 39 | -------------------------------------------------------------------------------- /examples/chat/chat_client_main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include 6 | 7 | #include "kbase/at_exit_manager.h" 8 | #include "kbase/command_line.h" 9 | #include "kbase/logging.h" 10 | #include "kbase/string_encoding_conversions.h" 11 | 12 | #include "ezio/io_service_context.h" 13 | #include "ezio/socket_address.h" 14 | 15 | #include "chat_client.h" 16 | 17 | int main(int argc, char* argv[]) 18 | { 19 | kbase::AtExitManager exit_manager; 20 | 21 | kbase::LoggingSettings logging_settings; 22 | logging_settings.logging_destination = kbase::LoggingDestination::LogToSystemDebugLog; 23 | kbase::ConfigureLoggingSettings(logging_settings); 24 | 25 | kbase::CommandLine::Init(argc, argv); 26 | 27 | auto cmdline = kbase::CommandLine::ForCurrentProcess(); 28 | auto args = cmdline.GetParameters(); 29 | if (args.size() < 2) { 30 | std::cerr << "Usage: chat-client ip port" << std::endl; 31 | return 1; 32 | } 33 | 34 | ezio::IOServiceContext::Init(); 35 | 36 | #if defined(OS_WIN) 37 | auto ip = kbase::WideToASCII(args[0]); 38 | #else 39 | auto ip = args[0]; 40 | #endif 41 | 42 | ezio::SocketAddress addr(ip, static_cast(std::stoul(args[1]))); 43 | 44 | ChatClient client(addr); 45 | 46 | client.Run(); 47 | 48 | return 0; 49 | } 50 | -------------------------------------------------------------------------------- /examples/chat/chat_server.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include "chat_server.h" 6 | 7 | #include "kbase/chrono_util.h" 8 | #include "kbase/error_exception_util.h" 9 | #include "kbase/logging.h" 10 | #include "kbase/string_format.h" 11 | #include "kbase/string_util.h" 12 | 13 | #include "ezio/socket_address.h" 14 | 15 | using namespace std::placeholders; 16 | 17 | ChatServer::ChatServer(unsigned short port) 18 | : srv_(&loop_, ezio::SocketAddress(port), "ChatServer") 19 | { 20 | srv_.set_on_connect(std::bind(&ChatServer::OnConnection, this, _1)); 21 | srv_.set_on_disconnect(std::bind(&ChatServer::OnConnection, this, _1)); 22 | srv_.set_on_message(std::bind(&DataCodec::OnDataReceive, &codec_, _1, _2, _3)); 23 | 24 | codec_.set_on_command(std::bind(&ChatServer::OnCommand, this, _1, _2, _3)); 25 | codec_.set_on_message(std::bind(&ChatServer::OnMessage, this, _1, _2, _3)); 26 | } 27 | 28 | void ChatServer::Start() 29 | { 30 | ezio::TCPServer::Options opt; 31 | opt.worker_num = std::thread::hardware_concurrency(); 32 | 33 | LOG(INFO) << "Chat-Server is about to run at " << srv_.ip_port(); 34 | srv_.Start(opt); 35 | 36 | loop_.Run(); 37 | } 38 | 39 | void ChatServer::OnConnection(const ezio::TCPConnectionPtr& conn) 40 | { 41 | if (conn->connected()) { 42 | LOG(INFO) << "Client at " << conn->peer_addr().ToHostPort() << " is online!"; 43 | OnSystemBroadcast(kbase::StringFormat("{0} joined the chat!", conn->name())); 44 | OnUserOnline(conn); 45 | } else { 46 | OnUserOffline(conn); 47 | OnSystemBroadcast(kbase::StringFormat("{0} left the chat!", conn->name())); 48 | LOG(INFO) << "Client at " << conn->peer_addr().ToHostPort() << " is offline!"; 49 | } 50 | } 51 | 52 | void ChatServer::OnUserOnline(const ezio::TCPConnectionPtr& conn) 53 | { 54 | UserData data(conn->name()); 55 | 56 | std::lock_guard lock(session_mtx_); 57 | sessions_[conn] = std::move(data); 58 | } 59 | 60 | void ChatServer::OnUserOffline(const ezio::TCPConnectionPtr& conn) 61 | { 62 | std::lock_guard lock(session_mtx_); 63 | size_t count = sessions_.erase(conn); 64 | ENSURE(CHECK, count == 1)(count).Require(); 65 | } 66 | 67 | void ChatServer::OnCommand(const ezio::TCPConnectionPtr& conn, const std::string& cmd, 68 | ezio::TimePoint ts) 69 | { 70 | auto time = kbase::TimePointToLocalTime(ts); 71 | auto prefix = kbase::StringPrintf("[%02d:%02d:%02d] *| ", time.first.tm_hour, time.first.tm_min, 72 | time.first.tm_sec); 73 | 74 | if (cmd == kCmdList) { 75 | std::vector members; 76 | 77 | { 78 | std::lock_guard lock(session_mtx_); 79 | for (const auto& session : sessions_) { 80 | members.push_back(session.second.nickname); 81 | } 82 | } 83 | 84 | std::string msg(prefix + "Current members in chat-room:\n"); 85 | msg.append(kbase::JoinString(members, "\n")); 86 | 87 | conn->Send(DataCodec::NewMessage(msg)); 88 | 89 | return; 90 | } 91 | 92 | if (cmd == kCmdName) { 93 | std::string name; 94 | 95 | { 96 | std::lock_guard lock(session_mtx_); 97 | name = sessions_[conn].nickname; 98 | } 99 | 100 | std::string msg(prefix + "Your current nickname is " + name); 101 | conn->Send(DataCodec::NewMessage(msg)); 102 | 103 | return; 104 | } 105 | 106 | if (kbase::StartsWith(cmd, kCmdUseName)) { 107 | std::vector components; 108 | if (kbase::SplitString(cmd, " ", components) != 2) { 109 | std::string error(prefix + "Incorrect usage of $USE-NAME$!"); 110 | conn->Send(DataCodec::NewMessage(error)); 111 | return; 112 | } 113 | 114 | { 115 | std::lock_guard lock(session_mtx_); 116 | sessions_[conn].nickname = components[1]; 117 | } 118 | 119 | std::string msg(prefix + "Your nickname has been changed to " + components[1]); 120 | conn->Send(DataCodec::NewMessage(msg)); 121 | 122 | return; 123 | } 124 | 125 | conn->Send(DataCodec::NewMessage(kbase::StringFormat("Unrecognized command {0}", cmd))); 126 | } 127 | 128 | void ChatServer::OnMessage(const ezio::TCPConnectionPtr& conn, const std::string& msg, 129 | ezio::TimePoint ts) const 130 | { 131 | auto t = kbase::TimePointToLocalTime(ts); 132 | auto time = kbase::StringPrintf("%02d:%02d:%02d", t.first.tm_hour, t.first.tm_min, 133 | t.first.tm_sec); 134 | 135 | // For simplicity, we use grand lock... 136 | std::lock_guard lock(session_mtx_); 137 | auto message = kbase::StringFormat("[{0}] {1}| {2}", time, sessions_.at(conn).nickname, msg); 138 | for (const auto& session : sessions_) { 139 | session.first->Send(DataCodec::NewMessage(message)); 140 | } 141 | } 142 | 143 | void ChatServer::OnSystemBroadcast(const std::string& broad_message) const 144 | { 145 | auto time = kbase::TimePointToLocalTime(std::chrono::system_clock::now()); 146 | auto content = kbase::StringPrintf("[%02d:%02d:%02d] *| %s", 147 | time.first.tm_hour, 148 | time.first.tm_min, 149 | time.first.tm_sec, 150 | broad_message.c_str()); 151 | 152 | std::lock_guard lock(session_mtx_); 153 | for (const auto& session : sessions_) { 154 | session.first->Send(DataCodec::NewMessage(content)); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /examples/chat/chat_server.h: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #ifndef CHAT_CHAT_SERVER_H_ 6 | #define CHAT_CHAT_SERVER_H_ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "kbase/basic_macros.h" 14 | 15 | #include "ezio/event_loop.h" 16 | #include "ezio/tcp_server.h" 17 | 18 | #include "data_codec.h" 19 | 20 | class ChatServer { 21 | private: 22 | struct UserData { 23 | std::string nickname; 24 | 25 | UserData() = default; 26 | 27 | explicit UserData(std::string uname) 28 | : nickname(std::move(uname)) 29 | {} 30 | }; 31 | 32 | public: 33 | explicit ChatServer(unsigned short port); 34 | 35 | ~ChatServer() = default; 36 | 37 | DISALLOW_COPY(ChatServer); 38 | 39 | DISALLOW_MOVE(ChatServer); 40 | 41 | void Start(); 42 | 43 | private: 44 | void OnConnection(const ezio::TCPConnectionPtr& conn); 45 | 46 | void OnUserOnline(const ezio::TCPConnectionPtr& conn); 47 | 48 | void OnUserOffline(const ezio::TCPConnectionPtr& conn); 49 | 50 | void OnCommand(const ezio::TCPConnectionPtr& conn, const std::string& cmd, ezio::TimePoint ts); 51 | 52 | void OnMessage(const ezio::TCPConnectionPtr& conn, const std::string& msg, 53 | ezio::TimePoint ts) const; 54 | 55 | void OnSystemBroadcast(const std::string& broad_message) const; 56 | 57 | private: 58 | ezio::EventLoop loop_; 59 | ezio::TCPServer srv_; 60 | 61 | mutable std::mutex session_mtx_; 62 | // Use TCPConnectionPtr as session id. 63 | std::unordered_map sessions_; 64 | 65 | DataCodec codec_; 66 | }; 67 | 68 | #endif // CHAT_CHAT_SERVER_H_ 69 | -------------------------------------------------------------------------------- /examples/chat/chat_server_main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include "kbase/at_exit_manager.h" 6 | #include "kbase/command_line.h" 7 | #include "kbase/logging.h" 8 | 9 | #include "ezio/io_service_context.h" 10 | 11 | #include "chat_server.h" 12 | 13 | constexpr const kbase::CommandLine::CharType kSwitchPort[] = CMDLINE_LITERAL("port"); 14 | 15 | int main(int argc, char* argv[]) 16 | { 17 | kbase::AtExitManager exit_manager; 18 | 19 | kbase::CommandLine::Init(argc, argv); 20 | auto cmdline = kbase::CommandLine::ForCurrentProcess(); 21 | 22 | kbase::LoggingSettings logging_settings; 23 | logging_settings.logging_destination = kbase::LoggingDestination::LogToSystemDebugLog; 24 | kbase::ConfigureLoggingSettings(logging_settings); 25 | 26 | std::string port("9876"); 27 | IGNORE_RESULT(cmdline.GetSwitchValueASCII(kSwitchPort, port)); 28 | 29 | ezio::IOServiceContext::Init(); 30 | 31 | ChatServer server(static_cast(std::stoul(port))); 32 | server.Start(); 33 | 34 | return 0; 35 | } 36 | -------------------------------------------------------------------------------- /examples/chat/data_codec.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include "data_codec.h" 6 | 7 | #include 8 | 9 | #include "kbase/error_exception_util.h" 10 | #include "kbase/logging.h" 11 | 12 | #include "ezio/buffer.h" 13 | 14 | void DataCodec::OnDataReceive(const ezio::TCPConnectionPtr& conn, ezio::Buffer& buf, 15 | ezio::TimePoint ts) const 16 | { 17 | while (buf.readable_size() >= (sizeof(DataType) + kDataLenTypeSize)) { 18 | auto data_type = static_cast(buf.PeekAs()); 19 | if (!(DataType::TypeBegin < data_type && data_type < DataType::TypeEnd)) { 20 | LOG(ERROR) << "Invalid data type: " << data_type; 21 | conn->Shutdown(); 22 | return; 23 | } 24 | 25 | auto data_len = ezio::NetworkToHost( 26 | *reinterpret_cast(buf.Peek() + sizeof(DataType))); 27 | if (data_len <= 0) { 28 | LOG(ERROR) << "Invalid data length: " << data_len; 29 | conn->Shutdown(); 30 | return; 31 | } 32 | 33 | if (buf.readable_size() >= (sizeof(DataType) + kDataLenTypeSize + data_len)) { 34 | buf.Consume(sizeof(DataType)); 35 | buf.Consume(kDataLenTypeSize); 36 | auto data = buf.ReadAsString(data_len); 37 | DispatchParsedData(data_type, data, conn, ts); 38 | } 39 | } 40 | } 41 | 42 | void DataCodec::DispatchParsedData(DataType type, const std::string& data, 43 | const ezio::TCPConnectionPtr& conn, ezio::TimePoint ts) const 44 | { 45 | switch (type) { 46 | case DataType::Command: 47 | on_command_(conn, data, ts); 48 | break; 49 | 50 | case DataType::Message: 51 | on_message_(conn, data, ts); 52 | break; 53 | 54 | default: 55 | ENSURE(CHECK, kbase::NotReached()).Require(); 56 | break; 57 | } 58 | } 59 | 60 | // static 61 | bool DataCodec::MatchCommand(const std::string& str, std::string& cmd) 62 | { 63 | std::smatch result; 64 | if (!std::regex_match(str, result, std::regex(R"(^\s*\$([\w\-]+)\$(\s+([\w\d\-_]+))?\s*$)"))) { 65 | return false; 66 | } 67 | 68 | cmd = result[1].str(); 69 | if (result[3].matched) { 70 | ENSURE(CHECK, result.size() == 4)(result.size()).Require(); 71 | cmd.append(" ").append(result[3]); 72 | } 73 | 74 | return true; 75 | } 76 | 77 | // static 78 | std::string DataCodec::NewMessage(const std::string& msg) 79 | { 80 | ezio::Buffer buf(64); 81 | buf.Write(static_cast(DataType::Message)); 82 | buf.Write(static_cast(msg.size())); 83 | buf.Write(msg.data(), msg.size()); 84 | 85 | return buf.ReadAllAsString(); 86 | } 87 | 88 | // static 89 | std::string DataCodec::NewCommand(const std::string& command) 90 | { 91 | ezio::Buffer buf(16); 92 | buf.Write(static_cast(DataType::Command)); 93 | buf.Write(static_cast(command.size())); 94 | buf.Write(command.data(), command.size()); 95 | 96 | return buf.ReadAllAsString(); 97 | } 98 | -------------------------------------------------------------------------------- /examples/chat/data_codec.h: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #ifndef DATA_CODEC_H_ 6 | #define DATA_CODEC_H_ 7 | 8 | #include 9 | 10 | #include "kbase/basic_macros.h" 11 | #include "kbase/string_view.h" 12 | 13 | #include "ezio/chrono_utils.h" 14 | #include "ezio/tcp_connection.h" 15 | 16 | constexpr kbase::StringView kCmdList {"LIST"}; 17 | constexpr kbase::StringView kCmdName {"NAME"}; 18 | constexpr kbase::StringView kCmdUseName {"USE-NAME"}; 19 | 20 | class DataCodec { 21 | public: 22 | enum DataType : int8_t { 23 | TypeBegin, 24 | Command, 25 | Message, 26 | TypeEnd 27 | }; 28 | 29 | using CommandHandler = std::function; 31 | using MessageHandler = std::function; 33 | 34 | DataCodec() = default; 35 | 36 | ~DataCodec() = default; 37 | 38 | DISALLOW_COPY(DataCodec); 39 | 40 | DISALLOW_MOVE(DataCodec); 41 | 42 | static bool MatchCommand(const std::string& str, std::string& cmd); 43 | 44 | static std::string NewMessage(const std::string& msg); 45 | 46 | static std::string NewCommand(const std::string& command); 47 | 48 | void set_on_command(CommandHandler handler) 49 | { 50 | on_command_ = std::move(handler); 51 | } 52 | 53 | void set_on_message(MessageHandler handler) 54 | { 55 | on_message_ = std::move(handler); 56 | } 57 | 58 | void OnDataReceive(const ezio::TCPConnectionPtr& conn, ezio::Buffer& buf, 59 | ezio::TimePoint ts) const; 60 | 61 | private: 62 | void DispatchParsedData(DataType type, 63 | const std::string& data, 64 | const ezio::TCPConnectionPtr& conn, 65 | ezio::TimePoint ts) const; 66 | 67 | private: 68 | static constexpr size_t kDataLenTypeSize = sizeof(uint16_t); 69 | 70 | CommandHandler on_command_; 71 | MessageHandler on_message_; 72 | }; 73 | 74 | #endif // DATA_CODEC_H_ 75 | -------------------------------------------------------------------------------- /examples/echo/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | add_executable(echo-server echo_server.cpp) 3 | 4 | apply_common_compile_properties_to_target(echo-server) 5 | 6 | set_target_properties(echo-server PROPERTIES 7 | FOLDER examples/echo 8 | ) 9 | 10 | target_link_libraries(echo-server 11 | PRIVATE 12 | ezio 13 | kbase 14 | ) 15 | 16 | add_executable(echo-client echo_client.cpp) 17 | 18 | apply_common_compile_properties_to_target(echo-client) 19 | 20 | set_target_properties(echo-client PROPERTIES 21 | FOLDER examples/echo 22 | ) 23 | 24 | target_link_libraries(echo-client 25 | PRIVATE 26 | ezio 27 | kbase 28 | ) 29 | -------------------------------------------------------------------------------- /examples/echo/echo_client.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include 6 | #include 7 | 8 | #include "kbase/at_exit_manager.h" 9 | #include "kbase/basic_macros.h" 10 | #include "kbase/command_line.h" 11 | #include "kbase/error_exception_util.h" 12 | #include "kbase/logging.h" 13 | #include "kbase/string_encoding_conversions.h" 14 | 15 | #include "ezio/event_loop.h" 16 | #include "ezio/io_service_context.h" 17 | #include "ezio/tcp_client.h" 18 | 19 | using namespace std::placeholders; 20 | 21 | class EchoClient { 22 | public: 23 | explicit EchoClient(const ezio::SocketAddress& addr) 24 | : tcp_client_(&io_loop_, addr, "EchoClient") 25 | { 26 | tcp_client_.set_on_connect(std::bind(&EchoClient::OnConnection, this, _1)); 27 | tcp_client_.set_on_disconnect(std::bind(&EchoClient::OnConnection, this, _1)); 28 | tcp_client_.set_on_message(std::bind(&EchoClient::OnReceiveMessage, this, _1, _2, _3)); 29 | } 30 | 31 | ~EchoClient() = default; 32 | 33 | DISALLOW_COPY(EchoClient); 34 | 35 | DISALLOW_MOVE(EchoClient); 36 | 37 | void Run() 38 | { 39 | tcp_client_.Connect(); 40 | io_loop_.Run(); 41 | } 42 | 43 | private: 44 | void ReadUserInput() 45 | { 46 | ENSURE(CHECK, !!conn_).Require(); 47 | 48 | std::string s; 49 | if (!std::getline(std::cin, s, '\n')) { 50 | tcp_client_.Disconnect(); 51 | return; 52 | } 53 | 54 | conn_->Send(s); 55 | } 56 | 57 | void OnConnection(const ezio::TCPConnectionPtr& conn) 58 | { 59 | if (conn->connected()) { 60 | LOG(INFO) << "Connected to " << conn->peer_addr().ToHostPort() << "; local addr: " 61 | << conn->local_addr().ToHostPort(); 62 | conn->SetTCPNoDelay(true); 63 | conn_ = conn; 64 | ReadUserInput(); 65 | } else { 66 | LOG(INFO) << "Disconnect from the server"; 67 | conn_ = nullptr; 68 | io_loop_.Quit(); 69 | } 70 | } 71 | 72 | void OnReceiveMessage(const ezio::TCPConnectionPtr&, ezio::Buffer& buf, ezio::TimePoint) 73 | { 74 | std::cout << buf.ReadAllAsString() << "\n"; 75 | ReadUserInput(); 76 | } 77 | 78 | private: 79 | ezio::EventLoop io_loop_; 80 | ezio::TCPClient tcp_client_; 81 | // TODO: Should we incorporate this connection-ptr into TCPClient? 82 | ezio::TCPConnectionPtr conn_; 83 | }; 84 | 85 | struct InvalidCmdArgsHandler { 86 | std::exception_ptr eptr; 87 | 88 | ~InvalidCmdArgsHandler() 89 | { 90 | if (eptr) { 91 | try { 92 | std::rethrow_exception(eptr); 93 | } catch (const std::exception& ex) { 94 | LOG(ERROR) << "Critical Failure: " << ex.what(); 95 | LOG(INFO) << "Usage: echo-client [ip] [port]"; 96 | } 97 | 98 | exit(1); 99 | } 100 | } 101 | }; 102 | 103 | int main(int argc, char* argv[]) 104 | { 105 | kbase::AtExitManager exit_manager; 106 | 107 | kbase::LoggingSettings logging_settings; 108 | logging_settings.logging_destination = kbase::LoggingDestination::LogToSystemDebugLog; 109 | kbase::ConfigureLoggingSettings(logging_settings); 110 | 111 | kbase::AlwaysCheckFirstInDebug(false); 112 | 113 | kbase::CommandLine::Init(argc, argv); 114 | 115 | const auto& cmdline = kbase::CommandLine::ForCurrentProcess(); 116 | 117 | std::unique_ptr endpoint; 118 | 119 | // Hnadle tedious command-line args. 120 | 121 | { 122 | InvalidCmdArgsHandler handler; 123 | 124 | try { 125 | auto params = cmdline.GetParameters(); 126 | ENSURE(THROW, params.size() >= 2)(params.size()).Require("at least ip and port"); 127 | 128 | #if defined(OS_WIN) 129 | auto ip = kbase::WideToASCII(params[0]); 130 | #else 131 | auto ip = params[0]; 132 | #endif 133 | endpoint = std::make_unique( 134 | ip, static_cast(std::stoul(params[1]))); 135 | } catch (const std::exception&) { 136 | handler.eptr = std::current_exception(); 137 | } 138 | } 139 | 140 | ezio::IOServiceContext::Init(); 141 | 142 | EchoClient client(*endpoint); 143 | 144 | client.Run(); 145 | 146 | return 0; 147 | } 148 | -------------------------------------------------------------------------------- /examples/echo/echo_server.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "kbase/at_exit_manager.h" 10 | #include "kbase/basic_macros.h" 11 | #include "kbase/command_line.h" 12 | #include "kbase/logging.h" 13 | 14 | #include "ezio/event_loop.h" 15 | #include "ezio/io_service_context.h" 16 | #include "ezio/tcp_server.h" 17 | 18 | using namespace std::placeholders; 19 | 20 | class EchoServer { 21 | public: 22 | EchoServer(unsigned short port, bool multithreaded) 23 | : tcp_srv_(&io_loop_, ezio::SocketAddress(port), "EchoServer"), 24 | multithreaded_(multithreaded), 25 | connections_(0), 26 | received_messages_(0) 27 | { 28 | tcp_srv_.set_on_connect(std::bind(&EchoServer::OnConnection, this, _1)); 29 | tcp_srv_.set_on_disconnect(std::bind(&EchoServer::OnConnection, this, _1)); 30 | tcp_srv_.set_on_message(std::bind(&EchoServer::OnMessage, this, _1, _2, _3)); 31 | io_loop_.RunTaskEvery(std::bind(&EchoServer::DumpServerStatus, this), 32 | std::chrono::seconds(5)); 33 | } 34 | 35 | ~EchoServer() = default; 36 | 37 | DISALLOW_COPY(EchoServer); 38 | 39 | DISALLOW_MOVE(EchoServer); 40 | 41 | void Start() 42 | { 43 | ezio::TCPServer::Options opt; 44 | if (multithreaded_) { 45 | opt.worker_num = std::thread::hardware_concurrency(); 46 | } 47 | 48 | LOG(INFO) << tcp_srv_.name() << " is about to run at " << tcp_srv_.ip_port() 49 | << "; multithreading: " << multithreaded_; 50 | 51 | tcp_srv_.Start(opt); 52 | 53 | io_loop_.Run(); 54 | } 55 | 56 | private: 57 | void OnConnection(const ezio::TCPConnectionPtr& conn) 58 | { 59 | const char* action = nullptr; 60 | if (conn->connected()) { 61 | action = "connected"; 62 | connections_.fetch_add(1); 63 | conn->SetTCPNoDelay(true); 64 | } else { 65 | action = "disconnected"; 66 | connections_.fetch_sub(1); 67 | } 68 | 69 | LOG(INFO) << conn->name() << " at " << conn->peer_addr().ToHostPort() << " is " << action; 70 | } 71 | 72 | void OnMessage(const ezio::TCPConnectionPtr& conn, ezio::Buffer& buf, ezio::TimePoint) 73 | { 74 | auto msg = buf.ReadAllAsString(); 75 | received_messages_.fetch_add(1); 76 | conn->Send(msg); 77 | } 78 | 79 | void DumpServerStatus() const 80 | { 81 | LOG(INFO) << "active connections = " 82 | << connections_.load(std::memory_order::memory_order_relaxed) 83 | << " | received messages = " 84 | << received_messages_.load(std::memory_order::memory_order_relaxed); 85 | } 86 | 87 | private: 88 | ezio::EventLoop io_loop_; 89 | ezio::TCPServer tcp_srv_; 90 | bool multithreaded_; 91 | std::atomic connections_; 92 | std::atomic received_messages_; 93 | }; 94 | 95 | constexpr const kbase::CommandLine::CharType kSwitchPort[] 96 | = CMDLINE_LITERAL("port"); 97 | constexpr const kbase::CommandLine::CharType kSwitchNoMultithread[] 98 | = CMDLINE_LITERAL("no-multithread"); 99 | 100 | int main(int argc, char* argv[]) 101 | { 102 | kbase::AtExitManager exit_manager; 103 | 104 | kbase::LoggingSettings logging_settings; 105 | logging_settings.logging_destination = kbase::LoggingDestination::LogToSystemDebugLog; 106 | kbase::ConfigureLoggingSettings(logging_settings); 107 | 108 | kbase::CommandLine::Init(argc, argv); 109 | 110 | const auto& cmdline = kbase::CommandLine::ForCurrentProcess(); 111 | 112 | std::string port; 113 | if (!cmdline.GetSwitchValueASCII(kSwitchPort, port)) { 114 | LOG(ERROR) << "Usage: echo_server --port= [--no-multithread]"; 115 | return 1; 116 | } 117 | 118 | bool multithreaded = !cmdline.HasSwitch(kSwitchNoMultithread); 119 | 120 | ezio::IOServiceContext::Init(); 121 | 122 | EchoServer server(static_cast(std::stoul(port)), multithreaded); 123 | 124 | server.Start(); 125 | 126 | return 0; 127 | } 128 | -------------------------------------------------------------------------------- /examples/socks4a/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | set(socks4a_SRCS 3 | main.cpp 4 | socks_proxy.cpp 5 | tunnel.cpp 6 | ) 7 | 8 | set(socks4a_HEADERS 9 | socks_proxy.h 10 | tunnel.h 11 | ) 12 | 13 | set(socks4a_FILES ${socks4a_SRCS} ${socks4a_HEADERS}) 14 | 15 | source_group("socks4a" FILES ${socks4a_FILES}) 16 | 17 | add_executable(socks4a-proxy ${socks4a_FILES}) 18 | 19 | apply_common_compile_properties_to_target(socks4a-proxy) 20 | 21 | set_target_properties(socks4a-proxy PROPERTIES 22 | FOLDER examples/socks-4a 23 | ) 24 | 25 | target_link_libraries(socks4a-proxy 26 | PRIVATE 27 | ezio 28 | kbase 29 | ) 30 | -------------------------------------------------------------------------------- /examples/socks4a/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include 6 | 7 | #include "kbase/at_exit_manager.h" 8 | #include "kbase/basic_macros.h" 9 | #include "kbase/command_line.h" 10 | #include "kbase/logging.h" 11 | 12 | #include "ezio/event_loop.h" 13 | #include "ezio/io_service_context.h" 14 | #include "ezio/socket_address.h" 15 | 16 | #include "socks_proxy.h" 17 | 18 | #if defined(OS_POSIX) 19 | #include 20 | #elif defined(OS_WIN) 21 | #include 22 | #endif 23 | 24 | namespace { 25 | 26 | ezio::EventLoop* main_loop = nullptr; 27 | 28 | void InstallSigIntHandler() 29 | { 30 | #if defined(OS_POSIX) 31 | signal(SIGINT, [](int) { 32 | main_loop->Quit(); 33 | }); 34 | #elif defined(OS_WIN) 35 | SetConsoleCtrlHandler([](DWORD ctrl_type) -> BOOL { 36 | if (ctrl_type == CTRL_C_EVENT) { 37 | main_loop->Quit(); 38 | return TRUE; 39 | } 40 | 41 | return FALSE; 42 | }, TRUE); 43 | #endif 44 | } 45 | 46 | } // namespace 47 | 48 | int main(int argc, char* argv[]) 49 | { 50 | kbase::AtExitManager exit_manager; 51 | 52 | kbase::CommandLine::Init(argc, argv); 53 | auto args = kbase::CommandLine::ForCurrentProcess().GetArgs(); 54 | if (args.size() < 2 || args[1].empty()) { 55 | printf("Usage: socks4a port\n"); 56 | return 1; 57 | } 58 | 59 | auto port = static_cast(std::stoi(args[1])); 60 | 61 | kbase::LoggingSettings logging_settings; 62 | logging_settings.logging_destination = kbase::LoggingDestination::LogToSystemDebugLog; 63 | kbase::ConfigureLoggingSettings(logging_settings); 64 | 65 | InstallSigIntHandler(); 66 | 67 | ezio::IOServiceContext::Init(); 68 | 69 | ezio::SocketAddress addr(port); 70 | 71 | ezio::EventLoop loop; 72 | main_loop = &loop; 73 | 74 | SocksProxy proxy(&loop, addr); 75 | proxy.Start(); 76 | 77 | loop.Run(); 78 | 79 | DLOG(INFO) << "Bye-bye!"; 80 | 81 | return 0; 82 | } 83 | -------------------------------------------------------------------------------- /examples/socks4a/socks_proxy.h: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #ifndef SOCKS4A_SOCKS_PROXY_H_ 6 | #define SOCKS4A_SOCKS_PROXY_H_ 7 | 8 | #include 9 | #include 10 | 11 | #include "ezio/event_loop.h" 12 | #include "ezio/socket_address.h" 13 | #include "ezio/tcp_connection.h" 14 | #include "ezio/tcp_server.h" 15 | 16 | #include "tunnel.h" 17 | 18 | class SocksProxy { 19 | public: 20 | SocksProxy(ezio::EventLoop* main_loop, const ezio::SocketAddress& addr); 21 | 22 | ~SocksProxy() = default; 23 | 24 | DISALLOW_COPY(SocksProxy); 25 | 26 | DISALLOW_MOVE(SocksProxy); 27 | 28 | void Start(); 29 | 30 | private: 31 | void OnClientConnect(const ezio::TCPConnectionPtr& conn) const; 32 | 33 | void OnClientDisconnect(const ezio::TCPConnectionPtr& conn); 34 | 35 | void OnClientMessage(const ezio::TCPConnectionPtr& conn, ezio::Buffer& buf, ezio::TimePoint ts); 36 | 37 | private: 38 | ezio::EventLoop* main_loop_; 39 | std::mutex tunnels_mtx_; 40 | std::unordered_map tunnels_; 41 | ezio::TCPServer server_; 42 | }; 43 | 44 | #endif // SOCKS4A_SOCKS_PROXY_H_ 45 | -------------------------------------------------------------------------------- /examples/socks4a/tunnel.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include "tunnel.h" 6 | 7 | #include 8 | #include 9 | 10 | #include "kbase/error_exception_util.h" 11 | #include "kbase/logging.h" 12 | 13 | using namespace std::placeholders; 14 | 15 | namespace { 16 | 17 | void DummyConnectionHandler(const ezio::TCPConnectionPtr&) 18 | {} 19 | 20 | void DummyMessageHandler(const ezio::TCPConnectionPtr&, ezio::Buffer&, ezio::TimePoint) 21 | {} 22 | 23 | } // namespace 24 | 25 | Tunnel::Tunnel(ezio::EventLoop* loop, const ezio::SocketAddress& addr, 26 | const ezio::TCPConnectionPtr& client_conn) 27 | : name_(client_conn->name() + "-tunnel"), 28 | client_(loop, addr, name_), 29 | client_conn_(client_conn), 30 | teardown_(false) 31 | {} 32 | 33 | Tunnel::~Tunnel() 34 | {} 35 | 36 | void Tunnel::Connect() 37 | { 38 | Setup(); 39 | client_.Connect(); 40 | LOG(INFO) << "Tunneling " << client_conn_->peer_addr().ToHostPort() << " <-> " 41 | << client_.remote_addr().ToHostPort(); 42 | } 43 | 44 | void Tunnel::Close() 45 | { 46 | LOG(INFO) << "Close tunnel " << client_conn_->peer_addr().ToHostPort() << " <-> " 47 | << client_.remote_addr().ToHostPort(); 48 | 49 | // Still would receive remote events in a short period after calling the Disconnect(). 50 | client_.Disconnect(); 51 | Teardown(); 52 | } 53 | 54 | void Tunnel::Send(kbase::StringView data) 55 | { 56 | FORCE_AS_NON_CONST_FUNCTION(); 57 | client_.connection()->Send(data); 58 | } 59 | 60 | void Tunnel::Setup() 61 | { 62 | client_.set_on_connect(std::bind(&Tunnel::OnRemoteConnect, shared_from_this(), _1)); 63 | client_.set_on_disconnect(std::bind(&Tunnel::OnRemoteDisconnect, shared_from_this(), _1)); 64 | client_.set_on_message(std::bind(&Tunnel::OnRemoteMessage, shared_from_this(), _1, _2, _3)); 65 | } 66 | 67 | void Tunnel::Teardown() 68 | { 69 | ENSURE(CHECK, !teardown_).Require(); 70 | teardown_ = true; 71 | 72 | // Release binding with the tunnel instance from TCPClient to let the tunnel go for 73 | // destruction. 74 | client_.set_on_connect(&DummyConnectionHandler); 75 | client_.set_on_disconnect(&DummyConnectionHandler); 76 | client_.set_on_message(&DummyMessageHandler); 77 | 78 | client_conn_ = nullptr; 79 | } 80 | 81 | void Tunnel::OnRemoteConnect(const ezio::TCPConnectionPtr& conn) const 82 | { 83 | DLOG(INFO) << conn->name() << " has connected on thread " << ezio::this_thread::GetName(); 84 | 85 | client_conn_->SetTCPNoDelay(true); 86 | 87 | char granted_resp[] = "\x00\x5A\x2A\x2A\x2A\x2A\x2A\x2A"; 88 | auto& addr = client_.remote_addr().raw(); 89 | memcpy(granted_resp + 2, &addr.sin_port, sizeof(addr.sin_port)); 90 | memcpy(granted_resp + 4, &addr.sin_addr.s_addr, sizeof(addr.sin_addr.s_addr)); 91 | client_conn_->Send(kbase::StringView(granted_resp, 8)); 92 | 93 | conn->SetTCPNoDelay(true); 94 | } 95 | 96 | void Tunnel::OnRemoteDisconnect(const ezio::TCPConnectionPtr& conn) const 97 | { 98 | DLOG(INFO) << conn->name() << " has disconnected on thread " << ezio::this_thread::GetName(); 99 | 100 | if (teardown_) { 101 | return; 102 | } 103 | 104 | // Shutdowning the client connection will trigger OnClientDisconnect() on the proxy too. 105 | ENSURE(CHECK, !!client_conn_).Require(); 106 | client_conn_->Shutdown(); 107 | } 108 | 109 | void Tunnel::OnRemoteMessage(const ezio::TCPConnectionPtr&, ezio::Buffer& buf, 110 | ezio::TimePoint) const 111 | { 112 | if (teardown_) { 113 | return; 114 | } 115 | 116 | ENSURE(CHECK, !!client_conn_).Require(); 117 | client_conn_->Send(buf.ReadAllAsString()); 118 | } 119 | -------------------------------------------------------------------------------- /examples/socks4a/tunnel.h: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #ifndef SOCKS4A_TUNNEL_H_ 6 | #define SOCKS4A_TUNNEL_H_ 7 | 8 | #include 9 | 10 | #include "kbase/basic_macros.h" 11 | 12 | #include "ezio/event_loop.h" 13 | #include "ezio/socket_address.h" 14 | #include "ezio/tcp_client.h" 15 | 16 | class Tunnel : public std::enable_shared_from_this { 17 | public: 18 | Tunnel(ezio::EventLoop* loop, const ezio::SocketAddress& addr, 19 | const ezio::TCPConnectionPtr& client_conn); 20 | 21 | ~Tunnel(); 22 | 23 | DISALLOW_COPY(Tunnel); 24 | 25 | DISALLOW_MOVE(Tunnel); 26 | 27 | void Connect(); 28 | 29 | void Close(); 30 | 31 | void Send(kbase::StringView data); 32 | 33 | const std::string& name() const noexcept 34 | { 35 | return name_; 36 | } 37 | 38 | private: 39 | void Setup(); 40 | 41 | void Teardown(); 42 | 43 | void OnRemoteConnect(const ezio::TCPConnectionPtr& conn) const; 44 | 45 | void OnRemoteDisconnect(const ezio::TCPConnectionPtr& conn) const; 46 | 47 | void OnRemoteMessage(const ezio::TCPConnectionPtr& conn, ezio::Buffer& buf, 48 | ezio::TimePoint ts) const; 49 | 50 | private: 51 | std::string name_; 52 | ezio::TCPClient client_; 53 | ezio::TCPConnectionPtr client_conn_; 54 | bool teardown_; 55 | }; 56 | 57 | using TunnelPtr = std::shared_ptr; 58 | 59 | #endif // SOCKS4A_TUNNEL_H_ 60 | -------------------------------------------------------------------------------- /ezio/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | declare_dep_module(KBase 3 | VERSION 0.1.4 4 | GIT_REPOSITORY https://github.com/kingsamchen/KBase.git 5 | GIT_TAG v0.1.4 6 | ) 7 | 8 | set(ezio_SRCS 9 | acceptor.cpp 10 | buffer.cpp 11 | connector_base.cpp 12 | event_loop.cpp 13 | event_pump.cpp 14 | io_service_context.cpp 15 | notifier.cpp 16 | socket_address.cpp 17 | socket_utils.cpp 18 | tcp_client.cpp 19 | tcp_connection.cpp 20 | tcp_server.cpp 21 | this_thread.cpp 22 | thread.cpp 23 | timer.cpp 24 | timer_queue.cpp 25 | worker_pool.cpp 26 | ) 27 | 28 | if(WIN32) 29 | list(APPEND ezio_SRCS 30 | acceptor_win.cpp 31 | connector_win.cpp 32 | event_pump_impl_win.cpp 33 | notifier_win.cpp 34 | socket_utils_win.cpp 35 | tcp_connection_win.cpp 36 | winsock_context.cpp 37 | ) 38 | elseif(UNIX) 39 | list(APPEND ezio_SRCS 40 | acceptor_posix.cpp 41 | buffer_posix.cpp 42 | connector_posix.cpp 43 | event_pump_impl_posix.cpp 44 | ignore_sigpipe.cpp 45 | notifier_posix.cpp 46 | socket_utils_posix.cpp 47 | tcp_connection_posix.cpp 48 | ) 49 | endif() 50 | 51 | set(ezio_HEADERS 52 | acceptor.h 53 | buffer.h 54 | chrono_utils.h 55 | common_event_handlers.h 56 | connector_base.h 57 | connector.h 58 | endian_utils.h 59 | event_loop.h 60 | event_pump.h 61 | io_context.h 62 | io_service_context.h 63 | notifier.h 64 | scoped_socket.h 65 | socket_address.h 66 | socket_utils.h 67 | tcp_client.h 68 | tcp_connection.h 69 | tcp_server.h 70 | this_thread.h 71 | thread.h 72 | timer_id.h 73 | timer_queue.h 74 | timer.h 75 | worker_pool.h 76 | ) 77 | 78 | if(WIN32) 79 | list(APPEND ezio_HEADERS 80 | connector_win.h 81 | event_pump_impl_win.h 82 | winsock_context.h 83 | ) 84 | elseif(UNIX) 85 | list(APPEND ezio_HEADERS 86 | connector_posix.h 87 | event_pump_impl_posix.h 88 | ignore_sigpipe.h 89 | ) 90 | endif() 91 | 92 | set(ezio_FILES ${ezio_HEADERS} ${ezio_SRCS}) 93 | 94 | source_group("ezio" FILES ${ezio_FILES}) 95 | 96 | add_library(ezio ${ezio_FILES}) 97 | 98 | apply_common_compile_properties_to_target(ezio) 99 | 100 | if(MSVC AND EZIO_ENABLE_CODE_ANALYSIS) 101 | enable_msvc_static_analysis_for_target(ezio 102 | WDL 103 | /wd6387 104 | ) 105 | endif() 106 | 107 | target_include_directories(ezio 108 | PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../ 109 | ) 110 | 111 | target_compile_definitions(ezio 112 | PUBLIC WIN32_LEAN_AND_MEAN 113 | ) 114 | 115 | set_target_properties(ezio PROPERTIES 116 | COTIRE_CXX_PREFIX_HEADER_INIT "${EZIO_PCH_HEADER}" 117 | ) 118 | 119 | cotire(ezio) 120 | 121 | target_link_libraries(ezio 122 | PRIVATE kbase 123 | ) 124 | 125 | if(UNIX) 126 | target_link_libraries(ezio 127 | INTERFACE pthread) 128 | endif() 129 | -------------------------------------------------------------------------------- /ezio/acceptor.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include "ezio/acceptor.h" 6 | 7 | #include "kbase/error_exception_util.h" 8 | 9 | #include "ezio/event_loop.h" 10 | #include "ezio/socket_utils.h" 11 | 12 | namespace ezio { 13 | 14 | #if defined(OS_POSIX) 15 | 16 | int MakeSentinelFD(); 17 | 18 | #endif 19 | 20 | Acceptor::Acceptor(EventLoop* loop, const SocketAddress& addr) 21 | : loop_(loop), 22 | listening_sock_(socket::CreateNonBlockingSocket()), 23 | listening_notifier_(loop, listening_sock_), 24 | #if defined(OS_POSIX) 25 | sentinel_fd_(MakeSentinelFD()), 26 | #elif defined(OS_WIN) 27 | accept_conn_(socket::CreateNonBlockingSocket()), 28 | accept_req_(IOEvent::Read), 29 | #endif 30 | listening_(false) 31 | { 32 | socket::SetReuseAddr(listening_sock_, true); 33 | socket::BindOrThrow(listening_sock_, addr); 34 | 35 | listening_notifier_.set_on_read(std::bind(&Acceptor::HandleNewConnection, this)); 36 | } 37 | 38 | Acceptor::~Acceptor() 39 | { 40 | listening_notifier_.DisableAll(); 41 | listening_notifier_.Detach(); 42 | } 43 | 44 | void Acceptor::Listen() 45 | { 46 | ENSURE(CHECK, loop_->BelongsToCurrentThread()).Require(); 47 | 48 | listening_ = true; 49 | listening_notifier_.EnableReading(); 50 | 51 | socket::ListenOrThrow(listening_sock_); 52 | 53 | #if defined(OS_WIN) 54 | PostAccept(); 55 | #endif 56 | } 57 | 58 | } // namespace ezio 59 | -------------------------------------------------------------------------------- /ezio/acceptor.h: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #ifndef EZIO_ACCEPTOR_H_ 6 | #define EZIO_ACCEPTOR_H_ 7 | 8 | #include 9 | 10 | #include "kbase/basic_macros.h" 11 | 12 | #include "ezio/notifier.h" 13 | #include "ezio/scoped_socket.h" 14 | #include "ezio/socket_address.h" 15 | 16 | #if defined(OS_POSIX) 17 | #include "kbase/scoped_handle.h" 18 | #elif defined(OS_WIN) 19 | #include "ezio/io_context.h" 20 | #endif 21 | 22 | namespace ezio { 23 | 24 | class EventLoop; 25 | 26 | class Acceptor { 27 | public: 28 | using NewConnectionHandler = std::function; 29 | 30 | Acceptor(EventLoop* loop, const SocketAddress& addr); 31 | 32 | ~Acceptor(); 33 | 34 | DISALLOW_COPY(Acceptor); 35 | 36 | DISALLOW_MOVE(Acceptor); 37 | 38 | void Listen(); 39 | 40 | void set_on_new_connection(NewConnectionHandler handler) 41 | { 42 | on_new_connection_ = std::move(handler); 43 | } 44 | 45 | bool listening() const noexcept 46 | { 47 | return listening_; 48 | } 49 | 50 | private: 51 | #if defined(OS_WIN) 52 | void PostAccept(); 53 | #endif 54 | 55 | void HandleNewConnection(); 56 | 57 | private: 58 | EventLoop* loop_; 59 | 60 | ScopedSocket listening_sock_; 61 | Notifier listening_notifier_; 62 | 63 | NewConnectionHandler on_new_connection_; 64 | 65 | #if defined(OS_POSIX) 66 | kbase::ScopedFD sentinel_fd_; 67 | #elif defined(OS_WIN) 68 | ScopedSocket accept_conn_; 69 | IORequest accept_req_; 70 | #endif 71 | 72 | bool listening_; 73 | }; 74 | 75 | } // namespace ezio 76 | 77 | #endif // EZIO_ACCEPTOR_H_ 78 | -------------------------------------------------------------------------------- /ezio/acceptor_posix.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include "ezio/acceptor.h" 6 | 7 | #include 8 | 9 | #include 10 | 11 | #include "kbase/error_exception_util.h" 12 | #include "kbase/logging.h" 13 | 14 | namespace ezio { 15 | 16 | int MakeSentinelFD() 17 | { 18 | int fd = open("/dev/null", O_RDONLY | O_CLOEXEC); 19 | ENSURE(CHECK, fd != -1)(errno).Require(); 20 | return fd; 21 | } 22 | 23 | void Acceptor::HandleNewConnection() 24 | { 25 | while (true) { 26 | sockaddr_in peer_raw_addr {}; 27 | socklen_t addr_len = sizeof(peer_raw_addr); 28 | int conn_fd = accept4(listening_sock_.get(), reinterpret_cast(&peer_raw_addr), 29 | &addr_len, SOCK_NONBLOCK | SOCK_CLOEXEC); 30 | 31 | // Handle failures. 32 | if (conn_fd < 0) { 33 | // No more pending requests. 34 | if (errno == EAGAIN) { 35 | return; 36 | } 37 | 38 | // No looping if we encounterred an error. 39 | 40 | auto err = errno; 41 | LOG(ERROR) << "accept4() failed: " << err; 42 | 43 | if (err == EMFILE) { 44 | sentinel_fd_ = nullptr; 45 | sentinel_fd_.reset(accept(listening_sock_.get(), nullptr, nullptr)); 46 | if (!sentinel_fd_) { 47 | err = errno; 48 | LOG(ERROR) << "Still failed for accept(): " << err; 49 | } 50 | 51 | // Restore. 52 | sentinel_fd_ = nullptr; 53 | sentinel_fd_.reset(MakeSentinelFD()); 54 | } 55 | 56 | return; 57 | } 58 | 59 | ScopedSocket conn_sock(conn_fd); 60 | SocketAddress peer_addr(peer_raw_addr); 61 | 62 | if (on_new_connection_) { 63 | on_new_connection_(std::move(conn_sock), peer_addr); 64 | } else { 65 | conn_sock = nullptr; 66 | LOG(WARNING) << "No handler set for new connections!"; 67 | } 68 | } 69 | } 70 | 71 | } // namespace ezio 72 | -------------------------------------------------------------------------------- /ezio/acceptor_win.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include "ezio/acceptor.h" 6 | 7 | #include "kbase/error_exception_util.h" 8 | #include "kbase/logging.h" 9 | 10 | #include "ezio/io_service_context.h" 11 | #include "ezio/socket_utils.h" 12 | 13 | namespace { 14 | 15 | // This memory block is required by AcceptEx(), however, we don't use it. 16 | constexpr DWORD kAddrLen = sizeof(sockaddr_in) + 16; 17 | char dummy_addr_block[kAddrLen * 2]; 18 | 19 | } // namespace 20 | 21 | namespace ezio { 22 | 23 | void Acceptor::PostAccept() 24 | { 25 | DWORD recv_len = 0; 26 | IOServiceContext::current().AsWinsockContext().AcceptEx(listening_sock_.get(), 27 | accept_conn_.get(), 28 | dummy_addr_block, 29 | 0, 30 | kAddrLen, 31 | kAddrLen, 32 | &recv_len, 33 | &accept_req_); 34 | } 35 | 36 | void Acceptor::HandleNewConnection() 37 | { 38 | auto listener = listening_sock_.get(); 39 | int rv = setsockopt(accept_conn_.get(), SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT, 40 | reinterpret_cast(&listener), sizeof(listener)); 41 | if (rv < 0) { 42 | auto err = WSAGetLastError(); 43 | LOG(ERROR) << "Set accept-conn SO_UPDATE_ACCEPT_CONTEXT failed: " << err; 44 | ENSURE(CHECK, kbase::NotReached())(err).Require(); 45 | } else { 46 | sockaddr_in addr {}; 47 | int addr_len = sizeof(addr); 48 | rv = getpeername(accept_conn_.get(), reinterpret_cast(&addr), &addr_len); 49 | LOG_IF(ERROR, rv != 0) << "getpeername() failed: " << WSAGetLastError(); 50 | 51 | if (on_new_connection_) { 52 | on_new_connection_(std::move(accept_conn_), SocketAddress(addr)); 53 | } else { 54 | accept_conn_ = nullptr; 55 | LOG(WARNING) << "No handler set for new connections!"; 56 | } 57 | } 58 | 59 | accept_req_.Reset(); 60 | accept_conn_ = socket::CreateNonBlockingSocket(); 61 | 62 | PostAccept(); 63 | } 64 | 65 | } // namespace ezio 66 | -------------------------------------------------------------------------------- /ezio/buffer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include "ezio/buffer.h" 6 | 7 | namespace ezio { 8 | 9 | Buffer::Buffer() 10 | : Buffer(kDefaultInitialSize) 11 | {} 12 | 13 | Buffer::Buffer(size_t initial_size) 14 | : buf_(initial_size + kDefaultPrependSize), 15 | reader_index_(kDefaultPrependSize), 16 | writer_index_(kDefaultPrependSize) 17 | {} 18 | 19 | void Buffer::Write(const void* data, size_t size) 20 | { 21 | ReserveWritable(size); 22 | ENSURE(CHECK, writable_size() >= size)(writable_size())(size).Require(); 23 | 24 | memcpy(BeginWrite(), data, size); 25 | EndWrite(size); 26 | } 27 | 28 | void Buffer::Consume(size_t data_size) 29 | { 30 | ENSURE(CHECK, data_size <= readable_size())(data_size)(readable_size()).Require(); 31 | if (data_size < readable_size()) { 32 | reader_index_ += data_size; 33 | } else { 34 | ConsumeAll(); 35 | } 36 | } 37 | 38 | std::string Buffer::ReadAsString(size_t length) 39 | { 40 | ENSURE(CHECK, readable_size() >= length)(readable_size())(length).Require(); 41 | auto b = begin(); 42 | std::string s(b, b + length); 43 | Consume(length); 44 | return s; 45 | } 46 | 47 | std::string Buffer::ReadAllAsString() 48 | { 49 | std::string s(begin(), end()); 50 | ConsumeAll(); 51 | return s; 52 | } 53 | 54 | void Buffer::Prepend(const void* data, size_t size) 55 | { 56 | ENSURE(CHECK, prependable_size() >= size)(prependable_size())(size).Require(); 57 | auto start = reader_index_ - size; 58 | memcpy(buf_.data() + start, data, size); 59 | reader_index_ -= size; 60 | } 61 | 62 | void Buffer::Prepend(int16_t n) 63 | { 64 | auto be = HostToNetwork(n); 65 | Prepend(&be, sizeof(be)); 66 | } 67 | 68 | void Buffer::Prepend(int32_t n) 69 | { 70 | auto be = HostToNetwork(n); 71 | Prepend(&be, sizeof(be)); 72 | } 73 | 74 | void Buffer::Prepend(int64_t n) 75 | { 76 | auto be = HostToNetwork(n); 77 | Prepend(&be, sizeof(be)); 78 | } 79 | 80 | void Buffer::ReserveWritable(size_t new_size) 81 | { 82 | if (writable_size() >= new_size) { 83 | return; 84 | } 85 | 86 | if (prependable_size() + writable_size() < kDefaultPrependSize + new_size) { 87 | buf_.resize(writer_index_ + new_size); 88 | } else { 89 | // Ranges may overlap. 90 | auto data_size = readable_size(); 91 | memmove(buf_.data() + kDefaultPrependSize, buf_.data() + reader_index_, data_size); 92 | reader_index_ = kDefaultPrependSize; 93 | writer_index_ = reader_index_ + data_size; 94 | } 95 | } 96 | 97 | char* Buffer::BeginWrite() 98 | { 99 | return buf_.data() + writer_index_; 100 | } 101 | 102 | void Buffer::EndWrite(size_t written_size) 103 | { 104 | ENSURE(CHECK, writable_size() >= written_size).Require(); 105 | writer_index_ += written_size; 106 | } 107 | 108 | } // namespace ezio 109 | -------------------------------------------------------------------------------- /ezio/buffer_posix.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include "ezio/buffer.h" 6 | 7 | #include 8 | 9 | namespace ezio { 10 | 11 | ssize_t ReadFDInVec(int fd, Buffer& buf) 12 | { 13 | constexpr size_t kExtraBufSize = 65535; 14 | char extra_buf[kExtraBufSize]; 15 | 16 | iovec vec[2]; 17 | vec[0].iov_base = buf.BeginWrite(); 18 | vec[0].iov_len = buf.writable_size(); 19 | vec[1].iov_base = extra_buf; 20 | vec[1].iov_len = kExtraBufSize; 21 | 22 | auto used_vec_cnt = buf.writable_size() < kExtraBufSize ? 2 : 1; 23 | auto size_read = readv(fd, vec, used_vec_cnt); 24 | if (size_read < 0) { 25 | return -1; 26 | } 27 | 28 | if (static_cast(size_read) <= buf.writable_size()) { 29 | buf.EndWrite(static_cast(size_read)); 30 | } else { 31 | auto writable = buf.writable_size(); 32 | buf.EndWrite(writable); 33 | buf.Write(extra_buf, size_read - writable); 34 | } 35 | 36 | return size_read; 37 | } 38 | 39 | } // namespace ezio 40 | -------------------------------------------------------------------------------- /ezio/chrono_utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #ifndef EZIO_CHRONO_UTILS_H_ 6 | #define EZIO_CHRONO_UTILS_H_ 7 | 8 | #include 9 | 10 | namespace ezio { 11 | 12 | #if defined(OS_POSIX) 13 | using TimeDuration = std::chrono::microseconds; 14 | #elif defined(OS_WIN) 15 | using TimeDuration = std::chrono::milliseconds; 16 | #endif 17 | 18 | using TimePoint = std::chrono::time_point; 19 | 20 | template 21 | TimePoint ToTimePoint(const From& tp) 22 | { 23 | return std::chrono::time_point_cast(tp); 24 | } 25 | 26 | } // namespace ezio 27 | 28 | #endif // EZIO_CHRONO_UTILS_H_ 29 | -------------------------------------------------------------------------------- /ezio/common_event_handlers.h: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #ifndef EZIO_COMMON_EVENT_HANDLERS_H_ 6 | #define EZIO_COMMON_EVENT_HANDLERS_H_ 7 | 8 | #include 9 | #include 10 | 11 | #include "ezio/chrono_utils.h" 12 | 13 | namespace ezio { 14 | 15 | class Buffer; 16 | class TCPConnection; 17 | 18 | using TCPConnectionPtr = std::shared_ptr; 19 | 20 | using ConnectionEventHandler = std::function; 21 | using CloseEventHandler = std::function; 22 | using DestroyEventHandler = std::function; 23 | using MessageEventHandler = std::function; 24 | 25 | } // namespace ezio 26 | 27 | #endif // EZIO_COMMON_EVENT_HANDLERS_H_ 28 | -------------------------------------------------------------------------------- /ezio/connector.h: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #ifndef EZIO_CONNECTOR_H_ 6 | #define EZIO_CONNECTOR_H_ 7 | 8 | #include 9 | 10 | #include "kbase/basic_macros.h" 11 | 12 | #if defined(OS_POSIX) 13 | #include "ezio/connector_posix.h" 14 | #elif defined(OS_WIN) 15 | #include "ezio/connector_win.h" 16 | #endif 17 | 18 | namespace ezio { 19 | 20 | #if defined(OS_POSIX) 21 | using Connector = ConnectorPosix; 22 | #elif defined(OS_WIN) 23 | using Connector = ConnectorWin; 24 | #endif 25 | 26 | inline std::unique_ptr MakeConnector(EventLoop* loop, const SocketAddress& addr) 27 | { 28 | return std::make_unique(loop, addr); 29 | } 30 | 31 | } // namespace ezio 32 | 33 | #endif // EZIO_CONNECTOR_H_ 34 | -------------------------------------------------------------------------------- /ezio/connector_base.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include "ezio/connector_base.h" 6 | 7 | namespace ezio { 8 | 9 | // This looks so damn weird... 10 | constexpr std::chrono::milliseconds ConnectorBase::kInitialRetryDelay; 11 | constexpr std::chrono::seconds ConnectorBase::kMaxRetryDelay; 12 | 13 | ConnectorBase::ConnectorBase(ezio::EventLoop* loop, const ezio::SocketAddress& addr) 14 | : loop_(loop), 15 | remote_addr_(addr), 16 | weakly_bound_(false), 17 | connecting_(false), 18 | retry_delay_(kInitialRetryDelay) 19 | {} 20 | 21 | void ConnectorBase::WeaklyBind(const std::shared_ptr& obj) 22 | { 23 | bound_object_ = obj; 24 | weakly_bound_ = true; 25 | } 26 | 27 | } // namespace ezio 28 | -------------------------------------------------------------------------------- /ezio/connector_base.h: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #ifndef EZIO_CONNECTOR_BASE_H_ 6 | #define EZIO_CONNECTOR_BASE_H_ 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include "ezio/scoped_socket.h" 13 | #include "ezio/socket_address.h" 14 | 15 | namespace ezio { 16 | 17 | class EventLoop; 18 | 19 | class ConnectorBase { 20 | public: 21 | using NewConnectionHandler = std::function; 22 | 23 | virtual ~ConnectorBase() = default; 24 | 25 | virtual void Connect() = 0; 26 | 27 | // It should be safe to call this function multiple times. 28 | virtual void Cancel() = 0; 29 | 30 | void WeaklyBind(const std::shared_ptr& obj); 31 | 32 | void set_on_new_connection(NewConnectionHandler handler) 33 | { 34 | on_new_connection_ = std::move(handler); 35 | } 36 | 37 | const SocketAddress& remote_addr() const noexcept 38 | { 39 | return remote_addr_; 40 | } 41 | 42 | bool connecting() const noexcept 43 | { 44 | return connecting_; 45 | } 46 | 47 | protected: 48 | ConnectorBase(EventLoop* loop, const SocketAddress& addr); 49 | 50 | virtual void HandleNewConnection() = 0; 51 | 52 | virtual void HandleError(bool restart) = 0; 53 | 54 | protected: 55 | static constexpr std::chrono::milliseconds kInitialRetryDelay {500}; 56 | static constexpr std::chrono::seconds kMaxRetryDelay {30}; 57 | 58 | EventLoop* loop_; 59 | 60 | SocketAddress remote_addr_; 61 | ScopedSocket socket_; 62 | 63 | std::weak_ptr bound_object_; 64 | bool weakly_bound_; 65 | 66 | NewConnectionHandler on_new_connection_; 67 | 68 | bool connecting_; 69 | 70 | std::chrono::milliseconds retry_delay_; 71 | }; 72 | 73 | } // namespace ezio 74 | 75 | #endif // EZIO_CONNECTOR_BASE_H_ 76 | -------------------------------------------------------------------------------- /ezio/connector_posix.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include "ezio/connector_posix.h" 6 | 7 | #include 8 | 9 | #include 10 | 11 | #include "kbase/error_exception_util.h" 12 | #include "kbase/logging.h" 13 | #include "kbase/stack_walker.h" 14 | 15 | #include "ezio/event_loop.h" 16 | #include "ezio/socket_utils.h" 17 | 18 | namespace ezio { 19 | 20 | ConnectorPosix::ConnectorPosix(EventLoop* loop, const SocketAddress& addr) 21 | : ConnectorBase(loop, addr), 22 | waiting_completion_(false) 23 | {} 24 | 25 | ConnectorPosix::~ConnectorPosix() 26 | { 27 | ENSURE(CHECK, !connecting_).Require(); 28 | ENSURE(CHECK, !socket_).Require(); 29 | ENSURE(CHECK, !sock_notifier_).Require(); 30 | ENSURE(CHECK, !waiting_completion_).Require(); 31 | } 32 | 33 | void ConnectorPosix::Connect() 34 | { 35 | ENSURE(CHECK, loop_->BelongsToCurrentThread()).Require(); 36 | ENSURE(CHECK, !socket_).Require(); 37 | ENSURE(CHECK, !sock_notifier_).Require(); 38 | 39 | socket_ = socket::CreateNonBlockingSocket(); 40 | 41 | connecting_ = true; 42 | 43 | int rv = connect(socket_.get(), reinterpret_cast(&remote_addr_.raw()), 44 | sizeof(remote_addr_.raw())); 45 | 46 | auto last_err = (rv == 0) ? 0 : errno; 47 | switch (last_err) { 48 | case 0: 49 | case EINPROGRESS: 50 | case EISCONN: 51 | case EINTR: 52 | WaitForConnectingComplete(); 53 | break; 54 | 55 | case EADDRINUSE: 56 | case EAGAIN: 57 | case ECONNREFUSED: 58 | case ENETUNREACH: 59 | case EADDRNOTAVAIL: 60 | LOG(WARNING) << "Currently cannot connect to the remote " << remote_addr_.ToHostPort() 61 | << " due to " << last_err; 62 | HandleError(true); 63 | break; 64 | 65 | default: 66 | LOG(ERROR) << "Failed to connect to the remote " << remote_addr_.ToHostPort() 67 | << " due to " << last_err; 68 | HandleError(false); 69 | break; 70 | } 71 | } 72 | 73 | void ConnectorPosix::Cancel() 74 | { 75 | ENSURE(CHECK, loop_->BelongsToCurrentThread()).Require(); 76 | 77 | if (connecting_) { 78 | if (waiting_completion_) { 79 | ResetNotifier(); 80 | socket_ = nullptr; 81 | waiting_completion_ = false; 82 | } else { 83 | // Cancel retrying. 84 | ENSURE(CHECK, !!retry_timer_).Require(); 85 | loop_->CancelTimedTask(retry_timer_); 86 | retry_timer_ = TimerID(); 87 | } 88 | 89 | connecting_ = false; 90 | } 91 | } 92 | 93 | void ConnectorPosix::WaitForConnectingComplete() 94 | { 95 | ENSURE(CHECK, loop_->BelongsToCurrentThread()).Require(); 96 | ENSURE(CHECK, connecting_).Require(); 97 | ENSURE(CHECK, !sock_notifier_).Require(); 98 | ENSURE(CHECK, !waiting_completion_).Require(); 99 | 100 | sock_notifier_ = std::make_unique(loop_, socket_); 101 | 102 | sock_notifier_->set_on_write(std::bind(&ConnectorPosix::HandleNewConnection, this)); 103 | sock_notifier_->set_on_error(std::bind(&ConnectorPosix::HandleError, this, true)); 104 | 105 | if (weakly_bound_) { 106 | sock_notifier_->WeaklyBind(bound_object_.lock()); 107 | } 108 | 109 | sock_notifier_->EnableWriting(); 110 | 111 | waiting_completion_ = true; 112 | } 113 | 114 | void ConnectorPosix::ResetNotifier() 115 | { 116 | ENSURE(CHECK, waiting_completion_).Require(); 117 | ENSURE(CHECK, !!socket_).Require(); 118 | 119 | sock_notifier_->DisableAll(); 120 | sock_notifier_->Detach(); 121 | 122 | // Release the notifier first, in case the next call of WaitForConnectingComplete() 123 | // finds that the `sock_notifier_` is not empty. 124 | // But the last notifier is retained until to the next pump. 125 | loop_->QueueTask([notifier = std::shared_ptr(std::move(sock_notifier_))] {}); 126 | } 127 | 128 | void ConnectorPosix::HandleNewConnection() 129 | { 130 | ENSURE(CHECK, loop_->BelongsToCurrentThread()).Require(); 131 | 132 | int err = socket::GetSocketErrorCode(socket_); 133 | if (err != 0) { 134 | // Because we dispatch error event (EPOLLERROR) prior to writable event(EPOLLOUT), 135 | // the error here, may have been handled by HandleError() already. 136 | if (waiting_completion_) { 137 | LOG(WARNING) << "Socket SO_ERROR " << err; 138 | HandleError(true); 139 | } 140 | } else { 141 | ENSURE(CHECK, waiting_completion_).Require(); 142 | if (socket::IsSelfConnected(socket_)) { 143 | LOG(WARNING) << "Self connected encounterred! discard and start over again."; 144 | HandleError(true); 145 | } else { 146 | ResetNotifier(); 147 | 148 | waiting_completion_ = false; 149 | connecting_ = false; 150 | retry_delay_ = kInitialRetryDelay; 151 | retry_timer_ = TimerID(); 152 | 153 | sockaddr_in local_addr {}; 154 | socklen_t addr_len = sizeof(local_addr); 155 | int rv = getsockname(socket_.get(), reinterpret_cast(&local_addr), &addr_len); 156 | LOG_IF(ERROR, rv != 0) << "getsockname() failed; Error: " << errno; 157 | 158 | on_new_connection_(std::move(socket_), SocketAddress(local_addr)); 159 | } 160 | } 161 | } 162 | 163 | void ConnectorPosix::HandleError(bool restart) 164 | { 165 | ENSURE(CHECK, loop_->BelongsToCurrentThread()).Require(); 166 | 167 | // We are in the middle of notification handling. 168 | if (waiting_completion_) { 169 | ResetNotifier(); 170 | waiting_completion_ = false; 171 | } 172 | 173 | socket_ = nullptr; 174 | 175 | if (restart) { 176 | DLOG(INFO) << "Cannot currently connect to the remote, schedule to start again."; 177 | retry_timer_ = loop_->RunTaskAfter(std::bind(&ConnectorPosix::Connect, this), retry_delay_); 178 | retry_delay_ = std::min(retry_delay_ * 2, kMaxRetryDelay); 179 | } else { 180 | connecting_ = false; 181 | retry_timer_ = TimerID(); 182 | } 183 | } 184 | 185 | } // namespace ezio 186 | -------------------------------------------------------------------------------- /ezio/connector_posix.h: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #ifndef EZIO_CONNECTOR_POSIX_H_ 6 | #define EZIO_CONNECTOR_POSIX_H_ 7 | 8 | #include 9 | 10 | #include "kbase/basic_macros.h" 11 | 12 | #include "ezio/connector_base.h" 13 | #include "ezio/notifier.h" 14 | #include "ezio/timer_id.h" 15 | 16 | namespace ezio { 17 | 18 | class ConnectorPosix : public ConnectorBase { 19 | public: 20 | ConnectorPosix(EventLoop* loop, const SocketAddress& addr); 21 | 22 | ~ConnectorPosix(); 23 | 24 | DISALLOW_COPY(ConnectorPosix); 25 | 26 | DISALLOW_MOVE(ConnectorPosix); 27 | 28 | void Connect() override; 29 | 30 | void Cancel() override; 31 | 32 | private: 33 | void WaitForConnectingComplete(); 34 | 35 | // This function should be called during notification handling. 36 | void ResetNotifier(); 37 | 38 | void HandleNewConnection() override; 39 | 40 | void HandleError(bool restart) override; 41 | 42 | private: 43 | std::unique_ptr sock_notifier_; 44 | TimerID retry_timer_; 45 | bool waiting_completion_; 46 | }; 47 | 48 | } // namespace ezio 49 | 50 | #endif // EZIO_CONNECTOR_POSIX_H_ 51 | -------------------------------------------------------------------------------- /ezio/connector_win.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include "ezio/connector_win.h" 6 | 7 | #include 8 | 9 | #include "kbase/error_exception_util.h" 10 | #include "kbase/logging.h" 11 | 12 | #include "ezio/event_loop.h" 13 | #include "ezio/io_service_context.h" 14 | #include "ezio/socket_utils.h" 15 | 16 | namespace ezio { 17 | 18 | ConnectorWin::ConnectorWin(EventLoop* loop, const SocketAddress& addr) 19 | : ConnectorBase(loop, addr) 20 | { 21 | memset(&ov, 0, sizeof(ov)); 22 | } 23 | 24 | ConnectorWin::~ConnectorWin() 25 | { 26 | ENSURE(CHECK, !connecting_).Require(); 27 | ENSURE(CHECK, !socket_).Require(); 28 | } 29 | 30 | void ConnectorWin::Connect() 31 | { 32 | ENSURE(CHECK, loop_->BelongsToCurrentThread()).Require(); 33 | ENSURE(CHECK, !socket_).Require(); 34 | 35 | socket_ = socket::CreateNonBlockingSocket(); 36 | 37 | // ConnectEx() requires the socket to be initially bound, for which 38 | // port == 0 && sin_addr == INADDR_ANY. 39 | SocketAddress addr(0); 40 | socket::BindOrThrow(socket_, addr); 41 | 42 | connecting_ = true; 43 | 44 | IOServiceContext::current().AsWinsockContext().ConnectEx( 45 | socket_.get(), 46 | reinterpret_cast(&remote_addr_.raw()), 47 | sizeof(remote_addr_.raw()), 48 | nullptr, 49 | 0, 50 | nullptr, 51 | &ov); 52 | 53 | TryCompleteConnect(); 54 | } 55 | 56 | void ConnectorWin::Cancel() 57 | { 58 | ENSURE(CHECK, loop_->BelongsToCurrentThread()).Require(); 59 | 60 | if (connecting_) { 61 | ENSURE(CHECK, !!retry_timer_).Require(); 62 | loop_->CancelTimedTask(retry_timer_); 63 | retry_timer_ = TimerID(); 64 | 65 | // Still trying to complete. 66 | if (socket_) { 67 | BOOL rv = CancelIo(reinterpret_cast(socket_.get())); 68 | LOG_IF(WARNING, rv == 0) << "CancelIo() failed due to " << GetLastError(); 69 | socket_ = nullptr; 70 | } 71 | 72 | connecting_ = false; 73 | } 74 | } 75 | 76 | void ConnectorWin::TryCompleteConnect() 77 | { 78 | ENSURE(CHECK, loop_->BelongsToCurrentThread()).Require(); 79 | ENSURE(CHECK, connecting_).Require(); 80 | 81 | DWORD bytes = 0; 82 | DWORD flags = 0; 83 | BOOL ok = WSAGetOverlappedResult(socket_.get(), &ov, &bytes, FALSE, &flags); 84 | if (ok) { 85 | HandleNewConnection(); 86 | } else { 87 | auto err = WSAGetLastError(); 88 | switch (err) { 89 | case WSA_IO_INCOMPLETE: 90 | DLOG(INFO) << "Cannot complete connection yet, schedule to retry"; 91 | retry_timer_ = loop_->RunTaskAfter( 92 | std::bind(&ConnectorWin::TryCompleteConnect, this), retry_delay_); 93 | retry_delay_ = std::min(retry_delay_ * 2, 94 | kMaxRetryDelay); 95 | break; 96 | 97 | case WSAEADDRINUSE: 98 | case WSAECONNREFUSED: 99 | case WSAENETUNREACH: 100 | case WSAEHOSTUNREACH: 101 | LOG(WARNING) << "Currently cannot connect to the remote " 102 | << remote_addr_.ToHostPort() << "; Error: " << err; 103 | HandleError(true); 104 | break; 105 | 106 | default: 107 | LOG(ERROR) << "Failed to connect to the remote " << remote_addr_.ToHostPort() 108 | << "; Error: " << err; 109 | HandleError(false); 110 | break; 111 | } 112 | } 113 | } 114 | 115 | void ConnectorWin::HandleNewConnection() 116 | { 117 | ENSURE(CHECK, loop_->BelongsToCurrentThread()).Require(); 118 | 119 | int rv = setsockopt(socket_.get(), SOL_SOCKET, SO_UPDATE_CONNECT_CONTEXT, nullptr, 0); 120 | if (rv != 0) { 121 | LOG(ERROR) << "Failed to SO_UPDATE_CONNECT_CONTEXT: " << WSAGetLastError(); 122 | HandleError(true); 123 | return; 124 | } 125 | 126 | // Cleanup for future reuse. 127 | connecting_ = false; 128 | memset(&ov, 0, sizeof(ov)); 129 | retry_delay_ = kInitialRetryDelay; 130 | retry_timer_ = TimerID(); 131 | 132 | sockaddr_in local_addr; 133 | memset(&local_addr, 0, sizeof(local_addr)); 134 | int addr_len = sizeof(local_addr); 135 | rv = getsockname(socket_.get(), reinterpret_cast(&local_addr), &addr_len); 136 | LOG_IF(ERROR, rv != 0) << "getsockname() failed; Error: " << WSAGetLastError(); 137 | 138 | std::shared_ptr binder; 139 | 140 | if (weakly_bound_) { 141 | binder = bound_object_.lock(); 142 | if (!binder) { 143 | return; 144 | } 145 | } 146 | 147 | on_new_connection_(std::move(socket_), SocketAddress(local_addr)); 148 | 149 | RETAIN_LIFETIME_TO_HERE(binder); 150 | } 151 | 152 | void ConnectorWin::HandleError(bool restart) 153 | { 154 | ENSURE(CHECK, loop_->BelongsToCurrentThread()).Require(); 155 | 156 | socket_ = nullptr; 157 | memset(&ov, 0, sizeof(ov)); 158 | 159 | if (restart) { 160 | retry_timer_ = loop_->RunTaskAfter(std::bind(&ConnectorWin::Connect, this), retry_delay_); 161 | retry_delay_ = std::min(retry_delay_ * 2, kMaxRetryDelay); 162 | } else { 163 | connecting_ = false; 164 | retry_timer_ = TimerID(); 165 | } 166 | } 167 | 168 | } // namespace ezio 169 | -------------------------------------------------------------------------------- /ezio/connector_win.h: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #ifndef EZIO_CONNECTOR_WIN_H_ 6 | #define EZIO_CONNECTOR_WIN_H_ 7 | 8 | #include "kbase/basic_macros.h" 9 | 10 | #include 11 | 12 | #include "ezio/connector_base.h" 13 | #include "ezio/timer_id.h" 14 | 15 | namespace ezio { 16 | 17 | class ConnectorWin : public ConnectorBase { 18 | public: 19 | ConnectorWin(EventLoop* loop, const SocketAddress& addr); 20 | 21 | ~ConnectorWin(); 22 | 23 | DISALLOW_COPY(ConnectorWin); 24 | 25 | DISALLOW_MOVE(ConnectorWin); 26 | 27 | void Connect() override; 28 | 29 | void Cancel() override; 30 | 31 | private: 32 | void TryCompleteConnect(); 33 | 34 | void HandleNewConnection() override; 35 | 36 | void HandleError(bool restart) override; 37 | 38 | private: 39 | OVERLAPPED ov; 40 | TimerID retry_timer_; 41 | }; 42 | 43 | } // namespace ezio 44 | 45 | #endif // EZIO_CONNECTOR_WIN_H_ 46 | -------------------------------------------------------------------------------- /ezio/endian_utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #ifndef EZIO_ENDIAN_UTILS_H_ 6 | #define EZIO_ENDIAN_UTILS_H_ 7 | 8 | #include 9 | #include 10 | 11 | #include "kbase/basic_macros.h" 12 | 13 | #if defined(OS_POSIX) 14 | #include 15 | #endif 16 | 17 | namespace ezio { 18 | 19 | // -*- little endian to big endian -*- 20 | 21 | #if defined(OS_POSIX) 22 | 23 | inline int16_t HostToNetwork(int16_t n) noexcept 24 | { 25 | return htobe16(n); 26 | } 27 | 28 | inline uint16_t HostToNetwork(uint16_t n) noexcept 29 | { 30 | return htobe16(n); 31 | } 32 | 33 | inline int32_t HostToNetwork(int32_t n) noexcept 34 | { 35 | return htobe32(n); 36 | } 37 | 38 | inline uint32_t HostToNetwork(uint32_t n) noexcept 39 | { 40 | return htobe32(n); 41 | } 42 | 43 | inline int64_t HostToNetwork(int64_t n) noexcept 44 | { 45 | return htobe64(n); 46 | } 47 | 48 | inline uint64_t HostToNetwork(uint64_t n) noexcept 49 | { 50 | return htobe64(n); 51 | } 52 | 53 | #elif defined(OS_WIN) 54 | 55 | inline int16_t HostToNetwork(int16_t n) noexcept 56 | { 57 | return _byteswap_ushort(n); 58 | } 59 | 60 | inline uint16_t HostToNetwork(uint16_t n) noexcept 61 | { 62 | return _byteswap_ushort(n); 63 | } 64 | 65 | inline int32_t HostToNetwork(int32_t n) noexcept 66 | { 67 | return _byteswap_ulong(n); 68 | } 69 | 70 | inline uint32_t HostToNetwork(uint32_t n) noexcept 71 | { 72 | return _byteswap_ulong(n); 73 | } 74 | 75 | inline int64_t HostToNetwork(int64_t n) noexcept 76 | { 77 | return _byteswap_uint64(n); 78 | } 79 | 80 | inline uint64_t HostToNetwork(uint64_t n) noexcept 81 | { 82 | return _byteswap_uint64(n); 83 | } 84 | 85 | #endif // OS_POSIX 86 | 87 | // -*- big endian to little endian -*- 88 | 89 | #if defined(OS_POSIX) 90 | 91 | inline int16_t NetworkToHost(int16_t n) noexcept 92 | { 93 | return be16toh(n); 94 | } 95 | 96 | inline uint16_t NetworkToHost(uint16_t n) noexcept 97 | { 98 | return be16toh(n); 99 | } 100 | 101 | inline int32_t NetworkToHost(int32_t n) noexcept 102 | { 103 | return be32toh(n); 104 | } 105 | 106 | inline uint32_t NetworkToHost(uint32_t n) noexcept 107 | { 108 | return be32toh(n); 109 | } 110 | 111 | inline int64_t NetworkToHost(int64_t n) noexcept 112 | { 113 | return be64toh(n); 114 | } 115 | 116 | inline uint64_t NetworkToHost(uint64_t n) noexcept 117 | { 118 | return be64toh(n); 119 | } 120 | 121 | #elif defined(OS_WIN) 122 | 123 | inline int16_t NetworkToHost(int16_t n) noexcept 124 | { 125 | return _byteswap_ushort(n); 126 | } 127 | 128 | inline uint16_t NetworkToHost(uint16_t n) noexcept 129 | { 130 | return _byteswap_ushort(n); 131 | } 132 | 133 | inline int32_t NetworkToHost(int32_t n) noexcept 134 | { 135 | return _byteswap_ulong(n); 136 | } 137 | 138 | inline uint32_t NetworkToHost(uint32_t n) noexcept 139 | { 140 | return _byteswap_ulong(n); 141 | } 142 | 143 | inline int64_t NetworkToHost(int64_t n) noexcept 144 | { 145 | return _byteswap_uint64(n); 146 | } 147 | 148 | inline uint64_t NetworkToHost(uint64_t n) noexcept 149 | { 150 | return _byteswap_uint64(n); 151 | } 152 | 153 | #endif // OS_POSIX 154 | 155 | } // namespace ezio 156 | 157 | #endif // EZIO_ENDIAN_UTILS_H_ 158 | -------------------------------------------------------------------------------- /ezio/event_loop.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include "ezio/event_loop.h" 6 | 7 | #include 8 | 9 | #include "kbase/error_exception_util.h" 10 | #include "kbase/logging.h" 11 | #include "kbase/scope_guard.h" 12 | 13 | #include "ezio/notifier.h" 14 | 15 | namespace { 16 | 17 | constexpr auto kPumpTimeout = std::chrono::seconds(10); 18 | 19 | } // namespace 20 | 21 | namespace ezio { 22 | 23 | thread_local EventLoop* tls_loop_in_thread {nullptr}; 24 | 25 | EventLoop::EventLoop() 26 | : is_running_(false), 27 | owner_thread_id_(this_thread::GetID()), 28 | event_pump_(this), 29 | timer_queue_(this), 30 | executing_pending_task_(false) 31 | { 32 | ENSURE(CHECK, tls_loop_in_thread == nullptr).Require(); 33 | tls_loop_in_thread = this; 34 | } 35 | 36 | EventLoop::~EventLoop() 37 | { 38 | ENSURE(CHECK, tls_loop_in_thread != nullptr).Require(); 39 | tls_loop_in_thread = nullptr; 40 | } 41 | 42 | void EventLoop::Run() 43 | { 44 | ENSURE(CHECK, !is_running_.load()).Require(); 45 | ENSURE(CHECK, BelongsToCurrentThread()).Require(); 46 | 47 | std::vector active_notifications; 48 | 49 | is_running_.store(true, std::memory_order_release); 50 | while (is_running_.load(std::memory_order_acquire)) { 51 | auto pumped_time = event_pump_.Pump(GetPumpTimeout(), active_notifications); 52 | 53 | // We handle expired timers right here on Windows, while they are handled on Linux 54 | // inside timer_fd notifier. 55 | #if defined(OS_WIN) 56 | timer_queue_.ProcessExpiredTimers(pumped_time); 57 | #endif 58 | 59 | for (const auto& item : active_notifications) { 60 | item.first->HandleEvent(pumped_time, item.second); 61 | } 62 | 63 | ProcessPendingTasks(); 64 | 65 | active_notifications.clear(); 66 | } 67 | } 68 | 69 | void EventLoop::Quit() 70 | { 71 | is_running_.store(false, std::memory_order_release); 72 | 73 | if (!BelongsToCurrentThread()) { 74 | Wakeup(); 75 | } 76 | } 77 | 78 | // static 79 | EventLoop* EventLoop::current() noexcept 80 | { 81 | return tls_loop_in_thread; 82 | } 83 | 84 | void EventLoop::RunTask(Task task) 85 | { 86 | if (BelongsToCurrentThread()) { 87 | task(); 88 | } else { 89 | QueueTask(std::move(task)); 90 | } 91 | } 92 | 93 | TimerID EventLoop::RunTaskAfter(Task task, TimeDuration delay) 94 | { 95 | return timer_queue_.AddTimer(std::move(task), 96 | ToTimePoint(std::chrono::system_clock::now()) + delay, 97 | TimeDuration::zero()); 98 | } 99 | 100 | TimerID EventLoop::RunTaskAt(Task task, TimePoint when) 101 | { 102 | return timer_queue_.AddTimer(std::move(task), 103 | when, 104 | TimeDuration::zero()); 105 | } 106 | 107 | TimerID EventLoop::RunTaskEvery(Task task, TimeDuration interval) 108 | { 109 | return timer_queue_.AddTimer(std::move(task), 110 | ToTimePoint(std::chrono::system_clock::now()) + interval, 111 | interval); 112 | } 113 | 114 | void EventLoop::CancelTimedTask(TimerID timer_id) 115 | { 116 | timer_queue_.Cancel(timer_id); 117 | } 118 | 119 | void EventLoop::QueueTask(Task task) 120 | { 121 | { 122 | std::lock_guard lock(task_queue_mutex_); 123 | task_queue_.push_back(std::move(task)); 124 | } 125 | 126 | if (!BelongsToCurrentThread() || executing_pending_task_) { 127 | Wakeup(); 128 | } 129 | } 130 | 131 | void EventLoop::RegisterNotifier(Notifier* notifier) 132 | { 133 | event_pump_.RegisterNotifier(notifier); 134 | } 135 | 136 | void EventLoop::UnregisterNotifier(Notifier* notifier) 137 | { 138 | event_pump_.UnregisterNotifier(notifier); 139 | } 140 | 141 | std::chrono::milliseconds EventLoop::GetPumpTimeout() const 142 | { 143 | #if defined(OS_POSIX) 144 | return kPumpTimeout; 145 | #elif defined(OS_WIN) 146 | auto expiration = timer_queue_.next_expiration(); 147 | if (!expiration.first) { 148 | return kPumpTimeout; 149 | } 150 | 151 | auto now = ToTimePoint(std::chrono::system_clock::now()); 152 | auto timeout = expiration.second - now; 153 | if (timeout < TimeDuration::zero()) { 154 | LOG(WARNING) << "Negative timeout; next expiration: " 155 | << expiration.second.time_since_epoch().count() 156 | << "; now: " << now.time_since_epoch().count(); 157 | timeout = TimeDuration::zero(); 158 | } 159 | 160 | return timeout; 161 | #endif 162 | } 163 | 164 | void EventLoop::ProcessPendingTasks() 165 | { 166 | executing_pending_task_ = true; 167 | ON_SCOPE_EXIT { executing_pending_task_ = false; }; 168 | 169 | decltype(task_queue_) pending_tasks; 170 | 171 | { 172 | std::lock_guard lock(task_queue_mutex_); 173 | pending_tasks.swap(task_queue_); 174 | } 175 | 176 | for (auto& task : pending_tasks) { 177 | task(); 178 | } 179 | } 180 | 181 | } // namespace ezio 182 | -------------------------------------------------------------------------------- /ezio/event_loop.h: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #ifndef EZIO_EVENT_LOOP_H_ 6 | #define EZIO_EVENT_LOOP_H_ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "kbase/basic_macros.h" 14 | 15 | #include "ezio/chrono_utils.h" 16 | #include "ezio/event_pump.h" 17 | #include "ezio/this_thread.h" 18 | #include "ezio/timer_queue.h" 19 | 20 | namespace ezio { 21 | 22 | class Notifier; 23 | 24 | class EventLoop { 25 | public: 26 | using Task = std::function; 27 | 28 | EventLoop(); 29 | 30 | ~EventLoop(); 31 | 32 | DISALLOW_COPY(EventLoop); 33 | 34 | DISALLOW_MOVE(EventLoop); 35 | 36 | void Run(); 37 | 38 | void Quit(); 39 | 40 | // Get the pointer to the EventLoop for current thread. 41 | static EventLoop* current() noexcept; 42 | 43 | // Queue the task in the loop thread. 44 | // The task will be executed shortly after the return from pumping events. 45 | // This function is thread-safe. 46 | void QueueTask(Task task); 47 | 48 | // If the function is called on loop thread, the `task` is then executed immediately 49 | // within the function. 50 | // Otherwise, the `task` is queued to the loop thread. 51 | // This function is thread-safe. 52 | void RunTask(Task task); 53 | 54 | TimerID RunTaskAt(Task task, TimePoint when); 55 | 56 | TimerID RunTaskAfter(Task task, TimeDuration delay); 57 | 58 | TimerID RunTaskEvery(Task task, TimeDuration interval); 59 | 60 | void CancelTimedTask(TimerID timer_id); 61 | 62 | // Returns true if the EventLoop is owned by current thread. 63 | // Returns false, otherwise. 64 | // This function is thread-safe. 65 | bool BelongsToCurrentThread() const noexcept 66 | { 67 | return owner_thread_id_ == this_thread::GetID(); 68 | } 69 | 70 | // It is allowed to register a notifier more than once in order to update its properties 71 | void RegisterNotifier(Notifier* notifier); 72 | 73 | void UnregisterNotifier(Notifier* notifier); 74 | 75 | void Wakeup() 76 | { 77 | event_pump_.Wakeup(); 78 | } 79 | 80 | private: 81 | std::chrono::milliseconds GetPumpTimeout() const; 82 | 83 | void ProcessPendingTasks(); 84 | 85 | private: 86 | std::atomic is_running_; 87 | this_thread::ThreadID owner_thread_id_; 88 | EventPump event_pump_; 89 | 90 | TimerQueue timer_queue_; 91 | 92 | bool executing_pending_task_; 93 | 94 | std::mutex task_queue_mutex_; 95 | std::vector task_queue_; 96 | }; 97 | 98 | } // namespace ezio 99 | 100 | #endif // EZIO_EVENT_LOOP_H_ 101 | -------------------------------------------------------------------------------- /ezio/event_pump.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include "ezio/event_pump.h" 6 | 7 | #include "kbase/basic_macros.h" 8 | #include "kbase/error_exception_util.h" 9 | 10 | #include "ezio/notifier.h" 11 | 12 | #if defined(OS_POSIX) 13 | #include "ezio/event_pump_impl_posix.h" 14 | #elif defined(OS_WIN) 15 | #include "ezio/event_pump_impl_win.h" 16 | #endif 17 | 18 | namespace ezio { 19 | 20 | EventPump::EventPump(EventLoop* loop) 21 | : impl_(std::make_unique(loop)) 22 | { 23 | #if defined(OS_POSIX) 24 | impl_->EnableWakeupNotification(); 25 | #endif 26 | } 27 | 28 | EventPump::~EventPump() 29 | { 30 | #if defined(OS_POSIX) 31 | impl_->DisableWakeupNotification(); 32 | #endif 33 | } 34 | 35 | TimePoint EventPump::Pump(std::chrono::milliseconds timeout, 36 | std::vector& notifications) 37 | { 38 | FORCE_AS_NON_CONST_FUNCTION(); 39 | 40 | ENSURE(CHECK, notifications.empty()).Require(); 41 | return impl_->Pump(timeout, notifications); 42 | } 43 | 44 | void EventPump::Wakeup() 45 | { 46 | FORCE_AS_NON_CONST_FUNCTION(); 47 | 48 | impl_->Wakeup(); 49 | } 50 | 51 | void EventPump::RegisterNotifier(Notifier* notifier) 52 | { 53 | FORCE_AS_NON_CONST_FUNCTION(); 54 | 55 | impl_->RegisterNotifier(notifier); 56 | } 57 | 58 | void EventPump::UnregisterNotifier(Notifier* notifier) 59 | { 60 | FORCE_AS_NON_CONST_FUNCTION(); 61 | 62 | ENSURE(CHECK, notifier->WatchNoneEvent())(notifier->watching_events()).Require(); 63 | impl_->UnregisterNotifier(notifier); 64 | } 65 | 66 | } // namespace ezio 67 | -------------------------------------------------------------------------------- /ezio/event_pump.h: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #ifndef EZIO_EVENT_PUMP_H_ 6 | #define EZIO_EVENT_PUMP_H_ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "kbase/basic_macros.h" 14 | 15 | #include "ezio/chrono_utils.h" 16 | #include "ezio/io_context.h" 17 | 18 | namespace ezio { 19 | 20 | class EventLoop; 21 | class Notifier; 22 | 23 | using IONotification = std::pair; 24 | 25 | class EventPump { 26 | public: 27 | explicit EventPump(EventLoop* loop); 28 | 29 | ~EventPump(); 30 | 31 | DISALLOW_COPY(EventPump); 32 | 33 | DISALLOW_MOVE(EventPump); 34 | 35 | TimePoint Pump(std::chrono::milliseconds timeout, std::vector& notifications); 36 | 37 | void Wakeup(); 38 | 39 | void RegisterNotifier(Notifier* notifier); 40 | 41 | void UnregisterNotifier(Notifier* notifier); 42 | 43 | private: 44 | static constexpr size_t kInitialEventNum = 16; 45 | 46 | class Impl; 47 | std::unique_ptr impl_; 48 | }; 49 | 50 | } // namespace ezio 51 | 52 | #endif // EZIO_EVENT_PUMP_H_ 53 | -------------------------------------------------------------------------------- /ezio/event_pump_impl_posix.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include "ezio/event_pump_impl_posix.h" 6 | 7 | #include 8 | 9 | #include 10 | 11 | #include "kbase/basic_types.h" 12 | #include "kbase/error_exception_util.h" 13 | #include "kbase/logging.h" 14 | 15 | #include "ezio/io_context.h" 16 | #include "ezio/notifier.h" 17 | 18 | namespace { 19 | 20 | int CreateEpollFD() 21 | { 22 | int epfd = epoll_create1(EPOLL_CLOEXEC); 23 | ENSURE(THROW, epfd != -1)(errno).Require(); 24 | return epfd; 25 | } 26 | 27 | int CreateEventFD() 28 | { 29 | int efd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC); 30 | ENSURE(THROW, efd != -1)(errno).Require(); 31 | return efd; 32 | } 33 | 34 | } // namespace 35 | 36 | namespace ezio { 37 | 38 | EventPump::Impl::Impl(EventLoop* loop) 39 | : epfd_(CreateEpollFD()), 40 | io_events_(kInitialEventNum), 41 | wakeup_fd_(CreateEventFD()), 42 | wakeup_notifier_(loop, wakeup_fd_) 43 | {} 44 | 45 | EventPump::Impl::~Impl() 46 | { 47 | ENSURE(CHECK, wakeup_notifier_.state() == Notifier::State::Unused).Require(); 48 | } 49 | 50 | TimePoint EventPump::Impl::Pump(std::chrono::milliseconds timeout, 51 | std::vector& notifications) 52 | { 53 | int count = epoll_wait(epfd_.get(), io_events_.data(), static_cast(io_events_.size()), 54 | static_cast(timeout.count())); 55 | auto err = errno; 56 | 57 | TimePoint returned_time = ToTimePoint(std::chrono::system_clock::now()); 58 | 59 | if (count == -1) { 60 | if (err != EINTR) { 61 | LOG(ERROR) << "epoll_wait() failed: " << err; 62 | } 63 | } else if (count > 0) { 64 | FillActiveNotifications(static_cast(count), notifications); 65 | if (static_cast(count) == io_events_.size()) { 66 | io_events_.resize(io_events_.size() * 2); 67 | } 68 | } 69 | 70 | return returned_time; 71 | } 72 | 73 | void EventPump::Impl::EnableWakeupNotification() 74 | { 75 | wakeup_notifier_.set_on_read(std::bind(&Impl::OnWakeup, this)); 76 | wakeup_notifier_.EnableReading(); 77 | } 78 | 79 | void EventPump::Impl::DisableWakeupNotification() 80 | { 81 | wakeup_notifier_.DisableAll(); 82 | wakeup_notifier_.Detach(); 83 | } 84 | 85 | void EventPump::Impl::Wakeup() 86 | { 87 | if (eventfd_write(wakeup_fd_.get(), 1) < 0) { 88 | LOG(ERROR) << "eventfd_write() failed : " << errno; 89 | ENSURE(CHECK, kbase::NotReached())(errno).Require(); 90 | } 91 | } 92 | 93 | void EventPump::Impl::OnWakeup() 94 | { 95 | eventfd_t value = 1; 96 | if (eventfd_read(wakeup_fd_.get(), &value) < 0) { 97 | LOG(ERROR) << "eventfd_read() failed : " << errno; 98 | ENSURE(CHECK, kbase::NotReached())(errno).Require(); 99 | } 100 | } 101 | 102 | void EventPump::Impl::RegisterNotifier(Notifier* notifier) 103 | { 104 | auto cur_state = notifier->state(); 105 | if (cur_state == Notifier::State::Unused || cur_state == Notifier::State::Inactive) { 106 | notifier->set_state(Notifier::State::Active); 107 | UpdateEpoll(EPOLL_CTL_ADD, notifier); 108 | } else { 109 | if (notifier->WatchNoneEvent()) { 110 | notifier->set_state(Notifier::State::Inactive); 111 | UpdateEpoll(EPOLL_CTL_DEL, notifier); 112 | } else { 113 | UpdateEpoll(EPOLL_CTL_MOD, notifier); 114 | } 115 | } 116 | } 117 | 118 | void EventPump::Impl::UnregisterNotifier(Notifier* notifier) 119 | { 120 | if (notifier->state() == Notifier::State::Active) { 121 | UpdateEpoll(EPOLL_CTL_DEL, notifier); 122 | } 123 | 124 | notifier->set_state(Notifier::State::Unused); 125 | } 126 | 127 | void EventPump::Impl::UpdateEpoll(int operation, const Notifier* notifier) 128 | { 129 | struct epoll_event ev {}; 130 | static_assert(std::is_same::value, 131 | "IOEventType should be identical to uint32_t"); 132 | ev.events = static_cast(notifier->watching_events()); 133 | ev.data.ptr = const_cast(notifier); 134 | 135 | if (epoll_ctl(epfd_.get(), operation, notifier->socket(), &ev) < 0) { 136 | auto err = errno; 137 | LOG(WARNING) << "epoll_ctl() failed for operation " << operation 138 | << " with fd" << notifier->socket() << " due to " << err; 139 | ENSURE(THROW, operation == EPOLL_CTL_DEL)(operation)(err).Require(); 140 | } 141 | } 142 | 143 | void EventPump::Impl::FillActiveNotifications(size_t count, 144 | std::vector& notifications) const 145 | { 146 | for (auto it = io_events_.cbegin(), end = std::next(it, count); it != end; ++it) { 147 | auto notifier = static_cast(it->data.ptr); 148 | IOContext io_ctx(it->events); 149 | notifications.emplace_back(notifier, io_ctx); 150 | } 151 | } 152 | 153 | } // namespace ezio 154 | -------------------------------------------------------------------------------- /ezio/event_pump_impl_posix.h: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #ifndef EZIO_EVENT_PUMP_IMPL_POSIX_H_ 6 | #define EZIO_EVENT_PUMP_IMPL_POSIX_H_ 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #include "kbase/basic_macros.h" 14 | #include "kbase/scoped_handle.h" 15 | 16 | #include "ezio/event_pump.h" 17 | #include "ezio/notifier.h" 18 | 19 | namespace ezio { 20 | 21 | class EventLoop; 22 | class Notifier; 23 | 24 | class EventPump::Impl { 25 | public: 26 | explicit Impl(EventLoop* loop); 27 | 28 | ~Impl(); 29 | 30 | DISALLOW_COPY(Impl); 31 | 32 | DISALLOW_MOVE(Impl); 33 | 34 | TimePoint Pump(std::chrono::milliseconds timeout, std::vector& notifications); 35 | 36 | void EnableWakeupNotification(); 37 | 38 | void DisableWakeupNotification(); 39 | 40 | void Wakeup(); 41 | 42 | void RegisterNotifier(Notifier* notifier); 43 | 44 | void UnregisterNotifier(Notifier* notifier); 45 | 46 | private: 47 | void UpdateEpoll(int operation, const Notifier* notifier); 48 | 49 | void FillActiveNotifications(size_t count, std::vector& notifications) const; 50 | 51 | void OnWakeup(); 52 | 53 | private: 54 | kbase::ScopedFD epfd_; 55 | std::vector io_events_; 56 | kbase::ScopedFD wakeup_fd_; 57 | Notifier wakeup_notifier_; 58 | }; 59 | 60 | } // namespace ezio 61 | 62 | #endif // EZIO_EVENT_PUMP_IMPL_POSIX_H_ 63 | -------------------------------------------------------------------------------- /ezio/event_pump_impl_win.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include "ezio/event_pump_impl_win.h" 6 | 7 | #include 8 | 9 | #include "kbase/basic_macros.h" 10 | #include "kbase/error_exception_util.h" 11 | #include "kbase/logging.h" 12 | 13 | #include "ezio/notifier.h" 14 | 15 | namespace { 16 | 17 | ULONG_PTR kWakeupCompletionKey = 0x1; 18 | 19 | kbase::ScopedWinHandle CreateIOCP() 20 | { 21 | kbase::ScopedWinHandle iocp(CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, 0, 1)); 22 | ENSURE(THROW, !!iocp)(kbase::LastError()).Require(); 23 | return iocp; 24 | } 25 | 26 | } // namespace 27 | 28 | namespace ezio { 29 | 30 | EventPump::Impl::Impl(EventLoop*) 31 | : io_port_(CreateIOCP()), 32 | io_events_(kInitialEventNum) 33 | {} 34 | 35 | EventPump::Impl::~Impl() 36 | {} 37 | 38 | TimePoint EventPump::Impl::Pump(std::chrono::milliseconds timeout, 39 | std::vector& notifications) 40 | { 41 | unsigned long dequeued_num = 0; 42 | auto succeed = GetQueuedCompletionStatusEx(io_port_.get(), 43 | io_events_.data(), 44 | static_cast(io_events_.size()), 45 | &dequeued_num, 46 | static_cast(timeout.count()), 47 | FALSE); 48 | 49 | TimePoint returned_time = ToTimePoint(std::chrono::system_clock::now()); 50 | 51 | if (!succeed) { 52 | auto err = WSAGetLastError(); 53 | LOG_IF(ERROR, err != WAIT_TIMEOUT) << "Dequeue for completion failure: " << err; 54 | } else if (dequeued_num > 0) { 55 | FillActiveNotifications(dequeued_num, notifications); 56 | if (dequeued_num == io_events_.size()) { 57 | io_events_.resize(io_events_.size() * 2); 58 | } 59 | } 60 | 61 | return returned_time; 62 | } 63 | 64 | void EventPump::Impl::Wakeup() 65 | { 66 | FORCE_AS_NON_CONST_FUNCTION(); 67 | 68 | if (!PostQueuedCompletionStatus(io_port_.get(), 0, kWakeupCompletionKey, nullptr)) { 69 | kbase::LastError error; 70 | LOG(ERROR) << "PostQueuedCompletionStatus() failed: " << error; 71 | ENSURE(CHECK, kbase::NotReached())(error).Require(); 72 | } 73 | } 74 | 75 | void EventPump::Impl::RegisterNotifier(Notifier* notifier) 76 | { 77 | if (notifier->state() == Notifier::State::Unused) { 78 | AssociateWithNotifier(notifier); 79 | notifier->set_state(Notifier::State::Active); 80 | 81 | ENSURE(CHECK, managed_notifiers_.count(notifier) == 0).Require(); 82 | managed_notifiers_.insert(notifier); 83 | } 84 | } 85 | 86 | void EventPump::Impl::UnregisterNotifier(Notifier* notifier) 87 | { 88 | notifier->set_state(Notifier::State::Inactive); 89 | 90 | ENSURE(CHECK, managed_notifiers_.count(notifier) != 0).Require(); 91 | managed_notifiers_.erase(notifier); 92 | } 93 | 94 | void EventPump::Impl::AssociateWithNotifier(const Notifier* notifier) const 95 | { 96 | auto iocp = io_port_.get(); 97 | bool succeeded = CreateIoCompletionPort(reinterpret_cast(notifier->socket()), 98 | iocp, 99 | reinterpret_cast(notifier), 100 | 0) == iocp; 101 | ENSURE(THROW, succeeded)(kbase::LastError()).Require(); 102 | } 103 | 104 | void EventPump::Impl::FillActiveNotifications(size_t count, 105 | std::vector& notifications) const 106 | { 107 | for (auto it = io_events_.cbegin(), end = std::next(it, count); it != end; ++it) { 108 | if (it->lpCompletionKey == kWakeupCompletionKey) { 109 | continue; 110 | } 111 | 112 | auto notifier = reinterpret_cast(it->lpCompletionKey); 113 | 114 | if (managed_notifiers_.count(notifier) == 0) { 115 | LOG(WARNING) << "Skip dead notifier of a dead socket"; 116 | continue; 117 | } 118 | 119 | auto io_req = static_cast(it->lpOverlapped); 120 | IOContext io_ctx(io_req, it->dwNumberOfBytesTransferred); 121 | 122 | notifications.emplace_back(notifier, io_ctx); 123 | } 124 | } 125 | 126 | } // namespace ezio 127 | -------------------------------------------------------------------------------- /ezio/event_pump_impl_win.h: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #ifndef EZIO_EVENT_PUMP_IMPL_WIN_H_ 6 | #define EZIO_EVENT_PUMP_IMPL_WIN_H_ 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #include "kbase/basic_macros.h" 14 | #include "kbase/scoped_handle.h" 15 | 16 | #include "ezio/event_pump.h" 17 | 18 | namespace ezio { 19 | 20 | class EventLoop; 21 | 22 | class EventPump::Impl { 23 | public: 24 | explicit Impl(EventLoop*); 25 | 26 | ~Impl(); 27 | 28 | DISALLOW_COPY(Impl); 29 | 30 | DISALLOW_MOVE(Impl); 31 | 32 | TimePoint Pump(std::chrono::milliseconds timeout, std::vector& notifications); 33 | 34 | void Wakeup(); 35 | 36 | void RegisterNotifier(Notifier* notifier); 37 | 38 | void UnregisterNotifier(Notifier* notifier); 39 | 40 | private: 41 | void AssociateWithNotifier(const Notifier* notifier) const; 42 | 43 | void FillActiveNotifications(size_t count, std::vector& notifications) const; 44 | 45 | private: 46 | kbase::ScopedWinHandle io_port_; 47 | std::vector io_events_; 48 | 49 | // Although a socket is automatically removed from the completion port when it is 50 | // closed, outstanding requests issued by the socket still get queued to the port and 51 | // yet, any use of the dead notifier, which associated with the socket, would result 52 | // in illegal access. 53 | // Therefore, we need bookkeeping of notifiers in interest, skipping completion events 54 | // of dead sockets. 55 | std::set managed_notifiers_; 56 | }; 57 | 58 | } // namespace ezio 59 | 60 | #endif // EZIO_EVENT_PUMP_IMPL_WIN_H_ 61 | -------------------------------------------------------------------------------- /ezio/ignore_sigpipe.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include "ezio/ignore_sigpipe.h" 6 | 7 | #include 8 | 9 | namespace ezio { 10 | 11 | IgnoreSigPipe::IgnoreSigPipe() noexcept 12 | { 13 | signal(SIGPIPE, SIG_IGN); 14 | } 15 | 16 | } // namespace ezio 17 | -------------------------------------------------------------------------------- /ezio/ignore_sigpipe.h: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #ifndef EZIO_IGNORE_SIGPIPE_H_ 6 | #define EZIO_IGNORE_SIGPIPE_H_ 7 | 8 | #include "kbase/basic_macros.h" 9 | 10 | namespace ezio { 11 | 12 | class IgnoreSigPipe { 13 | public: 14 | IgnoreSigPipe() noexcept; 15 | 16 | ~IgnoreSigPipe() = default; 17 | 18 | DISALLOW_COPY(IgnoreSigPipe); 19 | 20 | DISALLOW_MOVE(IgnoreSigPipe); 21 | }; 22 | 23 | } // namespace ezio 24 | 25 | #endif // EZIO_IGNORE_SIGPIPE_H_ 26 | -------------------------------------------------------------------------------- /ezio/io_context.h: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #ifndef EZIO_IO_CONTEXT_H_ 6 | #define EZIO_IO_CONTEXT_H_ 7 | 8 | #include 9 | #include 10 | 11 | #include "kbase/basic_macros.h" 12 | 13 | #if defined(OS_POSIX) 14 | #include 15 | #elif defined(OS_WIN) 16 | #include 17 | #endif 18 | 19 | namespace ezio { 20 | 21 | enum IOEvent : uint32_t { 22 | None = 0, 23 | #if defined(OS_POSIX) 24 | Read = EPOLLIN | EPOLLPRI, 25 | Write = EPOLLOUT 26 | #elif defined(OS_WIN) 27 | Probe = 1 << 0, 28 | Read = 1 << 1, 29 | Write = 1 << 2 30 | #endif 31 | }; 32 | 33 | using IOEventType = std::underlying_type_t; 34 | 35 | #if defined(OS_POSIX) 36 | 37 | struct IOContext { 38 | IOEventType events; 39 | 40 | // Details on Linux is currently no use. 41 | struct Details { 42 | constexpr Details() noexcept = default; 43 | ~Details() = default; 44 | }; 45 | 46 | constexpr explicit IOContext(IOEventType epoll_events) noexcept 47 | : events(epoll_events) 48 | {} 49 | 50 | constexpr Details ToDetails() const noexcept 51 | { 52 | return Details{}; 53 | } 54 | }; 55 | 56 | #elif defined(OS_WIN) 57 | 58 | struct IORequest : OVERLAPPED { 59 | IOEventType events; 60 | 61 | explicit IORequest(IOEventType events) 62 | : events(events) 63 | { 64 | Reset(); 65 | } 66 | 67 | void Reset() 68 | { 69 | Offset = 0; 70 | OffsetHigh = 0; 71 | Internal = 0; 72 | InternalHigh = 0; 73 | hEvent = nullptr; 74 | } 75 | 76 | bool IsProbing() const noexcept 77 | { 78 | return (events & IOEvent::Probe) != 0; 79 | } 80 | 81 | void DisableProbing() noexcept 82 | { 83 | events &= ~IOEvent::Probe; 84 | } 85 | }; 86 | 87 | struct IOContext { 88 | IORequest* io_req; 89 | DWORD bytes_transferred; 90 | 91 | struct Details { 92 | IOEventType events; 93 | DWORD bytes_transferred; 94 | 95 | Details(IOEventType events, DWORD bytes) 96 | : events(events), bytes_transferred(bytes) 97 | {} 98 | }; 99 | 100 | IOContext(IORequest* req, DWORD bytes) 101 | : io_req(req), bytes_transferred(bytes) 102 | {} 103 | 104 | Details ToDetails() const noexcept 105 | { 106 | return {io_req->events, bytes_transferred}; 107 | } 108 | }; 109 | 110 | #endif 111 | 112 | } // namespace ezio 113 | 114 | #endif // EZIO_IO_CONTEXT_H_ 115 | -------------------------------------------------------------------------------- /ezio/io_service_context.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include "ezio/io_service_context.h" 6 | 7 | #include "kbase/at_exit_manager.h" 8 | #include "kbase/error_exception_util.h" 9 | 10 | #include "ezio/this_thread.h" 11 | 12 | namespace { 13 | 14 | ezio::IOServiceContext* instance = nullptr; 15 | 16 | } // namespace 17 | 18 | namespace ezio { 19 | 20 | IOServiceContext::IOServiceContext() 21 | { 22 | this_thread::SetName(kMainThreadName, true); 23 | } 24 | 25 | // static 26 | void IOServiceContext::Init() 27 | { 28 | ENSURE(CHECK, instance == nullptr).Require(); 29 | instance = new IOServiceContext(); 30 | kbase::AtExitManager::RegisterCallback([] { 31 | delete instance; 32 | instance = nullptr; 33 | }); 34 | } 35 | 36 | // static 37 | const IOServiceContext& IOServiceContext::current() 38 | { 39 | ENSURE(CHECK, instance != nullptr).Require(); 40 | return *instance; 41 | } 42 | 43 | } // namespace ezio 44 | -------------------------------------------------------------------------------- /ezio/io_service_context.h: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #ifndef EZIO_IO_SERVICE_CONTEXT_H_ 6 | #define EZIO_IO_SERVICE_CONTEXT_H_ 7 | 8 | #include "kbase/basic_macros.h" 9 | 10 | #if defined(OS_POSIX) 11 | #include "ezio/ignore_sigpipe.h" 12 | #elif defined(OS_WIN) 13 | #include "ezio/winsock_context.h" 14 | #endif 15 | 16 | namespace ezio { 17 | 18 | // Users of ezio must first call IOServiceContext::Init() to initialize the conceptual 19 | // io-service-context, which wraps global-wide components needed by each platform. 20 | // This class relies on kbase::AtExitManager for cleanup. 21 | class IOServiceContext { 22 | public: 23 | ~IOServiceContext() = default; 24 | 25 | DISALLOW_COPY(IOServiceContext); 26 | 27 | DISALLOW_MOVE(IOServiceContext); 28 | 29 | static void Init(); 30 | 31 | static const IOServiceContext& current(); 32 | 33 | #if defined(OS_WIN) 34 | const WinsockContext& AsWinsockContext() const noexcept 35 | { 36 | return winsock_context_; 37 | } 38 | #endif 39 | 40 | private: 41 | IOServiceContext(); 42 | 43 | private: 44 | #if defined(OS_WIN) 45 | WinsockContext winsock_context_; 46 | #elif defined(OS_POSIX) 47 | IgnoreSigPipe ignore_sigpipe_; 48 | #endif 49 | }; 50 | 51 | } // namespace ezio 52 | 53 | #endif // EZIO_IO_SERVICE_CONTEXT_H_ 54 | -------------------------------------------------------------------------------- /ezio/notifier.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include "ezio/notifier.h" 6 | 7 | #include "kbase/error_exception_util.h" 8 | 9 | #include "ezio/event_loop.h" 10 | 11 | namespace ezio { 12 | 13 | Notifier::Notifier(ezio::EventLoop* loop, const ezio::ScopedSocket& socket) noexcept 14 | : loop_(loop), 15 | socket_(socket.get()), 16 | weakly_bound_(false), 17 | state_(State::Unused), 18 | watching_events_(IOEvent::None) 19 | {} 20 | 21 | void Notifier::EnableReading() 22 | { 23 | watching_events_ |= IOEvent::Read; 24 | Update(); 25 | } 26 | 27 | void Notifier::DisableReading() 28 | { 29 | watching_events_ &= ~IOEvent::Read; 30 | Update(); 31 | } 32 | 33 | void Notifier::EnableWriting() 34 | { 35 | watching_events_ |= IOEvent::Write; 36 | Update(); 37 | } 38 | 39 | void Notifier::DisableWriting() 40 | { 41 | watching_events_ &= ~IOEvent::Write; 42 | Update(); 43 | } 44 | 45 | void Notifier::DisableAll() 46 | { 47 | watching_events_ = IOEvent::None; 48 | Update(); 49 | } 50 | 51 | void Notifier::Detach() 52 | { 53 | ENSURE(CHECK, WatchNoneEvent())(watching_events_).Require(); 54 | loop_->UnregisterNotifier(this); 55 | } 56 | 57 | void Notifier::WeaklyBind(const std::shared_ptr& obj) 58 | { 59 | bound_object_ = obj; 60 | weakly_bound_ = true; 61 | } 62 | 63 | void Notifier::HandleEvent(ezio::TimePoint receive_time, ezio::IOContext io_ctx) const 64 | { 65 | std::shared_ptr object; 66 | 67 | if (weakly_bound_) { 68 | object = bound_object_.lock(); 69 | if (!object) { 70 | return; 71 | } 72 | } 73 | 74 | DoHandleEvent(receive_time, io_ctx); 75 | 76 | RETAIN_LIFETIME_TO_HERE(object); 77 | } 78 | 79 | void Notifier::Update() 80 | { 81 | loop_->RegisterNotifier(this); 82 | } 83 | 84 | } // namespace ezio 85 | -------------------------------------------------------------------------------- /ezio/notifier.h: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #ifndef EZIO_NOTIFIER_H_ 6 | #define EZIO_NOTIFIER_H_ 7 | 8 | #include 9 | #include 10 | 11 | #include "kbase/basic_macros.h" 12 | 13 | #include "ezio/chrono_utils.h" 14 | #include "ezio/io_context.h" 15 | #include "ezio/scoped_socket.h" 16 | 17 | namespace ezio { 18 | 19 | class EventLoop; 20 | 21 | class Notifier { 22 | public: 23 | using ReadEventHandler = std::function; 24 | using WriteEventHandler = std::function; 25 | using CloseEventHandler = std::function; 26 | using ErrorHandler = std::function; 27 | 28 | enum class State { 29 | Unused, 30 | Active, 31 | Inactive 32 | }; 33 | 34 | Notifier(EventLoop* loop, const ScopedSocket& socket) noexcept; 35 | 36 | ~Notifier() = default; 37 | 38 | DISALLOW_COPY(Notifier); 39 | 40 | DISALLOW_MOVE(Notifier); 41 | 42 | void set_on_read(ReadEventHandler handler) 43 | { 44 | on_read_ = std::move(handler); 45 | } 46 | 47 | void set_on_write(WriteEventHandler handler) 48 | { 49 | on_write_ = std::move(handler); 50 | } 51 | 52 | void set_on_close(CloseEventHandler handler) 53 | { 54 | on_close_ = std::move(handler); 55 | } 56 | 57 | void set_on_error(ErrorHandler handler) 58 | { 59 | on_error_ = std::move(handler); 60 | } 61 | 62 | IOEventType watching_events() const noexcept 63 | { 64 | return watching_events_; 65 | } 66 | 67 | bool WatchNoneEvent() const noexcept 68 | { 69 | return watching_events_ == IOEvent::None; 70 | } 71 | 72 | bool WatchReading() const noexcept 73 | { 74 | return (watching_events_ & IOEvent::Read) != IOEvent::None; 75 | } 76 | 77 | void EnableReading(); 78 | 79 | void DisableReading(); 80 | 81 | bool WatchWriting() const noexcept 82 | { 83 | return (watching_events_ & IOEvent::Write) != IOEvent::None; 84 | } 85 | 86 | void EnableWriting(); 87 | 88 | void DisableWriting(); 89 | 90 | void DisableAll(); 91 | 92 | void Detach(); 93 | 94 | // Prevent the associated object being destroyed during HandleEvent() or executing 95 | // HandleEvent() after the object being dead. 96 | void WeaklyBind(const std::shared_ptr& obj); 97 | 98 | void HandleEvent(TimePoint receive_time, IOContext io_ctx) const; 99 | 100 | State state() const noexcept 101 | { 102 | return state_; 103 | } 104 | 105 | void set_state(State new_state) noexcept 106 | { 107 | state_ = new_state; 108 | } 109 | 110 | ScopedSocket::Handle socket() const noexcept 111 | { 112 | return socket_; 113 | } 114 | 115 | private: 116 | void Update(); 117 | 118 | void DoHandleEvent(TimePoint receive_time, IOContext io_ctx) const; 119 | 120 | private: 121 | EventLoop* loop_; 122 | ScopedSocket::Handle socket_; 123 | 124 | std::weak_ptr bound_object_; 125 | bool weakly_bound_; 126 | 127 | ReadEventHandler on_read_; 128 | WriteEventHandler on_write_; 129 | CloseEventHandler on_close_; 130 | ErrorHandler on_error_; 131 | 132 | State state_; 133 | 134 | IOEventType watching_events_; 135 | }; 136 | 137 | } // namespace ezio 138 | 139 | #endif // EZIO_NOTIFIER_H_ 140 | -------------------------------------------------------------------------------- /ezio/notifier_posix.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include "ezio/notifier.h" 6 | 7 | namespace ezio { 8 | 9 | void Notifier::DoHandleEvent(TimePoint receive_time, IOContext io_ctx) const 10 | { 11 | auto events = io_ctx.events; 12 | constexpr auto details = io_ctx.ToDetails(); 13 | 14 | if ((events & EPOLLHUP) && !(events & EPOLLIN)) { 15 | if (on_close_) { 16 | on_close_(); 17 | } 18 | } 19 | 20 | if (events & EPOLLERR) { 21 | if (on_error_) { 22 | on_error_(); 23 | } 24 | } 25 | 26 | if (events & (EPOLLIN | EPOLLPRI | EPOLLRDHUP)) { 27 | if (on_read_) { 28 | on_read_(receive_time, details); 29 | } 30 | } 31 | 32 | if (events & EPOLLOUT) { 33 | if (on_write_) { 34 | on_write_(details); 35 | } 36 | } 37 | } 38 | 39 | } // namespace ezio 40 | -------------------------------------------------------------------------------- /ezio/notifier_win.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include "ezio/notifier.h" 6 | 7 | #include 8 | 9 | #include 10 | 11 | #include "kbase/error_exception_util.h" 12 | #include "kbase/logging.h" 13 | 14 | namespace { 15 | 16 | using Result = std::pair; 17 | 18 | Result CheckIfOperationSucceed(SOCKET socket, OVERLAPPED* overlapped) 19 | { 20 | DWORD bytes = 0; 21 | DWORD flags = 0; 22 | 23 | int error_code = ERROR_SUCCESS; 24 | 25 | BOOL rv = WSAGetOverlappedResult(socket, overlapped, &bytes, FALSE, &flags); 26 | ENSURE(CHECK, bytes == 0 || rv != TRUE)(bytes)(flags).Require(); 27 | if (!rv) { 28 | error_code = WSAGetLastError(); 29 | } 30 | 31 | return {rv == TRUE, error_code}; 32 | } 33 | 34 | } // namespace 35 | 36 | namespace ezio { 37 | 38 | void Notifier::DoHandleEvent(TimePoint receive_time, IOContext io_ctx) const 39 | { 40 | auto events = io_ctx.io_req->events; 41 | auto details = io_ctx.ToDetails(); 42 | 43 | if (details.bytes_transferred == 0) { 44 | auto result = CheckIfOperationSucceed(socket(), io_ctx.io_req); 45 | if (!result.first) { 46 | LOG(WARNING) << "Async operation " << events << " on socket " << socket() << " failed;" 47 | << " error: " << result.second; 48 | on_error_(); 49 | } 50 | } 51 | 52 | // An async-io operation can't be both read and write. 53 | // However, `on_read_` and `on_write_` must be error-aware, that is, they must be able 54 | // to handle async read/write errors. 55 | if (events & IOEvent::Read) { 56 | ENSURE(CHECK, WatchReading())(watching_events()).Require(); 57 | on_read_(receive_time, details); 58 | } else if (events & IOEvent::Write) { 59 | ENSURE(CHECK, WatchWriting())(watching_events()).Require(); 60 | on_write_(details); 61 | } else { 62 | ENSURE(CHECK, kbase::NotReached())(events).Require(); 63 | } 64 | } 65 | 66 | } // namespace ezio 67 | -------------------------------------------------------------------------------- /ezio/scoped_socket.h: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #ifndef EZIO_SCOPED_SOCKET_H_ 6 | #define EZIO_SCOPED_SOCKET_H_ 7 | 8 | #include "kbase/basic_macros.h" 9 | #include "kbase/scoped_handle.h" 10 | 11 | #if defined(OS_WIN) 12 | #include 13 | #endif 14 | 15 | namespace ezio { 16 | 17 | #if defined(OS_POSIX) 18 | 19 | using SocketTraits = kbase::FDTraits; 20 | 21 | #elif defined(OS_WIN) 22 | 23 | struct SocketTraits { 24 | using Handle = SOCKET; 25 | 26 | SocketTraits() = delete; 27 | ~SocketTraits() = delete; 28 | 29 | static Handle NullHandle() noexcept 30 | { 31 | return INVALID_SOCKET; 32 | } 33 | 34 | static bool IsValid(Handle handle) noexcept 35 | { 36 | return handle != INVALID_SOCKET; 37 | } 38 | 39 | static void Close(Handle handle) noexcept 40 | { 41 | closesocket(handle); 42 | } 43 | }; 44 | 45 | #endif 46 | 47 | using ScopedSocket = kbase::GenericScopedHandle; 48 | 49 | } // namespace ezio 50 | 51 | #endif // EZIO_SCOPED_SOCKET_H_ 52 | -------------------------------------------------------------------------------- /ezio/socket_address.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include "ezio/socket_address.h" 6 | 7 | #include 8 | #include 9 | 10 | #include "kbase/error_exception_util.h" 11 | #include "kbase/string_format.h" 12 | 13 | #include "ezio/socket_utils.h" 14 | 15 | #if defined(OS_WIN) 16 | #include 17 | #endif 18 | 19 | namespace { 20 | 21 | constexpr const char kAnyAddr[] = "0.0.0.0"; 22 | 23 | void StringifyHostPort(const char* ip, unsigned short port, std::string& out) 24 | { 25 | kbase::StringPrintf(out, "%s:%d", ip, port); 26 | } 27 | 28 | } // namespace 29 | 30 | namespace ezio { 31 | 32 | SocketAddress::SocketAddress(const sockaddr_in& addr) 33 | : addr_(addr) 34 | { 35 | std::array ip {}; 36 | auto ip_ptr = inet_ntop(AF_INET, &addr_.sin_addr, ip.data(), ip.size()); 37 | ENSURE(CHECK, ip_ptr != nullptr)(socket::GetLastErrorCode()).Require(); 38 | 39 | StringifyHostPort(ip.data(), NetworkToHost(addr_.sin_port), host_port_); 40 | } 41 | 42 | SocketAddress::SocketAddress(unsigned short port) 43 | { 44 | memset(&addr_, 0, sizeof(addr_)); 45 | 46 | addr_.sin_family = AF_INET; 47 | addr_.sin_port = HostToNetwork(port); 48 | addr_.sin_addr.s_addr = INADDR_ANY; 49 | 50 | StringifyHostPort(kAnyAddr, port, host_port_); 51 | } 52 | 53 | SocketAddress::SocketAddress(const std::string& ip, unsigned short port) 54 | { 55 | memset(&addr_, 0, sizeof(addr_)); 56 | 57 | addr_.sin_family = AF_INET; 58 | addr_.sin_port = HostToNetwork(port); 59 | int rv = inet_pton(AF_INET, ip.c_str(), &addr_.sin_addr); 60 | ENSURE(THROW, rv > 0)(socket::GetLastErrorCode())(ip)(port).Require(); 61 | 62 | StringifyHostPort(ip.c_str(), port, host_port_); 63 | } 64 | 65 | } // namespace ezio 66 | -------------------------------------------------------------------------------- /ezio/socket_address.h: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #ifndef EZIO_SOCKET_ADDRESS_H_ 6 | #define EZIO_SOCKET_ADDRESS_H_ 7 | 8 | #include 9 | 10 | #include "kbase/basic_macros.h" 11 | 12 | #include "ezio/endian_utils.h" 13 | 14 | #if defined(OS_POSIX) 15 | #include 16 | #elif defined(OS_WIN) 17 | #include 18 | #endif 19 | 20 | namespace ezio { 21 | 22 | class SocketAddress { 23 | public: 24 | explicit SocketAddress(const sockaddr_in& addr); 25 | 26 | explicit SocketAddress(unsigned short port); 27 | 28 | SocketAddress(const std::string& ip, unsigned short port); 29 | 30 | DEFAULT_COPY(SocketAddress); 31 | 32 | DEFAULT_MOVE(SocketAddress); 33 | 34 | unsigned short port() const noexcept 35 | { 36 | return NetworkToHost(addr_.sin_port); 37 | } 38 | 39 | const sockaddr_in& raw() const noexcept 40 | { 41 | return addr_; 42 | } 43 | 44 | const std::string& ToHostPort() const noexcept 45 | { 46 | return host_port_; 47 | } 48 | 49 | private: 50 | sockaddr_in addr_; 51 | std::string host_port_; 52 | }; 53 | 54 | } // namespace ezio 55 | 56 | #endif // EZIO_SOCKET_ADDRESS_H_ 57 | -------------------------------------------------------------------------------- /ezio/socket_utils.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include "ezio/socket_utils.h" 6 | 7 | #include "kbase/error_exception_util.h" 8 | #include "kbase/logging.h" 9 | 10 | #if defined(OS_POSIX) 11 | #include 12 | #endif 13 | 14 | namespace ezio { 15 | namespace socket { 16 | 17 | int GetSocketErrorCode(const ScopedSocket& sock) 18 | { 19 | optval_t optval; 20 | optlen_t optlen = sizeof(optval); 21 | 22 | if (getsockopt(sock.get(), SOL_SOCKET, SO_ERROR, &optval, &optlen) < 0) { 23 | return GetLastErrorCode(); 24 | } 25 | 26 | return optval; 27 | } 28 | 29 | void SetTCPNoDelay(const ScopedSocket& sock, bool enable) 30 | { 31 | optval_t optval = enable ? 1 : 0; 32 | if (setsockopt(sock.get(), IPPROTO_TCP, TCP_NODELAY, &optval, sizeof(optval)) < 0) { 33 | auto err = GetLastErrorCode(); 34 | LOG(ERROR) << "Set socket TCP_NODELAY " << enable << " failed: " << err; 35 | ENSURE(CHECK, kbase::NotReached())(err)(enable).Require(); 36 | } 37 | } 38 | 39 | void BindOrThrow(const ScopedSocket& sock, const SocketAddress& listening_addr) 40 | { 41 | const auto& raw_addr = listening_addr.raw(); 42 | int rv = bind(sock.get(), reinterpret_cast(&raw_addr), sizeof(raw_addr)); 43 | ENSURE(THROW, rv == 0)(GetLastErrorCode())(listening_addr.ToHostPort()).Require(); 44 | } 45 | 46 | void ListenOrThrow(const ScopedSocket& sock) 47 | { 48 | int rv = listen(sock.get(), SOMAXCONN); 49 | ENSURE(THROW, rv == 0)(GetLastErrorCode()).Require(); 50 | } 51 | 52 | } // namespace socket 53 | } // namespace ezio 54 | -------------------------------------------------------------------------------- /ezio/socket_utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #ifndef EZIO_SOCKET_UTILS_H_ 6 | #define EZIO_SOCKET_UTILS_H_ 7 | 8 | #include "kbase/basic_macros.h" 9 | 10 | #include "ezio/scoped_socket.h" 11 | #include "ezio/socket_address.h" 12 | 13 | #if defined(OS_POSIX) 14 | #include 15 | #include 16 | #elif defined(OS_WIN) 17 | #include 18 | #endif 19 | 20 | namespace ezio { 21 | namespace socket { 22 | 23 | #if defined(OS_POSIX) 24 | using optval_t = int; 25 | using optlen_t = socklen_t; 26 | #elif defined(OS_WIN) 27 | using optval_t = char; 28 | using optlen_t = int; 29 | #endif 30 | 31 | inline int GetLastErrorCode() noexcept 32 | { 33 | #if defined(OS_POSIX) 34 | return errno; 35 | #elif defined(OS_WIN) 36 | return WSAGetLastError(); 37 | #endif 38 | } 39 | 40 | int GetSocketErrorCode(const ScopedSocket& sock); 41 | 42 | // Create a non-blocking or an overlapped TCP socket. 43 | ScopedSocket CreateNonBlockingSocket(); 44 | 45 | // Set SO_REUSEADDR option for a given sockt on Linux; while setting SO_EXCLUSIVEADDRUSE 46 | // on Windows. 47 | // Winsock has misinterpreted how SO_REUSEADDR should work, and enabling which will 48 | // introduce security vulnerability, and that is why it then added SO_EXCLUSIVEADDRUSE 49 | // for server applications to remedy the issue. 50 | void SetReuseAddr(const ScopedSocket& sock, bool enable); 51 | 52 | // Enable or disable Nagle's algorithm. 53 | void SetTCPNoDelay(const ScopedSocket& sock, bool enable); 54 | 55 | // It seems Windows now can't disable delayed-ack on socket level. 56 | #if defined(OS_POSIX) 57 | 58 | void EnableTCPQuickACK(const ScopedSocket& sock); 59 | 60 | bool IsSelfConnected(const ScopedSocket& sock); 61 | 62 | #endif 63 | 64 | void BindOrThrow(const ScopedSocket& sock, const SocketAddress& listening_addr); 65 | 66 | void ListenOrThrow(const ScopedSocket& sock); 67 | 68 | void ShutdownWrite(const ScopedSocket& sock); 69 | 70 | } // namespace socket 71 | } // namespace ezio 72 | 73 | #endif // EZIO_SOCKET_UTILS_H_ 74 | -------------------------------------------------------------------------------- /ezio/socket_utils_posix.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include "ezio/socket_utils.h" 6 | 7 | #include 8 | 9 | #include 10 | 11 | #include "kbase/error_exception_util.h" 12 | #include "kbase/logging.h" 13 | 14 | namespace ezio { 15 | namespace socket { 16 | 17 | ScopedSocket CreateNonBlockingSocket() 18 | { 19 | ScopedSocket sock_fd(::socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)); 20 | ENSURE(THROW, sock_fd.get() > 0)(errno).Require(); 21 | return sock_fd; 22 | } 23 | 24 | void SetReuseAddr(const ScopedSocket& sock, bool enable) 25 | { 26 | int optval = enable ? 1 : 0; 27 | if (setsockopt(sock.get(), SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0) { 28 | auto err = errno; 29 | LOG(ERROR) << "Set socket SO_REUSEADDR " << enable << " failed: " << err; 30 | ENSURE(CHECK, kbase::NotReached())(err)(enable).Require(); 31 | } 32 | } 33 | 34 | void EnableTCPQuickACK(const ScopedSocket& sock) 35 | { 36 | int optval = 1; 37 | if (setsockopt(sock.get(), IPPROTO_TCP, TCP_QUICKACK, &optval, sizeof(optval)) < 0) { 38 | auto err = errno; 39 | LOG(ERROR) << "Enable socket TCP_QUICKACK failed: " << err; 40 | ENSURE(CHECK, kbase::NotReached())(err).Require(); 41 | } 42 | } 43 | 44 | bool IsSelfConnected(const ScopedSocket& sock) 45 | { 46 | sockaddr_in local_addr, peer_addr; 47 | memset(&local_addr, 0, sizeof(local_addr)); 48 | memset(&peer_addr, 0, sizeof(peer_addr)); 49 | socklen_t local_len = sizeof(local_addr); 50 | socklen_t peer_len = sizeof(peer_addr); 51 | 52 | if (getsockname(sock.get(), reinterpret_cast(&local_addr), &local_len) < 0) { 53 | ENSURE(CHECK, kbase::NotReached()).Require(); 54 | LOG(ERROR) << "getsockname() failed due to " << errno; 55 | } 56 | 57 | if (getpeername(sock.get(), reinterpret_cast(&peer_addr), &peer_len) < 0) { 58 | ENSURE(CHECK, kbase::NotReached()).Require(); 59 | LOG(ERROR) << "getpeername() failed due to " << errno; 60 | } 61 | 62 | return local_addr.sin_port == peer_addr.sin_port && 63 | memcpy(&local_addr.sin_addr, &peer_addr.sin_addr, sizeof(local_addr.sin_addr)); 64 | } 65 | 66 | void ShutdownWrite(const ScopedSocket& sock) 67 | { 68 | if (shutdown(sock.get(), SHUT_WR) < 0) { 69 | LOG(ERROR) << "Failed to shutdown write-side: " << errno; 70 | } 71 | } 72 | 73 | } // namespace socket 74 | } // namespace socket 75 | -------------------------------------------------------------------------------- /ezio/socket_utils_win.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include "ezio/socket_utils.h" 6 | 7 | #include "kbase/error_exception_util.h" 8 | #include "kbase/logging.h" 9 | 10 | namespace ezio { 11 | namespace socket { 12 | 13 | ScopedSocket CreateNonBlockingSocket() 14 | { 15 | ScopedSocket sock(WSASocketW(AF_INET, SOCK_STREAM, 0, nullptr, 0, WSA_FLAG_OVERLAPPED)); 16 | ENSURE(THROW, !!sock)(WSAGetLastError()).Require(); 17 | return sock; 18 | } 19 | 20 | void SetReuseAddr(const ScopedSocket& sock, bool enable) 21 | { 22 | char optval = enable ? 1 : 0; 23 | int rv = setsockopt(sock.get(), SOL_SOCKET, SO_EXCLUSIVEADDRUSE, &optval, sizeof(optval)); 24 | if (rv < 0) { 25 | auto err = WSAGetLastError(); 26 | LOG(ERROR) << "Set socket SO_EXCLUSIVEADDRUSE " << enable << " failed: " << err; 27 | ENSURE(CHECK, kbase::NotReached())(err)(enable).Require(); 28 | } 29 | } 30 | 31 | void ShutdownWrite(const ScopedSocket& sock) 32 | { 33 | if (shutdown(sock.get(), SD_SEND) < 0) { 34 | LOG(ERROR) << "Failed to shutdown write-side: " << WSAGetLastError(); 35 | } 36 | } 37 | 38 | } // namespace socket 39 | } // namespace ezio 40 | -------------------------------------------------------------------------------- /ezio/tcp_client.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include "ezio/tcp_client.h" 6 | 7 | #include 8 | 9 | #include "kbase/error_exception_util.h" 10 | #include "kbase/string_format.h" 11 | 12 | #include "ezio/event_loop.h" 13 | 14 | namespace { 15 | 16 | void OnConnectionDestroyDefault(const ezio::TCPConnectionPtr&) 17 | {} 18 | 19 | } // namespace 20 | 21 | namespace ezio { 22 | 23 | using namespace std::placeholders; 24 | 25 | TCPClient::TCPClient(EventLoop* loop, const SocketAddress& remote_addr, std::string name) 26 | : loop_(loop), 27 | name_(std::move(name)), 28 | state_(State::Disconnected), 29 | next_conn_id_(0), 30 | auto_reconnect_(false), 31 | connector_(MakeConnector(loop, remote_addr)), 32 | on_connection_destroy_(&OnConnectionDestroyDefault), 33 | alive_token_(std::make_shared()) 34 | { 35 | connector_->set_on_new_connection(std::bind(&TCPClient::HandleNewConnection, this, _1, _2)); 36 | connector_->WeaklyBind(alive_token_); 37 | } 38 | 39 | TCPClient::~TCPClient() 40 | { 41 | alive_token_ = nullptr; 42 | 43 | TCPConnectionPtr conn; 44 | bool unique = false; 45 | 46 | { 47 | std::lock_guard lock(conn_mutex_); 48 | unique = conn_.unique(); 49 | conn = std::move(conn_); 50 | } 51 | 52 | // The connector is either still connecting, or doing nothing at all. 53 | if (!conn) { 54 | loop_->RunTask([c = std::shared_ptr(std::move(connector_))] { 55 | c->Cancel(); 56 | }); 57 | 58 | return; 59 | } 60 | 61 | // As long as the connection is alive, there is a chance that the Disconnect() has been 62 | // called but the connection-close event hasn't been delivered. 63 | // Therefore, we should detach the connection from the control of the TCPClient instance. 64 | 65 | loop_->RunTask(std::bind(&TCPConnection::set_on_close, conn, 66 | [loop = loop_, _ = conn](const TCPConnectionPtr& c) { 67 | loop->QueueTask(std::bind(&TCPConnection::MakeTeardown, c)); 68 | })); 69 | 70 | if (unique && (state_.load(std::memory_order_acquire) == State::Connected)) { 71 | conn->ForceClose(); 72 | } 73 | } 74 | 75 | void TCPClient::Connect() 76 | { 77 | auto desired = State::Disconnected; 78 | if (state_.compare_exchange_strong(desired, State::Connecting, 79 | std::memory_order_acq_rel, std::memory_order_relaxed)) { 80 | ENSURE(CHECK, !connector_->connecting()).Require(); 81 | loop_->RunTask([this] { 82 | connector_->Connect(); 83 | }); 84 | } 85 | } 86 | 87 | void TCPClient::Disconnect() 88 | { 89 | auto desired = State::Connected; 90 | if (state_.compare_exchange_strong(desired, State::Disconnecting, 91 | std::memory_order_acq_rel, std::memory_order_relaxed)) { 92 | TCPConnectionPtr conn; 93 | 94 | { 95 | std::lock_guard lock(conn_mutex_); 96 | conn = conn_; 97 | } 98 | 99 | conn->Shutdown(); 100 | } 101 | } 102 | 103 | void TCPClient::Cancel() 104 | { 105 | if (state_.load(std::memory_order_acquire) == State::Connecting) { 106 | loop_->RunTask([this] { 107 | if (connector_->connecting()) { 108 | connector_->Cancel(); 109 | } 110 | 111 | // The connection could be established at this time, so we reset to disconnected 112 | // only if we are in connecting. 113 | auto desired = State::Connecting; 114 | state_.compare_exchange_strong(desired, State::Disconnected, std::memory_order_acq_rel, 115 | std::memory_order_relaxed); 116 | }); 117 | } 118 | } 119 | 120 | void TCPClient::HandleNewConnection(ScopedSocket&& sock, const SocketAddress& local_addr) 121 | { 122 | ENSURE(CHECK, loop_->BelongsToCurrentThread()).Require(); 123 | 124 | state_.store(State::Connected, std::memory_order_release); 125 | 126 | const auto& peer_addr = connector_->remote_addr(); 127 | auto conn_name = kbase::StringFormat("{0}-{1}#{2}", name_, peer_addr.ToHostPort(), 128 | next_conn_id_); 129 | ++next_conn_id_; 130 | 131 | auto conn = std::make_shared(loop_, 132 | std::move(conn_name), 133 | std::move(sock), 134 | local_addr, 135 | peer_addr); 136 | 137 | conn->set_on_connect(on_connect_); 138 | conn->set_on_disconnect(on_disconnect_); 139 | conn->set_on_close(std::bind(&TCPClient::HandleCloseConnection, this, _1)); 140 | conn->set_on_destroy(on_connection_destroy_); 141 | 142 | conn->set_on_message(on_message_); 143 | 144 | { 145 | std::lock_guard lock(conn_mutex_); 146 | conn_ = std::move(conn); 147 | } 148 | 149 | conn_->MakeEstablished(); 150 | } 151 | 152 | void TCPClient::HandleCloseConnection(const TCPConnectionPtr& conn) 153 | { 154 | ENSURE(CHECK, loop_->BelongsToCurrentThread()).Require(); 155 | ENSURE(CHECK, loop_ == conn->event_loop()).Require(); 156 | 157 | state_.store(State::Disconnected, std::memory_order_release); 158 | 159 | { 160 | std::lock_guard lock(conn_mutex_); 161 | conn_ = nullptr; 162 | } 163 | 164 | loop_->QueueTask(std::bind(&TCPConnection::MakeTeardown, conn)); 165 | 166 | if (auto_reconnect() && state_.load(std::memory_order_acquire) != State::Disconnected) { 167 | connector_->Connect(); 168 | } 169 | } 170 | 171 | } // namespace ezio 172 | -------------------------------------------------------------------------------- /ezio/tcp_client.h: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #ifndef EZIO_TCP_CLIENT_H_ 6 | #define EZIO_TCP_CLIENT_H_ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "kbase/basic_macros.h" 14 | 15 | #include "ezio/common_event_handlers.h" 16 | #include "ezio/connector.h" 17 | #include "ezio/socket_address.h" 18 | #include "ezio/tcp_connection.h" 19 | 20 | namespace ezio { 21 | 22 | class EventLoop; 23 | 24 | class TCPClient { 25 | enum class State { 26 | Disconnected, 27 | Disconnecting, 28 | Connecting, 29 | Connected 30 | }; 31 | 32 | struct AliveToken {}; 33 | 34 | public: 35 | TCPClient(EventLoop* loop, const SocketAddress& remote_addr, std::string name); 36 | 37 | ~TCPClient(); 38 | 39 | DISALLOW_COPY(TCPClient); 40 | 41 | DISALLOW_MOVE(TCPClient); 42 | 43 | // This function is thread-safe. 44 | void Connect(); 45 | 46 | // This function is thread-safe. 47 | void Disconnect(); 48 | 49 | // This function is thread-safe. 50 | void Cancel(); 51 | 52 | const std::string& name() const noexcept 53 | { 54 | return name_; 55 | } 56 | 57 | const SocketAddress& remote_addr() const noexcept 58 | { 59 | return connector_->remote_addr(); 60 | } 61 | 62 | TCPConnectionPtr connection() const 63 | { 64 | std::lock_guard lock(conn_mutex_); 65 | return conn_; 66 | } 67 | 68 | bool auto_reconnect() const noexcept 69 | { 70 | return auto_reconnect_; 71 | } 72 | 73 | void set_auto_reconnect(bool enable) noexcept 74 | { 75 | auto_reconnect_ = enable; 76 | } 77 | 78 | void set_on_connect(ConnectionEventHandler handler) 79 | { 80 | on_connect_ = std::move(handler); 81 | } 82 | 83 | void set_on_disconnect(ConnectionEventHandler handler) 84 | { 85 | on_disconnect_ = std::move(handler); 86 | } 87 | 88 | void set_on_connection_destroy(DestroyEventHandler handler) 89 | { 90 | on_connection_destroy_ = std::move(handler); 91 | } 92 | 93 | void set_on_message(MessageEventHandler handler) 94 | { 95 | on_message_ = std::move(handler); 96 | } 97 | 98 | private: 99 | void HandleNewConnection(ScopedSocket&& sock, const SocketAddress& local_addr); 100 | 101 | void HandleCloseConnection(const TCPConnectionPtr& conn); 102 | 103 | private: 104 | EventLoop* loop_; 105 | std::string name_; 106 | std::atomic state_; 107 | int next_conn_id_; 108 | 109 | bool auto_reconnect_; 110 | std::unique_ptr connector_; 111 | 112 | ConnectionEventHandler on_connect_; 113 | ConnectionEventHandler on_disconnect_; 114 | DestroyEventHandler on_connection_destroy_; 115 | 116 | MessageEventHandler on_message_; 117 | 118 | mutable std::mutex conn_mutex_; 119 | TCPConnectionPtr conn_; 120 | 121 | std::shared_ptr alive_token_; 122 | }; 123 | 124 | } // namespace ezio 125 | 126 | #endif // EZIO_TCP_CLIENT_H_ 127 | -------------------------------------------------------------------------------- /ezio/tcp_connection.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include "ezio/tcp_connection.h" 6 | 7 | #include "kbase/error_exception_util.h" 8 | #include "kbase/logging.h" 9 | 10 | #include "ezio/event_loop.h" 11 | #include "ezio/socket_utils.h" 12 | 13 | namespace { 14 | 15 | using namespace std::placeholders; 16 | 17 | } // namespace 18 | 19 | namespace ezio { 20 | 21 | TCPConnection::TCPConnection(EventLoop* loop, 22 | std::string name, 23 | ScopedSocket&& conn_sock, 24 | const SocketAddress& local_addr, 25 | const SocketAddress& peer_addr) 26 | : loop_(loop), 27 | name_(std::move(name)), 28 | state_(State::Connecting), 29 | conn_sock_(std::move(conn_sock)), 30 | conn_notifier_(loop, conn_sock_), 31 | local_addr_(local_addr), 32 | peer_addr_(peer_addr) 33 | { 34 | conn_notifier_.set_on_read(std::bind(&TCPConnection::HandleRead, this, _1, _2)); 35 | conn_notifier_.set_on_write(std::bind(&TCPConnection::HandleWrite, this, _1)); 36 | conn_notifier_.set_on_close(std::bind(&TCPConnection::HandleClose, this)); 37 | conn_notifier_.set_on_error(std::bind(&TCPConnection::HandleError, this)); 38 | } 39 | 40 | TCPConnection::~TCPConnection() 41 | { 42 | bool expected = state() == State::Disconnected; 43 | DLOG_IF(ERROR, !expected) << "TCPConnection is in " << kbase::enum_cast(state()) 44 | << " while destructing"; 45 | ENSURE(CHECK, expected)(state()).Require(); 46 | } 47 | 48 | void TCPConnection::Send(kbase::StringView data) 49 | { 50 | if (state() != State::Connected) { 51 | LOG(WARNING) << "Writing to a not-connected connection!"; 52 | return; 53 | } 54 | 55 | if (loop_->BelongsToCurrentThread()) { 56 | DoSend(data); 57 | } else { 58 | loop_->QueueTask([replica = data.ToString(), self = shared_from_this()] () mutable { 59 | self->DoSend(std::move(replica)); 60 | }); 61 | } 62 | } 63 | 64 | void TCPConnection::DoSend(std::string&& data) 65 | { 66 | ENSURE(CHECK, loop_->BelongsToCurrentThread()).Require(); 67 | 68 | auto str = std::move(data); 69 | DoSend(str); 70 | } 71 | 72 | void TCPConnection::MakeEstablished() 73 | { 74 | ENSURE(CHECK, loop_->BelongsToCurrentThread()).Require(); 75 | ENSURE(CHECK, state() == State::Connecting)(state()).Require(); 76 | 77 | set_state(State::Connected); 78 | 79 | conn_notifier_.WeaklyBind(shared_from_this()); 80 | conn_notifier_.EnableReading(); 81 | 82 | on_connect_(shared_from_this()); 83 | 84 | #if defined(OS_WIN) 85 | ENSURE(CHECK, io_reqs_.read_req.IsProbing())(io_reqs_.read_req.events).Require(); 86 | PostRead(); 87 | #endif 88 | } 89 | 90 | void TCPConnection::MakeTeardown() 91 | { 92 | ENSURE(CHECK, loop_->BelongsToCurrentThread()).Require(); 93 | 94 | auto running_state = State::Connected; 95 | if (state_.compare_exchange_strong(running_state, State::Disconnected, 96 | std::memory_order_acq_rel, std::memory_order_relaxed)) { 97 | on_disconnect_(shared_from_this()); 98 | 99 | conn_notifier_.DisableAll(); 100 | } 101 | 102 | on_destroy_(shared_from_this()); 103 | 104 | conn_notifier_.Detach(); 105 | } 106 | 107 | void TCPConnection::Shutdown() 108 | { 109 | auto running_state = State::Connected; 110 | if (state_.compare_exchange_strong(running_state, State::Disconnecting, 111 | std::memory_order_acq_rel, std::memory_order_relaxed)) { 112 | loop_->RunTask(std::bind(&TCPConnection::DoShutdown, this)); 113 | } 114 | } 115 | 116 | void TCPConnection::DoShutdown() 117 | { 118 | FORCE_AS_NON_CONST_FUNCTION(); 119 | 120 | ENSURE(CHECK, loop_->BelongsToCurrentThread()).Require(); 121 | 122 | if (!conn_notifier_.WatchWriting()) { 123 | socket::ShutdownWrite(conn_sock_); 124 | } 125 | } 126 | 127 | void TCPConnection::ForceClose() 128 | { 129 | auto running_state = State::Connected; 130 | if (state_.compare_exchange_strong(running_state, 131 | State::Disconnecting, 132 | std::memory_order_acq_rel, 133 | std::memory_order_relaxed) || 134 | state() == State::Disconnecting) 135 | { 136 | loop_->RunTask(std::bind(&TCPConnection::DoForceClose, this)); 137 | } 138 | } 139 | 140 | void TCPConnection::DoForceClose() 141 | { 142 | ENSURE(CHECK, loop_->BelongsToCurrentThread()).Require(); 143 | 144 | HandleClose(); 145 | } 146 | 147 | void TCPConnection::SetTCPNoDelay(bool enable) 148 | { 149 | FORCE_AS_NON_CONST_FUNCTION(); 150 | 151 | socket::SetTCPNoDelay(conn_sock_, enable); 152 | } 153 | 154 | void TCPConnection::HandleClose() 155 | { 156 | ENSURE(CHECK, loop_->BelongsToCurrentThread()).Require(); 157 | ENSURE(CHECK, state() == State::Connected || state() == State::Disconnecting) 158 | (state()).Require(); 159 | 160 | set_state(State::Disconnected); 161 | 162 | conn_notifier_.DisableAll(); 163 | 164 | TCPConnectionPtr conn(shared_from_this()); 165 | 166 | on_disconnect_(conn); 167 | on_close_(conn); 168 | } 169 | 170 | void TCPConnection::HandleError() const 171 | { 172 | auto err_code = socket::GetSocketErrorCode(conn_sock_); 173 | LOG(ERROR) << "Error occurred on " << name() << " with code " << err_code; 174 | } 175 | 176 | } // namespace ezio 177 | -------------------------------------------------------------------------------- /ezio/tcp_connection.h: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #ifndef EZIO_TCP_CONNECTION_H_ 6 | #define EZIO_TCP_CONNECTION_H_ 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include "kbase/basic_macros.h" 13 | #include "kbase/string_view.h" 14 | 15 | #include "ezio/buffer.h" 16 | #include "ezio/common_event_handlers.h" 17 | #include "ezio/notifier.h" 18 | #include "ezio/scoped_socket.h" 19 | #include "ezio/socket_address.h" 20 | 21 | #if defined(OS_WIN) 22 | #include "ezio/io_context.h" 23 | #endif 24 | 25 | namespace ezio { 26 | 27 | class EventLoop; 28 | 29 | class TCPConnection : public std::enable_shared_from_this { 30 | public: 31 | // The instance is created in `Connecting` state and becomes `Connected` after execution 32 | // of MakeEstablished(). Once Shutdown() is called, the state then transitions into 33 | // `Disconnecting` state, and it finally turns into `Disconnected` either by HandleClose() 34 | // or MakeTeardown(). 35 | enum class State { 36 | Connecting, 37 | Connected, 38 | Disconnecting, 39 | Disconnected 40 | }; 41 | 42 | TCPConnection(EventLoop* loop, std::string name, ScopedSocket&& conn_sock, 43 | const SocketAddress& local_addr, const SocketAddress& peer_addr); 44 | 45 | ~TCPConnection(); 46 | 47 | DISALLOW_COPY(TCPConnection); 48 | 49 | DISALLOW_MOVE(TCPConnection); 50 | 51 | // This function is thread-safe. 52 | void Send(kbase::StringView data); 53 | 54 | // This function is thread-safe. 55 | void Shutdown(); 56 | 57 | // This function is thread-safe. 58 | void ForceClose(); 59 | 60 | void SetTCPNoDelay(bool enable); 61 | 62 | // Must be called on connection's loop thread. 63 | void MakeEstablished(); 64 | 65 | // Must be called on connection's loop thread. 66 | void MakeTeardown(); 67 | 68 | EventLoop* event_loop() const noexcept 69 | { 70 | return loop_; 71 | } 72 | 73 | const std::string& name() const noexcept 74 | { 75 | return name_; 76 | } 77 | 78 | bool connected() const noexcept 79 | { 80 | return state() == State::Connected; 81 | } 82 | 83 | const SocketAddress& local_addr() const noexcept 84 | { 85 | return local_addr_; 86 | } 87 | 88 | const SocketAddress& peer_addr() const noexcept 89 | { 90 | return peer_addr_; 91 | } 92 | 93 | void set_on_connect(ConnectionEventHandler handler) 94 | { 95 | on_connect_ = std::move(handler); 96 | } 97 | 98 | void set_on_message(MessageEventHandler handler) 99 | { 100 | on_message_ = std::move(handler); 101 | } 102 | 103 | void set_on_disconnect(ConnectionEventHandler handler) 104 | { 105 | on_disconnect_ = std::move(handler); 106 | } 107 | 108 | void set_on_close(CloseEventHandler handler) 109 | { 110 | on_close_ = std::move(handler); 111 | } 112 | 113 | void set_on_destroy(DestroyEventHandler handler) 114 | { 115 | on_destroy_ = std::move(handler); 116 | } 117 | 118 | private: 119 | State state() const noexcept 120 | { 121 | return state_.load(std::memory_order_acquire); 122 | } 123 | 124 | void set_state(State new_state) noexcept 125 | { 126 | state_.store(new_state, std::memory_order_release); 127 | } 128 | 129 | void DoSend(kbase::StringView data); 130 | 131 | void DoSend(std::string&& data); 132 | 133 | void DoShutdown(); 134 | 135 | void DoForceClose(); 136 | 137 | void HandleRead(TimePoint timestamp, IOContext::Details details); 138 | 139 | void HandleWrite(IOContext::Details details); 140 | 141 | void HandleClose(); 142 | 143 | void HandleError() const; 144 | 145 | #if defined(OS_WIN) 146 | void PostRead(); 147 | 148 | void PostWrite(); 149 | #endif 150 | 151 | private: 152 | EventLoop* loop_; 153 | std::string name_; 154 | std::atomic state_; 155 | 156 | ScopedSocket conn_sock_; 157 | Notifier conn_notifier_; 158 | 159 | SocketAddress local_addr_; 160 | SocketAddress peer_addr_; 161 | 162 | Buffer input_buf_; 163 | Buffer output_buf_; 164 | 165 | #if defined(OS_WIN) 166 | struct IORequests { 167 | IORequest read_req; 168 | IORequest write_req; 169 | bool outstanding_write_req; 170 | 171 | IORequests() 172 | : read_req(IOEvent::Read | IOEvent::Probe), 173 | write_req(IOEvent::Write), 174 | outstanding_write_req(false) 175 | {} 176 | }; 177 | 178 | IORequests io_reqs_; 179 | #endif 180 | 181 | ConnectionEventHandler on_connect_; 182 | MessageEventHandler on_message_; 183 | ConnectionEventHandler on_disconnect_; 184 | CloseEventHandler on_close_; 185 | DestroyEventHandler on_destroy_; 186 | }; 187 | 188 | using TCPConnectionPtr = std::shared_ptr; 189 | 190 | } // namespace ezio 191 | 192 | #endif // EZIO_TCP_CONNECTION_H_ 193 | -------------------------------------------------------------------------------- /ezio/tcp_connection_posix.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include "ezio/tcp_connection.h" 6 | 7 | #include "kbase/error_exception_util.h" 8 | #include "kbase/logging.h" 9 | 10 | #include "ezio/event_loop.h" 11 | 12 | namespace ezio { 13 | 14 | void TCPConnection::HandleRead(TimePoint timestamp, IOContext::Details) 15 | { 16 | ENSURE(CHECK, loop_->BelongsToCurrentThread()).Require(); 17 | 18 | ssize_t bytes_read = ReadFDInVec(conn_sock_.get(), input_buf_); 19 | if (bytes_read < 0) { 20 | auto err = errno; 21 | LOG(ERROR) << "Failed to read data from socket " << conn_sock_.get() << "; err: " << err; 22 | HandleError(); 23 | } else if (bytes_read == 0) { 24 | HandleClose(); 25 | } else { 26 | on_message_(shared_from_this(), input_buf_, timestamp); 27 | } 28 | } 29 | 30 | void TCPConnection::DoSend(kbase::StringView data) 31 | { 32 | ENSURE(CHECK, loop_->BelongsToCurrentThread()).Require(); 33 | 34 | // In case the connection is down during queuing the task. 35 | if (state() != State::Connected) { 36 | LOG(WARNING) << "Writing to a not-connected connection!"; 37 | return; 38 | } 39 | 40 | // If no data queued in the output buffer, write to the socket directly. 41 | size_t remaining = data.size(); 42 | if (output_buf_.readable_size() == 0) { 43 | ENSURE(CHECK, !conn_notifier_.WatchWriting())(conn_notifier_.watching_events()).Require(); 44 | auto bytes_written = write(conn_sock_.get(), data.data(), data.size()); 45 | if (bytes_written < 0) { 46 | if (errno != EAGAIN) { 47 | auto err = errno; 48 | LOG(ERROR) << "Failed to write to a socket: " << err; 49 | if (err == EPIPE) { 50 | LOG(ERROR) << "Writing failure due to EPIPE; abandon unwritten data!"; 51 | return; 52 | } 53 | } 54 | } else { 55 | remaining -= bytes_written; 56 | } 57 | } 58 | 59 | ENSURE(CHECK, remaining <= data.size())(remaining)(data.size()).Require(); 60 | 61 | if (remaining == 0) { 62 | return; 63 | } 64 | 65 | auto written_size = data.size() - remaining; 66 | output_buf_.Write(data.data() + written_size, remaining); 67 | 68 | if (!conn_notifier_.WatchWriting()) { 69 | conn_notifier_.EnableWriting(); 70 | } 71 | } 72 | 73 | void TCPConnection::HandleWrite(IOContext::Details) 74 | { 75 | ENSURE(CHECK, loop_->BelongsToCurrentThread()).Require(); 76 | ENSURE(CHECK, output_buf_.readable_size() > 0).Require(); 77 | 78 | if (!conn_notifier_.WatchWriting()) { 79 | LOG(INFO) << "The connection of the socket " << conn_sock_.get() << " is down!"; 80 | return; 81 | } 82 | 83 | auto bytes_written = write(conn_sock_.get(), output_buf_.Peek(), output_buf_.readable_size()); 84 | if (bytes_written < 0) { 85 | LOG(ERROR) << "Failed to write to the socket " << conn_sock_.get() << "; errno: " << errno; 86 | return; 87 | } 88 | 89 | output_buf_.Consume(static_cast(bytes_written)); 90 | if (output_buf_.readable_size() == 0) { 91 | conn_notifier_.DisableWriting(); 92 | if (state() == State::Disconnecting) { 93 | DoShutdown(); 94 | } 95 | } 96 | } 97 | 98 | } // namespace ezio 99 | -------------------------------------------------------------------------------- /ezio/tcp_connection_win.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include "ezio/tcp_connection.h" 6 | 7 | #include 8 | 9 | #include "kbase/error_exception_util.h" 10 | #include "kbase/logging.h" 11 | 12 | #include "ezio/event_loop.h" 13 | 14 | namespace ezio { 15 | 16 | void TCPConnection::PostRead() 17 | { 18 | ENSURE(CHECK, loop_->BelongsToCurrentThread()).Require(); 19 | ENSURE(CHECK, conn_notifier_.WatchReading())(conn_notifier_.watching_events()).Require(); 20 | 21 | DWORD flags = 0; 22 | WSABUF buf {}; 23 | 24 | // Provide buffer to receive data if we are not probing. 25 | if (!io_reqs_.read_req.IsProbing()) { 26 | // Input buf is ran out, expand at disposal, or we can't read in anything. 27 | if (input_buf_.writable_size() == 0) { 28 | input_buf_.ReserveWritable(input_buf_.readable_size() / 2); 29 | } 30 | 31 | buf.buf = input_buf_.BeginWrite(); 32 | buf.len = static_cast(input_buf_.writable_size()); 33 | } 34 | 35 | io_reqs_.read_req.Reset(); 36 | 37 | int rv = WSARecv(conn_sock_.get(), &buf, 1, nullptr, &flags, &io_reqs_.read_req, nullptr); 38 | if (rv != 0 && WSAGetLastError() != WSA_IO_PENDING) { 39 | LOG(ERROR) << "Cannot emit async-read via WSARecv(); error: " << WSAGetLastError(); 40 | HandleError(); 41 | 42 | // If we failed to emit an async read request, we would never get the chance to 43 | // wait HandleRead() to be called, and hence stuck in the loop. 44 | ForceClose(); 45 | } 46 | } 47 | 48 | void TCPConnection::HandleRead(TimePoint timestamp, IOContext::Details details) 49 | { 50 | if ((details.events & IOEvent::Probe) != 0) { 51 | io_reqs_.read_req.DisableProbing(); 52 | } else { 53 | if (details.bytes_transferred == 0) { 54 | HandleClose(); 55 | return; 56 | } 57 | 58 | input_buf_.EndWrite(details.bytes_transferred); 59 | 60 | on_message_(shared_from_this(), input_buf_, timestamp); 61 | } 62 | 63 | PostRead(); 64 | } 65 | 66 | void TCPConnection::DoSend(kbase::StringView data) 67 | { 68 | ENSURE(CHECK, loop_->BelongsToCurrentThread()).Require(); 69 | 70 | output_buf_.Write(data.data(), data.size()); 71 | 72 | if (!io_reqs_.outstanding_write_req) { 73 | // The last PostWrite() may fail and we are still watching writing. 74 | if (!conn_notifier_.WatchWriting()) { 75 | conn_notifier_.EnableWriting(); 76 | } 77 | 78 | PostWrite(); 79 | } 80 | } 81 | 82 | void TCPConnection::PostWrite() 83 | { 84 | ENSURE(CHECK, loop_->BelongsToCurrentThread()).Require(); 85 | ENSURE(CHECK, conn_notifier_.WatchWriting())(conn_notifier_.watching_events()).Require(); 86 | ENSURE(CHECK, !io_reqs_.outstanding_write_req).Require(); 87 | 88 | DWORD flags = 0; 89 | 90 | WSABUF buf {}; 91 | buf.buf = const_cast(output_buf_.Peek()); 92 | buf.len = static_cast(output_buf_.readable_size()); 93 | 94 | io_reqs_.write_req.Reset(); 95 | 96 | int rv = WSASend(conn_sock_.get(), &buf, 1, nullptr, flags, &io_reqs_.write_req, nullptr); 97 | if (rv != 0 && WSAGetLastError() != WSA_IO_PENDING) { 98 | LOG(ERROR) << "Cannot emit async-write via WSASend(); error: " << WSAGetLastError(); 99 | HandleError(); 100 | 101 | // Same as which in PostRead(). 102 | ForceClose(); 103 | 104 | return; 105 | } 106 | 107 | io_reqs_.outstanding_write_req = true; 108 | } 109 | 110 | void TCPConnection::HandleWrite(IOContext::Details details) 111 | { 112 | ENSURE(CHECK, loop_->BelongsToCurrentThread()).Require(); 113 | ENSURE(CHECK, io_reqs_.outstanding_write_req).Require(); 114 | 115 | io_reqs_.outstanding_write_req = false; 116 | 117 | output_buf_.Consume(details.bytes_transferred); 118 | 119 | if (output_buf_.readable_size() > 0) { 120 | PostWrite(); 121 | } else { 122 | conn_notifier_.DisableWriting(); 123 | if (state() == State::Disconnecting) { 124 | DoShutdown(); 125 | } 126 | } 127 | } 128 | 129 | } // namespace ezio 130 | -------------------------------------------------------------------------------- /ezio/tcp_server.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include "ezio/tcp_server.h" 6 | 7 | #include 8 | 9 | #include "kbase/error_exception_util.h" 10 | #include "kbase/string_format.h" 11 | 12 | #include "ezio/event_loop.h" 13 | 14 | namespace { 15 | 16 | void OnConnectionDestroyDefault(const ezio::TCPConnectionPtr&) 17 | {} 18 | 19 | } // namespace 20 | 21 | namespace ezio { 22 | 23 | using namespace std::placeholders; 24 | 25 | TCPServer::TCPServer(ezio::EventLoop* loop, const SocketAddress& addr, std::string name) 26 | : loop_(loop), 27 | listen_addr_(addr), 28 | name_(std::move(name)), 29 | started_(false), 30 | acceptor_(loop, addr), 31 | next_conn_id_(0), 32 | on_connection_destroy_(&OnConnectionDestroyDefault) 33 | { 34 | acceptor_.set_on_new_connection(std::bind(&TCPServer::HandleNewConnection, this, _1, _2)); 35 | } 36 | 37 | TCPServer::~TCPServer() 38 | { 39 | ENSURE(CHECK, loop_->BelongsToCurrentThread()).Require(); 40 | 41 | for (auto& conn_item : connections_) { 42 | TCPConnectionPtr conn(std::move(conn_item.second)); 43 | auto conn_loop = conn->event_loop(); 44 | conn_loop->RunTask(std::bind(&TCPConnection::MakeTeardown, conn)); 45 | } 46 | } 47 | 48 | void TCPServer::Start() 49 | { 50 | Options default_opt; 51 | Start(default_opt); 52 | } 53 | 54 | void TCPServer::Start(const Options& opt) 55 | { 56 | if (!started_.exchange(true, std::memory_order_acq_rel)) { 57 | ENSURE(CHECK, !acceptor_.listening()).Require(); 58 | loop_->RunTask([this] { 59 | acceptor_.Listen(); 60 | }); 61 | 62 | if (opt.worker_num > 0) { 63 | const auto& pool_name = opt.worker_pool_name.empty() ? name() : opt.worker_pool_name; 64 | worker_pool_ = std::make_unique(loop_, 65 | opt.worker_num, 66 | pool_name); 67 | } 68 | } 69 | } 70 | 71 | void TCPServer::HandleNewConnection(ScopedSocket&& conn_sock, const SocketAddress& conn_addr) 72 | { 73 | ENSURE(CHECK, loop_->BelongsToCurrentThread()).Require(); 74 | 75 | auto conn_name = kbase::StringFormat("-{0}#{1}", listen_addr_.ToHostPort(), next_conn_id_); 76 | ++next_conn_id_; 77 | 78 | auto conn_loop = GetEventLoopForConnection(); 79 | 80 | auto conn = std::make_shared(conn_loop, 81 | std::move(conn_name), 82 | std::move(conn_sock), 83 | listen_addr_, 84 | conn_addr); 85 | 86 | connections_.insert({conn->name(), conn}); 87 | 88 | conn->set_on_connect(on_connect_); 89 | conn->set_on_disconnect(on_disconnect_); 90 | conn->set_on_close([this](const TCPConnectionPtr& conn_ptr) { 91 | loop_->RunTask(std::bind(&TCPServer::RemoveConnection, this, conn_ptr)); 92 | }); 93 | conn->set_on_destroy(on_connection_destroy_); 94 | 95 | conn->set_on_message(on_message_); 96 | 97 | conn_loop->RunTask(std::bind(&TCPConnection::MakeEstablished, conn)); 98 | } 99 | 100 | void TCPServer::RemoveConnection(const TCPConnectionPtr& conn) 101 | { 102 | ENSURE(CHECK, loop_->BelongsToCurrentThread()).Require(); 103 | 104 | auto removed_count = connections_.erase(conn->name()); 105 | ENSURE(CHECK, removed_count == 1)(removed_count).Require(); 106 | 107 | auto conn_loop = conn->event_loop(); 108 | conn_loop->QueueTask(std::bind(&TCPConnection::MakeTeardown, conn)); 109 | } 110 | 111 | EventLoop* TCPServer::GetEventLoopForConnection() const 112 | { 113 | ENSURE(CHECK, loop_->BelongsToCurrentThread()).Require(); 114 | 115 | if (worker_pool_) { 116 | return worker_pool_->GetNextEventLoop(); 117 | } 118 | 119 | return loop_; 120 | } 121 | 122 | } // namespace ezio 123 | -------------------------------------------------------------------------------- /ezio/tcp_server.h: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #ifndef EZIO_TCP_SERVER_H_ 6 | #define EZIO_TCP_SERVER_H_ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "kbase/basic_macros.h" 14 | 15 | #include "ezio/acceptor.h" 16 | #include "ezio/common_event_handlers.h" 17 | #include "ezio/socket_address.h" 18 | #include "ezio/tcp_connection.h" 19 | #include "ezio/worker_pool.h" 20 | 21 | namespace ezio { 22 | 23 | class EventLoop; 24 | 25 | class TCPServer { 26 | public: 27 | // 0 worker indicates we don't use worker-pool. 28 | // If worker-pool is used but `worker_pool_name` is empty, we use TCPServer's name 29 | // as the pool's name. 30 | struct Options { 31 | size_t worker_num; 32 | std::string worker_pool_name; 33 | 34 | Options() 35 | : worker_num(0) 36 | {} 37 | }; 38 | 39 | TCPServer(EventLoop* loop, const SocketAddress& addr, std::string name); 40 | 41 | ~TCPServer(); 42 | 43 | DISALLOW_COPY(TCPServer); 44 | 45 | DISALLOW_MOVE(TCPServer); 46 | 47 | // Start functions is thread-safe and it is no harm to call the function multiple times. 48 | 49 | void Start(); 50 | 51 | void Start(const Options& opt); 52 | 53 | const std::string& name() const noexcept 54 | { 55 | return name_; 56 | } 57 | 58 | std::string ip_port() const 59 | { 60 | return listen_addr_.ToHostPort(); 61 | } 62 | 63 | void set_on_connect(ConnectionEventHandler handler) 64 | { 65 | on_connect_ = std::move(handler); 66 | } 67 | 68 | void set_on_disconnect(ConnectionEventHandler handler) 69 | { 70 | on_disconnect_ = std::move(handler); 71 | } 72 | 73 | void set_on_connection_destroy(DestroyEventHandler handler) 74 | { 75 | on_connection_destroy_ = std::move(handler); 76 | } 77 | 78 | void set_on_message(MessageEventHandler handler) 79 | { 80 | on_message_ = std::move(handler); 81 | } 82 | 83 | private: 84 | void HandleNewConnection(ScopedSocket&& conn_sock, const SocketAddress& conn_addr); 85 | 86 | void RemoveConnection(const TCPConnectionPtr& conn); 87 | 88 | // If we are using worker-pool, then use worker thread's loop for new connections. 89 | // Otherwise, use main loop. 90 | EventLoop* GetEventLoopForConnection() const; 91 | 92 | private: 93 | EventLoop* loop_; 94 | SocketAddress listen_addr_; 95 | std::string name_; 96 | std::atomic started_; 97 | Acceptor acceptor_; 98 | std::unique_ptr worker_pool_; 99 | int next_conn_id_; 100 | 101 | using ConnectionMap = std::unordered_map; 102 | 103 | ConnectionMap connections_; 104 | 105 | ConnectionEventHandler on_connect_; 106 | ConnectionEventHandler on_disconnect_; 107 | DestroyEventHandler on_connection_destroy_; 108 | 109 | MessageEventHandler on_message_; 110 | }; 111 | 112 | } // namespace ezio 113 | 114 | #endif // EZIO_TCP_SERVER_H_ 115 | -------------------------------------------------------------------------------- /ezio/this_thread.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include "ezio/this_thread.h" 6 | 7 | #include "kbase/debugger.h" 8 | #include "kbase/error_exception_util.h" 9 | #include "kbase/logging.h" 10 | #include "kbase/string_encoding_conversions.h" 11 | 12 | #if defined(OS_POSIX) 13 | #include 14 | #include 15 | #include 16 | #endif 17 | 18 | namespace { 19 | 20 | #if defined(OS_POSIX) 21 | 22 | pid_t gettid() 23 | { 24 | return static_cast(syscall(SYS_gettid)); 25 | } 26 | 27 | void SetNativeThreadName(const char* name) 28 | { 29 | ENSURE(CHECK, gettid() != getpid()).Require("DO NOT change main thread's name!"); 30 | 31 | int rv = prctl(PR_SET_NAME, name); 32 | if (rv < 0) { 33 | auto err = errno; 34 | LOG(WARNING) << "prctl(PR_SET_NAME) failed: " << err; 35 | } 36 | } 37 | 38 | #elif defined(OS_WIN) 39 | 40 | struct win10_description {}; 41 | struct seh_tradition {}; 42 | 43 | const DWORD kVCThreadNameException = 0x406D1388; 44 | 45 | #pragma pack(push, 8) 46 | typedef struct tagTHREADNAME_INFO { 47 | DWORD dwType; // Must be 0x1000. 48 | LPCSTR szName; // Pointer to name (in user addr space). 49 | DWORD dwThreadID; // Thread ID (-1=caller thread). 50 | DWORD dwFlags; // Reserved for future use, must be zero. 51 | } THREADNAME_INFO; 52 | #pragma pack(pop) 53 | 54 | bool SetThreadNameInternal(const char* name, win10_description) 55 | { 56 | DECLARE_DLL_FUNCTION(SetThreadDescription, HRESULT(WINAPI*)(HANDLE, PCWSTR), "Kernel32.dll"); 57 | if (!SetThreadDescription) { 58 | return false; 59 | } 60 | 61 | auto hr = SetThreadDescription(GetCurrentThread(), kbase::ASCIIToWide(name).c_str()); 62 | if (FAILED(hr)) { 63 | kbase::LastError err; 64 | LOG(WARNING) << "SetThreadDescription() failed: " << err; 65 | return false; 66 | } 67 | 68 | return true; 69 | } 70 | 71 | // See @ https://goo.gl/ovx1L6. 72 | void SetThreadNameInternal(const char* name, seh_tradition) 73 | { 74 | THREADNAME_INFO info; 75 | info.dwType = 0x1000; 76 | info.szName = name; 77 | info.dwThreadID = static_cast(-1); 78 | info.dwFlags = 0; 79 | 80 | #pragma warning(push) 81 | #pragma warning(disable: 6320 6322) 82 | __try { 83 | RaiseException(kVCThreadNameException, 0, sizeof(info) / sizeof(ULONG_PTR), 84 | reinterpret_cast(&info)); 85 | } __except(EXCEPTION_EXECUTE_HANDLER) { 86 | } 87 | #pragma warning(pop) 88 | } 89 | 90 | void SetNativeThreadName(const char* name) 91 | { 92 | if (SetThreadNameInternal(name, win10_description{})) { 93 | return; 94 | } 95 | 96 | // The old way is necessary only when the debugger is present. 97 | if (kbase::IsDebuggerPresent()) { 98 | SetThreadNameInternal(name, seh_tradition{}); 99 | } 100 | } 101 | 102 | #endif 103 | 104 | } // namespace 105 | 106 | namespace ezio { 107 | namespace this_thread { 108 | 109 | #if defined(OS_POSIX) 110 | 111 | thread_local ThreadID tls_thread_id {0}; 112 | 113 | void CacheThreadID() 114 | { 115 | if (tls_thread_id == 0) { 116 | tls_thread_id = gettid(); 117 | } 118 | } 119 | 120 | #endif 121 | 122 | thread_local const char* tls_thread_name {"Unknown"}; 123 | 124 | void SetName(const char* name, bool skip_native_name) 125 | { 126 | tls_thread_name = name; 127 | 128 | if (!skip_native_name) { 129 | SetNativeThreadName(name); 130 | } 131 | } 132 | 133 | } // namespace this_thread 134 | } // namespace ezio 135 | -------------------------------------------------------------------------------- /ezio/this_thread.h: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #ifndef EZIO_THIS_THREAD_H_ 6 | #define EZIO_THIS_THREAD_H_ 7 | 8 | #include "kbase/basic_macros.h" 9 | 10 | #if defined(OS_POSIX) 11 | #include 12 | #elif defined(OS_WIN) 13 | #include 14 | #endif 15 | 16 | namespace ezio { 17 | 18 | constexpr const char* kMainThreadName = "ezio-Main"; 19 | 20 | namespace this_thread { 21 | 22 | #if defined(OS_POSIX) 23 | using ThreadID = pid_t; 24 | #elif defined(OS_WIN) 25 | using ThreadID = DWORD; 26 | #endif 27 | 28 | extern thread_local const char* tls_thread_name; 29 | 30 | #if defined(OS_POSIX) 31 | 32 | extern thread_local ThreadID tls_thread_id; 33 | 34 | void CacheThreadID(); 35 | 36 | #endif 37 | 38 | // On Windows, GetCurrentThreadId() is quite cheap and doesn't involve a system call. 39 | inline ThreadID GetID() 40 | { 41 | #if defined(OS_POSIX) 42 | if (tls_thread_id == 0) { 43 | CacheThreadID(); 44 | } 45 | 46 | return tls_thread_id; 47 | #elif defined(OS_WIN) 48 | return GetCurrentThreadId(); 49 | #endif 50 | } 51 | 52 | inline const char* GetName() 53 | { 54 | return tls_thread_name; 55 | } 56 | 57 | // We use TLS to cache the pointer to the name string, therefore the user must ensure the 58 | // lifetime of `name` is not shorter than the the thread itself. 59 | // Try not to set native thread name for main thread, especially on Linux; doing so may 60 | // cause some tools stop working. 61 | void SetName(const char* name, bool skip_native_name = false); 62 | 63 | } // namespace this_thread 64 | } // namespace ezio 65 | 66 | #endif // EZIO_THIS_THREAD_H_ 67 | -------------------------------------------------------------------------------- /ezio/thread.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include "ezio/thread.h" 6 | 7 | #include "kbase/error_exception_util.h" 8 | 9 | #include "ezio/event_loop.h" 10 | #include "ezio/this_thread.h" 11 | 12 | namespace ezio { 13 | 14 | Thread::Thread(std::string name) 15 | : name_(std::move(name)), 16 | raw_thread_(std::make_unique(std::bind(&Thread::ThreadMain, this))) 17 | { 18 | std::unique_lock lock(loop_init_mtx_); 19 | loop_inited_.wait(lock, [this] { return loop_.get() != nullptr; }); 20 | } 21 | 22 | Thread::~Thread() 23 | { 24 | // In case the loop quits before the destruction. 25 | if (loop_) { 26 | loop_->Quit(); 27 | } 28 | 29 | raw_thread_->join(); 30 | } 31 | 32 | void Thread::ThreadMain() 33 | { 34 | // EventLoop must be constructed on the target thread, and must die before the thread 35 | // exits. 36 | 37 | auto loop = std::make_unique(); 38 | 39 | { 40 | std::lock_guard lock(loop_init_mtx_); 41 | loop_ = std::move(loop); 42 | loop_inited_.notify_one(); 43 | } 44 | 45 | this_thread::SetName(name_.c_str()); 46 | 47 | loop_->Run(); 48 | 49 | loop_ = nullptr; 50 | } 51 | 52 | } // namespace ezio 53 | -------------------------------------------------------------------------------- /ezio/thread.h: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #ifndef EZIO_THREAD_H_ 6 | #define EZIO_THREAD_H_ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "kbase/basic_macros.h" 15 | 16 | namespace ezio { 17 | 18 | class EventLoop; 19 | 20 | // A Thread instance is a native thread running with an EventLoop. 21 | class Thread { 22 | public: 23 | explicit Thread(std::string name); 24 | 25 | ~Thread(); 26 | 27 | DISALLOW_COPY(Thread); 28 | 29 | DISALLOW_MOVE(Thread); 30 | 31 | const std::string& name() const noexcept 32 | { 33 | return name_; 34 | } 35 | 36 | EventLoop* event_loop() const noexcept 37 | { 38 | return loop_.get(); 39 | } 40 | 41 | private: 42 | void ThreadMain(); 43 | 44 | private: 45 | std::string name_; 46 | std::mutex loop_init_mtx_; 47 | std::condition_variable loop_inited_; 48 | std::unique_ptr raw_thread_; 49 | std::unique_ptr loop_; 50 | }; 51 | 52 | } // namespace ezio 53 | 54 | #endif // EZIO_THREAD_H_ 55 | -------------------------------------------------------------------------------- /ezio/timer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include "ezio/timer.h" 6 | 7 | namespace ezio { 8 | 9 | Timer::Timer(TickEventHandler handler, TimePoint when, TimeDuration interval) 10 | : on_tick_(std::move(handler)), 11 | expiration_(when), 12 | interval_(interval) 13 | {} 14 | 15 | void Timer::Tick() const 16 | { 17 | on_tick_(); 18 | } 19 | 20 | void Timer::Restart(TimePoint when) 21 | { 22 | expiration_ = when + interval_; 23 | } 24 | 25 | } // namespace ezio 26 | -------------------------------------------------------------------------------- /ezio/timer.h: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #ifndef EZIO_TIMER_H_ 6 | #define EZIO_TIMER_H_ 7 | 8 | #include 9 | 10 | #include "kbase/basic_macros.h" 11 | 12 | #include "ezio/chrono_utils.h" 13 | 14 | namespace ezio { 15 | 16 | class Timer { 17 | public: 18 | using TickEventHandler = std::function; 19 | 20 | Timer(TickEventHandler handler, TimePoint when, TimeDuration interval); 21 | 22 | ~Timer() = default; 23 | 24 | DISALLOW_COPY(Timer); 25 | 26 | DISALLOW_MOVE(Timer); 27 | 28 | void Tick() const; 29 | 30 | void Restart(TimePoint when); 31 | 32 | TimePoint expiration() const noexcept 33 | { 34 | return expiration_; 35 | } 36 | 37 | bool is_repeating() const noexcept 38 | { 39 | return interval_ > TimeDuration::zero(); 40 | } 41 | 42 | private: 43 | TickEventHandler on_tick_; 44 | TimePoint expiration_; 45 | TimeDuration interval_; 46 | }; 47 | 48 | } // namespace ezio 49 | 50 | #endif // EZIO_TIMER_H_ 51 | -------------------------------------------------------------------------------- /ezio/timer_id.h: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #ifndef EZIO_TIMER_ID_H_ 6 | #define EZIO_TIMER_ID_H_ 7 | 8 | #include "kbase/basic_macros.h" 9 | 10 | namespace ezio { 11 | 12 | class Timer; 13 | 14 | class TimerID { 15 | public: 16 | TimerID() noexcept 17 | : timer_(nullptr) 18 | {} 19 | 20 | explicit TimerID(Timer* timer) noexcept 21 | : timer_(timer) 22 | {} 23 | 24 | DEFAULT_COPY(TimerID); 25 | 26 | DEFAULT_MOVE(TimerID); 27 | 28 | explicit operator bool() const noexcept 29 | { 30 | return timer_ != nullptr; 31 | } 32 | 33 | Timer* timer() const noexcept 34 | { 35 | return timer_; 36 | } 37 | 38 | private: 39 | Timer* timer_; 40 | }; 41 | 42 | } // namespace ezio 43 | 44 | #endif // EZIO_TIMER_ID_H_ 45 | -------------------------------------------------------------------------------- /ezio/timer_queue.h: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #ifndef EZIO_TIMER_QUEUE_H_ 6 | #define EZIO_TIMER_QUEUE_H_ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "kbase/basic_macros.h" 14 | 15 | #include "ezio/chrono_utils.h" 16 | #include "ezio/timer.h" 17 | #include "ezio/timer_id.h" 18 | 19 | #if defined(OS_POSIX) 20 | #include "kbase/scoped_handle.h" 21 | 22 | #include "ezio/notifier.h" 23 | #endif 24 | 25 | namespace ezio { 26 | 27 | class EventLoop; 28 | 29 | class TimerQueue { 30 | public: 31 | explicit TimerQueue(EventLoop* loop); 32 | 33 | ~TimerQueue(); 34 | 35 | DISALLOW_COPY(TimerQueue); 36 | 37 | DISALLOW_MOVE(TimerQueue); 38 | 39 | TimerID AddTimer(Timer::TickEventHandler handler, TimePoint when, TimeDuration interval); 40 | 41 | void Cancel(TimerID timer_id); 42 | 43 | void ProcessExpiredTimers(TimePoint expiration); 44 | 45 | std::pair next_expiration() const; 46 | 47 | private: 48 | // `new_timer` is an owner. 49 | void AddTimerInLoop(Timer* new_timer); 50 | 51 | bool Insert(std::unique_ptr new_timer); 52 | 53 | void CancelInLoop(TimerID timer_id); 54 | 55 | #if defined(OS_POSIX) 56 | void OnTimerExpired(TimePoint timestamp); 57 | #endif 58 | 59 | private: 60 | EventLoop* loop_; 61 | 62 | #if defined(OS_POSIX) 63 | kbase::ScopedFD timer_fd_; 64 | Notifier timer_notifier_; 65 | #endif 66 | 67 | std::multimap> timers_; 68 | bool processing_expired_timers_; 69 | std::set canceling_timers_; 70 | }; 71 | 72 | } // namespace ezio 73 | 74 | #endif // EZIO_TIMER_QUEUE_H_ 75 | -------------------------------------------------------------------------------- /ezio/winsock_context.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include "ezio/winsock_context.h" 6 | 7 | #include "kbase/error_exception_util.h" 8 | #include "kbase/scope_guard.h" 9 | 10 | #pragma comment(lib, "ws2_32.lib") 11 | 12 | namespace { 13 | 14 | template 15 | void GetExtensionFunctionPointer(SOCKET sock, P& pfn, GUID guid) 16 | { 17 | DWORD recv_bytes = 0; 18 | auto rv = WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid), &pfn, 19 | sizeof(pfn), &recv_bytes, nullptr, nullptr); 20 | ENSURE(CHECK, rv == 0)(rv)(WSAGetLastError()).Require(); 21 | } 22 | 23 | } // namespace 24 | 25 | namespace ezio { 26 | 27 | WinsockContext::WinsockContext() 28 | : AcceptEx(nullptr) 29 | { 30 | WSADATA data {0}; 31 | int rv = WSAStartup(MAKEWORD(2, 2), &data); 32 | ENSURE(THROW, rv == 0)(rv).Require(); 33 | 34 | auto was_guard = MAKE_SCOPE_GUARD { WSACleanup(); }; 35 | 36 | auto sock = socket(AF_INET, SOCK_STREAM, 0); 37 | ENSURE(THROW, !!sock)(WSAGetLastError()).Require(); 38 | 39 | // Avoid to introduce scoped-socket concept here. 40 | ON_SCOPE_EXIT { closesocket(sock); }; 41 | 42 | GetExtensionFunctionPointer(sock, AcceptEx, WSAID_ACCEPTEX); 43 | GetExtensionFunctionPointer(sock, ConnectEx, WSAID_CONNECTEX); 44 | 45 | was_guard.Dismiss(); 46 | } 47 | 48 | WinsockContext::~WinsockContext() 49 | { 50 | WSACleanup(); 51 | } 52 | 53 | } // namespace ezio 54 | -------------------------------------------------------------------------------- /ezio/winsock_context.h: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #ifndef EZIO_WINSOCK_CONTEXT_H_ 6 | #define EZIO_WINSOCK_CONTEXT_H_ 7 | 8 | #include 9 | #include 10 | 11 | namespace ezio { 12 | 13 | struct WinsockContext { 14 | WinsockContext(); 15 | 16 | ~WinsockContext(); 17 | 18 | LPFN_ACCEPTEX AcceptEx; 19 | LPFN_CONNECTEX ConnectEx; 20 | }; 21 | 22 | } // namespace ezio 23 | 24 | #endif // EZIO_WINSOCK_CONTEXT_H_ 25 | -------------------------------------------------------------------------------- /ezio/worker_pool.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include "ezio/worker_pool.h" 6 | 7 | #include "kbase/error_exception_util.h" 8 | #include "kbase/string_format.h" 9 | 10 | #include "ezio/event_loop.h" 11 | #include "ezio/thread.h" 12 | 13 | namespace ezio { 14 | 15 | WorkerPool::WorkerPool(EventLoop* main_loop, size_t worker_num, std::string name) 16 | : main_loop_(main_loop), 17 | name_(std::move(name)), 18 | next_loop_idx_(0) 19 | { 20 | ENSURE(CHECK, main_loop_->BelongsToCurrentThread()).Require(); 21 | ENSURE(CHECK, worker_num > 0).Require(); 22 | 23 | for (size_t i = 0; i < worker_num; ++i) { 24 | auto worker_name = kbase::StringFormat("{0}-{1}", name_, i); 25 | auto worker = std::make_unique(std::move(worker_name)); 26 | 27 | loops_.push_back(worker->event_loop()); 28 | workers_.push_back(std::move(worker)); 29 | } 30 | } 31 | 32 | WorkerPool::~WorkerPool() 33 | {} 34 | 35 | EventLoop* WorkerPool::GetNextEventLoop() 36 | { 37 | ENSURE(CHECK, main_loop_->BelongsToCurrentThread()).Require(); 38 | ENSURE(CHECK, next_loop_idx_ < loops_.size())(next_loop_idx_).Require(); 39 | 40 | auto loop = loops_[next_loop_idx_++]; 41 | 42 | if (next_loop_idx_ >= loops_.size()) { 43 | next_loop_idx_ = 0; 44 | } 45 | 46 | return loop; 47 | } 48 | 49 | } // namespace ezio 50 | -------------------------------------------------------------------------------- /ezio/worker_pool.h: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #ifndef EZIO_WORKER_POOL_H_ 6 | #define EZIO_WORKER_POOL_H_ 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include "kbase/basic_macros.h" 13 | 14 | namespace ezio { 15 | 16 | class EventLoop; 17 | class Thread; 18 | 19 | class WorkerPool { 20 | public: 21 | // WorkerPool's name will be the prefix of every worker thread's name. 22 | WorkerPool(EventLoop* main_loop, size_t worker_num, std::string name); 23 | 24 | ~WorkerPool(); 25 | 26 | DISALLOW_COPY(WorkerPool); 27 | 28 | DISALLOW_MOVE(WorkerPool); 29 | 30 | const std::string& name() const noexcept 31 | { 32 | return name_; 33 | } 34 | 35 | EventLoop* GetNextEventLoop(); 36 | 37 | private: 38 | EventLoop* main_loop_; 39 | std::string name_; 40 | std::vector> workers_; 41 | std::vector loops_; 42 | size_t next_loop_idx_; 43 | }; 44 | 45 | } // namespace ezio 46 | 47 | #endif // EZIO_WORKER_POOL_H_ 48 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | declare_dep_module(Catch2 3 | VERSION 2.5.0 4 | GIT_REPOSITORY https://github.com/catchorg/Catch2.git 5 | GIT_TAG v2.5.0 6 | ) 7 | 8 | set(tests_SRCS 9 | main.cpp 10 | acceptor_unittest.cpp 11 | buffer_unittest.cpp 12 | connector_and_tcpclient.cpp 13 | endian_utils_unittest.cpp 14 | io_service_context_unittest.cpp 15 | loop_and_notifier_unittest.cpp 16 | scoped_socket_unittest.cpp 17 | socket_address_unittest.cpp 18 | tcp_server_and_connection.cpp 19 | thread_and_worker_pool.cpp 20 | ) 21 | 22 | if(WIN32) 23 | list(APPEND tests_SRCS 24 | winsock_context_unittest.cpp 25 | ) 26 | else(UNIX) 27 | list(APPEND tests_SRCS 28 | ignore_sigpipe_unittest.cpp 29 | ) 30 | endif() 31 | 32 | source_group("tests" FILES ${tests_SRCS}) 33 | 34 | add_executable(ezio_test ${tests_SRCS}) 35 | 36 | apply_common_compile_properties_to_target(ezio_test) 37 | 38 | set_target_properties(ezio_test PROPERTIES 39 | COTIRE_CXX_PREFIX_HEADER_INIT "${EZIO_PCH_HEADER}" 40 | ) 41 | 42 | cotire(ezio_test) 43 | 44 | target_link_libraries(ezio_test 45 | PRIVATE 46 | ezio 47 | kbase 48 | Catch2 49 | ) 50 | -------------------------------------------------------------------------------- /tests/acceptor_unittest.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include "catch2/catch.hpp" 6 | 7 | #include "kbase/at_exit_manager.h" 8 | 9 | #include "ezio/acceptor.h" 10 | #include "ezio/event_loop.h" 11 | #include "ezio/io_service_context.h" 12 | 13 | namespace ezio { 14 | 15 | TEST_CASE("Acceptor listens on incoming requests", "[Acceptor]") 16 | { 17 | kbase::AtExitManager exit_manager; 18 | IOServiceContext::Init(); 19 | 20 | SocketAddress addr(9876); 21 | 22 | EventLoop loop; 23 | 24 | Acceptor acceptor(&loop, addr); 25 | acceptor.set_on_new_connection([&loop](ScopedSocket&& sock, const SocketAddress& peer_addr) { 26 | printf("new conn is on %s\n", peer_addr.ToHostPort().c_str()); 27 | sock = nullptr; 28 | loop.Quit(); 29 | }); 30 | 31 | REQUIRE_FALSE(acceptor.listening()); 32 | 33 | acceptor.Listen(); 34 | 35 | REQUIRE(acceptor.listening()); 36 | printf("Listening on %s\n", addr.ToHostPort().c_str()); 37 | 38 | loop.Run(); 39 | 40 | printf("Main loop exit\n"); 41 | } 42 | 43 | } // namespace ezio 44 | -------------------------------------------------------------------------------- /tests/endian_utils_unittest.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include "catch2/catch.hpp" 6 | 7 | #include "ezio/endian_utils.h" 8 | 9 | namespace { 10 | 11 | template 12 | struct EndianPair { 13 | T le; 14 | T be; 15 | }; 16 | 17 | EndianPair usp {uint16_t(0x1234), uint16_t(0x3412)}; 18 | // 0xCCFF -> 0xFFCC 19 | EndianPair sp {int16_t(-13057), int16_t(-52)}; 20 | 21 | EndianPair uip {0x12345678U, 0x78563412U}; 22 | EndianPair ip {0x7FCCDEAD, int(0xADDECC7F)}; 23 | 24 | EndianPair ullp {uint64_t(0xDEADBEEFCCDDFFEE), uint64_t(0xEEFFDDCCEFBEADDE)}; 25 | EndianPair llp {int64_t(0x12345678ABCD00FF), int64_t(0xFF00CDAB78563412)}; 26 | 27 | } // namespace 28 | 29 | namespace ezio { 30 | 31 | TEST_CASE("Little to big", "[EndianUtils]") 32 | { 33 | SECTION("for 16-bit integers") 34 | { 35 | CHECK(HostToNetwork(usp.le) == usp.be); 36 | CHECK(HostToNetwork(sp.le) == sp.be); 37 | } 38 | 39 | SECTION("for 32-bit integers") 40 | { 41 | CHECK(HostToNetwork(uip.le) == uip.be); 42 | CHECK(HostToNetwork(ip.le) == ip.be); 43 | } 44 | 45 | SECTION("for 64-bit integers") 46 | { 47 | CHECK(HostToNetwork(ullp.le) == ullp.be); 48 | CHECK(HostToNetwork(llp.le) == llp.be); 49 | } 50 | } 51 | 52 | TEST_CASE("Big to little", "[EndianUtils]") 53 | { 54 | SECTION("for 16-bit integers") 55 | { 56 | CHECK(NetworkToHost(usp.be) == usp.le); 57 | CHECK(NetworkToHost(sp.be) == sp.le); 58 | } 59 | 60 | SECTION("for 32-bit integers") 61 | { 62 | CHECK(NetworkToHost(uip.be) == uip.le); 63 | CHECK(NetworkToHost(ip.be) == ip.le); 64 | } 65 | 66 | SECTION("for 64-bit integers") 67 | { 68 | CHECK(NetworkToHost(ullp.be) == ullp.le); 69 | CHECK(NetworkToHost(llp.be) == llp.le); 70 | } 71 | } 72 | 73 | } // namespace ezio 74 | -------------------------------------------------------------------------------- /tests/ignore_sigpipe_unittest.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include "catch2/catch.hpp" 6 | 7 | #include "ezio/ignore_sigpipe.h" 8 | 9 | #include 10 | 11 | #include "kbase/scoped_handle.h" 12 | 13 | namespace ezio { 14 | 15 | TEST_CASE("Ignore sigpipe handler", "[IgnoreSigPipe]") 16 | { 17 | int fds[2] {0}; 18 | int rv = pipe(fds); 19 | 20 | CHECK(rv == 0); 21 | INFO("Failed to call pipe(): " << errno); 22 | 23 | IgnoreSigPipe ignore_sigpipe; 24 | UNUSED_VAR(ignore_sigpipe); 25 | 26 | kbase::ScopedFD read_end(fds[0]); 27 | kbase::ScopedFD write_end(fds[1]); 28 | 29 | // Close read end. 30 | read_end = nullptr; 31 | 32 | int n = 1; 33 | ssize_t result = write(write_end.get(), &n, sizeof(n)); 34 | 35 | REQUIRE(result == -1); 36 | REQUIRE(errno == EPIPE); 37 | } 38 | 39 | } // namespace ezio 40 | -------------------------------------------------------------------------------- /tests/io_service_context_unittest.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include "catch2/catch.hpp" 6 | 7 | #include "ezio/io_service_context.h" 8 | 9 | #include "kbase/at_exit_manager.h" 10 | 11 | #if defined(OS_POSIX) 12 | #include 13 | 14 | #include "kbase/scoped_handle.h" 15 | #endif 16 | 17 | namespace ezio { 18 | 19 | TEST_CASE("Init IOServiceContext before using any facilities", "[IOServiceContext]") 20 | { 21 | kbase::AtExitManager exit_manager; 22 | IOServiceContext::Init(); 23 | 24 | #if defined(OS_WIN) 25 | SECTION("access winsock-context via io-service-context") 26 | { 27 | auto pfn = IOServiceContext::current().AsWinsockContext().AcceptEx; 28 | REQUIRE(pfn != nullptr); 29 | } 30 | #else 31 | SECTION("io-service-context on Linux uses IgnoreSigpipe") 32 | { 33 | int fds[2] {0}; 34 | int rv = pipe(fds); 35 | 36 | CHECK(rv == 0); 37 | INFO("Failed to call pipe(): " << errno); 38 | 39 | kbase::ScopedFD read_end(fds[0]); 40 | kbase::ScopedFD write_end(fds[1]); 41 | 42 | // Close read end. 43 | read_end = nullptr; 44 | 45 | int n = 1; 46 | ssize_t result = write(write_end.get(), &n, sizeof(n)); 47 | 48 | REQUIRE(result == -1); 49 | REQUIRE(errno == EPIPE); 50 | } 51 | #endif 52 | } 53 | 54 | } // namespace ezio 55 | -------------------------------------------------------------------------------- /tests/loop_and_notifier_unittest.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include "catch2/catch.hpp" 6 | 7 | #include "ezio/event_loop.h" 8 | 9 | #include 10 | #include 11 | 12 | #include "kbase/at_exit_manager.h" 13 | #include "kbase/logging.h" 14 | 15 | #include "ezio/chrono_utils.h" 16 | #include "ezio/io_service_context.h" 17 | 18 | namespace ezio { 19 | 20 | TEST_CASE("EventLoop and Notifier are two fundamental building blocks", "[MainLoop]") 21 | { 22 | kbase::AtExitManager exit_manager; 23 | IOServiceContext::Init(); 24 | 25 | SECTION("run a loop and quit from the loop") 26 | { 27 | EventLoop loop; 28 | 29 | std::thread th([&loop] { 30 | printf("worker is sleeping\n"); 31 | std::this_thread::sleep_for(std::chrono::seconds(3)); 32 | printf("Quit main loop from worker thread\n"); 33 | loop.Quit(); 34 | }); 35 | 36 | printf("Main loop runs\n"); 37 | loop.Run(); 38 | printf("Main loop ends\n"); 39 | 40 | th.join(); 41 | } 42 | 43 | SECTION("queue tasks and run tasks") 44 | { 45 | EventLoop loop; 46 | 47 | std::thread th([&loop] { 48 | // Queuing and running task both wake up the loop. 49 | std::this_thread::sleep_for(std::chrono::seconds(3)); 50 | printf("queue a task from thread %u\n", this_thread::GetID()); 51 | loop.QueueTask([&loop] { 52 | printf("queuing\n"); 53 | 54 | loop.RunTask([] { 55 | printf("execute task on thread %u\n...\n", this_thread::GetID()); 56 | }); 57 | 58 | // Quit should be executed sequentially after previous lambda. 59 | printf("quit\n"); 60 | loop.Quit(); 61 | }); 62 | }); 63 | 64 | printf("running loop on thread %u\n", this_thread::GetID()); 65 | 66 | loop.Run(); 67 | 68 | th.join(); 69 | } 70 | 71 | SECTION("runs a timed task") 72 | { 73 | EventLoop loop; 74 | 75 | loop.RunTaskAfter([] { 76 | printf("The timed task is executing\n"); 77 | EventLoop::current()->Quit(); 78 | }, std::chrono::seconds(3)); 79 | 80 | printf("EventLoop is running!\n"); 81 | 82 | loop.Run(); 83 | } 84 | 85 | SECTION("runs a timed task from other thread") 86 | { 87 | EventLoop loop; 88 | 89 | std::thread th([&loop] { 90 | std::this_thread::sleep_for(std::chrono::seconds(2)); 91 | loop.RunTaskAfter([] { 92 | printf("Timed task designated from other thread\n"); 93 | EventLoop::current()->Quit(); 94 | }, std::chrono::seconds(2)); 95 | }); 96 | 97 | loop.Run(); 98 | 99 | th.join(); 100 | } 101 | 102 | SECTION("timed tasks are ordered by their expiration") 103 | { 104 | EventLoop loop; 105 | 106 | loop.RunTaskAfter([] { 107 | LOG(INFO) << "Task with 5s delayed"; 108 | printf("Task with 5s delayed\n"); 109 | EventLoop::current()->RunTaskAfter([] { 110 | LOG(INFO) << "Wait for another 2s to quit"; 111 | printf("Wait for another 2s to quit\n"); 112 | EventLoop::current()->Quit(); 113 | }, std::chrono::seconds(2)); 114 | }, std::chrono::seconds(5)); 115 | 116 | std::thread th([&loop] { 117 | loop.RunTaskAt([] { 118 | LOG(INFO) << "Task with 3s delayed"; 119 | printf("Task with 3s delayed\n"); 120 | }, ToTimePoint(std::chrono::system_clock::now()) + std::chrono::seconds(3)); 121 | }); 122 | 123 | printf("EventLoop is running!\n"); 124 | 125 | loop.Run(); 126 | 127 | th.join(); 128 | } 129 | 130 | SECTION("cancel a timer") 131 | { 132 | EventLoop loop; 133 | 134 | auto t1 = loop.RunTaskAfter([] { 135 | printf("Task with 3s dealyed\n"); 136 | }, std::chrono::seconds(3)); 137 | 138 | loop.RunTaskAfter([] { 139 | printf("quit\n"); 140 | EventLoop::current()->Quit(); 141 | }, std::chrono::seconds(5)); 142 | 143 | std::thread th([&loop, t1] { 144 | printf("worker is zzzz\n"); 145 | std::this_thread::sleep_for(std::chrono::seconds(2)); 146 | loop.CancelTimedTask(t1); 147 | printf("first timed task is canceled!\n"); 148 | }); 149 | 150 | printf("EventLoop is running!\n"); 151 | 152 | loop.Run(); 153 | 154 | th.join(); 155 | } 156 | 157 | SECTION("cancel a repeating timer") 158 | { 159 | EventLoop loop; 160 | 161 | int repeated_count = 0; 162 | // Yuck. Don't use this hack in production code. 163 | TimerID timer(nullptr); 164 | timer = loop.RunTaskEvery([&repeated_count, &timer] { 165 | printf("run for %d time\n", ++repeated_count); 166 | if (repeated_count >= 5) { 167 | EventLoop::current()->CancelTimedTask(timer); 168 | EventLoop::current()->Quit(); 169 | } 170 | }, std::chrono::seconds(1)); 171 | 172 | printf("EventLoop is running!\n"); 173 | 174 | loop.Run(); 175 | } 176 | } 177 | 178 | } // namespace ezio 179 | -------------------------------------------------------------------------------- /tests/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #define CATCH_CONFIG_RUNNER 6 | #include "catch2/catch.hpp" 7 | 8 | #include "kbase/command_line.h" 9 | 10 | int main(int argc, char* argv[]) 11 | { 12 | #if defined(OS_POSIX) 13 | kbase::CommandLine::Init(argc, argv); 14 | #elif defined(OS_WIN) 15 | kbase::CommandLine::Init(0, nullptr); 16 | #endif 17 | 18 | Catch::Session session; 19 | session.applyCommandLine(argc, argv); 20 | 21 | return session.run(); 22 | } -------------------------------------------------------------------------------- /tests/scoped_socket_unittest.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include "catch2/catch.hpp" 6 | 7 | #include "ezio/scoped_socket.h" 8 | 9 | #if defined(OS_POSIX) 10 | #include 11 | #include 12 | #else 13 | #include "ezio/winsock_context.h" 14 | #endif 15 | 16 | namespace { 17 | 18 | ezio::ScopedSocket::Handle CreateSocket() 19 | { 20 | auto sock = socket(AF_INET, SOCK_STREAM, 0); 21 | REQUIRE(ezio::SocketTraits::IsValid(sock)); 22 | return sock; 23 | } 24 | 25 | } // namespace 26 | 27 | namespace ezio { 28 | 29 | TEST_CASE("Null valid and auto-close", "[ScopedSocket]") 30 | { 31 | #if defined(OS_WIN) 32 | WinsockContext winsock_ctx; 33 | #endif 34 | 35 | ScopedSocket sock; 36 | REQUIRE(!static_cast(sock)); 37 | 38 | sock.reset(CreateSocket()); 39 | REQUIRE(static_cast(sock)); 40 | 41 | sock = nullptr; 42 | REQUIRE(!static_cast(sock)); 43 | } 44 | 45 | } // namespace ezio 46 | -------------------------------------------------------------------------------- /tests/socket_address_unittest.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include "catch2/catch.hpp" 6 | 7 | #include 8 | 9 | #include "ezio/socket_address.h" 10 | 11 | namespace { 12 | 13 | bool operator==(const sockaddr_in& lhs, const sockaddr_in& rhs) 14 | { 15 | return lhs.sin_family == rhs.sin_family && 16 | lhs.sin_port == rhs.sin_port && 17 | lhs.sin_addr.s_addr == rhs.sin_addr.s_addr; 18 | } 19 | 20 | } // namespace 21 | 22 | namespace ezio { 23 | 24 | TEST_CASE("SocketAddress represents the abstraction of sockaddr_in", "[SocketAddress]") 25 | { 26 | SECTION("create from port only") 27 | { 28 | SocketAddress addr(1234); 29 | REQUIRE(1234 == addr.port()); 30 | REQUIRE("0.0.0.0:1234" == addr.ToHostPort()); 31 | } 32 | 33 | SECTION("create from ip address and port") 34 | { 35 | SocketAddress addr("127.0.0.1", 8888); 36 | REQUIRE(8888 == addr.port()); 37 | REQUIRE("127.0.0.1:8888" == addr.ToHostPort()); 38 | } 39 | 40 | SECTION("create from raw sockaddr") 41 | { 42 | sockaddr_in saddr; 43 | memset(&saddr, 0, sizeof(saddr)); 44 | 45 | saddr.sin_family = AF_INET; 46 | saddr.sin_port = HostToNetwork(static_cast(9876)); 47 | saddr.sin_addr.s_addr = HostToNetwork(INADDR_LOOPBACK); 48 | 49 | SocketAddress addr(saddr); 50 | REQUIRE(9876 == addr.port()); 51 | REQUIRE("127.0.0.1:9876" == addr.ToHostPort()); 52 | } 53 | } 54 | 55 | TEST_CASE("Copy and move SocketAddress instances", "[SocketAddress]") 56 | { 57 | SECTION("Copy from a SocketAddress") 58 | { 59 | SocketAddress addr_x(1234); 60 | SocketAddress addr_y(addr_x); 61 | REQUIRE(addr_x.ToHostPort() == addr_y.ToHostPort()); 62 | REQUIRE((addr_x.raw() == addr_y.raw())); 63 | 64 | SocketAddress addr_z("127.0.0.1", 9876); 65 | REQUIRE_FALSE((addr_z.raw() == addr_x.raw())); 66 | 67 | addr_z = addr_y; 68 | REQUIRE(addr_z.ToHostPort() == addr_x.ToHostPort()); 69 | REQUIRE((addr_z.raw() == addr_x.raw())); 70 | } 71 | 72 | SECTION("Move from a SocketAddress") 73 | { 74 | SocketAddress addr_x(SocketAddress("127.0.0.1", 9876)); 75 | REQUIRE(addr_x.port() == 9876); 76 | REQUIRE(addr_x.ToHostPort() == "127.0.0.1:9876"); 77 | 78 | SocketAddress addr_y(1234); 79 | addr_y = std::move(addr_x); 80 | REQUIRE(addr_y.port() == 9876); 81 | REQUIRE(addr_y.ToHostPort() == "127.0.0.1:9876"); 82 | } 83 | } 84 | 85 | } // namespace ezio 86 | -------------------------------------------------------------------------------- /tests/tcp_server_and_connection.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include "catch2/catch.hpp" 6 | 7 | #include "kbase/at_exit_manager.h" 8 | #include "kbase/md5.h" 9 | 10 | #include "ezio/io_service_context.h" 11 | #include "ezio/event_loop.h" 12 | #include "ezio/tcp_server.h" 13 | #include 14 | 15 | namespace { 16 | 17 | using namespace ezio; 18 | 19 | void OnConnection(const TCPConnectionPtr& conn) 20 | { 21 | const char* state = conn->connected() ? "connected" : "disconnected"; 22 | printf("Connection %s is %s\n", conn->peer_addr().ToHostPort().c_str(), state); 23 | } 24 | 25 | } // namespace 26 | 27 | namespace ezio { 28 | 29 | TEST_CASE("Accept and read", "[TCPServer]") 30 | { 31 | kbase::AtExitManager exit_manager; 32 | IOServiceContext::Init(); 33 | 34 | SocketAddress addr(9876); 35 | 36 | EventLoop loop; 37 | 38 | TCPServer server(&loop, addr, "Discarder"); 39 | 40 | server.set_on_connect(&OnConnection); 41 | server.set_on_disconnect(&OnConnection); 42 | server.set_on_message([](const TCPConnectionPtr& conn, Buffer& buf, TimePoint ts) { 43 | // Assume no message decoding needed 44 | auto msg = buf.ReadAllAsString(); 45 | if (msg.find("[quit]") != std::string::npos) { 46 | printf("bye-bye\n"); 47 | EventLoop::current()->Quit(); 48 | } 49 | 50 | printf("%" PRId64 " msg from %s: %s\n", ts.time_since_epoch().count(), 51 | conn->peer_addr().ToHostPort().c_str(), msg.c_str()); 52 | }); 53 | 54 | server.Start(); 55 | printf("%s is running at %s\n", server.name().c_str(), server.ip_port().c_str()); 56 | 57 | loop.Run(); 58 | } 59 | 60 | TEST_CASE("Echo", "[TCPServer]") 61 | { 62 | kbase::AtExitManager exit_manager; 63 | IOServiceContext::Init(); 64 | 65 | EventLoop loop; 66 | 67 | SocketAddress addr(9876); 68 | TCPServer server(&loop, addr, "Echo"); 69 | 70 | server.set_on_connect(&OnConnection); 71 | server.set_on_disconnect(&OnConnection); 72 | server.set_on_message([](const TCPConnectionPtr& conn, Buffer& buf, TimePoint ts) { 73 | auto msg = buf.ReadAllAsString(); 74 | if (msg.find("[quit]") != std::string::npos) { 75 | conn->Shutdown(); 76 | return; 77 | } 78 | 79 | if (msg.find("[poweroff]") != std::string::npos) { 80 | printf("bye-bye\n"); 81 | EventLoop::current()->Quit(); 82 | return; 83 | } 84 | 85 | printf("%" PRId64 " [%s]: %s\n" , ts.time_since_epoch().count(), conn->name().c_str(), msg.c_str()); 86 | conn->Send(msg); 87 | }); 88 | 89 | server.Start(); 90 | printf("%s is running at %s\n", server.name().c_str(), server.ip_port().c_str()); 91 | 92 | loop.Run(); 93 | } 94 | 95 | TEST_CASE("Server with worker pool", "[TCPServer]") 96 | { 97 | kbase::AtExitManager exit_manager; 98 | IOServiceContext::Init(); 99 | 100 | EventLoop loop; 101 | 102 | SocketAddress addr(9876); 103 | TCPServer server(&loop, addr, "MT-Echo"); 104 | 105 | auto connection_handler = [](const TCPConnectionPtr& conn) { 106 | const char* state = conn->connected() ? "connected" : "disconnected"; 107 | printf("Connection %s is %s on%s\n", conn->peer_addr().ToHostPort().c_str(), state, 108 | this_thread::GetName()); 109 | }; 110 | 111 | server.set_on_connect(connection_handler); 112 | server.set_on_disconnect(connection_handler); 113 | 114 | server.set_on_message([main_loop = &loop](const TCPConnectionPtr& conn, Buffer& buf, 115 | TimePoint) { 116 | auto msg = buf.ReadAllAsString(); 117 | if (msg.find("[quit]") != std::string::npos) { 118 | conn->Shutdown(); 119 | return; 120 | } 121 | 122 | if (msg.find("[poweroff]") != std::string::npos) { 123 | printf("bye-bye\n"); 124 | main_loop->Quit(); 125 | return; 126 | } 127 | 128 | printf("[%s:%s]: %s", conn->name().c_str(), this_thread::GetName(), msg.c_str()); 129 | conn->Send(msg); 130 | }); 131 | 132 | TCPServer::Options opt; 133 | opt.worker_num = 4; 134 | server.Start(opt); 135 | 136 | printf("%s is running at %s on %s\n", server.name().c_str(), server.ip_port().c_str(), 137 | this_thread::GetName()); 138 | 139 | loop.Run(); 140 | } 141 | 142 | TEST_CASE("Large data transfer", "[TCPServer]") 143 | { 144 | kbase::AtExitManager exit_manager; 145 | IOServiceContext::Init(); 146 | 147 | EventLoop loop; 148 | 149 | SocketAddress addr(9876); 150 | TCPServer server(&loop, addr, "DataTransfer"); 151 | 152 | server.set_on_connect(&OnConnection); 153 | 154 | size_t message_len = 0; 155 | server.set_on_message([&message_len](const TCPConnectionPtr& conn, Buffer& buf, TimePoint) { 156 | if (message_len == 0 && buf.readable_size() >= sizeof(message_len)) { 157 | message_len = buf.ReadAs(); 158 | printf("msg-len: %zu\n", message_len); 159 | } 160 | 161 | if (buf.readable_size() >= message_len) { 162 | auto msg = buf.ReadAsString(message_len); 163 | printf("full-message read %zu\n", msg.size()); 164 | auto pin = kbase::MD5String(msg); 165 | printf("hash: %s\n", pin.c_str()); 166 | conn->Send(pin); 167 | // Clear. 168 | message_len = 0; 169 | } 170 | }); 171 | 172 | server.Start(); 173 | 174 | printf("DataTransfer server is about ro serve at %s\n", server.ip_port().c_str()); 175 | 176 | loop.Run(); 177 | } 178 | 179 | } // namespace ezio 180 | -------------------------------------------------------------------------------- /tests/thread_and_worker_pool.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include "catch2/catch.hpp" 6 | 7 | #include "ezio/thread.h" 8 | 9 | #include 10 | #include 11 | 12 | #include "kbase/at_exit_manager.h" 13 | 14 | #include "ezio/event_loop.h" 15 | #include "ezio/io_service_context.h" 16 | #include "ezio/this_thread.h" 17 | #include "ezio/worker_pool.h" 18 | 19 | namespace ezio { 20 | 21 | TEST_CASE("Thread is native thread running EventLoop", "[Thread]") 22 | { 23 | printf("Main thread %u\n", this_thread::GetID()); 24 | 25 | Thread th("Worker-101"); 26 | th.event_loop()->RunTask([&th] { 27 | printf("%s thread %u\n", th.name().c_str(), this_thread::GetID()); 28 | }); 29 | 30 | th.event_loop()->RunTaskAfter([] { 31 | printf("timed beep!\n"); 32 | }, std::chrono::seconds(3)); 33 | 34 | printf("Main thread gets sleep for 5s\n"); 35 | std::this_thread::sleep_for(std::chrono::seconds(5)); 36 | } 37 | 38 | TEST_CASE("EventLoop of a Thread quits from outside", "[Thread]") 39 | { 40 | printf("Main thread %u\n", this_thread::GetID()); 41 | 42 | Thread th("Worker-101"); 43 | th.event_loop()->RunTask([&th] { 44 | printf("%s thread %u\n", th.name().c_str(), this_thread::GetID()); 45 | }); 46 | 47 | // Make sure worker thread runs before we call Quit(). 48 | std::this_thread::sleep_for(std::chrono::seconds(2)); 49 | 50 | // Danger!! 51 | // DO NOT DO THIS IN PRODUCTION CODE. 52 | th.event_loop()->Quit(); 53 | 54 | // Simulate busy working on main thread, ensure Quit() takes effect before `th` goes 55 | // destruction. 56 | std::this_thread::sleep_for(std::chrono::seconds(2)); 57 | } 58 | 59 | TEST_CASE("WorkerPool runs several worker threads", "[WorkerPool]") 60 | { 61 | kbase::AtExitManager exit_manager; 62 | IOServiceContext::Init(); 63 | 64 | // We don't even need to run the main-loop. 65 | EventLoop main; 66 | 67 | WorkerPool pool(&main, 3, "Batman"); 68 | 69 | for (int i = 0; i < 3; ++i) { 70 | pool.GetNextEventLoop()->RunTask([] { 71 | printf("%s %u\n", this_thread::GetName(), this_thread::GetID()); 72 | }); 73 | } 74 | 75 | printf("%s %u\n", this_thread::GetName(), this_thread::GetID()); 76 | } 77 | 78 | } // namespace ezio 79 | -------------------------------------------------------------------------------- /tests/winsock_context_unittest.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | @ 0xCCCCCCCC 3 | */ 4 | 5 | #include "catch2/catch.hpp" 6 | 7 | #include "ezio/winsock_context.h" 8 | 9 | namespace ezio { 10 | 11 | TEST_CASE("General usages", "[WinsockContext]") 12 | { 13 | WinsockContext winsock_ctx; 14 | REQUIRE(winsock_ctx.AcceptEx != nullptr); 15 | } 16 | 17 | } // namespace ezio 18 | --------------------------------------------------------------------------------