├── .clang-tidy-ignore ├── vcpkg.json ├── test ├── main.cpp ├── CMakeLists.txt ├── test_AddrLookup.cpp ├── MockSocketCore.h ├── test_TcpServer.cpp ├── test_TcpClient.cpp └── test_UdpSocket.cpp ├── scripts └── ci_setup_clang.sh ├── ci ├── gcc1311.yml ├── asan.yml ├── tsan.yml ├── ubsan.yml ├── gcc1311rel.yml ├── clang16.yml ├── gcc840.yml ├── gcc940.yml ├── gcc1030.yml ├── gcc1130.yml ├── gcc1230.yml ├── clang16rel.yml └── pipeline.yml ├── .gitignore ├── .clang-tidy ├── include └── sockets-cpp │ ├── SocketCommon.h │ ├── AddrLookup.h │ ├── SocketCore.h │ ├── TcpClient.h │ ├── UdpSocket.h │ └── TcpServer.h ├── examples ├── CMakeLists.txt ├── mcastApp.cpp ├── getopt.h ├── clientApp.cpp ├── unicastApp.cpp ├── serverApp.cpp └── getopt.cpp ├── .vscode ├── c_cpp_properties.json └── settings.json ├── .clang-format ├── README.md ├── .gitea └── workflows │ └── ci.yml ├── .github └── workflows │ └── ci.yml ├── CMake └── CodeCoverage.cmake ├── CMakeLists.txt └── LICENSE /.clang-tidy-ignore: -------------------------------------------------------------------------------- 1 | test/* 2 | build/* 3 | -------------------------------------------------------------------------------- /vcpkg.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": [ 3 | "gtest", 4 | "fmt" 5 | ] 6 | } -------------------------------------------------------------------------------- /test/main.cpp: -------------------------------------------------------------------------------- 1 | #include "gtest/gtest.h" 2 | 3 | int main(int argc, char**argv) 4 | { 5 | ::testing::InitGoogleTest(&argc,argv); 6 | return RUN_ALL_TESTS(); 7 | } -------------------------------------------------------------------------------- /scripts/ci_setup_clang.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ex 4 | 5 | VERSION=$1 6 | 7 | apt-get update 8 | apt-get install -y libc++-${VERSION}-dev libc++abi-${VERSION}-dev 9 | 10 | if [[ "${VERSION}" -ge 12 ]]; then 11 | apt-get install -y --no-install-recommends libunwind-${VERSION}-dev 12 | fi -------------------------------------------------------------------------------- /ci/gcc1311.yml: -------------------------------------------------------------------------------- 1 | --- 2 | platform: linux 3 | image_resource: 4 | type: registry-image 5 | source: { repository: fir.love.io:3005/amd64/sockets-cpp-gcc1311 } 6 | 7 | inputs: 8 | - name: sockets-cpp-git 9 | 10 | run: 11 | path: ./sockets-cpp-git/build.sh 12 | args: [ "--concourse", "-builddir=build" ] -------------------------------------------------------------------------------- /ci/asan.yml: -------------------------------------------------------------------------------- 1 | --- 2 | platform: linux 3 | image_resource: 4 | type: registry-image 5 | source: { repository: fir.love.io:3005/amd64/sockets-cpp-gcc1311 } 6 | 7 | inputs: 8 | - name: sockets-cpp-git 9 | 10 | run: 11 | path: ./sockets-cpp-git/build.sh 12 | args: [ "--concourse", "-builddir=build", "--cmake=-DCMAKE_BUILD_TYPE=asan" ] -------------------------------------------------------------------------------- /ci/tsan.yml: -------------------------------------------------------------------------------- 1 | --- 2 | platform: linux 3 | image_resource: 4 | type: registry-image 5 | source: { repository: fir.love.io:3005/amd64/sockets-cpp-gcc1311 } 6 | 7 | inputs: 8 | - name: sockets-cpp-git 9 | 10 | run: 11 | path: ./sockets-cpp-git/build.sh 12 | args: [ "--concourse", "-builddir=build", "--cmake=-DCMAKE_BUILD_TYPE=tsan" ] -------------------------------------------------------------------------------- /ci/ubsan.yml: -------------------------------------------------------------------------------- 1 | --- 2 | platform: linux 3 | image_resource: 4 | type: registry-image 5 | source: { repository: fir.love.io:3005/amd64/sockets-cpp-gcc1311 } 6 | 7 | inputs: 8 | - name: sockets-cpp-git 9 | 10 | run: 11 | path: ./sockets-cpp-git/build.sh 12 | args: [ "--concourse", "-builddir=build", "--cmake=-DCMAKE_BUILD_TYPE=ubsan" ] -------------------------------------------------------------------------------- /ci/gcc1311rel.yml: -------------------------------------------------------------------------------- 1 | --- 2 | platform: linux 3 | image_resource: 4 | type: registry-image 5 | source: { repository: fir.love.io:3005/amd64/sockets-cpp-gcc1311 } 6 | 7 | inputs: 8 | - name: sockets-cpp-git 9 | 10 | run: 11 | path: ./sockets-cpp-git/build.sh 12 | args: [ "--concourse", "-builddir=build", "--cmake=-DCMAKE_BUILD_TYPE=Release" ] -------------------------------------------------------------------------------- /ci/clang16.yml: -------------------------------------------------------------------------------- 1 | --- 2 | platform: linux 3 | image_resource: 4 | type: registry-image 5 | source: { repository: fir.love.io:3005/amd64/sockets-cpp-gcc1311 } 6 | 7 | inputs: 8 | - name: sockets-cpp-git 9 | 10 | run: 11 | path: ./sockets-cpp-git/build.sh 12 | args: [ "--concourse", "-builddir=build", "-cc=/usr/bin/clang", "-cxx=/usr/bin/clang++" ] -------------------------------------------------------------------------------- /ci/gcc840.yml: -------------------------------------------------------------------------------- 1 | --- 2 | platform: linux 3 | image_resource: 4 | type: registry-image 5 | source: { repository: fir.love.io:3005/amd64/sockets-cpp-gcc840 } 6 | 7 | inputs: 8 | - name: sockets-cpp-git 9 | 10 | run: 11 | path: ./sockets-cpp-git/build.sh 12 | args: [ "--concourse", "-builddir=build", "-cc=/opt/gcc840/bin/gcc", "-cxx=/opt/gcc840/bin/g++" ] -------------------------------------------------------------------------------- /ci/gcc940.yml: -------------------------------------------------------------------------------- 1 | --- 2 | platform: linux 3 | image_resource: 4 | type: registry-image 5 | source: { repository: fir.love.io:3005/amd64/sockets-cpp-gcc940 } 6 | 7 | inputs: 8 | - name: sockets-cpp-git 9 | 10 | run: 11 | path: ./sockets-cpp-git/build.sh 12 | args: [ "--concourse", "-builddir=build", "-cc=/opt/gcc940/bin/gcc", "-cxx=/opt/gcc940/bin/g++" ] -------------------------------------------------------------------------------- /ci/gcc1030.yml: -------------------------------------------------------------------------------- 1 | --- 2 | platform: linux 3 | image_resource: 4 | type: registry-image 5 | source: { repository: fir.love.io:3005/amd64/sockets-cpp-gcc1030 } 6 | 7 | inputs: 8 | - name: sockets-cpp-git 9 | 10 | run: 11 | path: ./sockets-cpp-git/build.sh 12 | args: [ "--concourse", "-builddir=build", "-cc=/opt/gcc1030/bin/gcc", "-cxx=/opt/gcc1030/bin/g++" ] -------------------------------------------------------------------------------- /ci/gcc1130.yml: -------------------------------------------------------------------------------- 1 | --- 2 | platform: linux 3 | image_resource: 4 | type: registry-image 5 | source: { repository: fir.love.io:3005/amd64/sockets-cpp-gcc1130 } 6 | 7 | inputs: 8 | - name: sockets-cpp-git 9 | 10 | run: 11 | path: ./sockets-cpp-git/build.sh 12 | args: [ "--concourse", "-builddir=build", "-cc=/opt/gcc1130/bin/gcc", "-cxx=/opt/gcc1130/bin/g++" ] -------------------------------------------------------------------------------- /ci/gcc1230.yml: -------------------------------------------------------------------------------- 1 | --- 2 | platform: linux 3 | image_resource: 4 | type: registry-image 5 | source: { repository: fir.love.io:3005/amd64/sockets-cpp-gcc1230 } 6 | 7 | inputs: 8 | - name: sockets-cpp-git 9 | 10 | run: 11 | path: ./sockets-cpp-git/build.sh 12 | args: [ "--concourse", "-builddir=build", "-cc=/opt/gcc1230/bin/gcc", "-cxx=/opt/gcc1230/bin/g++" ] -------------------------------------------------------------------------------- /ci/clang16rel.yml: -------------------------------------------------------------------------------- 1 | --- 2 | platform: linux 3 | image_resource: 4 | type: registry-image 5 | source: { repository: fir.love.io:3005/amd64/sockets-cpp-gcc1311 } 6 | 7 | inputs: 8 | - name: sockets-cpp-git 9 | 10 | run: 11 | path: ./sockets-cpp-git/build.sh 12 | args: [ "--concourse", "-builddir=build", "-cc=/usr/bin/clang", "-cxx=/usr/bin/clang++", "--cmake=-DCMAKE_BUILD_TYPE=Release" ] -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ---> C++ 2 | build 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | 22 | # Compiled Static libraries 23 | *.lai 24 | *.la 25 | *.a 26 | *.lib 27 | 28 | # Executables 29 | *.exe 30 | *.out 31 | *.app 32 | 33 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: '*,-google-runtime-references,-fuchsia*,-abseil*,-readability-else-after-return,-readability-avoid-const-params-in-decls,-hicpp-signed-bitwise,-cppcoreguidelines-pro-bounds-constant-array-index,-cppcoreguidelines-pro-bounds-pointer-arithmetic,-cppcoreguidelines-pro-type-reinterpret-cast,-llvm-header-guard,-readability-static-accessed-through-instance,-google-readability-todo,-clang-diagnostic*,-gsl*,-cppcoreguidelines*,-modernize-use-trailing-return-type,-llvm*,-hicpp*,-misc*,-altera*,-readability-function-cognitive*,-android*,-bugprone-easily-swappable-parameters,-readability-convert-member-functions-to-static,-readability-identify-length' 3 | WarningsAsErrors: '*' 4 | HeaderFilterRegex: '.*' 5 | AnalyzeTemporaryDtors: false 6 | FormatStyle: none 7 | ... 8 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.17) 2 | 3 | project (socketTests) 4 | 5 | enable_testing() 6 | 7 | # Disable clang-tidy checks for unit test code 8 | set(CMAKE_CXX_CLANG_TIDY "") 9 | 10 | add_definitions( -DGTEST_LINKED_AS_SHARED_LIBRARY ) 11 | 12 | if (BUILD_COVERAGE) 13 | add_definitions( --coverage ) 14 | endif (BUILD_COVERAGE) 15 | 16 | include_directories( 17 | ${gtest_SOURCE_DIR}/include ${gtest_SOURCE_DIR} 18 | ${CMAKE_SOURCE_DIR}/include/sockets-cpp 19 | ${CMAKE_SOURCE_DIR}/src 20 | ${CMAKE_SOURCE_DIR}/test 21 | ) 22 | 23 | set ( socketTests_SRC 24 | main.cpp 25 | test_AddrLookup.cpp 26 | test_UdpSocket.cpp 27 | test_TcpClient.cpp 28 | test_TcpServer.cpp 29 | ) 30 | 31 | add_executable ( socketTests ${socketTests_SRC} ) 32 | 33 | if (FMT_SUPPORT) 34 | target_link_libraries( socketTests PUBLIC fmt::fmt ) 35 | endif(FMT_SUPPORT) 36 | target_link_libraries( socketTests PUBLIC GTest::gtest GTest::gmock Threads::Threads ) 37 | 38 | 39 | if (BUILD_COVERAGE) 40 | target_link_libraries( socketTests gcov ) 41 | endif (BUILD_COVERAGE) 42 | 43 | add_test (socketTests socketTests ) 44 | -------------------------------------------------------------------------------- /include/sockets-cpp/SocketCommon.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | namespace sockets { 6 | /** 7 | * @brief Default values for SO_SNDBUF and SO_RCVBUF 8 | * 9 | */ 10 | constexpr int TX_BUFFER_SIZE = 10240; 11 | constexpr int RX_BUFFER_SIZE = 10240; 12 | 13 | /** 14 | * @brief Status structure returned by socket class methods. 15 | * 16 | */ 17 | struct SocketRet { 18 | /** 19 | * @brief Error message text 20 | */ 21 | std::string m_msg; 22 | 23 | /** 24 | * @brief Indication of whether the operation succeeded or failed 25 | */ 26 | bool m_success = false; 27 | }; 28 | 29 | 30 | /** 31 | * @brief Socket options to be used. 32 | * 33 | */ 34 | struct SocketOpt { 35 | 36 | 37 | /** 38 | * @brief Value passed to setsockopt(SO_SNDBUF) 39 | * 40 | */ 41 | int m_txBufSize = TX_BUFFER_SIZE; 42 | 43 | /** 44 | * @brief Value passed to setsockopt(SO_RCVBUF) 45 | * 46 | */ 47 | int m_rxBufSize = RX_BUFFER_SIZE; 48 | 49 | /** 50 | * @brief Socket listen address 51 | * 52 | */ 53 | std::string m_listenAddr = "0.0.0.0"; 54 | 55 | }; 56 | 57 | } // Namespace sockets -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.17) 2 | 3 | project(examples) 4 | 5 | include_directories( 6 | ${CMAKE_SOURCE_DIR}/include 7 | ${CMAKE_SOURCE_DIR}/include/sockets-cpp 8 | . 9 | ) 10 | 11 | set (getopt_SRC 12 | ) 13 | if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows" ) 14 | set(getopt_SRC getopt.cpp) 15 | endif() 16 | 17 | set (clientApp_SRC clientApp.cpp) 18 | add_executable( clientApp ${clientApp_SRC} ${getopt_SRC} ) 19 | if (FMT_SUPPORT) 20 | target_link_libraries( clientApp PUBLIC fmt::fmt ) 21 | endif(FMT_SUPPORT) 22 | target_link_libraries( clientApp PUBLIC Threads::Threads ) 23 | 24 | set (serverApp_SRC serverApp.cpp) 25 | add_executable( serverApp ${serverApp_SRC} ${getopt_SRC} ) 26 | if (FMT_SUPPORT) 27 | target_link_libraries( serverApp PUBLIC fmt::fmt ) 28 | endif(FMT_SUPPORT) 29 | target_link_libraries( serverApp PUBLIC Threads::Threads ) 30 | 31 | set (mcastApp_SRC mcastApp.cpp) 32 | add_executable( mcastApp ${mcastApp_SRC} ${getopt_SRC} ) 33 | if (FMT_SUPPORT) 34 | target_link_libraries( mcastApp PUBLIC fmt::fmt ) 35 | endif(FMT_SUPPORT) 36 | target_link_libraries( mcastApp PUBLIC Threads::Threads ) 37 | 38 | set (unicastApp_SRC unicastApp.cpp) 39 | add_executable( unicastApp ${unicastApp_SRC} ${getopt_SRC} ) 40 | if (FMT_SUPPORT) 41 | target_link_libraries( unicastApp PUBLIC fmt::fmt ) 42 | endif(FMT_SUPPORT) 43 | target_link_libraries( unicastApp PUBLIC Threads::Threads ) -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Linux", 5 | "includePath": [ 6 | "${workspaceFolder}/include", 7 | "${workspaceFolder}/include/sockets-cpp", 8 | "${workspaceFolder}/test", 9 | "${workspaceFolder}/examples" 10 | ], 11 | "defines": [ "FMT_SUPPORT" ], 12 | "compilerPath": "/usr/bin/g++", 13 | "cStandard": "c11", 14 | "cppStandard": "c++14", 15 | "configurationProvider": "vector-of-bool.cmake-tools", 16 | "compileCommands": "${workspaceFolder}/build/compile_commands.json" 17 | }, 18 | { 19 | "name": "Win32", 20 | "includePath": [ 21 | "${workspaceFolder}/include", 22 | "${workspaceFolder}/include/sockets-cpp", 23 | "${workspaceFolder}/test", 24 | "${workspaceFolder}/examples" 25 | ], 26 | "defines": [ "_WIN32" ], 27 | "compilerPath": "C:/Program Files/Microsoft Visual Studio/2022/Community/Common7/IDE/CommonExtensions/Microsoft/CMake/CMake/bin/cmake.exe", 28 | "cStandard": "c11", 29 | "cppStandard": "c++14", 30 | "configurationProvider": "vector-of-bool.cmake-tools", 31 | "compileCommands": "${workspaceFolder}/build/compile_commands.json" 32 | } 33 | ], 34 | "version": 4 35 | } -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: WebKit 3 | AccessModifierOffset: -4 4 | ConstructorInitializerIndentWidth: 4 5 | AlignEscapedNewlinesLeft: true 6 | AlignTrailingComments: true 7 | AllowAllParametersOfDeclarationOnNextLine: true 8 | AllowShortFunctionsOnASingleLine: false 9 | # AllowShortBlocksOnASingleLine: SBS_Never 10 | AllowShortIfStatementsOnASingleLine: false 11 | AllowShortLoopsOnASingleLine: false 12 | AlwaysBreakTemplateDeclarations: true 13 | AlwaysBreakBeforeMultilineStrings: true 14 | BreakBeforeBinaryOperators: false 15 | BreakBeforeTernaryOperators: true 16 | BreakConstructorInitializersBeforeComma: false 17 | BinPackParameters: true 18 | ColumnLimit: 128 19 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 20 | DerivePointerBinding: true 21 | ExperimentalAutoDetectBinPacking: false 22 | IndentCaseLabels: false 23 | MaxEmptyLinesToKeep: 1 24 | NamespaceIndentation: None 25 | ObjCSpaceBeforeProtocolList: false 26 | PenaltyBreakBeforeFirstCallParameter: 1 27 | PenaltyBreakComment: 60 28 | PenaltyBreakString: 1000 29 | PenaltyBreakFirstLessLess: 120 30 | PenaltyExcessCharacter: 1000000 31 | PenaltyReturnTypeOnItsOwnLine: 200 32 | PointerBindsToType: true 33 | SpacesBeforeTrailingComments: 2 34 | Cpp11BracedListStyle: true 35 | Standard: Cpp11 36 | IndentWidth: 4 37 | TabWidth: 4 38 | UseTab: Never 39 | BreakBeforeBraces: Attach 40 | IndentFunctionDeclarationAfterType: true 41 | SpacesInParentheses: false 42 | SpacesInAngles: false 43 | SpaceInEmptyParentheses: false 44 | SpacesInCStyleCastParentheses: false 45 | SpaceAfterControlStatementKeyword: true 46 | SpaceBeforeAssignmentOperators: true 47 | ContinuationIndentWidth: 4 48 | ... 49 | 50 | -------------------------------------------------------------------------------- /ci/pipeline.yml: -------------------------------------------------------------------------------- 1 | --- 2 | resources: 3 | - name: sockets-cpp-git 4 | type: git 5 | icon: github 6 | source: 7 | uri: http://fir.love.io:3000/CJLove/sockets-cpp 8 | 9 | jobs: 10 | - name: set-pipeline 11 | public: true 12 | plan: 13 | - get: sockets-cpp-git 14 | trigger: true 15 | - set_pipeline: sockets-cpp 16 | file: sockets-cpp-git/ci/pipeline.yml 17 | - name: build-matrix 18 | public: true 19 | plan: 20 | - get: sockets-cpp-git 21 | passed: [ set-pipeline ] 22 | trigger: true 23 | - in_parallel: 24 | - task: gcc13_1_1 25 | file: sockets-cpp-git/ci/gcc1311.yml 26 | - task: gcc12_3_0 27 | file: sockets-cpp-git/ci/gcc1230.yml 28 | - task: gcc11_3_0 29 | file: sockets-cpp-git/ci/gcc1130.yml 30 | - task: gcc10_3_0 31 | file: sockets-cpp-git/ci/gcc1030.yml 32 | - task: gcc9_4_0 33 | file: sockets-cpp-git/ci/gcc940.yml 34 | - task: gcc8_4_0 35 | file: sockets-cpp-git/ci/gcc840.yml 36 | - task: clang16 37 | file: sockets-cpp-git/ci/clang16.yml 38 | 39 | - name: release 40 | public: true 41 | plan: 42 | - get: sockets-cpp-git 43 | passed: [ build-matrix ] 44 | trigger: true 45 | - in_parallel: 46 | - task: gcc_13_1_1_rel 47 | file: sockets-cpp-git/ci/gcc1311rel.yml 48 | - task: clang16_rel 49 | file: sockets-cpp-git/ci/clang16rel.yml 50 | 51 | - name: sanitizers 52 | public: true 53 | plan: 54 | - get: sockets-cpp-git 55 | passed: [ build-matrix ] 56 | trigger: true 57 | - in_parallel: 58 | - task: asan 59 | privileged: true 60 | file: sockets-cpp-git/ci/asan.yml 61 | - task: tsan 62 | privileged: true 63 | file: sockets-cpp-git/ci/tsan.yml 64 | - task: ubsan 65 | privileged: true 66 | file: sockets-cpp-git/ci/ubsan.yml 67 | 68 | 69 | -------------------------------------------------------------------------------- /include/sockets-cpp/AddrLookup.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "SocketCore.h" 3 | #ifdef _WIN32 4 | #ifndef WIN32_LEAN_AND_MEAN 5 | #define WIN32_LEAN_AND_MEAN 6 | #endif // WIN32_LEAN_AND_MEAN 7 | #include 8 | #include 9 | #include 10 | #include 11 | #else 12 | #include 13 | #include 14 | #include 15 | #endif 16 | #include 17 | #include 18 | 19 | namespace sockets { 20 | 21 | template 22 | class AddrLookup { 23 | public: 24 | AddrLookup() = default; 25 | 26 | explicit AddrLookup(SocketImpl &impl): m_socketCore(impl) 27 | {} 28 | 29 | ~AddrLookup() = default; 30 | 31 | /** 32 | * @brief Return the first IPV4 hostname for a hostname 33 | * 34 | * @param host - hostname to resolve 35 | * @param addr - IPV4 address 36 | * @return int - 0 indicates success, non-zero indicates failure 37 | */ 38 | int lookupHost(const char *host, in_addr_t &addr) { 39 | struct addrinfo hints; 40 | struct addrinfo *res = nullptr; 41 | struct addrinfo *result = nullptr; 42 | int errcode = 0; 43 | 44 | memset(&hints, 0, sizeof(hints)); 45 | hints.ai_family = AF_INET; 46 | 47 | errcode = m_socketCore.GetAddrInfo(host, nullptr, &hints, &result); 48 | if (errcode != 0) { 49 | return -1; 50 | } 51 | 52 | res = result; 53 | 54 | if (res != nullptr) { 55 | addr = reinterpret_cast(res->ai_addr)->sin_addr.s_addr; 56 | } else { 57 | errcode = -1; 58 | } 59 | 60 | m_socketCore.FreeAddrInfo(result); 61 | 62 | return errcode; 63 | } 64 | 65 | private: 66 | SocketImpl &m_socketCore; 67 | }; 68 | 69 | } // namespace sockets -------------------------------------------------------------------------------- /test/test_AddrLookup.cpp: -------------------------------------------------------------------------------- 1 | #include "AddrLookup.h" 2 | #include "MockSocketCore.h" 3 | #include "gtest/gtest.h" 4 | #include "gmock/gmock.h" 5 | 6 | using ::testing::AtLeast; 7 | using ::testing::Return; 8 | using ::testing::NotNull; 9 | using ::testing::_; 10 | using ::testing::SetArgPointee; 11 | using ::testing::DoAll; 12 | 13 | TEST(AddrLookup, badhost) 14 | { 15 | const char *name = "badhost"; 16 | in_addr_t addr {}; 17 | MockSocketCore core; 18 | sockets::AddrLookup lookup(core); 19 | EXPECT_CALL(core, GetAddrInfo(name,_, NotNull(),NotNull())).WillOnce(Return(-1)); 20 | EXPECT_EQ(-1, lookup.lookupHost(name,addr)); 21 | } 22 | 23 | TEST(AddrLookup, localhost) 24 | { 25 | const char *name = "localhost"; 26 | in_addr_t addr { }; 27 | in_addr_t expected { 0x100007f }; 28 | MockSocketCore core; 29 | struct addrinfo res; 30 | struct sockaddr theAddr = { 0, 31 | #ifdef __APPLE__ 32 | 0, 33 | #endif 34 | "\000\000\177\000\000\001" }; 35 | res.ai_addr = &theAddr; 36 | sockets::AddrLookup lookup(core); 37 | EXPECT_CALL(core, GetAddrInfo(name,_, NotNull(),_)).WillOnce(DoAll(SetArgPointee<3>(&res), Return(0))); 38 | EXPECT_CALL(core, FreeAddrInfo(_)); 39 | EXPECT_EQ(0, lookup.lookupHost(name,addr)); 40 | EXPECT_EQ(expected,addr); 41 | } 42 | 43 | TEST(AddrLookup, ipv4addr) 44 | { 45 | const char *name = "127.0.0.1"; 46 | in_addr_t addr { }; 47 | in_addr_t expected { 0x100007f }; 48 | MockSocketCore core; 49 | struct addrinfo res; 50 | struct sockaddr theAddr = { 0, 51 | #ifdef __APPLE__ 52 | 0, 53 | #endif 54 | "\000\000\177\000\000\001" }; 55 | res.ai_addr = &theAddr; 56 | sockets::AddrLookup lookup(core); 57 | EXPECT_CALL(core, GetAddrInfo(name,_, NotNull(),_)).WillOnce(DoAll(SetArgPointee<3>(&res), Return(0))); 58 | EXPECT_CALL(core, FreeAddrInfo(_)); 59 | EXPECT_EQ(0, lookup.lookupHost(name,addr)); 60 | EXPECT_EQ(expected,addr); 61 | } 62 | 63 | TEST(AddrLookup, badresult) 64 | { 65 | const char *name = "127.0.0.1"; 66 | in_addr_t addr { }; 67 | MockSocketCore core; 68 | sockets::AddrLookup lookup(core); 69 | EXPECT_CALL(core, GetAddrInfo(name,_, NotNull(),_)).WillOnce(DoAll(SetArgPointee<3>(nullptr), Return(0))); 70 | EXPECT_CALL(core, FreeAddrInfo(_)); 71 | EXPECT_EQ(-1, lookup.lookupHost(name,addr)); 72 | } 73 | -------------------------------------------------------------------------------- /examples/mcastApp.cpp: -------------------------------------------------------------------------------- 1 | #include "UdpSocket.h" 2 | #include 3 | #ifdef _WIN32 4 | #include "getopt.h" 5 | #else 6 | #include 7 | #endif 8 | 9 | class McastApp { 10 | public: 11 | // UDP Multicast 12 | McastApp(const char *multicastAddr, uint16_t port, const char *localIface); 13 | 14 | virtual ~McastApp() = default; 15 | 16 | void onReceiveData(const char *data, size_t size); 17 | 18 | void sendMsg(const char *data, size_t len); 19 | 20 | private: 21 | sockets::UdpSocket m_mcast; 22 | }; 23 | 24 | McastApp::McastApp(const char *multicastAddr, uint16_t port, const char *localIface) : m_mcast(*this) { 25 | sockets::SocketRet ret = m_mcast.startMcast(multicastAddr, port, localIface); 26 | if (ret.m_success) { 27 | std::cout << "Connected to mcast group " << multicastAddr << ":" << port << "\n"; 28 | } else { 29 | std::cout << "Error: " << ret.m_msg << "\n"; 30 | exit(1); // NOLINT 31 | } 32 | } 33 | 34 | void McastApp::sendMsg(const char *data, size_t len) { 35 | auto ret = m_mcast.sendMsg(data, len); 36 | if (!ret.m_success) { 37 | std::cout << "Send Error: " << ret.m_msg << "\n"; 38 | } 39 | } 40 | 41 | void McastApp::onReceiveData(const char *data, size_t size) { 42 | std::string str(reinterpret_cast(data), size); 43 | 44 | std::cout << "Received: " << str << "\n"; 45 | } 46 | 47 | void usage() { 48 | std::cout << "McastApp -m -p -[-l ]\n"; 49 | } 50 | 51 | int main(int argc, char **argv) { 52 | int arg = 0; 53 | const char *addr = nullptr; 54 | const char *local = nullptr; 55 | uint16_t port = 0; 56 | while ((arg = getopt(argc, argv, "m:p:l:?")) != EOF) { // NOLINT 57 | switch (arg) { 58 | case 'm': 59 | addr = optarg; 60 | break; 61 | case 'p': 62 | port = static_cast(std::stoul(optarg)); 63 | break; 64 | case 'l': 65 | local = optarg; 66 | break; 67 | case '?': 68 | usage(); 69 | exit(1); // NOLINT 70 | } 71 | } 72 | 73 | auto *app = new McastApp(addr, port, local); 74 | 75 | while (true) { 76 | std::string data; 77 | std::cout << "Data >"; 78 | std::getline(std::cin, data); 79 | if (data == "quit") { 80 | break; 81 | } 82 | app->sendMsg(data.data(), data.size()); 83 | } 84 | 85 | delete app; 86 | } -------------------------------------------------------------------------------- /examples/getopt.h: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2012-2017, Kim Grasman 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * * Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of Kim Grasman nor the 13 | * names of contributors may be used to endorse or promote products 14 | * derived from this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL KIM GRASMAN BE LIABLE FOR ANY 20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | * 27 | ******************************************************************************/ 28 | 29 | #ifndef INCLUDED_GETOPT_PORT_H 30 | #define INCLUDED_GETOPT_PORT_H 31 | 32 | #if defined(__cplusplus) 33 | extern "C" { 34 | #endif 35 | 36 | #define no_argument 1 37 | #define required_argument 2 38 | #define optional_argument 3 39 | 40 | extern char* optarg; 41 | extern int optind, opterr, optopt; 42 | 43 | struct option { 44 | const char* name; 45 | int has_arg; 46 | int* flag; 47 | int val; 48 | }; 49 | 50 | int getopt(int argc, char* const argv[], const char* optstring); 51 | 52 | int getopt_long(int argc, char* const argv[], 53 | const char* optstring, const struct option* longopts, int* longindex); 54 | 55 | #if defined(__cplusplus) 56 | } 57 | #endif 58 | 59 | #endif // INCLUDED_GETOPT_PORT_H -------------------------------------------------------------------------------- /test/MockSocketCore.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "gmock/gmock.h" 3 | #include "gtest/gtest.h" 4 | #ifdef _WIN32 5 | #ifndef WIN32_LEAN_AND_MEAN 6 | #define WIN32_LEAN_AND_MEAN 7 | #endif // WIN32_LEAN_AND_MEAN 8 | #include 9 | #include 10 | #include 11 | #include 12 | // Need to link with Ws2_32.lib, Mswsock.lib, and Advapi32.lib 13 | #pragma comment (lib, "Ws2_32.lib") 14 | #pragma comment (lib, "Mswsock.lib") 15 | #pragma comment (lib, "AdvApi32.lib") 16 | #else 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #endif 23 | 24 | #ifdef _WIN32 25 | using ssize_t = int; 26 | using in_addr_t = ULONG; 27 | #else 28 | using SOCKET = int; 29 | constexpr SOCKET INVALID_SOCKET = -1; 30 | #endif 31 | 32 | /** 33 | * @brief Mock class for socket api calls 34 | * 35 | */ 36 | class MockSocketCore { 37 | public: 38 | MockSocketCore() = default; 39 | 40 | MockSocketCore(const MockSocketCore &) = default; 41 | 42 | ~MockSocketCore() = default; 43 | 44 | int Initialize() { 45 | return 0; 46 | } 47 | 48 | MOCK_METHOD(int, Socket, (int domain, int type, int protocol), ()); 49 | 50 | MOCK_METHOD(int, SetSockOpt, (int sockfd, int level, int optname, void *optval, socklen_t optlen), ()); 51 | 52 | MOCK_METHOD(int, Bind, (int sockfd, const struct sockaddr *addr, socklen_t addrlen), ()); 53 | 54 | MOCK_METHOD(int, Accept, (int sockfd, struct sockaddr *addr, socklen_t *addrlen), ()); 55 | 56 | MOCK_METHOD(int, Listen, (int sockfd, int backlog), ()); 57 | 58 | MOCK_METHOD(int, Connect, (int sockfd, const struct sockaddr *addr, socklen_t addrlen), ()); 59 | 60 | MOCK_METHOD(int, Close, (int sockfd), ()); 61 | 62 | MOCK_METHOD(int, Select, (int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout), ()); 63 | 64 | MOCK_METHOD(ssize_t, Recv, (int sockfd, char *buf, size_t len, int flags), ()); 65 | 66 | MOCK_METHOD(ssize_t, SendTo, 67 | (int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen), ()); 68 | 69 | MOCK_METHOD(ssize_t, Send, (int sockfd, const void *buf, size_t len, int flags), ()); 70 | 71 | MOCK_METHOD(int, GetAddrInfo, (const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res), ()); 72 | 73 | MOCK_METHOD(void, FreeAddrInfo,(struct addrinfo *res), ()); 74 | }; -------------------------------------------------------------------------------- /examples/clientApp.cpp: -------------------------------------------------------------------------------- 1 | #include "TcpClient.h" 2 | #ifdef _WIN32 3 | #include "getopt.h" 4 | #else 5 | #include 6 | #endif 7 | 8 | class ClientApp { 9 | public: 10 | // TCP Client 11 | ClientApp(const char *remoteIp, uint16_t port); 12 | 13 | virtual ~ClientApp() = default; 14 | 15 | void onReceiveData(const char *data, size_t size); 16 | 17 | void onDisconnect(const sockets::SocketRet &ret); 18 | 19 | void sendMsg(const char *data, size_t len); 20 | 21 | private: 22 | sockets::TcpClient m_client; 23 | }; 24 | 25 | ClientApp::ClientApp(const char *remoteIp, uint16_t port) : m_client(*this) { 26 | while (true) { 27 | sockets::SocketRet ret = m_client.connectTo(remoteIp, port); 28 | if (ret.m_success) { 29 | std::cout << "Connected to " << remoteIp << ":" << port << "\n"; 30 | break; 31 | } else { 32 | std::cout << ret.m_msg << "\n"; 33 | exit(1); // NOLINT 34 | } 35 | } 36 | } 37 | 38 | void ClientApp::sendMsg(const char *data, size_t len) { 39 | auto ret = m_client.sendMsg(data, len); 40 | if (!ret.m_success) { 41 | std::cout << "Send Error: " << ret.m_msg << "\n"; 42 | } 43 | } 44 | 45 | void ClientApp::onReceiveData(const char *data, size_t size) { 46 | std::string str(data, size); 47 | 48 | std::cout << "Received: " << str << "\n"; 49 | } 50 | 51 | void ClientApp::onDisconnect(const sockets::SocketRet &ret) { 52 | std::cout << "Disconnect: " << ret.m_msg << "\n"; 53 | exit(0); // NOLINT 54 | } 55 | 56 | void usage() { 57 | std::cout << "ClientApp [-a ][-p ]\n"; 58 | } 59 | 60 | int main(int argc, char **argv) { 61 | int arg = 0; 62 | const char *addr = nullptr; 63 | uint16_t port = 0; 64 | while ((arg = getopt(argc, argv, "a:p:?")) != EOF) { // NOLINT 65 | switch (arg) { 66 | case 'a': 67 | addr = optarg; 68 | break; 69 | case 'p': 70 | port = static_cast(std::stoul(optarg)); 71 | break; 72 | case '?': 73 | usage(); 74 | exit(1); // NOLINT 75 | } 76 | } 77 | 78 | auto *app = new ClientApp(addr, port); 79 | 80 | while (true) { 81 | std::string data; 82 | std::cout << "Data >"; 83 | std::getline(std::cin, data); 84 | if (data == "quit") { 85 | break; 86 | } 87 | app->sendMsg(data.data(), data.size()); 88 | } 89 | 90 | delete app; 91 | } -------------------------------------------------------------------------------- /examples/unicastApp.cpp: -------------------------------------------------------------------------------- 1 | #include "UdpSocket.h" 2 | #include 3 | #ifdef _WIN32 4 | #include "getopt.h" 5 | #else 6 | #include 7 | #endif 8 | 9 | class UnicastApp { 10 | public: 11 | // UDP Multicast 12 | UnicastApp(const char *remoteAddr, const char *listenAddr, uint16_t localPort, uint16_t port); 13 | 14 | virtual ~UnicastApp() = default; 15 | 16 | void onReceiveData(const char *data, size_t size) ; 17 | 18 | void sendMsg(const char *data, size_t len); 19 | 20 | private: 21 | sockets::SocketOpt m_socketOpts; 22 | sockets::UdpSocket m_unicast; 23 | }; 24 | 25 | UnicastApp::UnicastApp(const char *remoteAddr, const char *listenAddr, uint16_t localPort, uint16_t port) : m_socketOpts({ sockets::TX_BUFFER_SIZE, sockets::RX_BUFFER_SIZE, listenAddr}), m_unicast(*this, &m_socketOpts) { 26 | sockets::SocketRet ret = m_unicast.startUnicast(remoteAddr, localPort, port); 27 | if (ret.m_success) { 28 | std::cout << "Listening on UDP " << listenAddr << ":" << localPort << " sending to " << remoteAddr << ":" << port << "\n"; 29 | } else { 30 | std::cout << "Error: " << ret.m_msg << "\n"; 31 | exit(1); // NOLINT 32 | } 33 | } 34 | 35 | void UnicastApp::sendMsg(const char *data, size_t len) { 36 | auto ret = m_unicast.sendMsg(data, len); 37 | if (!ret.m_success) { 38 | std::cout << "Send Error: " << ret.m_msg << "\n"; 39 | } 40 | } 41 | 42 | void UnicastApp::onReceiveData(const char *data, size_t size) { 43 | std::string str(reinterpret_cast(data), size); 44 | 45 | std::cout << "Received: " << str << "\n"; 46 | } 47 | 48 | void usage() { 49 | std::cout << "UnicastApp -a -l -p -L \n"; 50 | } 51 | 52 | int main(int argc, char **argv) { 53 | int arg = 0; 54 | const char *addr = nullptr; 55 | const char *listenAddr = "0.0.0.0"; 56 | uint16_t port = 0; 57 | uint16_t localPort = 0; 58 | while ((arg = getopt(argc, argv, "a:l:p:L:?")) != EOF) { // NOLINT 59 | switch (arg) { 60 | case 'a': 61 | addr = optarg; 62 | break; 63 | case 'l': 64 | localPort = static_cast(std::stoul(optarg)); 65 | break; 66 | case 'p': 67 | port = static_cast(std::stoul(optarg)); 68 | break; 69 | case 'L': 70 | listenAddr = optarg; 71 | break; 72 | case '?': 73 | usage(); 74 | exit(1); // NOLINT 75 | } 76 | } 77 | 78 | auto *app = new UnicastApp(addr, listenAddr, localPort, port); 79 | 80 | while (true) { 81 | std::string data; 82 | std::cout << "Data >"; 83 | std::getline(std::cin, data); 84 | if (data == "quit") { 85 | break; 86 | } 87 | app->sendMsg(data.data(), data.size()); 88 | } 89 | 90 | delete app; 91 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "keyboard.dispatch": "keyCode", 3 | "cmake.configureOnOpen": true, 4 | "clangtidy.buildPath": "${workspaceFolder}/build", 5 | "clangtidy.sourcePath": "${workspaceFolder}/src", 6 | "clangtidy.checksPath": "${workspaceFolder}/.vscode", 7 | "C_Cpp.configurationWarnings": "disabled", 8 | "files.associations": { 9 | "vector": "cpp", 10 | "cctype": "cpp", 11 | "clocale": "cpp", 12 | "cmath": "cpp", 13 | "csignal": "cpp", 14 | "cstdarg": "cpp", 15 | "cstddef": "cpp", 16 | "cstdio": "cpp", 17 | "cstdlib": "cpp", 18 | "cstring": "cpp", 19 | "ctime": "cpp", 20 | "cwchar": "cpp", 21 | "cwctype": "cpp", 22 | "array": "cpp", 23 | "atomic": "cpp", 24 | "strstream": "cpp", 25 | "bit": "cpp", 26 | "*.tcc": "cpp", 27 | "bitset": "cpp", 28 | "chrono": "cpp", 29 | "codecvt": "cpp", 30 | "complex": "cpp", 31 | "condition_variable": "cpp", 32 | "cstdint": "cpp", 33 | "deque": "cpp", 34 | "forward_list": "cpp", 35 | "list": "cpp", 36 | "map": "cpp", 37 | "set": "cpp", 38 | "unordered_map": "cpp", 39 | "unordered_set": "cpp", 40 | "exception": "cpp", 41 | "algorithm": "cpp", 42 | "functional": "cpp", 43 | "iterator": "cpp", 44 | "memory": "cpp", 45 | "memory_resource": "cpp", 46 | "numeric": "cpp", 47 | "optional": "cpp", 48 | "random": "cpp", 49 | "ratio": "cpp", 50 | "regex": "cpp", 51 | "string": "cpp", 52 | "string_view": "cpp", 53 | "system_error": "cpp", 54 | "tuple": "cpp", 55 | "type_traits": "cpp", 56 | "utility": "cpp", 57 | "fstream": "cpp", 58 | "initializer_list": "cpp", 59 | "iomanip": "cpp", 60 | "iosfwd": "cpp", 61 | "iostream": "cpp", 62 | "istream": "cpp", 63 | "limits": "cpp", 64 | "new": "cpp", 65 | "ostream": "cpp", 66 | "shared_mutex": "cpp", 67 | "sstream": "cpp", 68 | "stdexcept": "cpp", 69 | "streambuf": "cpp", 70 | "thread": "cpp", 71 | "cfenv": "cpp", 72 | "cinttypes": "cpp", 73 | "typeindex": "cpp", 74 | "typeinfo": "cpp", 75 | "valarray": "cpp", 76 | "variant": "cpp", 77 | "cassert": "cpp", 78 | "mutex": "cpp", 79 | "compare": "cpp", 80 | "concepts": "cpp", 81 | "ranges": "cpp", 82 | "stop_token": "cpp", 83 | "numbers": "cpp", 84 | "semaphore": "cpp", 85 | "any": "cpp", 86 | "charconv": "cpp", 87 | "format": "cpp", 88 | "ios": "cpp", 89 | "locale": "cpp", 90 | "xfacet": "cpp", 91 | "xhash": "cpp", 92 | "xiosbase": "cpp", 93 | "xlocale": "cpp", 94 | "xlocbuf": "cpp", 95 | "xlocinfo": "cpp", 96 | "xlocmes": "cpp", 97 | "xlocmon": "cpp", 98 | "xlocnum": "cpp", 99 | "xloctime": "cpp", 100 | "xmemory": "cpp", 101 | "xstddef": "cpp", 102 | "xstring": "cpp", 103 | "xtr1common": "cpp", 104 | "xtree": "cpp", 105 | "xutility": "cpp" 106 | } 107 | } -------------------------------------------------------------------------------- /include/sockets-cpp/SocketCore.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #ifdef _WIN32 5 | #ifndef WIN32_LEAN_AND_MEAN 6 | #define WIN32_LEAN_AND_MEAN 7 | #endif // WIN32_LEAN_AND_MEAN 8 | #include 9 | #include 10 | #include 11 | #include 12 | // Need to link with Ws2_32.lib, Mswsock.lib, and Advapi32.lib 13 | #pragma comment (lib, "Ws2_32.lib") 14 | #pragma comment (lib, "Mswsock.lib") 15 | #pragma comment (lib, "AdvApi32.lib") 16 | #else 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #endif 23 | 24 | namespace sockets { 25 | 26 | #ifdef _WIN32 27 | using ssize_t = int; 28 | using in_addr_t = ULONG; 29 | #else 30 | using SOCKET = int; 31 | constexpr SOCKET INVALID_SOCKET = -1; 32 | #endif 33 | 34 | /** 35 | * @brief SocketCore is a pass-through interface to standard Socket API calls. 36 | * This class exists to facilitate unit testing using an alternate MockSocketCore 37 | * implementation. 38 | */ 39 | class SocketCore { 40 | public: 41 | SocketCore() = default; 42 | 43 | ~SocketCore() { 44 | #ifdef _WIN32 45 | ::WSACleanup(); 46 | #endif 47 | } 48 | 49 | int Initialize() { 50 | #ifdef _WIN32 51 | return WSAStartup(MAKEWORD(2,2), &m_wsaData); 52 | #else 53 | return 0; 54 | #endif 55 | } 56 | 57 | SOCKET Socket(int domain, int type, int protocol) { 58 | return ::socket(domain, type, protocol); 59 | } 60 | 61 | int SetSockOpt(SOCKET sockfd, int level, int optname, void * optval, socklen_t optlen) { 62 | #ifdef _WIN32 63 | return ::setsockopt(sockfd, level, optname, reinterpret_cast(optval), optlen); 64 | #else 65 | return ::setsockopt(sockfd, level, optname, optval, optlen); 66 | #endif 67 | } 68 | 69 | int Bind(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen) { 70 | return ::bind(sockfd, addr, addrlen); 71 | } 72 | 73 | SOCKET Accept(SOCKET sockfd, struct sockaddr *addr, socklen_t *addrlen) { 74 | return ::accept(sockfd, addr, addrlen); 75 | } 76 | 77 | int Listen(SOCKET sockfd, int backlog) { 78 | return ::listen(sockfd, backlog); 79 | } 80 | 81 | int Connect(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen) { 82 | return ::connect(sockfd, addr, addrlen); 83 | } 84 | 85 | int Close(SOCKET sockfd) { 86 | #ifdef _WIN32 87 | return ::closesocket(sockfd); 88 | #else 89 | return ::close(sockfd); 90 | #endif 91 | } 92 | 93 | int Select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval * timeout) { 94 | return ::select(nfds, readfds, writefds, exceptfds, timeout); 95 | } 96 | 97 | ssize_t Recv(int sockfd, void *buf, size_t len, int flags) { 98 | #ifdef _WIN32 99 | return ::recv(sockfd, reinterpret_cast(buf), static_cast(len), flags); 100 | #else 101 | return ::recv(sockfd, buf, len, flags); 102 | #endif 103 | } 104 | 105 | ssize_t SendTo(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen) { 106 | #ifdef _WIN32 107 | return ::sendto(sockfd, reinterpret_cast(buf), static_cast(len), flags, dest_addr, addrlen); 108 | #else 109 | return ::sendto(sockfd, buf, len, flags, dest_addr, addrlen); 110 | #endif 111 | } 112 | 113 | ssize_t Send(int sockfd, const void *buf, size_t len, int flags) { 114 | #ifdef _WIN32 115 | return ::send(sockfd, reinterpret_cast(buf), static_cast(len), flags); 116 | #else 117 | return ::send(sockfd, buf, len, flags); 118 | #endif 119 | } 120 | 121 | int GetAddrInfo(const char *node, const char *service, const addrinfo *hints, addrinfo **res) { 122 | return ::getaddrinfo(node, service, hints, res); 123 | } 124 | 125 | void FreeAddrInfo(struct addrinfo *res) { 126 | return ::freeaddrinfo(res); 127 | } 128 | 129 | private: 130 | #ifdef _WIN32 131 | WSADATA m_wsaData; 132 | #endif 133 | }; 134 | 135 | } // namespace sockets -------------------------------------------------------------------------------- /examples/serverApp.cpp: -------------------------------------------------------------------------------- 1 | #include "TcpServer.h" 2 | #include 3 | #ifdef _WIN32 4 | #include "getopt.h" 5 | #else 6 | #include 7 | #endif 8 | 9 | class ServerApp { 10 | public: 11 | // TCP Server 12 | explicit ServerApp(const char *listenAddr, uint16_t port); 13 | 14 | virtual ~ServerApp(); 15 | 16 | void onClientConnect(const sockets::ClientHandle &client); 17 | 18 | void onReceiveClientData(const sockets::ClientHandle &client, const char *data, size_t size); 19 | 20 | void onClientDisconnect(const sockets::ClientHandle &client, const sockets::SocketRet &ret); 21 | 22 | void sendMsg(int idx, const char *data, size_t len); 23 | 24 | private: 25 | sockets::SocketOpt m_socketOpt; 26 | sockets::TcpServer m_server; 27 | int m_clientIdx = 0; 28 | std::set m_clients; 29 | std::mutex m_mutex; 30 | }; 31 | 32 | ServerApp::ServerApp(const char *listenAddr, uint16_t port) : m_socketOpt({ sockets::TX_BUFFER_SIZE, sockets::RX_BUFFER_SIZE, listenAddr}), m_server(*this, &m_socketOpt) { 33 | sockets::SocketRet ret = m_server.start(port); 34 | if (ret.m_success) { 35 | std::cout << "Server started on port " << port << "\n"; 36 | } else { 37 | std::cout << "Error: " << ret.m_msg << "\n"; 38 | } 39 | } 40 | 41 | ServerApp::~ServerApp() 42 | { 43 | { 44 | std::lock_guard guard(m_mutex); 45 | m_clients.clear(); 46 | } 47 | } 48 | 49 | void ServerApp::sendMsg(int idx, const char *data, size_t len) { 50 | std::lock_guard guard(m_mutex); 51 | if (idx == 0) { 52 | auto ret = m_server.sendBcast(data, len); 53 | if (!ret.m_success) { 54 | std::cout << "Broadcast send Error: " << ret.m_msg << "\n"; 55 | } 56 | } else if (m_clients.count(idx) > 0) { 57 | auto ret = m_server.sendClientMessage(idx, data, len); 58 | if (!ret.m_success) { 59 | std::cout << "Send Error: " << ret.m_msg << "\n"; 60 | } 61 | } else { 62 | std::cout << "Client " << idx << " doesn't exist\n"; 63 | } 64 | } 65 | 66 | void ServerApp::onReceiveClientData(const sockets::ClientHandle &client, const char *data, size_t size) { 67 | std::string str(reinterpret_cast(data), size); 68 | std::cout << "Client " << client << " Rcvd: " << str << "\n"; 69 | } 70 | 71 | void ServerApp::onClientConnect(const sockets::ClientHandle &client) { 72 | std::string ipAddr; 73 | uint16_t port; 74 | bool connected; 75 | if (m_server.getClientInfo(client,ipAddr,port,connected)) { 76 | std::cout << "Client " << client << " connection from " << ipAddr << ":" << port << std::endl; 77 | { 78 | std::lock_guard guard(m_mutex); 79 | m_clients.insert(client); 80 | } 81 | } 82 | } 83 | 84 | void ServerApp::onClientDisconnect(const sockets::ClientHandle &client, const sockets::SocketRet &ret) { 85 | std::cout << "Client " << client << " Disconnect: " << ret.m_msg << "\n"; 86 | { 87 | std::lock_guard guard(m_mutex); 88 | m_clients.erase(client); 89 | } 90 | } 91 | 92 | void usage() { 93 | std::cout << "ServerApp -p -L \n"; 94 | } 95 | 96 | int main(int argc, char **argv) { 97 | int arg = 0; 98 | uint16_t port = 0; 99 | const char *listenAddr = "0.0.0.0"; 100 | while ((arg = getopt(argc, argv, "p:L:?")) != EOF) { // NOLINT 101 | switch (arg) { 102 | case 'p': 103 | port = static_cast(std::stoul(optarg)); 104 | break; 105 | case 'L': 106 | listenAddr = optarg; 107 | break; 108 | case '?': 109 | usage(); 110 | exit(1); // NOLINT 111 | } 112 | } 113 | 114 | auto *app = new ServerApp(listenAddr, port); 115 | 116 | while (true) { 117 | std::string data; 118 | std::cout << "Data >"; 119 | std::getline(std::cin, data); 120 | if (data == "quit") { 121 | break; 122 | } 123 | int idx = 0; 124 | if (data[0] != 'B' && data[0] != 'b') { 125 | try { 126 | idx = std::stoi(data); 127 | } catch (...) { continue; } 128 | } 129 | 130 | app->sendMsg(idx, data.substr(2, data.size() - 2).c_str(), data.size() - 2); 131 | } 132 | 133 | delete app; 134 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # sockets-cpp 3 | 4 | A header-only library with socket classes: 5 | [![ci](https://github.com/CJLove/sockets-cpp/actions/workflows/ci.yml/badge.svg)](https://github.com/CJLove/sockets-cpp/actions/workflows/ci.yml) 6 | 7 | * `TcpClient` - template class for a TCP client socket 8 | * `TcpServer` - template class for a TCP Server supporting multiple client connections 9 | * `UdpSocket` - template class for a UDP Multicast/Unicast 10 | 11 | ## Dependencies 12 | * Optional dependency on `fmt` for error string formatting 13 | * C++14 or later 14 | * CMake 15 | * gtest and gmock for unit tests. Enable unit tests by specifying `-DBUILD_TESTS=ON` when running `CMake` 16 | 17 | # Socket Options 18 | ```c++ 19 | struct SocketOpt { 20 | /** 21 | * @brief Value passed to setsockopt(SO_SNDBUF) 22 | * 23 | */ 24 | int m_txBufSize = TX_BUFFER_SIZE; 25 | 26 | /** 27 | * @brief Value passed to setsockopt(SO_RCVBUF) 28 | * 29 | */ 30 | int m_rxBufSize = RX_BUFFER_SIZE; 31 | 32 | /** 33 | * @brief Socket listen address 34 | * 35 | */ 36 | std::string m_listenAddr = "0.0.0.0"; 37 | 38 | }; 39 | ``` 40 | 41 | # UdpSocket 42 | The UdpSocket class is templated on the "callback" class which receives data via UDP. 43 | 44 | The template argument type must support the following interface: 45 | ```c++ 46 | void onReceiveData(const char *data, size_t size) ; 47 | ``` 48 | 49 | The UdpSocket class has the following interface for managing the UDP socket: 50 | ```c++ 51 | // Constructor 52 | UdpSocket(CallbackImpl &callback, SocketOpt *options = nullptr); 53 | 54 | // Start a multicast socket 55 | SocketRet startMcast(const char *mcastAddr, uint16_t port, const char *localIpAddr = nullptr); 56 | 57 | // Start a unicast client/server socket 58 | SocketRet startUnicast(const char *remoteAddr, uint16_t localPort, uint16_t port) 59 | 60 | // Start a unicast server-only socket 61 | SocketRet startUnicast(uint16_t localPort); 62 | 63 | // Send data via UDP 64 | SocketRet sendMsg(const char *msg, size_t size); 65 | 66 | // Shutdown the UDP socket 67 | void finish(); 68 | ``` 69 | 70 | # TcpClient 71 | The TcpClient class is templated on the "callback" class which receives data via TCP. 72 | 73 | The template argument type must support the following interface: 74 | ```c++ 75 | void onReceiveData(const char *data, size_t size); 76 | 77 | void onDisconnect(const sockets::SocketRet &ret); 78 | ``` 79 | 80 | The TcpClient class has the following interface for managing the TCP client connection: 81 | ```c++ 82 | // Constructor 83 | TcpClient(CallbackImpl &callback, SocketOpt *options = nullptr); 84 | 85 | // Connect to a TCP server 86 | SocketRet connectTo(const char *remoteIp, uint16_t remotePort); 87 | 88 | // Send data to the TCP server 89 | SocketRet sendMsg(const char *msg, size_t size); 90 | 91 | // Shutdown the TCP client socket 92 | void finish(); 93 | ``` 94 | 95 | # TcpServer 96 | The TcpServer class is templated on the "callback" class which manages the TCP server. 97 | 98 | The template argument must support the following interface: 99 | ```c++ 100 | void onClientConnect(const sockets::ClientHandle &client); 101 | 102 | void onReceiveClientData(const sockets::ClientHandle &client, const char *data, size_t size); 103 | 104 | void onClientDisconnect(const sockets::ClientHandle &client, const sockets::SocketRet &ret); 105 | ``` 106 | 107 | The TcpServer class has the following interfaces: 108 | ```c++ 109 | // Create a TCP server socket 110 | TcpServer(CallbackImpl &callback, SocketOpt *options = nullptr); 111 | 112 | // Start the server listening on the specified port 113 | SocketRet start(uint16_t port); 114 | 115 | // Send a message to all connected clients 116 | SocketRet sendBcast(const char *msg, size_t size); 117 | 118 | // Send a message to a specific client connection 119 | SocketRet sendClientMessage(ClientHandle &clientId, const char *msg, size_t size); 120 | 121 | // Shutdown the TCP server socket 122 | void finish(); 123 | ``` 124 | 125 | 126 | 127 | # Sample socket apps using these classes: 128 | Enable building sample apps by specifying `-DBUILD_EXAMPLES=ON` when running `CMake`. 129 | 130 | ## TCP Client 131 | ```bash 132 | $ ./clientApp -a -p 133 | ``` 134 | ## TCP Server 135 | ```bash 136 | $ ./serverApp -p -L 137 | ``` 138 | ## UDP Multicast 139 | ```bash 140 | $ ./mcastApp -m -p 141 | ``` 142 | ## UDP Unicast 143 | ```bash 144 | $ ./unicastApp -a -l -p -L 145 | ``` 146 | 147 | 148 | -------------------------------------------------------------------------------- /.gitea/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build_linux: 7 | runs-on: ubuntu-latest 8 | defaults: 9 | run: 10 | shell: bash 11 | # Use container with docker installed 12 | container: 13 | image: catthehacker/ubuntu:act-latest 14 | strategy: 15 | fail-fast: true 16 | max-parallel: 4 17 | matrix: 18 | config: 19 | - { name: g++15, image: fir.love.io:3005/amd64/gcc1511, cc: /usr/bin/gcc, cxx: /usr/bin/g++, build_type: Debug, cppstd: 17 } 20 | - { name: g++15, image: fir.love.io:3005/amd64/gcc1511, cc: /usr/bin/gcc, cxx: /usr/bin/g++, build_type: Release, cppstd: 17 } 21 | - { name: clang15, image: fir.love.io:3005/amd64/clang15, cc: /usr/bin/clang-15, cxx: /usr/bin/clang++-15, build_type: Debug, cppstd: 17 } 22 | - { name: clang17, image: fir.love.io:3005/amd64/clang17, cc: /usr/bin/clang-17, cxx: /usr/bin/clang++-17, build_type: Debug, cppstd: 17 } 23 | - { name: clang18, image: fir.love.io:3005/amd64/clang18, cc: /usr/bin/clang-18, cxx: /usr/bin/clang++-18, build_type: Debug, cppstd: 17 } 24 | - { name: clang19, image: fir.love.io:3005/amd64/clang19, cc: /usr/bin/clang-19, cxx: /usr/bin/clang++-19, build_type: Debug, cppstd: 17 } 25 | - { name: clang20, image: fir.love.io:3005/amd64/gcc1511, cc: /usr/bin/clang, cxx: /usr/bin/clang++, build_type: Debug, cppstd: 17 } 26 | - { name: clang20, image: fir.love.io:3005/amd64/gcc1511, cc: /usr/bin/clang, cxx: /usr/bin/clang++, build_type: Release, cppstd: 17 } 27 | - { name: asan, image: fir.love.io:3005/amd64/gcc1511, cc: /usr/bin/gcc, cxx: /usr/bin/g++, build_type: asan, cppstd: 17 } 28 | - { name: tsan, image: fir.love.io:3005/amd64/gcc1511, cc: /usr/bin/gcc, cxx: /usr/bin/g++, build_type: tsan, cppstd: 17 } 29 | - { name: ubsan, image: fir.love.io:3005/amd64/gcc1511, cc: /usr/bin/gcc, cxx: /usr/bin/g++, build_type: ubsan, cppstd: 17 } 30 | - { name: g++8.4.0, image: fir.love.io:3005/amd64/gcc840, cc: /opt/gcc840/bin/gcc, cxx: /opt/gcc840/bin/g++, build_type: Debug, cppstd: 17 } 31 | - { name: g++9.4.0, image: fir.love.io:3005/amd64/gcc940, cc: /opt/gcc940/bin/gcc, cxx: /opt/gcc940/bin/g++, build_type: Debug, cppstd: 17 } 32 | - { name: g++10.3.0, image: fir.love.io:3005/amd64/gcc1030, cc: /opt/gcc1030/bin/gcc, cxx: /opt/gcc1030/bin/g++, build_type: Debug, cppstd: 14 } 33 | - { name: g++11.3.0, image: fir.love.io:3005/amd64/gcc1130, cc: /opt/gcc1130/bin/gcc, cxx: /opt/gcc1130/bin/g++, build_type: Debug, cppstd: 14 } 34 | - { name: g++12.3.0, image: fir.love.io:3005/amd64/gcc1230, cc: /opt/gcc1230/bin/gcc, cxx: /opt/gcc1230/bin/g++, build_type: Debug, cppstd: 20 } 35 | - { name: g++13.3.0, image: fir.love.io:3005/amd64/gcc1330, cc: /opt/gcc1330/bin/gcc, cxx: /opt/gcc1330/bin/g++, build_type: Debug, cppstd: 20 } 36 | - { name: g++14.3.0, image: fir.love.io:3005/amd64/gcc1430, cc: /opt/gcc1430/bin/gcc, cxx: /opt/gcc1430/bin/g++, build_type: Debug, cppstd: 20 } 37 | 38 | name: "${{ matrix.config.name}} (C++${{ matrix.config.cppstd }}, ${{ matrix.config.build_type }})" 39 | steps: 40 | - uses: actions/checkout@v3 41 | - name: Build 42 | uses: https://github.com/addnab/docker-run-action@v3 43 | with: 44 | image: ${{ matrix.config.image }} 45 | options: --rm --volumes-from=${{ env.JOB_CONTAINER_NAME }} --pull always -v /home/vcpkg:/home/vcpkg 46 | run: | 47 | cd ${{ gitea.workspace }} 48 | export VCPKG_DEFAULT_BINARY_CACHE=/home/vcpkg 49 | ./build.sh -cc=${{ matrix.config.cc }} --cxx=${{ matrix.config.cxx }} --vcpkg --cmake="-DCMAKE_BUILD_TYPE=${{ matrix.config.cppstd }} -DCMAKE_CXX_STD=${{ matrix.config.cppstd }} -DFMT_SUPPORT=ON -DBUILD_TESTS=ON -DBUILD_EXAMPLES=ON" 50 | 51 | results: 52 | runs-on: ubuntu-latest 53 | if: always() 54 | needs: build_linux 55 | steps: 56 | - name: Report failure 57 | if: failure() 58 | uses: tsickert/discord-webhook@v5.4.0 59 | with: 60 | webhook-url: ${{ secrets.WEBHOOK_URL }} 61 | username: Gitea 62 | avatar-url: https://about.gitea.com/gitea-text.svg 63 | content: "Repo ${{ gitea.repository }} branch ${{ gitea.ref }} build :x:" 64 | - name: Report success 65 | if: success() 66 | uses: tsickert/discord-webhook@v5.4.0 67 | with: 68 | webhook-url: ${{ secrets.WEBHOOK_URL }} 69 | username: Gitea 70 | avatar-url: https://about.gitea.com/gitea-text.svg 71 | content: "Repo ${{ gitea.repository }} branch ${{ gitea.ref }} build :white_check_mark:" -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build_linux: 7 | runs-on: ubuntu-latest 8 | defaults: 9 | run: 10 | shell: bash 11 | strategy: 12 | matrix: 13 | config: 14 | - { compiler: gcc, version: 10, build_type: Release, cppstd: 17 } 15 | - { compiler: gcc, version: 11, build_type: Debug, cppstd: 17 } 16 | - { compiler: gcc, version: 12, build_type: Release, cppstd: 17 } 17 | - { compiler: gcc, version: 13, build_type: Debug, cppstd: 17 } 18 | - { compiler: gcc, version: 14, build_type: Debug, cppstd: 20 } 19 | - { compiler: gcc, version: 14, build_type: Release, cppstd: 20 } 20 | - { compiler: gcc, version: 15, build_type: Debug, cppstd: 20 } 21 | - { compiler: gcc, version: 15, build_type: Release, cppstd: 20 } 22 | 23 | - { compiler: clang, version: 11, build_type: Release, cppstd: 17 } 24 | - { compiler: clang, version: 12, build_type: Debug, cppstd: 17 } 25 | - { compiler: clang, version: 13, build_type: Debug, cppstd: 17 } 26 | - { compiler: clang, version: 14, build_type: Debug, cppstd: 17 } 27 | - { compiler: clang, version: 15, build_type: Release, cppstd: 20 } 28 | - { compiler: clang, version: 16, build_type: Release, cppstd: 20 } 29 | - { compiler: clang, version: 17, build_type: Release, cppstd: 20 } 30 | - { compiler: clang, version: 18, build_type: Release, cppstd: 20 } 31 | - { compiler: clang, version: 19, build_type: Release, cppstd: 20 } 32 | - { compiler: clang, version: 20, build_type: Release, cppstd: 20 } 33 | 34 | container: 35 | image: ${{ matrix.config.compiler == 'clang' && 'teeks99/clang-ubuntu' || matrix.config.compiler }}:${{ matrix.config.version }} 36 | name: "${{ matrix.config.compiler}} ${{ matrix.config.version }} (C++${{ matrix.config.cppstd }}, ${{ matrix.config.build_type }})" 37 | steps: 38 | - uses: actions/checkout@v3 39 | - name: Setup 40 | run: | 41 | apt-get update && apt-get install -y curl git zip unzip tar pkg-config 42 | CMAKE_VERSION="3.24.2" 43 | curl -sSL https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cmake-${CMAKE_VERSION}-linux-x86_64.sh -o install-cmake.sh 44 | chmod +x install-cmake.sh 45 | ./install-cmake.sh --prefix=/usr/local --skip-license 46 | - name: Setup Compiler 47 | if: matrix.config.compiler == 'clang' 48 | run: | 49 | if [[ "${{ matrix.config.version }}" -ge 4 ]]; then 50 | scripts/ci_setup_clang.sh "${{ matrix.config.version }}" 51 | echo "CXXFLAGS=-stdlib=libc++" >> $GITHUB_ENV 52 | fi 53 | echo "CC=clang-${{ matrix.config.version }}" >> $GITHUB_ENV 54 | echo "CXX=clang++-${{ matrix.config.version }}" >> $GITHUB_ENV 55 | - name: Setup vcpkg 56 | uses: lukka/run-vcpkg@v11 57 | with: 58 | vcpkgJsonGlob: '**/vcpkg.json' 59 | vcpkgDirectory: '${{ runner.workspace }}/b/vcpkg' 60 | vcpkgGitCommitId: 120deac3062162151622ca4860575a33844ba10b 61 | - name: Build 62 | # Note that run-vcpkg sets VCPKG_ROOT so use it to specify the toolchain 63 | # and avoid oddities with ${{ runner.workspace }} 64 | run: | 65 | mkdir -p build && cd build 66 | cmake .. \ 67 | -DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake \ 68 | -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} \ 69 | -DCMAKE_CXX_STANDARD=${{ matrix.config.cppstd }} \ 70 | -DBUILD_EXAMPLES=${{ matrix.config.examples || 'ON' }} \ 71 | -DBUILD_TESTS=ON 72 | make -j2 73 | ctest -j2 --output-on-failure 74 | 75 | build_osx: 76 | runs-on: macOS-latest 77 | name: "OS X Clang (C++17, Release)" 78 | steps: 79 | - uses: actions/checkout@v3 80 | - name: Setup vcpkg 81 | uses: lukka/run-vcpkg@v11 82 | with: 83 | vcpkgJsonGlob: '**/vcpkg.json' 84 | vcpkgDirectory: '${{ runner.workspace }}/b/vcpkg' 85 | vcpkgGitCommitId: 120deac3062162151622ca4860575a33844ba10b 86 | - name: Build 87 | run: | 88 | mkdir -p build && cd build 89 | cmake .. \ 90 | -DCMAKE_TOOLCHAIN_FILE=${{ runner.workspace }}/b/vcpkg/scripts/buildsystems/vcpkg.cmake \ 91 | -DCMAKE_PREFIX_PATH=/usr/local/lib/cmake \ 92 | -DCMAKE_BUILD_TYPE=Release \ 93 | -DCMAKE_CXX_STANDARD=17 \ 94 | -DFMT_SUPPORT=ON \ 95 | -DBUILD_EXAMPLES=ON \ 96 | -DBUILD_TESTS=ON 97 | make -j2 98 | ctest -j2 --output-on-failure 99 | 100 | build_windows: 101 | runs-on: windows-latest 102 | name: "Windows VS2022 (C++17, Release)" 103 | steps: 104 | - uses: actions/checkout@v3 105 | - uses: ilammy/msvc-dev-cmd@v1 106 | - name: Print env 107 | run: | 108 | echo github.event.action: ${{ github.event.action }} 109 | echo github.event_name: ${{ github.event_name }} 110 | # - name: Install dependencies on windows 111 | # run: | 112 | # choco install ninja cmake 113 | # ninja --version 114 | # cmake --version 115 | - name: Setup vcpkg 116 | uses: lukka/run-vcpkg@v11 117 | with: 118 | vcpkgJsonGlob: '**/vcpkg.json' 119 | vcpkgDirectory: '${{ runner.workspace }}/b/vcpkg' 120 | vcpkgGitCommitId: 120deac3062162151622ca4860575a33844ba10b 121 | - name: Build 122 | run: | 123 | mkdir build 124 | cd build 125 | cmake .. -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${{ runner.workspace }}/b/vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_STANDARD=17 -DFMT_SUPPORT=OFF -DBUILD_EXAMPLES=ON -DBUILD_TESTS=ON 126 | ninja 127 | -------------------------------------------------------------------------------- /CMake/CodeCoverage.cmake: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2012 - 2015, Lars Bilke 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without modification, 5 | # are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # 2. Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # 3. Neither the name of the copyright holder nor the names of its contributors 15 | # may be used to endorse or promote products derived from this software without 16 | # specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 22 | # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | # 29 | # 30 | # 31 | # 2012-01-31, Lars Bilke 32 | # - Enable Code Coverage 33 | # 34 | # 2013-09-17, Joakim Söderberg 35 | # - Added support for Clang. 36 | # - Some additional usage instructions. 37 | # 38 | # USAGE: 39 | 40 | # 0. (Mac only) If you use Xcode 5.1 make sure to patch geninfo as described here: 41 | # http://stackoverflow.com/a/22404544/80480 42 | # 43 | # 1. Copy this file into your cmake modules path. 44 | # 45 | # 2. Add the following line to your CMakeLists.txt: 46 | # INCLUDE(CodeCoverage) 47 | # 48 | # 3. Set compiler flags to turn off optimization and enable coverage: 49 | # SET(CMAKE_CXX_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage") 50 | # SET(CMAKE_C_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage") 51 | # 52 | # 3. Use the function SETUP_TARGET_FOR_COVERAGE to create a custom make target 53 | # which runs your test executable and produces a lcov code coverage report: 54 | # Example: 55 | # SETUP_TARGET_FOR_COVERAGE( 56 | # my_coverage_target # Name for custom target. 57 | # test_driver # Name of the test driver executable that runs the tests. 58 | # # NOTE! This should always have a ZERO as exit code 59 | # # otherwise the coverage generation will not complete. 60 | # coverage # Name of output directory. 61 | # ) 62 | # 63 | # 4. Build a Debug build: 64 | # cmake -DCMAKE_BUILD_TYPE=Debug .. 65 | # make 66 | # make my_coverage_target 67 | # 68 | # 69 | 70 | # Check prereqs 71 | FIND_PROGRAM( GCOV_PATH gcov ) 72 | FIND_PROGRAM( LCOV_PATH lcov ) 73 | FIND_PROGRAM( GENHTML_PATH genhtml ) 74 | FIND_PROGRAM( GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/tests) 75 | 76 | IF(NOT GCOV_PATH) 77 | MESSAGE(FATAL_ERROR "gcov not found! Aborting...") 78 | ENDIF() # NOT GCOV_PATH 79 | 80 | IF(NOT CMAKE_COMPILER_IS_GNUCXX) 81 | # Clang version 3.0.0 and greater now supports gcov as well. 82 | MESSAGE(WARNING "Compiler is not GNU gcc! Clang Version 3.0.0 and greater supports gcov as well, but older versions don't.") 83 | 84 | IF(NOT "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") 85 | MESSAGE(FATAL_ERROR "Compiler is not GNU gcc! Aborting...") 86 | ENDIF() 87 | ENDIF() # NOT CMAKE_COMPILER_IS_GNUCXX 88 | 89 | SET(CMAKE_CXX_FLAGS_COVERAGE 90 | "-g -O0 --coverage -fprofile-arcs -ftest-coverage" 91 | CACHE STRING "Flags used by the C++ compiler during coverage builds." 92 | FORCE ) 93 | SET(CMAKE_C_FLAGS_COVERAGE 94 | "-g -O0 --coverage -fprofile-arcs -ftest-coverage" 95 | CACHE STRING "Flags used by the C compiler during coverage builds." 96 | FORCE ) 97 | SET(CMAKE_EXE_LINKER_FLAGS_COVERAGE 98 | "" 99 | CACHE STRING "Flags used for linking binaries during coverage builds." 100 | FORCE ) 101 | SET(CMAKE_SHARED_LINKER_FLAGS_COVERAGE 102 | "" 103 | CACHE STRING "Flags used by the shared libraries linker during coverage builds." 104 | FORCE ) 105 | MARK_AS_ADVANCED( 106 | CMAKE_CXX_FLAGS_COVERAGE 107 | CMAKE_C_FLAGS_COVERAGE 108 | CMAKE_EXE_LINKER_FLAGS_COVERAGE 109 | CMAKE_SHARED_LINKER_FLAGS_COVERAGE ) 110 | 111 | IF ( NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "Coverage")) 112 | MESSAGE( WARNING "Code coverage results with an optimized (non-Debug) build may be misleading" ) 113 | ENDIF() # NOT CMAKE_BUILD_TYPE STREQUAL "Debug" 114 | 115 | 116 | # Param _targetname The name of new the custom make target 117 | # Param _testrunner The name of the target which runs the tests. 118 | # MUST return ZERO always, even on errors. 119 | # If not, no coverage report will be created! 120 | # Param _outputname lcov output is generated as _outputname.info 121 | # HTML report is generated in _outputname/index.html 122 | # Optional fourth parameter is passed as arguments to _testrunner 123 | # Pass them in list form, e.g.: "-j;2" for -j 2 124 | FUNCTION(SETUP_TARGET_FOR_COVERAGE _targetname _testrunner _outputname) 125 | 126 | IF(NOT LCOV_PATH) 127 | MESSAGE(FATAL_ERROR "lcov not found! Aborting...") 128 | ENDIF() # NOT LCOV_PATH 129 | 130 | IF(NOT GENHTML_PATH) 131 | MESSAGE(FATAL_ERROR "genhtml not found! Aborting...") 132 | ENDIF() # NOT GENHTML_PATH 133 | 134 | SET(coverage_info "${CMAKE_BINARY_DIR}/${_outputname}.info") 135 | SET(coverage_cleaned "${coverage_info}.cleaned") 136 | 137 | SEPARATE_ARGUMENTS(test_command UNIX_COMMAND "${_testrunner}") 138 | 139 | # Setup target 140 | ADD_CUSTOM_TARGET(${_targetname} 141 | 142 | # Cleanup lcov 143 | ${LCOV_PATH} --directory . --zerocounters 144 | 145 | # Run tests 146 | COMMAND ${test_command} ${ARGV3} 147 | 148 | # Capturing lcov counters and generating report 149 | COMMAND ${LCOV_PATH} --directory . --capture --output-file ${coverage_info} 150 | COMMAND ${LCOV_PATH} --remove ${coverage_info} 'test/*' '/usr/*' 'BlockingCollection*' --output-file ${coverage_cleaned} 151 | COMMAND ${GENHTML_PATH} -o ${_outputname} ${coverage_cleaned} 152 | COMMAND ${CMAKE_COMMAND} -E remove ${coverage_info} ${coverage_cleaned} 153 | 154 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR} 155 | COMMENT "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report." 156 | ) 157 | 158 | # Show info where to find the report 159 | ADD_CUSTOM_COMMAND(TARGET ${_targetname} POST_BUILD 160 | COMMAND ; 161 | COMMENT "Open ./${_outputname}/index.html in your browser to view the coverage report." 162 | ) 163 | 164 | ENDFUNCTION() # SETUP_TARGET_FOR_COVERAGE 165 | 166 | # Param _targetname The name of new the custom make target 167 | # Param _testrunner The name of the target which runs the tests 168 | # Param _outputname cobertura output is generated as _outputname.xml 169 | # Optional fourth parameter is passed as arguments to _testrunner 170 | # Pass them in list form, e.g.: "-j;2" for -j 2 171 | FUNCTION(SETUP_TARGET_FOR_COVERAGE_COBERTURA _targetname _testrunner _outputname) 172 | 173 | IF(NOT PYTHON_EXECUTABLE) 174 | MESSAGE(FATAL_ERROR "Python not found! Aborting...") 175 | ENDIF() # NOT PYTHON_EXECUTABLE 176 | 177 | IF(NOT GCOVR_PATH) 178 | MESSAGE(FATAL_ERROR "gcovr not found! Aborting...") 179 | ENDIF() # NOT GCOVR_PATH 180 | 181 | ADD_CUSTOM_TARGET(${_targetname} 182 | 183 | # Run tests 184 | ${_testrunner} ${ARGV3} 185 | 186 | # Running gcovr 187 | COMMAND ${GCOVR_PATH} -x -r ${CMAKE_SOURCE_DIR} -e '${CMAKE_SOURCE_DIR}/tests/' -o ${_outputname}.xml 188 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR} 189 | COMMENT "Running gcovr to produce Cobertura code coverage report." 190 | ) 191 | 192 | # Show info where to find the report 193 | ADD_CUSTOM_COMMAND(TARGET ${_targetname} POST_BUILD 194 | COMMAND ; 195 | COMMENT "Cobertura code coverage report saved in ${_outputname}.xml." 196 | ) 197 | 198 | ENDFUNCTION() # SETUP_TARGET_FOR_COVERAGE_COBERTURA 199 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.17) 2 | 3 | project(sockets-cpp) 4 | 5 | message( "CMAKE_SYSTEM_NAME: ${CMAKE_SYSTEM_NAME} ") 6 | 7 | set(VERSION "0.1.0") 8 | #--------------------------------------------------------------------------------------- 9 | # Build Types including support for sanitizers 10 | #--------------------------------------------------------------------------------------- 11 | 12 | set(CMAKE_BUILD_TYPE ${CMAKE_BUILD_TYPE} 13 | CACHE STRING "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel tsan asan lsan msan ubsan" 14 | FORCE) 15 | 16 | # ThreadSanitizer 17 | set(CMAKE_C_FLAGS_TSAN 18 | "-fsanitize=thread -g -O1" 19 | CACHE STRING "Flags used by the C compiler during ThreadSanitizer builds." 20 | FORCE) 21 | set(CMAKE_CXX_FLAGS_TSAN 22 | "-fsanitize=thread -g -O1" 23 | CACHE STRING "Flags used by the C++ compiler during ThreadSanitizer builds." 24 | FORCE) 25 | 26 | # AddressSanitize 27 | set(CMAKE_C_FLAGS_ASAN 28 | "-fsanitize=address -fno-optimize-sibling-calls -fsanitize-address-use-after-scope -fno-omit-frame-pointer -g -O1" 29 | CACHE STRING "Flags used by the C compiler during AddressSanitizer builds." 30 | FORCE) 31 | set(CMAKE_CXX_FLAGS_ASAN 32 | "-fsanitize=address -fno-optimize-sibling-calls -fsanitize-address-use-after-scope -fno-omit-frame-pointer -g -O1" 33 | CACHE STRING "Flags used by the C++ compiler during AddressSanitizer builds." 34 | FORCE) 35 | 36 | # LeakSanitizer 37 | set(CMAKE_C_FLAGS_LSAN 38 | "-fsanitize=leak -fno-omit-frame-pointer -g -O1" 39 | CACHE STRING "Flags used by the C compiler during LeakSanitizer builds." 40 | FORCE) 41 | set(CMAKE_CXX_FLAGS_LSAN 42 | "-fsanitize=leak -fno-omit-frame-pointer -g -O1" 43 | CACHE STRING "Flags used by the C++ compiler during LeakSanitizer builds." 44 | FORCE) 45 | 46 | # MemorySanitizer (clang only) 47 | set(CMAKE_C_FLAGS_MSAN 48 | "-fsanitize=memory -fno-optimize-sibling-calls -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer -g -O2" 49 | CACHE STRING "Flags used by the C compiler during MemorySanitizer builds." 50 | FORCE) 51 | set(CMAKE_CXX_FLAGS_MSAN 52 | "-fsanitize=memory -fno-optimize-sibling-calls -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer -g -O2" 53 | CACHE STRING "Flags used by the C++ compiler during MemorySanitizer builds." 54 | FORCE) 55 | 56 | # UndefinedBehaviour 57 | set(CMAKE_C_FLAGS_UBSAN 58 | "-fsanitize=undefined" 59 | CACHE STRING "Flags used by the C compiler during UndefinedBehaviourSanitizer builds." 60 | FORCE) 61 | set(CMAKE_CXX_FLAGS_UBSAN 62 | "-fsanitize=undefined" 63 | CACHE STRING "Flags used by the C++ compiler during UndefinedBehaviourSanitizer builds." 64 | FORCE) 65 | # MemorySanitizer only supported by clang 66 | if ("${CMAKE_BUILD_TYPE}" STREQUAL "msan" ) 67 | if (CMAKE_CXX_COMPILER_ID MATCHES "GNU") 68 | message( FATAL_ERROR "msan not supported for g++") 69 | endif () 70 | endif () 71 | 72 | if(NOT CMAKE_BUILD_TYPE) 73 | set (CMAKE_BUILD_TYPE Debug) 74 | endif(NOT CMAKE_BUILD_TYPE) 75 | 76 | #--------------------------------------------------------------------------------------- 77 | # Project options 78 | #--------------------------------------------------------------------------------------- 79 | 80 | option(FMT_SUPPORT "Use Fmt for string formatting" ON) 81 | option(BUILD_EXAMPLES "Build examples" OFF) 82 | option(BUILD_TESTS "Build unit tests." OFF) 83 | option(BUILD_COVERAGE "Build for code coverage." OFF) 84 | option(BUILD_SHARED_LIBS "Build Shared Libraries" ON) 85 | option(BUILD_STATIC_LIBS "Build Static Libraries" OFF) 86 | 87 | option(ENABLE_CLANG_TIDY "Enable testing with clang-tidy" FALSE) 88 | option(ENABLE_CPPCHECK "Enable testing with cppcheck" FALSE) 89 | 90 | set (CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} 91 | ${CMAKE_CURRENT_SOURCE_DIR}/CMake) 92 | 93 | 94 | #--------------------------------------------------------------------------------------- 95 | # compiler config 96 | #--------------------------------------------------------------------------------------- 97 | set(CMAKE_CXX_STANDARD 17) 98 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 99 | set(CMAKE_CXX_EXTENSIONS OFF) 100 | 101 | if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") 102 | add_compile_options("-Wall") 103 | add_compile_options("-Wextra") 104 | add_compile_options("-Wconversion") 105 | add_compile_options("-pedantic") 106 | add_compile_options("-Wfatal-errors") 107 | 108 | endif() 109 | 110 | #--------------------------------------------------------------------------------------- 111 | # Dependencies 112 | #--------------------------------------------------------------------------------------- 113 | 114 | include(FetchContent) 115 | 116 | if (FMT_SUPPORT) 117 | # Fmt 118 | find_package(fmt REQUIRED) 119 | # FetchContent_Declare(fmt 120 | # GIT_REPOSITORY https://github.com/fmtlib/fmt.git 121 | # GIT_TAG master 122 | # ) 123 | # FetchContent_MakeAvailable(fmt) 124 | 125 | add_definitions(-DFMT_SUPPORT) 126 | endif(FMT_SUPPORT) 127 | 128 | # Threads 129 | find_package(Threads REQUIRED) 130 | 131 | if (BUILD_TESTS) 132 | # Google Test 133 | find_package(GTest REQUIRED) 134 | # FetchContent_Declare(googletest 135 | # GIT_REPOSITORY https://github.com/google/googletest.git 136 | # GIT_TAG main 137 | # ) 138 | # FetchContent_MakeAvailable(googletest) 139 | 140 | endif() 141 | 142 | # clang-tidy 143 | if(ENABLE_CLANG_TIDY) 144 | find_program(CLANGTIDY clang-tidy) 145 | if(CLANGTIDY) 146 | message(STATUS "Using ${CLANGTIDY} for clang-tidy") 147 | set(CMAKE_CXX_CLANG_TIDY "${CLANGTIDY}") 148 | else() 149 | message(SEND_ERROR "clang-tidy requested but executable not found") 150 | endif() 151 | endif() 152 | configure_file(.clang-tidy .clang-tidy COPYONLY) 153 | 154 | # cppcheck 155 | if(ENABLE_CPPCHECK) 156 | find_program(CPPCHECK cppcheck) 157 | if(CPPCHECK) 158 | message(STATUS "Enabling cppcheck") 159 | set(CMAKE_CXX_CPPCHECK ${CPPCHECK}) 160 | list( 161 | APPEND CMAKE_CXX_CPPCHECK 162 | "--enable=warning,style" 163 | "--suppressions-list=${CMAKE_SOURCE_DIR}/suppressions.txt" 164 | ) 165 | else() 166 | message(SEND_ERROR "cppcheck requested but executable not found") 167 | endif() 168 | endif() 169 | 170 | #--------------------------------------------------------------------------------------- 171 | # Sources, headers, directories and libs 172 | #--------------------------------------------------------------------------------------- 173 | # From http://www.cmake.org/pipermail/cmake/2010-March/035992.html: 174 | # function to collect all the sources from sub-directories 175 | # into a single list 176 | function(add_sources) 177 | get_property(is_defined GLOBAL PROPERTY SRCS_LIST DEFINED) 178 | if(NOT is_defined) 179 | define_property(GLOBAL PROPERTY SRCS_LIST 180 | BRIEF_DOCS "List of source files" 181 | FULL_DOCS "List of all source files in the entire project") 182 | endif() 183 | # make absolute paths 184 | set(SRCS) 185 | foreach(s IN LISTS ARGN) 186 | if(NOT IS_ABSOLUTE "${s}") 187 | get_filename_component(s "${s}" ABSOLUTE) 188 | endif() 189 | list(APPEND SRCS "${s}") 190 | endforeach() 191 | # append to global list 192 | set_property(GLOBAL APPEND PROPERTY SRCS_LIST "${SRCS}") 193 | endfunction(add_sources) 194 | 195 | file(GLOB sources "src/[a-zA-Z]*.cpp") 196 | file(GLOB_RECURSE public_headers "include/sockets-cpp/[a-zA-Z]*.h") 197 | file(GLOB private_headers "src/[a-zA-Z]*.h") 198 | file(GLOB examples "examples/[a-zA-Z]*.[ch]*") 199 | file(GLOB tests "test/[a-zA-Z]*.cpp") 200 | 201 | set(library_sources 202 | ${sourcse} 203 | ${public_headers} 204 | ${private_headers} 205 | ${examples} 206 | ${tests} 207 | ) 208 | add_sources(${library_sources}) 209 | 210 | if(VERBOSE) 211 | message(STATUS "sources: ${sources}") 212 | message(STATUS "public_headers: ${public_headers}") 213 | message(STATUS "private_headers: ${private_headers}") 214 | message(STATUS "examples: ${examples}") 215 | message(STATUS "tests: ${tests}") 216 | endif(VERBOSE) 217 | 218 | #add_subdirectory(src) 219 | if (BUILD_EXAMPLES) 220 | add_subdirectory(examples) 221 | endif(BUILD_EXAMPLES) 222 | if (BUILD_TESTS) 223 | include(CTest) 224 | if (BUILD_COVERAGE) 225 | include (CodeCoverage) 226 | setup_target_for_coverage( socketCoverage test/socketTests coverage ) 227 | endif (BUILD_COVERAGE) 228 | add_subdirectory(test) 229 | endif(BUILD_TESTS) 230 | 231 | #--------------------------------------------------------------------------------------- 232 | # Addons 233 | #--------------------------------------------------------------------------------------- 234 | 235 | # Formatting 236 | get_property(all_sources GLOBAL PROPERTY SRCS_LIST) 237 | add_custom_target(format 238 | COMMAND clang-format --style=file -i ${all_sources} 239 | COMMENT "Running clang-format" 240 | VERBATIM) 241 | 242 | 243 | 244 | 245 | 246 | -------------------------------------------------------------------------------- /examples/getopt.cpp: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2012-2017, Kim Grasman 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * * Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of Kim Grasman nor the 13 | * names of contributors may be used to endorse or promote products 14 | * derived from this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL KIM GRASMAN BE LIABLE FOR ANY 20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | * 27 | ******************************************************************************/ 28 | 29 | #include "getopt.h" 30 | 31 | #include 32 | #include 33 | 34 | char* optarg; 35 | int optopt; 36 | /* The variable optind [...] shall be initialized to 1 by the system. */ 37 | int optind = 1; 38 | int opterr; 39 | 40 | static char* optcursor = NULL; 41 | 42 | /* Implemented based on [1] and [2] for optional arguments. 43 | optopt is handled FreeBSD-style, per [3]. 44 | Other GNU and FreeBSD extensions are purely accidental. 45 | 46 | [1] http://pubs.opengroup.org/onlinepubs/000095399/functions/getopt.html 47 | [2] http://www.kernel.org/doc/man-pages/online/pages/man3/getopt.3.html 48 | [3] http://www.freebsd.org/cgi/man.cgi?query=getopt&sektion=3&manpath=FreeBSD+9.0-RELEASE 49 | */ 50 | int getopt(int argc, char* const argv[], const char* optstring) { 51 | int optchar = -1; 52 | const char* optdecl = NULL; 53 | 54 | optarg = NULL; 55 | opterr = 0; 56 | optopt = 0; 57 | 58 | /* Unspecified, but we need it to avoid overrunning the argv bounds. */ 59 | if (optind >= argc) 60 | goto no_more_optchars; 61 | 62 | /* If, when getopt() is called argv[optind] is a null pointer, getopt() 63 | shall return -1 without changing optind. */ 64 | if (argv[optind] == NULL) 65 | goto no_more_optchars; 66 | 67 | /* If, when getopt() is called *argv[optind] is not the character '-', 68 | getopt() shall return -1 without changing optind. */ 69 | if (*argv[optind] != '-') 70 | goto no_more_optchars; 71 | 72 | /* If, when getopt() is called argv[optind] points to the string "-", 73 | getopt() shall return -1 without changing optind. */ 74 | if (strcmp(argv[optind], "-") == 0) 75 | goto no_more_optchars; 76 | 77 | /* If, when getopt() is called argv[optind] points to the string "--", 78 | getopt() shall return -1 after incrementing optind. */ 79 | if (strcmp(argv[optind], "--") == 0) { 80 | ++optind; 81 | goto no_more_optchars; 82 | } 83 | 84 | if (optcursor == NULL || *optcursor == '\0') 85 | optcursor = argv[optind] + 1; 86 | 87 | optchar = *optcursor; 88 | 89 | /* FreeBSD: The variable optopt saves the last known option character 90 | returned by getopt(). */ 91 | optopt = optchar; 92 | 93 | /* The getopt() function shall return the next option character (if one is 94 | found) from argv that matches a character in optstring, if there is 95 | one that matches. */ 96 | optdecl = strchr(optstring, optchar); 97 | if (optdecl) { 98 | /* [I]f a character is followed by a colon, the option takes an 99 | argument. */ 100 | if (optdecl[1] == ':') { 101 | optarg = ++optcursor; 102 | if (*optarg == '\0') { 103 | /* GNU extension: Two colons mean an option takes an 104 | optional arg; if there is text in the current argv-element 105 | (i.e., in the same word as the option name itself, for example, 106 | "-oarg"), then it is returned in optarg, otherwise optarg is set 107 | to zero. */ 108 | if (optdecl[2] != ':') { 109 | /* If the option was the last character in the string pointed to by 110 | an element of argv, then optarg shall contain the next element 111 | of argv, and optind shall be incremented by 2. If the resulting 112 | value of optind is greater than argc, this indicates a missing 113 | option-argument, and getopt() shall return an error indication. 114 | 115 | Otherwise, optarg shall point to the string following the 116 | option character in that element of argv, and optind shall be 117 | incremented by 1. 118 | */ 119 | if (++optind < argc) { 120 | optarg = argv[optind]; 121 | } else { 122 | /* If it detects a missing option-argument, it shall return the 123 | colon character ( ':' ) if the first character of optstring 124 | was a colon, or a question-mark character ( '?' ) otherwise. 125 | */ 126 | optarg = NULL; 127 | optchar = (optstring[0] == ':') ? ':' : '?'; 128 | } 129 | } else { 130 | optarg = NULL; 131 | } 132 | } 133 | 134 | optcursor = NULL; 135 | } 136 | } else { 137 | /* If getopt() encounters an option character that is not contained in 138 | optstring, it shall return the question-mark ( '?' ) character. */ 139 | optchar = '?'; 140 | } 141 | 142 | if (optcursor == NULL || *++optcursor == '\0') 143 | ++optind; 144 | 145 | return optchar; 146 | 147 | no_more_optchars: 148 | optcursor = NULL; 149 | return -1; 150 | } 151 | 152 | /* Implementation based on [1]. 153 | 154 | [1] http://www.kernel.org/doc/man-pages/online/pages/man3/getopt.3.html 155 | */ 156 | int getopt_long(int argc, char* const argv[], const char* optstring, 157 | const struct option* longopts, int* longindex) { 158 | const struct option* o = longopts; 159 | const struct option* match = NULL; 160 | int num_matches = 0; 161 | size_t argument_name_length = 0; 162 | const char* current_argument = NULL; 163 | int retval = -1; 164 | 165 | optarg = NULL; 166 | optopt = 0; 167 | 168 | if (optind >= argc) 169 | return -1; 170 | 171 | if (strlen(argv[optind]) < 3 || strncmp(argv[optind], "--", 2) != 0) 172 | return getopt(argc, argv, optstring); 173 | 174 | /* It's an option; starts with -- and is longer than two chars. */ 175 | current_argument = argv[optind] + 2; 176 | argument_name_length = strcspn(current_argument, "="); 177 | for (; o->name; ++o) { 178 | if (strncmp(o->name, current_argument, argument_name_length) == 0) { 179 | match = o; 180 | ++num_matches; 181 | } 182 | } 183 | 184 | if (num_matches == 1) { 185 | /* If longindex is not NULL, it points to a variable which is set to the 186 | index of the long option relative to longopts. */ 187 | if (longindex) 188 | *longindex = (match - longopts); 189 | 190 | /* If flag is NULL, then getopt_long() shall return val. 191 | Otherwise, getopt_long() returns 0, and flag shall point to a variable 192 | which shall be set to val if the option is found, but left unchanged if 193 | the option is not found. */ 194 | if (match->flag) 195 | *(match->flag) = match->val; 196 | 197 | retval = match->flag ? 0 : match->val; 198 | 199 | if (match->has_arg != no_argument) { 200 | optarg = strchr(argv[optind], '='); 201 | if (optarg != NULL) 202 | ++optarg; 203 | 204 | if (match->has_arg == required_argument) { 205 | /* Only scan the next argv for required arguments. Behavior is not 206 | specified, but has been observed with Ubuntu and Mac OSX. */ 207 | if (optarg == NULL && ++optind < argc) { 208 | optarg = argv[optind]; 209 | } 210 | 211 | if (optarg == NULL) 212 | retval = ':'; 213 | } 214 | } else if (strchr(argv[optind], '=')) { 215 | /* An argument was provided to a non-argument option. 216 | I haven't seen this specified explicitly, but both GNU and BSD-based 217 | implementations show this behavior. 218 | */ 219 | retval = '?'; 220 | } 221 | } else { 222 | /* Unknown option or ambiguous match. */ 223 | retval = '?'; 224 | } 225 | 226 | ++optind; 227 | return retval; 228 | } -------------------------------------------------------------------------------- /test/test_TcpServer.cpp: -------------------------------------------------------------------------------- 1 | #include "gtest/gtest.h" 2 | #include "gmock/gmock.h" 3 | #define TEST_CORE_ACCESS 4 | #include "TcpServer.h" 5 | #include "MockSocketCore.h" 6 | #include 7 | #include 8 | 9 | using ::testing::AtLeast; 10 | using ::testing::Return; 11 | using ::testing::NotNull; 12 | using ::testing::_; 13 | using ::testing::SetArgPointee; 14 | using ::testing::SetArrayArgument; 15 | using ::testing::DoAll; 16 | 17 | class TcpServerTestApp { 18 | public: 19 | TcpServerTestApp(): m_socket(*this) 20 | {} 21 | 22 | TcpServerTestApp(sockets::SocketOpt *opts): m_socket(*this,opts) 23 | {} 24 | 25 | ~TcpServerTestApp() = default; 26 | 27 | void onClientConnect(const sockets::ClientHandle &client); 28 | 29 | void onReceiveClientData(const sockets::ClientHandle &client, const char *data, size_t size); 30 | 31 | void onClientDisconnect(const sockets::ClientHandle &client, const sockets::SocketRet &ret); 32 | 33 | sockets::TcpServer m_socket; 34 | 35 | std::map m_receiveData; 36 | 37 | std::set m_clients; 38 | 39 | }; 40 | 41 | void TcpServerTestApp::onClientConnect(const sockets::ClientHandle &client) { 42 | m_clients.insert(client); 43 | } 44 | 45 | void TcpServerTestApp::onReceiveClientData(const sockets::ClientHandle &client, const char *data, size_t size) { 46 | m_receiveData[client] = std::string(data,size); 47 | } 48 | 49 | void TcpServerTestApp::onClientDisconnect(const sockets::ClientHandle &client, const sockets::SocketRet &) { 50 | m_clients.erase(client); 51 | } 52 | 53 | TEST(TcpServerSocket,start_socket_fail) 54 | { 55 | TcpServerTestApp app; 56 | MockSocketCore &core = app.m_socket.getCore(); 57 | 58 | EXPECT_CALL(core, Socket(_,_,_)).WillOnce(Return(-1)); 59 | auto ret = app.m_socket.start(5000); 60 | EXPECT_EQ(false,ret.m_success); 61 | } 62 | 63 | TEST(TcpServerSocket,start_setsockopt_fail1) 64 | { 65 | TcpServerTestApp app; 66 | MockSocketCore &core = app.m_socket.getCore(); 67 | 68 | EXPECT_CALL(core, Socket(_,_,_)).WillOnce(Return(4)); 69 | EXPECT_CALL(core, SetSockOpt(_,_,_,_,_)).WillOnce(Return(-1)); 70 | EXPECT_CALL(core, Close(_)).WillOnce(Return(0)); 71 | auto ret = app.m_socket.start(5000); 72 | EXPECT_EQ(false,ret.m_success); 73 | } 74 | 75 | TEST(TcpServerSocket,start_setsockopt_fail2) 76 | { 77 | TcpServerTestApp app; 78 | MockSocketCore &core = app.m_socket.getCore(); 79 | 80 | EXPECT_CALL(core, Socket(_,_,_)).WillOnce(Return(4)); 81 | EXPECT_CALL(core, SetSockOpt(_,_,_,_,_)).WillOnce(Return(0)).WillOnce(Return(-1)); 82 | EXPECT_CALL(core, Close(_)).WillOnce(Return(0)); 83 | auto ret = app.m_socket.start(5000); 84 | EXPECT_EQ(false,ret.m_success); 85 | } 86 | 87 | TEST(TcpServerSocket,start_setsockopt_fail3) 88 | { 89 | TcpServerTestApp app; 90 | MockSocketCore &core = app.m_socket.getCore(); 91 | 92 | EXPECT_CALL(core, Socket(_,_,_)).WillOnce(Return(4)); 93 | EXPECT_CALL(core, SetSockOpt(_,_,_,_,_)).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(-1)); 94 | EXPECT_CALL(core, Close(_)).WillOnce(Return(0)); 95 | auto ret = app.m_socket.start(5000); 96 | EXPECT_EQ(false,ret.m_success); 97 | } 98 | 99 | TEST(TcpServerSocket,start_bind_fail) 100 | { 101 | TcpServerTestApp app; 102 | MockSocketCore &core = app.m_socket.getCore(); 103 | 104 | EXPECT_CALL(core, Socket(_,_,_)).WillOnce(Return(4)); 105 | EXPECT_CALL(core, SetSockOpt(_,_,_,_,_)).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(0)); 106 | EXPECT_CALL(core, Bind(_,_,_)).WillOnce(Return(-1)); 107 | EXPECT_CALL(core, Close(_)).WillOnce(Return(0)); 108 | auto ret = app.m_socket.start(5000); 109 | EXPECT_EQ(false,ret.m_success); 110 | } 111 | 112 | TEST(TcpServerSocket,start_listen_fail) 113 | { 114 | TcpServerTestApp app; 115 | MockSocketCore &core = app.m_socket.getCore(); 116 | 117 | EXPECT_CALL(core, Socket(_,_,_)).WillOnce(Return(4)); 118 | EXPECT_CALL(core, SetSockOpt(_,_,_,_,_)).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(0)); 119 | EXPECT_CALL(core, Bind(_,_,_)).WillOnce(Return(0)); 120 | EXPECT_CALL(core, Listen(_,_)).WillOnce(Return(-1)); 121 | EXPECT_CALL(core, Close(_)).WillOnce(Return(0)); 122 | auto ret = app.m_socket.start(5000); 123 | EXPECT_EQ(false,ret.m_success); 124 | } 125 | 126 | TEST(TcpServerSocket,start_success) 127 | { 128 | TcpServerTestApp app; 129 | MockSocketCore &core = app.m_socket.getCore(); 130 | 131 | EXPECT_CALL(core, Socket(_,_,_)).WillOnce(Return(4)); 132 | EXPECT_CALL(core, SetSockOpt(_,_,_,_,_)).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(0)); 133 | EXPECT_CALL(core, Bind(_,_,_)).WillOnce(Return(0)); 134 | EXPECT_CALL(core, Listen(_,_)).WillOnce(Return(0)); 135 | EXPECT_CALL(core, Select(_,_,_,_,_)).WillRepeatedly(Return(0)); 136 | EXPECT_CALL(core, Close(_)).WillOnce(Return(0)); 137 | auto ret = app.m_socket.start(5000); 138 | EXPECT_EQ(true,ret.m_success); 139 | 140 | std::this_thread::sleep_for(std::chrono::seconds(1)); 141 | app.m_socket.finish(); 142 | } 143 | 144 | TEST(TcpServerSocket,client_connect_send) 145 | { 146 | TcpServerTestApp app; 147 | MockSocketCore &core = app.m_socket.getCore(); 148 | struct sockaddr client_addr = { 0, 149 | #ifdef __APPLE__ 150 | 0, 151 | #endif 152 | "\000\000\177\000\000\001" }; 153 | struct sockaddr *ptr = &client_addr; 154 | struct sockaddr *endPtr = ptr + 1; 155 | fd_set fds; 156 | FD_ZERO(&fds); 157 | FD_SET(4,&fds); 158 | EXPECT_CALL(core, Socket(_,_,_)).WillOnce(Return(4)); 159 | EXPECT_CALL(core, SetSockOpt(_,_,_,_,_)).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(0)); 160 | EXPECT_CALL(core, Bind(_,_,_)).WillOnce(Return(0)); 161 | EXPECT_CALL(core, Listen(_,_)).WillOnce(Return(0)); 162 | EXPECT_CALL(core, Select(_,_,_,_,_)).WillOnce(DoAll(SetArgPointee<1>(fds),Return(1))).WillRepeatedly(Return(0)); 163 | EXPECT_CALL(core, Accept(_,_,_)).WillOnce(DoAll(SetArrayArgument<1>(ptr,endPtr),Return(5))); 164 | EXPECT_CALL(core, Send(_,_,_,_)).WillOnce(Return(-1)).WillOnce(Return(5)).WillOnce(Return(12)); 165 | 166 | EXPECT_CALL(core, Close(_)).WillRepeatedly(Return(0)); 167 | auto ret = app.m_socket.start(5000); 168 | EXPECT_EQ(true,ret.m_success); 169 | 170 | std::this_thread::sleep_for(std::chrono::seconds(1)); 171 | 172 | sockets::ClientHandle badHandle = 3; 173 | sockets::ClientHandle goodHandle = 5; 174 | 175 | // Bad Client handle 176 | ret = app.m_socket.sendClientMessage(badHandle,"Message Data",12); 177 | EXPECT_EQ(false,ret.m_success); 178 | 179 | // Good Client handle, send() fails 180 | ret = app.m_socket.sendClientMessage(goodHandle,"Message Data",12); 181 | EXPECT_EQ(false,ret.m_success); 182 | 183 | // Good client handle, partial send() 184 | ret = app.m_socket.sendClientMessage(goodHandle,"Message Data",12); 185 | EXPECT_EQ(false,ret.m_success); 186 | 187 | ret = app.m_socket.sendClientMessage(goodHandle,"Message Data",12); 188 | EXPECT_EQ(true,ret.m_success); 189 | 190 | std::this_thread::sleep_for(std::chrono::seconds(1)); 191 | 192 | app.m_socket.finish(); 193 | 194 | EXPECT_EQ(true,(app.m_clients.find(5) != app.m_clients.end())); 195 | } 196 | 197 | TEST(TcpServerSocket,client_connect_receive_disconnect) 198 | { 199 | TcpServerTestApp app; 200 | MockSocketCore &core = app.m_socket.getCore(); 201 | struct sockaddr client_addr = { 0, 202 | #ifdef __APPLE__ 203 | 0, 204 | #endif 205 | "\000\000\177\000\000\001" }; 206 | struct sockaddr *ptr = &client_addr; 207 | struct sockaddr *endPtr = ptr + 1; 208 | fd_set acceptFds; 209 | FD_ZERO(&acceptFds); 210 | FD_SET(4,&acceptFds); 211 | fd_set recvFds; 212 | FD_ZERO(&recvFds); 213 | FD_SET(5,&recvFds); 214 | char receiveData[] = { "Received Data" }; 215 | char *dataPtr = receiveData; 216 | EXPECT_CALL(core, Socket(_,_,_)).WillOnce(Return(4)); 217 | EXPECT_CALL(core, SetSockOpt(_,_,_,_,_)).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(0)); 218 | EXPECT_CALL(core, Bind(_,_,_)).WillOnce(Return(0)); 219 | EXPECT_CALL(core, Listen(_,_)).WillOnce(Return(0)); 220 | EXPECT_CALL(core, Select(_,_,_,_,_)).WillOnce(DoAll(SetArgPointee<1>(acceptFds),Return(1))).WillOnce(DoAll(SetArgPointee<1>(recvFds),Return(1))).WillOnce(DoAll(SetArgPointee<1>(recvFds),Return(1))).WillRepeatedly(Return(0)); 221 | EXPECT_CALL(core, Accept(_,_,_)).WillOnce(DoAll(SetArrayArgument<1>(ptr,endPtr),Return(5))); 222 | EXPECT_CALL(core, Recv(_,_,_,_)).WillOnce(DoAll(SetArrayArgument<1>(dataPtr,dataPtr+13), Return(13))).WillOnce(DoAll(SetArrayArgument<1>(dataPtr,dataPtr+13), Return(0))); 223 | EXPECT_CALL(core, Close(_)).WillRepeatedly(Return(0)); 224 | auto ret = app.m_socket.start(5000); 225 | EXPECT_EQ(true,ret.m_success); 226 | 227 | std::this_thread::sleep_for(std::chrono::seconds(1)); 228 | 229 | app.m_socket.finish(); 230 | 231 | EXPECT_EQ(app.m_receiveData[5],"Received Data"); 232 | EXPECT_EQ(true,(app.m_clients.find(5) == app.m_clients.end())); 233 | } -------------------------------------------------------------------------------- /include/sockets-cpp/TcpClient.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "AddrLookup.h" 4 | #include "SocketCommon.h" 5 | #include "SocketCore.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #if defined(FMT_SUPPORT) 17 | #include 18 | #endif 19 | 20 | namespace sockets { 21 | 22 | constexpr size_t MAX_PACKET_SIZE = 65536; 23 | 24 | constexpr uint32_t MSG_SIZE = 100; 25 | 26 | /** 27 | * @brief TcpClient encapsulates a TCP client socket connection to a server 28 | * 29 | */ 30 | template 31 | class TcpClient { 32 | public: 33 | /** 34 | * @brief Construct a new Tcp Client object 35 | * 36 | * @param callback - reference to the callback object which will handle notifications 37 | * @param options - optional socket options to configure SO_SNDBUF and SO_RCVBUF 38 | */ 39 | explicit TcpClient(CallbackImpl &callback, SocketOpt *options = nullptr) 40 | : m_stop(false), m_callback(callback), m_addrLookup(m_socketCore) { 41 | if (options != nullptr) { 42 | m_sockOptions = *options; 43 | } 44 | } 45 | 46 | TcpClient(const TcpClient &) = delete; 47 | TcpClient(TcpClient &&) = delete; 48 | 49 | /** 50 | * @brief Destroy the Tcp Client object 51 | */ 52 | ~TcpClient() { 53 | finish(); 54 | } 55 | 56 | TcpClient &operator=(const TcpClient &) = delete; 57 | TcpClient &operator=(TcpClient &&) = delete; 58 | 59 | #if defined(TEST_CORE_ACCESS) 60 | SocketImpl &getCore() { 61 | return m_socketCore; 62 | } 63 | #endif 64 | 65 | /** 66 | * @brief Establish the TCP client connection 67 | * 68 | * @param remoteIp - remote IP address to connect to 69 | * @param remotePort - remote port number to connect to 70 | * @return SocketRet - indication of whether the client connection was established 71 | */ 72 | SocketRet connectTo(const char *remoteIp, uint16_t remotePort) { 73 | m_sockfd = INVALID_SOCKET; 74 | SocketRet ret; 75 | 76 | int result = m_socketCore.Initialize(); 77 | if (result != 0) { 78 | #if defined(FMT_SUPPORT) 79 | ret.m_msg = fmt::format("Error: Socket initializatio failed: {}", result); 80 | #else 81 | std::array msg; 82 | (void)snprintf(msg.data(),msg.size(),"Error: Socket initialization failed: %d",result); 83 | ret.m_msg = msg.data(); 84 | #endif 85 | ret.m_success = false; 86 | return ret; 87 | } 88 | 89 | m_sockfd = m_socketCore.Socket(AF_INET, SOCK_STREAM, 0); 90 | if (m_sockfd == INVALID_SOCKET) { // socket failed 91 | ret.m_success = false; 92 | #if defined(FMT_SUPPORT) 93 | ret.m_msg = fmt::format("Error: Failed to create socket: errno {}", errno); 94 | #else 95 | std::array msg; 96 | (void)snprintf(msg.data(),msg.size(),"Error: Failed to create socket: %d",errno); 97 | ret.m_msg = msg.data(); 98 | #endif 99 | return ret; 100 | } 101 | 102 | // set TX and RX buffer sizes 103 | if (m_socketCore.SetSockOpt(m_sockfd, SOL_SOCKET, SO_RCVBUF, reinterpret_cast(&m_sockOptions.m_rxBufSize), 104 | sizeof(m_sockOptions.m_rxBufSize)) < 0) { 105 | ret.m_success = false; 106 | #if defined(FMT_SUPPORT) 107 | ret.m_msg = fmt::format("Error: setsockopt(SO_RCVBUF) failed: errno {}", errno); 108 | #else 109 | std::array msg; 110 | (void)snprintf(msg.data(),msg.size(),"Error: setsockopt(SO_REUSEADDR) failed: %d", errno); 111 | ret.m_msg = msg.data(); 112 | #endif 113 | return ret; 114 | } 115 | 116 | if (m_socketCore.SetSockOpt(m_sockfd, SOL_SOCKET, SO_SNDBUF, reinterpret_cast(&m_sockOptions.m_txBufSize), 117 | sizeof(m_sockOptions.m_txBufSize)) < 0) { 118 | ret.m_success = false; 119 | #if defined(FMT_SUPPORT) 120 | ret.m_msg = fmt::format("Error: setsockopt(SO_SNDBUF) failed: errno {}", errno); 121 | #else 122 | std::array msg; 123 | (void)snprintf(msg.data(),msg.size(),"Error: setsockopt(SO_SNDBUF) failed: %d", errno); 124 | ret.m_msg = msg.data(); 125 | #endif 126 | return ret; 127 | } 128 | 129 | if (m_addrLookup.lookupHost(remoteIp, m_server.sin_addr.s_addr) != 0) { 130 | #if defined(FMT_SUPPORT) 131 | ret.m_msg = fmt::format("Failed to resolve hostname {}", remoteIp); 132 | #else 133 | std::array msg; 134 | (void)snprintf(msg.data(),msg.size(),"Failed to resolve hostname %s",remoteIp); 135 | ret.m_msg = msg.data(); 136 | #endif 137 | return ret; 138 | // } 139 | } 140 | m_server.sin_family = AF_INET; 141 | m_server.sin_port = htons(remotePort); 142 | 143 | int connectRet = m_socketCore.Connect(m_sockfd, reinterpret_cast(&m_server), sizeof(m_server)); 144 | if (connectRet == -1) { 145 | ret.m_success = false; 146 | #if defined(FMT_SUPPORT) 147 | ret.m_msg = fmt::format("Error: connect() failed errno {}", errno); 148 | #else 149 | std::array msg; 150 | (void)snprintf(msg.data(),msg.size(),"Error: connect() failed errno %d",errno); 151 | ret.m_msg = msg.data(); 152 | #endif 153 | return ret; 154 | } 155 | m_thread = std::thread(&TcpClient::ReceiveTask, this); 156 | ret.m_success = true; 157 | return ret; 158 | } 159 | 160 | /** 161 | * @brief Send a message to the TCP server 162 | * 163 | * @param msg - pointer to the message data 164 | * @param size - length of the message data 165 | * @return SocketRet - indication of whether the message was sent successfully 166 | */ 167 | SocketRet sendMsg(const char *msg, size_t size) { 168 | SocketRet ret; 169 | ssize_t numBytesSent = m_socketCore.Send(m_sockfd, reinterpret_cast(msg), size, 0); 170 | if (numBytesSent < 0) { // send failed 171 | ret.m_success = false; 172 | #if defined(FMT_SUPPORT) 173 | ret.m_msg = fmt::format("Error: send() failed errno {}", errno); 174 | #else 175 | std::array msg; 176 | (void)snprintf(msg.data(),msg.size(),"Error: send() failed: %d",errno); 177 | ret.m_msg = msg.data(); 178 | #endif 179 | return ret; 180 | } 181 | if (static_cast(numBytesSent) < size) { // not all bytes were sent 182 | ret.m_success = false; 183 | #if defined(FMT_SUPPORT) 184 | ret.m_msg = fmt::format("Error: Only {} bytes out of {} sent to client", numBytesSent, size); 185 | #else 186 | std::array msg; 187 | (void)snprintf(msg.data(), msg.size(), "Only %ld bytes out of %lu was sent to client", numBytesSent, size); 188 | ret.m_msg = msg.data(); 189 | #endif 190 | return ret; 191 | } 192 | ret.m_success = true; 193 | return ret; 194 | } 195 | 196 | /** 197 | * @brief Shut down the TCP client 198 | */ 199 | void finish() { 200 | m_stop.store(true); 201 | if (m_thread.joinable()) { 202 | try { 203 | m_thread.join(); 204 | } catch (...) { } 205 | } 206 | if (m_sockfd != INVALID_SOCKET) { 207 | m_socketCore.Close(m_sockfd); 208 | } 209 | m_sockfd = INVALID_SOCKET; 210 | } 211 | 212 | private: 213 | /** 214 | * @brief Publish message received from TCP server 215 | * 216 | * @param msg - pointer to the message data 217 | * @param msgSize - length of the message data 218 | */ 219 | void publishServerMsg(const char *msg, size_t msgSize) { 220 | m_callback.onReceiveData(msg, msgSize); 221 | } 222 | 223 | /** 224 | * @brief Publish notification of disconnection 225 | * 226 | * @param ret - error information 227 | */ 228 | void publishDisconnected(const SocketRet &ret) { 229 | m_callback.onDisconnect(ret); 230 | } 231 | 232 | /** 233 | * @brief Thread which receives data from the TCP server 234 | */ 235 | void ReceiveTask() { 236 | constexpr int64_t USEC_DELAY = 500000; 237 | while (!m_stop.load()) { 238 | fd_set fds; 239 | struct timeval delay { 240 | 0, USEC_DELAY 241 | }; 242 | FD_ZERO(&fds); 243 | FD_SET(m_sockfd, &fds); 244 | int selectRet = m_socketCore.Select(m_sockfd + 1, &fds, nullptr, nullptr, &delay); 245 | if (selectRet <= 0) { // select failed or timeout 246 | if (m_stop) { 247 | break; 248 | } 249 | } else if (FD_ISSET(m_sockfd, &fds)) { 250 | std::array msg; 251 | ssize_t numOfBytesReceived = m_socketCore.Recv(m_sockfd, msg.data(), MAX_PACKET_SIZE, 0); 252 | if (numOfBytesReceived < 1) { 253 | SocketRet ret; 254 | ret.m_success = false; 255 | m_stop = true; 256 | if (numOfBytesReceived == 0) { // server closed connection 257 | #if defined(FMT_SUPPORT) 258 | ret.m_msg = fmt::format("Server closed connection"); 259 | #else 260 | ret.m_msg = "Server closed connection"; 261 | #endif 262 | } else { 263 | #if defined(FMT_SUPPORT) 264 | ret.m_msg = fmt::format("Error: recv() failed errno {}", errno); 265 | #else 266 | std::array errMsg; 267 | (void)snprintf(errMsg.data(),errMsg.size(),"Error: recv() failed: %d",errno); 268 | ret.m_msg = errMsg.data(); 269 | #endif 270 | } 271 | publishDisconnected(ret); 272 | break; 273 | } 274 | publishServerMsg(msg.data(), static_cast(numOfBytesReceived)); 275 | } 276 | } 277 | } 278 | 279 | /** 280 | * @brief The socket file descriptor 281 | */ 282 | SOCKET m_sockfd = INVALID_SOCKET; 283 | 284 | /** 285 | * @brief Indicator that the receive thread should stop 286 | */ 287 | std::atomic_bool m_stop; 288 | 289 | /** 290 | * @brief Socket address of the server 291 | */ 292 | struct sockaddr_in m_server; 293 | 294 | /** 295 | * @brief Receive thread 296 | */ 297 | std::thread m_thread; 298 | 299 | /** 300 | * @brief Pointer to the registered callback receipient 301 | */ 302 | CallbackImpl &m_callback; 303 | 304 | /** 305 | * @brief Socket options for SO_SNDBUF and SO_RCVBUF 306 | */ 307 | SocketOpt m_sockOptions; 308 | 309 | /** 310 | * @brief Interface for socket calls 311 | */ 312 | SocketImpl m_socketCore; 313 | 314 | /** 315 | * @brief Helper for hostname resolution 316 | */ 317 | AddrLookup m_addrLookup; 318 | }; 319 | 320 | } // Namespace sockets -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2015 Chris Love (cjlove@san.rr.com) 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /test/test_TcpClient.cpp: -------------------------------------------------------------------------------- 1 | #include "gtest/gtest.h" 2 | #include "gmock/gmock.h" 3 | #define TEST_CORE_ACCESS 4 | #include "TcpClient.h" 5 | #include "MockSocketCore.h" 6 | #include 7 | 8 | using ::testing::AtLeast; 9 | using ::testing::Return; 10 | using ::testing::NotNull; 11 | using ::testing::_; 12 | using ::testing::SetArgPointee; 13 | using ::testing::SetArrayArgument; 14 | using ::testing::DoAll; 15 | 16 | class TcpClientTestApp { 17 | public: 18 | TcpClientTestApp(): m_socket(*this) 19 | {}; 20 | 21 | TcpClientTestApp(sockets::SocketOpt *opts): m_socket(*this,opts) 22 | {}; 23 | 24 | virtual ~TcpClientTestApp() = default; 25 | 26 | void onReceiveData(const char *data, size_t size); 27 | 28 | void onDisconnect(const sockets::SocketRet &ret); 29 | 30 | sockets::TcpClient m_socket; 31 | 32 | std::string m_receiveData; 33 | 34 | bool m_disconnected; 35 | }; 36 | 37 | void 38 | TcpClientTestApp::onDisconnect(const sockets::SocketRet &) { 39 | m_disconnected = true; 40 | } 41 | 42 | void 43 | TcpClientTestApp::onReceiveData(const char *data, size_t size) { 44 | m_receiveData = std::string(data,size); 45 | } 46 | 47 | TEST(TcpClientSocket,tcp_socket_fail) 48 | { 49 | TcpClientTestApp app; 50 | MockSocketCore &core = app.m_socket.getCore(); 51 | 52 | EXPECT_CALL(core, Socket(_,_,_)).WillOnce(Return(-1)); 53 | auto ret = app.m_socket.connectTo("localhost",5000); 54 | EXPECT_EQ(false,ret.m_success); 55 | } 56 | 57 | TEST(TcpClientSocket,tcp_sockopt_fail1) 58 | { 59 | TcpClientTestApp app; 60 | MockSocketCore &core = app.m_socket.getCore(); 61 | 62 | EXPECT_CALL(core, Socket(_,_,_)).WillOnce(Return(4)); 63 | EXPECT_CALL(core, SetSockOpt(_,_,_,_,_)).WillOnce(Return(-1)); 64 | EXPECT_CALL(core, Close(_)).WillOnce(Return(0)); 65 | auto ret = app.m_socket.connectTo("localhost",5000); 66 | EXPECT_EQ(false,ret.m_success); 67 | } 68 | 69 | TEST(TcpClientSocket,tcp_sockopt_fail2) 70 | { 71 | TcpClientTestApp app; 72 | MockSocketCore &core = app.m_socket.getCore(); 73 | 74 | EXPECT_CALL(core, Socket(_,_,_)).WillOnce(Return(4)); 75 | EXPECT_CALL(core, SetSockOpt(_,_,_,_,_)).WillOnce(Return(0)).WillOnce(Return(-1)); 76 | EXPECT_CALL(core, Close(_)).WillOnce(Return(0)); 77 | auto ret = app.m_socket.connectTo("localhost",5000); 78 | EXPECT_EQ(false,ret.m_success); 79 | } 80 | 81 | TEST(TcpClientSocket,unicast_lookup_fail) 82 | { 83 | TcpClientTestApp app; 84 | MockSocketCore &core = app.m_socket.getCore(); 85 | 86 | EXPECT_CALL(core, Socket(_,_,_)).WillOnce(Return(4)); 87 | EXPECT_CALL(core, SetSockOpt(_,_,_,_,_)).WillOnce(Return(0)).WillOnce(Return(0)); 88 | EXPECT_CALL(core, Close(_)).WillOnce(Return(0)); 89 | EXPECT_CALL(core, GetAddrInfo(_,_, NotNull(),NotNull())).WillOnce(Return(-1)); 90 | auto ret = app.m_socket.connectTo("localhost",5000); 91 | 92 | EXPECT_EQ(false,ret.m_success); 93 | } 94 | 95 | TEST(TcpClientSocket,tcp_connect_fail) 96 | { 97 | // Exercise passing socket options in 98 | sockets::SocketOpt opts; 99 | opts.m_rxBufSize = 8192; 100 | opts.m_txBufSize = 8192; 101 | 102 | TcpClientTestApp app(&opts); 103 | MockSocketCore &core = app.m_socket.getCore(); 104 | struct addrinfo res; 105 | struct sockaddr theAddr = { 0, 106 | #ifdef __APPLE__ 107 | 0, 108 | #endif 109 | "\000\000\177\000\000\001" }; 110 | res.ai_addr = &theAddr; 111 | EXPECT_CALL(core, Socket(_,_,_)).WillOnce(Return(4)); 112 | EXPECT_CALL(core, SetSockOpt(_,_,_,_,_)).WillOnce(Return(0)).WillOnce(Return(0)); 113 | EXPECT_CALL(core, GetAddrInfo(_,_, NotNull(),_)).WillOnce(DoAll(SetArgPointee<3>(&res), Return(0))); 114 | EXPECT_CALL(core, FreeAddrInfo(_)); 115 | EXPECT_CALL(core, Connect(_,_,_)).WillOnce(Return(-1)); 116 | EXPECT_CALL(core, Close(_)).WillOnce(Return(0)); 117 | auto ret = app.m_socket.connectTo("localhost",5000); 118 | EXPECT_EQ(false,ret.m_success); 119 | } 120 | 121 | TEST(TcpClientSocket,tcp_connect) 122 | { 123 | TcpClientTestApp app; 124 | MockSocketCore &core = app.m_socket.getCore(); 125 | struct addrinfo res; 126 | struct sockaddr theAddr = { 0, 127 | #ifdef __APPLE__ 128 | 0, 129 | #endif 130 | "\000\000\177\000\000\001" }; 131 | res.ai_addr = &theAddr; 132 | EXPECT_CALL(core, Socket(_,_,_)).WillOnce(Return(4)); 133 | EXPECT_CALL(core, SetSockOpt(_,_,_,_,_)).WillOnce(Return(0)).WillOnce(Return(0)); 134 | EXPECT_CALL(core, GetAddrInfo(_,_, NotNull(),_)).WillOnce(DoAll(SetArgPointee<3>(&res), Return(0))); 135 | EXPECT_CALL(core, FreeAddrInfo(_)); 136 | EXPECT_CALL(core, Connect(_,_,_)).WillOnce(Return(0)); 137 | EXPECT_CALL(core, Select(_,_,_,_,_)).WillRepeatedly(Return(0)); 138 | EXPECT_CALL(core, Close(_)).WillOnce(Return(0)); 139 | auto ret = app.m_socket.connectTo("localhost",5000); 140 | EXPECT_EQ(true,ret.m_success); 141 | 142 | std::this_thread::sleep_for(std::chrono::seconds(1)); 143 | app.m_socket.finish(); 144 | } 145 | 146 | TEST(TcpClientSocket,tcp_finish_fail) 147 | { 148 | TcpClientTestApp app; 149 | MockSocketCore &core = app.m_socket.getCore(); 150 | struct addrinfo res; 151 | struct sockaddr theAddr = { 0, 152 | #ifdef __APPLE__ 153 | 0, 154 | #endif 155 | "\000\000\177\000\000\001" }; 156 | res.ai_addr = &theAddr; 157 | EXPECT_CALL(core, Socket(_,_,_)).WillOnce(Return(4)); 158 | EXPECT_CALL(core, SetSockOpt(_,_,_,_,_)).WillOnce(Return(0)).WillOnce(Return(0)); 159 | EXPECT_CALL(core, GetAddrInfo(_,_, NotNull(),_)).WillOnce(DoAll(SetArgPointee<3>(&res), Return(0))); 160 | EXPECT_CALL(core, FreeAddrInfo(_)); 161 | EXPECT_CALL(core, Connect(_,_,_)).WillOnce(Return(0)); 162 | EXPECT_CALL(core, Select(_,_,_,_,_)).WillRepeatedly(Return(0)); 163 | EXPECT_CALL(core, Close(_)).WillOnce(Return(-1)); 164 | auto ret = app.m_socket.connectTo("localhost",5000); 165 | EXPECT_EQ(true,ret.m_success); 166 | 167 | std::this_thread::sleep_for(std::chrono::seconds(1)); 168 | app.m_socket.finish(); 169 | } 170 | 171 | TEST(TcpClientSocket,tcp_send_fail) 172 | { 173 | TcpClientTestApp app; 174 | MockSocketCore &core = app.m_socket.getCore(); 175 | struct addrinfo res; 176 | struct sockaddr theAddr = { 0, 177 | #ifdef __APPLE__ 178 | 0, 179 | #endif 180 | "\000\000\177\000\000\001" }; 181 | res.ai_addr = &theAddr; 182 | EXPECT_CALL(core, Socket(_,_,_)).WillOnce(Return(4)); 183 | EXPECT_CALL(core, SetSockOpt(_,_,_,_,_)).WillOnce(Return(0)).WillOnce(Return(0)); 184 | EXPECT_CALL(core, GetAddrInfo(_,_, NotNull(),_)).WillOnce(DoAll(SetArgPointee<3>(&res), Return(0))); 185 | EXPECT_CALL(core, FreeAddrInfo(_)); 186 | EXPECT_CALL(core, Connect(_,_,_)).WillOnce(Return(0)); 187 | EXPECT_CALL(core, Select(_,_,_,_,_)).WillRepeatedly(Return(0)); 188 | EXPECT_CALL(core, Send(_,_,_,_)).WillOnce(Return(-1)); 189 | EXPECT_CALL(core, Close(_)).WillOnce(Return(0)); 190 | auto ret = app.m_socket.connectTo("localhost",5000); 191 | EXPECT_EQ(true,ret.m_success); 192 | 193 | ret = app.m_socket.sendMsg("Message Data",12); 194 | EXPECT_EQ(false,ret.m_success); 195 | 196 | std::this_thread::sleep_for(std::chrono::seconds(1)); 197 | app.m_socket.finish(); 198 | } 199 | 200 | TEST(TcpClientSocket,tcp_send_partial) 201 | { 202 | TcpClientTestApp app; 203 | MockSocketCore &core = app.m_socket.getCore(); 204 | struct addrinfo res; 205 | struct sockaddr theAddr = { 0, 206 | #ifdef __APPLE__ 207 | 0, 208 | #endif 209 | "\000\000\177\000\000\001" }; 210 | res.ai_addr = &theAddr; 211 | EXPECT_CALL(core, Socket(_,_,_)).WillOnce(Return(4)); 212 | EXPECT_CALL(core, SetSockOpt(_,_,_,_,_)).WillOnce(Return(0)).WillOnce(Return(0)); 213 | EXPECT_CALL(core, GetAddrInfo(_,_, NotNull(),_)).WillOnce(DoAll(SetArgPointee<3>(&res), Return(0))); 214 | EXPECT_CALL(core, FreeAddrInfo(_)); 215 | EXPECT_CALL(core, Connect(_,_,_)).WillOnce(Return(0)); 216 | EXPECT_CALL(core, Select(_,_,_,_,_)).WillRepeatedly(Return(0)); 217 | EXPECT_CALL(core, Send(_,_,_,_)).WillOnce(Return(5)); 218 | EXPECT_CALL(core, Close(_)).WillOnce(Return(0)); 219 | auto ret = app.m_socket.connectTo("localhost",5000); 220 | EXPECT_EQ(true,ret.m_success); 221 | 222 | ret = app.m_socket.sendMsg("Message Data",12); 223 | EXPECT_EQ(false,ret.m_success); 224 | 225 | std::this_thread::sleep_for(std::chrono::seconds(1)); 226 | app.m_socket.finish(); 227 | } 228 | 229 | TEST(TcpClientSocket,tcp_send_success) 230 | { 231 | TcpClientTestApp app; 232 | MockSocketCore &core = app.m_socket.getCore(); 233 | struct addrinfo res; 234 | struct sockaddr theAddr = { 0, 235 | #ifdef __APPLE__ 236 | 0, 237 | #endif 238 | "\000\000\177\000\000\001" }; 239 | res.ai_addr = &theAddr; 240 | EXPECT_CALL(core, Socket(_,_,_)).WillOnce(Return(4)); 241 | EXPECT_CALL(core, SetSockOpt(_,_,_,_,_)).WillOnce(Return(0)).WillOnce(Return(0)); 242 | EXPECT_CALL(core, GetAddrInfo(_,_, NotNull(),_)).WillOnce(DoAll(SetArgPointee<3>(&res), Return(0))); 243 | EXPECT_CALL(core, FreeAddrInfo(_)); 244 | EXPECT_CALL(core, Connect(_,_,_)).WillOnce(Return(0)); 245 | EXPECT_CALL(core, Select(_,_,_,_,_)).WillRepeatedly(Return(0)); 246 | EXPECT_CALL(core, Send(_,_,_,_)).WillOnce(Return(12)); 247 | EXPECT_CALL(core, Close(_)).WillOnce(Return(0)); 248 | auto ret = app.m_socket.connectTo("localhost",5000); 249 | EXPECT_EQ(true,ret.m_success); 250 | 251 | ret = app.m_socket.sendMsg("Message Data",12); 252 | EXPECT_EQ(true,ret.m_success); 253 | 254 | std::this_thread::sleep_for(std::chrono::seconds(1)); 255 | app.m_socket.finish(); 256 | } 257 | 258 | TEST(TcpClientSocket,tcp_receive) 259 | { 260 | TcpClientTestApp app; 261 | MockSocketCore &core = app.m_socket.getCore(); 262 | struct addrinfo res; 263 | struct sockaddr theAddr = { 0, 264 | #ifdef __APPLE__ 265 | 0, 266 | #endif 267 | "\000\000\177\000\000\001" }; 268 | res.ai_addr = &theAddr; 269 | fd_set fds; 270 | FD_ZERO(&fds); 271 | FD_SET(4,&fds); 272 | char receiveData[] = { "Received Data" }; 273 | char *ptr = receiveData; 274 | 275 | EXPECT_CALL(core, Socket(_,_,_)).WillOnce(Return(4)); 276 | EXPECT_CALL(core, SetSockOpt(_,_,_,_,_)).WillOnce(Return(0)).WillOnce(Return(0)); 277 | EXPECT_CALL(core, GetAddrInfo(_,_, NotNull(),_)).WillOnce(DoAll(SetArgPointee<3>(&res), Return(0))); 278 | EXPECT_CALL(core, FreeAddrInfo(_)); 279 | EXPECT_CALL(core, Connect(_,_,_)).WillOnce(Return(0)); 280 | EXPECT_CALL(core, Select(_,_,_,_,_)).WillOnce(DoAll(SetArgPointee<1>(fds),Return(1))).WillRepeatedly(Return(0)); 281 | EXPECT_CALL(core, Recv(_,_,_,_)).WillOnce(DoAll(SetArrayArgument<1>(ptr,ptr+13), Return(13))); 282 | EXPECT_CALL(core, Close(_)).WillOnce(Return(0)); 283 | auto ret = app.m_socket.connectTo("localhost",5000); 284 | EXPECT_EQ(true,ret.m_success); 285 | 286 | std::this_thread::sleep_for(std::chrono::seconds(1)); 287 | app.m_socket.finish(); 288 | 289 | EXPECT_EQ(app.m_receiveData,"Received Data"); 290 | } 291 | 292 | TEST(TcpClientSocket,tcp_server_disconnect) 293 | { 294 | TcpClientTestApp app; 295 | MockSocketCore &core = app.m_socket.getCore(); 296 | struct addrinfo res; 297 | struct sockaddr theAddr = { 0, 298 | #ifdef __APPLE__ 299 | 0, 300 | #endif 301 | "\000\000\177\000\000\001" }; 302 | res.ai_addr = &theAddr; 303 | fd_set fds; 304 | FD_ZERO(&fds); 305 | FD_SET(4,&fds); 306 | char receiveData[] = { "Received Data" }; 307 | char *ptr = receiveData; 308 | 309 | EXPECT_CALL(core, Socket(_,_,_)).WillOnce(Return(4)); 310 | EXPECT_CALL(core, SetSockOpt(_,_,_,_,_)).WillOnce(Return(0)).WillOnce(Return(0)); 311 | EXPECT_CALL(core, GetAddrInfo(_,_, NotNull(),_)).WillOnce(DoAll(SetArgPointee<3>(&res), Return(0))); 312 | EXPECT_CALL(core, FreeAddrInfo(_)); 313 | EXPECT_CALL(core, Connect(_,_,_)).WillOnce(Return(0)); 314 | EXPECT_CALL(core, Select(_,_,_,_,_)).WillOnce(DoAll(SetArgPointee<1>(fds),Return(1))).WillRepeatedly(Return(0)); 315 | EXPECT_CALL(core, Recv(_,_,_,_)).WillOnce(DoAll(SetArrayArgument<1>(ptr,ptr+13), Return(0))); 316 | EXPECT_CALL(core, Close(_)).WillOnce(Return(0)); 317 | auto ret = app.m_socket.connectTo("localhost",5000); 318 | EXPECT_EQ(true,ret.m_success); 319 | 320 | std::this_thread::sleep_for(std::chrono::seconds(1)); 321 | app.m_socket.finish(); 322 | 323 | EXPECT_EQ(true,app.m_disconnected); 324 | } -------------------------------------------------------------------------------- /test/test_UdpSocket.cpp: -------------------------------------------------------------------------------- 1 | #include "gtest/gtest.h" 2 | #include "gmock/gmock.h" 3 | #include "MockSocketCore.h" 4 | #define TEST_CORE_ACCESS 5 | #include "UdpSocket.h" 6 | #include 7 | #include 8 | #include 9 | 10 | using ::testing::AtLeast; 11 | using ::testing::Return; 12 | using ::testing::NotNull; 13 | using ::testing::_; 14 | using ::testing::SetArgPointee; 15 | using ::testing::SetArrayArgument; 16 | using ::testing::DoAll; 17 | 18 | class UdpTestApp { 19 | public: 20 | UdpTestApp(): m_socket(*this) 21 | {}; 22 | 23 | UdpTestApp(sockets::SocketOpt *opts): m_socket(*this,opts) 24 | {}; 25 | 26 | void onReceiveData(const char *data, size_t size) ; 27 | 28 | sockets::UdpSocket m_socket; 29 | 30 | std::string m_receiveData; 31 | 32 | }; 33 | 34 | void UdpTestApp::onReceiveData(const char *data, size_t size) 35 | { 36 | m_receiveData = std::string(data,size); 37 | } 38 | 39 | TEST(UdpSocket,unicast_socket_fail) 40 | { 41 | UdpTestApp app; 42 | MockSocketCore &core = app.m_socket.getCore(); 43 | 44 | EXPECT_CALL(core, Socket(_,_,_)).WillOnce(Return(-1)); 45 | auto ret = app.m_socket.startUnicast(5000); 46 | EXPECT_EQ(false,ret.m_success); 47 | } 48 | 49 | TEST(UdpSocket,mcast_socket_fail) 50 | { 51 | UdpTestApp app; 52 | MockSocketCore &core = app.m_socket.getCore(); 53 | 54 | EXPECT_CALL(core, Socket(_,_,_)).WillOnce(Return(-1)); 55 | auto ret = app.m_socket.startMcast("224.0.0.1",5000); 56 | EXPECT_EQ(false,ret.m_success); 57 | } 58 | 59 | TEST(UdpSocket,unicast_sockopt_fail1) 60 | { 61 | UdpTestApp app; 62 | MockSocketCore &core = app.m_socket.getCore(); 63 | 64 | EXPECT_CALL(core, Socket(_,_,_)).WillOnce(Return(4)); 65 | EXPECT_CALL(core, SetSockOpt(_,_,_,_,_)).WillOnce(Return(-1)); 66 | EXPECT_CALL(core, Close(_)).WillOnce(Return(0)); 67 | auto ret = app.m_socket.startUnicast(5000); 68 | EXPECT_EQ(false,ret.m_success); 69 | } 70 | 71 | TEST(UdpSocket,mcast_sockopt_fail1) 72 | { 73 | UdpTestApp app; 74 | MockSocketCore &core = app.m_socket.getCore(); 75 | 76 | EXPECT_CALL(core, Socket(_,_,_)).WillOnce(Return(4)); 77 | EXPECT_CALL(core, SetSockOpt(_,_,_,_,_)).WillOnce(Return(-1)); 78 | EXPECT_CALL(core, Close(_)).WillOnce(Return(0)); 79 | auto ret = app.m_socket.startMcast("224.0.0.1",5000); 80 | EXPECT_EQ(false,ret.m_success); 81 | } 82 | 83 | TEST(UdpSocket,unicast_sockopt_fail2) 84 | { 85 | UdpTestApp app; 86 | MockSocketCore &core = app.m_socket.getCore(); 87 | 88 | EXPECT_CALL(core, Socket(_,_,_)).WillOnce(Return(4)); 89 | EXPECT_CALL(core, SetSockOpt(_,_,_,_,_)).WillOnce(Return(0)).WillOnce(Return(-1)); 90 | EXPECT_CALL(core, Close(_)).WillOnce(Return(0)); 91 | auto ret = app.m_socket.startUnicast(5000); 92 | EXPECT_EQ(false,ret.m_success); 93 | } 94 | 95 | TEST(UdpSocket,mcast_sockopt_fail2) 96 | { 97 | UdpTestApp app; 98 | MockSocketCore &core = app.m_socket.getCore(); 99 | 100 | EXPECT_CALL(core, Socket(_,_,_)).WillOnce(Return(4)); 101 | EXPECT_CALL(core, SetSockOpt(_,_,_,_,_)).WillOnce(Return(0)).WillOnce(Return(-1)); 102 | EXPECT_CALL(core, Close(_)).WillOnce(Return(0)); 103 | auto ret = app.m_socket.startMcast("224.0.0.1",5000); 104 | EXPECT_EQ(false,ret.m_success); 105 | } 106 | 107 | TEST(UdpSocket,unicast_sockopt_fail3) 108 | { 109 | UdpTestApp app; 110 | MockSocketCore &core = app.m_socket.getCore(); 111 | 112 | EXPECT_CALL(core, Socket(_,_,_)).WillOnce(Return(4)); 113 | EXPECT_CALL(core, SetSockOpt(_,_,_,_,_)).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(-1)); 114 | EXPECT_CALL(core, Close(_)).WillOnce(Return(0)); 115 | auto ret = app.m_socket.startUnicast(5000); 116 | EXPECT_EQ(false,ret.m_success); 117 | } 118 | 119 | TEST(UdpSocket,mcastsockopt_fail3) 120 | { 121 | UdpTestApp app; 122 | MockSocketCore &core = app.m_socket.getCore(); 123 | 124 | EXPECT_CALL(core, Socket(_,_,_)).WillOnce(Return(4)); 125 | EXPECT_CALL(core, SetSockOpt(_,_,_,_,_)).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(-1)); 126 | EXPECT_CALL(core, Close(_)).WillOnce(Return(0)); 127 | auto ret = app.m_socket.startMcast("224.0.0.1",5000); 128 | EXPECT_EQ(false,ret.m_success); 129 | } 130 | 131 | 132 | TEST(UdpSocket,unicast_bind_fail3) 133 | { 134 | UdpTestApp app; 135 | MockSocketCore &core = app.m_socket.getCore(); 136 | 137 | EXPECT_CALL(core, Socket(_,_,_)).WillOnce(Return(4)); 138 | EXPECT_CALL(core, SetSockOpt(_,_,_,_,_)).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(0)); 139 | EXPECT_CALL(core,Bind(_,_,_)).WillOnce(Return(-1)); 140 | EXPECT_CALL(core, Close(_)).WillOnce(Return(0)); 141 | auto ret = app.m_socket.startUnicast(5000); 142 | EXPECT_EQ(false,ret.m_success); 143 | } 144 | 145 | TEST(UdpSocket,mcast_bind_fail3) 146 | { 147 | UdpTestApp app; 148 | MockSocketCore &core = app.m_socket.getCore(); 149 | 150 | EXPECT_CALL(core, Socket(_,_,_)).WillOnce(Return(4)); 151 | EXPECT_CALL(core, SetSockOpt(_,_,_,_,_)).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(0)); 152 | EXPECT_CALL(core,Bind(_,_,_)).WillOnce(Return(-1)); 153 | EXPECT_CALL(core, Close(_)).WillOnce(Return(0)); 154 | auto ret = app.m_socket.startMcast("224.0.0.1",5000); 155 | EXPECT_EQ(false,ret.m_success); 156 | } 157 | 158 | TEST(UdpSocket,mcast_join_fail) 159 | { 160 | // Exercise passing socket options in 161 | sockets::SocketOpt opts; 162 | opts.m_rxBufSize = 8192; 163 | opts.m_txBufSize = 8192; 164 | UdpTestApp app(&opts); 165 | MockSocketCore &core = app.m_socket.getCore(); 166 | 167 | EXPECT_CALL(core, Socket(_,_,_)).WillOnce(Return(4)); 168 | EXPECT_CALL(core, SetSockOpt(_,_,_,_,_)).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(-1)); 169 | EXPECT_CALL(core,Bind(_,_,_)).WillOnce(Return(0)); 170 | EXPECT_CALL(core, Close(_)).WillOnce(Return(0)); 171 | EXPECT_CALL(core, Select(_,_,_,_,_)).WillRepeatedly(Return(0)); 172 | auto ret = app.m_socket.startMcast("224.0.0.1",5000); 173 | EXPECT_EQ(false,ret.m_success); 174 | } 175 | 176 | TEST(UdpSocket,unicast_start_stop) 177 | { 178 | UdpTestApp app; 179 | MockSocketCore &core = app.m_socket.getCore(); 180 | 181 | EXPECT_CALL(core, Socket(_,_,_)).WillOnce(Return(4)); 182 | EXPECT_CALL(core, SetSockOpt(_,_,_,_,_)).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(0)); 183 | EXPECT_CALL(core,Bind(_,_,_)).WillOnce(Return(0)); 184 | EXPECT_CALL(core, Close(_)).WillOnce(Return(0)); 185 | EXPECT_CALL(core, Select(_,_,_,_,_)).WillRepeatedly(Return(0)); 186 | auto ret = app.m_socket.startUnicast(5000); 187 | EXPECT_EQ(true,ret.m_success); 188 | 189 | std::this_thread::sleep_for(std::chrono::seconds(1)); 190 | app.m_socket.finish(); 191 | } 192 | 193 | TEST(UdpSocket,mcast_start_stop) 194 | { 195 | UdpTestApp app; 196 | MockSocketCore &core = app.m_socket.getCore(); 197 | 198 | EXPECT_CALL(core, Socket(_,_,_)).WillOnce(Return(4)); 199 | EXPECT_CALL(core, SetSockOpt(_,_,_,_,_)).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(0)); 200 | EXPECT_CALL(core,Bind(_,_,_)).WillOnce(Return(0)); 201 | EXPECT_CALL(core, Close(_)).WillOnce(Return(0)); 202 | EXPECT_CALL(core, Select(_,_,_,_,_)).WillRepeatedly(Return(0)); 203 | auto ret = app.m_socket.startMcast("224.0.0.1",5000); 204 | EXPECT_EQ(true,ret.m_success); 205 | 206 | std::this_thread::sleep_for(std::chrono::seconds(1)); 207 | app.m_socket.finish(); 208 | } 209 | 210 | TEST(UdpSocket,mcast_start_stop_local) 211 | { 212 | UdpTestApp app; 213 | MockSocketCore &core = app.m_socket.getCore(); 214 | 215 | EXPECT_CALL(core, Socket(_,_,_)).WillOnce(Return(4)); 216 | EXPECT_CALL(core, SetSockOpt(_,_,_,_,_)).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(0)); 217 | EXPECT_CALL(core,Bind(_,_,_)).WillOnce(Return(0)); 218 | EXPECT_CALL(core, Close(_)).WillOnce(Return(0)); 219 | EXPECT_CALL(core, Select(_,_,_,_,_)).WillRepeatedly(Return(0)); 220 | auto ret = app.m_socket.startMcast("224.0.0.1",5000,"127.0.0.1"); 221 | EXPECT_EQ(true,ret.m_success); 222 | 223 | std::this_thread::sleep_for(std::chrono::seconds(1)); 224 | app.m_socket.finish(); 225 | } 226 | 227 | TEST(UdpSocket,finish_close_failure) 228 | { 229 | UdpTestApp app; 230 | MockSocketCore &core = app.m_socket.getCore(); 231 | 232 | EXPECT_CALL(core, Socket(_,_,_)).WillOnce(Return(4)); 233 | EXPECT_CALL(core, SetSockOpt(_,_,_,_,_)).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(0)); 234 | EXPECT_CALL(core,Bind(_,_,_)).WillOnce(Return(0)); 235 | EXPECT_CALL(core, Close(_)).WillOnce(Return(-1)); 236 | EXPECT_CALL(core, Select(_,_,_,_,_)).WillRepeatedly(Return(0)); 237 | auto ret = app.m_socket.startUnicast(5000); 238 | EXPECT_EQ(true,ret.m_success); 239 | 240 | std::this_thread::sleep_for(std::chrono::seconds(1)); 241 | app.m_socket.finish(); 242 | } 243 | 244 | TEST(UdpSocket,unicast_lookup_fail) 245 | { 246 | UdpTestApp app; 247 | MockSocketCore &core = app.m_socket.getCore(); 248 | 249 | EXPECT_CALL(core, GetAddrInfo(_,_, NotNull(),NotNull())).WillOnce(Return(-1)); 250 | auto ret = app.m_socket.startUnicast("badhost",5000,5001); 251 | EXPECT_EQ(false,ret.m_success); 252 | } 253 | 254 | TEST(UdpSocket,unicast_lookup_success) 255 | { 256 | UdpTestApp app; 257 | MockSocketCore &core = app.m_socket.getCore(); 258 | 259 | struct addrinfo res; 260 | struct sockaddr theAddr = { 0, 261 | #ifdef __APPLE__ 262 | 0, 263 | #endif 264 | "\000\000\177\000\000\001" }; 265 | res.ai_addr = &theAddr; 266 | sockets::AddrLookup lookup(core); 267 | EXPECT_CALL(core, GetAddrInfo(_,_, NotNull(),_)).WillOnce(DoAll(SetArgPointee<3>(&res), Return(0))); 268 | EXPECT_CALL(core, FreeAddrInfo(_)); 269 | EXPECT_CALL(core, Socket(_,_,_)).WillOnce(Return(4)); 270 | EXPECT_CALL(core, SetSockOpt(_,_,_,_,_)).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(0)); 271 | EXPECT_CALL(core, Bind(_,_,_)).WillOnce(Return(0)); 272 | EXPECT_CALL(core, Close(_)).WillOnce(Return(0)); 273 | EXPECT_CALL(core, Select(_,_,_,_,_)).WillRepeatedly(Return(0)); 274 | auto ret = app.m_socket.startUnicast("localhost",5000,5001); 275 | EXPECT_EQ(true,ret.m_success); 276 | 277 | std::this_thread::sleep_for(std::chrono::seconds(1)); 278 | app.m_socket.finish(); 279 | } 280 | 281 | TEST(UdpSocket,unicast_receive_data) 282 | { 283 | UdpTestApp app; 284 | MockSocketCore &core = app.m_socket.getCore(); 285 | fd_set fds; 286 | FD_ZERO(&fds); 287 | FD_SET(4,&fds); 288 | char receiveData[] = { "Received Data" }; 289 | char *ptr = receiveData; 290 | 291 | EXPECT_CALL(core, Socket(_,_,_)).WillOnce(Return(4)); 292 | EXPECT_CALL(core, SetSockOpt(_,_,_,_,_)).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(0)); 293 | EXPECT_CALL(core,Bind(_,_,_)).WillOnce(Return(0)); 294 | EXPECT_CALL(core, Close(_)).WillOnce(Return(0)); 295 | EXPECT_CALL(core, Select(_,_,_,_,_)).WillOnce(DoAll(SetArgPointee<1>(fds),Return(1))).WillRepeatedly(Return(0)); 296 | EXPECT_CALL(core, Recv(_,_,_,_)).WillOnce(DoAll(SetArrayArgument<1>(ptr,ptr+13), Return(13))); 297 | auto ret = app.m_socket.startUnicast(5000); 298 | EXPECT_EQ(true,ret.m_success); 299 | 300 | std::this_thread::sleep_for(std::chrono::seconds(1)); 301 | app.m_socket.finish(); 302 | 303 | EXPECT_EQ(app.m_receiveData,"Received Data"); 304 | } 305 | 306 | TEST(UdpSocket,send_msg_fail) 307 | { 308 | UdpTestApp app; 309 | MockSocketCore &core = app.m_socket.getCore(); 310 | 311 | struct addrinfo res; 312 | struct sockaddr theAddr = { 0, 313 | #ifdef __APPLE__ 314 | 0, 315 | #endif 316 | "\000\000\177\000\000\001" }; 317 | res.ai_addr = &theAddr; 318 | sockets::AddrLookup lookup(core); 319 | EXPECT_CALL(core, GetAddrInfo(_,_, NotNull(),_)).WillOnce(DoAll(SetArgPointee<3>(&res), Return(0))); 320 | EXPECT_CALL(core, FreeAddrInfo(_)); 321 | EXPECT_CALL(core, Socket(_,_,_)).WillOnce(Return(4)); 322 | EXPECT_CALL(core, SetSockOpt(_,_,_,_,_)).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(0)); 323 | EXPECT_CALL(core, Bind(_,_,_)).WillOnce(Return(0)); 324 | EXPECT_CALL(core, Close(_)).WillOnce(Return(0)); 325 | EXPECT_CALL(core, SendTo(_,_,_,_,_,_)).WillOnce(Return(-1)); 326 | EXPECT_CALL(core, Select(_,_,_,_,_)).WillRepeatedly(Return(0)); 327 | auto ret = app.m_socket.startUnicast("localhost",5000,5001); 328 | EXPECT_EQ(true,ret.m_success); 329 | 330 | ret = app.m_socket.sendMsg("test message",11); 331 | EXPECT_EQ(false,ret.m_success); 332 | 333 | std::this_thread::sleep_for(std::chrono::seconds(1)); 334 | app.m_socket.finish(); 335 | } 336 | 337 | TEST(UdpSocket,send_msg_partial) 338 | { 339 | UdpTestApp app; 340 | MockSocketCore &core = app.m_socket.getCore(); 341 | 342 | struct addrinfo res; 343 | struct sockaddr theAddr = { 0, 344 | #ifdef __APPLE__ 345 | 0, 346 | #endif 347 | "\000\000\177\000\000\001" }; 348 | res.ai_addr = &theAddr; 349 | sockets::AddrLookup lookup(core); 350 | EXPECT_CALL(core, GetAddrInfo(_,_, NotNull(),_)).WillOnce(DoAll(SetArgPointee<3>(&res), Return(0))); 351 | EXPECT_CALL(core, FreeAddrInfo(_)); 352 | EXPECT_CALL(core, Socket(_,_,_)).WillOnce(Return(4)); 353 | EXPECT_CALL(core, SetSockOpt(_,_,_,_,_)).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(0)); 354 | EXPECT_CALL(core, Bind(_,_,_)).WillOnce(Return(0)); 355 | EXPECT_CALL(core, Close(_)).WillOnce(Return(0)); 356 | EXPECT_CALL(core, SendTo(_,_,_,_,_,_)).WillOnce(Return(5)); 357 | EXPECT_CALL(core, Select(_,_,_,_,_)).WillRepeatedly(Return(0)); 358 | auto ret = app.m_socket.startUnicast("localhost",5000,5001); 359 | EXPECT_EQ(true,ret.m_success); 360 | 361 | ret = app.m_socket.sendMsg("test message",11); 362 | EXPECT_EQ(false,ret.m_success); 363 | 364 | std::this_thread::sleep_for(std::chrono::seconds(1)); 365 | app.m_socket.finish(); 366 | } 367 | 368 | TEST(UdpSocket,send_msg) 369 | { 370 | UdpTestApp app; 371 | MockSocketCore &core = app.m_socket.getCore(); 372 | 373 | struct addrinfo res; 374 | struct sockaddr theAddr = { 0, 375 | #ifdef __APPLE__ 376 | 0, 377 | #endif 378 | "\000\000\177\000\000\001" }; 379 | res.ai_addr = &theAddr; 380 | sockets::AddrLookup lookup(core); 381 | EXPECT_CALL(core, GetAddrInfo(_,_, NotNull(),_)).WillOnce(DoAll(SetArgPointee<3>(&res), Return(0))); 382 | EXPECT_CALL(core, FreeAddrInfo(_)); 383 | EXPECT_CALL(core, Socket(_,_,_)).WillOnce(Return(4)); 384 | EXPECT_CALL(core, SetSockOpt(_,_,_,_,_)).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(0)); 385 | EXPECT_CALL(core, Bind(_,_,_)).WillOnce(Return(0)); 386 | EXPECT_CALL(core, Close(_)).WillOnce(Return(0)); 387 | EXPECT_CALL(core, SendTo(_,_,_,_,_,_)).WillOnce(Return(11)); 388 | EXPECT_CALL(core, Select(_,_,_,_,_)).WillRepeatedly(Return(0)); 389 | auto ret = app.m_socket.startUnicast("localhost",5000,5001); 390 | EXPECT_EQ(true,ret.m_success); 391 | 392 | ret = app.m_socket.sendMsg("test message",11); 393 | EXPECT_EQ(true,ret.m_success); 394 | 395 | std::this_thread::sleep_for(std::chrono::seconds(1)); 396 | app.m_socket.finish(); 397 | } -------------------------------------------------------------------------------- /include/sockets-cpp/UdpSocket.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "AddrLookup.h" 3 | #include "SocketCommon.h" 4 | #include "SocketCore.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #if defined(FMT_SUPPORT) 11 | #include 12 | #endif 13 | 14 | namespace sockets { 15 | /** 16 | * @brief Max UDP datagram size for UDP protocol 17 | * 18 | */ 19 | constexpr size_t MAX_PACKET_SIZE = 65507; 20 | 21 | constexpr uint32_t MSG_SIZE = 100; 22 | 23 | /** 24 | * @brief The UdpSocket class represents a UDP unicast or multicast socket connection 25 | * 26 | */ 27 | template 28 | class UdpSocket { 29 | public: 30 | /** 31 | * @brief Construct a new UDP Socket object 32 | * 33 | * @param callback - the callback recipient 34 | * @param options - optional socket options 35 | */ 36 | explicit UdpSocket(CallbackImpl &callback, SocketOpt *options = nullptr) 37 | : m_sockaddr({}), m_stop(false), m_callback(callback), m_addrLookup(m_socketCore) { 38 | if (options != nullptr) { 39 | m_sockOptions = *options; 40 | } 41 | } 42 | 43 | UdpSocket(const UdpSocket &) = delete; 44 | UdpSocket(UdpSocket &&) = delete; 45 | 46 | /** 47 | * @brief Destroy the UDP Socket object 48 | * 49 | */ 50 | ~UdpSocket() { 51 | finish(); 52 | } 53 | 54 | UdpSocket &operator=(const UdpSocket &) = delete; 55 | UdpSocket &operator=(UdpSocket &&) = delete; 56 | 57 | #if defined(TEST_CORE_ACCESS) 58 | SocketImpl &getCore() { 59 | return m_socketCore; 60 | } 61 | #endif 62 | 63 | /** 64 | * @brief Start a UDP multicast socket by binding to the server address and joining the 65 | * multicast group. 66 | * 67 | * @param mcastAddr - multicast group address to join 68 | * @param port - port number to listen/connect to 69 | * @return SocketRet - indication that multicast setup was successful 70 | */ 71 | SocketRet startMcast(const char *mcastAddr, uint16_t port, const char *localIpAddr = nullptr) { 72 | SocketRet ret; 73 | 74 | int result = m_socketCore.Initialize(); 75 | if (result != 0) { 76 | #if defined(FMT_SUPPORT) 77 | ret.m_msg = fmt::format("Error: Socket initialization failed: {}", result); 78 | #else 79 | std::array msg; 80 | (void)snprintf(msg.data(),msg.size(),"Error: Socket initialization failed: %d",result); 81 | ret.m_msg = msg.data(); 82 | #endif 83 | ret.m_success = false; 84 | return ret; 85 | } 86 | 87 | m_fd = m_socketCore.Socket(AF_INET, SOCK_DGRAM, 0); 88 | if (m_fd == INVALID_SOCKET) { 89 | ret.m_success = false; 90 | #if defined(FMT_SUPPORT) 91 | ret.m_msg = fmt::format("Error: socket() failed: errno {}", errno); 92 | #else 93 | std::array msg; 94 | (void)snprintf(msg.data(),msg.size(),"Error: socket() failed: %d",errno); 95 | ret.m_msg = msg.data(); 96 | #endif 97 | return ret; 98 | } 99 | // Allow multiple sockets to use the same port 100 | unsigned yes = 1; 101 | if (m_socketCore.SetSockOpt(m_fd, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&yes), sizeof(yes)) < 0) { 102 | ret.m_success = false; 103 | #if defined(FMT_SUPPORT) 104 | ret.m_msg = fmt::format("Error: setsockopt(SO_REUSEADDR) failed: errno {}", errno); 105 | #else 106 | std::array msg; 107 | (void)snprintf(msg.data(),msg.size(),"Error: setsockopt(SO_REULSEADDR) failed: %d",errno); 108 | ret.m_msg = msg.data(); 109 | #endif 110 | return ret; 111 | } 112 | // Set TX and RX buffer sizes 113 | if (m_socketCore.SetSockOpt(m_fd, SOL_SOCKET, SO_RCVBUF, reinterpret_cast(&m_sockOptions.m_rxBufSize), 114 | sizeof(m_sockOptions.m_rxBufSize)) < 0) { 115 | ret.m_success = false; 116 | #if defined(FMT_SUPPORT) 117 | ret.m_msg = fmt::format("Error: setsockopt(SO_RCVBUF) failed: errno {}", errno); 118 | #else 119 | std::array msg; 120 | (void)snprintf(msg.data(),msg.size(),"Error: setsockopt(SO_RCVBUF) failed: %d",errno); 121 | ret.m_msg = msg.data(); 122 | #endif 123 | return ret; 124 | } 125 | 126 | if (m_socketCore.SetSockOpt(m_fd, SOL_SOCKET, SO_SNDBUF, reinterpret_cast(&m_sockOptions.m_txBufSize), 127 | sizeof(m_sockOptions.m_txBufSize)) < 0) { 128 | ret.m_success = false; 129 | #if defined(FMT_SUPPORT) 130 | ret.m_msg = fmt::format("Error: setsockopt(SO_SNDBUF) failed: errno {}", errno); 131 | #else 132 | std::array msg; 133 | (void)snprintf(msg.data(),msg.size(),"Error: setsockopt(SO_SNDBUF) failed: %d",errno); 134 | ret.m_msg = msg.data(); 135 | #endif 136 | return ret; 137 | } 138 | 139 | sockaddr_in localAddr = {}; 140 | localAddr.sin_family = AF_INET; 141 | localAddr.sin_addr.s_addr = htonl(INADDR_ANY); 142 | localAddr.sin_port = htons(port); 143 | 144 | if (m_socketCore.Bind(m_fd, reinterpret_cast(&localAddr), sizeof(localAddr)) < 0) { 145 | ret.m_success = false; 146 | #if defined(FMT_SUPPORT) 147 | ret.m_msg = fmt::format("Error: bind() failed: errno {}", errno); 148 | #else 149 | std::array msg; 150 | (void)snprintf(msg.data(),msg.size(),"Error: bind() failed: %d",errno); 151 | ret.m_msg = msg.data(); 152 | #endif 153 | return ret; 154 | } 155 | 156 | // store the multicast group address for use by send() 157 | memset(&m_sockaddr, 0, sizeof(sockaddr)); 158 | m_sockaddr.sin_family = AF_INET; 159 | inet_pton(AF_INET,mcastAddr,&m_sockaddr.sin_addr); 160 | //m_sockaddr.sin_addr.s_addr = inet_addr(mcastAddr); 161 | m_sockaddr.sin_port = htons(port); 162 | 163 | // use setsockopt() to request that the kernel join a multicast group 164 | // 165 | struct ip_mreq mreq { }; 166 | inet_pton(AF_INET,mcastAddr,&mreq.imr_multiaddr); 167 | if (localIpAddr) { 168 | inet_pton(AF_INET, localIpAddr, &mreq.imr_interface.s_addr); 169 | } else { 170 | mreq.imr_interface.s_addr = htonl(INADDR_ANY); 171 | } 172 | if (m_socketCore.SetSockOpt(m_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, reinterpret_cast(&mreq), sizeof(mreq)) < 0) { 173 | ret.m_success = false; 174 | #if defined(FMT_SUPPORT) 175 | ret.m_msg = fmt::format("Error: setsockopt(IP_ADD_MEMBERSHIP) failed: errno {}", errno); 176 | #else 177 | std::array msg; 178 | (void)snprintf(msg.data(),msg.size(),"Error: setsockopt(IP_ADD_MEMBERSHIP) failed: %d",errno); 179 | ret.m_msg = msg.data(); 180 | #endif 181 | return ret; 182 | } 183 | 184 | struct in_addr addr; 185 | if (localIpAddr) { 186 | inet_pton(AF_INET, localIpAddr, &addr.s_addr); 187 | } else { 188 | mreq.imr_interface.s_addr = htonl(INADDR_ANY); 189 | } 190 | if (m_socketCore.SetSockOpt(m_fd, IPPROTO_IP, IP_MULTICAST_IF, &addr, sizeof(addr)) < 0) 191 | { 192 | ret.m_success = false; 193 | #if defined(FMT_SUPPORT) 194 | ret.m_msg = fmt::format("Error: setsockopt(IP_MULTICAST_IF) failed: errno {}", errno); 195 | #else 196 | std::array msg; 197 | (void)snprintf(msg.data(),msg.size(),"Error: setsockopt(IP_MULTICAST_IF) failed: %d", errno); 198 | ret.m_msg = msg.data(); 199 | #endif 200 | return ret; 201 | } 202 | 203 | m_thread = std::thread(&UdpSocket::ReceiveTask, this); 204 | ret.m_success = true; 205 | return ret; 206 | } 207 | 208 | /** 209 | * @brief Start a UDP unicast socket by binding to the server address and storing the 210 | * IP address and port number for the peer. 211 | * 212 | * @param remoteAddr - remote IP address 213 | * @param localPort - local port to listen on 214 | * @param port - remote port to connect to when sending messages 215 | * @return SocketRet - Indication that unicast setup was successful 216 | */ 217 | SocketRet startUnicast(const char *remoteAddr, uint16_t localPort, uint16_t port) { 218 | SocketRet ret; 219 | 220 | int result = m_socketCore.Initialize(); 221 | if (result != 0) { 222 | #if defined(FMT_SUPPORT) 223 | ret.m_msg = fmt::format("Error: Socket initialization failed: {}", result); 224 | #else 225 | std::array msg; 226 | (void)snprintf(msg.data(),msg.size(),"Error: Socket initialization failed: %d",result); 227 | ret.m_msg = msg.data(); 228 | #endif 229 | ret.m_success = false; 230 | return ret; 231 | } 232 | 233 | // store the remoteaddress for use by sendto() 234 | memset(&m_sockaddr, 0, sizeof(sockaddr)); 235 | m_sockaddr.sin_family = AF_INET; 236 | if (m_addrLookup.lookupHost(remoteAddr, m_sockaddr.sin_addr.s_addr) != 0) { 237 | #if defined(FMT_SUPPORT) 238 | ret.m_msg = fmt::format("Failed to resolve hostname {}", remoteAddr); 239 | #else 240 | std::array msg; 241 | (void)snprintf(msg.data(),msg.size(),"Failed to resolve hostname %s",remoteAddr); 242 | ret.m_msg = msg.data(); 243 | #endif 244 | return ret; 245 | } 246 | 247 | m_sockaddr.sin_port = htons(port); 248 | 249 | // return the result of setting up the local server 250 | return startUnicast(localPort); 251 | } 252 | 253 | /** 254 | * @brief Start a UDP unicast socket by binding to the server address 255 | * 256 | * @param localPort - local port to listen on 257 | * @return SocketRet - Indication that unicast setup was successful 258 | */ 259 | SocketRet startUnicast(uint16_t localPort) { 260 | SocketRet ret; 261 | int result = m_socketCore.Initialize(); 262 | if (result != 0) { 263 | #if defined(FMT_SUPPORT) 264 | ret.m_msg = fmt::format("Error: Socket initialization failed: {}", result); 265 | #else 266 | std::array msg; 267 | (void)snprintf(msg.data(),msg.size(),"Error: Socket initialization failed: %d",result); 268 | ret.m_msg = msg.data(); 269 | #endif 270 | ret.m_success = false; 271 | return ret; 272 | } 273 | 274 | m_fd = m_socketCore.Socket(AF_INET, SOCK_DGRAM, 0); 275 | if (m_fd == INVALID_SOCKET) { 276 | ret.m_success = false; 277 | #if defined(FMT_SUPPORT) 278 | ret.m_msg = fmt::format("Error: socket() failed: errno {}", errno); 279 | #else 280 | ret.m_msg = "socket() failed"; 281 | #endif 282 | return ret; 283 | } 284 | // Allow multiple sockets to use the same port 285 | unsigned yes = 1; 286 | if (m_socketCore.SetSockOpt(m_fd, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&yes), sizeof(yes)) < 0) { 287 | ret.m_success = false; 288 | #if defined(FMT_SUPPORT) 289 | ret.m_msg = fmt::format("Error: setsockopt(SO_REUSEADDR) failed: errno {}", errno); 290 | #else 291 | std::array msg; 292 | (void)snprintf(msg.data(),msg.size(),"Error: setsockopt(SO_REUSEADDR) failed: errno %d", errno); 293 | ret.m_msg = msg.data(); 294 | #endif 295 | return ret; 296 | } 297 | 298 | // Set TX and RX buffer sizes 299 | if (m_socketCore.SetSockOpt(m_fd, SOL_SOCKET, SO_RCVBUF, reinterpret_cast(&m_sockOptions.m_rxBufSize), 300 | sizeof(m_sockOptions.m_rxBufSize)) < 0) { 301 | ret.m_success = false; 302 | #if defined(FMT_SUPPORT) 303 | ret.m_msg = fmt::format("Error: setsockopt(SO_RCVBUF) failed: errno {}", errno); 304 | #else 305 | std::array msg; 306 | (void)snprintf(msg.data(),msg.size(),"Error: setsockopt(SO_RCVBUF) failed: errno %d", errno); 307 | ret.m_msg = msg.data(); 308 | #endif 309 | return ret; 310 | } 311 | 312 | if (m_socketCore.SetSockOpt(m_fd, SOL_SOCKET, SO_SNDBUF, reinterpret_cast(&m_sockOptions.m_txBufSize), 313 | sizeof(m_sockOptions.m_txBufSize)) < 0) { 314 | ret.m_success = false; 315 | #if defined(FMT_SUPPORT) 316 | ret.m_msg = fmt::format("Error: setsockopt(SO_SNDBUF) failed: errno {}", errno); 317 | #else 318 | std::array msg; 319 | (void)snprintf(msg.data(),msg.size(),"Error: setsockopt(SO_SNDBUF) failed: errno %d", errno); 320 | ret.m_msg = msg.data(); 321 | #endif 322 | return ret; 323 | } 324 | 325 | sockaddr_in localAddr {}; 326 | localAddr.sin_family = AF_INET; 327 | inet_pton(AF_INET,m_sockOptions.m_listenAddr.c_str(),&localAddr.sin_addr.s_addr); 328 | localAddr.sin_port = htons(localPort); 329 | 330 | if (m_socketCore.Bind(m_fd, reinterpret_cast(&localAddr), sizeof(localAddr)) < 0) { 331 | ret.m_success = false; 332 | #if defined(FMT_SUPPORT) 333 | ret.m_msg = fmt::format("Error: bind() failed: errno {}", errno); 334 | #else 335 | std::array msg; 336 | (void)snprintf(msg.data(),msg.size(),"Error: bind() failed: %d", errno); 337 | ret.m_msg = msg.data(); 338 | #endif 339 | return ret; 340 | } 341 | 342 | m_thread = std::thread(&UdpSocket::ReceiveTask, this); 343 | ret.m_success = true; 344 | return ret; 345 | } 346 | 347 | /** 348 | * @brief Send a message over UDP 349 | * 350 | * @param msg - pointer to the message data 351 | * @param size - length of the message data 352 | * @return SocketRet - indication that the message was sent successfully 353 | */ 354 | SocketRet sendMsg(const char *msg, size_t size) { 355 | SocketRet ret; 356 | // If destination addr/port specified 357 | if (m_sockaddr.sin_port != 0) { 358 | ssize_t numBytesSent = m_socketCore.SendTo( 359 | m_fd, &msg[0], size, 0, reinterpret_cast(&m_sockaddr), sizeof(m_sockaddr)); 360 | if (numBytesSent < 0) { // send failed 361 | ret.m_success = false; 362 | #if defined(FMT_SUPPORT) 363 | ret.m_msg = fmt::format("Error: sendto() failed: {}", errno); 364 | #else 365 | std::array msg; 366 | (void)snprintf(msg.data(),msg.size(),"Error: sendto() failed: %d",errno); 367 | ret.m_msg = msg.data(); 368 | #endif 369 | return ret; 370 | } 371 | if (static_cast(numBytesSent) < size) { // not all bytes were sent 372 | ret.m_success = false; 373 | #if defined(FMT_SUPPORT) 374 | ret.m_msg = fmt::format("Only {} bytes of {} was sent to client", numBytesSent, size); 375 | #else 376 | std::array msg; 377 | (void)snprintf(msg.data(), msg.size(), "Only %ld bytes out of %lu was sent to client", numBytesSent, size); 378 | ret.m_msg = msg.data(); 379 | #endif 380 | return ret; 381 | } 382 | } 383 | ret.m_success = true; 384 | return ret; 385 | } 386 | 387 | /** 388 | * @brief Shutdown the UDP socket 389 | */ 390 | void finish() { 391 | m_stop.store(true); 392 | if (m_thread.joinable()) { 393 | try { 394 | m_thread.join(); 395 | } 396 | catch (...) { 397 | } 398 | } 399 | if (m_fd != INVALID_SOCKET) { 400 | m_socketCore.Close(m_fd); 401 | } 402 | m_fd = INVALID_SOCKET; 403 | } 404 | 405 | private: 406 | /** 407 | * @brief Publish a UDP message received from a peer 408 | * 409 | * @param msg - pointer to the message data 410 | * @param msgSize - length of the message data 411 | */ 412 | void publishUdpMsg(const char *msg, size_t msgSize) { 413 | m_callback.onReceiveData(msg, msgSize); 414 | } 415 | 416 | /** 417 | * @brief The receive thread for receiving data from UDP peer(s). 418 | */ 419 | void ReceiveTask() { 420 | constexpr int64_t USEC_DELAY = 500000; 421 | while (!m_stop.load()) { 422 | if (m_fd != INVALID_SOCKET) { 423 | fd_set fds; 424 | struct timeval delay { 425 | 0, USEC_DELAY 426 | }; 427 | FD_ZERO(&fds); 428 | FD_SET(m_fd, &fds); 429 | int selectRet = m_socketCore.Select(m_fd + 1, &fds, nullptr, nullptr, &delay); 430 | if (selectRet <= 0) { // select failed or timeout 431 | if (m_stop) { 432 | break; 433 | } 434 | } else if (FD_ISSET(m_fd, &fds)) { 435 | 436 | std::array msg; 437 | ssize_t numOfBytesReceived = m_socketCore.Recv(m_fd, msg.data(), MAX_PACKET_SIZE, 0); 438 | // Note: recv() returning 0 can happen for zero-length datagrams 439 | if (numOfBytesReceived >= 0) { 440 | publishUdpMsg(msg.data(), static_cast(numOfBytesReceived)); 441 | } 442 | } 443 | } 444 | } 445 | } 446 | 447 | /** 448 | * @brief The remote or multicast socket address 449 | */ 450 | struct sockaddr_in m_sockaddr; 451 | 452 | /** 453 | * @brief The socket file descriptor 454 | */ 455 | SOCKET m_fd = INVALID_SOCKET; 456 | 457 | /** 458 | * @brief Indicator that the receive thread should exit 459 | */ 460 | std::atomic_bool m_stop; 461 | 462 | /** 463 | * @brief Pointer to the callback recipient 464 | */ 465 | CallbackImpl &m_callback; 466 | 467 | /** 468 | * @brief Handle of the receive thread 469 | */ 470 | std::thread m_thread; 471 | 472 | /** 473 | * @brief Socket options for SO_SNDBUF and SO_RCVBUF 474 | */ 475 | SocketOpt m_sockOptions; 476 | 477 | /** 478 | * @brief Interface for socket calls 479 | */ 480 | SocketImpl m_socketCore; 481 | 482 | /** 483 | * @brief Helper for hostname resolution 484 | */ 485 | AddrLookup m_addrLookup; 486 | }; 487 | 488 | } // Namespace sockets -------------------------------------------------------------------------------- /include/sockets-cpp/TcpServer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "SocketCommon.h" 3 | #include "SocketCore.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #if defined(FMT_SUPPORT) 19 | #include 20 | #endif 21 | namespace sockets { 22 | 23 | constexpr size_t MAX_PACKET_SIZE = 65536; 24 | 25 | constexpr uint32_t MSG_SIZE = 100; 26 | 27 | /** 28 | * @brief ClientHandle is an identifier which refers to a TCP client connection 29 | * established with this server. 30 | * 31 | */ 32 | using ClientHandle = int32_t; 33 | 34 | /** 35 | * @brief The TcpServer class encapsulates a TCP server supporting one or more TCP client connections 36 | * 37 | */ 38 | template 39 | class TcpServer { 40 | public: 41 | /** 42 | * @brief Construct a new TCP Server object 43 | * 44 | * @param callback - pointer to the callback recipient 45 | * @param options - optional socket options to specify SO_SNDBUF and SO_RCVBUF 46 | */ 47 | explicit TcpServer(CallbackImpl &callback, SocketOpt *options = nullptr) 48 | : m_serverAddress({}), m_clientAddress({}), m_fds({}), m_stop(false), m_callback(callback) { 49 | if (options != nullptr) { 50 | m_sockOptions = *options; 51 | } 52 | } 53 | 54 | TcpServer(const TcpServer &) = delete; 55 | TcpServer(TcpServer &&) = delete; 56 | 57 | /** 58 | * @brief Shutdown and destroy the TCP Server object 59 | */ 60 | ~TcpServer() { 61 | finish(); 62 | } 63 | 64 | TcpServer &operator=(const TcpServer &) = delete; 65 | TcpServer &operator=(TcpServer &&) = delete; 66 | 67 | #if defined(TEST_CORE_ACCESS) 68 | SocketImpl &getCore() { 69 | return m_socketCore; 70 | } 71 | #endif 72 | 73 | /** 74 | * @brief Start the TCP server listening on the specified port number 75 | * 76 | * @param port - port to listen on for connections 77 | * @return SocketRet - indicator of whether the server was started successfully 78 | */ 79 | SocketRet start(uint16_t port) { 80 | SocketRet ret; 81 | 82 | int result = m_socketCore.Initialize(); 83 | if (result != 0) { 84 | #if defined(FMT_SUPPORT) 85 | ret.m_msg = fmt::format("Error: Socket initialization failed: {}", result); 86 | #else 87 | std::array msg; 88 | (void)snprintf(msg.data(),msg.size(),"Error: Socket initialization failed: %d",result); 89 | ret.m_msg = msg.data(); 90 | #endif 91 | ret.m_success = false; 92 | return ret; 93 | } 94 | 95 | m_sockfd = m_socketCore.Socket(AF_INET, SOCK_STREAM, 0); 96 | if (m_sockfd == INVALID_SOCKET) { // socket failed 97 | ret.m_success = false; 98 | #if defined(FMT_SUPPORT) 99 | ret.m_msg = fmt::format("Error: Socket creation failed errno{}", errno); 100 | #else 101 | std::array msg; 102 | (void)snprintf(msg.data(),msg.size(),"Error: Socket creation failed: %d",errno); 103 | ret.m_msg = msg.data(); 104 | #endif 105 | return ret; 106 | } 107 | // set socket for reuse (otherwise might have to wait 4 minutes every time socket is closed) 108 | int option = 1; 109 | if (m_socketCore.SetSockOpt(m_sockfd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option)) < 0) { 110 | ret.m_success = false; 111 | #if defined(FMT_SUPPORT) 112 | ret.m_msg = fmt::format("Error: SetSockOpt(SO_REUSEADDR) failed: errno {}", errno); 113 | #else 114 | std::array msg; 115 | (void)snprintf(msg.data(),msg.size(),"Error: SetSockOpt(SO_REUSEADDR) failed: %d",errno); 116 | ret.m_msg = msg.data(); 117 | #endif 118 | return ret; 119 | } 120 | 121 | // set TX and RX buffer sizes 122 | if (m_socketCore.SetSockOpt(m_sockfd, SOL_SOCKET, SO_RCVBUF, reinterpret_cast(&m_sockOptions.m_rxBufSize), 123 | sizeof(m_sockOptions.m_rxBufSize)) < 0) { 124 | ret.m_success = false; 125 | #if defined(FMT_SUPPORT) 126 | ret.m_msg = fmt::format("Error: SetSockOpt(SO_RCVBUF) failed: errno {}", errno); 127 | #else 128 | std::array msg; 129 | (void)snprintf(msg.data(),msg.size(),"Error: SetSockOpt(SO_RCVBUF) failed: %d",errno); 130 | ret.m_msg = msg.data(); 131 | #endif 132 | return ret; 133 | } 134 | 135 | if (m_socketCore.SetSockOpt(m_sockfd, SOL_SOCKET, SO_SNDBUF, reinterpret_cast(&m_sockOptions.m_txBufSize), 136 | sizeof(m_sockOptions.m_txBufSize)) < 0) { 137 | ret.m_success = false; 138 | #if defined(FMT_SUPPORT) 139 | ret.m_msg = fmt::format("Error: SetSockOpt(SO_SNDBUF) failed: errno {}", errno); 140 | #else 141 | std::array msg; 142 | (void)snprintf(msg.data(),msg.size(),"Error: SetSockOpt(SO_SNDBUF) failed: %d",errno); 143 | ret.m_msg = msg.data(); 144 | #endif 145 | return ret; 146 | } 147 | 148 | memset(&m_serverAddress, 0, sizeof(m_serverAddress)); 149 | m_serverAddress.sin_family = AF_INET; 150 | inet_pton(AF_INET, m_sockOptions.m_listenAddr.c_str(),&m_serverAddress.sin_addr.s_addr); 151 | // m_serverAddress.sin_addr.s_addr = htonl(INADDR_ANY); 152 | m_serverAddress.sin_port = htons(port); 153 | 154 | int bindSuccess = 155 | m_socketCore.Bind(m_sockfd, reinterpret_cast(&m_serverAddress), sizeof(m_serverAddress)); 156 | if (bindSuccess == -1) { // bind failed 157 | ret.m_success = false; 158 | #if defined(FMT_SUPPORT) 159 | ret.m_msg = fmt::format("Error: errno {}", errno); 160 | #else 161 | std::array msg; 162 | (void)snprintf(msg.data(),msg.size(),"BInd error: %d",errno); 163 | ret.m_msg = msg.data(); 164 | #endif 165 | return ret; 166 | } 167 | const int clientsQueueSize = 5; 168 | int listenSuccess = m_socketCore.Listen(m_sockfd, clientsQueueSize); 169 | if (listenSuccess == -1) { // listen failed 170 | ret.m_success = false; 171 | #if defined(FMT_SUPPORT) 172 | ret.m_msg = fmt::format("Error: listen() failed errno {}", errno); 173 | #else 174 | std::array msg; 175 | (void)snprintf(msg.data(),msg.size(),"Error: listen() failed: %d",errno); 176 | ret.m_msg = msg.data(); 177 | #endif 178 | return ret; 179 | } 180 | ret.m_success = true; 181 | 182 | // Add the accept socket to m_fds 183 | FD_ZERO(&m_fds); 184 | FD_SET(m_sockfd, &m_fds); 185 | 186 | m_thread = std::thread(&TcpServer::serverTask, this); 187 | 188 | return ret; 189 | } 190 | 191 | /** 192 | * @brief Remove a TCP client connection 193 | * 194 | * @param handle - handle of the TCP client to be dropped 195 | * @return true 196 | * @return false 197 | */ 198 | bool deleteClient(ClientHandle &handle) { 199 | std::lock_guard guard(m_mutex); 200 | if (m_clients.count(handle) > 0) { 201 | Client &client = m_clients[handle]; 202 | 203 | // Close socket connection and remove from m_fds 204 | m_socketCore.Close(client.m_sockfd); 205 | FD_CLR(client.m_sockfd, &m_fds); 206 | 207 | m_clients.erase(handle); 208 | return true; 209 | } 210 | return false; 211 | } 212 | 213 | /** 214 | * @brief Send a broadcast message to all connected TCP clients 215 | * 216 | * @param msg - pointer to the message data 217 | * @param size - length of the message data 218 | * @return SocketRet - indication that the message was sent to all clients 219 | */ 220 | SocketRet sendBcast(const char *msg, size_t size) { 221 | SocketRet ret; 222 | ret.m_success = true; 223 | std::lock_guard guard(m_mutex); 224 | for (auto &client : m_clients) { 225 | auto clientRet = client.second.sendMsg(msg, size); 226 | ret.m_success &= clientRet.m_success; 227 | if (!clientRet.m_success) { 228 | ret.m_msg = clientRet.m_msg; 229 | break; 230 | } 231 | } 232 | return ret; 233 | } 234 | 235 | /** 236 | * @brief Send a message to a specific connected client 237 | * 238 | * @param client - handle of the TCP client 239 | * @param msg - pointer to the message data 240 | * @param size - length of the message data 241 | * @return SocketRet - indication that the message was sent to the client 242 | */ 243 | SocketRet sendClientMessage(ClientHandle &clientId, const char *msg, size_t size) { 244 | SocketRet ret; 245 | Client *client = nullptr; 246 | { 247 | std::lock_guard guard(m_mutex); 248 | if (m_clients.count(clientId) > 0) { 249 | client = &m_clients[clientId]; 250 | } 251 | } 252 | if (client != nullptr) { 253 | ret = client->sendMsg(msg, size); 254 | return ret; 255 | } 256 | #if defined(FMT_SUPPORT) 257 | ret.m_msg = fmt::format("Error: Client {} not found", clientId); 258 | #else 259 | std::array errMsg; 260 | (void)snprintf(errMsg.data(),errMsg.size(),"Error: Client %d not found",clientId); 261 | ret.m_msg = errMsg.data(); 262 | #endif 263 | ret.m_success = false; 264 | return ret; 265 | } 266 | 267 | /** 268 | * @brief Shut down the TCP server 269 | */ 270 | void finish() { 271 | m_stop = true; 272 | if (m_thread.joinable()) { 273 | m_stop = true; 274 | try { 275 | m_thread.join(); 276 | } 277 | catch (...) { 278 | } 279 | } 280 | 281 | // Close client sockets 282 | std::lock_guard guard(m_mutex); 283 | for (auto &client : m_clients) { 284 | m_socketCore.Close(client.second.m_sockfd); 285 | } 286 | 287 | // Close accept socket 288 | if (m_sockfd != INVALID_SOCKET) { 289 | m_socketCore.Close(m_sockfd); 290 | } 291 | m_sockfd = INVALID_SOCKET; 292 | m_clients.clear(); 293 | } 294 | 295 | /** 296 | * @brief Get current info for a client 297 | * 298 | * @param clientId - handle to this client connection 299 | * @param ipAddr - Client's IP address 300 | * @param port - Client's port number 301 | * @param connected - indicates client is connected 302 | * @return true - clientId is valid and information was returned 303 | * @return false - clientId is invalid 304 | */ 305 | bool getClientInfo(ClientHandle clientId, std::string &ipAddr, uint16_t &port, bool &connected) { 306 | std::lock_guard guard(m_mutex); 307 | if (m_clients.count(clientId) > 0) { 308 | Client &client = m_clients[clientId]; 309 | ipAddr = client.m_ip; 310 | port = client.m_port; 311 | connected = client.m_isConnected; 312 | return true; 313 | } 314 | return false; 315 | } 316 | 317 | private: 318 | /** 319 | * @brief Client represents a connection to a TCP client 320 | */ 321 | struct Client { 322 | SocketImpl *m_socketCore; 323 | 324 | /** 325 | * @brief The TCP client's IP address 326 | */ 327 | std::string m_ip; 328 | 329 | /** 330 | * @brief The socket file descriptor for the TCP client connection 331 | */ 332 | SOCKET m_sockfd = INVALID_SOCKET; 333 | 334 | /** 335 | * @brief The peer's port 336 | */ 337 | uint16_t m_port = 0; 338 | 339 | /** 340 | * @brief Indicator whether TCP client is connected 341 | */ 342 | bool m_isConnected = false; 343 | 344 | /** 345 | * @brief Construct a new Client object 346 | * 347 | * @param ipAddr - client's IP address 348 | * @param clientFd - file descriptor for the client connection 349 | * @param port - client's port number 350 | */ 351 | Client(SocketImpl *socketImpl, const char *ipAddr, SOCKET clientFd, uint16_t port) 352 | : m_socketCore(socketImpl), m_ip(ipAddr), m_sockfd(clientFd), m_port(port), m_isConnected(true) { 353 | } 354 | 355 | /** 356 | * @brief Construct a new Client object 357 | */ 358 | Client() : m_socketCore(nullptr), m_sockfd(INVALID_SOCKET) { 359 | } 360 | 361 | /** 362 | * @brief Send a message to this TCP client 363 | * 364 | * @param msg - pointer to the message data 365 | * @param size - length of the message data 366 | * @return SocketRet - indication of whether the message was sent successfully 367 | */ 368 | SocketRet sendMsg(const char *msg, size_t size) { 369 | SocketRet ret; 370 | if (m_sockfd != INVALID_SOCKET) { 371 | ssize_t numBytesSent = m_socketCore->Send(m_sockfd, reinterpret_cast(msg), size, 0); 372 | if (numBytesSent < 0) { // send failed 373 | ret.m_success = false; 374 | #if defined(FMT_SUPPORT) 375 | ret.m_msg = fmt::format("Error: send() failed errno {}", errno); 376 | #else 377 | std::array msg; 378 | (void)snprintf(msg.data(),msg.size(),"Error: send() failed: %d",errno); 379 | ret.m_msg = msg.data(); 380 | #endif 381 | return ret; 382 | } 383 | if (static_cast(numBytesSent) < size) { // not all bytes were sent 384 | ret.m_success = false; 385 | #if defined(FMT_SUPPORT) 386 | ret.m_msg = fmt::format("Only {} bytes out of {} was sent to client", numBytesSent, size); 387 | #else 388 | std::array msg; 389 | (void)snprintf(msg.data(), msg.size(), "Only %ld bytes out of %lu was sent to client", numBytesSent, size); 390 | ret.m_msg = msg.data(); 391 | #endif 392 | return ret; 393 | } 394 | } 395 | ret.m_success = true; 396 | return ret; 397 | } 398 | }; 399 | 400 | /** 401 | * @brief Publish data received from a TCP client 402 | * 403 | * @param client - handle of the TCP client which sent the data 404 | * @param msg - pointer to the message data 405 | * @param msgSize - length of the message data 406 | */ 407 | void publishClientMsg(const ClientHandle &client, const char *msg, size_t msgSize) { 408 | m_callback.onReceiveClientData(client, msg, msgSize); 409 | } 410 | 411 | /** 412 | * @brief Publish notification that a TCP client has disconnected 413 | * 414 | * @param client - handle of the TCP client which has disconnected 415 | */ 416 | void publishDisconnected(const ClientHandle &client) { 417 | SocketRet ret; 418 | #if defined(FMT_SUPPORT) 419 | ret.m_msg = fmt::format("Client {} disconnected", client); 420 | #else 421 | std::array msg; 422 | (void)snprintf(msg.data(),msg.size(),"Client %d disconnected",client); 423 | ret.m_msg = msg.data(); 424 | #endif 425 | m_callback.onClientDisconnect(client, ret); 426 | } 427 | 428 | /** 429 | * @brief Publish notification of a new TCP client connection 430 | * 431 | * @param client - handle of the new TCP client 432 | */ 433 | void publishClientConnect(const ClientHandle &client) { 434 | m_callback.onClientConnect(client); 435 | } 436 | 437 | /** 438 | * @brief Find the maximum file descriptor among listen socket and all client sockets for 439 | * use by select() 440 | * @return int 441 | */ 442 | int findMaxFd() { 443 | int maxfd = m_sockfd; 444 | for (const auto &client : m_clients) { 445 | maxfd = std::max(maxfd, client.second.m_sockfd); 446 | } 447 | return maxfd + 1; 448 | } 449 | 450 | /** 451 | * @brief Thread handling all accept requests and reception of data from connected clients 452 | */ 453 | void serverTask() { 454 | constexpr int64_t USEC_DELAY = 500000; 455 | std::array msg; 456 | 457 | while (!m_stop.load()) { 458 | struct timeval delay { 459 | 0, USEC_DELAY 460 | }; 461 | fd_set read_set = m_fds; 462 | int maxfds = findMaxFd(); 463 | int selectRet = m_socketCore.Select(maxfds, &read_set, nullptr, nullptr, &delay); 464 | if (selectRet <= 0) { 465 | // select() failed or timed out, so retry after a shutdown check 466 | continue; 467 | } 468 | for (int fd = 0; fd < maxfds; fd++) { 469 | if (FD_ISSET(fd, &read_set)) { 470 | Client *client = nullptr; 471 | { 472 | std::lock_guard guard(m_mutex); 473 | if (m_clients.count(fd) > 0) { 474 | client = &m_clients[fd]; 475 | } 476 | } 477 | if (client != nullptr) { 478 | // data on client socket 479 | ssize_t numOfBytesReceived = m_socketCore.Recv(fd, msg.data(), MAX_PACKET_SIZE, 0); 480 | if (numOfBytesReceived < 1) { 481 | client->m_isConnected = false; 482 | if (numOfBytesReceived == 0) { // client closed connection 483 | deleteClient(fd); 484 | publishDisconnected(fd); 485 | } 486 | } else { 487 | publishClientMsg(fd, msg.data(), static_cast(numOfBytesReceived)); 488 | } 489 | } else { 490 | // data on accept socket 491 | socklen_t sosize = sizeof(m_clientAddress); 492 | int clientfd = m_socketCore.Accept(fd, reinterpret_cast(&m_clientAddress), &sosize); 493 | if (clientfd == -1) { 494 | // accept() failed 495 | } else { 496 | std::array addr; 497 | inet_ntop(AF_INET, &m_clientAddress.sin_addr, addr.data(), INET_ADDRSTRLEN); 498 | { 499 | std::lock_guard guard(m_mutex); 500 | m_clients.emplace(clientfd, 501 | Client(&m_socketCore, addr.data(), clientfd, 502 | static_cast(ntohs(m_clientAddress.sin_port)))); 503 | } 504 | FD_SET(clientfd, &m_fds); 505 | publishClientConnect(clientfd); 506 | } 507 | } 508 | } 509 | } 510 | } 511 | } 512 | 513 | /** 514 | * @brief The socket file descriptor used for accepting connections 515 | */ 516 | SOCKET m_sockfd = INVALID_SOCKET; 517 | 518 | /** 519 | * @brief The server socket address 520 | */ 521 | struct sockaddr_in m_serverAddress; 522 | 523 | /** 524 | * @brief The client socket address when a connection is accepted 525 | */ 526 | struct sockaddr_in m_clientAddress; 527 | 528 | /** 529 | * @brief The set of file descriptor(s) for accepting connections and receiving data 530 | */ 531 | fd_set m_fds = {}; 532 | 533 | /** 534 | * @brief Flag to stop the server thread 535 | */ 536 | std::atomic_bool m_stop; 537 | 538 | /** 539 | * @brief The collection of connected TCP clients 540 | */ 541 | std::unordered_map m_clients; 542 | 543 | /** 544 | * @brief Mutex protecting m_clients 545 | */ 546 | std::mutex m_mutex; 547 | 548 | /** 549 | * @brief The registered callback recipient 550 | */ 551 | CallbackImpl &m_callback; 552 | 553 | /** 554 | * @brief Server thread 555 | */ 556 | std::thread m_thread; 557 | 558 | /** 559 | * @brief Socket options for SO_SNDBUF and SO_RCVBUF 560 | */ 561 | SocketOpt m_sockOptions; 562 | 563 | /** 564 | * @brief Interface for socket calls 565 | */ 566 | SocketImpl m_socketCore; 567 | }; 568 | 569 | } // Namespace sockets --------------------------------------------------------------------------------