├── .gitignore ├── README.md ├── include └── ggsock │ ├── common.h │ ├── communicator.h │ ├── file-server.h │ └── serialization.h ├── tests ├── CMakeLists.txt └── test0.cpp ├── tools ├── CMakeLists.txt ├── test-server.cpp ├── test-client.cpp ├── test-file-server.cpp ├── test_server.cpp ├── test_client.cpp └── test-file-client.cpp ├── src ├── CMakeLists.txt ├── serialization.cpp ├── file-server.cpp └── communicator.cpp ├── cmake ├── GitVars.cmake └── BuildTypes.cmake └── CMakeLists.txt /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_STORE 2 | .clangd 3 | 4 | build 5 | build-* 6 | 7 | compile_commands.json 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ggsock 2 | Non-blocking sockets wrapper. Useful for building c++ socket apps with emscripten. 3 | -------------------------------------------------------------------------------- /include/ggsock/common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace GGSock { 7 | using TPort = int32_t; 8 | using TAddress = std::string; 9 | } 10 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | enable_testing() 2 | 3 | set (TEST_TARGET test0) 4 | 5 | add_executable(${TEST_TARGET} 6 | test0.cpp 7 | ) 8 | 9 | target_link_libraries(${TEST_TARGET} PRIVATE 10 | ggsock 11 | ) 12 | 13 | add_test(NAME test0 COMMAND $) 14 | -------------------------------------------------------------------------------- /tools/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TOOL_TARGET test-server) 2 | add_executable(${TOOL_TARGET} test-server.cpp) 3 | target_link_libraries(${TOOL_TARGET} PRIVATE ggsock) 4 | 5 | set(TOOL_TARGET test-client) 6 | add_executable(${TOOL_TARGET} test-client.cpp) 7 | target_link_libraries(${TOOL_TARGET} PRIVATE ggsock) 8 | 9 | set(TOOL_TARGET test-file-server) 10 | add_executable(${TOOL_TARGET} test-file-server.cpp) 11 | target_link_libraries(${TOOL_TARGET} PRIVATE ggsock) 12 | 13 | set(TOOL_TARGET test-file-client) 14 | add_executable(${TOOL_TARGET} test-file-client.cpp) 15 | target_link_libraries(${TOOL_TARGET} PRIVATE ggsock) 16 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | ## ggsock 3 | 4 | add_library(ggsock 5 | communicator.cpp 6 | file-server.cpp 7 | serialization.cpp 8 | ) 9 | 10 | target_include_directories(ggsock PUBLIC 11 | ./ 12 | ../include/ 13 | ) 14 | 15 | target_include_directories(ggsock PRIVATE 16 | ) 17 | 18 | target_link_libraries(ggsock PUBLIC 19 | ${CMAKE_THREAD_LIBS_INIT} 20 | ) 21 | 22 | if (WIN32) 23 | target_link_libraries(ggsock PRIVATE wsock32 ws2_32) 24 | endif() 25 | 26 | if (MINGW) 27 | target_link_libraries(ggsock PRIVATE wsock32 ws2_32 stdc++) 28 | endif() 29 | 30 | install(TARGETS ggsock 31 | LIBRARY DESTINATION lib 32 | ARCHIVE DESTINATION lib/static 33 | ) 34 | -------------------------------------------------------------------------------- /cmake/GitVars.cmake: -------------------------------------------------------------------------------- 1 | find_package(Git) 2 | 3 | # the commit's SHA1 4 | execute_process(COMMAND 5 | "${GIT_EXECUTABLE}" describe --match=NeVeRmAtCh --always --abbrev=8 6 | WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" 7 | OUTPUT_VARIABLE GIT_SHA1 8 | ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) 9 | 10 | # the date of the commit 11 | execute_process(COMMAND 12 | "${GIT_EXECUTABLE}" log -1 --format=%ad --date=local 13 | WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" 14 | OUTPUT_VARIABLE GIT_DATE 15 | ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) 16 | 17 | # the subject of the commit 18 | execute_process(COMMAND 19 | "${GIT_EXECUTABLE}" log -1 --format=%s 20 | WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" 21 | OUTPUT_VARIABLE GIT_COMMIT_SUBJECT 22 | ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) 23 | -------------------------------------------------------------------------------- /tools/test-server.cpp: -------------------------------------------------------------------------------- 1 | /*! \file test_server.cpp 2 | * \brief Enter description here. 3 | * \author Georgi Gerganov 4 | */ 5 | 6 | #include "ggsock/communicator.h" 7 | 8 | #include 9 | #include 10 | 11 | int main(int argc, char ** argv) { 12 | printf("Usage: %s port\n", argv[0]); 13 | if (argc < 2) { 14 | return -1; 15 | } 16 | 17 | int port = atoi(argv[1]); 18 | 19 | GGSock::Communicator server0(false); 20 | GGSock::Communicator server1(false); 21 | 22 | std::vector data(128*1024); 23 | 24 | while (true) { 25 | server0.listen(port, 0); 26 | server1.listen(port, 0); 27 | 28 | if (server0.isConnected()) { 29 | printf("sending client 1\n"); 30 | server0.send(95); 31 | server0.send(94, data.data(), data.size()); 32 | } 33 | 34 | if (server1.isConnected()) { 35 | printf("sending client 2\n"); 36 | server1.send(95); 37 | server1.send(94, data.data(), data.size()); 38 | } 39 | 40 | server0.update(); 41 | server1.update(); 42 | 43 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 44 | } 45 | 46 | return 0; 47 | } 48 | -------------------------------------------------------------------------------- /tools/test-client.cpp: -------------------------------------------------------------------------------- 1 | /*! \file test_client.cpp 2 | * \brief Enter description here. 3 | * \author Georgi Gerganov 4 | */ 5 | 6 | #ifdef __EMSCRIPTEN__ 7 | #include "emscripten/emscripten.h" 8 | #endif 9 | 10 | #include "ggsock/communicator.h" 11 | 12 | #include 13 | 14 | std::function g_update; 15 | 16 | void update() { 17 | g_update(); 18 | } 19 | 20 | int main(int argc, char ** argv) { 21 | printf("Usage: %s ip port\n", argv[0]); 22 | if (argc < 3) { 23 | return -1; 24 | } 25 | 26 | std::string ip = "127.0.0.1"; 27 | int port = 12003; 28 | 29 | if (argc > 1) ip = argv[1]; 30 | if (argc > 2) port = atoi(argv[2]); 31 | 32 | printf("Connecting to %s : %d\n", ip.c_str(), port); 33 | 34 | GGSock::Communicator client(false); 35 | 36 | client.setMessageCallback(95, [](const char * , size_t ) { 37 | printf("Received message 95\n"); 38 | return 0; 39 | }); 40 | client.setMessageCallback(94, [](const char * , size_t dataSize) { 41 | printf("Received message 94, dataSize = %d\n", (int) dataSize); 42 | return 0; 43 | }); 44 | 45 | g_update = [&]() { 46 | if (client.connect(ip, port, 0)) { 47 | printf("Started connecting ...\n"); 48 | } 49 | 50 | if (client.isConnected()) { 51 | client.send(195); 52 | } 53 | 54 | client.update(); 55 | }; 56 | 57 | 58 | #ifdef __EMSCRIPTEN__ 59 | emscripten_set_main_loop(update, 0, 1); 60 | #else 61 | while(true) { 62 | update(); 63 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 64 | } 65 | #endif 66 | 67 | return 0; 68 | } 69 | -------------------------------------------------------------------------------- /tools/test-file-server.cpp: -------------------------------------------------------------------------------- 1 | #include "ggsock/file-server.h" 2 | 3 | #include 4 | #include 5 | 6 | int main(int argc, char ** argv) { 7 | printf("Usage: %s port\n", argv[0]); 8 | if (argc < 2) { 9 | return -1; 10 | } 11 | 12 | int port = atoi(argv[1]); 13 | 14 | GGSock::FileServer server; 15 | 16 | if (server.init({ 4, 8, 128, 128, port}) == false) { 17 | printf("Failed to initialize GGSock::FileServer\n"); 18 | return -1; 19 | } 20 | 21 | GGSock::FileServer::FileData file0 { { "test-uri-0", 0, "test-filename-0" }, std::vector(6343) }; 22 | GGSock::FileServer::FileData file1 { { "test-uri-1", 0, "test-filename-1" }, std::vector(3535342) }; 23 | GGSock::FileServer::FileData file2 { { "test-uri-2", 0, "test-filename-2" }, std::vector(37) }; 24 | 25 | for (int i = 0; i < (int) file0.data.size(); ++i) file0.data[i] = i%101; 26 | for (int i = 0; i < (int) file1.data.size(); ++i) file1.data[i] = (3*i + 1)%103; 27 | 28 | server.addFile(std::move(file1)); 29 | server.addFile(std::move(file0)); 30 | server.addFile(std::move(file2)); 31 | 32 | server.startListening(); 33 | 34 | while (true) { 35 | printf("Listening ...\n"); 36 | auto clientInfos = server.getClientInfos(); 37 | 38 | if (clientInfos.size() > 0) { 39 | printf("Connected clients:\n"); 40 | for (const auto & client : clientInfos) { 41 | printf(" - %d : %s\n", client.first, client.second.address.c_str()); 42 | } 43 | } 44 | 45 | std::this_thread::sleep_for(std::chrono::seconds(1)); 46 | } 47 | 48 | return 0; 49 | } 50 | -------------------------------------------------------------------------------- /tools/test_server.cpp: -------------------------------------------------------------------------------- 1 | /*! \file test_server.cpp 2 | * \brief Enter description here. 3 | * \author Georgi Gerganov 4 | */ 5 | 6 | #include "ggsock/Communicator.h" 7 | 8 | #include 9 | #include 10 | 11 | int main(int argc, char ** argv) { 12 | printf("Usage: %s port\n", argv[0]); 13 | if (argc < 2) { 14 | return -1; 15 | } 16 | 17 | int port = atoi(argv[1]); 18 | 19 | auto worker = std::thread([&]() { 20 | GGSock::Communicator server; 21 | 22 | std::vector data(128*1024); 23 | 24 | while(true) { 25 | printf("init server, port = %d\n", port); 26 | if (server.listen(port, 1000) == false) continue; 27 | while (server.isConnected()) { 28 | printf("sending client 2\n"); 29 | server.send(95); 30 | server.send(94, data.data(), data.size()); 31 | std::this_thread::sleep_for(std::chrono::seconds(1)); 32 | } 33 | std::this_thread::sleep_for(std::chrono::seconds(1)); 34 | } 35 | }); 36 | 37 | GGSock::Communicator server; 38 | 39 | std::vector data(128*1024); 40 | 41 | while(true) { 42 | printf("init server, port = %d\n", port); 43 | if (server.listen(port, 1000) == false) continue; 44 | while (server.isConnected()) { 45 | printf("sending client 1\n"); 46 | server.send(95); 47 | server.send(94, data.data(), data.size()); 48 | std::this_thread::sleep_for(std::chrono::seconds(1)); 49 | } 50 | std::this_thread::sleep_for(std::chrono::seconds(1)); 51 | } 52 | 53 | worker.join(); 54 | 55 | return 0; 56 | } 57 | -------------------------------------------------------------------------------- /include/ggsock/communicator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ggsock/common.h" 4 | 5 | #include 6 | #include 7 | 8 | namespace GGSock { 9 | class Communicator { 10 | public: 11 | using TErrorCode = int16_t; 12 | using TBufferSize = uint32_t; 13 | using TMessageType = uint16_t; 14 | 15 | using CBError = std::function; 16 | using CBMessage = std::function; 17 | 18 | Communicator(bool startOwnWorker); 19 | ~Communicator(); 20 | 21 | bool update(); 22 | 23 | bool listen(TPort port, int32_t timeout_ms, int32_t maxConnections = 1); 24 | bool connect(const TAddress & address, TPort port, int32_t timeout_ms); 25 | 26 | bool disconnect(); 27 | bool stopListening(); 28 | bool isConnected() const; 29 | bool isConnecting() const; 30 | TAddress getPeerAddress() const; 31 | 32 | bool send(TMessageType type); 33 | bool send(TMessageType type, const char * dataBuffer, TBufferSize dataSize); 34 | 35 | bool setErrorCallback(CBError && callback); 36 | bool setMessageCallback(TMessageType type, CBMessage && callback); 37 | 38 | bool removeErrorCallback(); 39 | bool removeMessageCallback(TMessageType type); 40 | 41 | static TAddress getLocalAddress(); 42 | 43 | private: 44 | struct Data; 45 | std::unique_ptr data_; 46 | Data & getData() { return *data_; } 47 | const Data & getData() const { return *data_; } 48 | }; 49 | } 50 | -------------------------------------------------------------------------------- /tools/test_client.cpp: -------------------------------------------------------------------------------- 1 | /*! \file test_client.cpp 2 | * \brief Enter description here. 3 | * \author Georgi Gerganov 4 | */ 5 | 6 | #ifdef __EMSCRIPTEN__ 7 | #include "emscripten/emscripten.h" 8 | #endif 9 | 10 | #include "ggsock/Communicator.h" 11 | 12 | #include 13 | 14 | void update() { 15 | } 16 | 17 | int main(int argc, char ** argv) { 18 | printf("Usage: %s ip port\n", argv[0]); 19 | if (argc < 3) { 20 | return -1; 21 | } 22 | 23 | std::string ip = "127.0.0.1"; 24 | int port = 12003; 25 | 26 | if (argc > 1) ip = argv[1]; 27 | if (argc > 2) port = atoi(argv[2]); 28 | 29 | printf("Connecting to %s : %d\n", ip.c_str(), port); 30 | 31 | GGSock::Communicator client; 32 | client.setMessageCallback(95, [](const char * dataBuffer, size_t dataSize) { 33 | printf("Received message 95\n"); 34 | return 0; 35 | }); 36 | client.setMessageCallback(94, [](const char * dataBuffer, size_t dataSize) { 37 | printf("Received message 94, dataSize = %d\n", (int) dataSize); 38 | return 0; 39 | }); 40 | 41 | std::thread worker = std::thread([&]() { 42 | while(true) { 43 | printf("init client\n"); 44 | if (client.connect(ip, port, 1000) == false) continue; 45 | printf("after connect\n"); 46 | 47 | while (client.isConnected()) { 48 | client.send(195); 49 | std::this_thread::sleep_for(std::chrono::seconds(1)); 50 | } 51 | std::this_thread::sleep_for(std::chrono::seconds(1)); 52 | } 53 | }); 54 | 55 | #ifdef __EMSCRIPTEN__ 56 | emscripten_set_main_loop(update, 0, 1); 57 | #else 58 | while(true) { 59 | update(); 60 | std::this_thread::sleep_for(std::chrono::seconds(1)); 61 | } 62 | #endif 63 | 64 | return 0; 65 | } 66 | -------------------------------------------------------------------------------- /cmake/BuildTypes.cmake: -------------------------------------------------------------------------------- 1 | # Add new build types 2 | 3 | # ReleaseGG - Release with enabled asserts 4 | 5 | SET(CMAKE_CXX_FLAGS_RELEASEGG 6 | "-O3" 7 | CACHE STRING "Flags used by the c++ compiler during release builds with enabled asserts." 8 | FORCE ) 9 | SET(CMAKE_C_FLAGS_RELEASEGG 10 | "-O3" 11 | CACHE STRING "Flags used by the compiler during release builds with enabled asserts." 12 | FORCE ) 13 | SET(CMAKE_EXE_LINKER_FLAGS_RELEASEGG 14 | "" 15 | CACHE STRING "Flags used for linking binaries during release builds with enabled asserts." 16 | FORCE ) 17 | SET(CMAKE_SHARED_LINKER_FLAGS_RELEASEGG 18 | "" 19 | CACHE STRING "Flags used by the shared libraries linker during release builds with enabled asserts." 20 | FORCE ) 21 | MARK_AS_ADVANCED( 22 | CMAKE_CXX_FLAGS_RELEASEGG 23 | CMAKE_C_FLAGS_RELEASEGG 24 | CMAKE_EXE_LINKER_FLAGS_RELEASEGG 25 | CMAKE_SHARED_LINKER_FLAGS_RELEASEGG ) 26 | 27 | # RelWithDebInfoGG - RelWithDebInfo with enabled asserts 28 | 29 | SET(CMAKE_CXX_FLAGS_RELWITHDEBINFOGG 30 | "-O2 -g" 31 | CACHE STRING "Flags used by the c++ compiler during release builds with debug symbols and enabled asserts." 32 | FORCE ) 33 | SET(CMAKE_C_FLAGS_RELWITHDEBINFOGG 34 | "-O2 -g" 35 | CACHE STRING "Flags used by the compiler during release builds with debug symbols and enabled asserts." 36 | FORCE ) 37 | SET(CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFOGG 38 | "" 39 | CACHE STRING "Flags used for linking binaries during release builds with debug symbols and enabled asserts." 40 | FORCE ) 41 | SET(CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFOGG 42 | "" 43 | CACHE STRING "Flags used by the shared libraries linker during release builds with debug symbols and enabled asserts." 44 | FORCE ) 45 | MARK_AS_ADVANCED( 46 | CMAKE_CXX_FLAGS_RELWITHDEBINFOGG 47 | CMAKE_C_FLAGS_RELWITHDEBINFOGG 48 | CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFOGG 49 | CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFOGG ) 50 | 51 | if (NOT XCODE AND NOT MSVC AND NOT CMAKE_BUILD_TYPE) 52 | set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE) 53 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo" "ReleaseGG" "RelWithDebInfoGG") 54 | endif() 55 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.10) 2 | project (ggsock) 3 | 4 | set(CMAKE_EXPORT_COMPILE_COMMANDS "on") 5 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) 6 | 7 | if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) 8 | set(GGSOCK_STANDALONE ON) 9 | include(cmake/GitVars.cmake) 10 | include(cmake/BuildTypes.cmake) 11 | else() 12 | set(GGSOCK_STANDALONE OFF) 13 | endif() 14 | 15 | if (EMSCRIPTEN) 16 | set(BUILD_SHARED_LIBS_DEFAULT OFF) 17 | else() 18 | set(BUILD_SHARED_LIBS_DEFAULT ON) 19 | endif() 20 | 21 | # options 22 | 23 | option(BUILD_SHARED_LIBS "ggsock: build shared libs" ${BUILD_SHARED_LIBS_DEFAULT}) 24 | 25 | option(GGSOCK_ALL_WARNINGS "ggsock: enable all compiler warnings" ON) 26 | option(GGSOCK_ALL_WARNINGS_3RD_PARTY "ggsock: enable all compiler warnings in 3rd party libs" ON) 27 | 28 | option(GGSOCK_SANITIZE_THREAD "ggsock: enable thread sanitizer" OFF) 29 | option(GGSOCK_SANITIZE_ADDRESS "ggsock: enable address sanitizer" OFF) 30 | option(GGSOCK_SANITIZE_UNDEFINED "ggsock: enable undefined sanitizer" OFF) 31 | 32 | option(GGSOCK_BUILD_EXAMPLES "ggsock: build examples" ${GGSOCK_STANDALONE}) 33 | 34 | # sanitizers 35 | 36 | if (GGSOCK_SANITIZE_THREAD) 37 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=thread") 38 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread") 39 | endif() 40 | 41 | if (GGSOCK_SANITIZE_ADDRESS) 42 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer") 43 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer") 44 | endif() 45 | 46 | if (GGSOCK_SANITIZE_UNDEFINED) 47 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=undefined") 48 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined") 49 | endif() 50 | 51 | # dependencies 52 | 53 | find_package(Threads REQUIRED) 54 | 55 | # main 56 | 57 | set(CMAKE_CXX_STANDARD 14) 58 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 59 | 60 | if (GGSOCK_ALL_WARNINGS) 61 | if (CMAKE_COMPILER_IS_GNUCC OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") 62 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic") 63 | else() 64 | # todo : windows 65 | endif() 66 | endif() 67 | 68 | add_subdirectory(src) 69 | 70 | if (GGSOCK_DISABLE_PROGRAMS_BUILD) 71 | return() 72 | endif () 73 | 74 | if (GGSOCK_STANDALONE AND GGSOCK_BUILD_EXAMPLES) 75 | add_subdirectory(tests) 76 | add_subdirectory(tools) 77 | endif() 78 | -------------------------------------------------------------------------------- /tests/test0.cpp: -------------------------------------------------------------------------------- 1 | #include "ggsock/communicator.h" 2 | 3 | #include 4 | 5 | int main() { 6 | { 7 | GGSock::Communicator server(true); 8 | server.setErrorCallback([](GGSock::Communicator::TErrorCode code) { 9 | printf("Network error = %d\n", code); 10 | }); 11 | 12 | for (int i = 0; i < 3; ++i) { 13 | printf("iter %d\n", i); 14 | if (server.isConnected()) return 1; 15 | if (server.listen(12345, 10) == true) return 2; 16 | if (server.isConnected()) return 3; 17 | if (server.disconnect() == false) return 4; 18 | if (server.isConnected()) return 5; 19 | } 20 | 21 | for (int i = 0; i < 3; ++i) { 22 | GGSock::Communicator client(true); 23 | if (server.listen(12345, 0) == false) return 6; 24 | if (client.connect("127.0.0.1", 12345, 100) == false) return 7; 25 | while (client.isConnected() == false) {} 26 | while (server.isConnected() == false) {} 27 | if (client.connect("127.0.0.1", 12345, 100) == true) return 8; 28 | if (client.disconnect() == false) return 9; 29 | while (server.isConnected()) {} 30 | while (client.isConnected()) {} 31 | if (client.connect("127.0.0.1", 12345, 100) == true) return 10; 32 | if (server.listen(12345, 0) == false) return 11; 33 | if (client.connect("127.0.0.1", 12345, 100) == false) return 12; 34 | while (client.isConnected() == false) {} 35 | while (server.isConnected() == false) {} 36 | if (client.disconnect() == false) return 13; 37 | while (client.isConnected()) {} 38 | while (server.isConnected()) {} 39 | } 40 | } 41 | 42 | { 43 | GGSock::Communicator server(true); 44 | server.setErrorCallback([](GGSock::Communicator::TErrorCode code) { 45 | printf("Network error = %d\n", code); 46 | }); 47 | server.setMessageCallback(42, [&](const char * , size_t dataSize) { 48 | printf("Received buffer. Size = %d\n", (int) dataSize); 49 | server.send(43); 50 | return true; 51 | }); 52 | 53 | server.listen(12345, 0); 54 | 55 | GGSock::Communicator client(true); 56 | client.setMessageCallback(43, [](const char * , size_t ) { 57 | printf("Received acknowledgment\n"); 58 | return true; 59 | }); 60 | 61 | if (client.connect("127.0.0.1", 12345, 100) == false) return 14; 62 | 63 | while (client.isConnected() == false) {} 64 | while (server.isConnected() == false) {} 65 | 66 | char buf[16]; 67 | 68 | if (client.send(42, buf, 16) == false) return 15; 69 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 70 | if (client.send(42, buf, 16) == false) return 16; 71 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 72 | if (client.send(42, buf, 16) == false) return 17; 73 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 74 | 75 | if (client.disconnect() == false) return 18; 76 | } 77 | 78 | printf("Done!\n"); 79 | 80 | return 0; 81 | } 82 | -------------------------------------------------------------------------------- /include/ggsock/file-server.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ggsock/common.h" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace GGSock { 10 | class FileServer { 11 | public: 12 | using TURI = std::string; 13 | using TFilesize = int64_t; 14 | using TFilename = std::string; 15 | 16 | using TFileId = int32_t; 17 | using TClientId = int32_t; 18 | using TChunkId = int32_t; 19 | using TProgress = float; 20 | 21 | struct FileInfo; 22 | struct ClientInfo; 23 | 24 | using TFileInfos = std::map; 25 | using TClientInfos = std::map; 26 | 27 | using TBinaryBlob = std::vector; 28 | 29 | enum MessageType { 30 | MsgFileInfosRequest = 100, 31 | MsgFileInfosResponse, 32 | MsgFileChunkRequest, 33 | MsgFileChunkResponse, 34 | }; 35 | 36 | struct FileChunkRequestData { 37 | TURI uri = ""; 38 | TChunkId chunkId = 0; 39 | int32_t nChunksHave = 0; 40 | int32_t nChunksExpected = 0; 41 | }; 42 | 43 | struct FileChunkResponseData { 44 | TURI uri = ""; 45 | TChunkId chunkId = 0; 46 | TBinaryBlob data; 47 | int64_t pStart = 0; 48 | int64_t pLen = 0; 49 | }; 50 | 51 | struct Parameters { 52 | int32_t nWorkerThreads = 4; 53 | int32_t nMaxClients = 8; 54 | int32_t nMaxFiles = 128; 55 | int32_t nDefaultFileChunks = 128; 56 | 57 | TPort listenPort = 22765; 58 | }; 59 | 60 | struct FileInfo { 61 | TURI uri = "?"; 62 | TFilesize filesize = 0; 63 | TFilename filename = "?"; 64 | 65 | int32_t nChunks = 0; 66 | }; 67 | 68 | struct FileData { 69 | FileInfo info; 70 | TBinaryBlob data; 71 | }; 72 | 73 | struct ClientInfo { 74 | TPort port = 0; 75 | TAddress address = "0.0.0.0"; 76 | 77 | int64_t dataTx_bytes = 0; 78 | int64_t dataRx_bytes = 0; 79 | 80 | TFileId currentFileId = -1; 81 | TProgress currentProgress = 0.0f; 82 | }; 83 | 84 | FileServer(); 85 | ~FileServer(); 86 | 87 | bool init(const Parameters & parameters); 88 | 89 | bool startListening(); 90 | bool stopListening(); 91 | bool isListening(); 92 | 93 | bool update(); 94 | 95 | bool addFile(FileData && data); 96 | bool clearAllFiles(); 97 | bool clearFile(const TURI & uri); 98 | 99 | TFileInfos getFileInfos() const; 100 | TClientInfos getClientInfos() const; 101 | 102 | const Parameters & getParameters() const; 103 | const FileData & getFileData(const TURI & uri) const; 104 | 105 | private: 106 | struct Impl; 107 | std::unique_ptr m_impl; 108 | }; 109 | 110 | } 111 | -------------------------------------------------------------------------------- /tools/test-file-client.cpp: -------------------------------------------------------------------------------- 1 | #include "ggsock/communicator.h" 2 | #include "ggsock/file-server.h" 3 | #include "ggsock/serialization.h" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | std::function g_update; 10 | 11 | void update() { 12 | g_update(); 13 | } 14 | 15 | int main(int argc, char ** argv) { 16 | printf("Usage: %s ip port\n", argv[0]); 17 | if (argc < 3) { 18 | return -1; 19 | } 20 | 21 | std::string ip = "127.0.0.1"; 22 | int port = 12003; 23 | 24 | if (argc > 1) ip = argv[1]; 25 | if (argc > 2) port = atoi(argv[2]); 26 | 27 | printf("Connecting to %s : %d\n", ip.c_str(), port); 28 | 29 | bool hasFileInfos = false; 30 | bool hasRequestedFiles = false; 31 | GGSock::FileServer::TFileInfos fileInfos; 32 | std::map files; 33 | 34 | GGSock::Communicator client(false); 35 | 36 | client.setErrorCallback([](GGSock::Communicator::TErrorCode code) { 37 | printf("Disconnected with code = %d\n", code); 38 | }); 39 | 40 | client.setMessageCallback(GGSock::FileServer::MsgFileInfosResponse, [&](const char * dataBuffer, size_t dataSize) { 41 | printf("Received message %d, size = %d\n", GGSock::FileServer::MsgFileInfosResponse, (int) dataSize); 42 | 43 | size_t offset = 0; 44 | GGSock::Unserialize()(fileInfos, dataBuffer, dataSize, offset); 45 | 46 | for (const auto & info : fileInfos) { 47 | printf(" - %s : %s (size = %d, chunks = %d)\n", info.second.uri.c_str(), info.second.filename.c_str(), (int) info.second.filesize, (int) info.second.nChunks); 48 | files[info.second.uri].info = info.second; 49 | files[info.second.uri].data.resize(info.second.filesize); 50 | } 51 | 52 | hasFileInfos = true; 53 | 54 | return 0; 55 | }); 56 | 57 | client.setMessageCallback(GGSock::FileServer::MsgFileChunkResponse, [&](const char * dataBuffer, size_t dataSize) { 58 | GGSock::FileServer::FileChunkResponseData data; 59 | 60 | size_t offset = 0; 61 | GGSock::Unserialize()(data, dataBuffer, dataSize, offset); 62 | 63 | printf("Received chunk %d for file '%s', size = %d\n", data.chunkId, data.uri.c_str(), (int) data.data.size()); 64 | std::memcpy(files[data.uri].data.data() + data.pStart, data.data.data(), data.pLen); 65 | 66 | if (data.chunkId == files[data.uri].info.nChunks - 1) { 67 | if (data.uri == "test-uri-0") { 68 | for (int i = 0; i < (int) files[data.uri].data.size(); ++i) { 69 | assert(files[data.uri].data[i] == i%101); 70 | } 71 | } 72 | if (data.uri == "test-uri-1") { 73 | for (int i = 0; i < (int) files[data.uri].data.size(); ++i) { 74 | assert(files[data.uri].data[i] == (3*i + 1)%103); 75 | } 76 | } 77 | } 78 | 79 | return 0; 80 | }); 81 | 82 | g_update = [&]() { 83 | if (client.connect(ip, port, 0)) { 84 | printf("Started connecting ...\n"); 85 | } 86 | 87 | if (client.isConnected()) { 88 | if (!hasFileInfos) { 89 | client.send(GGSock::FileServer::MsgFileInfosRequest); 90 | } else if (hasRequestedFiles == false) { 91 | printf("Requesting files ...\n"); 92 | for (const auto & fileInfo : fileInfos) { 93 | for (int i = 0; i < fileInfo.second.nChunks; ++i) { 94 | GGSock::FileServer::FileChunkRequestData data; 95 | data.uri = fileInfo.second.uri; 96 | data.chunkId = i; 97 | data.nChunksHave = 0; 98 | data.nChunksExpected = fileInfo.second.nChunks; 99 | 100 | GGSock::SerializationBuffer buffer; 101 | GGSock::Serialize()(data, buffer); 102 | client.send(GGSock::FileServer::MsgFileChunkRequest, buffer.data(), buffer.size()); 103 | client.update(); 104 | } 105 | } 106 | hasRequestedFiles = true; 107 | } 108 | } 109 | 110 | client.update(); 111 | }; 112 | 113 | while(true) { 114 | update(); 115 | std::this_thread::sleep_for(std::chrono::milliseconds(1)); 116 | } 117 | 118 | return 0; 119 | } 120 | -------------------------------------------------------------------------------- /src/serialization.cpp: -------------------------------------------------------------------------------- 1 | #include "ggsock/serialization.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace { 8 | template 9 | inline void advance(size_t & offset, size_t amount, Operator & op) noexcept { 10 | offset += amount; 11 | op.nBytesProcessed += amount; 12 | } 13 | 14 | // type - fundamental 15 | 16 | template 17 | inline bool serialize_fundamental(const T & obj, GGSock::SerializationBuffer & buffer, size_t & offset, GGSock::Serialize & op) noexcept { 18 | static_assert(std::is_fundamental::value, "Fundamental type required"); 19 | 20 | auto osize = sizeof(obj); 21 | 22 | if (offset + osize > buffer.size()) { 23 | buffer.resize(offset + osize); 24 | } 25 | 26 | std::memcpy(buffer.data() + offset, &obj, osize); 27 | ::advance(offset, osize, op); 28 | 29 | return true; 30 | } 31 | 32 | template 33 | inline bool unserialize_fundamental(T & obj, const char * bufferData, size_t bufferSize, size_t & offset, GGSock::Unserialize & op) noexcept { 34 | static_assert(std::is_fundamental::value, "Fundamental type required"); 35 | 36 | auto osize = sizeof(obj); 37 | 38 | if (offset + osize > bufferSize) return false; 39 | 40 | std::memcpy(reinterpret_cast(&obj), bufferData + offset, osize); 41 | ::advance(offset, osize, op); 42 | 43 | return true; 44 | } 45 | 46 | // type - vector 47 | 48 | template 49 | inline bool serialize_vector(const T * objs, int32_t n, GGSock::SerializationBuffer & buffer, size_t & offset, GGSock::Serialize & op) noexcept { 50 | static_assert(std::is_fundamental::value, "Fundamental type required"); 51 | 52 | auto osize = sizeof(T)*n; 53 | 54 | if (offset + osize > buffer.size()) { 55 | buffer.resize(offset + osize); 56 | } 57 | 58 | std::memcpy(buffer.data() + offset, objs, osize); 59 | ::advance(offset, osize, op); 60 | 61 | return true; 62 | } 63 | 64 | template 65 | inline bool unserialize_vector(T * objs, int32_t n, const char * bufferData, size_t bufferSize, size_t & offset, GGSock::Unserialize & op) noexcept { 66 | static_assert(std::is_fundamental::value, "Fundamental type required"); 67 | 68 | auto osize = sizeof(T)*n; 69 | 70 | if (offset + osize > bufferSize) { 71 | return false; 72 | } 73 | 74 | std::copy( 75 | bufferData + offset, 76 | bufferData + offset + osize, reinterpret_cast(objs)); 77 | 78 | ::advance(offset, osize, op); 79 | 80 | return true; 81 | } 82 | } 83 | 84 | #define ADD_HELPER(T, type) \ 85 | template <> \ 86 | bool Serialize::operator()(const T & obj, SerializationBuffer & buffer, size_t & offset) { \ 87 | return ::serialize_##type(obj, buffer, offset, *this); \ 88 | } \ 89 | \ 90 | template <> \ 91 | bool Unserialize::operator()(T & obj, const char * bufferData, size_t bufferSize, size_t & offset) { \ 92 | return ::unserialize_##type(obj, bufferData, bufferSize, offset, *this); \ 93 | } 94 | 95 | namespace GGSock { 96 | 97 | // fundamental 98 | 99 | ADD_HELPER(bool, fundamental) 100 | ADD_HELPER(char, fundamental) 101 | 102 | ADD_HELPER(int8_t, fundamental) 103 | ADD_HELPER(int16_t, fundamental) 104 | ADD_HELPER(int32_t, fundamental) 105 | ADD_HELPER(int64_t, fundamental) 106 | 107 | ADD_HELPER(uint8_t, fundamental) 108 | ADD_HELPER(uint16_t, fundamental) 109 | ADD_HELPER(uint32_t, fundamental) 110 | ADD_HELPER(uint64_t, fundamental) 111 | 112 | ADD_HELPER(float, fundamental) 113 | ADD_HELPER(double, fundamental) 114 | 115 | // std::string 116 | 117 | template <> bool Serialize::operator()(const std::string & t, SerializationBuffer & buffer, size_t & offset) { 118 | bool res = true; 119 | 120 | int32_t n = (int32_t) t.size(); 121 | res &= operator()(n, buffer, offset); 122 | res &= ::serialize_vector(t.data(), n, buffer, offset, *this); 123 | 124 | return res; 125 | } 126 | 127 | template <> bool Unserialize::operator()(std::string & t, const char * bufferData, size_t bufferSize, size_t & offset) { 128 | bool res = true; 129 | 130 | int32_t n = 0; 131 | res &= operator()(n, bufferData, bufferSize, offset); 132 | 133 | t.resize(n); 134 | res &= ::unserialize_vector(&t[0], n, bufferData, bufferSize, offset, *this); 135 | 136 | return res; 137 | } 138 | 139 | 140 | } 141 | -------------------------------------------------------------------------------- /include/ggsock/serialization.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace GGSock { 11 | 12 | struct SerializationBuffer : public std::vector { using vector::vector; }; 13 | 14 | // if the following macro is defined, include the STL serialization overloads 15 | struct Serialize { 16 | template bool operator()(const T & obj, SerializationBuffer & buffer, size_t & offset); 17 | 18 | // helpers 19 | template bool operator()(const T & obj, SerializationBuffer & buffer); 20 | 21 | // STL 22 | template bool operator()(const std::vector & obj, SerializationBuffer & buffer, size_t & offset); 23 | template bool operator()(const std::shared_ptr & obj, SerializationBuffer & buffer, size_t & offset); 24 | template bool operator()(const std::pair & obj, SerializationBuffer & buffer, size_t & offset); 25 | template bool operator()(const std::map & obj, SerializationBuffer & buffer, size_t & offset); 26 | 27 | uint64_t nBytesProcessed = 0; 28 | }; 29 | 30 | struct Unserialize { 31 | template bool operator()(T & obj, const char * bufferData, size_t bufferSize, size_t & offset); 32 | 33 | // helpers 34 | template bool operator()(T & obj, const SerializationBuffer & buffer); 35 | template bool operator()(T & obj, const SerializationBuffer & buffer, size_t & offset); 36 | 37 | // STL 38 | template bool operator()(std::vector & obj, const char * bufferData, size_t bufferSize, size_t & offset); 39 | template bool operator()(std::shared_ptr & obj, const char * bufferData, size_t bufferSize, size_t & offset); 40 | template bool operator()(std::pair & obj, const char * bufferData, size_t bufferSize, size_t & offset); 41 | template bool operator()(std::map & obj, const char * bufferData, size_t bufferSize, size_t & offset); 42 | 43 | // bool deepCopy = true; 44 | 45 | uint64_t nBytesProcessed = 0; 46 | }; 47 | 48 | // 49 | // Serialize helpers 50 | // 51 | 52 | template bool Serialize::operator()(const T & obj, SerializationBuffer & buffer) { 53 | size_t offset = 0; 54 | return operator()(obj, buffer, offset); 55 | } 56 | 57 | // 58 | // Unserialize helpers 59 | // 60 | 61 | template bool Unserialize::operator()(T & obj, const SerializationBuffer & buffer) { 62 | size_t offset = 0; 63 | return operator()(obj, buffer, offset); 64 | } 65 | 66 | template bool Unserialize::operator()(T & obj, const SerializationBuffer & buffer, size_t & offset) { 67 | return operator()(obj, buffer.data(), buffer.size(), offset); 68 | } 69 | 70 | // 71 | // STL overloads 72 | // 73 | 74 | // std::vector 75 | 76 | template bool Serialize::operator()(const std::vector & t, SerializationBuffer & buffer, size_t & offset) { 77 | bool res = true; 78 | 79 | int32_t n = (int32_t) t.size(); 80 | res &= operator()(n, buffer, offset); 81 | for (const auto & p : t) { 82 | res &= operator()(p, buffer, offset); 83 | } 84 | 85 | return res; 86 | } 87 | 88 | template bool Unserialize::operator()(std::vector & t, const char * bufferData, size_t bufferSize, size_t & offset) { 89 | bool res = true; 90 | 91 | int32_t n = 0; 92 | res &= operator()(n, bufferData, bufferSize, offset); 93 | 94 | t.resize(n); 95 | for (int i = 0; i < n; ++i) { 96 | res &= operator()(t[i], bufferData, bufferSize, offset); 97 | } 98 | 99 | return true; 100 | } 101 | 102 | // std::shared_ptr 103 | 104 | template bool Serialize::operator()(const std::shared_ptr & t, SerializationBuffer & buffer, size_t & offset) { 105 | return t ? operator()(*t, buffer, offset) : false; 106 | } 107 | 108 | template bool Unserialize::operator()(std::shared_ptr & t, const char * bufferData, size_t bufferSize, size_t & offset) { 109 | bool res = true; 110 | 111 | auto tmp = std::make_shared::type>(); 112 | res &= operator()(*tmp, bufferData, bufferSize, offset); 113 | t = tmp; 114 | 115 | return true; 116 | } 117 | 118 | // std::pair 119 | 120 | template bool Serialize::operator()(const std::pair & t, SerializationBuffer & buffer, size_t & offset) { 121 | bool res = true; 122 | 123 | res &= operator()(t.first, buffer, offset); 124 | res &= operator()(t.second, buffer, offset); 125 | 126 | return res; 127 | } 128 | 129 | template bool Unserialize::operator()( std::pair & t, const char * bufferData, size_t bufferSize, size_t & offset) { 130 | bool res = true; 131 | 132 | res &= operator()(t.first, bufferData, bufferSize, offset); 133 | res &= operator()(t.second, bufferData, bufferSize, offset); 134 | 135 | return res; 136 | } 137 | 138 | // std::map 139 | 140 | template bool Serialize::operator()(const std::map & t, SerializationBuffer & buffer, size_t & offset) { 141 | bool res = true; 142 | 143 | int32_t n = (int32_t) t.size(); 144 | res &= operator()(n, buffer, offset); 145 | for (const auto & p : t) { 146 | res &= operator()(p.first, buffer, offset); 147 | res &= operator()(p.second, buffer, offset); 148 | } 149 | 150 | return res; 151 | } 152 | 153 | template bool Unserialize::operator()(std::map & t, const char * bufferData, size_t bufferSize, size_t & offset) { 154 | bool res = true; 155 | 156 | int32_t n = 0; 157 | res &= operator()(n, bufferData, bufferSize, offset); 158 | for (int i = 0; i < n; ++i) { 159 | Key k; 160 | Value v; 161 | res &= operator()(k, bufferData, bufferSize, offset); 162 | res &= operator()(v, bufferData, bufferSize, offset); 163 | t[k] = std::move(v); 164 | } 165 | 166 | return res; 167 | } 168 | 169 | } 170 | -------------------------------------------------------------------------------- /src/file-server.cpp: -------------------------------------------------------------------------------- 1 | #include "ggsock/file-server.h" 2 | 3 | #include "ggsock/communicator.h" 4 | 5 | #include "ggsock/serialization.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace GGSock { 13 | 14 | // 15 | // Serialization 16 | // 17 | 18 | // FileServer::FileInfo 19 | 20 | template <> 21 | bool Serialize::operator()(const FileServer::FileInfo & obj, SerializationBuffer & buffer, size_t & offset) { 22 | bool res = true; 23 | 24 | res = res && operator()(obj.uri, buffer, offset); 25 | res = res && operator()(obj.filesize, buffer, offset); 26 | res = res && operator()(obj.filename, buffer, offset); 27 | res = res && operator()(obj.nChunks, buffer, offset); 28 | 29 | return res; 30 | } 31 | 32 | template <> 33 | bool Unserialize::operator()(FileServer::FileInfo & obj, const char * bufferData, size_t bufferSize, size_t & offset) { 34 | bool res = true; 35 | 36 | res = res && operator()(obj.uri, bufferData, bufferSize, offset); 37 | res = res && operator()(obj.filesize, bufferData, bufferSize, offset); 38 | res = res && operator()(obj.filename, bufferData, bufferSize, offset); 39 | res = res && operator()(obj.nChunks, bufferData, bufferSize, offset); 40 | 41 | return res; 42 | } 43 | 44 | // FileServer::FileChunkRequestData 45 | 46 | template <> 47 | bool Serialize::operator()(const FileServer::FileChunkRequestData & obj, SerializationBuffer & buffer, size_t & offset) { 48 | bool res = true; 49 | 50 | res = res && operator()(obj.uri, buffer, offset); 51 | res = res && operator()(obj.chunkId, buffer, offset); 52 | res = res && operator()(obj.nChunksHave, buffer, offset); 53 | res = res && operator()(obj.nChunksExpected, buffer, offset); 54 | 55 | return res; 56 | } 57 | 58 | template <> 59 | bool Unserialize::operator()(FileServer::FileChunkRequestData & obj, const char * bufferData, size_t bufferSize, size_t & offset) { 60 | bool res = true; 61 | 62 | res = res && operator()(obj.uri, bufferData, bufferSize, offset); 63 | res = res && operator()(obj.chunkId, bufferData, bufferSize, offset); 64 | res = res && operator()(obj.nChunksHave, bufferData, bufferSize, offset); 65 | res = res && operator()(obj.nChunksExpected, bufferData, bufferSize, offset); 66 | 67 | return res; 68 | } 69 | 70 | // FileServer::FileChunkResponseData 71 | 72 | template <> 73 | bool Serialize::operator()(const FileServer::FileChunkResponseData & obj, SerializationBuffer & buffer, size_t & offset) { 74 | bool res = true; 75 | 76 | res = res && operator()(obj.uri, buffer, offset); 77 | res = res && operator()(obj.chunkId, buffer, offset); 78 | res = res && operator()(obj.data, buffer, offset); 79 | res = res && operator()(obj.pStart, buffer, offset); 80 | res = res && operator()(obj.pLen, buffer, offset); 81 | 82 | return res; 83 | } 84 | 85 | template <> 86 | bool Unserialize::operator()(FileServer::FileChunkResponseData & obj, const char * bufferData, size_t bufferSize, size_t & offset) { 87 | bool res = true; 88 | 89 | res = res && operator()(obj.uri, bufferData, bufferSize, offset); 90 | res = res && operator()(obj.chunkId, bufferData, bufferSize, offset); 91 | res = res && operator()(obj.data, bufferData, bufferSize, offset); 92 | res = res && operator()(obj.pStart, bufferData, bufferSize, offset); 93 | res = res && operator()(obj.pLen, bufferData, bufferSize, offset); 94 | 95 | return res; 96 | } 97 | 98 | // 99 | // FileServer 100 | // 101 | 102 | struct ClientData { 103 | ClientData() { 104 | communicator = std::make_shared(false); 105 | } 106 | 107 | FileServer::ClientInfo info; 108 | 109 | bool isUpdating = false; 110 | bool needsNewChunk = false; 111 | bool sendFileInfos = false; 112 | bool wasConnected = false; 113 | 114 | std::deque fileChunkRequests; 115 | 116 | std::shared_ptr communicator; 117 | }; 118 | 119 | struct FileServer::Impl { 120 | bool updateFileInfos() { 121 | fileInfos.clear(); 122 | 123 | for (int i = 0; i < (int) files.size(); ++i) { 124 | if (fileUsed[i] == false) { 125 | continue; 126 | } 127 | const auto & file = files[i]; 128 | fileInfos[i] = file.info; 129 | } 130 | 131 | return true; 132 | } 133 | 134 | bool updateClientInfos() { 135 | clientInfos.clear(); 136 | 137 | for (int i = 0; i < (int) clients.size(); ++i) { 138 | const auto & client = clients[i]; 139 | if (client.communicator->isConnected() == false) { 140 | continue; 141 | } 142 | 143 | clientInfos[i] = client.info; 144 | } 145 | 146 | return true; 147 | } 148 | 149 | bool exists(const FileInfo & info) const { 150 | for (int i = 0; i < (int) files.size(); ++i) { 151 | if (fileUsed[i] == false) { 152 | continue; 153 | } 154 | 155 | const auto & file = files[i]; 156 | if (info.uri == file.info.uri && 157 | info.filesize == file.info.filesize && 158 | info.filename == file.info.filename) { 159 | return true; 160 | } 161 | } 162 | 163 | return false; 164 | } 165 | 166 | Parameters parameters; 167 | 168 | std::atomic isRunning { false }; 169 | 170 | bool isListening = false; 171 | 172 | int currentFileUpdateId = 0; 173 | int currentClientUpdateId = 0; 174 | 175 | std::vector fileUsed; 176 | 177 | std::vector files; 178 | std::vector clients; 179 | 180 | bool changedFileInfos = false; 181 | TFileInfos fileInfos; 182 | 183 | bool changedClientInfos = false; 184 | TClientInfos clientInfos; 185 | 186 | std::mutex mutex; 187 | std::vector workers; 188 | }; 189 | 190 | FileServer::FileServer() : m_impl(new Impl()) { 191 | } 192 | 193 | FileServer::~FileServer() { 194 | m_impl->isRunning = false; 195 | 196 | for (auto & worker : m_impl->workers) { 197 | worker.join(); 198 | } 199 | } 200 | 201 | bool FileServer::init(const Parameters & parameters) { 202 | if (m_impl->isRunning) { 203 | return false; 204 | } 205 | 206 | m_impl->parameters = parameters; 207 | 208 | m_impl->fileUsed.resize(m_impl->parameters.nMaxFiles); 209 | m_impl->files.resize(m_impl->parameters.nMaxFiles); 210 | m_impl->clients.resize(m_impl->parameters.nMaxClients); 211 | 212 | for (int i = 0; i <(int) m_impl->clients.size(); ++i) { 213 | auto & client = m_impl->clients[i]; 214 | 215 | client.communicator->setErrorCallback([i](Communicator::TErrorCode code) { 216 | printf("Client %d disconnected, code = %d\n", i, code); 217 | }); 218 | 219 | client.communicator->setMessageCallback(MsgFileInfosRequest, [this, i](const char * , Communicator::TBufferSize ) { 220 | printf("Received message %d\n", MsgFileInfosRequest); 221 | { 222 | std::lock_guard lock(m_impl->mutex); 223 | m_impl->clients[i].sendFileInfos = true; 224 | } 225 | 226 | return 0; 227 | }); 228 | 229 | client.communicator->setMessageCallback(MsgFileChunkRequest, [this, i](const char * dataBuffer, Communicator::TBufferSize dataSize) { 230 | size_t offset = 0; 231 | FileChunkRequestData data; 232 | Unserialize()(data, dataBuffer, dataSize, offset); 233 | //printf("Received chunk request %d for file '%s'\n", data.chunkId, data.uri.c_str()); 234 | 235 | { 236 | std::lock_guard lock(m_impl->mutex); 237 | m_impl->clients[i].fileChunkRequests.emplace_back(std::move(data)); 238 | } 239 | 240 | return 0; 241 | }); 242 | } 243 | 244 | m_impl->isRunning = true; 245 | m_impl->workers.resize(m_impl->parameters.nWorkerThreads); 246 | for (auto & worker : m_impl->workers) { 247 | worker = std::thread([this]() { 248 | while (m_impl->isRunning) { 249 | update(); 250 | std::this_thread::sleep_for(std::chrono::milliseconds(1)); 251 | } 252 | }); 253 | } 254 | 255 | return true; 256 | } 257 | 258 | bool FileServer::startListening() { 259 | { 260 | std::lock_guard lock(m_impl->mutex); 261 | if (m_impl->isListening) { 262 | return false; 263 | } 264 | 265 | m_impl->isListening = true; 266 | } 267 | 268 | return true; 269 | } 270 | 271 | bool FileServer::stopListening() { 272 | { 273 | std::lock_guard lock(m_impl->mutex); 274 | if (m_impl->isListening == false) { 275 | return false; 276 | } 277 | 278 | m_impl->isListening = false; 279 | } 280 | 281 | return true; 282 | } 283 | 284 | bool FileServer::isListening() { 285 | std::lock_guard lock(m_impl->mutex); 286 | return m_impl->isListening; 287 | } 288 | 289 | bool FileServer::update() { 290 | bool doListen = false; 291 | bool doSendFileInfos = false; 292 | bool doSendFileChunk = false; 293 | 294 | FileChunkResponseData fileChunkToSend; 295 | 296 | TClientId updateId = -1; 297 | 298 | { 299 | std::lock_guard lock(m_impl->mutex); 300 | 301 | if (m_impl->changedFileInfos) { 302 | m_impl->updateFileInfos(); 303 | m_impl->changedFileInfos = false; 304 | } 305 | 306 | if (m_impl->changedClientInfos) { 307 | m_impl->updateClientInfos(); 308 | m_impl->changedClientInfos = false; 309 | } 310 | 311 | updateId = m_impl->currentClientUpdateId; 312 | 313 | if (++m_impl->currentClientUpdateId == (int) m_impl->clients.size()) { 314 | m_impl->currentClientUpdateId = 0; 315 | } 316 | 317 | auto & client = m_impl->clients.at(updateId); 318 | 319 | if (client.isUpdating) { 320 | return false; 321 | } 322 | 323 | doListen = m_impl->isListening; 324 | 325 | doSendFileInfos = client.sendFileInfos; 326 | client.sendFileInfos = false; 327 | 328 | if (client.fileChunkRequests.size() > 0) { 329 | const auto & req = client.fileChunkRequests.front(); 330 | 331 | // todo : data checks 332 | fileChunkToSend.uri = req.uri; 333 | fileChunkToSend.chunkId = req.chunkId; 334 | for (int i = 0; i < (int) m_impl->files.size(); ++i) { 335 | if (m_impl->fileUsed[i] == false) { 336 | continue; 337 | } 338 | 339 | const auto & file = m_impl->files[i]; 340 | if (file.info.uri != req.uri) { 341 | continue; 342 | } 343 | 344 | auto chunkSize = file.data.size()/file.info.nChunks; 345 | 346 | int64_t pStart = req.chunkId*chunkSize; 347 | int64_t pLen = req.chunkId == (file.info.nChunks - 1) ? file.data.size() - pStart : chunkSize; 348 | 349 | fileChunkToSend.pStart = pStart; 350 | fileChunkToSend.pLen = pLen; 351 | fileChunkToSend.data.assign(file.data.begin() + pStart, file.data.begin() + pStart + pLen); 352 | 353 | doSendFileChunk = true; 354 | 355 | break; 356 | } 357 | 358 | client.fileChunkRequests.pop_front(); 359 | } 360 | 361 | client.isUpdating = true; 362 | 363 | if (client.wasConnected && client.communicator->isConnected() == false) { 364 | printf("Client %d has disconnected\n", updateId); 365 | client.wasConnected = false; 366 | m_impl->updateClientInfos(); 367 | } 368 | 369 | if (client.wasConnected == false && client.communicator->isConnected()) { 370 | printf("Client %d has connected\n", updateId); 371 | client.wasConnected = true; 372 | client.info.address = client.communicator->getPeerAddress(); 373 | m_impl->updateClientInfos(); 374 | } 375 | } 376 | 377 | { 378 | auto & client = m_impl->clients.at(updateId); 379 | if (doListen) { 380 | client.communicator->listen(m_impl->parameters.listenPort, 0); 381 | } else { 382 | client.communicator->stopListening(); 383 | } 384 | if (doSendFileInfos) { 385 | SerializationBuffer buffer; 386 | Serialize()(m_impl->fileInfos, buffer); 387 | client.communicator->send(MsgFileInfosResponse, buffer.data(), (int) buffer.size()); 388 | client.communicator->update(); 389 | } 390 | if (doSendFileChunk) { 391 | SerializationBuffer buffer; 392 | Serialize()(fileChunkToSend, buffer); 393 | client.communicator->send(MsgFileChunkResponse, buffer.data(), (int) buffer.size()); 394 | client.communicator->update(); 395 | } 396 | client.communicator->update(); 397 | } 398 | 399 | { 400 | std::lock_guard lock(m_impl->mutex); 401 | m_impl->clients[updateId].isUpdating = false; 402 | } 403 | 404 | return true; 405 | } 406 | 407 | bool FileServer::addFile(FileData && data) { 408 | { 409 | std::lock_guard lock(m_impl->mutex); 410 | 411 | if (data.data.size() == 0) { 412 | return false; 413 | } 414 | 415 | if (m_impl->exists(data.info)) { 416 | return false; 417 | } 418 | 419 | // todo : handle max files reached 420 | if (m_impl->currentFileUpdateId == (int) m_impl->files.size()) { 421 | return false; 422 | } 423 | 424 | if (data.info.nChunks == 0) { 425 | data.info.nChunks = m_impl->parameters.nDefaultFileChunks; 426 | } 427 | 428 | if (data.info.nChunks > (int32_t) data.data.size()) { 429 | data.info.nChunks = (int32_t) data.data.size(); 430 | } 431 | 432 | data.info.filesize = data.data.size(); 433 | 434 | m_impl->fileUsed[m_impl->currentFileUpdateId] = true; 435 | m_impl->files[m_impl->currentFileUpdateId] = std::move(data); 436 | 437 | m_impl->currentFileUpdateId++; 438 | 439 | m_impl->changedFileInfos = true; 440 | } 441 | 442 | return true; 443 | } 444 | 445 | bool FileServer::clearAllFiles() { 446 | { 447 | std::lock_guard lock(m_impl->mutex); 448 | 449 | m_impl->currentFileUpdateId = 0; 450 | for (int i = 0; i < (int) m_impl->fileUsed.size(); ++i) { 451 | m_impl->fileUsed[i] = false; 452 | } 453 | 454 | for (auto & file : m_impl->files) { 455 | file = {}; 456 | } 457 | 458 | m_impl->changedFileInfos = true; 459 | } 460 | 461 | return true; 462 | } 463 | 464 | bool FileServer::clearFile(const TURI & uri) { 465 | { 466 | std::lock_guard lock(m_impl->mutex); 467 | 468 | for (int i = 0; i < (int) m_impl->fileUsed.size(); ++i) { 469 | if (m_impl->files[i].info.uri != uri) { 470 | continue; 471 | } 472 | m_impl->fileUsed[i] = false; 473 | m_impl->files[i] = {}; 474 | 475 | break; 476 | } 477 | 478 | m_impl->changedFileInfos = true; 479 | } 480 | 481 | return true; 482 | } 483 | 484 | FileServer::TFileInfos FileServer::getFileInfos() const { 485 | TFileInfos result; 486 | 487 | { 488 | std::lock_guard lock(m_impl->mutex); 489 | result = m_impl->fileInfos; 490 | } 491 | 492 | return result; 493 | } 494 | 495 | FileServer::TClientInfos FileServer::getClientInfos() const { 496 | TClientInfos result; 497 | 498 | { 499 | std::lock_guard lock(m_impl->mutex); 500 | result = m_impl->clientInfos; 501 | } 502 | 503 | return result; 504 | } 505 | 506 | const FileServer::Parameters & FileServer::getParameters() const { 507 | return m_impl->parameters; 508 | } 509 | 510 | const FileServer::FileData & FileServer::getFileData(const TURI & uri) const { 511 | { 512 | std::lock_guard lock(m_impl->mutex); 513 | 514 | for (int i = 0; i < (int) m_impl->files.size(); ++i) { 515 | if (m_impl->fileUsed[i] == false) { 516 | continue; 517 | } 518 | 519 | if (m_impl->files[i].info.uri != uri) { 520 | continue; 521 | } 522 | 523 | return m_impl->files[i]; 524 | } 525 | } 526 | 527 | static FileData empty; 528 | return empty; 529 | } 530 | 531 | } 532 | -------------------------------------------------------------------------------- /src/communicator.cpp: -------------------------------------------------------------------------------- 1 | #include "ggsock/communicator.h" 2 | 3 | #ifdef _WIN32 4 | #include 5 | #include 6 | #define close closesocket 7 | #pragma comment(lib, "Ws2_32.lib") 8 | #else 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #endif 19 | #include 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | namespace { 30 | using TSocketDescriptor = int32_t; 31 | 32 | void closeAndReset(TSocketDescriptor & sock) { 33 | if (sock != -1) { 34 | #ifndef __EMSCRIPTEN__ 35 | shutdown(sock, 0); 36 | #endif 37 | close(sock); 38 | sock = -1; 39 | } 40 | } 41 | 42 | void setNonBlocking(TSocketDescriptor & sock) { 43 | #ifdef _WIN32 44 | unsigned long nonblocking = 1; 45 | ioctlsocket(sock, FIONBIO, &nonblocking); 46 | #else 47 | int flags; 48 | flags = fcntl(sock, F_GETFL, 0); 49 | fcntl(sock, F_SETFL, flags | O_NONBLOCK); 50 | 51 | int flag = 1; 52 | int result = setsockopt(sock, /* socket affected */ 53 | IPPROTO_TCP, /* set option at TCP level */ 54 | TCP_NODELAY, /* name of option */ 55 | (char *) &flag, /* the cast is historical cruft */ 56 | sizeof(int)); /* length of option value */ 57 | if (result != 0) { 58 | fprintf(stderr, "Failed to set non-blocking socket\n"); 59 | } 60 | #endif 61 | } 62 | 63 | bool e_wouldBlock() { 64 | #ifdef _WIN32 65 | return errno == EWOULDBLOCK || WSAGetLastError() == WSAEWOULDBLOCK; 66 | #else 67 | return errno == EWOULDBLOCK; 68 | #endif 69 | } 70 | 71 | bool e_isConnected() { 72 | #ifdef _WIN32 73 | return errno == EISCONN || WSAGetLastError() == WSAEISCONN; 74 | #else 75 | return errno == EISCONN; 76 | #endif 77 | } 78 | 79 | bool e_inProgress() { 80 | #ifdef _WIN32 81 | return errno == EINPROGRESS || errno == EALREADY || WSAGetLastError() == WSAEINPROGRESS || WSAGetLastError() == WSAEALREADY; 82 | #else 83 | return errno == EINPROGRESS || errno == EALREADY; 84 | #endif 85 | } 86 | 87 | struct MessageHeader { 88 | ::GGSock::Communicator::TBufferSize size; 89 | ::GGSock::Communicator::TMessageType type; 90 | 91 | static constexpr size_t getSizeInBytes() { 92 | return 93 | sizeof(::GGSock::Communicator::TBufferSize) + 94 | sizeof(::GGSock::Communicator::TMessageType); 95 | } 96 | }; 97 | } 98 | 99 | namespace GGSock { 100 | struct Communicator::Data { 101 | Data(bool startOwnWorker) { 102 | // todo : maybe move this to a static method 103 | static bool isFirst = true; 104 | if (isFirst) { 105 | #ifdef _WIN32 106 | // Initialize Winsock 107 | WSADATA wsaData; 108 | int iResult = WSAStartup(MAKEWORD(2,2), &wsaData); 109 | if (iResult != 0) { 110 | printf("WSAStartup failed with error: %d\n", iResult); 111 | return; 112 | } 113 | #endif 114 | 115 | #ifndef _WIN32 116 | // this is needed to avoid program crash upon sending data to disconnected clients 117 | signal(SIGPIPE, SIG_IGN); 118 | #endif 119 | 120 | isFirst = false; 121 | } 122 | 123 | std::lock_guard lock(mutex); 124 | 125 | bufferDataRecv.resize(256*1024, 0); 126 | 127 | if (startOwnWorker) { 128 | isRunning = true; 129 | worker = std::thread([this]() { 130 | while (isRunning) { 131 | update(); 132 | std::this_thread::sleep_for(std::chrono::milliseconds(1)); 133 | } 134 | }); 135 | } 136 | } 137 | 138 | ~Data() { 139 | { 140 | std::lock_guard lock(mutex); 141 | isRunning = false; 142 | isConnected = false; 143 | isConnecting = false; 144 | isListening = false; 145 | ::closeAndReset(sdpeer); 146 | ::closeAndReset(sd); 147 | } 148 | 149 | if (worker.joinable()) { 150 | worker.join(); 151 | } 152 | } 153 | 154 | bool update() { 155 | std::lock_guard lock(mutex); 156 | if (isServer && isListening) { 157 | doListen(); 158 | } else if (isServer && isListening == false && isConnected) { 159 | doRead(); 160 | } else if (isServer == false && isConnecting) { 161 | doConnect(); 162 | } else if (isServer == false && isConnecting == false && isConnected) { 163 | doRead(); 164 | } 165 | { 166 | std::lock_guard lock(mutexSend); 167 | if (isConnected && (rbHead != rbEnd)) { 168 | doSend(); 169 | } 170 | if (isConnected == false) { 171 | rbHead = rbEnd; 172 | } 173 | } 174 | 175 | return true; 176 | } 177 | 178 | bool doListen() { 179 | FD_ZERO(&master_set); 180 | max_sd = sd; 181 | FD_SET(sd, &master_set); 182 | 183 | memcpy(&working_set, &master_set, sizeof(master_set)); 184 | 185 | timeout.tv_sec = timeoutListen_ms/1000; 186 | timeout.tv_usec = (timeoutListen_ms%1000)*1000; 187 | 188 | int rc = select(max_sd + 1, &working_set, NULL, NULL, &timeout); 189 | if (rc < 0) { 190 | return false; 191 | } 192 | 193 | if (rc == 0) { 194 | return false; 195 | } 196 | 197 | int ndesc = rc; 198 | if (ndesc != 1) { 199 | printf("WARNING: ndesc = %d\n", ndesc); 200 | } 201 | 202 | if (FD_ISSET(sd, &working_set)) { 203 | --ndesc; 204 | 205 | do { 206 | sdpeer = accept(sd, NULL, NULL); 207 | if (sdpeer < 0) { 208 | if (e_wouldBlock() == false) { 209 | perror(" accept() failed"); 210 | } 211 | return false; 212 | } 213 | 214 | socklen_t len; 215 | len = sizeof(peeraddr); 216 | getpeername(sdpeer, (struct sockaddr*)&peeraddr, &len); 217 | 218 | printf(" New incoming connection - %d, %d, ip = %s\n", sd, sdpeer, inet_ntoa(peeraddr.sin_addr)); 219 | 220 | ::setNonBlocking(sdpeer); 221 | 222 | isListening = false; 223 | isConnected = true; 224 | 225 | // stop listening for connections 226 | ::closeAndReset(sd); 227 | 228 | break; 229 | } while (sdpeer != -1); 230 | } else { 231 | return false; 232 | } 233 | 234 | return true; 235 | } 236 | 237 | bool doConnect() { 238 | auto tStart = std::chrono::high_resolution_clock::now(); 239 | 240 | while (isConnecting) { 241 | auto rc = ::connect(sd, (struct sockaddr *) &addr, sizeof(addr)); 242 | if (rc < 0 && e_isConnected() == false) { 243 | if (e_inProgress() == false && e_wouldBlock() == false) { 244 | ::closeAndReset(sd); 245 | sd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 246 | 247 | int enable = 1; 248 | if (setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, (char *)&enable, sizeof(int)) < 0) { 249 | fprintf(stderr, "setsockopt(SO_REUSEADDR) failed"); 250 | } 251 | 252 | #ifndef _WIN32 253 | if (setsockopt(sd, SOL_SOCKET, SO_REUSEPORT, (char *)&enable, sizeof(int)) < 0) { 254 | fprintf(stderr, "setsockopt(SO_REUSEPORT) failed"); 255 | } 256 | #endif 257 | 258 | //{ 259 | // linger lin; 260 | // lin.l_onoff = 0; 261 | // lin.l_linger = 0; 262 | // if (setsockopt(sd, SOL_SOCKET, SO_LINGER, (const char *)&lin, sizeof(lin)) < 0) { 263 | // fprintf(stderr, "setsockopt(SO_LINGER) failed"); 264 | // } 265 | //} 266 | 267 | ::setNonBlocking(sd); 268 | } 269 | if (timeoutConnect_ms > 0) { 270 | std::this_thread::sleep_for(std::chrono::milliseconds(1)); 271 | 272 | auto tEnd = std::chrono::high_resolution_clock::now(); 273 | if (std::chrono::duration_cast(tEnd - tStart).count() >= timeoutConnect_ms) { 274 | ::closeAndReset(sd); 275 | isConnecting = false; 276 | } 277 | continue; 278 | } 279 | 280 | return false; 281 | } 282 | 283 | sdpeer = sd; 284 | 285 | printf("Connected successfully, sd = %d\n", sd); 286 | 287 | isConnecting = false; 288 | isConnected = true; 289 | 290 | return true; 291 | } 292 | 293 | return false; 294 | } 295 | 296 | void doRead() { 297 | int rc = (int) recv(sdpeer, bufferHeaderRecv.data(), bufferHeaderRecv.size(), 0); 298 | if (rc < 0) { 299 | if (e_wouldBlock() == false) { 300 | isConnected = false; 301 | ::closeAndReset(sdpeer); 302 | ::closeAndReset(sd); 303 | 304 | TErrorCode errorCode = errno; 305 | if (errorCallback) { 306 | errorCallback(errorCode); 307 | } 308 | } 309 | return; 310 | } 311 | 312 | if (rc == 0) { 313 | isConnected = false; 314 | isListening = false; 315 | ::closeAndReset(sdpeer); 316 | ::closeAndReset(sd); 317 | 318 | TErrorCode errorCode = errno; 319 | if (errorCallback) { 320 | errorCallback(errorCode); 321 | } 322 | 323 | return; 324 | } 325 | 326 | //printf(" %d bytes received, %d %d\n", rc, 327 | // (int)(*reinterpret_cast(bufferHeaderRecv.data())), 328 | // (int)(*reinterpret_cast(bufferHeaderRecv.data() + sizeof(int32_t))) 329 | // ); 330 | 331 | if (rc == (int) bufferHeaderRecv.size()) { 332 | TBufferSize size = 0; 333 | TMessageType type = 0; 334 | memcpy(&size, bufferHeaderRecv.data(), sizeof(size)); 335 | memcpy(&type, bufferHeaderRecv.data() + sizeof(size), sizeof(type)); 336 | if (size == ::MessageHeader::getSizeInBytes()) { 337 | if (const auto & cb = messageCallback[type]) { 338 | cb(bufferDataRecv.data(), 0); 339 | } 340 | } else if (size > ::MessageHeader::getSizeInBytes()) { 341 | lastMessageType = type; 342 | leftToReceive = size - ::MessageHeader::getSizeInBytes(); 343 | offsetReceive = 0; 344 | if (leftToReceive > (int) bufferDataRecv.size()) { 345 | printf("Extend receive buffer to %d bytes\n", (int) bufferDataRecv.size()); 346 | bufferDataRecv.resize(leftToReceive); 347 | } 348 | 349 | while (leftToReceive > 0) { 350 | //printf("left = %d\n", (int) leftToReceive); 351 | size_t curSize = (std::min)(65536, (int) leftToReceive); 352 | 353 | int rc = (int) recv(sdpeer, bufferDataRecv.data() + offsetReceive, curSize, 0); 354 | if (rc < 0) { 355 | if (e_wouldBlock() == false) { 356 | isConnected = false; 357 | ::closeAndReset(sdpeer); 358 | ::closeAndReset(sd); 359 | 360 | if (errorCallback) { 361 | TErrorCode errorCode = errno; 362 | errorCallback(errorCode); 363 | } 364 | 365 | break; 366 | } 367 | 368 | continue; 369 | } 370 | 371 | if (rc == 0) { 372 | isConnected = false; 373 | isListening = false; 374 | ::closeAndReset(sdpeer); 375 | ::closeAndReset(sd); 376 | 377 | TErrorCode errorCode = errno; 378 | if (errorCallback) errorCallback(errorCode); 379 | 380 | return; 381 | } 382 | 383 | leftToReceive -= rc; 384 | offsetReceive += rc; 385 | } 386 | 387 | if (const auto & cb = messageCallback[type]) { 388 | cb(bufferDataRecv.data(), size - ::MessageHeader::getSizeInBytes()); 389 | } 390 | } else { 391 | // error 392 | } 393 | } 394 | } 395 | 396 | void doSend() { 397 | const auto & curMessage = ringBufferSend[rbHead]; 398 | TBufferSize size = (TBufferSize) curMessage.size(); 399 | 400 | int offset = 0; 401 | while (size > 0) { 402 | int rc = (int) ::send(sdpeer, curMessage.data() + offset, size, 0); 403 | if (rc < 0) { 404 | if (e_wouldBlock() == false) { 405 | isConnected = false; 406 | ::closeAndReset(sdpeer); 407 | ::closeAndReset(sd); 408 | 409 | if (errorCallback) { 410 | TErrorCode errorCode = errno; 411 | errorCallback(errorCode); 412 | } 413 | 414 | break; 415 | } 416 | continue; 417 | } 418 | size -= rc; 419 | offset += rc; 420 | } 421 | 422 | if (++rbHead >= (int) ringBufferSend.size()) { 423 | rbHead = 0; 424 | } 425 | } 426 | 427 | bool addMessageToSend(std::string && msg) { 428 | ringBufferSend[rbEnd] = std::move(msg); 429 | 430 | if (++rbEnd >= (int) ringBufferSend.size()) { 431 | rbEnd = 0; 432 | } 433 | 434 | return rbEnd != rbHead; 435 | } 436 | 437 | bool isServer = true; 438 | bool isListening = false; 439 | bool isConnected = false; 440 | bool isConnecting = false; 441 | bool isRunning = false; 442 | 443 | int32_t timeoutListen_ms = 0; 444 | int32_t timeoutConnect_ms = 0; 445 | 446 | TSocketDescriptor sd = -1; 447 | TSocketDescriptor sdpeer = -1; 448 | TSocketDescriptor max_sd = -1; 449 | 450 | struct timeval timeout; 451 | struct sockaddr_in addr; 452 | struct sockaddr_in peeraddr; 453 | 454 | fd_set master_set; 455 | fd_set working_set; 456 | 457 | int32_t offsetReceive = 0; 458 | int32_t leftToReceive = 0; 459 | TMessageType lastMessageType = -1; 460 | 461 | std::int32_t rbHead = 0; 462 | std::int32_t rbEnd = 0; 463 | std::array ringBufferSend; 464 | 465 | std::vector bufferDataRecv; 466 | 467 | std::array bufferHeaderRecv; 468 | std::array bufferHeaderSend; 469 | 470 | mutable std::mutex mutex; 471 | mutable std::mutex mutexSend; 472 | std::thread worker; 473 | 474 | CBError errorCallback = nullptr; 475 | std::map messageCallback; 476 | }; 477 | 478 | Communicator::Communicator(bool startOwnWorker) : data_(new Data(startOwnWorker)) {} 479 | Communicator::~Communicator() {} 480 | 481 | bool Communicator::update() { 482 | return getData().update(); 483 | } 484 | 485 | bool Communicator::listen(TPort port, int32_t timeout_ms, int32_t maxConnections) { 486 | auto & data = getData(); 487 | 488 | std::lock_guard lock(data.mutex); 489 | 490 | if (data.isConnected) return false; 491 | if (data.isListening) return false; 492 | 493 | ::closeAndReset(data.sd); 494 | 495 | data.sd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 496 | if (data.sd < 0) { 497 | ::closeAndReset(data.sd); 498 | fprintf(stderr, "Error creating socket (%d %s)\n", errno, strerror(errno)); 499 | return false; 500 | } 501 | 502 | int enable = 1; 503 | if (setsockopt(data.sd, SOL_SOCKET, SO_REUSEADDR, (char *)&enable, sizeof(int)) < 0) { 504 | fprintf(stderr, "setsockopt(SO_REUSEADDR) failed"); 505 | } 506 | 507 | #ifndef _WIN32 508 | if (setsockopt(data.sd, SOL_SOCKET, SO_REUSEPORT, (char *)&enable, sizeof(int)) < 0) { 509 | fprintf(stderr, "setsockopt(SO_REUSEPORT) failed"); 510 | } 511 | #endif 512 | 513 | //{ 514 | // linger lin; 515 | // lin.l_onoff = 0; 516 | // lin.l_linger = 0; 517 | // if (setsockopt(data.sd, SOL_SOCKET, SO_LINGER, (const char *)&lin, sizeof(lin)) < 0) { 518 | // fprintf(stderr, "setsockopt(SO_LINGER) failed"); 519 | // } 520 | //} 521 | 522 | auto & addr = data.addr; 523 | 524 | memset(&addr, 0, sizeof(addr)); 525 | addr.sin_family = AF_INET; 526 | addr.sin_addr.s_addr = INADDR_ANY; 527 | addr.sin_port = htons(port); 528 | 529 | int res = bind(data.sd, (struct sockaddr *)&addr, sizeof(addr)); 530 | if (res == -1) { 531 | perror("Bind failed"); 532 | fprintf(stderr, "Error: bind failed"); 533 | return false; 534 | } 535 | 536 | res = ::listen(data.sd, maxConnections); 537 | if (res == -1) { 538 | fprintf(stderr, "Error: listen failed"); 539 | return false; 540 | } 541 | 542 | ::setNonBlocking(data.sd); 543 | 544 | data.isServer = true; 545 | data.isListening = true; 546 | 547 | data.timeoutListen_ms = (std::max)(0, timeout_ms); 548 | if (timeout_ms > 0) { 549 | bool success = data.doListen(); 550 | 551 | data.isListening = false; 552 | return success; 553 | } else if (timeout_ms < 0) { 554 | data.timeoutListen_ms = 1; 555 | while (data.isListening) { 556 | bool success = data.doListen(); 557 | if (success) { 558 | data.isListening = false; 559 | return true; 560 | } 561 | } 562 | return false; 563 | } 564 | 565 | return true; 566 | } 567 | 568 | bool Communicator::connect(const TAddress & address, TPort port, int32_t timeout_ms) { 569 | auto & data = getData(); 570 | 571 | std::lock_guard lock(data.mutex); 572 | 573 | if (data.isConnected) return false; 574 | if (data.isConnecting) return false; 575 | 576 | struct hostent *server; 577 | 578 | data.sd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 579 | if (data.sd < 0) { 580 | ::closeAndReset(data.sd); 581 | fprintf(stderr, "Error creating socket (%d %s)\n", errno, strerror(errno)); 582 | return false; 583 | } 584 | 585 | int enable = 1; 586 | if (setsockopt(data.sd, SOL_SOCKET, SO_REUSEADDR, (char *)&enable, sizeof(int)) < 0) { 587 | fprintf(stderr, "setsockopt(SO_REUSEADDR) failed"); 588 | } 589 | 590 | #ifndef _WIN32 591 | if (setsockopt(data.sd, SOL_SOCKET, SO_REUSEPORT, (char *)&enable, sizeof(int)) < 0) { 592 | fprintf(stderr, "setsockopt(SO_REUSEPORT) failed"); 593 | } 594 | #endif 595 | 596 | //{ 597 | // linger lin; 598 | // lin.l_onoff = 0; 599 | // lin.l_linger = 0; 600 | // if (setsockopt(data.sd, SOL_SOCKET, SO_LINGER, (const char *)&lin, sizeof(lin)) < 0) { 601 | // fprintf(stderr, "setsockopt(SO_LINGER) failed"); 602 | // } 603 | //} 604 | 605 | ::setNonBlocking(data.sd); 606 | 607 | server = gethostbyname(address.c_str()); 608 | if (server == NULL) { 609 | fprintf(stderr,"ERROR, no such host\n"); 610 | return false; 611 | } 612 | 613 | auto & addr = data.addr; 614 | memset((char *) &addr, '\0', sizeof(addr)); 615 | 616 | addr.sin_family = AF_INET; 617 | addr.sin_port = htons(port); 618 | addr.sin_addr.s_addr = inet_addr(address.c_str()); 619 | 620 | data.isServer = false; 621 | data.isConnecting = true; 622 | 623 | data.timeoutConnect_ms = (std::max)(0, timeout_ms); 624 | if (timeout_ms > 0) { 625 | bool res = data.doConnect(); 626 | 627 | data.isConnecting = false; 628 | return res; 629 | } else if (timeout_ms < 0) { 630 | data.timeoutConnect_ms = 1; 631 | while (data.isConnecting) { 632 | bool success = data.doConnect(); 633 | if (success) { 634 | data.isConnecting = false; 635 | return true; 636 | } 637 | } 638 | return false; 639 | } 640 | 641 | return true; 642 | } 643 | 644 | bool Communicator::disconnect() { 645 | auto & data = getData(); 646 | 647 | std::lock_guard lock(data.mutex); 648 | 649 | data.isListening = false; 650 | data.isConnecting = false; 651 | data.isConnected = false; 652 | 653 | ::closeAndReset(data.sdpeer); 654 | ::closeAndReset(data.sd); 655 | 656 | return true; 657 | } 658 | 659 | bool Communicator::stopListening() { 660 | auto & data = getData(); 661 | 662 | std::lock_guard lock(data.mutex); 663 | 664 | if (data.isListening) { 665 | data.isListening = false; 666 | 667 | ::closeAndReset(data.sdpeer); 668 | ::closeAndReset(data.sd); 669 | 670 | return true; 671 | } 672 | 673 | return false; 674 | } 675 | 676 | bool Communicator::isConnected() const { 677 | auto & data = getData(); 678 | 679 | std::lock_guard lock(data.mutex); 680 | 681 | return data.isConnected; 682 | } 683 | 684 | bool Communicator::isConnecting() const { 685 | auto & data = getData(); 686 | 687 | std::lock_guard lock(data.mutex); 688 | 689 | return data.isConnecting; 690 | } 691 | 692 | TAddress Communicator::getPeerAddress() const { 693 | auto & data = getData(); 694 | 695 | std::lock_guard lock(data.mutex); 696 | 697 | return inet_ntoa(data.peeraddr.sin_addr); 698 | } 699 | 700 | bool Communicator::send(TMessageType type) { 701 | auto & data = getData(); 702 | 703 | std::lock_guard lock(data.mutexSend); 704 | 705 | if (data.isConnected == false) return false; 706 | 707 | { 708 | std::string msg; 709 | TBufferSize size = ::MessageHeader::getSizeInBytes(); 710 | 711 | msg.reserve(size); 712 | 713 | msg.append(reinterpret_cast(&size), reinterpret_cast(&size)+sizeof(size)); 714 | msg.append(reinterpret_cast(&type), reinterpret_cast(&type)+sizeof(type)); 715 | 716 | if (data.addMessageToSend(std::move(msg)) == false) { 717 | // error, send buffer is full 718 | return false; 719 | } 720 | } 721 | 722 | return true; 723 | } 724 | 725 | bool Communicator::send(TMessageType type, const char * dataBuffer, TBufferSize dataSize) { 726 | auto & data = getData(); 727 | 728 | std::lock_guard lock(data.mutexSend); 729 | 730 | if (data.isConnected == false) return false; 731 | 732 | { 733 | std::string msg; 734 | TBufferSize size = ::MessageHeader::getSizeInBytes() + dataSize; 735 | 736 | msg.reserve(size); 737 | 738 | msg.append(reinterpret_cast(&size), reinterpret_cast(&size)+sizeof(size)); 739 | msg.append(reinterpret_cast(&type), reinterpret_cast(&type)+sizeof(type)); 740 | msg.append(dataBuffer, dataBuffer + dataSize); 741 | 742 | if (data.addMessageToSend(std::move(msg)) == false) { 743 | // error, send buffer is full 744 | return false; 745 | } 746 | } 747 | 748 | return true; 749 | } 750 | 751 | bool Communicator::setErrorCallback(CBError && callback) { 752 | auto & data = getData(); 753 | 754 | std::lock_guard lock(data.mutex); 755 | 756 | data.errorCallback = std::move(callback); 757 | 758 | return true; 759 | } 760 | 761 | bool Communicator::setMessageCallback(TMessageType type, CBMessage && callback) { 762 | auto & data = getData(); 763 | 764 | std::lock_guard lock(data.mutex); 765 | 766 | data.messageCallback[type] = std::move(callback); 767 | 768 | return true; 769 | } 770 | 771 | bool Communicator::removeErrorCallback() { 772 | auto & data = getData(); 773 | 774 | std::lock_guard lock(data.mutex); 775 | 776 | if (data.errorCallback) { 777 | data.errorCallback = nullptr; 778 | return true; 779 | } 780 | 781 | return false; 782 | } 783 | 784 | bool Communicator::removeMessageCallback(TMessageType type) { 785 | auto & data = getData(); 786 | 787 | std::lock_guard lock(data.mutex); 788 | 789 | if (data.messageCallback[type]) { 790 | data.messageCallback[type] = nullptr; 791 | return true; 792 | } 793 | 794 | return false; 795 | } 796 | 797 | TAddress Communicator::getLocalAddress() { 798 | int sock = socket(PF_INET, SOCK_DGRAM, 0); 799 | sockaddr_in loopback; 800 | 801 | if (sock == -1) { 802 | return ""; 803 | } 804 | 805 | std::memset(&loopback, 0, sizeof(loopback)); 806 | loopback.sin_family = AF_INET; 807 | loopback.sin_addr.s_addr = INADDR_LOOPBACK; // using loopback ip address 808 | loopback.sin_port = htons(9); // using debug port 809 | 810 | if (::connect(sock, reinterpret_cast(&loopback), sizeof(loopback)) == -1) { 811 | close(sock); 812 | return ""; 813 | } 814 | 815 | socklen_t addrlen = sizeof(loopback); 816 | if (getsockname(sock, reinterpret_cast(&loopback), &addrlen) == -1) { 817 | close(sock); 818 | return ""; 819 | } 820 | 821 | close(sock); 822 | 823 | char buf[INET_ADDRSTRLEN]; 824 | if (inet_ntop(AF_INET, &loopback.sin_addr, buf, INET_ADDRSTRLEN) == 0x0) { 825 | return ""; 826 | } 827 | 828 | return buf; 829 | } 830 | } 831 | --------------------------------------------------------------------------------