├── test ├── main.cpp ├── exception_tests.cpp ├── dns_tests.cpp ├── CMakeLists.txt ├── ip_address_tests.cpp ├── context_tests.cpp ├── endpoint_tests.cpp └── socket_tests.cpp ├── include └── net │ ├── exception.hpp │ ├── export.hpp │ ├── dns.hpp │ ├── ip_address.hpp │ ├── endpoint.hpp │ ├── context.hpp │ ├── native.hpp │ └── socket.hpp ├── src ├── exception.cpp ├── CMakeLists.txt ├── context.cpp ├── ip_address.cpp ├── dns.cpp ├── endpoint.cpp ├── native.cpp └── socket.cpp ├── .github └── workflows │ ├── windows.yml │ ├── cov.yml │ └── linux.yml ├── .gitignore ├── LICENSE ├── CMakeLists.txt ├── README_ko_KR.md └── README.md /test/main.cpp: -------------------------------------------------------------------------------- 1 | #include "gtest/gtest.h" 2 | #include "net/native.hpp" 3 | 4 | int main(int argc, char **argv) { 5 | EXPECT_EQ(net::native::initialize(), true); 6 | ::testing::InitGoogleTest(&argc, argv); 7 | return RUN_ALL_TESTS(); 8 | } 9 | -------------------------------------------------------------------------------- /include/net/exception.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "net/export.hpp" 6 | 7 | namespace net 8 | { 9 | class exception final : public std::exception 10 | { 11 | public: 12 | NETCPP_API explicit exception(std::string_view msg); 13 | 14 | [[nodiscard]] NETCPP_API const char* what() const noexcept override; 15 | [[nodiscard]] NETCPP_API static int get_code(); 16 | 17 | private: 18 | std::string _msg; 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /include/net/export.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef _WIN32 4 | #ifdef NETCPP_BUILD_SHARED 5 | #define NETCPP_API __declspec(dllexport) 6 | #pragma warning(disable: 4251) 7 | #elif NETCPP_SHARED 8 | #define NETCPP_API __declspec(dllimport) 9 | #endif 10 | #else 11 | #ifdef NETCPP_BUILD_SHARED 12 | #define NETCPP_API __attribute__((__visibility__("default"))) 13 | #elif NETCPP_SHARED 14 | #define NETCPP_API __attribute__((__visibility__("default"))) 15 | #endif 16 | #endif 17 | #ifndef NETCPP_API 18 | #define NETCPP_API 19 | #endif -------------------------------------------------------------------------------- /include/net/dns.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "net/export.hpp" 7 | 8 | namespace net 9 | { 10 | struct host_entry 11 | { 12 | std::vector address_list; 13 | std::vector alias_list; 14 | std::string host_name; 15 | }; 16 | 17 | class NETCPP_API dns 18 | { 19 | public: 20 | static std::string get_host_name(); 21 | static host_entry get_host_entry(std::string_view hostname); 22 | static host_entry get_host_entry(ip_address host); 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /test/exception_tests.cpp: -------------------------------------------------------------------------------- 1 | #include "gtest/gtest.h" 2 | #include "net/exception.hpp" 3 | #include "net/socket.hpp" 4 | 5 | TEST(exception, what) 6 | { 7 | try { 8 | net::socket sock; 9 | EXPECT_EQ(sock.is_open(), false); 10 | EXPECT_EQ(sock.listen(), false); 11 | throw net::exception("listen()"); 12 | } 13 | catch (net::exception& e) 14 | { 15 | EXPECT_NE(e.what(), ""); 16 | } 17 | } 18 | 19 | TEST(exception, get_code) 20 | { 21 | net::socket sock; 22 | EXPECT_EQ(sock.is_open(), false); 23 | EXPECT_EQ(sock.listen(), false); 24 | net::exception err("listen()"); 25 | EXPECT_NE(err.get_code(), 0); 26 | } -------------------------------------------------------------------------------- /src/exception.cpp: -------------------------------------------------------------------------------- 1 | #include "net/exception.hpp" 2 | #include "net/native.hpp" 3 | 4 | #include 5 | 6 | using namespace net; 7 | 8 | exception::exception(std::string_view msg) 9 | { 10 | const auto error = 11 | #ifdef _WIN32 12 | WSAGetLastError(); 13 | #else 14 | errno; 15 | #endif 16 | std::stringstream ss; 17 | ss << msg << ": " << std::system_category().message(error); 18 | _msg = ss.str(); 19 | } 20 | 21 | char const *exception::what() const noexcept 22 | { 23 | return _msg.c_str(); 24 | } 25 | 26 | int exception::get_code() 27 | { 28 | #ifdef _WIN32 29 | return WSAGetLastError(); 30 | #else 31 | return errno; 32 | #endif 33 | } 34 | -------------------------------------------------------------------------------- /.github/workflows/windows.yml: -------------------------------------------------------------------------------- 1 | name: windows 2 | 3 | on: 4 | pull_request: 5 | branches: [ "release" ] 6 | push: 7 | branches: [ "release" ] 8 | 9 | env: 10 | BUILD_TYPE: Release 11 | 12 | jobs: 13 | windows: 14 | runs-on: windows-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | - name: Configure CMake 19 | run: | 20 | cmake -B ${{github.workspace}}/build ` 21 | -DNETCPP_TEST=ON 22 | 23 | - name: Build 24 | run: cmake --build ${{github.workspace}}\build --config ${{env.BUILD_TYPE}} 25 | 26 | - name: Google Test 27 | working-directory: ${{github.workspace}}\build\bin\${{env.BUILD_TYPE}} 28 | run: .\unitTest.exe -------------------------------------------------------------------------------- /include/net/ip_address.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "net/native.hpp" 6 | #include "net/export.hpp" 7 | 8 | namespace net 9 | { 10 | class NETCPP_API ip_address : sockaddr_in 11 | { 12 | friend class endpoint; 13 | friend class dns; 14 | friend class socket; 15 | public: 16 | static ip_address any; 17 | static ip_address none; 18 | static ip_address loopback; 19 | static ip_address broadcast; 20 | public: 21 | ip_address(); 22 | explicit ip_address(const sockaddr_in& addr); 23 | public: 24 | static bool try_parse(std::string_view ipStr, ip_address* addr); 25 | static ip_address parse(int ipNum); 26 | public: 27 | bool operator==(const ip_address& ipAdr) const; 28 | bool operator==(ip_address&& ipAdr) const; 29 | public: 30 | [[nodiscard]] std::string to_string() const; 31 | }; 32 | } // namespace net 33 | -------------------------------------------------------------------------------- /include/net/endpoint.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "net/ip_address.hpp" 6 | #include "net/native.hpp" 7 | #include "net/export.hpp" 8 | 9 | namespace net 10 | { 11 | class NETCPP_API endpoint 12 | { 13 | public: 14 | endpoint() = default; 15 | endpoint(ip_address address, unsigned short port); 16 | public: 17 | [[nodiscard]] const ip_address& get_address() const; 18 | [[nodiscard]] int get_port() const; 19 | void set_address(ip_address address); 20 | void set_port(unsigned short port); 21 | 22 | [[nodiscard]] std::string to_string() const; 23 | public: 24 | static endpoint parse(sockaddr_in addr); 25 | static bool try_parse(std::string_view s, endpoint* ep); 26 | public: 27 | bool operator==(const endpoint& endpoint) const; 28 | bool operator==(endpoint&& endpoint) const; 29 | private: 30 | ip_address _address; 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /test/dns_tests.cpp: -------------------------------------------------------------------------------- 1 | #include "gtest/gtest.h" 2 | #include "net/dns.hpp" 3 | #include "net/ip_address.hpp" 4 | 5 | TEST(dns, get_host_name) 6 | { 7 | EXPECT_NE(net::dns::get_host_name(), ""); 8 | } 9 | 10 | TEST(dns, get_host_entry_url) 11 | { 12 | auto youtubeEntry = net::dns::get_host_entry("www.youtube.com"); 13 | EXPECT_GT(youtubeEntry.address_list.size(), 0); 14 | EXPECT_GT(youtubeEntry.alias_list.size(), 0); 15 | } 16 | 17 | TEST(dns, get_host_entry_address) 18 | { 19 | auto entryByAddress = net::dns::get_host_entry(net::ip_address::loopback); 20 | EXPECT_GT(entryByAddress.address_list.size(), 0); 21 | } 22 | 23 | TEST(dns, get_host_entry_url_failure) 24 | { 25 | auto entry = net::dns::get_host_entry(".com"); 26 | EXPECT_EQ(entry.address_list.size(), 0); 27 | } 28 | 29 | TEST(dns, get_host_entry_address_failure) 30 | { 31 | auto entry = net::dns::get_host_entry(net::ip_address::none); 32 | EXPECT_EQ(entry.address_list.size(), 0); 33 | } 34 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB_RECURSE TEST_FILES "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp") 2 | 3 | set(BINARY_NAME "unitTest") 4 | 5 | option(gtest_disable_pthreads ON) 6 | 7 | add_executable(${BINARY_NAME} ${TEST_FILES} ${SOURCE_FILES}) 8 | 9 | target_compile_definitions(${BINARY_NAME} PRIVATE NETCPP_STATIC) 10 | 11 | target_include_directories(${BINARY_NAME} PRIVATE ${INCLUDES}) 12 | 13 | target_link_libraries(${BINARY_NAME} PRIVATE gtest) 14 | 15 | if (MSVC) 16 | target_link_libraries(${BINARY_NAME} PRIVATE ws2_32) 17 | else () 18 | target_include_directories(${BINARY_NAME} PRIVATE ${URING_INCLUDE_DIRS}) 19 | target_link_libraries(${BINARY_NAME} PRIVATE ${URING_LINK_LIBRARIES}) 20 | 21 | if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 22 | set(CMAKE_CXX_FLAGS "--coverage") 23 | elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") 24 | target_link_libraries(${BINARY_NAME} PRIVATE atomic) 25 | set(CMAKE_CXX_FLAGS "-fprofile-instr-generate -fcoverage-mapping") 26 | endif () 27 | endif () 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/c++,cmake 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=c++,cmake 3 | 4 | ### C++ ### 5 | # Prerequisites 6 | *.d 7 | 8 | # Compiled Object files 9 | *.slo 10 | *.lo 11 | *.o 12 | *.obj 13 | 14 | # Precompiled Headers 15 | *.gch 16 | *.pch 17 | 18 | # Compiled Dynamic libraries 19 | *.so 20 | *.dylib 21 | *.dll 22 | 23 | # Fortran module files 24 | *.mod 25 | *.smod 26 | 27 | # Compiled Static libraries 28 | *.lai 29 | *.la 30 | *.a 31 | *.lib 32 | 33 | # Executables 34 | *.exe 35 | *.out 36 | *.app 37 | 38 | ### CMake ### 39 | CMakeLists.txt.user 40 | CMakeCache.txt 41 | CMakeFiles 42 | CMakeScripts 43 | Testing 44 | Makefile 45 | cmake_install.cmake 46 | install_manifest.txt 47 | compile_commands.json 48 | CTestTestfile.cmake 49 | _deps 50 | 51 | ### CMake Patch ### 52 | # External projects 53 | *-prefix/ 54 | 55 | .vs/ 56 | .idea/ 57 | 58 | cmake-build-*/ 59 | out/ 60 | 61 | # End of https://www.toptal.com/developers/gitignore/api/c++,cmake -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) 2 | 3 | # Build 4 | if (NETCPP_BUILD_SHARED) 5 | add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES}) 6 | target_compile_definitions(${PROJECT_NAME} PRIVATE NETCPP_BUILD_SHARED INTERFACE NETCPP_SHARED) 7 | else () 8 | add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES}) 9 | target_compile_definitions(${PROJECT_NAME} PRIVATE NETCPP_STATIC) 10 | endif () 11 | 12 | add_library(${EXPORT_NAMESPACE}${PROJECT_NAME} ALIAS ${PROJECT_NAME}) 13 | target_include_directories(${PROJECT_NAME} PUBLIC 14 | $ 15 | $) 16 | 17 | if (MSVC) 18 | if (NETCPP_BUILD_SHARED) 19 | target_link_libraries(${PROJECT_NAME} PUBLIC ws2_32) 20 | else () 21 | target_link_libraries(${PROJECT_NAME} PRIVATE ws2_32) 22 | endif () 23 | else () 24 | target_include_directories(${PROJECT_NAME} PRIVATE ${URING_INCLUDE_DIRS}) 25 | target_link_libraries(${PROJECT_NAME} PRIVATE ${URING_LINK_LIBRARIES}) 26 | endif () -------------------------------------------------------------------------------- /test/ip_address_tests.cpp: -------------------------------------------------------------------------------- 1 | #include "gtest/gtest.h" 2 | #include "net/ip_address.hpp" 3 | 4 | TEST(ip_address, try_parse) 5 | { 6 | net::ip_address addr; 7 | EXPECT_EQ(net::ip_address::try_parse("127.0.0.1", &addr), true); 8 | } 9 | 10 | TEST(ip_address, try_parse_failure) 11 | { 12 | EXPECT_EQ(net::ip_address::try_parse("127.0.0.1", nullptr), false); 13 | } 14 | 15 | TEST(ip_address, parse) 16 | { 17 | auto addr = net::ip_address::parse(INADDR_LOOPBACK); 18 | EXPECT_EQ(addr.to_string(), "127.0.0.1"); 19 | } 20 | 21 | TEST(ip_address, to_string) 22 | { 23 | auto addr = net::ip_address::loopback; 24 | EXPECT_EQ(addr.to_string(), "127.0.0.1"); 25 | } 26 | 27 | TEST(ip_address, operator_equal_lvalue) 28 | { 29 | auto addr = net::ip_address::loopback; 30 | EXPECT_EQ(addr.to_string(), "127.0.0.1"); 31 | EXPECT_EQ(addr == net::ip_address::loopback, true); 32 | } 33 | 34 | TEST(ip_address, operator_equal_rvalue) 35 | { 36 | auto addr = net::ip_address::loopback; 37 | EXPECT_EQ(addr.to_string(), "127.0.0.1"); 38 | EXPECT_EQ(net::ip_address::loopback == std::move(addr), true); 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 INDEX 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/cov.yml: -------------------------------------------------------------------------------- 1 | name: Update coverage 2 | on: 3 | push: 4 | branches: [ "release" ] 5 | 6 | env: 7 | BUILD_TYPE: Release 8 | 9 | jobs: 10 | codecov: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | 15 | - name: Setup compiler 16 | run: | 17 | sudo apt-get update 18 | sudo apt-get install -y g++-10 19 | 20 | - name: Setup dependencies 21 | run: | 22 | sudo apt-get update 23 | sudo apt-get install -y liburing-dev 24 | 25 | - name: Configure CMake 26 | run: | 27 | cmake -B ${{github.workspace}}/build \ 28 | -DCMAKE_CXX_COMPILER=g++-10 \ 29 | -DNETCPP_TEST=ON 30 | 31 | - name: Build 32 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} 33 | 34 | - name: Google Test 35 | working-directory: ${{github.workspace}}/build/bin 36 | run: ./unitTest 37 | 38 | - name: Upload coverage reports to Codecov 39 | uses: codecov/codecov-action@v4.0.1 40 | with: 41 | token: ${{ secrets.CODECOV_TOKEN }} 42 | -------------------------------------------------------------------------------- /test/context_tests.cpp: -------------------------------------------------------------------------------- 1 | #include "gtest/gtest.h" 2 | #include "net/context.hpp" 3 | 4 | TEST(context, set_buffer_ptr) 5 | { 6 | char buffer[128] = ""; 7 | net::context ctx; 8 | ctx.set_buffer(buffer, 0, 128); 9 | } 10 | 11 | TEST(context, set_buffer_span) 12 | { 13 | char buffer[128] = ""; 14 | net::context ctx; 15 | ctx.set_buffer(buffer); 16 | } 17 | 18 | TEST(context, add_data_ptr) 19 | { 20 | char buffer[128] = ""; 21 | net::context ctx; 22 | ctx.add_data(buffer, 0, sizeof(buffer)); 23 | } 24 | 25 | TEST(context, add_data_span) 26 | { 27 | char buffer[128] = ""; 28 | net::context ctx; 29 | ctx.add_data(buffer); 30 | } 31 | 32 | TEST(context, set_buffer_ptr_failure) 33 | { 34 | net::context ctx; 35 | 36 | char buffer[128] = ""; 37 | ctx.add_data(buffer); 38 | 39 | try 40 | { 41 | ctx.set_buffer(buffer, 0, 128); 42 | } 43 | catch (std::exception& e) 44 | { 45 | EXPECT_NE(e.what(), ""); 46 | return; 47 | } 48 | EXPECT_EQ(true, false); 49 | } 50 | 51 | TEST(context, set_buffer_span_failure) 52 | { 53 | net::context ctx; 54 | 55 | char buffer[128] = ""; 56 | ctx.add_data(buffer); 57 | 58 | try 59 | { 60 | ctx.set_buffer(buffer); 61 | } 62 | catch (std::exception& e) 63 | { 64 | EXPECT_NE(e.what(), ""); 65 | return; 66 | } 67 | EXPECT_EQ(true, false); 68 | } 69 | -------------------------------------------------------------------------------- /include/net/context.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "export.hpp" 7 | #include "net/native.hpp" 8 | #include "net/socket.hpp" 9 | 10 | namespace net 11 | { 12 | enum class io_type 13 | { 14 | none, 15 | accept, 16 | connect, 17 | disconnect, 18 | send, 19 | receive 20 | }; 21 | 22 | class NETCPP_API context 23 | #ifdef _WIN32 24 | : private OVERLAPPED 25 | #endif 26 | { 27 | friend class socket; 28 | friend class native; 29 | 30 | using callback = std::function; 31 | public: 32 | context(); 33 | ~context(); 34 | public: 35 | callback completed; 36 | public: 37 | void set_buffer(char* buffer, int offset, int count); 38 | void set_buffer(std::span buffer); 39 | 40 | void add_data(char* buffer, int offset, int count); 41 | void add_data(std::span buffer); 42 | public: 43 | std::shared_ptr accept_socket; 44 | std::optional endpoint; 45 | u_long length; 46 | private: 47 | void init(); 48 | private: 49 | std::span _buffer; 50 | #ifdef _WIN32 51 | std::vector _buffer_list; 52 | #else 53 | std::vector _buffer_list; 54 | #endif 55 | 56 | void* _token; 57 | io_type _io_type; 58 | }; 59 | } 60 | -------------------------------------------------------------------------------- /src/context.cpp: -------------------------------------------------------------------------------- 1 | #include "net/context.hpp" 2 | 3 | #include 4 | 5 | using namespace net; 6 | 7 | context::context() : 8 | #if _WIN32 9 | OVERLAPPED(), 10 | #endif 11 | completed([](context *, bool) {}), length(0), _token(nullptr), _io_type(io_type::none) 12 | { 13 | init(); 14 | } 15 | 16 | void context::init() 17 | { 18 | #ifdef _WIN32 19 | ZeroMemory(this, sizeof(OVERLAPPED)); 20 | #endif 21 | _io_type = io_type::none; 22 | } 23 | 24 | context::~context() = default; 25 | 26 | void context::set_buffer(char* buffer, int offset, int count) 27 | { 28 | if (!_buffer_list.empty()) 29 | throw std::runtime_error("Can't be use with `buffer_list`"); 30 | 31 | _buffer = std::span(buffer + offset, buffer + offset + count); 32 | } 33 | 34 | void context::set_buffer(std::span buffer) 35 | { 36 | if (!_buffer_list.empty()) 37 | throw std::runtime_error("Can't be use with `buffer_list`"); 38 | 39 | _buffer = buffer; 40 | } 41 | 42 | void context::add_data(char* buffer, int offset, int count) 43 | { 44 | #ifdef _WIN32 45 | _buffer_list.emplace_back(static_cast(count), buffer + offset); 46 | #else 47 | _buffer_list.push_back({buffer + offset, static_cast(count)}); 48 | #endif 49 | } 50 | 51 | void context::add_data(std::span buffer) 52 | { 53 | #ifdef _WIN32 54 | _buffer_list.emplace_back(static_cast(buffer.size()), buffer.data()); 55 | #else 56 | _buffer_list.push_back({buffer.data(), buffer.size()}); 57 | #endif 58 | } 59 | -------------------------------------------------------------------------------- /.github/workflows/linux.yml: -------------------------------------------------------------------------------- 1 | name: linux 2 | 3 | on: 4 | pull_request: 5 | branches: [ "release" ] 6 | push: 7 | branches: [ "release" ] 8 | 9 | env: 10 | BUILD_TYPE: Release 11 | 12 | jobs: 13 | linux: 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | compiler: 18 | - { cc: "gcc-10", cxx: "g++-10", os: "ubuntu-22.04" } 19 | - { cc: "clang-12", cxx: "clang++-12", os: "ubuntu-22.04" } 20 | 21 | name: ${{ matrix.compiler.cc }} 22 | runs-on: ${{ matrix.compiler.os }} 23 | steps: 24 | - uses: actions/checkout@v4 25 | 26 | - name: Setup Compiler 27 | run: | 28 | if [[ "${{ matrix.compiler.cc }}" == "clang"* ]]; then 29 | sudo apt-get update 30 | sudo apt-get install -y ${{ matrix.compiler.cc }} 31 | else 32 | sudo apt-get update 33 | sudo apt-get install -y ${{ matrix.compiler.cxx }} 34 | fi 35 | 36 | - name: Setup dependencies 37 | run: sudo apt-get install liburing-dev 38 | 39 | - name: Configure CMake 40 | run: | 41 | cmake -B ${{github.workspace}}/build \ 42 | -DCMAKE_CXX_COMPILER=${{ matrix.compiler.cxx }} \ 43 | -DNETCPP_TEST=ON 44 | 45 | - name: Build 46 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} 47 | 48 | - name: Google Test 49 | working-directory: ${{github.workspace}}/build/bin 50 | run: ./unitTest 51 | -------------------------------------------------------------------------------- /src/ip_address.cpp: -------------------------------------------------------------------------------- 1 | #include "net/ip_address.hpp" 2 | 3 | using namespace net; 4 | 5 | ip_address ip_address::any = parse(INADDR_ANY); 6 | ip_address ip_address::none = parse(INADDR_NONE); 7 | ip_address ip_address::loopback = parse(INADDR_LOOPBACK); 8 | ip_address ip_address::broadcast = parse(INADDR_BROADCAST); 9 | 10 | bool ip_address::try_parse(std::string_view ipStr, ip_address *addr) 11 | { 12 | if (!addr) 13 | return false; 14 | return SOCKET_ERROR != inet_pton(AF_INET, ipStr.data(), &addr->sin_addr); 15 | } 16 | 17 | ip_address ip_address::parse(int ipNum) 18 | { 19 | ip_address addr{}; 20 | ZeroMemory(&addr, sizeof(sockaddr_in)); 21 | 22 | addr.sin_addr.s_addr = htonl(ipNum); 23 | 24 | return addr; 25 | } 26 | 27 | ip_address::ip_address() : sockaddr_in() 28 | { 29 | ZeroMemory(this, sizeof(sockaddr_in)); 30 | sin_family = AF_INET; 31 | } 32 | 33 | ip_address::ip_address(const sockaddr_in& adrs) : sockaddr_in() 34 | { 35 | sin_addr = adrs.sin_addr; 36 | sin_family = AF_INET; 37 | sin_port = htons(adrs.sin_port); 38 | } 39 | 40 | std::string ip_address::to_string() const 41 | { 42 | char ipStrBuf[16] = ""; 43 | inet_ntop(AF_INET, &sin_addr, ipStrBuf, 16); 44 | return ipStrBuf; 45 | } 46 | 47 | bool ip_address::operator==(const ip_address& ipAdr) const 48 | { 49 | return 0 == memcmp(this, &ipAdr, sizeof(sockaddr_in)); 50 | } 51 | 52 | bool ip_address::operator==(ip_address&& ipAdr) const 53 | { 54 | return 0 == memcmp(this, &ipAdr, sizeof(sockaddr_in)); 55 | } 56 | -------------------------------------------------------------------------------- /src/dns.cpp: -------------------------------------------------------------------------------- 1 | #include "net/dns.hpp" 2 | #include "net/ip_address.hpp" 3 | 4 | using namespace net; 5 | 6 | std::string dns::get_host_name() 7 | { 8 | char buf[128] = ""; 9 | gethostname(buf, 128); 10 | return buf; 11 | } 12 | 13 | host_entry dns::get_host_entry(std::string_view host) 14 | { 15 | auto entry = gethostbyname(host.data()); 16 | if (entry == nullptr) 17 | return {}; 18 | 19 | host_entry hostEntry{.host_name = entry->h_name}; 20 | for (int i = 0; entry->h_addr_list[i] != nullptr; ++i) 21 | { 22 | sockaddr_in addr_in{}; 23 | addr_in.sin_addr.s_addr = *reinterpret_cast(entry->h_addr_list[i]); 24 | hostEntry.address_list.emplace_back(addr_in); 25 | } 26 | 27 | for (int i = 0; entry->h_aliases[i] != nullptr; ++i) 28 | { 29 | hostEntry.alias_list.emplace_back(entry->h_aliases[i]); 30 | } 31 | 32 | return hostEntry; 33 | } 34 | host_entry dns::get_host_entry(net::ip_address host) 35 | { 36 | auto entry = gethostbyaddr(reinterpret_cast(&host.sin_addr), sizeof(in_addr), AF_INET); 37 | if (entry == nullptr) 38 | return {}; 39 | 40 | host_entry hostEntry{.host_name = entry->h_name}; 41 | for (int i = 0; entry->h_addr_list[i] != nullptr; ++i) 42 | { 43 | sockaddr_in addr_in{}; 44 | addr_in.sin_addr.s_addr = *reinterpret_cast(entry->h_addr_list[i]); 45 | hostEntry.address_list.emplace_back(addr_in); 46 | } 47 | for (int i = 0; entry->h_aliases[i] != nullptr; ++i) 48 | { 49 | hostEntry.alias_list.emplace_back(entry->h_aliases[i]); 50 | } 51 | 52 | return hostEntry; 53 | } -------------------------------------------------------------------------------- /src/endpoint.cpp: -------------------------------------------------------------------------------- 1 | #include "net/endpoint.hpp" 2 | 3 | using namespace net; 4 | 5 | endpoint::endpoint(ip_address address, u_short port) : _address(address) 6 | { 7 | _address.sin_family = AF_INET; 8 | _address.sin_port = htons(port); 9 | } 10 | 11 | const ip_address& endpoint::get_address() const 12 | { 13 | return _address; 14 | } 15 | 16 | void endpoint::set_address(ip_address address) 17 | { 18 | _address = address; 19 | } 20 | 21 | int endpoint::get_port() const 22 | { 23 | return ntohs(_address.sin_port); 24 | } 25 | 26 | void endpoint::set_port(u_short port) 27 | { 28 | _address.sin_port = htons(port); 29 | } 30 | 31 | std::string endpoint::to_string() const 32 | { 33 | return _address.to_string() + ":" + std::to_string(get_port()); 34 | } 35 | 36 | endpoint endpoint::parse(sockaddr_in addr) 37 | { 38 | endpoint ep; 39 | ep.set_port(addr.sin_port); 40 | ep.set_address(ip_address(addr)); 41 | 42 | return ep; 43 | } 44 | 45 | bool endpoint::try_parse(std::string_view s, endpoint* ep) 46 | { 47 | auto idx = s.find(':'); 48 | if (idx == std::string::npos) 49 | return false; 50 | 51 | ip_address addr; 52 | if (!ip_address::try_parse(s.substr(0, idx), &addr)) 53 | return false; 54 | 55 | auto port = std::stoi(s.substr(idx+1, s.length()).data()); 56 | ep->set_address(addr); 57 | ep->set_port(port); 58 | 59 | return true; 60 | } 61 | 62 | bool endpoint::operator==(const endpoint& endpoint) const { 63 | return (get_address() == endpoint.get_address()) && 64 | (get_port() == endpoint.get_port()); 65 | } 66 | 67 | bool endpoint::operator==(endpoint&& endpoint) const { 68 | return (get_address() == endpoint.get_address()) && 69 | (get_port() == endpoint.get_port()); 70 | } 71 | -------------------------------------------------------------------------------- /test/endpoint_tests.cpp: -------------------------------------------------------------------------------- 1 | #include "gtest/gtest.h" 2 | #include "net/endpoint.hpp" 3 | 4 | TEST(endpoint, constructor) 5 | { 6 | net::endpoint endpoint(net::ip_address::loopback, 8080); 7 | EXPECT_EQ(endpoint.to_string(), "127.0.0.1:8080"); 8 | } 9 | 10 | TEST(endpoint, get_address) 11 | { 12 | net::endpoint endpoint(net::ip_address::loopback, 8080); 13 | EXPECT_EQ(endpoint.get_address().to_string(), "127.0.0.1"); 14 | } 15 | 16 | TEST(endpoint, set_address) 17 | { 18 | auto addr = net::ip_address::loopback; 19 | net::endpoint endpoint; 20 | endpoint.set_address(addr); 21 | EXPECT_EQ(endpoint.get_address(), addr); 22 | } 23 | 24 | TEST(endpoint, get_port) 25 | { 26 | u_short port = 8080; 27 | net::endpoint endpoint(net::ip_address::loopback, port); 28 | EXPECT_EQ(endpoint.get_port(), port); 29 | } 30 | 31 | TEST(endpoint, set_port) 32 | { 33 | u_short port = 8080; 34 | net::endpoint endpoint; 35 | endpoint.set_port(port); 36 | EXPECT_EQ(endpoint.get_port(), port); 37 | } 38 | 39 | TEST(endpoint, to_string) 40 | { 41 | net::endpoint endpoint(net::ip_address::loopback, 8080); 42 | EXPECT_EQ(endpoint.to_string(), "127.0.0.1:8080"); 43 | } 44 | 45 | TEST(endpoint, parse) 46 | { 47 | u_short port = 8080; 48 | sockaddr_in addr {}; 49 | addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); 50 | addr.sin_port = port; 51 | 52 | auto endpoint = net::endpoint::parse(addr); 53 | EXPECT_EQ(endpoint.get_port(), port); 54 | } 55 | 56 | TEST(endpoint, try_parse) 57 | { 58 | net::endpoint endpoint; 59 | EXPECT_EQ(net::endpoint::try_parse("127.0.0.1:8080", &endpoint), true); 60 | } 61 | 62 | TEST(endpoint, operator_equla_lvalue) 63 | { 64 | auto addr = net::ip_address::loopback; 65 | auto port = 8080; 66 | net::endpoint ep1(addr, port), ep2(addr, port); 67 | EXPECT_EQ(ep1 == ep2, true); 68 | } 69 | 70 | TEST(endpoint, operator_equla_rvalue) 71 | { 72 | auto addr = net::ip_address::loopback; 73 | auto port = 8080; 74 | net::endpoint ep1(addr, port), ep2(addr, port); 75 | EXPECT_EQ(ep1 == std::move(ep2), true); 76 | } 77 | -------------------------------------------------------------------------------- /include/net/native.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef _WIN32 4 | #include 5 | #include 6 | #include 7 | 8 | #define SOCK_DISCONNECT SD_BOTH 9 | 10 | using SOCKLEN = int; 11 | #else 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include 33 | 34 | #define SOCKET_ERROR (-1) 35 | #define INVALID_SOCKET SOCKET_ERROR 36 | 37 | #define ZeroMemory(addr, size) memset(addr, 0, size) 38 | 39 | #define SOCK_DISCONNECT SHUT_RDWR 40 | 41 | using SOCKET = int; 42 | using SOCKLEN = socklen_t; 43 | 44 | #endif 45 | #include 46 | 47 | #include "net/export.hpp" 48 | 49 | namespace net 50 | { 51 | class context; 52 | class socket; 53 | 54 | class NETCPP_API native 55 | { 56 | public: 57 | struct option final 58 | { 59 | static bool auto_run; 60 | static unsigned thread_count; 61 | static unsigned long timeout; 62 | static u_int entry_count; 63 | }; 64 | 65 | #ifdef _WIN32 66 | static LPFN_ACCEPTEX accept; 67 | static LPFN_CONNECTEX connect; 68 | static LPFN_DISCONNECTEX disconnect; 69 | static LPFN_GETACCEPTEXSOCKADDRS get_accept_socket_address; 70 | #endif 71 | public: 72 | #ifdef _WIN32 73 | static HANDLE get_handle(); 74 | #else 75 | static io_uring* get_handle(); 76 | #endif 77 | public: 78 | static bool initialize(); 79 | 80 | static void run_io(unsigned num); 81 | static void io(unsigned id); 82 | static bool observe(socket* sock); 83 | private: 84 | static bool demux(context*, u_long, bool); 85 | 86 | private: 87 | #ifdef _WIN32 88 | static HANDLE _cp; 89 | #else 90 | static std::vector _io_uring_list; 91 | static thread_local io_uring* _io_uring; 92 | #endif 93 | }; 94 | } // namespace net -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.23) 2 | 3 | project(netcpp VERSION 0.4 DESCRIPTION "C++ Network library." LANGUAGES CXX) 4 | 5 | include(GNUInstallDirs) 6 | 7 | # Platforms 8 | if (MSVC) 9 | set(CMAKE_EXE_LINKER_FLAGS_DEBUG "/DEBUG") 10 | else() 11 | find_package(PkgConfig REQUIRED) 12 | pkg_check_modules(URING REQUIRED liburing) 13 | add_compile_definitions(_LIBCPP_ENABLE_EXPERIMENTAL) 14 | endif(MSVC) 15 | add_compile_definitions(NETCPP_BUILD) 16 | 17 | # Initialize 18 | option(NETCPP_TEST "Enable build with unitTest source" ON) 19 | 20 | set(CMAKE_CXX_STANDARD 20) 21 | file(GLOB_RECURSE SOURCE_FILES "${PROJECT_SOURCE_DIR}/src/*.cpp") 22 | set(INCLUDES "${PROJECT_SOURCE_DIR}/include") 23 | 24 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) 25 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) 26 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) 27 | 28 | set(EXPORT_NAMESPACE "${PROJECT_NAME}::") 29 | set(CMAKE_CONFIG_FILE_BASENAME "${PROJECT_NAME}Config.cmake") 30 | set(CMAKE_EXPORT_FILE_BASENAME "${PROJECT_NAME}Export.cmake") 31 | 32 | string(REPLACE "/${CMAKE_LIBRARY_ARCHITECTURE}" "" CMAKE_INSTALL_LIBDIR_ARCHIND "${CMAKE_INSTALL_LIBDIR}") 33 | 34 | add_subdirectory(src) 35 | 36 | # googletest 37 | if (NETCPP_TEST) 38 | include(FetchContent) 39 | FetchContent_Declare( 40 | googletest 41 | GIT_REPOSITORY https://github.com/google/googletest 42 | GIT_TAG v1.15.2 43 | ) 44 | FetchContent_GetProperties(googletest) 45 | if(NOT googletest_POPULATED) 46 | FetchContent_Populate(googletest) 47 | add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR}) 48 | include_directories(${gtest_SOURCE_DIR}/include ${gmock_SOURCE_DIR}/include) 49 | endif() 50 | 51 | FetchContent_MakeAvailable(googletest) 52 | enable_testing() 53 | 54 | add_subdirectory(test) 55 | endif () 56 | 57 | # INSTALL 58 | file(GLOB_RECURSE HEADERS "${INCLUDES}/*.h" "${INCLUDES}/*.hxx" "${INCLUDES}/*.hpp") 59 | string(REPLACE "/${CMAKE_LIBRARY_ARCHITECTURE}" "" CMAKE_INSTALL_LIBDIR_ARCHIND "${CMAKE_INSTALL_LIBDIR}") 60 | foreach(headerFile ${HEADERS}) 61 | get_filename_component(headerFileParentDir "${headerFile}" DIRECTORY) 62 | file(RELATIVE_PATH headerFileRelParentDir "${INCLUDES}" "${headerFileParentDir}") 63 | 64 | install(FILES "${headerFile}" 65 | DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${headerFileRelParentDir}" 66 | ) 67 | endforeach() 68 | 69 | install(TARGETS ${PROJECT_NAME} 70 | EXPORT ${PROJECT_NAME} 71 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 72 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 73 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 74 | INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") 75 | 76 | export(TARGETS "${PROJECT_NAME}" 77 | NAMESPACE "${EXPORT_NAMESPACE}" 78 | FILE "${CMAKE_EXPORT_FILE_BASENAME}" 79 | EXPORT_LINK_INTERFACE_LIBRARIES) 80 | 81 | install(EXPORT "${PROJECT_NAME}" 82 | FILE "${CMAKE_CONFIG_FILE_BASENAME}" 83 | NAMESPACE "${EXPORT_NAMESPACE}" 84 | DESTINATION "${CMAKE_INSTALL_LIBDIR_ARCHIND}/cmake/${PROJECT_NAME}") 85 | -------------------------------------------------------------------------------- /README_ko_KR.md: -------------------------------------------------------------------------------- 1 | # netcpp ![windows](https://github.com/index1207/netcpp/actions/workflows/windows.yml/badge.svg) ![linux](https://github.com/index1207/netcpp/actions/workflows/linux.yml/badge.svg) [![codecov](https://codecov.io/gh/index1207/netcpp/graph/badge.svg?_token=BVVUC5S422)](https://codecov.io/gh/index1207/netcpp) ![lang](https://img.shields.io/badge/language-C++20-blue) [![Vcpkg package](https://img.shields.io/badge/vcpkg-0.5.0-yellow)](https://github.com/microsoft/vcpkg/tree/master/ports/netcpp) [![License](https://img.shields.io/github/license/index1207/netcpp.svg)](LICENSE) 2 | [[돌아가기]](https://github.com/index1207/netcpp)
3 | netcpp는 간단하게 사용할 수 있는 크로스플랫폼 C++ 네트워크 라이브러리로, Windows와 Linux 플랫폼을 지원합니다. 4 | 5 | ## 설치 6 | 이 라이브러리는 [vcpkg](https://github.com/microsoft/vcpkg)포트를 지원합니다. 만약 이미 vcpkg가 설치되어 있다면 아래의 명령줄을 통해 설치가 가능합니다. 7 | ```shell 8 | vcpkg install netcpp # 클래식 모드에서 9 | vcpkg add port netcpp # 매니페스트 모드에서 10 | ``` 11 | 12 | 직접 클론하고 빌드하여 사용할 수 있습니다. 13 | ```shell 14 | git clone https://github.com/index1207/netcpp.git && cd netcpp # 클론 및 디렉토리 이동 15 | cmake -B build # CMake 설정 16 | cmake --build build --config # 지정한 모드로 빌드 17 | cmake --install build --config --prefix # 지정된 경로에 설치 18 | ``` 19 | 20 | netcpp는 다음의 CMake 타겟을 제공합니다. 21 | ```cmake 22 | find_package(netcpp CONFIG REQUIRED) 23 | target_link_libraries(main PRIVATE netcpp::netcpp) 24 | ``` 25 | 26 | ## CMake 옵션 27 | | 옵션 | 설명 | 28 | |-----------------------|-------------------| 29 | | `NETCPP_BUILD_SHARED` | 공유 라이브러리로 빌드합니다. | 30 | | `NETCPP_TEST` | 유닛테스트를 빌드에 포함합니다. | 31 | 32 | ## 예제 33 | - 소켓 생성 34 | ```cpp 35 | // 36 | net::socket tcp_socket(net::protocol::tcp); // 새 TCP 소켓 생성 37 | 38 | net::socket udp_socket(net::protocol::udp); // 새 UDP 소켓 생성 39 | 40 | net::socket empty_socket; 41 | empty_socket.create(net::protocol::tcp); // 메서드를 통한 새 TCP 소켓 생성 42 | ``` 43 | - 비동기 I/O 44 | ```cpp 45 | // 46 | net::socket sock(net::protocol::tcp); // 새 TCP 소켓 생성 47 | net::context connect_ctx; // 비동기 I/O에 필요한 컨텍스트 변수 48 | connect_ctx.endpoint = ENDPOINT; // 연결 엔드포인트 지정 49 | connect_ctx.completed = [](net::context* ctx, bool success) { // 콜백 50 | if (success) 51 | { 52 | std::cout << "Connected" << std::endl; 53 | } 54 | else 55 | { 56 | std::cout << "Failed to connect" << std::endl; 57 | } 58 | }; 59 | sock.connect(&connect_ctx); // 지정된 엔드포인트에 연결 시도 60 | ``` 61 | - 기본 연결 62 | ```cpp 63 | // Server 64 | #include 65 | #include 66 | 67 | int main() 68 | { 69 | net::native::initialize(); // 네이티브 API 초기화 70 | 71 | net::socket sock(net::protocol::tcp); // 새 TCP 소켓 생성 72 | if (!sock.is_open()) // 소켓이 유효한지 검사 73 | return -1; 74 | if (!sock.bind(net::endpoint(net::ip_address::loopback, 8085))) // `tcp://loopback:8085`로 주소 할당 75 | return -1; 76 | if (!sock.listen()) // accept의 준비 77 | return -1; 78 | 79 | while (true) 80 | { 81 | auto client = sock.accept(); // 다른 클라이언트 접속. 접속된 소켓 리턴 82 | std::cout << "Connected\n"; 83 | } 84 | } 85 | ``` 86 | ```cpp 87 | // Client 88 | #include 89 | #include 90 | 91 | int main() 92 | { 93 | net::native::initialize(); // 네이티브 API 초기화 94 | 95 | net::socket sock(net::protocol::tcp); // 새 TCP 소켓 생성 96 | if (!sock.is_open()) // 소켓이 유효한지 검사 97 | return -1; 98 | if (!sock.connect(net::endpoint(net::ip_address::loopback, 8085))) // 서버에 연결 시도 99 | return -1; 100 | std::cout << "Connected!"; 101 | } 102 | ``` 103 | - DNS 104 | ```cpp 105 | // 106 | net::dns::get_host_name() // 호스트 이름 리턴 107 | 108 | net::dns::get_host_entry("www.example.com") // www.example.com의 호스트 엔트리 리턴 109 | ``` 110 | - 예외처리 111 | ```cpp 112 | try { 113 | if (!sock.connect(ENDPOINT)) 114 | throw net::network_exception("connect()"); 115 | } 116 | catch(std::exception& e) { 117 | std::cout << e.what() << std::endl; // connect(): 요청된 주소를 할당할 수 없습니다. [10049] 118 | } 119 | ``` 120 | 121 | ## 종속성 122 | | OS | Library | 123 | |---------|----------| 124 | | Windows | Winsock2 | 125 | | Linux | liburing | 126 | 127 | ## 기여 128 | 이 레포지토리는 언제나 이슈나 PR을 환영합니다! 129 | 130 | ## 최소 컴파일러 버전 131 | - Windows 132 | - Visual Studio 2019 133 | - Linux 134 | - Clang 12 135 | - GCC 10 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # netcpp ![windows](https://github.com/index1207/netcpp/actions/workflows/windows.yml/badge.svg) ![linux](https://github.com/index1207/netcpp/actions/workflows/linux.yml/badge.svg) [![codecov](https://codecov.io/gh/index1207/netcpp/graph/badge.svg?_token=BVVUC5S422)](https://codecov.io/gh/index1207/netcpp) ![lang](https://img.shields.io/badge/language-C++20-blue) [![Vcpkg package](https://img.shields.io/badge/vcpkg-0.5.0-yellow)](https://github.com/microsoft/vcpkg/tree/master/ports/netcpp) [![License](https://img.shields.io/github/license/index1207/netcpp.svg)](LICENSE) 2 | [[한국어]](README_ko_KR.md)
3 | netcpp is **simple** C++ network library. 4 | It supports windows and linux platform. 5 | 6 | ## Installation 7 | This library supports [vcpkg](https://github.com/microsoft/vcpkg) port. If you had already installed vcpkg, You can install this package simply with the command line below. 8 | ```shell 9 | vcpkg install netcpp # In classic mode 10 | vcpkg add port netcpp # In manifest mode 11 | ``` 12 | 13 | Or clone this repository, and execute the command line below. 14 | ```shell 15 | git clone https://github.com/index1207/netcpp.git && cd netcpp # clone and move directory 16 | cmake -B build # CMake Configuration 17 | cmake --build build --config # Build library 18 | cmake --install build --config --prefix # Install to other project 19 | ``` 20 | 21 | netcpp provides CMake targets: 22 | ```cmake 23 | find_package(netcpp CONFIG REQUIRED) 24 | target_link_libraries(main PRIVATE netcpp::netcpp) 25 | ``` 26 | 27 | ## CMake Options 28 | | Option | Description | 29 | |-----------------------|----------------------------| 30 | | `NETCPP_BUILD_SHARED` | Build by shared library | 31 | | `NETCPP_TEST` | Include unit test in build | 32 | 33 | ## Example 34 | - Create a socket 35 | ```cpp 36 | // 37 | net::socket tcp_socket(net::protocol::tcp); // Create a TCP socket 38 | 39 | net::socket udp_socket(net::protocol::udp); // Create a UDP socket 40 | 41 | net::socket empty_socket; 42 | empty_socket.create(net::protocol::tcp); // Create a new TCP socket 43 | ``` 44 | - Async I/O 45 | ```cpp 46 | // 47 | net::socket sock(net::protocol::tcp); // Create a new TCP socket. 48 | net::context connect_ctx; // A context that necessary to async I/O 49 | connect_ctx.endpoint = ENDPOINT; // Specify the endpoint. 50 | connect_ctx.completed = [](net::context* ctx, bool success) { // callback 51 | if (success) 52 | { 53 | std::cout << "Connected" << std::endl; 54 | } 55 | else 56 | { 57 | std::cout << "Failed to connect" << std::endl; 58 | } 59 | }; 60 | sock.connect(&connect_ctx); // Connect to specified endpoint asynchronously. 61 | ``` 62 | - Basic connection 63 | ```cpp 64 | // Server 65 | #include 66 | #include 67 | 68 | int main() 69 | { 70 | net::native::initialize(); // Initialize Native API 71 | 72 | net::socket sock(net::protocol::tcp); // Create new TCP socket 73 | if (!sock.is_open()) // Validate socket 74 | return -1; 75 | if (!sock.bind(net::endpoint(net::ip_address::loopback, 8085))) // Bind the address `tcp://loopback:8085` 76 | return -1; 77 | if (!sock.listen()) // Ready to accept 78 | return -1; 79 | 80 | while(true) 81 | { 82 | auto client = sock.accept(); // accept other client. it returns new client socket. 83 | std::cout << "Connected\n"; 84 | } 85 | } 86 | ``` 87 | ```cpp 88 | // Client 89 | #include 90 | #include 91 | 92 | int main() 93 | { 94 | net::native::initialize(); // Initialize Native API 95 | 96 | net::socket sock(net::protocol::tcp); // Create new TCP socket 97 | if (!sock.is_open()) // Validate socket 98 | return -1; 99 | if (!sock.connect(net::endpoint(net::ip_address::loopback, 8085))) // Try to connect to server. 100 | return -1; 101 | std::cout << "Connected!"; 102 | } 103 | ``` 104 | - DNS 105 | ```cpp 106 | // 107 | net::dns::get_host_name() // get host's name 108 | 109 | net::dns::get_host_entry("www.example.com") // get www.example.com's host entry 110 | ``` 111 | - Exception 112 | ```cpp 113 | try { 114 | if (!sock.connect(ENDPOINT)) 115 | throw net::network_exception("connect()"); 116 | } 117 | catch(std::exception& e) { 118 | std::cout << e.what() << std::endl; // connect(): Cannot assign requested address. [10049] 119 | } 120 | ``` 121 | 122 | ## Dependencies 123 | | OS | Library | 124 | |---------|----------| 125 | | Windows | Winsock2 | 126 | | Linux | liburing | 127 | 128 | ## Contribute 129 | The repository is whenever welcome any issues or PRs! 130 | 131 | ## Minimum required compiler version 132 | - Windows 133 | - Visual Studio 2019 134 | - Linux 135 | - Clang 12 136 | - GCC 10 -------------------------------------------------------------------------------- /include/net/socket.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "net/endpoint.hpp" 7 | #include "net/native.hpp" 8 | #include "net/export.hpp" 9 | 10 | namespace net 11 | { 12 | enum class protocol 13 | { 14 | ip = IPPROTO_IP, 15 | tcp = IPPROTO_TCP, 16 | udp = IPPROTO_UDP 17 | }; 18 | 19 | enum class address_family 20 | { 21 | ipv4 = AF_INET, 22 | ipv6 = AF_INET6 23 | }; 24 | 25 | enum class socket_type 26 | { 27 | stream = SOCK_STREAM, 28 | dgram = SOCK_DGRAM 29 | }; 30 | 31 | enum option 32 | { 33 | // socket Level 34 | linger = SO_LINGER, 35 | reuse_address = SO_REUSEADDR, 36 | send_buffer = SO_SNDBUF, 37 | receive_buffer = SO_RCVBUF, 38 | broadcast = SO_BROADCAST, 39 | 40 | #ifdef _WIN32 41 | accept_context = SO_UPDATE_ACCEPT_CONTEXT, 42 | connect_context = SO_UPDATE_CONNECT_CONTEXT, 43 | #endif 44 | // ip Level 45 | ttl = IP_TTL, 46 | multicast_ttl = IP_MULTICAST_TTL, 47 | 48 | // tcp Level 49 | no_delay = TCP_NODELAY 50 | }; 51 | 52 | namespace options 53 | { 54 | enum class level 55 | { 56 | ip = IPPROTO_IP, 57 | ipv6 = IPPROTO_IPV6, 58 | socket = SOL_SOCKET, 59 | }; 60 | 61 | struct linger 62 | { 63 | bool enabled; 64 | int time; 65 | }; 66 | } 67 | } 68 | 69 | namespace net 70 | { 71 | class context; 72 | 73 | class NETCPP_API socket 74 | { 75 | friend class native; 76 | public: 77 | socket(); 78 | explicit socket(protocol pt); 79 | socket(const socket& sock); 80 | socket(socket&& sock) noexcept; 81 | virtual ~socket(); 82 | public: 83 | void close(); 84 | void create(protocol pt = protocol::ip); 85 | 86 | void set_handle(SOCKET s); 87 | 88 | bool bind(endpoint ep); 89 | bool listen(int backlog = SOMAXCONN) const; 90 | public: 91 | [[nodiscard]] SOCKET get_handle() const; 92 | [[nodiscard]] std::optional get_remote_endpoint() const; 93 | [[nodiscard]] std::optional get_local_endpoint() const; 94 | public: 95 | void disconnect(); 96 | [[nodiscard]] socket accept() const; 97 | bool connect(endpoint ep); 98 | 99 | bool send(std::span s) const; 100 | bool send(std::span s, endpoint target) const; 101 | 102 | int receive(std::span s) const; 103 | int receive(std::span s, endpoint target) const; 104 | public: 105 | bool disconnect(context* context); 106 | bool accept(context* context); 107 | bool connect(context* context); 108 | bool send(context* context) const; 109 | bool receive(context* context) const; 110 | 111 | public: 112 | template 113 | bool set_option(options::level level, option name, T value) const 114 | { 115 | if (_sock == INVALID_SOCKET) 116 | return false; 117 | return SOCKET_ERROR != setsockopt(_sock, static_cast(level), static_cast(name), 118 | reinterpret_cast(&value), sizeof(T)); 119 | } 120 | template 121 | bool get_option(options::level level, option name, T& value) const 122 | { 123 | if (_sock == INVALID_SOCKET) 124 | return false; 125 | SOCKLEN optLen = sizeof(T); 126 | return SOCKET_ERROR != getsockopt(_sock, static_cast(level), static_cast(name), 127 | reinterpret_cast(&value), &optLen); 128 | } 129 | 130 | [[nodiscard]] bool set_blocking(bool blocking) const; 131 | [[nodiscard]] bool set_linger(options::linger linger) const; 132 | [[nodiscard]] bool set_broadcast(bool broadcast) const; 133 | [[nodiscard]] bool set_reuse_address(bool reuse) const; 134 | [[nodiscard]] bool set_no_delay(bool no_delay) const; 135 | [[nodiscard]] bool set_ttl(int ttl) const; 136 | [[nodiscard]] bool set_send_buffer(int size) const; 137 | [[nodiscard]] bool set_receive_buffer(int size) const; 138 | 139 | [[nodiscard]] bool is_open() const; 140 | public: 141 | bool operator==(const socket& sock) const; 142 | bool operator==(socket&& sock) const; 143 | 144 | socket &operator=(const socket& sock); 145 | socket &operator=(socket&& sock) noexcept; 146 | private: 147 | std::optional _remote_endpoint; 148 | std::optional _local_endpoint; 149 | SOCKET _sock; 150 | }; 151 | #ifdef _WIN32 152 | template bool NETCPP_API socket::set_option<::linger>(options::level level, option name, ::linger value) const; 153 | template bool NETCPP_API socket::set_option(options::level level, option name, int value) const; 154 | template bool NETCPP_API socket::set_option(options::level level, option name, bool value) const; 155 | template bool NETCPP_API socket::set_option(options::level level, option name, DWORD value) const; 156 | 157 | template bool NETCPP_API socket::get_option<::linger>(options::level level, option name, ::linger& value) const; 158 | template bool NETCPP_API socket::get_option(options::level level, option name, int& value) const; 159 | template bool NETCPP_API socket::get_option(options::level level, option name, bool& value) const; 160 | template bool NETCPP_API socket::get_option(options::level level, option name, DWORD& value) const; 161 | #endif 162 | } -------------------------------------------------------------------------------- /src/native.cpp: -------------------------------------------------------------------------------- 1 | #include "net/native.hpp" 2 | #include "net/socket.hpp" 3 | #include "net/context.hpp" 4 | 5 | #include 6 | #include 7 | 8 | using namespace net; 9 | 10 | bool native::option::auto_run = true; 11 | unsigned native::option::thread_count = std::thread::hardware_concurrency(); 12 | #ifdef _WIN32 13 | unsigned long native::option::timeout = INFINITE; 14 | #else 15 | unsigned long native::option::timeout = 0; 16 | #endif 17 | u_int native::option::entry_count = 128; 18 | 19 | #ifdef _WIN32 20 | LPFN_ACCEPTEX native::accept = nullptr; 21 | LPFN_CONNECTEX native::connect = nullptr; 22 | LPFN_DISCONNECTEX native::disconnect = nullptr; 23 | LPFN_GETACCEPTEXSOCKADDRS native::get_accept_socket_address = nullptr; 24 | 25 | bool bind_extension_function(SOCKET s, GUID guid, PVOID *func) 26 | { 27 | DWORD dwBytes; 28 | return SOCKET_ERROR != WSAIoctl(s, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(GUID), func, sizeof(*func), 29 | &dwBytes, NULL, NULL); 30 | } 31 | 32 | HANDLE native::_cp = nullptr; 33 | #else 34 | std::vector native::_io_uring_list; 35 | thread_local io_uring* native::_io_uring = nullptr; 36 | #endif 37 | 38 | bool native::initialize() 39 | { 40 | #ifdef _WIN32 41 | WSADATA wsaData{}; 42 | if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) 43 | return false; 44 | 45 | socket dummy(protocol::tcp); 46 | if (!bind_extension_function(dummy.get_handle(), WSAID_ACCEPTEX, reinterpret_cast(&accept))) 47 | return false; 48 | if (!bind_extension_function(dummy.get_handle(), WSAID_CONNECTEX, reinterpret_cast(&connect))) 49 | return false; 50 | if (!bind_extension_function(dummy.get_handle(), WSAID_DISCONNECTEX, reinterpret_cast(&disconnect))) 51 | return false; 52 | if (!bind_extension_function(dummy.get_handle(), WSAID_GETACCEPTEXSOCKADDRS, 53 | reinterpret_cast(&native::get_accept_socket_address))) 54 | return false; 55 | 56 | _cp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, NULL, NULL); 57 | #else 58 | #endif 59 | if (option::auto_run) 60 | { 61 | run_io(option::thread_count); 62 | } 63 | return true; 64 | } 65 | 66 | void native::run_io(unsigned int num) 67 | { 68 | #ifndef _WIN32 69 | _io_uring_list.clear(); 70 | _io_uring_list.resize(num); 71 | #endif 72 | for (unsigned i = 0; i < num; ++i) 73 | { 74 | new std::thread(&native::io, i); 75 | } 76 | } 77 | 78 | void native::io(unsigned id) 79 | { 80 | #ifdef _WIN32 81 | context *context = nullptr; 82 | ULONG_PTR key = 0; 83 | DWORD numOfBytes = 0; 84 | #else 85 | io_uring ring {}; 86 | io_uring_cqe* cqe = nullptr; 87 | 88 | _io_uring = ˚ 89 | if (io_uring_queue_init(option::entry_count, &ring, 0)) 90 | perror("io_uring_queue_init()"); 91 | else 92 | _io_uring_list[id] = ˚ 93 | #endif 94 | while (true) 95 | { 96 | #ifdef _WIN32 97 | auto ret = GetQueuedCompletionStatus(_cp, 98 | &numOfBytes, 99 | &key, 100 | reinterpret_cast(&context), 101 | option::timeout); 102 | if (ret) 103 | { 104 | if (!demux(context, numOfBytes, true)) 105 | break; 106 | } 107 | else 108 | { 109 | const auto err = WSAGetLastError(); 110 | switch (err) { 111 | case WAIT_TIMEOUT: 112 | case ERROR_OPERATION_ABORTED: 113 | break; 114 | default: 115 | if (!demux(context, numOfBytes, false)) 116 | break; 117 | break; 118 | } 119 | } 120 | #else 121 | if (io_uring_wait_cqe(&ring, &cqe)) 122 | continue; 123 | 124 | if (!cqe->user_data) 125 | { 126 | perror("io_uring_wait_cqe()"); 127 | io_uring_cqe_seen(&ring, cqe); 128 | break; 129 | } 130 | 131 | auto ctx = reinterpret_cast(cqe->user_data); 132 | if (cqe->res < 0) 133 | { 134 | if (!demux(ctx, static_cast(cqe->res), false)) 135 | break; 136 | } 137 | else 138 | { 139 | if (!demux(ctx, static_cast(cqe->res), true)) 140 | break; 141 | } 142 | 143 | 144 | io_uring_cqe_seen(&ring, cqe); 145 | #endif 146 | } 147 | } 148 | 149 | bool native::demux(context* context, u_long transferred, bool success) 150 | { 151 | switch (context->_io_type) 152 | { 153 | case io_type::accept: 154 | if (success) 155 | { 156 | auto listen_socket = static_cast(context->_token); 157 | #ifdef _WIN32 158 | if (!observe(context->accept_socket.get())) 159 | return false; 160 | 161 | if (!context->accept_socket->set_option(options::level::socket, static_cast(SO_UPDATE_ACCEPT_CONTEXT), listen_socket->get_handle())) 162 | return false; 163 | #else 164 | context->accept_socket->close(); 165 | context->accept_socket->set_handle(static_cast(transferred)); 166 | #endif 167 | 168 | sockaddr_in addr {}; 169 | SOCKLEN len = sizeof(addr); 170 | if (SOCKET_ERROR == getpeername(context->accept_socket->get_handle(), reinterpret_cast(&addr), &len)) 171 | perror("getpeername()"); 172 | 173 | auto endpoint = endpoint::parse(addr); 174 | context->accept_socket->_remote_endpoint = endpoint; 175 | endpoint.set_port(listen_socket->get_local_endpoint()->get_port()); 176 | context->accept_socket->_local_endpoint = endpoint; 177 | } 178 | context->_token = nullptr; 179 | context->completed(context, success); 180 | break; 181 | case io_type::connect: 182 | if (success) 183 | { 184 | #ifdef _WIN32 185 | if (!static_cast(context->_token)->set_option(options::level::socket, static_cast(SO_UPDATE_CONNECT_CONTEXT), nullptr)) 186 | return false; 187 | #endif 188 | } 189 | context->completed(context, success); 190 | break; 191 | case io_type::disconnect: 192 | context->completed(context, success); 193 | break; 194 | case io_type::receive: 195 | if (transferred == 0) 196 | success = true; 197 | case io_type::send: 198 | context->length = transferred; 199 | context->completed(context, success); 200 | break; 201 | default: 202 | return false; 203 | } 204 | return true; 205 | } 206 | 207 | bool native::observe(socket* sock) 208 | { 209 | #ifdef _WIN32 210 | auto r = CreateIoCompletionPort(reinterpret_cast(sock->get_handle()), _cp, NULL, NULL); 211 | return nullptr != r; 212 | #else 213 | return true; 214 | #endif 215 | } 216 | 217 | #ifdef _WIN32 218 | HANDLE native::get_handle() 219 | { 220 | return _cp; 221 | } 222 | #else 223 | io_uring* native::get_handle() 224 | { 225 | static auto random = [](auto min, auto max) { 226 | static std::random_device rd; 227 | static std::mt19937_64 gen(rd()); 228 | 229 | std::uniform_int_distribution dist(min, max); 230 | return dist(rd); 231 | }; 232 | 233 | auto uring = _io_uring; 234 | if (!uring) 235 | uring = _io_uring_list[random(0, _io_uring_list.size()-1)]; 236 | return uring; 237 | } 238 | 239 | #endif -------------------------------------------------------------------------------- /src/socket.cpp: -------------------------------------------------------------------------------- 1 | #include "net/socket.hpp" 2 | 3 | #include "net/context.hpp" 4 | #include "net/dns.hpp" 5 | #include "net/native.hpp" 6 | 7 | #include 8 | 9 | using namespace net; 10 | 11 | socket::socket(protocol pt) : socket() 12 | { 13 | create(pt); 14 | } 15 | 16 | socket::socket(const socket& sock) 17 | { 18 | _sock = sock._sock; 19 | _local_endpoint = sock._local_endpoint; 20 | _remote_endpoint = sock._remote_endpoint; 21 | } 22 | 23 | socket::socket(socket&& sock) noexcept 24 | { 25 | _sock = sock._sock; 26 | std::swap(_local_endpoint, sock._local_endpoint); 27 | std::swap(_remote_endpoint, sock._remote_endpoint); 28 | } 29 | 30 | net::socket::~socket() 31 | { 32 | close(); 33 | } 34 | 35 | socket::socket() 36 | { 37 | _sock = INVALID_SOCKET; 38 | } 39 | 40 | void socket::set_handle(SOCKET s) 41 | { 42 | _sock = s; 43 | } 44 | 45 | void socket::close() 46 | { 47 | if (_sock != INVALID_SOCKET) 48 | { 49 | #ifdef _WIN32 50 | closesocket(_sock); 51 | #else 52 | ::close(_sock); 53 | #endif 54 | _sock = INVALID_SOCKET; 55 | } 56 | } 57 | 58 | bool socket::connect(endpoint ep) 59 | { 60 | if (!is_open()) 61 | return false; 62 | 63 | if (!native::observe(this)) 64 | return false; 65 | 66 | _remote_endpoint = ep; 67 | ip_address ipAdr = ep.get_address(); 68 | return SOCKET_ERROR != ::connect(_sock, reinterpret_cast(&ipAdr), sizeof(sockaddr_in)); 69 | } 70 | 71 | bool socket::bind(endpoint ep) 72 | { 73 | _local_endpoint = ep; 74 | ip_address ipAdr = _local_endpoint->get_address(); 75 | if (SOCKET_ERROR != ::bind(_sock, reinterpret_cast(&ipAdr), sizeof(sockaddr_in))) 76 | return native::observe(this); 77 | return false; 78 | } 79 | 80 | bool socket::listen(int backlog) const 81 | { 82 | return SOCKET_ERROR != ::listen(_sock, backlog); 83 | } 84 | 85 | SOCKET socket::get_handle() const 86 | { 87 | return _sock; 88 | } 89 | 90 | std::optional socket::get_remote_endpoint() const 91 | { 92 | return _remote_endpoint; 93 | } 94 | 95 | std::optional socket::get_local_endpoint() const 96 | { 97 | return _local_endpoint; 98 | } 99 | 100 | void socket::disconnect() 101 | { 102 | shutdown(_sock, SOCK_DISCONNECT); 103 | _remote_endpoint = std::nullopt; 104 | } 105 | 106 | net::socket socket::accept() const 107 | { 108 | socket clientSock; 109 | clientSock.set_handle(::accept(_sock, nullptr, nullptr)); 110 | 111 | return clientSock; 112 | } 113 | 114 | bool socket::accept(context* context) 115 | { 116 | if (!context) 117 | return false; 118 | 119 | context->init(); 120 | context->_io_type = io_type::accept; 121 | 122 | context->_token = this; 123 | context->accept_socket = std::make_shared(protocol::tcp); 124 | #ifdef _WIN32 125 | DWORD dwByte = 0; 126 | char buf[(sizeof(SOCKADDR_IN) + 16) * 2] = ""; 127 | if (!native::accept(_sock, context->accept_socket->get_handle(), buf, 0, sizeof(SOCKADDR_IN) + 16, 128 | sizeof(SOCKADDR_IN) + 16, &dwByte, context)) 129 | { 130 | const auto err = WSAGetLastError(); 131 | return err == WSA_IO_PENDING; 132 | } 133 | #else 134 | auto uring = native::get_handle(); 135 | auto sqe = io_uring_get_sqe(uring); 136 | 137 | io_uring_prep_accept(sqe, get_handle(), nullptr, nullptr, 0); 138 | io_uring_sqe_set_data(sqe, context); 139 | io_uring_submit(uring); 140 | #endif 141 | return true; 142 | } 143 | 144 | bool socket::connect(context* context) 145 | { 146 | if (!context) 147 | return false; 148 | 149 | context->init(); 150 | context->_io_type = io_type::connect; 151 | _remote_endpoint = context->endpoint; 152 | #ifdef _WIN32 153 | if (!bind(endpoint(ip_address::any, 0))) 154 | return false; 155 | 156 | _local_endpoint = std::nullopt; 157 | 158 | context->_token = static_cast(this); 159 | 160 | ip_address ipAdr = context->endpoint->get_address(); 161 | DWORD dw; 162 | if (!native::connect(_sock, reinterpret_cast(&ipAdr), sizeof(SOCKADDR_IN), nullptr, NULL, &dw, 163 | reinterpret_cast(context))) 164 | { 165 | const auto err = WSAGetLastError(); 166 | return WSA_IO_PENDING == err; 167 | } 168 | #else 169 | auto uring = native::get_handle(); 170 | auto sqe = io_uring_get_sqe(uring); 171 | 172 | auto addr = context->endpoint->get_address(); 173 | io_uring_prep_connect(sqe, get_handle(), reinterpret_cast(&addr), sizeof(sockaddr_in)); 174 | io_uring_sqe_set_data(sqe, context); 175 | io_uring_submit(uring); 176 | #endif 177 | return true; 178 | } 179 | 180 | bool socket::send(context* context) const 181 | { 182 | if (!context) 183 | return false; 184 | 185 | context->init(); 186 | context->_io_type = io_type::send; 187 | #ifdef _WIN32 188 | if (context->_buffer_list.empty()) 189 | { 190 | WSABUF wsaBuf { 191 | .len = static_cast(context->_buffer.size()), 192 | .buf = context->_buffer.data() 193 | }; 194 | if (SOCKET_ERROR == WSASend(_sock, &wsaBuf, 1, &wsaBuf.len, 0, context, nullptr)) 195 | { 196 | return WSA_IO_PENDING == WSAGetLastError(); 197 | } 198 | } 199 | else 200 | { 201 | DWORD dwSent = 0; 202 | if (SOCKET_ERROR == WSASend(_sock, context->_buffer_list.data(), static_cast(context->_buffer_list.size()), &dwSent, 0, context, nullptr)) 203 | { 204 | return WSA_IO_PENDING == WSAGetLastError(); 205 | } 206 | } 207 | #else 208 | auto uring = native::get_handle(); 209 | auto sqe = io_uring_get_sqe(uring); 210 | 211 | if (context->_buffer_list.empty()) 212 | { 213 | io_uring_prep_send(sqe, _sock, context->_buffer.data(), context->_buffer.size(), 0); 214 | } 215 | else 216 | { 217 | msghdr msg {}; 218 | msg.msg_iov = context->_buffer_list.data(); 219 | msg.msg_iovlen = context->_buffer_list.size(); 220 | io_uring_prep_sendmsg(sqe, _sock, &msg, 0); 221 | } 222 | 223 | io_uring_sqe_set_data(sqe, context); 224 | io_uring_submit(uring); 225 | #endif 226 | return true; 227 | } 228 | 229 | bool socket::receive(context* context) const 230 | { 231 | if (!context) 232 | return false; 233 | 234 | context->init(); 235 | context->_io_type = io_type::receive; 236 | #ifdef _WIN32 237 | DWORD recvBytes = 0, flags = 0; 238 | if (context->_buffer_list.empty()) 239 | { 240 | WSABUF wsaBuf { 241 | .len = static_cast(context->_buffer.size()), 242 | .buf = context->_buffer.data() 243 | }; 244 | 245 | if (SOCKET_ERROR == WSARecv(_sock, &wsaBuf, 1, &recvBytes, &flags, context, nullptr)) 246 | { 247 | return WSA_IO_PENDING == WSAGetLastError(); 248 | } 249 | } 250 | else 251 | { 252 | if (SOCKET_ERROR == WSARecv(_sock, context->_buffer_list.data(), static_cast(context->_buffer_list.size()), &recvBytes, &flags, context, nullptr)) 253 | { 254 | return WSA_IO_PENDING == WSAGetLastError(); 255 | } 256 | } 257 | #else 258 | auto uring = native::get_handle(); 259 | auto sqe = io_uring_get_sqe(uring); 260 | 261 | if (context->_buffer_list.empty()) 262 | { 263 | io_uring_prep_recv(sqe, get_handle(), context->_buffer.data(), context->_buffer.size(), 0); 264 | } 265 | else 266 | { 267 | msghdr msg {}; 268 | msg.msg_iov = context->_buffer_list.data(); 269 | msg.msg_iovlen = context->_buffer_list.size(); 270 | io_uring_prep_recvmsg(sqe, get_handle(), &msg, 0); 271 | } 272 | 273 | io_uring_sqe_set_data(sqe, context); 274 | io_uring_submit(uring); 275 | #endif 276 | return true; 277 | } 278 | 279 | bool net::socket::disconnect(context* context) 280 | { 281 | if (!context) 282 | return false; 283 | 284 | context->init(); 285 | 286 | context->_io_type = io_type::disconnect; 287 | 288 | _remote_endpoint = std::nullopt; 289 | #ifdef _WIN32 290 | if (!native::disconnect(_sock, reinterpret_cast(context), 0, 0)) 291 | { 292 | const int err = WSAGetLastError(); 293 | return err == WSA_IO_PENDING; 294 | } 295 | #else 296 | auto uring = native::get_handle(); 297 | auto sqe = io_uring_get_sqe(uring); 298 | 299 | io_uring_prep_shutdown(sqe, get_handle(), SOCK_DISCONNECT); 300 | io_uring_sqe_set_data(sqe, context); 301 | io_uring_submit(uring); 302 | #endif 303 | return true; 304 | } 305 | 306 | bool socket::send(std::span s) const 307 | { 308 | return SOCKET_ERROR != ::send(_sock, s.data(), static_cast(s.size()), 0); 309 | } 310 | 311 | bool socket::send(std::span s, endpoint target) const 312 | { 313 | auto& addr = target.get_address(); 314 | return SOCKET_ERROR == sendto(_sock, s.data(), static_cast(s.size()), 0, 315 | reinterpret_cast(&addr), sizeof(sockaddr_in)); 316 | } 317 | 318 | int socket::receive(std::span s) const 319 | { 320 | auto ret = recv(_sock, s.data(), static_cast(s.size()), 0); 321 | return static_cast(ret); 322 | } 323 | 324 | int socket::receive(std::span s, endpoint target) const 325 | { 326 | auto& addr = const_cast(target.get_address()); 327 | SOCKLEN len = sizeof(sockaddr_in); 328 | auto ret = recvfrom(_sock, s.data(), static_cast(s.size()), 0, reinterpret_cast(&addr), &len); 329 | return static_cast(ret); 330 | } 331 | 332 | bool socket::set_blocking(bool blocking) const 333 | { 334 | #ifdef _WIN32 335 | u_long opt = !blocking; 336 | return SOCKET_ERROR != ioctlsocket(_sock, FIONBIO, &opt); 337 | #else 338 | int flags = fcntl(_sock, F_GETFL, 0); 339 | if (flags == SOCKET_ERROR) 340 | return false; 341 | flags = blocking ? (flags & ~O_NONBLOCK) : (flags | O_NONBLOCK); 342 | return 0 == fcntl(_sock, F_SETFL, flags); 343 | #endif 344 | } 345 | 346 | bool socket::set_linger(options::linger linger) const 347 | { 348 | ::linger lingerData{ 349 | #ifdef _WIN32 350 | .l_onoff = static_cast(linger.enabled), 351 | .l_linger = static_cast(linger.time) 352 | #else 353 | .l_onoff = static_cast(linger.enabled), 354 | .l_linger = linger.time 355 | #endif 356 | }; 357 | return set_option<::linger>(options::level::socket, option::linger, lingerData); 358 | } 359 | 360 | bool socket::set_broadcast(bool broadcast) const 361 | { 362 | int value = broadcast; 363 | return set_option(options::level::socket, option::broadcast, value); 364 | } 365 | 366 | bool socket::set_reuse_address(bool reuse) const 367 | { 368 | #ifdef _WIN32 369 | return set_option(options::level::socket, option::reuse_address, static_cast(reuse)); 370 | #else 371 | return set_option(options::level::socket, option::reuse_address, static_cast(reuse)); 372 | #endif 373 | } 374 | 375 | bool socket::set_no_delay(bool no_delay) const 376 | { 377 | #ifdef _WIN32 378 | return set_option(static_cast(protocol::tcp), option::no_delay, static_cast(no_delay)); 379 | #else 380 | return set_option(static_cast(protocol::tcp), option::no_delay, static_cast(no_delay)); 381 | #endif 382 | } 383 | 384 | bool socket::set_ttl(int ttl) const 385 | { 386 | return set_option(options::level::ip, option::ttl, ttl); 387 | } 388 | 389 | bool socket::set_send_buffer(int size) const 390 | { 391 | return set_option(options::level::socket, option::send_buffer, size); 392 | } 393 | 394 | bool socket::set_receive_buffer(int size) const 395 | { 396 | return set_option(options::level::socket, option::receive_buffer, size); 397 | } 398 | 399 | bool socket::is_open() const 400 | { 401 | return INVALID_SOCKET != _sock; 402 | } 403 | 404 | net::socket &socket::operator=(socket&& sock) noexcept 405 | { 406 | this->_sock = sock._sock; 407 | std::swap(_local_endpoint, sock._local_endpoint); 408 | std::swap(_remote_endpoint, sock._remote_endpoint); 409 | return *this; 410 | } 411 | 412 | net::socket &socket::operator=(const socket& sock) = default; 413 | 414 | void socket::create(protocol pt) 415 | { 416 | auto type = socket_type::stream; 417 | if (pt == protocol::udp) 418 | type = socket_type::dgram; 419 | _sock = ::socket(PF_INET, static_cast(type), static_cast(pt)); 420 | } 421 | 422 | bool socket::operator==(const socket& sock) const 423 | { 424 | return _sock == sock._sock; 425 | } 426 | 427 | bool socket::operator==(socket&& sock) const 428 | { 429 | return _sock == sock._sock; 430 | } 431 | -------------------------------------------------------------------------------- /test/socket_tests.cpp: -------------------------------------------------------------------------------- 1 | #include "gtest/gtest.h" 2 | 3 | #include "net/socket.hpp" 4 | #include "net/dns.hpp" 5 | #include "net/context.hpp" 6 | 7 | #include 8 | 9 | #define TEST_ENDPOINT net::endpoint(net::ip_address::loopback, 8888) 10 | 11 | using namespace std::chrono_literals; 12 | 13 | TEST(socket, open) 14 | { 15 | net::socket s1(net::protocol::tcp); 16 | EXPECT_EQ(s1.is_open(), true); 17 | 18 | net::socket s2(net::protocol::udp); 19 | EXPECT_EQ(s2.is_open(), true); 20 | } 21 | 22 | TEST(socket, close) 23 | { 24 | net::socket sock(net::protocol::tcp); 25 | EXPECT_EQ(sock.is_open(), true); 26 | 27 | sock.close(); 28 | EXPECT_EQ(sock.is_open(), false); 29 | } 30 | 31 | TEST(socket, create) 32 | { 33 | net::socket sock; 34 | EXPECT_EQ(sock.is_open(), false); 35 | 36 | sock.create(); 37 | EXPECT_EQ(sock.is_open(), true); 38 | } 39 | 40 | TEST(socket, getHandle) 41 | { 42 | net::socket sock; 43 | EXPECT_EQ(sock.is_open(), false); 44 | EXPECT_EQ(sock.get_handle(), INVALID_SOCKET); 45 | 46 | sock.create(net::protocol::tcp); 47 | EXPECT_NE(sock.get_handle(), INVALID_SOCKET); 48 | } 49 | 50 | TEST(socket, set_handle) 51 | { 52 | net::socket s1(net::protocol::tcp), s2; 53 | EXPECT_EQ(s1.is_open(), true); 54 | EXPECT_EQ(s2.is_open(), false); 55 | 56 | auto handle = s1.get_handle(); 57 | 58 | s2.set_handle(handle); 59 | EXPECT_EQ(s2.get_handle(), handle); 60 | } 61 | 62 | TEST(socket, setOption_failure) 63 | { 64 | net::socket sock; 65 | EXPECT_EQ(sock.is_open(), false); 66 | EXPECT_EQ(sock.set_option(net::options::level::socket, net::option::broadcast, false), false); 67 | } 68 | 69 | TEST(socket, sync_connect) 70 | { 71 | net::socket sock(net::protocol::tcp); 72 | EXPECT_EQ(sock.is_open(), true); 73 | 74 | auto httpsPort = 443; 75 | auto example = "www.example.com"; 76 | auto entry = net::dns::get_host_entry(example); 77 | EXPECT_GT(entry.address_list.size(), 0); 78 | 79 | net::endpoint endpoint(entry.address_list[0], httpsPort); 80 | EXPECT_EQ(sock.connect(endpoint), true); 81 | } 82 | 83 | TEST(socket, async_connect) 84 | { 85 | net::socket sock(net::protocol::tcp); 86 | EXPECT_EQ(sock.is_open(), true); 87 | 88 | auto httpsPort = 443; 89 | auto example = "www.example.com"; 90 | auto entry = net::dns::get_host_entry(example); 91 | EXPECT_GT(entry.address_list.size(), 0); 92 | 93 | net::endpoint endpoint(entry.address_list[0], httpsPort); 94 | std::atomic> flag; 95 | net::context ctx; 96 | ctx.endpoint = endpoint; 97 | ctx.completed = [&flag](net::context*, bool success) { 98 | flag = success; 99 | }; 100 | EXPECT_EQ(sock.connect(&ctx), true); 101 | 102 | while (!flag.load().has_value()) {} 103 | EXPECT_EQ(flag.load(), true); 104 | } 105 | 106 | TEST(socket, bind) 107 | { 108 | net::socket sock(net::protocol::tcp); 109 | EXPECT_EQ(sock.is_open(), true); 110 | EXPECT_EQ(sock.set_reuse_address(true), true); 111 | EXPECT_EQ(sock.bind(TEST_ENDPOINT), true); 112 | 113 | auto localEndpoint = sock.get_local_endpoint(); 114 | auto remoteEndpoint = sock.get_remote_endpoint(); 115 | EXPECT_EQ(localEndpoint.has_value(), true); 116 | EXPECT_EQ(remoteEndpoint.has_value(), false); 117 | EXPECT_EQ(localEndpoint.value(), TEST_ENDPOINT); 118 | } 119 | 120 | TEST(socket, listen) 121 | { 122 | net::socket sock(net::protocol::tcp); 123 | EXPECT_EQ(sock.is_open(), true); 124 | EXPECT_EQ(sock.set_reuse_address(true), true); 125 | EXPECT_EQ(sock.bind(TEST_ENDPOINT), true); 126 | EXPECT_EQ(sock.listen(), true); 127 | } 128 | 129 | TEST(socket, server_get_local_endpoint) 130 | { 131 | net::socket sock(net::protocol::tcp); 132 | EXPECT_EQ(sock.is_open(), true); 133 | EXPECT_EQ(sock.set_reuse_address(true), true); 134 | EXPECT_EQ(sock.bind(TEST_ENDPOINT), true); 135 | 136 | auto localEndpoint = sock.get_local_endpoint(); 137 | EXPECT_EQ(localEndpoint.has_value(), true); 138 | EXPECT_EQ(localEndpoint.value(), TEST_ENDPOINT); 139 | } 140 | 141 | TEST(socket, server_get_remote_endpoint) 142 | { 143 | net::socket sock(net::protocol::tcp); 144 | EXPECT_EQ(sock.is_open(), true); 145 | EXPECT_EQ(sock.set_reuse_address(true), true); 146 | EXPECT_EQ(sock.bind(TEST_ENDPOINT), true); 147 | 148 | auto remoteEndpoint = sock.get_remote_endpoint(); 149 | EXPECT_EQ(remoteEndpoint.has_value(), false); 150 | } 151 | 152 | TEST(socket, sync_disconnect) 153 | { 154 | net::socket sock(net::protocol::tcp); 155 | EXPECT_EQ(sock.is_open(), true); 156 | 157 | auto httpsPort = 443; 158 | auto example = "www.example.com"; 159 | auto entry = net::dns::get_host_entry(example); 160 | EXPECT_GT(entry.address_list.size(), 0); 161 | 162 | net::endpoint endpoint(entry.address_list[0], httpsPort); 163 | EXPECT_EQ(sock.connect(endpoint), true); 164 | 165 | sock.disconnect(); 166 | EXPECT_EQ(sock.get_remote_endpoint().has_value(), false); 167 | } 168 | 169 | TEST(socket, async_disconnect) 170 | { 171 | net::socket sock(net::protocol::tcp); 172 | EXPECT_EQ(sock.is_open(), true); 173 | 174 | auto httpsPort = 443; 175 | auto example = "www.example.com"; 176 | auto entry = net::dns::get_host_entry(example); 177 | EXPECT_GT(entry.address_list.size(), 0); 178 | 179 | net::endpoint endpoint(entry.address_list[0], httpsPort); 180 | EXPECT_EQ(sock.connect(endpoint), true); 181 | 182 | std::atomic> flag; 183 | auto ctx = new net::context; 184 | ctx->completed = [&](net::context*, bool success) { 185 | flag = success; 186 | }; 187 | EXPECT_EQ(sock.disconnect(ctx), true); 188 | 189 | while (!flag.load().has_value()) {} 190 | EXPECT_EQ(flag.load(), true); 191 | } 192 | 193 | TEST(socket, sync_accept) 194 | { 195 | auto server = std::async(std::launch::async, [] { 196 | net::socket sock(net::protocol::tcp); 197 | EXPECT_EQ(sock.is_open(), true); 198 | EXPECT_EQ(sock.set_reuse_address(true), true); 199 | EXPECT_EQ(sock.bind(TEST_ENDPOINT), true); 200 | EXPECT_EQ(sock.listen(), true); 201 | EXPECT_EQ(sock.accept().is_open(), true); 202 | }); 203 | std::this_thread::sleep_for(100ms); 204 | // auto client = std::async(std::launch::async, [] { 205 | net::socket sock(net::protocol::tcp); 206 | EXPECT_EQ(sock.is_open(), true); 207 | EXPECT_EQ(sock.connect(TEST_ENDPOINT), true); 208 | // }); 209 | 210 | server.get(); 211 | //client.get(); 212 | } 213 | 214 | TEST(socket, async_accept) 215 | { 216 | net::socket sock(net::protocol::tcp); 217 | EXPECT_EQ(sock.is_open(), true); 218 | EXPECT_EQ(sock.set_reuse_address(true), true); 219 | EXPECT_EQ(sock.bind(TEST_ENDPOINT), true); 220 | EXPECT_EQ(sock.listen(), true); 221 | 222 | std::atomic> flag; 223 | auto ctx = new net::context; 224 | ctx->completed = [&flag](net::context* ctx, bool success) { 225 | flag = success; 226 | //delete ctx; 227 | }; 228 | EXPECT_EQ(sock.accept(ctx), true); 229 | 230 | std::this_thread::sleep_for(100ms); 231 | 232 | auto client = std::async(std::launch::async, [] { 233 | net::socket sock(net::protocol::tcp); 234 | EXPECT_EQ(sock.is_open(), true); 235 | EXPECT_EQ(sock.connect(TEST_ENDPOINT), true); 236 | }); 237 | 238 | while(!flag.load().has_value()) {} 239 | EXPECT_EQ(flag.load(), true); 240 | } 241 | 242 | TEST(socket, sync_send) 243 | { 244 | net::socket sock(net::protocol::tcp); 245 | EXPECT_EQ(sock.is_open(), true); 246 | 247 | auto httpsPort = 443; 248 | auto example = "www.example.com"; 249 | auto entry = net::dns::get_host_entry(example); 250 | EXPECT_GT(entry.address_list.size(), 0); 251 | 252 | net::endpoint endpoint(entry.address_list[0], httpsPort); 253 | EXPECT_EQ(sock.connect(endpoint), true); 254 | 255 | char buffer[] = "Hello"; 256 | EXPECT_GE(sock.send(buffer), 0); 257 | } 258 | 259 | TEST(socket, async_send) 260 | { 261 | net::socket sock(net::protocol::tcp); 262 | EXPECT_EQ(sock.is_open(), true); 263 | 264 | auto httpsPort = 443; 265 | auto example = "www.example.com"; 266 | auto entry = net::dns::get_host_entry(example); 267 | EXPECT_GT(entry.address_list.size(), 0); 268 | 269 | net::endpoint endpoint(entry.address_list[0], httpsPort); 270 | EXPECT_EQ(sock.connect(endpoint), true); 271 | 272 | std::atomic> flag; 273 | std::string data = "Hello, World!"; 274 | auto ctx = new net::context; 275 | ctx->completed = [&flag](net::context* ctx, bool success) { 276 | flag = success && ctx->length > 0; 277 | }; 278 | ctx->set_buffer(data); 279 | EXPECT_EQ(sock.send(ctx), true); 280 | 281 | while(!flag.load().has_value()) {} 282 | EXPECT_EQ(flag.load(), true); 283 | 284 | delete ctx; 285 | } 286 | 287 | TEST(socket, async_send_buffer_list) 288 | { 289 | net::socket sock(net::protocol::tcp); 290 | EXPECT_EQ(sock.is_open(), true); 291 | 292 | auto httpsPort = 443; 293 | auto example = "www.example.com"; 294 | auto entry = net::dns::get_host_entry(example); 295 | EXPECT_GT(entry.address_list.size(), 0); 296 | 297 | net::endpoint endpoint(entry.address_list[0], httpsPort); 298 | EXPECT_EQ(sock.connect(endpoint), true); 299 | 300 | std::atomic> flag; 301 | 302 | std::string data1 = "Hello", data2 = "World"; 303 | 304 | auto ctx = new net::context; 305 | ctx->completed = [&flag](net::context* ctx, bool success) { 306 | flag = success && ctx->length > 0; 307 | }; 308 | ctx->add_data(data1); 309 | ctx->add_data(data2); 310 | EXPECT_EQ(sock.send(ctx), true); 311 | 312 | while(!flag.load().has_value()) {} 313 | EXPECT_EQ(flag.load(), true); 314 | } 315 | 316 | TEST(socket, sync_sendto) 317 | { 318 | net::socket server(net::protocol::udp); 319 | EXPECT_EQ(server.is_open(), true); 320 | EXPECT_EQ(server.bind(TEST_ENDPOINT), true); 321 | 322 | net::socket client(net::protocol::udp); 323 | EXPECT_EQ(client.is_open(), true); 324 | 325 | char buffer[] = "Hello"; 326 | EXPECT_GE(client.send(buffer, TEST_ENDPOINT), 0); 327 | } 328 | 329 | TEST(socket, sync_receive) 330 | { 331 | auto server = std::async(std::launch::async, [] { 332 | net::socket sock(net::protocol::tcp); 333 | EXPECT_EQ(sock.is_open(), true); 334 | EXPECT_EQ(sock.set_reuse_address(true), true); 335 | EXPECT_EQ(sock.bind(TEST_ENDPOINT), true); 336 | EXPECT_EQ(sock.listen(), true); 337 | 338 | auto client = sock.accept(); 339 | EXPECT_EQ(client.is_open(), true); 340 | 341 | char buffer[] = "hello"; 342 | EXPECT_GE(client.send(buffer), 0); 343 | }); 344 | std::this_thread::sleep_for(100ms); 345 | auto client = std::async(std::launch::async, [] { 346 | net::socket sock(net::protocol::tcp); 347 | EXPECT_EQ(sock.is_open(), true); 348 | EXPECT_EQ(sock.connect(TEST_ENDPOINT), true); 349 | 350 | char buffer[16] = { 0, }; 351 | EXPECT_GE(sock.receive(buffer), 0); 352 | }); 353 | 354 | server.get(); 355 | client.get(); 356 | } 357 | 358 | TEST(socket, async_receive) 359 | { 360 | auto server = std::async(std::launch::async, [] { 361 | net::socket sock(net::protocol::tcp); 362 | EXPECT_EQ(sock.is_open(), true); 363 | EXPECT_EQ(sock.set_reuse_address(true), true); 364 | EXPECT_EQ(sock.bind(TEST_ENDPOINT), true); 365 | EXPECT_EQ(sock.listen(), true); 366 | 367 | auto client = sock.accept(); 368 | EXPECT_EQ(client.is_open(), true); 369 | 370 | std::string data = "Hello"; 371 | EXPECT_GT(client.send(data), 0); 372 | }); 373 | 374 | std::this_thread::sleep_for(100ms); 375 | 376 | auto client = std::async(std::launch::async, [&] { 377 | std::atomic> flag; 378 | net::socket sock(net::protocol::tcp); 379 | EXPECT_EQ(sock.is_open(), true); 380 | EXPECT_EQ(sock.connect(TEST_ENDPOINT), true); 381 | 382 | auto ctx = new net::context; 383 | char buffer[16] = { 0, }; 384 | ctx->set_buffer(buffer); 385 | ctx->completed = [&flag](net::context* ctx, bool success) { 386 | flag = success && ctx->length > 0; 387 | }; 388 | EXPECT_EQ(sock.receive(ctx), true); 389 | while (!flag.load().has_value()) {}; 390 | return flag.load(); 391 | }); 392 | 393 | server.get(); 394 | EXPECT_EQ(client.get(), true); 395 | } 396 | 397 | TEST(socket, sync_receive_from) 398 | { 399 | auto server = std::async(std::launch::async, [] { 400 | net::socket server(net::protocol::udp); 401 | EXPECT_EQ(server.is_open(), true); 402 | EXPECT_EQ(server.set_reuse_address(true), true); 403 | EXPECT_EQ(server.bind(TEST_ENDPOINT), true); 404 | 405 | net::endpoint clientEndpoint; 406 | char buffer[16] { 0, }; 407 | EXPECT_GE(server.receive(buffer, clientEndpoint), 0); 408 | }); 409 | std::this_thread::sleep_for(100ms); 410 | auto client = std::async(std::launch::async, [] { 411 | net::socket client(net::protocol::udp); 412 | EXPECT_EQ(client.is_open(), true); 413 | 414 | char buffer[] = "Hello"; 415 | EXPECT_GE(client.send(buffer, TEST_ENDPOINT), 0); 416 | }); 417 | } 418 | 419 | TEST(socket, disable_blocking) 420 | { 421 | net::socket sock(net::protocol::tcp); 422 | EXPECT_EQ(sock.is_open(), true); 423 | EXPECT_EQ(sock.set_blocking(false), true); 424 | 425 | char buffer[16] { 0, }; 426 | EXPECT_EQ(sock.receive(buffer), SOCKET_ERROR); 427 | } 428 | 429 | TEST(socket, disable_blocking_failure) 430 | { 431 | net::socket sock; 432 | EXPECT_EQ(sock.is_open(), false); 433 | EXPECT_EQ(sock.set_blocking(false), false); 434 | } 435 | 436 | TEST(socket, set_linger) 437 | { 438 | net::socket sock(net::protocol::tcp); 439 | EXPECT_EQ(sock.is_open(), true); 440 | EXPECT_EQ(sock.set_linger({.enabled = true, .time = 0}), true); 441 | } 442 | 443 | TEST(socket, set_broadcast) 444 | { 445 | net::socket sock(net::protocol::udp); 446 | EXPECT_EQ(sock.is_open(), true); 447 | EXPECT_EQ(sock.set_broadcast(true), true); 448 | } 449 | 450 | TEST(socket, set_reuse_address) 451 | { 452 | net::socket sock(net::protocol::tcp); 453 | EXPECT_EQ(sock.is_open(), true); 454 | EXPECT_EQ(sock.set_reuse_address(true), true); 455 | } 456 | 457 | TEST(socket, set_no_delay) 458 | { 459 | net::socket sock(net::protocol::tcp); 460 | EXPECT_EQ(sock.is_open(), true); 461 | EXPECT_EQ(sock.set_no_delay(true), true); 462 | } 463 | 464 | TEST(socket, set_ttl) 465 | { 466 | net::socket sock(net::protocol::tcp); 467 | EXPECT_EQ(sock.is_open(), true); 468 | EXPECT_EQ(sock.set_ttl(255), true); 469 | } 470 | 471 | TEST(socket, set_send_buffer) 472 | { 473 | net::socket sock(net::protocol::tcp); 474 | EXPECT_EQ(sock.is_open(), true); 475 | EXPECT_EQ(sock.set_send_buffer(1024), true); 476 | } 477 | 478 | TEST(socket, set_receive_buffer) 479 | { 480 | net::socket sock(net::protocol::tcp); 481 | EXPECT_EQ(sock.is_open(), true); 482 | EXPECT_EQ(sock.set_receive_buffer(1024), true); 483 | } 484 | 485 | TEST(socket, is_open) 486 | { 487 | net::socket sock(net::protocol::tcp); 488 | EXPECT_EQ(sock.is_open(), true); 489 | } 490 | 491 | TEST(socket, get_option) 492 | { 493 | net::socket sock(net::protocol::tcp); 494 | EXPECT_EQ(sock.is_open(), true); 495 | 496 | int value{}; 497 | EXPECT_EQ(sock.get_option(net::options::level::socket, net::option::send_buffer, value), true); 498 | } 499 | 500 | TEST(socket, get_option_failure) 501 | { 502 | net::socket sock; 503 | EXPECT_EQ(sock.is_open(), false); 504 | 505 | int value{}; 506 | EXPECT_EQ(sock.get_option(net::options::level::socket, net::option::send_buffer, value), false); 507 | } 508 | 509 | TEST(socket, operator_assignment_lvalue) 510 | { 511 | net::socket s1(net::protocol::tcp); 512 | EXPECT_EQ(s1.is_open(), true); 513 | 514 | net::socket s2; 515 | s2 = s1; 516 | EXPECT_EQ(s2.is_open(), true); 517 | } 518 | 519 | TEST(socket, operator_assignment_rvalue) 520 | { 521 | net::socket sock; 522 | sock = net::socket(net::protocol::tcp); 523 | EXPECT_EQ(sock.is_open(), true); 524 | } 525 | 526 | TEST(socket, operator_equal_lvalue) 527 | { 528 | net::socket s1(net::protocol::tcp); 529 | EXPECT_EQ(s1.is_open(), true); 530 | 531 | net::socket s2 = s1; 532 | EXPECT_EQ(s2.is_open(), true); 533 | EXPECT_EQ(s1 == s2, true); 534 | } 535 | 536 | TEST(socket, operator_equal_rvalue) 537 | { 538 | net::socket s1(net::protocol::tcp); 539 | EXPECT_EQ(s1.is_open(), true); 540 | 541 | net::socket s2; 542 | s2.set_handle(s1.get_handle()); 543 | EXPECT_EQ(s2.is_open(), true); 544 | EXPECT_EQ(s1 == std::move(s2), true); 545 | } 546 | 547 | TEST(socket, constructor_lvalue) 548 | { 549 | net::socket s1(net::protocol::tcp); 550 | EXPECT_EQ(s1.is_open(), true); 551 | 552 | auto handle = s1.get_handle(); 553 | EXPECT_NE(handle, INVALID_SOCKET); 554 | 555 | net::socket s2(s1); 556 | EXPECT_EQ(s2.get_handle(), handle); 557 | } 558 | 559 | TEST(socket, constructor_rvalue) 560 | { 561 | net::socket s1(net::protocol::tcp); 562 | EXPECT_EQ(s1.is_open(), true); 563 | 564 | auto handle = s1.get_handle(); 565 | EXPECT_NE(handle, INVALID_SOCKET); 566 | 567 | net::socket s2(std::move(s1)); 568 | EXPECT_EQ(s2.get_handle(), handle); 569 | } 570 | --------------------------------------------------------------------------------