├── .gitignore ├── CMakeLists.txt ├── LICENSE.txt ├── MempoolHeap ├── CMakeLists.txt ├── examples │ ├── CMakeLists.txt │ └── ExampleMempoolHeap.cpp └── src │ ├── CMakeLists.txt │ └── MemPoolHeap.h ├── MempoolIPC ├── CMakeLists.txt ├── examples │ ├── CMakeLists.txt │ ├── Example_MemPoolArrayIPC.cpp │ ├── Example_MemPoolGrowerIPC.cpp │ └── Example_MemPoolIPC.cpp └── src │ ├── CMakeLists.txt │ ├── MemPoolIPC.h │ ├── RouterIPC.h │ └── UtilsIPC.h ├── Networking ├── CMakeLists.txt ├── examples │ ├── CMakeLists.txt │ └── Example_Networking.cpp └── src │ ├── CMakeLists.txt │ ├── MultiClientReceiver.hpp │ ├── MultiClientSender.hpp │ ├── UtilsASIO.hpp │ ├── args.hpp │ ├── client.hpp │ ├── http_session.cpp │ ├── http_session.hpp │ ├── listener.cpp │ ├── listener.hpp │ ├── net.hpp │ ├── rate_limiter.cpp │ ├── rate_limiter.hpp │ ├── shared_state.cpp │ ├── shared_state.hpp │ ├── websocket_session.cpp │ └── websocket_session.hpp ├── README.md └── rights_release_MC-04228.pdf /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.4) 2 | project(MemPoolProject CXX) 3 | 4 | add_subdirectory (MempoolHeap) 5 | add_subdirectory (MempoolIPC) 6 | add_subdirectory (Networking) 7 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Keith Patrick Rausch 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 | -------------------------------------------------------------------------------- /MempoolHeap/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | option(BUILD_LIB_MEMPOOLHEAP "Build the mempool-heap library" ON) 3 | option(BUILD_LIB_MEMPOOLHEAP_EXAMPLES "Build the mempool-heap library examples" ON) 4 | 5 | 6 | if (BUILD_LIB_MEMPOOLHEAP) 7 | add_subdirectory(src) 8 | endif() 9 | 10 | if (BUILD_LIB_MEMPOOLHEAP_EXAMPLES) 11 | add_subdirectory(examples) 12 | endif() -------------------------------------------------------------------------------- /MempoolHeap/examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | set(CMAKE_CXX_STANDARD 11) 3 | 4 | # set the main() files for the executables we want to build. 5 | add_executable(UnitTest_MemPoolHeap ExampleMempoolHeap.cpp) 6 | 7 | # attach the library to the exectuables, and any other dependenciees we need 8 | target_link_libraries(UnitTest_MemPoolHeap Lib_MemPoolHeap ) # pthread atomic rt) 9 | 10 | target_compile_options(UnitTest_MemPoolHeap PRIVATE $<$: -Werror -Wall -Wextra> ) -------------------------------------------------------------------------------- /MempoolHeap/examples/ExampleMempoolHeap.cpp: -------------------------------------------------------------------------------- 1 | // Keith Rausch 2 | // 3 | // This example shows how to use an allocator-based mempool to get shared_ptr's without 4 | // repeatedly newing/deleting the memory for the shared_ptr's control block. 5 | // An array version is also available and demoed here. 6 | // 7 | 8 | #include "MemPoolHeap.h" 9 | 10 | #include 11 | #include 12 | #include // mutex, etc 13 | 14 | using namespace IPC; // dont crucify me 15 | 16 | struct MyClass 17 | { 18 | // just some random variables 19 | short x; 20 | char someChars[1]; 21 | 22 | MyClass() : x(0) { std::cout << "MyClass()" << std::endl; } 23 | MyClass(int x_in) : x(x_in) { std::cout << "MyClass(" << x << ")\n"; } 24 | ~MyClass() { std::cout << "~MyClass(" << x << ")\n"; } 25 | }; 26 | 27 | void test_static_pool() 28 | { 29 | // 30 | // backend allocators are optional, will default to std::allocator. 31 | // PassthroughAllocator is an option that prints its allocations/ deallocations for you to see 32 | // 33 | 34 | size_t poolSize = 5; 35 | size_t pixelsPerImage = 3; 36 | SharedPointerAllocator myAllocator(poolSize); 37 | SharedPointerArrayAllocator myArrayAllocator(poolSize, pixelsPerImage); 38 | 39 | std::cout << " ---------- ALL MEMORY SHOULD BE ALLOCATED BY THIS POINT ---------- \n"; 40 | 41 | std::cout << " ---------- make some shared pointers to MyClass objects ---------- \n"; 42 | 43 | { 44 | auto pA1 = myAllocator.allocate_shared(1); 45 | auto pA2 = pA1; 46 | pA1 = nullptr; 47 | 48 | auto pB1 = myAllocator.allocate_shared(2); 49 | auto pB2 = pB1; 50 | pB1 = nullptr; 51 | 52 | auto pC1 = myAllocator.allocate_shared(3); 53 | auto pC2 = pC1; 54 | pC1 = nullptr; 55 | 56 | std::cout << " ---------- returning elements back to their pools ---------- \n"; 57 | pA2 = nullptr; 58 | pB2 = nullptr; 59 | pC2 = nullptr; 60 | 61 | } 62 | 63 | // print some statistics 64 | size_t nOutstanding; 65 | size_t nAvailable; 66 | size_t historic_min_available; 67 | myAllocator.statistics(nOutstanding, nAvailable, historic_min_available); 68 | std::printf("\nshared_ptr pool has %zu outstanding, %zu available, %zu is historic-low in availability\n\n", nOutstanding, nAvailable, historic_min_available); 69 | 70 | std::cout << " ---------- make some shared pointers to T[] objects- THESE OBJECTS ARE NOT ALLOCATED, BUT THEY ARE CONSTRUCTED IF NON-ARITHMETIC ---------- \n"; 71 | 72 | // allocate a bunch of different arrays 73 | // fill the arrays with data 74 | std::vector> pointerVec; 75 | for (size_t i = 0; i < poolSize; ++i) 76 | { 77 | pointerVec.push_back(myArrayAllocator.allocate_raw(i)); 78 | } 79 | 80 | // print the data in those arrays 81 | size_t frameIndex = 0; 82 | for (auto &rawPtr : pointerVec) 83 | { 84 | for (size_t pixelIndex = 0; pixelIndex < pixelsPerImage; ++pixelIndex) 85 | { 86 | std::printf("frame[%zu], pixel[%zu] = ", frameIndex, pixelIndex); 87 | auto &obj = *(*rawPtr + pixelIndex); 88 | std::cout << obj.x << std::endl; 89 | } 90 | frameIndex++; 91 | } 92 | 93 | // print some 94 | myArrayAllocator.Statistics(nOutstanding, nAvailable, historic_min_available); 95 | std::printf("\narray has %zu outstanding, %zu available, %zu is historic low in availability\n\n", nOutstanding, nAvailable, historic_min_available); 96 | } 97 | 98 | 99 | 100 | 101 | int main(int, char *[]) 102 | { 103 | std::cout << " ---------- testing static pool ---------- \n"; 104 | test_static_pool(); 105 | std::cout << " ---------- end of main() ---------- \n"; 106 | 107 | return 0; 108 | } -------------------------------------------------------------------------------- /MempoolHeap/src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | set(CMAKE_CXX_STANDARD 11) 3 | 4 | # all headers 5 | set(HEADERS_MEMPOOL_HEAP 6 | MemPoolHeap.h) 7 | 8 | # all source files 9 | set(SRC_MEMPOOL_HEAP) 10 | 11 | # headers and source files together for convenience 12 | set(HEADERS_AND_SRC_MEMPOOL_HEAP_LIB 13 | ${HEADERS_MEMPOOL_HEAP} 14 | ${SRC_MEMPOOL_HEAP}) 15 | 16 | # set up the library we are building 17 | add_library(Lib_MemPoolHeap ${HEADERS_AND_SRC_MEMPOOL_HEAP_LIB}) 18 | set_target_properties(Lib_MemPoolHeap PROPERTIES LINKER_LANGUAGE CXX) 19 | 20 | target_include_directories(Lib_MemPoolHeap PUBLIC .) 21 | -------------------------------------------------------------------------------- /MempoolIPC/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | option(BUILD_LIB_MEMPOOLIPC "Build the mempool-ipc library" ON) 3 | option(BUILD_LIB_MEMPOOLIPC_EXAMPLES "Build the mempool-ipc library examples" ON) 4 | 5 | if (BUILD_LIB_MEMPOOLIPC) 6 | add_subdirectory(src) 7 | endif() 8 | 9 | # if (BUILD_LIB_MEMPOOLIPC_EXAMPLES) 10 | # add_subdirectory(examples) 11 | # endif() -------------------------------------------------------------------------------- /MempoolIPC/examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | set(CMAKE_CXX_STANDARD 11) 3 | 4 | # find external packages 5 | find_package(Boost 1.70 REQUIRED COMPONENTS filesystem) # system thread filesystem) 6 | 7 | # set the main() files for the executables we want to build. 8 | add_executable(Example_MemPoolIPC Example_MemPoolIPC.cpp) 9 | add_executable(Example_MemPoolArrayIPC Example_MemPoolArrayIPC.cpp) 10 | add_executable(Example_MemPoolGrowerIPC Example_MemPoolGrowerIPC.cpp) 11 | 12 | target_compile_options(Example_MemPoolIPC PRIVATE $<$: -Werror -Wall -Wextra> ) 13 | target_compile_options(Example_MemPoolArrayIPC PRIVATE $<$: -Werror -Wall -Wextra> ) 14 | target_compile_options(Example_MemPoolGrowerIPC PRIVATE $<$: -Werror -Wall -Wextra> ) 15 | 16 | # attach the library to the exectuables, and any other dependenciees we need 17 | target_link_libraries(Example_MemPoolIPC Lib_MemPoolIPC 18 | $<$:pthread> 19 | $<$:rt> 20 | Boost::filesystem) 21 | 22 | target_link_libraries(Example_MemPoolArrayIPC Lib_MemPoolIPC 23 | $<$:pthread> 24 | $<$:rt> 25 | Boost::filesystem) 26 | 27 | target_link_libraries(Example_MemPoolGrowerIPC Lib_MemPoolIPC 28 | $<$:pthread> 29 | $<$:rt> 30 | Boost::filesystem) 31 | -------------------------------------------------------------------------------- /MempoolIPC/examples/Example_MemPoolArrayIPC.cpp: -------------------------------------------------------------------------------- 1 | // Keith Rausch 2 | // 3 | // This example shows how to use an interprocess-compatible, allocator-based mempool to 4 | // create an fetch arrays of elements. You can use this to create mempools of raw images, 5 | // for example. 6 | // 7 | 8 | #include 9 | #include //std::system 10 | #include 11 | #include "UtilsIPC.h" 12 | 13 | #include "MemPoolIPC.h" 14 | 15 | using namespace IPC; // dont crucify me 16 | 17 | struct MyClassIPC 18 | { 19 | short x; 20 | double i, j, k, l, m, n, o, p, q, r, s, t, u, v, w; 21 | MyClassIPC() : x(0) { std::cout << "MyClassIPC()" << std::endl; } 22 | MyClassIPC(int x_in) : x(x_in) { std::cout << "MyClassIPC(" << x << ")\n"; } 23 | ~MyClassIPC() { std::cout << "~MyClassIPC(" << x << ")\n"; } 24 | }; 25 | 26 | void Process() 27 | { 28 | namespace ipc = boost::interprocess; 29 | 30 | // the type to route 31 | typedef MyClassIPC T; 32 | typedef MemPoolIPC PoolArrayT; 33 | 34 | std::cout << "starting process...\n"; 35 | 36 | size_t poolSize = 10; 37 | size_t arraySize = 2; 38 | auto segmentPtr = utils_ipc::open_or_create_mapping("MySharedMemory", 4096 * 16); 39 | auto poolPtr = utils_ipc::find_or_create_shared_object(segmentPtr, "poolArray", segmentPtr->get_segment_manager(), poolSize, arraySize); 40 | if (!segmentPtr || !poolPtr) 41 | { 42 | std::cout << "CRITICAL ERROR - SEGMENT AND/OR POOLARRAY COULD NOT BE CONSTRUCTED\n"; 43 | return; 44 | } 45 | 46 | auto elementPtr = poolPtr->make_pooled(123); // smart pointer to an image. for example 47 | auto &element0 = *elementPtr; // first pixel in the image 48 | auto &element1 = elementPtr.get()[1]; // second pixel in the image 49 | (void)element0; // suppress [-Werror=unused-parameter] 50 | (void)element1; // suppress [-Werror=unused-parameter] 51 | elementPtr.get()[1].x = 456; 52 | 53 | std::cout << "ending process...\n"; 54 | } 55 | 56 | int main(int, char *[]) 57 | { 58 | utils_ipc::RemoveSharedMemoryNuclear("MySharedMemory"); 59 | 60 | try 61 | { 62 | // try-catch guarantees stack unwinding, so the interprocess objects will be handleded properly, even with thrown exceptions 63 | Process(); 64 | } 65 | catch (const std::exception &e) 66 | { 67 | std::cerr << "PROCESS ENCOUNTERED AN ERROR: " << e.what() << '\n'; 68 | } 69 | 70 | return 0; 71 | } -------------------------------------------------------------------------------- /MempoolIPC/examples/Example_MemPoolGrowerIPC.cpp: -------------------------------------------------------------------------------- 1 | // Keith Rausch 2 | // 3 | // This example shows how to use an interprocess-compatible utility to automatically 'resize' 4 | // existing mempools when they are out of available elements. 5 | // 6 | // To prove that this is interprocess capable, the parent process creates the mempool, and a 7 | // child process pulls elements from it - forcing it to resize repeatedly 8 | 9 | #include 10 | #include //std::system 11 | #include 12 | #include "UtilsIPC.h" 13 | #include "RouterIPC.h" 14 | 15 | #include "MemPoolIPC.h" 16 | 17 | using namespace IPC; // dont crucify me 18 | 19 | struct MyClassIPC 20 | { 21 | short x; 22 | double i, j, k, l, m, n, o, p, q, r, s, t, u, v, w; 23 | MyClassIPC() : x(0) { std::cout << "MyClassIPC()" << std::endl; } 24 | MyClassIPC(int x_in) : x(x_in) { std::cout << "MyClassIPC(" << x << ")\n"; } 25 | ~MyClassIPC() { std::cout << "~MyClassIPC(" << x << ")\n"; } 26 | }; 27 | 28 | // 29 | // parent process. generates data 30 | // 31 | void ChildProcess() 32 | { 33 | 34 | namespace ipc = boost::interprocess; 35 | 36 | // the type to route 37 | typedef MyClassIPC T; 38 | typedef MemPoolGrowerIPC> PoolT; 39 | 40 | std::cout << "starting child process...\n"; 41 | 42 | // ask OS for memory region and map it into address space 43 | size_t poolSize = 1; 44 | auto segmentPtr = utils_ipc::open_or_create_mapping("MySharedMemory", 4096 * 16); 45 | auto poolPtr = utils_ipc::find_or_create_shared_object(segmentPtr, "pool", segmentPtr->get_segment_manager(), poolSize, 3); 46 | 47 | if (!segmentPtr || !poolPtr) 48 | { 49 | std::cout << "CRITICAL ERROR - SEGMENT OR POOL COULD NOT BE CONSTRUCTED\n"; 50 | return; 51 | } 52 | 53 | std::vector buffer; 54 | 55 | for (size_t i = 0; i < poolSize * 10; ++i) 56 | { 57 | // make up some data 58 | auto ptr = poolPtr->make_pooled(i); 59 | 60 | buffer.push_back(ptr); 61 | } 62 | 63 | size_t nOutstanding; 64 | size_t nAvailable; 65 | poolPtr->pool.Statistics(nOutstanding, nAvailable); 66 | std::printf("\nshared_ptr pool has %zu outstanding and %zu available. the pool was reconstructed %zu times\n\n", nOutstanding, nAvailable, poolPtr->nReconstructions); 67 | 68 | std::cout << "ending child process...\n"; 69 | } 70 | 71 | int main(int argc, char *argv[]) 72 | { 73 | if (argc == 1) 74 | { //Parent process 75 | 76 | utils_ipc::RemoveSharedMemoryNuclear("MySharedMemory"); 77 | 78 | // create all the objects in one process 79 | 80 | // ask OS for memory region and map it into address space 81 | 82 | typedef MyClassIPC T; 83 | typedef MemPoolGrowerIPC> PoolT; 84 | size_t poolSize = 1; 85 | auto segmentPtr = utils_ipc::open_or_create_mapping("MySharedMemory", 4096 * 16); 86 | auto poolPtr = utils_ipc::find_or_create_shared_object(segmentPtr, "pool", segmentPtr->get_segment_manager(), poolSize, 3); 87 | 88 | if (!segmentPtr || !poolPtr) 89 | { 90 | std::cout << "CRITICAL ERROR - SEGMENT OR POOL COULD NOT BE CONSTRUCTED\n"; 91 | return 1; 92 | } 93 | 94 | // start a new process - but do it from another thread so we dont block 95 | std::string procName(argv[0]); 96 | procName += " child "; 97 | std::function runOtherProcess = [procName] { 98 | if (0 != std::system(procName.c_str())) 99 | std::cerr << "OTHER PROCESS RETURNED ERROR\n"; 100 | }; 101 | std::thread otherProcess(runOtherProcess); 102 | 103 | otherProcess.join(); 104 | } 105 | else 106 | { 107 | try 108 | { 109 | // try-catch guarantees stack unwinding, so the interprocess objects will be handleded properly, even with thrown exceptions 110 | ChildProcess(); 111 | } 112 | catch (const std::exception &e) 113 | { 114 | std::cerr << "CHILD ENCOUNTERED AN ERROR: " << e.what() << '\n'; 115 | } 116 | } 117 | 118 | return 0; 119 | } -------------------------------------------------------------------------------- /MempoolIPC/examples/Example_MemPoolIPC.cpp: -------------------------------------------------------------------------------- 1 | // Keith Rausch 2 | // 3 | // This example shows how to use an interprocess-compatible, allocator-based mempool 4 | // to get shared_ptr's without newing/deleting the memory for the shared_ptr's control block. 5 | // An interprocess-compatible router / pipe mechanism is also demoed here. 6 | // 7 | // Two processes (parent and child) are created. The parent fetches elements from the mempool 8 | // and adds them to the pipe. The child process receives the elements on the other side of the 9 | // pipe. Either process may create the mempool/router/pipe/elements, it doesnt matter who is the first 10 | // or last to use anything. Either process may start or stop at any time, and the elements, pool, pipes, 11 | // and anything else are kept in scope until everyone (nomatter what process they are) is done using them. 12 | // For example, the parent could send all elements to the child and then shutdown. The pipe and child will 13 | // keep the elements and their parent mempool alive until either/both pipe and child nolonger have references 14 | // to them. 15 | // 16 | 17 | #include 18 | #include //std::system 19 | #include 20 | #include "UtilsIPC.h" 21 | #include "RouterIPC.h" 22 | 23 | #include "MemPoolIPC.h" 24 | 25 | using namespace IPC; // dont crucify me 26 | 27 | struct MyClassIPC 28 | { 29 | short x; 30 | double i, j, k, l, m, n, o, p, q, r, s, t, u, v, w; 31 | MyClassIPC() : x(0) { std::cout << "MyClassIPC()" << std::endl; } 32 | MyClassIPC(int x_in) : x(x_in) { std::cout << "MyClassIPC(" << x << ")\n"; } 33 | ~MyClassIPC() { std::cout << "~MyClassIPC(" << x << ")\n"; } 34 | }; 35 | 36 | struct MyClassForTiming 37 | { 38 | typedef std::chrono::_V2::steady_clock::time_point TimeT; 39 | TimeT time; 40 | unsigned char some_large_object[1000]; // big object, why not. not copying it 41 | MyClassForTiming() : time() { } 42 | ~MyClassForTiming() { } 43 | }; 44 | 45 | // 46 | // parent process. generates data 47 | // 48 | void ParentProcess() 49 | { 50 | namespace ipc = boost::interprocess; 51 | 52 | // the type to route 53 | typedef MyClassForTiming T; 54 | typedef MemPoolIPC PoolT; 55 | typedef utils_ipc::RouterIPC RouterT; 56 | 57 | std::cout << "starting parent process...\n"; 58 | 59 | // ask OS for memory region and map it into address space 60 | size_t poolSize = 10; 61 | auto segmentPtr = utils_ipc::open_or_create_mapping("MySharedMemory", 4096 * 16); 62 | auto poolPtr = utils_ipc::find_or_create_shared_object(segmentPtr, "pool", segmentPtr->get_segment_manager(), poolSize); 63 | auto routerPtr = utils_ipc::find_or_create_shared_object(segmentPtr, "Router", segmentPtr->get_segment_manager()); //, pipeSize, *segmentPtr); 64 | 65 | if (!segmentPtr || !routerPtr || !poolPtr) 66 | { 67 | std::cout << "CRITICAL ERROR - SEGMENT, PIPE AND/OR POOL COULD NOT BE CONSTRUCTED\n"; 68 | return; 69 | } 70 | 71 | // simple way of waiting for consumer to come up. completely up to the user 72 | for (size_t i = 0; i < 5; ++i) 73 | { 74 | if (routerPtr->NumReceivers() > 0) 75 | break; 76 | std::this_thread::sleep_for(std::chrono::duration(1.0)); 77 | } 78 | 79 | // send data 80 | for (size_t i = 0; i < poolSize * 2; ++i) 81 | { 82 | // make up some data 83 | auto elementPtr = poolPtr->make_pooled(); 84 | elementPtr->time = std::chrono::steady_clock::now(); 85 | 86 | // route it 87 | routerPtr->Send(elementPtr, RouterT::ENQUEUE_MODE::WAIT_IF_FULL, 1); 88 | 89 | std::this_thread::sleep_for(std::chrono::milliseconds(2)); 90 | // ^^^ not needed, just want to keep timing accurate 91 | } 92 | 93 | size_t nOutstanding; 94 | size_t nAvailable; 95 | poolPtr->Statistics(nOutstanding, nAvailable); 96 | std::printf("\nshared_ptr pool has %zu outstanding and %zu available\n\n", nOutstanding, nAvailable); 97 | 98 | // send shutdown signal. completely optional. destroying this pipe also shuts it down 99 | // std::this_thread::sleep_for(std::chrono::duration(0.1)); 100 | routerPtr->Shutdown(); 101 | 102 | std::cout << "ending parent process...\n"; 103 | } 104 | 105 | // 106 | // child process. consumes data 107 | // 108 | void ChildProcess() 109 | { 110 | namespace ipc = boost::interprocess; 111 | 112 | // the type to route 113 | typedef MyClassForTiming T; 114 | typedef MemPoolIPC PoolT; 115 | typedef utils_ipc::RouterIPC RouterT; 116 | 117 | std::cout << "starting child process...\n"; 118 | 119 | size_t pipeSize = 5; 120 | auto segmentPtr = utils_ipc::open_or_create_mapping("MySharedMemory", 4096 * 16); 121 | auto routerPtr = utils_ipc::find_or_create_shared_object(segmentPtr, "Router", segmentPtr->get_segment_manager()); 122 | if (!segmentPtr || !routerPtr) 123 | { 124 | std::cout << "CRITICAL ERROR - SEGMENT AND/OR ROUTER COULD NOT BE CONSTRUCTED\n"; 125 | return; 126 | } 127 | 128 | auto pipePtr = routerPtr->RegisterAsReceiver(pipeSize, *segmentPtr); 129 | if (!pipePtr) 130 | { 131 | std::cout << "CRITICAL ERROR - PIPE COULD NOT BE CONSTRUCTED\n"; 132 | return; 133 | } 134 | 135 | // receive data 136 | bool timedout = false; 137 | while (*pipePtr && !timedout) 138 | { 139 | RouterT::sPtrT elementPtr; 140 | timedout = pipePtr->Receive(elementPtr, 5); // timed_wait just for dev purposes 141 | if (timedout || ! elementPtr) continue; 142 | 143 | using namespace std::chrono; 144 | auto timeRcv = steady_clock::now(); 145 | auto usec = duration_cast(timeRcv - elementPtr->time).count(); 146 | std::cout << "element took " << usec << " microseconds to send\n"; 147 | // std::cout << "got ptr... use_count = " << ptr.use_count() << std::endl; 148 | } 149 | 150 | std::cout << "ending child proces...\n"; 151 | } 152 | 153 | int main(int argc, char *argv[]) 154 | { 155 | if (argc == 1) 156 | { //Parent process 157 | 158 | utils_ipc::RemoveSharedMemoryNuclear("MySharedMemory"); 159 | 160 | // immediately start the child process 161 | // this way, both processes will be "starting at the same time" and 162 | // asking the system for the same resources 163 | // 164 | // start a new process - but do it from another thread so we dont block 165 | std::string procName(argv[0]); 166 | procName += " child "; 167 | std::function runOtherProcess = [procName] { 168 | if (0 != std::system(procName.c_str())) 169 | std::cerr << "OTHER PROCESS RETURNED ERROR\n"; 170 | }; 171 | std::thread otherProcess(runOtherProcess); 172 | 173 | try 174 | { 175 | // try-catch guarantees stack unwinding, so the interprocess objects will be handleded properly, even with thrown exceptions 176 | ParentProcess(); // now actually start the parent 177 | } 178 | catch (const std::exception &e) 179 | { 180 | std::cerr << "PARENT ENCOUNTERED AN ERROR: " << e.what() << '\n'; 181 | } 182 | 183 | otherProcess.join(); 184 | } 185 | else 186 | { 187 | try 188 | { 189 | // try-catch guarantees stack unwinding, so the interprocess objects will be handleded properly, even with thrown exceptions 190 | ChildProcess(); 191 | } 192 | catch (const std::exception &e) 193 | { 194 | std::cerr << "CHILD ENCOUNTERED AN ERROR: " << e.what() << '\n'; 195 | } 196 | } 197 | 198 | return 0; 199 | } -------------------------------------------------------------------------------- /MempoolIPC/src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | set(CMAKE_CXX_STANDARD 11) 3 | 4 | # all headers 5 | set(HEADERS_MEMPOOL_IPC 6 | UtilsIPC.h 7 | RouterIPC.h 8 | MemPoolIPC.h 9 | ) 10 | 11 | # all source files 12 | set(SRC_MEMPOOL_IPC) 13 | 14 | # headers and source files together for convenience 15 | set(HEADERS_AND_SRC_MEMPOOL_IPC_LIB 16 | ${HEADERS_MEMPOOL_IPC} 17 | ${SRC_MEMPOOL_IPC}) 18 | 19 | # set up the library we are building 20 | add_library(Lib_MemPoolIPC ${HEADERS_AND_SRC_MEMPOOL_IPC_LIB}) 21 | set_target_properties(Lib_MemPoolIPC PROPERTIES LINKER_LANGUAGE CXX) 22 | 23 | target_include_directories(Lib_MemPoolIPC PUBLIC .) 24 | -------------------------------------------------------------------------------- /MempoolIPC/src/RouterIPC.h: -------------------------------------------------------------------------------- 1 | #ifndef ROUTER_IPC_H 2 | #define ROUTER_IPC_H 3 | 4 | #define DEBUG_PRINT_ROUTER_IPC false 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | #include 19 | 20 | namespace utils_ipc 21 | { 22 | 23 | namespace ipc = boost::interprocess; // convenience 24 | 25 | template 26 | class PipeIPC 27 | { 28 | private: 29 | size_t indexPush; 30 | size_t indexPop; 31 | size_t capacity; 32 | std::atomic size; 33 | 34 | //Semaphores to protect and synchronize access 35 | //ipc::interprocess_semaphore mutex; // TODO why did the examples use this innstead of mutex 36 | ipc::interprocess_mutex mutex; 37 | ipc::interprocess_semaphore nempty; 38 | ipc::interprocess_semaphore nstored; 39 | 40 | ipc::allocator allocator; 41 | 42 | ipc::offset_ptr items; 43 | std::atomic_bool shutdown; // this should be fine to do in a shared space. atomics are cpu instruction based 44 | 45 | public: 46 | typedef SharedPtr_T sPtrT; 47 | enum ENQUEUE_MODE 48 | { 49 | WAIT_IF_FULL, 50 | SOFT_FAIL_IF_FULL 51 | }; 52 | 53 | PipeIPC(size_t capacity_in, ipc::managed_shared_memory &segment_in) 54 | : indexPush(0), indexPop(0), capacity(capacity_in), size(0), 55 | /*mutex(1),*/ nempty(capacity_in), nstored(0), 56 | allocator(segment_in.get_segment_manager()), 57 | items(allocator.allocate(capacity)), shutdown(false) 58 | { 59 | if (DEBUG_PRINT_ROUTER_IPC) 60 | std::cout << "PipeIPC()\n"; 61 | 62 | if (!items) 63 | std::cout << "PipeIPC::PipeIPC() - error allocating\n"; 64 | 65 | // we have allocated this memory. now we need to construct it. 66 | // placement-new a bunch of empty pointers in it. if we didnt 67 | // do this, bad things would happen since wed be assigning to 68 | // unconstructed memory 69 | for (size_t i = 0; i < capacity; ++i) 70 | new (&items[i]) SharedPtr_T(); 71 | } 72 | 73 | ~PipeIPC() 74 | { 75 | // this function not mutex protected because there should be no other 76 | // instances out there if the destructor is being called... "should" being the key word here 77 | if (DEBUG_PRINT_ROUTER_IPC) 78 | std::cout << "~Pipe()\n"; 79 | Shutdown(); // just in case, why not 80 | 81 | if (items) 82 | { 83 | for (size_t i = 0; i < capacity; ++i) 84 | items[i].reset(); // destroy 85 | allocator.deallocate(items, capacity); 86 | items = nullptr; 87 | } 88 | } 89 | 90 | // timeout of 0 will wait indefinitely 91 | // return hadError 92 | bool Send(const SharedPtr_T &ptr, const ENQUEUE_MODE &enqueueMode = ENQUEUE_MODE::SOFT_FAIL_IF_FULL, const size_t &timeout_seconds = 0) 93 | { 94 | if (shutdown) 95 | { 96 | std::cerr << "ROUTER - COULD NOT ROUTE THIS POINTER, ROUTER IS SHUT DOWN\n"; 97 | return false; 98 | } 99 | 100 | bool success = true; // default 101 | if (ENQUEUE_MODE::SOFT_FAIL_IF_FULL == enqueueMode) 102 | success = nempty.try_wait(); 103 | else 104 | { 105 | if (0 != timeout_seconds) 106 | { 107 | using namespace boost::posix_time; 108 | ptime abs_time = second_clock::universal_time() + seconds(timeout_seconds); 109 | success = nempty.timed_wait(abs_time); 110 | } 111 | else 112 | { 113 | nempty.wait(); 114 | } 115 | } 116 | 117 | success &= !shutdown; // cant be succussful if we are shutting down 118 | 119 | if (success) 120 | { 121 | { // lock_guard scope 122 | ipc::scoped_lock guard(mutex); 123 | //mutex.wait(); 124 | items[indexPush] = ptr; 125 | indexPush = (indexPush + 1) % capacity; 126 | ++size; 127 | //mutex.post(); 128 | } 129 | nstored.post(); 130 | } 131 | else 132 | std::cerr << "ROUTER - COULD NOT ROUTE THIS POINTER, ROUTER FULL OR SHUTING DOWN\n"; 133 | 134 | return !success; 135 | } 136 | 137 | // timeout of 0 will wait indefinitely 138 | bool Receive(SharedPtr_T &ptr, const size_t &timeout_seconds = 0) 139 | { 140 | ptr.reset(); 141 | bool timedout = false; // default 142 | 143 | if (0 != timeout_seconds) 144 | { 145 | using namespace boost::posix_time; 146 | ptime abs_time = second_clock::universal_time() + seconds(timeout_seconds); 147 | timedout = !nstored.timed_wait(abs_time); 148 | } 149 | else 150 | nstored.wait(); 151 | 152 | if (!timedout) 153 | { 154 | { // lock_guard scope 155 | ipc::scoped_lock guard(mutex); 156 | //mutex.wait(); 157 | ptr = items[indexPop]; 158 | items[indexPop].reset(); 159 | indexPop = (indexPop + 1) % capacity; 160 | --size; 161 | //mutex.post(); 162 | } 163 | nempty.post(); 164 | } 165 | return timedout; 166 | } 167 | 168 | void Shutdown() 169 | { 170 | shutdown = true; // atomic 171 | 172 | nstored.post(); // notify the receiver 173 | 174 | nempty.post(); // notify any pending senders 175 | } 176 | 177 | // return true if the pipe in not shutdown OR has queued elements 178 | operator bool() const 179 | { 180 | return !shutdown || size > 0; 181 | } 182 | }; 183 | 184 | // 185 | // Utility class for sending data to multiple receivers at once. 186 | // You ask it for a pipe and it'll return a strong pointer while maintaining a weak pointer for itself. 187 | // Calling Send() here will send elements to all known pipes. 188 | // 189 | template 190 | class RouterIPC 191 | { 192 | typedef PipeIPC PipeT; 193 | 194 | typedef ipc::managed_shared_memory::segment_manager segment_manager_type; 195 | 196 | typedef ipc::allocator void_allocator_type; 197 | typedef ipc::deleter deleter_type; 198 | public: 199 | typedef ipc::shared_ptr sStrongPipeT; 200 | typedef ipc::weak_ptr sWeakPipeT; 201 | private: 202 | // ipc::allocator allocator; 203 | typedef std::scoped_allocator_adaptor> ShmemAllocator; 204 | typedef ipc::vector MyVector; 205 | 206 | ipc::interprocess_mutex mutex; 207 | MyVector pipes; 208 | // segment_manager_type *segment_manager; 209 | 210 | public: 211 | typedef SharedPtr_T sPtrT; 212 | typedef typename PipeT::ENQUEUE_MODE ENQUEUE_MODE; 213 | 214 | RouterIPC(SegmentManager *segmentManager_in) 215 | : pipes(segmentManager_in) 216 | { 217 | } 218 | 219 | // 220 | // return a strong pointer to the pipe and record a weak pointer internally 221 | // 222 | sStrongPipeT RegisterAsReceiver(size_t capacity_in, ipc::managed_shared_memory &segment_in) 223 | { 224 | // create a new pipe. return a strong pointer to it, and store a weak pointer to it inside this class 225 | 226 | segment_manager_type *segment_manager = segment_in.get_segment_manager(); 227 | 228 | if (!segment_manager) 229 | return sStrongPipeT(); 230 | 231 | sStrongPipeT newPipe(segment_manager->template construct(ipc::anonymous_instance, std::nothrow)(capacity_in, segment_in), 232 | void_allocator_type(segment_manager), 233 | deleter_type(segment_manager)); 234 | 235 | if (nullptr == newPipe) 236 | return sStrongPipeT(); 237 | 238 | sWeakPipeT newPipeWeak = newPipe; 239 | 240 | ipc::scoped_lock guard(mutex); 241 | pipes.push_back(newPipeWeak); 242 | 243 | return newPipe; 244 | } 245 | 246 | // 247 | // Send element to all pipes. 248 | // The Send signature is whatever you want it to be. 249 | // Return the number of LIVING pipes that had an errr while sending. 250 | // 251 | template 252 | size_t Send(Args &&...args) 253 | { 254 | size_t nLivingPipesWithSendError = 0; 255 | ipc::scoped_lock guard(mutex); 256 | 257 | // I take this opportunity to filter the vector for dead pointers. if 258 | // If we encounter a weak_ptr that we couldnt promote, swap it with the element 259 | // at the end of the vector and try again. then shrink the vector 260 | 261 | for (int i = 0; i < (int)pipes.size(); ++i) // signed number desired 262 | { 263 | sStrongPipeT pipePtr = pipes[i].lock(); 264 | 265 | if (nullptr == pipePtr) 266 | { 267 | std::cout << "RouterIPC::Send() - found bad pipe, swapping and popping\n"; 268 | pipes[i] = pipes.back(); 269 | pipes.pop_back(); // this logic works even if the bad pipe is the last in the vector 270 | --i; 271 | continue; 272 | } 273 | 274 | // this pipe must be good, send the data to it 275 | if (pipePtr->Send(std::forward(args)...)) 276 | ++nLivingPipesWithSendError; 277 | } 278 | 279 | return nLivingPipesWithSendError; 280 | } 281 | 282 | // 283 | // Send shutodwn signal to all pipes 284 | // 285 | void Shutdown() 286 | { 287 | ipc::scoped_lock guard(mutex); 288 | 289 | for (auto &pipe : pipes) 290 | { 291 | sStrongPipeT pipePtr = pipe.lock(); 292 | if (pipePtr) 293 | { 294 | pipePtr->Shutdown(); 295 | } 296 | } 297 | 298 | // i dont think there's a strong reason to call pipes.clear() 299 | } 300 | 301 | // 302 | // get number of living pipes 303 | // 304 | size_t NumReceivers() 305 | { 306 | // We dont care about dead pipes 307 | ipc::scoped_lock guard(mutex); 308 | 309 | size_t count = 0; 310 | 311 | for (auto &pipe : pipes) 312 | { 313 | if (pipe.lock()) 314 | ++count; 315 | } 316 | 317 | return count; 318 | } 319 | }; 320 | 321 | } // namespace 322 | 323 | #endif -------------------------------------------------------------------------------- /MempoolIPC/src/UtilsIPC.h: -------------------------------------------------------------------------------- 1 | #ifndef UTILS_IPC_H 2 | #define UTILS_IPC_H 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include // random number gen 11 | #include // ::remove() 12 | 13 | // TODO send error messages to std::cerr 14 | 15 | namespace utils_ipc 16 | { 17 | 18 | namespace ipc = boost::interprocess; // convenience 19 | 20 | // wrapped these functions in the own namespace in an attempt to 21 | // make them private. not sure if this is a good idea or not. i like 22 | // giving the user options. 23 | namespace 24 | { 25 | static std::string GetObjectMutexName(const std::string &name) 26 | { 27 | return "__" + name + "_object_mutex__"; 28 | } 29 | static std::string GetObjectCounterName(const std::string &name) 30 | { 31 | return "__" + name + "_object_couter__"; 32 | } 33 | static std::string GetObjectName(const std::string &name) 34 | { 35 | return "__" + name + "_object__"; 36 | } 37 | static std::string GetMappingMutexName(const std::string &name) 38 | { 39 | return "__" + name + "_mapping_mutex__"; 40 | } 41 | static std::string GetMappingCounterName(const std::string &name) 42 | { 43 | return "__" + name + "_mapping_count__"; 44 | } 45 | static std::string GetMappingName(const std::string &name) 46 | { 47 | return "__" + name + "_mapping__"; 48 | } 49 | 50 | // 51 | // destroy an object by name in a shared segment 52 | // 53 | template 54 | static void destroy_shared_object(const std::shared_ptr &segmentPtr, const std::string &name) 55 | { 56 | if (!segmentPtr) 57 | return; 58 | 59 | std::string name_mutex = GetObjectMutexName(name); 60 | std::string name_counter = GetObjectCounterName(name); 61 | std::string name_object = GetObjectName(name); 62 | 63 | // find_or_create the mutex (should just find it) 64 | auto mutexPtr = segmentPtr->find_or_construct(name_mutex.c_str(), std::nothrow)(); 65 | if (!mutexPtr) 66 | { 67 | std::printf("SOMETHING HAS GONE CRITICALLY WRONG IN LOOKING FOR \"%s\" TO DESTROY \"%s\"\n", name_mutex.c_str(), name.c_str()); 68 | return; 69 | } 70 | auto &mutex = *mutexPtr; 71 | 72 | // lock the mutex 73 | ipc::scoped_lock guard(mutex); 74 | 75 | // find or create the reference counter (should just find it) 76 | auto counterPtr = segmentPtr->find_or_construct(name_counter.c_str(), std::nothrow)(0); 77 | if (!counterPtr) 78 | { 79 | std::printf("SOMETHING HAS GONE CRITICALLY WRONG IN LOOKING FOR \"%s\" TO DESTROY \"%s\"\n", name_counter.c_str(), name.c_str()); 80 | return; 81 | } 82 | auto &counter = *counterPtr; 83 | 84 | // decrement the reference counter 85 | auto counterOld = counter; 86 | if (counter > 0) 87 | --counter; 88 | 89 | // destroy the object if appropriate 90 | if (1 == counterOld) 91 | { 92 | std::printf("\"%s\" reference count was %zu, now %zu. DELETING...\n", name.c_str(), counterOld, counter); 93 | 94 | // try-catch JUST IN CASE. guarantees stack unwinding. 95 | try 96 | { 97 | segmentPtr->destroy(name_object.c_str()); 98 | } 99 | catch (const std::exception &e) 100 | { 101 | std::printf("ERROR WHILE DESTROYING OBJECT \"%s\". Error is: %s\n", name_object.c_str(), e.what()); 102 | } 103 | } 104 | else 105 | std::printf("\"%s\" reference count was %zu, now %zu - not deleting.\n", name.c_str(), counterOld, counter); 106 | } 107 | 108 | // 109 | // remove a shared segment by name 110 | // 111 | static void remove_mapping(ipc::managed_shared_memory *segmentPtr, const std::string &name, bool &shouldRemove) 112 | { 113 | shouldRemove = false; 114 | 115 | if (!segmentPtr) 116 | return; 117 | 118 | std::string name_mutex = GetMappingMutexName(name); 119 | std::string name_counter = GetMappingCounterName(name); 120 | std::string name_mapping = GetMappingName(name); 121 | 122 | // find_or_create the mutex (should just find it) 123 | auto mutexPtr = segmentPtr->find_or_construct(name_mutex.c_str(), std::nothrow)(); 124 | if (!mutexPtr) 125 | return; // this should never happen 126 | auto &mutex = *mutexPtr; 127 | 128 | // lock the mutex 129 | ipc::scoped_lock guard(mutex); 130 | 131 | // find or create the reference counter (should just find it) 132 | auto counterPtr = segmentPtr->find_or_construct(name_counter.c_str(), std::nothrow)(0); 133 | if (!counterPtr) 134 | return; // this should never happen 135 | auto &counter = *counterPtr; 136 | 137 | // decrement the reference counter 138 | auto counterOld = counter; 139 | if (counter > 0) 140 | --counter; 141 | 142 | // remove the mapping from the OS if appropriate 143 | shouldRemove = 1 == counterOld; 144 | 145 | std::printf("\"%s\" reference count was %zu, now %zu.\n", name.c_str(), counterOld, counter); 146 | } 147 | 148 | } // anonymous namespace 149 | 150 | // 151 | // Creates an object in the mapped region and returns it wrapped in a shared_ptr. Using this function lets 152 | // us track its use count, so the last reference to that object (among ALL processes) will automatically 153 | // destroy it. The object's deleter function holds a copy of the segment pointer, so the object will keep 154 | // the segment in scope. no worries 155 | // 156 | template 157 | static std::shared_ptr find_or_create_shared_object(const std::shared_ptr &segmentPtr, size_t nProcessMax, const std::string &name, Args &&...args) 158 | { 159 | // 160 | // in the future, when we support variadic labda's, wrap most of this code in a labda and call the SegmentManager::atomic_func() 161 | // this will let us crate 3 objects atomically... it will also let us do the same in destroy_shared_object(), where we will be able 162 | // to destroy three objects atomically and not worry about race conditions affecting the object count. 163 | // the main reason for this is so that we can get the names of the objects not-yet destroyed in the segment 164 | // before we remove the segment. that's just a nice check. 165 | // 166 | 167 | if (!segmentPtr) 168 | return nullptr; 169 | 170 | std::string name_mutex = GetObjectMutexName(name); 171 | std::string name_counter = GetObjectCounterName(name); 172 | std::string name_object = GetObjectName(name); 173 | 174 | // find_or_create the mutex 175 | ipc::interprocess_mutex *mutexPtr = segmentPtr->find_or_construct(name_mutex.c_str(), std::nothrow)(); 176 | if (!mutexPtr) 177 | return nullptr; 178 | auto &mutex = *mutexPtr; 179 | 180 | // lock the mutex 181 | ipc::scoped_lock guard(mutex); 182 | 183 | // find or create the reference counter 184 | std::size_t *counterPtr = segmentPtr->find_or_construct(name_counter.c_str(), std::nothrow)(0); 185 | if (!counterPtr) 186 | return nullptr; 187 | auto &counter = *counterPtr; 188 | 189 | if (counter >= nProcessMax) 190 | return nullptr; 191 | 192 | // find_or_create the object 193 | T *objectPtr = nullptr; 194 | try 195 | { 196 | objectPtr = segmentPtr->find_or_construct(name_object.c_str(), std::nothrow)(std::forward(args)...); 197 | // std::nothrow returns nullptr if there was not enough memory. if there was an exception during construction, it 198 | // wont handle that, so we wrap this in a try-catch just in case. 199 | 200 | // increment the reference counter 201 | ++counter; // should this be inside or outside, idk. if an object failed to construct, did it really construct? 202 | // destructors arent called if an error is thrown during construction... 203 | } 204 | catch (const std::exception &e) 205 | { 206 | objectPtr = nullptr; 207 | std::printf("ERROR WHILE CONSTRUCTING OBJECT \"%s\". Error is: %s\n", name_object.c_str(), e.what()); 208 | } 209 | 210 | // wrap the object in a shared pointer with a deleter that will handle cleanup 211 | std::function deleter = [segmentPtr, name](T *) { 212 | destroy_shared_object(segmentPtr, name); 213 | }; 214 | std::shared_ptr shared_ip_object(objectPtr, deleter); 215 | 216 | std::printf("\"%s\" reference count was %zu, now %zu. There are %zu bytes free in the shared segment\n", name.c_str(), objectPtr ? counter - 1 : counter, counter, segmentPtr->get_free_memory()); 217 | 218 | return shared_ip_object; 219 | } 220 | 221 | template 222 | static std::shared_ptr find_or_create_shared_object(const std::shared_ptr &segmentPtr, const std::string &name, Args &&...args) 223 | { 224 | return find_or_create_shared_object(segmentPtr, std::numeric_limits::max(), name, std::forward(args)...); 225 | } 226 | 227 | // 228 | // This function is more of a convenience than a threadsafe guarantee of corruption 229 | // it is impossible (as far as i can tell) to prevent the race condition of one thread removing (unmapping) 230 | // the shared memory the moment after another thread opens/creates it. In such a case, remove() will throw 231 | // BUT the resource will still be removed in name, and only when all handles to it are destroyed will 232 | // the OS destroy and destroy the mapping itself. 233 | // 234 | // see: https://www.boost.org/doc/libs/1_74_0/doc/html/interprocess/sharedmemorybetweenprocesses.html#interprocess.sharedmemorybetweenprocesses.sharedmemory.removing 235 | // 236 | // If one or more references to the shared memory object exist when its unlinked, the name will be removed 237 | // before the function returns, but the removal of the memory object contents will be postponed until all 238 | // open and map references to the shared memory object have been removed. 239 | // 240 | static std::shared_ptr open_or_create_mapping(const std::string &name, size_t size) 241 | { 242 | std::string name_mutex = GetMappingMutexName(name); 243 | std::string name_counter = GetMappingCounterName(name); 244 | std::string name_mapping = GetMappingName(name); 245 | 246 | auto deleter = [name](ipc::managed_shared_memory *ptr) { 247 | // written this way because it seems like the proper order of operations. 248 | // 1 - decrement counters 249 | // 2 - deleter segment manager (the object in this process. typically itll be on the stack anyways) 250 | // 3 - remove the mapping 251 | bool shouldRemove; 252 | remove_mapping(ptr, name, shouldRemove); 253 | 254 | // Uncomment this block in the future when we support c++14. That will let us use variadic lambda's 255 | // That will be possible when we move 99% of the body of find_or_create_shared_object() into a lambda and call. 256 | // That will let us atomically create reference counted objects. and the mutex and counter objects associated 257 | // with them will also be deleted atomically when we move 99% of the body of destroy_shared_object() into a 258 | // labda to mirror it. 259 | // 260 | // auto remainingChecker = [ptr, name]() 261 | // { 262 | // size_t nNamed = ptr->get_num_named_objects(); 263 | // size_t nUnique = ptr->get_num_unique_objects(); 264 | // std::printf("There are %zu named and %zu unnamed objects remaining in the segment %s", nNamed, nUnique, name.c_str()); 265 | // 266 | // typedef ipc::managed_shared_memory::const_named_iterator const_named_it; 267 | // const_named_it named_beg = ptr->named_begin(); 268 | // const_named_it named_end = ptr->named_end(); 269 | // 270 | // typedef ipc::managed_shared_memory::const_unique_iterator const_unique_it; 271 | // const_unique_it unique_beg = ptr->unique_begin(); 272 | // const_unique_it unique_end = ptr->unique_end(); 273 | // 274 | // for(; named_beg != named_end; ++named_beg) 275 | // std::printf("%s\n", named_beg->name()); 276 | // 277 | // for(; unique_beg != unique_end; ++unique_beg) 278 | // std::printf("%s\n", unique_beg->name()); 279 | // }; 280 | // 281 | // if (shouldRemove) 282 | // ptr->atomic_func(remainingChecker); 283 | 284 | delete ptr; 285 | 286 | if (shouldRemove) 287 | { 288 | std::printf("REMOVING \"%s\"\n", name.c_str()); 289 | 290 | std::string name_mapping = GetMappingName(name); 291 | bool hadError = !ipc::shared_memory_object::remove(name_mapping.c_str()); 292 | if (hadError) 293 | { 294 | std::cerr << "GOT ERROR WHILE TRYING TO REMOVE \"" 295 | << name 296 | << "\". " 297 | "THIS CAN HAPPEN IF THERE ARE OUTSTANDING HANDLES AT THE TIME OF REMOVE. " 298 | "ANOTHER PROCESS MAY HAVE OPENED A HANDLE IN A RACE CONDITION. " 299 | "REGARDLESS, THE MAPPING HAS BEEN REMOVED IN NAME AND WILL BE " 300 | "FULLY REMOVED/DESTROYED BY THE KERNEL WHEN ALL OPEN REFERENCES " 301 | "TO IT ARE CLOSED. THIS MEANS THAT THIS PARTICULAR MAPPING IS " 302 | "BASICALLY A GHOST NOW AND CANNOT BE FOUND BY ANYONE LOOKING FOR IT. " 303 | "YOU ARE ABOUT TO HAVE A BAD TIME.\n"; 304 | } 305 | } 306 | else 307 | { 308 | std::printf("NOT REMOVING \"%s\"\n", name.c_str()); 309 | } 310 | }; 311 | 312 | // open or create the mapping first 313 | // i dont LOVE creating the ipc::managed_shared_memory object like this, but this is the only way 314 | // we can easily specify a custom deleter for automatic cleanup, so i feel like thats worth it. 315 | // its also not really any different than what make_shared would do, so what's it matter... 316 | // i fully recognize that im convincing myself here more than you 317 | std::shared_ptr segmentPtr(new ipc::managed_shared_memory(ipc::open_or_create, name_mapping.c_str(), size), deleter); 318 | // when you really think about things, the fact that im new-ing a segment_manager here should 319 | // make your brain hurt, and that is not the kind of thing i would normally say lightly 320 | 321 | if (!segmentPtr) 322 | return nullptr; 323 | 324 | // find_or_create the mutex 325 | auto mutexPtr = segmentPtr->find_or_construct(name_mutex.c_str(), std::nothrow)(); 326 | if (!mutexPtr) 327 | return nullptr; 328 | auto &mutex = *mutexPtr; 329 | 330 | // lock the mutex 331 | ipc::scoped_lock guard(mutex); 332 | 333 | // find or create the reference counter 334 | auto counterPtr = segmentPtr->find_or_construct(name_counter.c_str(), std::nothrow)(0); 335 | if (!counterPtr) 336 | return nullptr; 337 | auto &counter = *counterPtr; 338 | 339 | // increment the reference counter 340 | ++counter; 341 | 342 | std::printf("\"%s\" reference count was %zu, now %zu.\n", name.c_str(), counter - 1, counter); 343 | 344 | return segmentPtr; 345 | } 346 | 347 | // 348 | // get a random number to uniquely identify an instance of shared memory across all computers and time. its far from perfect. 349 | // 350 | static std::uint_fast64_t GenerateUniqueID() 351 | { 352 | // there are much better ways in do this. The different answers here 353 | // https://stackoverflow.com/questions/24334012/best-way-to-seed-mt19937-64-for-monte-carlo-simulations 354 | // are pretty good ideas. 355 | 356 | // also interesting 357 | // https://stackoverflow.com/questions/8500677/what-is-uint-fast32-t-and-why-should-it-be-used-instead-of-the-regular-int-and-u 358 | 359 | std::mt19937_64 prng; 360 | std::random_device device; 361 | std::seed_seq seq{device(), device(), device(), device()}; 362 | prng.seed(seq); 363 | std::uint_fast64_t number = prng(); 364 | 365 | return number; 366 | } 367 | 368 | // This is the nuclear option for removing shared memory segments. 369 | // ipc::shared_memory_object::remove() fails if there is still a reference to the shared memory. 370 | // This typically takes the form of an undestructed segment manager... which can happen if a 371 | // process crashed or didnt shutdown properly 372 | // 373 | // the solution (on linux) is to just remove the shared memory segment from /dev/shm. Since posix 374 | // shared memory segments are basically just "files in disguise" (their words), and since 375 | // boost::interprocess appears to use /dev/shm, we can just delete the shared memory region as though it were a file 376 | // 377 | // NOTE: TODO I have not implemented this function for windows 378 | // 379 | // https://stackoverflow.com/questions/56021045/i-cannot-see-the-shared-memory-created-by-the-boostinterprocess-via-shell-comm 380 | // https://stackoverflow.com/questions/36629713/shm-unlink-from-the-shell 381 | // 382 | // 383 | // unrelated, but i know this will bite me one day: 384 | // https://superuser.com/questions/1117764/why-are-the-contents-of-dev-shm-is-being-removed-automatically 385 | static bool RemoveSharedMemoryNuclear(const std::string &name) 386 | { 387 | bool hadError = true; 388 | 389 | std::string name_mapping = GetMappingName(name); 390 | 391 | #ifdef _WIN32 392 | std::cout << "utils_ipc::RemoveSharedMemoryNuclear() - attempting to forcibly wipe any existing shared memory, but i havent written this code yet\n"; 393 | hadError = true; 394 | return hadError; 395 | #endif 396 | 397 | #ifdef __linux__ 398 | hadError = false; 399 | std::string fullPath = "/dev/shm/" + name_mapping; 400 | // race condition here, but this was a nuclear option anyways 401 | if (boost::filesystem::exists(fullPath)) 402 | { 403 | std::printf("utils_ipc::RemoveSharedMemoryNuclear() - found a shared segment by the name of \"%s\". Attempting to forcibly remove it. If there is a process still using this region, it is about to have a bad time.\n", name.c_str()); 404 | hadError = !boost::filesystem::remove(fullPath); 405 | } 406 | return hadError; 407 | #endif 408 | 409 | // if we got here, we dont support this OS 410 | hadError = true; 411 | return hadError; 412 | } 413 | 414 | } // namespace utils_ipc 415 | 416 | #endif -------------------------------------------------------------------------------- /Networking/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | option(BUILD_LIB_BEASTNETWORKING "Build the networking library" ON) 3 | option(BUILD_LIB_BEASTNETWORKING_EXAMPLES "Build the networking library examples" ON) 4 | 5 | if (BUILD_LIB_BEASTNETWORKING) 6 | add_subdirectory(src) 7 | endif() 8 | 9 | if (BUILD_LIB_BEASTNETWORKING_EXAMPLES) 10 | add_subdirectory(examples) 11 | endif() -------------------------------------------------------------------------------- /Networking/examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | set(CMAKE_CXX_STANDARD 17) 3 | 4 | # find external packages 5 | find_package(Boost 1.70 REQUIRED COMPONENTS filesystem) # system thread filesystem) 6 | 7 | # set the main() files for the executables we want to build. 8 | add_executable(Example_Networking Example_Networking.cpp ) 9 | 10 | target_compile_options(Example_Networking PRIVATE $<$: -Werror -Wall -Wextra> -Wno-unused-function) 11 | 12 | # attach the library to the exectuables, and any other dependenciees we need 13 | target_link_libraries(Example_Networking lib_beastNetworking Lib_MemPoolIPC 14 | $<$:pthread> 15 | $<$:rt> 16 | Boost::filesystem 17 | ) 18 | 19 | -------------------------------------------------------------------------------- /Networking/examples/Example_Networking.cpp: -------------------------------------------------------------------------------- 1 | // Keith Rausch 2 | // 3 | // This example shows how to use the networking utilities to send data in loopback. 4 | // two processes are created (parent and child. One sends out a heartbeat interprocess-compatible, allocator-based mempool 5 | // to get shared_ptr's without newing/deleting the memory for the shared_ptr's control block. 6 | // An interprocess-compatible router / pipe mechanism is also demoed here. 7 | // 8 | // Two processes (parent and child) are created. One pulls elements from a mempool and sends them out 9 | // over the network. The second (child) process receives the elements and adds them to a pipe to be send out to 10 | // anyone else on the same receiving computer. The sender (parent) advertises its topic at 1Hz. The receiver (child) 11 | // listens for those UDP broadcasts and (when it hears a matching topic), connects to the sender via a WebSocket. 12 | // 13 | // The parent and child processes may start / stop at any time. If the recevier hears the 1Hz broadcast, itll automatically 14 | // reconnect to the sender. In this particular example, the sender is told to wait until it has at least one recevier 15 | // connected to it. 16 | // 17 | // A real implementation of this multi-device, pub-sub architecture would be to put one receiver on each computer. That 18 | // receiver would be responsible for receiving all incoming messages for all topics and adding them to the routers/pipes 19 | // on that computer. Computers can have any number of senders. i guess its a mesh network of many-to-one's. 20 | // 21 | // if a receiving computer determines it has the same unique identifier for a mempool, then it wont bother to route messages 22 | // over the network.... those messages should already be put in the router on that computer by the original sender. of course, 23 | // this is optional 24 | // 25 | // Its important to understand that this networking code is completely independant of the IPC code. The callbacks for 26 | // broadcast-recevier, socket-receive, socket-accept, socket-close, socket-error, etc. are all user provided. 27 | // You should be able to use this code to make any two comptures on a network talk super easily. it was meant to be super 28 | // flexible and reusable. The downside is that you have to handle your own serialization logic. The library should let 29 | // you know the endianess of the sender, but you still have to deal with it. 30 | // 31 | 32 | #include 33 | #include //std::system 34 | #include 35 | #include 36 | 37 | #include "MemPoolIPC.h" 38 | #include "RouterIPC.h" 39 | #include "MultiClientSender.hpp" 40 | #include "MultiClientReceiver.hpp" 41 | 42 | using namespace IPC; // dont crucify me 43 | using namespace BeastNetworking; 44 | 45 | struct MyClassIPC 46 | { 47 | short x; 48 | double i, j, k, l, m, n, o, p, q, r, s, t, u, v, w; 49 | MyClassIPC() : x(0) { std::cout << "MyClassIPC()" << std::endl; } 50 | MyClassIPC(int x_in) : x(x_in) { std::cout << "MyClassIPC(" << x << ")\n"; } 51 | MyClassIPC(const void *msgPtr, size_t msgSize) 52 | { 53 | if (msgSize != sizeof(MyClassIPC)) 54 | return; 55 | memcpy(this, msgPtr, msgSize); 56 | std::printf("MyClassIPC(%p, %zu), basically MyClassIPC(%d)\n", msgPtr, msgSize, (int)x); 57 | } 58 | ~MyClassIPC() { std::cout << "~MyClassIPC(" << x << ")\n"; } 59 | }; 60 | 61 | // 62 | // parent process. generates data 63 | // 64 | void ProducerProcess() 65 | { 66 | namespace ipc = boost::interprocess; 67 | 68 | // the type to route 69 | typedef MyClassIPC T; 70 | typedef MemPoolIPC PoolT; 71 | 72 | std::cout << "starting producer process...\n"; 73 | 74 | // ask OS for memory region and map it into address space 75 | size_t poolSize = 50; 76 | auto segmentPtr = utils_ipc::open_or_create_mapping("MySharedMemory", 4096 * 16); 77 | auto poolPtr = utils_ipc::find_or_create_shared_object(segmentPtr, "pool", segmentPtr->get_segment_manager(), poolSize); 78 | if (!segmentPtr || !poolPtr) 79 | { 80 | std::cout << "CRITICAL ERROR - SEGMENT AND/OR POOL COULD NOT BE CONSTRUCTED\n"; 81 | return; 82 | } 83 | 84 | // create io_context (work_guarded) 85 | size_t nThreads = 1; 86 | utils_asio::GuardedContext guarded_context(nThreads); 87 | 88 | boost::asio::ssl::context ssl_context{boost::asio::ssl::context::tlsv12}; 89 | 90 | MultiClientSenderArgs args; 91 | MultiClientSender sender(guarded_context.GetIOContext(), ssl_context, "myTopic", args, poolPtr->UniqueInstanceID()); 92 | sender.StartHeartbeat(); 93 | 94 | std::this_thread::sleep_for(std::chrono::duration(1.5)); 95 | 96 | for (size_t i = 0; i < 50; ++i) 97 | { 98 | // make up some data 99 | auto ptr = poolPtr->make_pooled(i); 100 | if (!ptr) 101 | { 102 | std::cout << "pool depleted!\n"; 103 | std::this_thread::sleep_for(std::chrono::duration(0.1)); 104 | continue; 105 | } 106 | 107 | // just one of many ways to send data. 108 | // the std::function method is the most generic, 109 | // but this is a convenience function for most simple cases 110 | sender.SendAsync((void*)(&*ptr), sizeof(*ptr), [ptr](boost::beast::error_code, size_t, boost::asio::ip::tcp::endpoint){}); 111 | } 112 | 113 | std::this_thread::sleep_for(std::chrono::duration(1.0)); // give the sender some time to finish up 114 | 115 | // shutdown while objects associated with async functions are still in scope. not necessary here, wouldnt hurt. 116 | // guarded_context.Shutdown(); 117 | 118 | std::cout << "ending producer process...\n"; 119 | } 120 | 121 | // 122 | // child process. consumes data 123 | // 124 | void ConsumerProcess() 125 | { 126 | namespace ipc = boost::interprocess; 127 | 128 | // the type to route 129 | typedef MyClassIPC T; 130 | typedef MemPoolIPC PoolT; 131 | typedef utils_ipc::RouterIPC RouterT; 132 | 133 | std::cout << "starting consumer process...\n"; 134 | size_t poolSize = 50; 135 | auto segmentPtr = utils_ipc::open_or_create_mapping("MySharedMemory", 4096 * 16); 136 | auto poolPtr = utils_ipc::find_or_create_shared_object(segmentPtr, "pool", segmentPtr->get_segment_manager(), poolSize); 137 | auto routerPtr = utils_ipc::find_or_create_shared_object(segmentPtr, "Router", segmentPtr->get_segment_manager()); 138 | if (!segmentPtr) 139 | { 140 | std::cout << "CRITICAL ERROR - SEGMENT COULD NOT BE CONSTRUCTED\n"; 141 | return; 142 | } 143 | 144 | // 145 | // setup all the callbacks for each topic 146 | // 147 | MultiClientReceiver::TopicStatesT receivableTopics; 148 | 149 | auto callbackRead = [poolPtr, routerPtr](const tcp::endpoint &endpoint, const void *msgPtr, size_t msgSize) { 150 | // std::string msg(static_cast(msgPtr), msgSize); 151 | 152 | auto elementPtr = poolPtr->make_pooled(msgPtr, msgSize); 153 | size_t nPipesHadError = routerPtr->Send(elementPtr, RouterT::ENQUEUE_MODE::SOFT_FAIL_IF_FULL); 154 | if (nPipesHadError > 0) 155 | std::printf("MultiClientReceiver::TopicCallbacksT::CallbackRead() - Could not route element on topic \"%s\". Router had %zu pipes with errors and set to SOFT_FAIL_IF_FULL", "myTopic", nPipesHadError); 156 | 157 | std::stringstream ss; 158 | ss << endpoint; 159 | std::string endpointString = ss.str(); 160 | std::printf("CONSUMER-READ - endpoint: %s\n", endpointString.c_str()); 161 | }; 162 | 163 | std::shared_ptr state = std::make_shared(); 164 | state->callbacks.callbackWSRead = callbackRead; 165 | state->callbacks.callbackAccept = [](const tcp::endpoint &endpoint) { std::cout << "session accepted:"<callbacks.callbackUpgrade = [](const tcp::endpoint &endpoint) { std::cout << "session upgraded:"< receiver = std::make_shared(guarded_context.GetIOContext(), ssl_context, receivableTopics, args, poolPtr->UniqueInstanceID()); 183 | receiver->ListenForTopics(); 184 | 185 | guarded_context.GetIOContext().run(); // block until the receiver timeout out 186 | 187 | 188 | std::cout << "ending consumer proces...\n"; 189 | } 190 | 191 | int main(int argc, char *argv[]) 192 | { 193 | 194 | if (argc == 1) 195 | { //Producer process 196 | 197 | utils_ipc::RemoveSharedMemoryNuclear("MySharedMemory"); 198 | 199 | // start producer, but do it from another thread so we dont block 200 | std::string procName(argv[0]); 201 | procName += " child "; 202 | std::function runOtherProcess = [procName] { 203 | if (0 != std::system(procName.c_str())) 204 | std::cerr << "OTHER PROCESS RETURNED ERROR\n"; 205 | }; 206 | std::thread otherProcess(runOtherProcess); 207 | 208 | // immediately start the producer process 209 | try 210 | { 211 | // try-catch guarantees stack unwinding, so the interprocess objects will be handleded properly, even with thrown exceptions 212 | ProducerProcess(); // now actually start the parent 213 | } 214 | catch (const std::exception &e) 215 | { 216 | std::cerr << "PRODUCER ENCOUNTERED AN ERROR: " << e.what() << '\n'; 217 | } 218 | 219 | otherProcess.join(); 220 | } 221 | else 222 | { 223 | try 224 | { 225 | // try-catch guarantees stack unwinding, so the interprocess objects will be handleded properly, even with thrown exceptions 226 | ConsumerProcess(); 227 | } 228 | catch (const std::exception &e) 229 | { 230 | std::cerr << "CONSUMER ENCOUNTERED AN ERROR: " << e.what() << '\n'; 231 | } 232 | } 233 | 234 | return 0; 235 | } -------------------------------------------------------------------------------- /Networking/src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | set(CMAKE_CXX_STANDARD 17) # using std::filesystem, shared_mutex, all else is c++11 3 | 4 | set(LIB_NAME lib_beastNetworking) 5 | 6 | 7 | # all headers 8 | set(HEADERS_${LIB_NAME} 9 | 10 | args.hpp 11 | 12 | # top level classes 13 | MultiClientSender.hpp 14 | MultiClientReceiver.hpp 15 | listener.hpp 16 | rate_limiter.hpp 17 | 18 | # general utils 19 | UtilsASIO.hpp 20 | 21 | # websocket utility files 22 | http_session.hpp 23 | net.hpp 24 | shared_state.hpp 25 | websocket_session.hpp 26 | ) 27 | 28 | 29 | # all source files 30 | set(SRC_${LIB_NAME} 31 | http_session.cpp 32 | listener.cpp 33 | shared_state.cpp 34 | websocket_session.cpp 35 | rate_limiter.cpp 36 | ) 37 | 38 | # 39 | # SSL SUPPORT REQUIRES OPENSSL TO BE INSTALL: 40 | # sudo apt-get install libssl-dev 41 | # 42 | find_package(OpenSSL REQUIRED) 43 | 44 | find_package(Boost 1.70 REQUIRED COMPONENTS system filesystem) 45 | include_directories(${Boost_INCLUDE_DIRS}) 46 | 47 | if (MSVC) 48 | add_definitions(/bigobj) 49 | endif () 50 | 51 | # set up the library we are building 52 | add_library(${LIB_NAME} STATIC ${HEADERS_${LIB_NAME}} ${SRC_${LIB_NAME}}) 53 | set_target_properties(${LIB_NAME} PROPERTIES LINKER_LANGUAGE CXX) 54 | 55 | target_include_directories(${LIB_NAME} PUBLIC .) 56 | target_include_directories(${LIB_NAME} PUBLIC ${Boost_INCLUDE_DIRS}) 57 | 58 | target_link_libraries(${LIB_NAME} 59 | $<$:atomic> 60 | OpenSSL::SSL 61 | OpenSSL::Crypto 62 | ) 63 | 64 | set_target_properties(${LIB_NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON) 65 | 66 | -------------------------------------------------------------------------------- /Networking/src/MultiClientReceiver.hpp: -------------------------------------------------------------------------------- 1 | // Modified by Keith Rausch 2 | 3 | #ifndef BEASTWEBSERVERFLEXIBLE_MULTI_CLIENT_RECEIVER_HPP 4 | #define BEASTWEBSERVERFLEXIBLE_MULTI_CLIENT_RECEIVER_HPP 5 | 6 | #include "args.hpp" 7 | #include 8 | #include "UtilsASIO.hpp" 9 | #include 10 | #include 11 | #include "listener.hpp" 12 | #include "http_session.hpp" 13 | 14 | 15 | namespace BeastNetworking 16 | { 17 | 18 | 19 | class MultiClientReceiver : public std::enable_shared_from_this 20 | { 21 | 22 | private: 23 | 24 | struct SenderCharacteristics 25 | { 26 | std::string topic; 27 | unsigned short port_insecure; 28 | unsigned short port_secure; 29 | std::uint_fast64_t uniqueHandleID; 30 | uint16_t endianness; 31 | int64_t time; // not all that precise. this isnt ntp afterall 32 | 33 | SenderCharacteristics() : topic(), port_insecure(0), port_secure(0), uniqueHandleID(0), endianness(0), time(0) 34 | { 35 | } 36 | 37 | bool Parse(const std::string &str) 38 | { 39 | bool hadError = false; 40 | 41 | std::smatch matches; 42 | 43 | // topic 44 | std::regex_search(str, matches, std::regex(R"delim(topic:(\w+),)delim")); 45 | topic = (matches.size() == 2) ? std::string(matches[1]) : std::string(""); 46 | hadError |= matches.size() != 2; // THIS IS AN ERROR CONDITION 47 | 48 | // port (insecure) 49 | std::regex_search(str, matches, std::regex(R"delim(port_insecure:(\d+),)delim")); 50 | port_insecure = (unsigned short)(matches.size() == 2) ? std::stoull(matches[1]) : -1; 51 | 52 | // port (secure) 53 | std::regex_search(str, matches, std::regex(R"delim(port_secure:(\d+),)delim")); 54 | port_secure = (unsigned short)(matches.size() == 2) ? std::stoull(matches[1]) : -1; 55 | 56 | // uniqueHandleID 57 | std::regex_search(str, matches, std::regex(R"delim(id:(\d+),)delim")); 58 | uniqueHandleID = (std::uint_fast64_t)(matches.size() == 2) ? std::stoull(matches[1]) : 0; 59 | 60 | // endianness 61 | std::regex_search(str, matches, std::regex(R"delim(endian:(\d+),)delim")); 62 | endianness = (uint16_t)(matches.size() == 2) ? std::stoull(matches[1]) : 0; 63 | 64 | // time 65 | std::regex_search(str, matches, std::regex(R"delim(system_time:(\d+),)delim")); 66 | time = (int64_t)(matches.size() == 2) ? std::stoull(matches[1]) : 0; 67 | 68 | return hadError; 69 | } 70 | }; 71 | 72 | 73 | template 74 | std::string UniqueClientName(const std::string &topic, const EndpointT &endpoint) 75 | { 76 | return "__" + topic + "__" + EndpointToString(endpoint); 77 | } 78 | 79 | bool ConnectToServer(const std::string &topic, boost::asio::ip::tcp::endpoint serverEndpoint) 80 | { 81 | bool created_client = false; 82 | 83 | auto &state = topicStates[topic]; 84 | if ( ! state) 85 | { 86 | std::cout << "BeastNetworking::MultiClientReceiver() - the state for topic \""+topic+"\" is null. you are about to have a very bad time.\n"; 87 | return created_client; 88 | } 89 | auto uniqueClientName = UniqueClientName(topic, serverEndpoint); 90 | 91 | if (args.useSSL) 92 | { 93 | // we have to not already have an active session 94 | std::shared_ptr client; 95 | 96 | std::lock_guard lock(clientsMutex); // leave this locked, we access clients again below 97 | client = clientsSSL[uniqueClientName].lock(); 98 | 99 | if (client) 100 | return created_client; // this session is still alive and kicking, leave it 101 | 102 | // this client does not already exist, we get to create a new one 103 | client = BeastNetworking::make_websocket_session_client(io_context, 104 | ssl_context, 105 | state, 106 | serverEndpoint.address().to_string(), 107 | serverEndpoint.port()); 108 | 109 | if (client) 110 | { 111 | client->RunClient(); 112 | clientsSSL[uniqueClientName] = client; 113 | created_client = true; 114 | } 115 | } 116 | else 117 | { 118 | // we have to not already have an active session 119 | std::shared_ptr client; 120 | 121 | std::lock_guard lock(clientsMutex); // leave this locked, we access clients again below 122 | client = clients[uniqueClientName].lock(); 123 | 124 | if (client) 125 | return created_client; // this session is still alive and kicking, leave it 126 | 127 | // this client does not already exist, we get to create a new one 128 | client = BeastNetworking::make_websocket_session_client(io_context, 129 | state, 130 | serverEndpoint.address().to_string(), 131 | serverEndpoint.port()); 132 | 133 | if (client) 134 | { 135 | client->RunClient(); 136 | clients[uniqueClientName] = client; 137 | created_client = true; 138 | } 139 | } 140 | 141 | return created_client; 142 | } 143 | 144 | 145 | void ProcessBroadcast(const boost::asio::ip::udp::endpoint &endpoint, const void *msgPtr, size_t msgSize) 146 | { 147 | using namespace std::chrono; 148 | int64_t time_ms = duration_cast(system_clock::now().time_since_epoch()).count(); 149 | 150 | std::string msg((char *)msgPtr, msgSize); 151 | std::string endpointString = EndpointToString(endpoint); 152 | if (args.verbose) 153 | std::printf("RECEIVED BROADCAST FROM: %s\n%s\n", endpointString.c_str(), msg.c_str()); 154 | 155 | // search the broadcast for the server's claimed address and port 156 | std::string serverAddress; 157 | std::string serverPort; 158 | SenderCharacteristics characteristics; 159 | bool hadError = characteristics.Parse(msg); 160 | 161 | // default to secure port if available, else fall back to insecure 162 | unsigned short port_to_use = 0; 163 | if (args.useSSL) 164 | { 165 | if (characteristics.port_secure > 0) 166 | { 167 | port_to_use = characteristics.port_secure; 168 | } 169 | } 170 | else 171 | { 172 | if (characteristics.port_insecure > 0) 173 | { 174 | port_to_use = characteristics.port_insecure; 175 | } 176 | } 177 | 178 | hadError |= (port_to_use == 0); 179 | 180 | if (hadError) 181 | return; 182 | 183 | auto topic = characteristics.topic; 184 | // auto serverEndpoint = characteristics.claimedServerAddress; 185 | auto serverEndpoint = boost::asio::ip::tcp::endpoint(endpoint.address(), port_to_use); 186 | if (args.verbose) 187 | std::cout << "delta time between software send & receive (ms)" << time_ms - characteristics.time << std::endl; 188 | 189 | // bind the topic (if we have callbacks for it) 190 | 191 | if (!args.permitLoopback) 192 | { 193 | if (characteristics.uniqueHandleID == uniqueInstanceID && uniqueInstanceID != 0) 194 | { 195 | if (args.verbose) 196 | std::cout << "MultiClientReceiver::ProcessBroadcast() - received matching instance ID's and loopback disabled\n"; 197 | return; 198 | } 199 | } 200 | 201 | // we have to have callbacks for this topic 202 | if (topicStates.count(topic) == 0) 203 | { 204 | if (args.verbose) 205 | std::printf("MultiClientReceiver::ProcessBroadcast() - received a broadcast for topic \"%s\", but no callbacks for it were provided...\n", topic.c_str()); 206 | return; 207 | } 208 | 209 | ConnectToServer(topic, serverEndpoint); 210 | 211 | } 212 | 213 | void on_wait(std::string topic, boost::asio::ip::tcp::endpoint serverEndpoint, std::shared_ptr timer, double period_seconds, const boost::system::error_code &error) 214 | { 215 | if (error) 216 | { 217 | return; 218 | } 219 | 220 | try_connection(topic, serverEndpoint, timer, period_seconds); 221 | } 222 | 223 | void try_connection(std::string topic, boost::asio::ip::tcp::endpoint serverEndpoint, std::shared_ptr timer, double period_seconds) 224 | { 225 | bool created_new_client = ConnectToServer(topic, serverEndpoint); 226 | if (created_new_client && args.verbose) 227 | { 228 | std::cout << "BeastNetworking::try_connection() - creating new connection for topic \"" + topic + "\" at endpoint: " + EndpointToString(serverEndpoint) + "\n"; 229 | } 230 | 231 | timer->expires_after(boost::asio::chrono::milliseconds((size_t)(period_seconds * 1000))); // cancels the timer and resets it 232 | timer->async_wait(boost::beast::bind_front_handler(&MultiClientReceiver::on_wait, shared_from_this(), topic, serverEndpoint, timer, period_seconds)); 233 | } 234 | 235 | public: 236 | 237 | 238 | typedef std::unordered_map> TopicStatesT; 239 | 240 | boost::asio::io_context &io_context; 241 | boost::asio::ssl::context &ssl_context; 242 | TopicStatesT topicStates; 243 | std::unordered_map> clients; // TODO this assumes that we cant get the same topic from two different places 244 | std::unordered_map> clientsSSL; // TODO this assumes that we cant get the same topic from two different places 245 | std::mutex clientsMutex; 246 | std::shared_ptr udpReceiverPtr; 247 | MultiClientReceiverArgs args; 248 | 249 | std::uint_fast64_t uniqueInstanceID; 250 | 251 | template 252 | static std::string EndpointToString(const /*boost::asio::ip::udp::endpoint*/ EndpointT &endpoint) 253 | { 254 | return endpoint.address().to_string() + ":" + std::to_string(endpoint.port()); 255 | } 256 | 257 | MultiClientReceiver(boost::asio::io_context &io_context_in, boost::asio::ssl::context &ssl_context_in, const TopicStatesT &topicStates_in, const MultiClientReceiverArgs &args_in, std::uint_fast64_t uniqueInstanceID_in = 0) 258 | : io_context(io_context_in), ssl_context(ssl_context_in), topicStates(topicStates_in), args(args_in), uniqueInstanceID(uniqueInstanceID_in) 259 | { 260 | } 261 | 262 | void RequestTopics(std::string topic, boost::asio::ip::tcp::endpoint serverEndpoint, double period_seconds) 263 | { 264 | auto timer = std::make_shared(io_context); 265 | if (timer) 266 | boost::asio::post(io_context, boost::beast::bind_front_handler(&MultiClientReceiver::try_connection, shared_from_this(), topic, serverEndpoint, timer, period_seconds)); 267 | } 268 | 269 | void ListenForTopics() 270 | { 271 | utils_asio::UDPReceiver::Callbacks callbacks; 272 | 273 | auto callbackError = [](const boost::asio::ip::udp::endpoint &, boost::system::error_code ec) { std::cout << "CONSUMER-ERROR CALLBACK WAS CALLED: " << ec.message() << ec.value() << std::endl; }; 274 | callbacks.callbackError = callbackError; 275 | 276 | std::weak_ptr weak = shared_from_this(); 277 | auto callbackTimeout = [weak]() { 278 | auto strong = weak.lock(); 279 | if (!strong) return; 280 | std::cout << "MULTICLIENTRECEIVER::CALLBACK_TIMEOUT() - STOPPING IO_CONTEXT\n"; 281 | strong->io_context.stop(); 282 | }; 283 | callbacks.callbackTimeout = callbackTimeout; 284 | 285 | callbacks.callbackRead = std::bind(&MultiClientReceiver::ProcessBroadcast, this /*do not use owning reference*/, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3); 286 | 287 | udpReceiverPtr = std::make_shared(io_context, 288 | args.broadcastRcvPort, 289 | args.maxMessageLength, 290 | args.timeout_seconds, 291 | callbacks); 292 | udpReceiverPtr->run(); 293 | } 294 | 295 | template 296 | void SendAsync(const std::string &topic, const EndpointT &endpoint, void* msgPtr, size_t msgSize, BeastNetworking::shared_state::CompletionHandlerT &&completionHandler = BeastNetworking::shared_state::CompletionHandlerT(), bool force_send=false, size_t max_queue_size=std::numeric_limits::max()) 297 | { 298 | auto uniqueClientName = UniqueClientName(topic, endpoint); 299 | std::lock_guard lock(clientsMutex); 300 | 301 | bool found = false; 302 | 303 | { 304 | auto client = clients[uniqueClientName].lock(); 305 | if (client) 306 | { 307 | client->sendAsync(msgPtr, msgSize, std::move(completionHandler), force_send, max_queue_size); 308 | found = true; 309 | } 310 | } 311 | { 312 | auto client = clientsSSL[uniqueClientName].lock(); 313 | if (client) 314 | { 315 | client->sendAsync(msgPtr, msgSize, std::move(completionHandler), force_send, max_queue_size); 316 | found = true; 317 | } 318 | } 319 | 320 | if ( ! found) 321 | completionHandler(boost::asio::error::operation_aborted, 0, endpoint); 322 | 323 | } 324 | 325 | void SendAsync(void* msgPtr, size_t msgSize, BeastNetworking::shared_state::CompletionHandlerT &&completionHandler = BeastNetworking::shared_state::CompletionHandlerT(), bool force_send=false, size_t max_queue_size=std::numeric_limits::max()) 326 | { 327 | std::lock_guard lock(clientsMutex); 328 | 329 | bool found = false; 330 | 331 | for (auto & pair : clients) 332 | { 333 | auto client = pair.second.lock(); 334 | if (client) 335 | { 336 | client->sendAsync(msgPtr, msgSize, std::move(completionHandler), force_send, max_queue_size); 337 | found = true; 338 | } 339 | } 340 | 341 | for (auto & pair : clientsSSL) 342 | { 343 | auto client = pair.second.lock(); 344 | if (client) 345 | { 346 | client->sendAsync(msgPtr, msgSize, std::move(completionHandler), force_send, max_queue_size); 347 | found = true; 348 | } 349 | } 350 | 351 | if ( ! found) 352 | completionHandler(boost::asio::error::operation_aborted, 0, boost::asio::ip::tcp::endpoint()); 353 | 354 | } 355 | 356 | 357 | template 358 | void SendAsync(const std::string &topic, const EndpointT &endpoint, const std::string &str, bool force_send=false, size_t max_queue_size=std::numeric_limits::max()) 359 | { 360 | std::shared_ptr strPtr = std::make_shared(str); 361 | auto uniqueClientName = UniqueClientName(topic, endpoint); 362 | std::lock_guard lock(clientsMutex); 363 | 364 | { 365 | auto client = clients[uniqueClientName].lock(); 366 | if (client) 367 | client->sendAsync((*strPtr).data(), (*strPtr).length(), [strPtr](boost::beast::error_code, size_t, const boost::asio::ip::tcp::endpoint &){}, force_send, max_queue_size); 368 | } 369 | { 370 | auto client = clientsSSL[uniqueClientName].lock(); 371 | if (client) 372 | client->sendAsync((*strPtr).data(), (*strPtr).length(), [strPtr](boost::beast::error_code, size_t, const boost::asio::ip::tcp::endpoint &){}, force_send, max_queue_size); 373 | } 374 | } 375 | 376 | void SendAsync(const std::string &str, bool force_send=false, size_t max_queue_size=std::numeric_limits::max()) 377 | { 378 | std::shared_ptr strPtr = std::make_shared(str); 379 | std::lock_guard lock(clientsMutex); 380 | 381 | for (auto &pair : clients) 382 | { 383 | auto client = pair.second.lock(); 384 | if (client) 385 | client->sendAsync((*strPtr).data(), (*strPtr).length(), [strPtr](boost::beast::error_code, size_t, const boost::asio::ip::tcp::endpoint &){}, force_send, max_queue_size); 386 | } 387 | 388 | for (auto & pair : clientsSSL) 389 | { 390 | auto client = pair.second.lock(); 391 | if (client) 392 | client->sendAsync((*strPtr).data(), (*strPtr).length(), [strPtr](boost::beast::error_code, size_t, const boost::asio::ip::tcp::endpoint &){}, force_send, max_queue_size); 393 | } 394 | } 395 | 396 | void count_connections(size_t &nConnections_insecure, size_t &nConnections_ssl ) 397 | { 398 | 399 | nConnections_insecure = 0; 400 | nConnections_ssl = 0; 401 | 402 | { // lock guard 403 | std::lock_guard lock(clientsMutex); // leave this locked, we access clients again below 404 | 405 | for (auto & pair : clientsSSL) 406 | { 407 | if (pair.second.lock()) 408 | ++nConnections_ssl; 409 | } 410 | 411 | for (auto & pair : clients) 412 | { 413 | if (pair.second.lock()) 414 | ++nConnections_insecure; 415 | } 416 | 417 | } 418 | } 419 | 420 | }; 421 | 422 | typedef std::shared_ptr sMultiClientReceiver; 423 | } // namespace 424 | 425 | #endif -------------------------------------------------------------------------------- /Networking/src/MultiClientSender.hpp: -------------------------------------------------------------------------------- 1 | // Keith Rausch 2 | 3 | #ifndef BEASTWEBSERVERFLEXIBLE_MULTI_CLIENT_SENDER_HPP 4 | #define BEASTWEBSERVERFLEXIBLE_MULTI_CLIENT_SENDER_HPP 5 | 6 | #include "args.hpp" 7 | #include "UtilsASIO.hpp" 8 | #include "shared_state.hpp" 9 | #include "listener.hpp" 10 | 11 | 12 | namespace BeastNetworking 13 | { 14 | 15 | 16 | struct MultiClientSender 17 | { 18 | 19 | 20 | boost::asio::io_context &ioc; 21 | boost::asio::ssl::context &ssl_context; 22 | std::string topic; // topic name 23 | std::shared_ptr sharedState; // for sending data 24 | unsigned short boundServerPort_insecure; 25 | unsigned short boundServerPort_secure; 26 | MultiClientSenderArgs args; 27 | 28 | std::uint_fast64_t uniqueInstanceID; 29 | 30 | // establish server and get its bound addres and port 31 | MultiClientSender(boost::asio::io_context &ioc_in, boost::asio::ssl::context &ssl_context_in, const std::string &topic_in, const MultiClientSenderArgs &args_in, std::uint_fast64_t uniqueInstanceID_in = 0/*, const std::shared_ptr &rate_tracker_in=nullptr*/) 32 | : ioc(ioc_in), ssl_context(ssl_context_in), topic(topic_in), boundServerPort_insecure(0), boundServerPort_secure(0), args(args_in), uniqueInstanceID(uniqueInstanceID_in) 33 | { 34 | 35 | shared_state::Callbacks callbacks; 36 | 37 | callbacks.callbackWSRead = [](const tcp::endpoint &endpoint, const void *msgPtr, size_t msgSize) { 38 | std::stringstream ss; 39 | ss << endpoint; 40 | std::string endpointString = ss.str(); 41 | 42 | std::string msg(static_cast(msgPtr), msgSize); 43 | 44 | std::printf("PRODUCER-READ - endpoint: %s\n%s\n", endpointString.c_str(), msg.c_str()); 45 | }; 46 | 47 | if (args.verbose) 48 | { 49 | auto to_string = [](const tcp::endpoint &endpoint){ return endpoint.address().to_string() + ":" + std::to_string(endpoint.port());}; 50 | callbacks.callbackUpgrade = [this, to_string](const tcp::endpoint &endpoint) { std::cout << "InterprocessMemPool::MultiClientSender::on_upgrade() - topic: \""+topic+"\", accepting endpoint: " + to_string(endpoint) + "\n"; }; 51 | callbacks.callbackClose = [this, to_string](const tcp::endpoint &endpoint) { std::cout << "InterprocessMemPool::MultiClientSender::on_close() - topic: \""+topic+"\", closing endpoint: " + to_string(endpoint) + "\n"; }; 52 | } 53 | 54 | sharedState = std::make_shared(callbacks); 55 | 56 | // bind to any address, any port (insecure) 57 | if (args.serverBindPort_insecure == args.serverBindPort_secure && args.serverBindPort_secure > 0) 58 | { 59 | tcp::endpoint our_endpoint(net::ip::make_address(args.serverBindAddress), args.serverBindPort_secure); 60 | auto listenerPtr = std::make_shared(ioc, ssl_context, our_endpoint, sharedState, BeastNetworking::Security::BOTH); 61 | if (listenerPtr) 62 | { 63 | listenerPtr->run(); 64 | boundServerPort_insecure = listenerPtr->localEndpoint.port(); 65 | boundServerPort_secure = listenerPtr->localEndpoint.port(); 66 | } 67 | } 68 | else 69 | { 70 | if (args.serverBindPort_insecure > 0) 71 | { 72 | tcp::endpoint our_endpoint(net::ip::make_address(args.serverBindAddress), args.serverBindPort_insecure); 73 | auto listenerPtr = std::make_shared(ioc, ssl_context, our_endpoint, sharedState, BeastNetworking::Security::INSECURE); 74 | if (listenerPtr) 75 | { 76 | listenerPtr->run(); 77 | boundServerPort_insecure = listenerPtr->localEndpoint.port(); 78 | } 79 | } 80 | 81 | // bind to any address, any port (secure) 82 | if (args.serverBindPort_secure > 0) 83 | { 84 | tcp::endpoint our_endpoint(net::ip::make_address(args.serverBindAddress), args.serverBindPort_secure); 85 | auto listenerPtr = std::make_shared(ioc, ssl_context, our_endpoint, sharedState, BeastNetworking::Security::BOTH); 86 | if (listenerPtr) 87 | { 88 | listenerPtr->run(); 89 | boundServerPort_secure = listenerPtr->localEndpoint.port(); 90 | } 91 | } 92 | } 93 | } 94 | 95 | void StartHeartbeat() 96 | { 97 | if (nullptr == sharedState) 98 | { 99 | std::cout << "NOT SENDING BROADCAST, SERVER IS DOWN\n"; 100 | return; 101 | } 102 | 103 | // this is a lambda because we update the time on every send 104 | auto broadcastMsgCreator = [this]() { 105 | using namespace std::chrono; 106 | 107 | std::stringstream ss; 108 | ss << "topic:" << topic 109 | << ",port_insecure:" << boundServerPort_insecure 110 | << ",port_secure:" << boundServerPort_secure 111 | << ",id:" << uniqueInstanceID 112 | << ",endian:" << (uint16_t)1 113 | << ",system_time:" << duration_cast(system_clock::now().time_since_epoch()).count() 114 | << ","; 115 | 116 | 117 | return ss.str(); 118 | }; 119 | 120 | // send broadcast 121 | auto heartbeatPtr = std::make_shared(ioc, 122 | broadcastMsgCreator, 123 | args.broadcastSendPort, 124 | args.broadcastDestination, 125 | args.broadcastReceiverPort, 126 | args.heartbeatPeriod_seconds); 127 | 128 | if (heartbeatPtr) 129 | heartbeatPtr->run(); 130 | } 131 | 132 | void SendAsync(const void * msgPtr, size_t msgSize, shared_state::CompletionHandlerT && completionHandler = shared_state::CompletionHandlerT(), bool force_send=false, size_t max_queue_size = std::numeric_limits::max()) 133 | { 134 | if (sharedState) 135 | sharedState->sendAsync(msgPtr, msgSize, std::forward(completionHandler), force_send, max_queue_size); 136 | } 137 | }; 138 | 139 | typedef std::shared_ptr sMultiClientSender; 140 | 141 | } // namespace 142 | 143 | #endif -------------------------------------------------------------------------------- /Networking/src/UtilsASIO.hpp: -------------------------------------------------------------------------------- 1 | // Keith Rausch 2 | 3 | #ifndef BEASTWEBSERVERFLEXIBLE_UTILS_ASIO_HPP 4 | #define BEASTWEBSERVERFLEXIBLE_UTILS_ASIO_HPP 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace utils_asio 15 | { 16 | 17 | using boost::asio::ip::address; 18 | using boost::asio::ip::tcp; 19 | using boost::asio::ip::udp; 20 | 21 | // 22 | // MUST USE THIS CLASS INSIDE A std::shared_ptr via std::make_shared 23 | // 24 | class UDPReceiver : public std::enable_shared_from_this 25 | { 26 | 27 | udp::socket socket; 28 | udp::endpoint remoteEndpoint; 29 | std::vector recvBuffer; 30 | 31 | boost::asio::steady_timer timeout; 32 | double timeout_period_seconds; // only has millisecond precision 33 | std::atomic timedout; 34 | 35 | public: 36 | struct Callbacks 37 | { 38 | typedef std::function CallbackReadT; 39 | typedef std::function CallbackErrorT; 40 | typedef std::function CallbackTimeoutT; 41 | 42 | CallbackReadT callbackRead; 43 | CallbackErrorT callbackError; 44 | CallbackTimeoutT callbackTimeout; 45 | }; 46 | 47 | Callbacks callbacks; 48 | 49 | explicit UDPReceiver(boost::asio::io_context &io_context, 50 | unsigned short portNumber, 51 | size_t maxMessageLength, 52 | double timeout_period_seconds_in, 53 | const Callbacks &callbacks_in = Callbacks()) 54 | : socket(io_context, udp::endpoint(udp::v4(), portNumber)), 55 | recvBuffer(maxMessageLength), 56 | timeout(io_context), 57 | timeout_period_seconds(timeout_period_seconds_in), 58 | callbacks(callbacks_in) 59 | { 60 | socket.set_option(boost::asio::ip::udp::socket::reuse_address(true)); 61 | // DO NOT CALL start_recieve(); here. shared_from_this() needs a fully constructed 62 | // shared_ptr to work, and calling start_receive here enevitably calls shared_from_this() 63 | // before the constructor is finished 64 | // https://stackoverflow.com/questions/43261673/stdbad-weak-ptr-while-shared-from-this 65 | } 66 | 67 | // DO NOT CALL START IN CONSTRUCTOR. no shared pointer exits yet, enabled_shared_from_this will break 68 | // https://stackoverflow.com/questions/43261673/stdbad-weak-ptr-while-shared-from-this 69 | void run() 70 | { 71 | timedout = false; 72 | timeout.async_wait(boost::beast::bind_front_handler(&UDPReceiver::check_timeout, shared_from_this())); 73 | start_recieve(); 74 | } 75 | 76 | udp::socket & get_socket() 77 | { 78 | return socket; 79 | } 80 | 81 | private: 82 | void on_error(boost::system::error_code ec) 83 | { 84 | if (callbacks.callbackError && !timedout) 85 | callbacks.callbackError(remoteEndpoint, ec); 86 | 87 | // if (callbacks.callbackClose) 88 | // callbacks.callbackClose(remoteEndpoint, ec); 89 | } 90 | 91 | void on_recieve(boost::system::error_code ec, std::size_t bytes_transferred) 92 | { 93 | if (ec) 94 | { 95 | on_error(ec); 96 | return; 97 | } 98 | 99 | if (callbacks.callbackRead) 100 | callbacks.callbackRead(remoteEndpoint, (void *)&*recvBuffer.begin(), bytes_transferred); 101 | 102 | start_recieve(); 103 | } 104 | 105 | void start_recieve() 106 | { 107 | // Set a deadline for the read operation. 108 | timedout = false; 109 | if (timeout_period_seconds <= 0) 110 | timeout.expires_at(boost::asio::steady_timer::time_point::max()); 111 | else 112 | { 113 | size_t timeout_period_ms = timeout_period_seconds * 1000 ; 114 | timeout.expires_after(std::chrono::milliseconds(timeout_period_ms)); // cancels the timer and resets it 115 | } 116 | 117 | socket.async_receive_from(boost::asio::buffer(recvBuffer), 118 | remoteEndpoint, 119 | boost::beast::bind_front_handler(&UDPReceiver::on_recieve, shared_from_this())); 120 | } 121 | 122 | // 123 | // This function is called when the timer expires, OR when it is reset/ cancelled (like with steady_timer::expires_after()) 124 | // 125 | // This handler calls itself. Every time the timer is reset, this handler will get called with an 126 | // error (but we dont care about the error). that means that whenever this function is called, a 127 | // timer either just expired, or was reset and needs to be used to time a new function. It doesnt 128 | // actually matter which scenario called this handler to fire, we just need to know how long to sleep 129 | // the timer for, and if the callbackTimeout() member should be called 130 | // 131 | void check_timeout(boost::system::error_code ec) 132 | { 133 | if (ec && ec != boost::asio::error::operation_aborted) 134 | on_error(ec); 135 | 136 | // Check whether the deadline has passed. We compare the deadline against 137 | // the current time since a new asynchronous operation may have moved the 138 | // deadline before this actor had a chance to run. 139 | if (timeout.expiry() <= boost::asio::steady_timer::clock_type::now()) 140 | { 141 | timedout = true; 142 | 143 | if (callbacks.callbackTimeout) 144 | callbacks.callbackTimeout(); 145 | 146 | // The deadline has passed. The socket is closed so that any outstanding 147 | // asynchronous operations are cancelled. 148 | socket.close(); 149 | 150 | // There is no longer an active deadline. The expiry is set to the 151 | // maximum time point so that the actor takes no action until a new 152 | // deadline is set. 153 | timeout.expires_at(boost::asio::steady_timer::time_point::max()); 154 | } 155 | 156 | // Put the actor back to sleep. 157 | timeout.async_wait(boost::beast::bind_front_handler(&UDPReceiver::check_timeout, shared_from_this())); 158 | } 159 | }; 160 | 161 | /* 162 | static void SendBroadcast(boost::asio::io_service &io_service, const std::string &msg, unsigned short portSender, const std::string &destination, unsigned short portReceiver) 163 | { 164 | namespace ba = boost::asio; 165 | namespace bs = boost::system; 166 | using ba::ip::udp; 167 | 168 | udp::socket sock(io_service, udp::endpoint(udp::v4(), portSender)); 169 | sock.set_option(ba::ip::udp::socket::reuse_address(true)); 170 | sock.set_option(ba::socket_base::broadcast(true)); 171 | 172 | udp::endpoint sender_endpoint(ba::ip::address_v4::from_string(destination), portReceiver); 173 | 174 | auto handler = [](const boost::system::error_code &, std::size_t) {}; 175 | sock.async_send_to(boost::asio::buffer(msg.c_str(), msg.length()), sender_endpoint, handler); 176 | } 177 | */ 178 | 179 | // 180 | // MUST USE THIS CLASS INSIDE A std::shared_ptr via std::make_shared 181 | // 182 | class Heartbeat : public std::enable_shared_from_this 183 | { 184 | // namespace ba = boost::asio; 185 | // namespace bs = boost::system; 186 | // using boost::asio::ip::udp; 187 | 188 | boost::asio::io_context &io_context; 189 | udp::socket socket; 190 | udp::endpoint receiver_endpoint; 191 | boost::asio::steady_timer timer; 192 | float period_seconds; 193 | 194 | typedef std::function MsgCreatorT; 195 | MsgCreatorT msgCreator; 196 | std::string msg; 197 | bool running; 198 | 199 | public: 200 | Heartbeat(boost::asio::io_context &io_context_in, const MsgCreatorT &msgCreator_in, unsigned short portSender, const std::string &destination, unsigned short portReceiver, float period_seconds_in) 201 | : io_context(io_context_in), 202 | socket(io_context, udp::v4()), 203 | receiver_endpoint(boost::asio::ip::address_v4::from_string(destination), portReceiver), 204 | timer(io_context), 205 | period_seconds(period_seconds_in), 206 | msgCreator(msgCreator_in), 207 | msg(), 208 | running(false) 209 | { 210 | socket.set_option(udp::socket::reuse_address(true)); 211 | socket.set_option(boost::asio::socket_base::broadcast(true)); 212 | socket.bind(udp::endpoint(udp::v4(), portSender)); 213 | } 214 | 215 | void run() 216 | { 217 | // calling run multiple times could stomp on the message before its sent 218 | if (running) 219 | return; 220 | 221 | running = true; 222 | start_send(); 223 | } 224 | 225 | private: 226 | void on_wait(const boost::system::error_code &error) 227 | { 228 | if (error) 229 | { 230 | return; 231 | } 232 | 233 | start_send(); 234 | } 235 | 236 | void on_send(const boost::system::error_code &/*error*/, std::size_t /*nBytes*/) 237 | { 238 | timer.expires_after(boost::asio::chrono::milliseconds((size_t)(period_seconds * 1000))); // cancels the timer and resets it 239 | timer.async_wait(boost::beast::bind_front_handler(&Heartbeat::on_wait, shared_from_this())); 240 | } 241 | 242 | void start_send() 243 | { 244 | if (msgCreator) 245 | msg = msgCreator(); 246 | 247 | socket.async_send_to(boost::asio::buffer(msg.c_str(), msg.length()), 248 | receiver_endpoint, 249 | boost::beast::bind_front_handler(&Heartbeat::on_send, shared_from_this())); 250 | // when the socket is destroped, all pending ops are cancelled, os msg cant go out of scope before its used 251 | } 252 | }; 253 | 254 | // 255 | // A utility class for getting an io_context and then protecting it with an executor_work_guard. 256 | // This is useful because calling io_context::run() without any pending handlers will shutdown the 257 | // executor inside io_context immediately. Furthermore, When the io_context runs out of handlers to 258 | // run, it will shut down the execuctor, and the calls to ::run() will stop blocking. This class keeps 259 | // that from happening (by using boost::asio::executor_work_guard) 260 | // 261 | // This class goes one step further and sets up handlers for SIGINT and SIGTERM and shutsdown the io_context 262 | // when they are received. This means that we can cntrl+c our program and it will shut down gracefully. sick. 263 | // 264 | class GuardedContext 265 | { 266 | boost::asio::io_context io_context; 267 | 268 | using work_guard_type = boost::asio::executor_work_guard; 269 | work_guard_type work_guard; // keeps io_context::run() from returning if all jobs are finished 270 | 271 | std::vector threads; // threads that call io_context::run() 272 | 273 | boost::asio::signal_set signals; 274 | std::mutex mutex; 275 | 276 | typedef std::function SignalCallbackT; 277 | typedef std::function ShutdownCallbackT; 278 | SignalCallbackT signalCallback; 279 | ShutdownCallbackT shutdownCallback; 280 | 281 | void on_sigevent(const boost::system::error_code &error, int signal_number) 282 | { 283 | if (error) 284 | std::cout << "GuardedContext::on_sigevent() - CAUGHT SIGEVENT (" + std::to_string(signal_number) + "). BUT HAD ERROR: " << error << std::endl; 285 | 286 | if (error) 287 | return; 288 | 289 | std::cout << "GuardedContext::on_sigevent() - CAUGHT SIGEVENT (" + std::to_string(signal_number) + "). STOPPING IOCONTEXT.\n"; 290 | 291 | io_context.stop(); 292 | // do not call Shutdown() here. 293 | // that would mean that the thread handling this handler would be trying to join itself 294 | // and that would deadlock. 295 | // https://stackoverflow.com/questions/64039374/c-terminate-called-after-throwing-an-instance-of-stdsystem-error-what-r 296 | 297 | if (signalCallback) 298 | { 299 | signalCallback(error, signal_number); 300 | signalCallback = 0; // good practice, i think 301 | } 302 | } 303 | 304 | public: 305 | GuardedContext() 306 | : io_context(), work_guard(io_context.get_executor()), signals(io_context) 307 | { 308 | } 309 | 310 | GuardedContext(size_t nThreads) 311 | : io_context(), work_guard(io_context.get_executor()), signals(io_context) 312 | { 313 | Run(nThreads); 314 | } 315 | 316 | void SetSignalCallback(const SignalCallbackT & signalCallback_in) 317 | { 318 | signalCallback = signalCallback_in; 319 | } 320 | 321 | void SetShutdownCallback(const ShutdownCallbackT & shutdownCallback_in) 322 | { 323 | shutdownCallback = shutdownCallback_in; 324 | } 325 | 326 | ~GuardedContext() 327 | { 328 | Shutdown(); 329 | } 330 | 331 | boost::asio::io_context &GetIOContext() 332 | { 333 | return io_context; 334 | } 335 | 336 | void Run(size_t nThreads) 337 | { 338 | // Do not run if already called. 339 | // This is a design choice since caling Run() multiple times wouldnt truly hurt much. 340 | // This function could be so much more complicated. 341 | if (threads.size() > 0) 342 | { 343 | std::cout << "GuardedContext::Run() - YOU MAY NOT CALL RUN MULTIPLE TIMES\n"; 344 | return; 345 | } 346 | 347 | // Run the I/O service on the requested number of threads 348 | threads.reserve(nThreads); 349 | for (size_t i = 0; i < nThreads; ++i) 350 | threads.emplace_back([this] { io_context.run(); }); 351 | 352 | // set handlers for sigint, sigterm, etccc 353 | 354 | // Construct a signal set registered for process termination. 355 | // Start an asynchronous wait for one of the signals to occur. 356 | signals.add(SIGINT); 357 | signals.add(SIGTERM); 358 | signals.async_wait(boost::beast::bind_front_handler(&GuardedContext::on_sigevent, this)); 359 | } 360 | 361 | void Shutdown() 362 | { 363 | std::lock_guard guard(mutex); 364 | 365 | if (shutdownCallback) 366 | { 367 | shutdownCallback(); 368 | shutdownCallback = 0; // prevent it from being called twice 369 | } 370 | 371 | signals.cancel(); // cancel the callbacks. this frees up the scope of the lambdas. do this before stopping the service 372 | 373 | if (!io_context.stopped()) 374 | io_context.stop(); // required because we have a executor_work_guard 375 | 376 | // Block until all the threads exit 377 | for (auto &thread : threads) 378 | { 379 | if (thread.joinable()) 380 | thread.join(); 381 | } 382 | } 383 | }; 384 | 385 | } // namespace utils_asio 386 | 387 | #endif -------------------------------------------------------------------------------- /Networking/src/args.hpp: -------------------------------------------------------------------------------- 1 | 2 | #ifndef BEASTWEBSERVERFLEXIBLE_ARGS_HPP 3 | #define BEASTWEBSERVERFLEXIBLE_ARGS_HPP 4 | 5 | #include 6 | 7 | namespace BeastNetworking 8 | { 9 | 10 | struct MultiClientReceiverArgs 11 | { 12 | unsigned short broadcastRcvPort = 8081; 13 | size_t maxMessageLength = 500; 14 | double timeout_seconds = 3; 15 | bool permitLoopback = true; 16 | bool verbose = false; 17 | bool useSSL = true; 18 | }; 19 | 20 | 21 | struct MultiClientSenderArgs 22 | { 23 | std::string broadcastDestination = "255.255.255.255"; // send broadcast to all listeners 24 | unsigned short broadcastSendPort = 0; // send broadcast on any port 25 | unsigned short broadcastReceiverPort = 8081; // change me - broadcast receiver port 26 | float heartbeatPeriod_seconds = 0.5; // seconds between heartbeats 27 | 28 | std::string serverBindAddress = "0.0.0.0"; // bind server to any address 29 | unsigned short serverBindPort_insecure = 0; // port for http and ws 30 | unsigned short serverBindPort_secure = 0; // port for https and wss 31 | bool verbose = false; 32 | }; 33 | 34 | 35 | 36 | } 37 | 38 | #endif -------------------------------------------------------------------------------- /Networking/src/client.hpp: -------------------------------------------------------------------------------- 1 | 2 | #ifndef BEASTWEBSERVERFLEXIBLE_CLIENT_HPP 3 | #define BEASTWEBSERVERFLEXIBLE_CLIENT_HPP 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | 10 | namespace BeastNetworking 11 | { 12 | 13 | template 14 | class TCPClientBase 15 | { 16 | public: 17 | 18 | struct Callbacks 19 | { 20 | typedef boost::asio::ip::tcp::endpoint endpointT; 21 | typedef std::function CallbackReadT; 22 | typedef std::function CallbackSocketAcceptT; 23 | typedef std::function CallbackSocketCloseT; 24 | typedef std::function CallbackErrorT; 25 | 26 | CallbackReadT callbackRead; 27 | CallbackSocketAcceptT callbackAccept; 28 | CallbackSocketCloseT callbackClose; 29 | CallbackErrorT callbackError; 30 | }; 31 | 32 | Callbacks callbacks; 33 | 34 | Derived& derived() 35 | { 36 | return static_cast(*this); 37 | } 38 | 39 | TCPClientBase(boost::asio::io_context& io_context_in, const std::string server_address_in, uint16_t port_in, size_t max_message_length, const Callbacks &callbacks_in) 40 | : callbacks(callbacks_in), 41 | resolver_(io_context_in), 42 | server_address(server_address_in), 43 | port(port_in), 44 | connected_(false), 45 | io_context_(io_context_in), 46 | // socket_(boost::asio::make_strand(io_context_in), context), 47 | read_buffer(max_message_length) 48 | { 49 | } 50 | 51 | ~TCPClientBase() 52 | { 53 | handle_close(); 54 | } 55 | 56 | void write(const std::string& msg) 57 | { 58 | // rely on stranding to add this message to the write queue so we can remain lock-free 59 | boost::asio::post(derived().socket().get_executor(), boost::beast::bind_front_handler(&TCPClientBase::schedule_write, derived().shared_from_this(), msg)); 60 | } 61 | 62 | void close() 63 | { 64 | boost::asio::post(derived().socket().get_executor(), boost::beast::bind_front_handler( &TCPClientBase::handle_close, derived().shared_from_this())); 65 | } 66 | 67 | bool connected() 68 | { 69 | return connected_; 70 | } 71 | 72 | void run() 73 | { 74 | // Look up the domain name 75 | resolver_.async_resolve( server_address, std::to_string(port), 76 | boost::beast::bind_front_handler( &TCPClientBase::on_resolve, derived().shared_from_this())); 77 | } 78 | 79 | void on_finish_connect_or_handshake(boost::beast::error_code ec) 80 | { 81 | 82 | if (! ec) 83 | { 84 | connected_ = true; 85 | 86 | if (callbacks.callbackAccept) 87 | { 88 | callbacks.callbackAccept(endpoint); 89 | } 90 | 91 | do_read(); 92 | } 93 | else 94 | { 95 | handle_error(ec); 96 | handle_close(); 97 | } 98 | } 99 | 100 | 101 | 102 | // void wait() 103 | // { 104 | // boost::system::error_code ec; 105 | // socket_.wait(boost::asio::ip::tcp::socket::wait_error, ec); 106 | // socket_.wait(boost::asio::ip::tcp::socket::wait_write, ec); 107 | // socket_.wait(boost::asio::ip::tcp::socket::wait_read, ec); 108 | // std::cout << ec << "\n"; 109 | // } 110 | 111 | protected: 112 | 113 | void on_resolve(boost::beast::error_code ec, boost::asio::ip::tcp::resolver::results_type results) 114 | { 115 | if (ec) 116 | return handle_error(ec); 117 | 118 | endpoint = results->endpoint(); 119 | // Make the connection on the IP address we get from a lookup 120 | 121 | derived().lowest_layer().close(); // reuse error var 122 | 123 | derived().lowest_layer().async_connect( 124 | endpoint, 125 | boost::beast::bind_front_handler( &Derived::on_connect, derived().shared_from_this())); 126 | } 127 | 128 | 129 | 130 | void do_read() 131 | { 132 | derived().socket().async_read_some( 133 | // boost::asio::async_read_some(socket_, 134 | boost::asio::buffer(read_buffer.data(), read_buffer.size()), 135 | boost::beast::bind_front_handler(&TCPClientBase::on_read, derived().shared_from_this())); 136 | } 137 | 138 | void schedule_write(const std::string &msg) 139 | { 140 | bool write_in_progress = !write_msgs_.empty(); 141 | write_msgs_.push_back(msg); 142 | if (!write_in_progress) 143 | { 144 | do_write(); 145 | } 146 | } 147 | 148 | void do_write() 149 | { 150 | boost::asio::async_write(derived().socket(), 151 | boost::asio::buffer(write_msgs_.front().data(), write_msgs_.front().length()), 152 | boost::beast::bind_front_handler(&TCPClientBase::on_write, derived().shared_from_this())); 153 | } 154 | 155 | void on_write(boost::system::error_code ec, std::size_t /*length*/) 156 | { 157 | if (!ec) 158 | { 159 | write_msgs_.pop_front(); 160 | if (!write_msgs_.empty()) 161 | { 162 | do_write(); 163 | } 164 | } 165 | else 166 | { 167 | write_msgs_.clear(); // probably a mistake..... NOTE lookie here when you have errors in the future 168 | handle_error(ec); 169 | handle_close(); 170 | } 171 | } 172 | 173 | void on_read(boost::system::error_code ec, std::size_t length) 174 | { 175 | if (!ec) 176 | { 177 | if (callbacks.callbackRead) 178 | { 179 | callbacks.callbackRead(endpoint, read_buffer.data(), length); 180 | } 181 | do_read(); 182 | } 183 | else 184 | { 185 | handle_error(ec); 186 | handle_close(); 187 | } 188 | } 189 | 190 | void handle_error(const boost::system::error_code &ec) 191 | { 192 | 193 | if (callbacks.callbackError) 194 | { 195 | callbacks.callbackError(endpoint, ec); 196 | } 197 | } 198 | 199 | void handle_close() 200 | { 201 | if (connected_) 202 | { 203 | boost::system::error_code ec; 204 | derived().lowest_layer().close(ec); 205 | 206 | if (callbacks.callbackClose) 207 | { 208 | callbacks.callbackClose(endpoint); 209 | } 210 | 211 | connected_ = false; 212 | } 213 | } 214 | 215 | protected: 216 | boost::asio::ip::tcp::resolver resolver_; 217 | std::string server_address; 218 | uint16_t port; 219 | boost::asio::ip::tcp::endpoint endpoint; 220 | std::atomic connected_; 221 | boost::asio::io_context& io_context_; 222 | // boost::asio::ip::tcp::socket socket_; 223 | // boost::asio::ssl::stream socket_; 224 | std::vector read_buffer; 225 | std::deque write_msgs_; 226 | }; 227 | 228 | 229 | 230 | // Handles an plain TPL connection 231 | class PlainTCPClient 232 | : public TCPClientBase 233 | , public std::enable_shared_from_this 234 | { 235 | boost::asio::ip::tcp::socket socket_; 236 | // boost::beast::tcp_stream socket_; 237 | 238 | public: 239 | 240 | explicit 241 | PlainTCPClient(boost::asio::io_context& io_context_in, const std::string server_address_in, uint16_t port_in, size_t max_message_length, const Callbacks &callbacks_in) 242 | : TCPClientBase(io_context_in, server_address_in, port_in, max_message_length, callbacks_in), 243 | socket_(boost::asio::make_strand(io_context_in)) 244 | { 245 | } 246 | 247 | // Called by the base class 248 | boost::asio::ip::tcp::socket& socket() 249 | { 250 | return socket_; 251 | } 252 | 253 | boost::asio::ip::tcp::socket& lowest_layer() 254 | { 255 | return socket_; 256 | } 257 | 258 | 259 | void on_connect(boost::beast::error_code ec) 260 | { 261 | on_finish_connect_or_handshake(ec); 262 | } 263 | 264 | }; 265 | 266 | 267 | 268 | 269 | // Handles an SSL TPL connection 270 | class SSLTCPClient 271 | : public TCPClientBase 272 | , public std::enable_shared_from_this 273 | { 274 | boost::asio::ssl::stream socket_; 275 | 276 | public: 277 | 278 | explicit 279 | SSLTCPClient(boost::asio::io_context& io_context_in, boost::asio::ssl::context& ssl_context, const std::string server_address_in, uint16_t port_in, size_t max_message_length, const Callbacks &callbacks_in) 280 | : TCPClientBase(io_context_in, server_address_in, port_in, max_message_length, callbacks_in), 281 | socket_(boost::asio::make_strand(io_context_in), ssl_context) 282 | { 283 | } 284 | 285 | bool set_tlsext_host_name() 286 | { 287 | // and this https://stackoverflow.com/questions/59224873/boost-beast-handshake-sslv3-alert-handshake-failure-error 288 | // see this https://github.com/boostorg/beast/blob/master/example/http/client/sync-ssl/http_client_sync_ssl.cpp 289 | bool had_error = (! SSL_set_tlsext_host_name(socket_.native_handle(), server_address.c_str())); 290 | 291 | // beast::error_code ec{static_cast(::ERR_get_error()), net::error::get_ssl_category()}; 292 | 293 | return had_error; 294 | } 295 | 296 | // Called by the base class 297 | boost::asio::ssl::stream& socket() 298 | { 299 | return socket_; 300 | } 301 | 302 | decltype(socket_.lowest_layer()) lowest_layer() 303 | { 304 | return socket_.lowest_layer(); 305 | } 306 | 307 | 308 | void on_connect(boost::beast::error_code ec) 309 | { 310 | if (! ec) 311 | { 312 | socket_.async_handshake(boost::asio::ssl::stream_base::client, 313 | boost::beast::bind_front_handler( &TCPClientBase::on_finish_connect_or_handshake, shared_from_this())); 314 | } 315 | else 316 | { 317 | handle_error(ec); 318 | handle_close(); 319 | } 320 | } 321 | 322 | }; 323 | 324 | 325 | 326 | 327 | 328 | template class TCPClientBase; 329 | template class TCPClientBase; 330 | 331 | } 332 | 333 | #endif -------------------------------------------------------------------------------- /Networking/src/http_session.cpp: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include "http_session.hpp" 4 | 5 | namespace BeastNetworking 6 | { 7 | 8 | 9 | 10 | // Access the derived class, this is part of 11 | // the Curiously Recurring Template Pattern idiom. 12 | template 13 | Derived& http_session::derived() 14 | { 15 | return static_cast(*this); 16 | } 17 | 18 | // Construct the session 19 | template 20 | http_session::http_session( 21 | beast::flat_buffer buffer, 22 | std::shared_ptr const& state, 23 | // std::shared_ptr const& doc_root, 24 | tcp::endpoint remote_endpoint_in 25 | ) 26 | : state_(state), 27 | // doc_root_(doc_root), 28 | endpoint(remote_endpoint_in) 29 | , queue_(*this) 30 | , buffer_(std::move(buffer)) 31 | , queue_size_(0) 32 | { 33 | } 34 | 35 | template 36 | void http_session::do_read() 37 | { 38 | // shoving this branch here so that we can natively support raw tcp sockets instead of 39 | // forcing people to use http. sure i should have made another class, but i woke up at 3am 40 | // and we need this 41 | if (state_ && state_->server_is_tcp_instead_of_http()) 42 | { 43 | size_t header_size = state_->nonhttp_header_size(); 44 | if (nonhttp_buffer_size_ < header_size) 45 | { 46 | nonhttp_buffer_.resize(header_size); 47 | nonhttp_buffer_size_ = nonhttp_buffer_.size(); 48 | } 49 | 50 | boost::asio::async_read(derived().socket(), 51 | boost::asio::buffer(nonhttp_buffer_.data(), header_size), 52 | beast::bind_front_handler( 53 | &http_session::on_nonhttp_read_header, derived().shared_from_this())); 54 | } 55 | else 56 | { 57 | // Construct a new parser for each message 58 | parser_.emplace(); 59 | 60 | // Apply a reasonable limit to the allowed size 61 | // of the body in bytes to prevent abuse. 62 | parser_->body_limit(10000); 63 | 64 | // Set the timeout. 65 | // beast::get_lowest_layer(derived().stream()).expires_after(std::chrono::seconds(30)); 66 | 67 | // Read a request using the parser-oriented interface 68 | http::async_read( 69 | derived().stream(), 70 | buffer_, 71 | *parser_, 72 | beast::bind_front_handler( 73 | &http_session::on_read, 74 | derived().shared_from_this())); 75 | } 76 | } 77 | 78 | template 79 | void http_session::on_nonhttp_read_header(const boost::system::error_code& error, size_t /*bytes_transferred*/) 80 | { 81 | if (error) 82 | { 83 | on_error(error); 84 | return; 85 | } 86 | 87 | if (state_ && state_->callbacks.callbackGetBodyLengthFromNonHTTPHeader) 88 | { 89 | size_t body_length = state_->callbacks.callbackGetBodyLengthFromNonHTTPHeader((void*)nonhttp_buffer_.data(), nonhttp_buffer_.size()); 90 | size_t header_length = state_->nonhttp_header_size(); 91 | size_t total_msg_length = body_length + header_length; 92 | 93 | if (nonhttp_buffer_size_ < total_msg_length) 94 | { 95 | nonhttp_buffer_.resize(total_msg_length); 96 | nonhttp_buffer_size_ = nonhttp_buffer_.size(); 97 | } 98 | 99 | boost::asio::async_read(derived().socket(), 100 | boost::asio::buffer(nonhttp_buffer_.data()+header_length, body_length), 101 | beast::bind_front_handler(&http_session::on_nonhttp_read_body, derived().shared_from_this(), header_length, body_length)); 102 | } 103 | } 104 | 105 | template 106 | void http_session::on_nonhttp_read_body(size_t header_size, size_t body_size, const boost::system::error_code& error, size_t bytes_transferred) 107 | { 108 | bool had_error = false; 109 | size_t expected_byte_count = header_size+body_size; 110 | 111 | had_error |= (body_size != bytes_transferred); 112 | had_error |= (expected_byte_count > nonhttp_buffer_.size()); 113 | 114 | if (error || had_error) 115 | { 116 | on_error(error); 117 | return; 118 | } 119 | 120 | if (state_) 121 | state_->on_http_read(endpoint, nonhttp_buffer_.data(), expected_byte_count); 122 | do_read(); 123 | } 124 | 125 | 126 | 127 | 128 | template 129 | void http_session::on_read(beast::error_code ec, std::size_t bytes_transferred) 130 | { 131 | boost::ignore_unused(bytes_transferred); 132 | 133 | // This means they closed the connection 134 | if(ec == http::error::end_of_stream) 135 | return derived().do_eof(); 136 | 137 | if(ec) 138 | return fail(ec, "on_read"); 139 | 140 | // See if it is a WebSocket Upgrade 141 | if(websocket::is_upgrade(parser_->get())) 142 | { 143 | // Disable the timeout. 144 | // The websocket::stream uses its own timeout settings. 145 | beast::get_lowest_layer(derived().stream()).expires_never(); 146 | 147 | // Create a websocket session, transferring ownership 148 | // of both the socket and the HTTP request. 149 | return make_websocket_session_server( 150 | derived().release_stream(), state_, endpoint, 151 | parser_->release()); 152 | } 153 | 154 | // Send the response 155 | auto on_post = [this](const void* ptr, size_t count) 156 | { 157 | if (state_) 158 | state_->on_http_read(endpoint, ptr, count); 159 | }; 160 | 161 | handle_request(state_->doc_root(), parser_->release(), queue_, on_post); 162 | 163 | // If we aren't at the queue limit, try to pipeline another request 164 | if(! queue_.is_full()) 165 | do_read(); 166 | } 167 | 168 | template 169 | void http_session::on_write(bool close, beast::error_code ec, std::size_t bytes_transferred) 170 | { 171 | boost::ignore_unused(bytes_transferred); 172 | 173 | if(ec) 174 | return fail(ec, "on_write"); 175 | 176 | if(close) 177 | { 178 | // This means we should close the connection, usually because 179 | // the response indicated the "Connection: close" semantic. 180 | return derived().do_eof(); 181 | } 182 | 183 | // Inform the queue that a write completed 184 | if(queue_.on_write()) 185 | { 186 | // Read another request 187 | do_read(); 188 | } 189 | } 190 | 191 | template 192 | void http_session::sendAsync(const void* msgPtr, size_t msgSize, shared_state::CompletionHandlerT completionHandler, bool force_send, size_t max_queue_size) 193 | { 194 | // Post our work to the strand, this ensures 195 | // that the members of `this` will not be 196 | // accessed concurrently. 197 | 198 | net::post( 199 | derived().stream().get_executor(), 200 | beast::bind_front_handler(&http_session::on_send, derived().shared_from_this(), msgPtr, msgSize, std::move(completionHandler), force_send, max_queue_size)); 201 | } 202 | 203 | template 204 | void http_session::on_send(const void* msgPtr, size_t msgSize, shared_state::CompletionHandlerT &&completionHandler, bool force_send, size_t max_queue_size) 205 | { 206 | // update hypothetical send stats here 207 | // rate_enforcer.update_hypothetical_rate(msgSize); 208 | 209 | // if we have more messages in the queue than allowed, we need to start blowing them away 210 | // BUT we cant blow away messages marked as 'must_send' nomatter what 211 | // se we are basically doing our best to honor the max_queue_size, but will exceed it if we need to 212 | // in order to not drop important messages 213 | max_queue_size = std::max(max_queue_size, (size_t)2); // the 1st element is being sent right now, cant replace it 214 | 215 | for (size_t i = 2; i < send_queue_.size() && send_queue_.size() >= max_queue_size ; ++i) 216 | { 217 | auto & msg = send_queue_[i]; 218 | auto & callback = std::get<2>(msg); 219 | auto force = std::get<3>(msg); 220 | if ( ! force) 221 | { 222 | if (callback) 223 | { 224 | callback(boost::asio::error::operation_aborted, 0, endpoint); 225 | callback = shared_state::CompletionHandlerT(); // not sure if this avoids move-sideeffects later 226 | } 227 | 228 | // queue_.pop_back(); 229 | send_queue_.erase(send_queue_.begin() + i); 230 | // queue size recorded further down 231 | --i; 232 | } 233 | } 234 | 235 | 236 | send_queue_.emplace_back(msgPtr, msgSize, std::move(completionHandler), force_send); 237 | queue_size_ = send_queue_.size(); 238 | 239 | // Are we already writing? 240 | if (send_queue_.size() > 1) 241 | return; 242 | 243 | bool may_send = true;//rate_enforcer.should_send(msgSize) || force_send; 244 | may_send &= (msgPtr!=nullptr && msgSize>0); 245 | 246 | if ( may_send && queue_.size() == 0) 247 | { 248 | // We are not currently writing, so send this immediately 249 | derived().socket().async_send( 250 | boost::asio::buffer(msgPtr, msgSize), 251 | beast::bind_front_handler(&http_session::on_write2, derived().shared_from_this())); 252 | } 253 | else 254 | { 255 | net::post(derived().stream().get_executor(), 256 | beast::bind_front_handler(&http_session::on_write2, derived().shared_from_this(), boost::asio::error::operation_aborted, 0) 257 | ); 258 | } 259 | } 260 | 261 | template 262 | void http_session::on_write2( beast::error_code ec, std::size_t bytes_transferred) 263 | { 264 | // call the user's completion handler 265 | auto& handler = std::get<2>(send_queue_.front()); 266 | if (handler) 267 | handler(ec, bytes_transferred, endpoint); 268 | 269 | // Remove the string from the queue 270 | send_queue_.pop_front(); 271 | queue_size_ = send_queue_.size(); 272 | 273 | // Handle the error, if any 274 | if (ec) 275 | { 276 | on_error(ec); 277 | } 278 | 279 | // Send the next message if any 280 | if (!send_queue_.empty()) 281 | { 282 | auto &next = send_queue_.front(); 283 | const void *msgPtr = std::get<0>(next); 284 | size_t msgSize = std::get<1>(next); 285 | bool force = std::get<3>(next); 286 | 287 | bool may_send = true;//rate_enforcer.should_send(msgSize) || force; 288 | may_send &= (msgPtr!=nullptr && msgSize>0); 289 | 290 | if (may_send && queue_.size() == 0) 291 | { 292 | derived().socket().async_send( 293 | boost::asio::buffer(msgPtr, msgSize), 294 | beast::bind_front_handler(&http_session::on_write2, derived().shared_from_this())); 295 | } 296 | else 297 | { 298 | net::post(derived().stream().get_executor(), 299 | beast::bind_front_handler(&http_session::on_write2, derived().shared_from_this(), boost::asio::error::operation_aborted, 0) 300 | ); 301 | } 302 | } 303 | } 304 | 305 | 306 | template 307 | void http_session::on_error(beast::error_code ec) 308 | { 309 | // Don't report these 310 | if (ec == net::error::operation_aborted || ec == websocket::error::closed) 311 | return; 312 | 313 | state_->on_error(endpoint, ec); 314 | } 315 | 316 | 317 | template class http_session; 318 | template class http_session; 319 | 320 | //------------------------------------------------------------------------------ 321 | 322 | 323 | plain_http_session::plain_http_session( 324 | beast::tcp_stream&& stream, 325 | beast::flat_buffer&& buffer, 326 | std::shared_ptr const& state 327 | //std::shared_ptr const& doc_root 328 | ) 329 | : http_session( 330 | std::move(buffer), 331 | state, 332 | stream.socket().remote_endpoint()) 333 | , stream_(std::move(stream)) 334 | { 335 | } 336 | 337 | plain_http_session::~plain_http_session() 338 | { 339 | state_->leave(this); 340 | } 341 | 342 | // Start the session 343 | void plain_http_session::run() 344 | { 345 | state_->join(this); // join the shared state here now that we support tcp comms 346 | this->do_read(); 347 | } 348 | 349 | // Called by the base class 350 | beast::tcp_stream& plain_http_session::stream() 351 | { 352 | return stream_; 353 | } 354 | 355 | boost::asio::ip::tcp::socket& plain_http_session::socket() 356 | { 357 | return stream_.socket(); 358 | } 359 | 360 | // Called by the base class 361 | beast::tcp_stream plain_http_session::release_stream() 362 | { 363 | return std::move(stream_); 364 | } 365 | 366 | // Called by the base class 367 | void plain_http_session::do_eof() 368 | { 369 | // Send a TCP shutdown 370 | beast::error_code ec; 371 | stream_.socket().shutdown(tcp::socket::shutdown_send, ec); 372 | 373 | // At this point the connection is closed gracefully 374 | } 375 | 376 | 377 | //------------------------------------------------------------------------------ 378 | 379 | 380 | ssl_http_session::ssl_http_session( 381 | beast::tcp_stream&& stream, 382 | ssl::context& ctx, 383 | beast::flat_buffer&& buffer, 384 | std::shared_ptr const& state 385 | //std::shared_ptr const& doc_root 386 | ) 387 | : http_session( 388 | std::move(buffer), 389 | state, 390 | stream.socket().remote_endpoint()) 391 | , stream_(std::move(stream), ctx) 392 | { 393 | } 394 | 395 | ssl_http_session::~ssl_http_session() 396 | { 397 | state_->leave(this); 398 | } 399 | 400 | // Start the session 401 | void ssl_http_session::run() 402 | { 403 | // Set the timeout. 404 | // beast::get_lowest_layer(stream_).expires_after(std::chrono::seconds(30)); 405 | 406 | // Perform the SSL handshake 407 | // Note, this is the buffered version of the handshake. 408 | stream_.async_handshake( 409 | ssl::stream_base::server, 410 | buffer_.data(), 411 | beast::bind_front_handler( 412 | &ssl_http_session::on_handshake, 413 | shared_from_this())); 414 | } 415 | 416 | // Called by the base class 417 | beast::ssl_stream& ssl_http_session::stream() 418 | { 419 | return stream_; 420 | } 421 | 422 | boost::asio::ip::tcp::socket& ssl_http_session::socket() 423 | { 424 | return stream_.next_layer().socket(); 425 | } 426 | 427 | // Called by the base class 428 | beast::ssl_stream ssl_http_session::release_stream() 429 | { 430 | return std::move(stream_); 431 | } 432 | 433 | // Called by the base class 434 | void ssl_http_session::do_eof() 435 | { 436 | // Set the timeout. 437 | // beast::get_lowest_layer(stream_).expires_after(std::chrono::seconds(30)); 438 | 439 | // Perform the SSL shutdown 440 | stream_.async_shutdown( 441 | beast::bind_front_handler( 442 | &ssl_http_session::on_shutdown, 443 | shared_from_this())); 444 | } 445 | 446 | void ssl_http_session::on_handshake( 447 | beast::error_code ec, 448 | std::size_t bytes_used) 449 | { 450 | if(ec) 451 | return fail(ec, "on_handshake"); 452 | 453 | // Consume the portion of the buffer used by the handshake 454 | buffer_.consume(bytes_used); 455 | 456 | state_->join(this); // join the shared state here now that we support tcp comms 457 | do_read(); 458 | } 459 | 460 | void ssl_http_session::on_shutdown(beast::error_code ec) 461 | { 462 | if(ec) 463 | return fail(ec, "on_shutdown"); 464 | 465 | // At this point the connection is closed gracefully 466 | } 467 | 468 | 469 | //------------------------------------------------------------------------------ 470 | 471 | 472 | detect_session::detect_session( 473 | tcp::socket&& socket, 474 | ssl::context& ctx, 475 | std::shared_ptr const& state, 476 | // std::shared_ptr const& doc_root 477 | Security security 478 | ) 479 | : stream_(std::move(socket)) 480 | , ctx_(ctx) 481 | // , doc_root_(doc_root) 482 | , state_(state) 483 | , security_(security) 484 | { 485 | } 486 | 487 | // Launch the detector 488 | void detect_session::run() 489 | { 490 | // We need to be executing within a strand to perform async operations 491 | // on the I/O objects in this session. Although not strictly necessary 492 | // for single-threaded contexts, this example code is written to be 493 | // thread-safe by default. 494 | net::dispatch( 495 | stream_.get_executor(), 496 | beast::bind_front_handler( 497 | &detect_session::on_run, 498 | this->shared_from_this())); 499 | } 500 | 501 | void detect_session::on_run() 502 | { 503 | // Set the timeout. 504 | // stream_.expires_after(std::chrono::seconds(30)); 505 | 506 | // if the user wants both http/ws and https/wss on this port, you must detect what the client wants 507 | if (Security::BOTH == security_) 508 | { 509 | beast::async_detect_ssl( 510 | stream_, 511 | buffer_, 512 | beast::bind_front_handler( 513 | &detect_session::on_detect, 514 | this->shared_from_this())); 515 | } 516 | else 517 | { 518 | // else pretend the client specified what they want 519 | on_detect(beast::error_code(), Security::SECURE == security_); 520 | } 521 | } 522 | 523 | void detect_session::on_detect(beast::error_code ec, bool result) 524 | { 525 | if(ec) 526 | return fail(ec, "on_detect"); 527 | 528 | if(result) 529 | { 530 | // Launch SSL session 531 | std::make_shared( 532 | std::move(stream_), 533 | ctx_, 534 | std::move(buffer_), 535 | state_)->run(); 536 | return; 537 | } 538 | 539 | // Launch plain session 540 | std::make_shared( 541 | std::move(stream_), 542 | std::move(buffer_), 543 | state_)->run(); 544 | } 545 | 546 | 547 | } // namespace 548 | -------------------------------------------------------------------------------- /Networking/src/http_session.hpp: -------------------------------------------------------------------------------- 1 | 2 | #ifndef BEASTWEBSERVERFLEXIBLE_HTTP_SESSION_HPP 3 | #define BEASTWEBSERVERFLEXIBLE_HTTP_SESSION_HPP 4 | 5 | #include "net.hpp" 6 | 7 | #include 8 | #include "websocket_session.hpp" 9 | 10 | namespace BeastNetworking 11 | { 12 | 13 | 14 | 15 | // Return a reasonable mime type based on the extension of a file. 16 | static beast::string_view mime_type(beast::string_view path) 17 | { 18 | using beast::iequals; 19 | auto const ext = [&path] 20 | { 21 | auto const pos = path.rfind("."); 22 | if(pos == beast::string_view::npos) 23 | return beast::string_view{}; 24 | return path.substr(pos); 25 | }(); 26 | if(iequals(ext, ".htm")) return "text/html"; 27 | if(iequals(ext, ".html")) return "text/html"; 28 | if(iequals(ext, ".php")) return "text/html"; 29 | if(iequals(ext, ".css")) return "text/css"; 30 | if(iequals(ext, ".txt")) return "text/plain"; 31 | if(iequals(ext, ".js")) return "application/javascript"; 32 | if(iequals(ext, ".json")) return "application/json"; 33 | if(iequals(ext, ".xml")) return "application/xml"; 34 | if(iequals(ext, ".swf")) return "application/x-shockwave-flash"; 35 | if(iequals(ext, ".flv")) return "video/x-flv"; 36 | if(iequals(ext, ".png")) return "image/png"; 37 | if(iequals(ext, ".jpe")) return "image/jpeg"; 38 | if(iequals(ext, ".jpeg")) return "image/jpeg"; 39 | if(iequals(ext, ".jpg")) return "image/jpeg"; 40 | if(iequals(ext, ".gif")) return "image/gif"; 41 | if(iequals(ext, ".bmp")) return "image/bmp"; 42 | if(iequals(ext, ".ico")) return "image/vnd.microsoft.icon"; 43 | if(iequals(ext, ".tiff")) return "image/tiff"; 44 | if(iequals(ext, ".tif")) return "image/tiff"; 45 | if(iequals(ext, ".svg")) return "image/svg+xml"; 46 | if(iequals(ext, ".svgz")) return "image/svg+xml"; 47 | return "application/text"; 48 | } 49 | 50 | // Append an HTTP rel-path to a local filesystem path. 51 | // The returned path is normalized for the platform. 52 | static std::string path_cat( beast::string_view base, beast::string_view path) 53 | { 54 | if(base.empty()) 55 | return std::string(path); 56 | std::string result(base); 57 | #ifdef BOOST_MSVC 58 | char constexpr path_separator = '\\'; 59 | if(result.back() == path_separator) 60 | result.resize(result.size() - 1); 61 | result.append(path.data(), path.size()); 62 | for(auto& c : result) 63 | if(c == '/') 64 | c = path_separator; 65 | #else 66 | char constexpr path_separator = '/'; 67 | if(result.back() == path_separator) 68 | result.resize(result.size() - 1); 69 | result.append(path.data(), path.size()); 70 | #endif 71 | return result; 72 | } 73 | 74 | // This function produces an HTTP response for the given 75 | // request. The type of the response object depends on the 76 | // contents of the request, so the interface requires the 77 | // caller to pass a generic lambda for receiving the response. 78 | template< class Body, class Allocator, class Send, class OnPostCallback> 79 | void handle_request( beast::string_view doc_root, http::request>&& req, Send&& send, const OnPostCallback &on_post) 80 | { 81 | // Returns a bad request response 82 | auto const bad_request = 83 | [&req](beast::string_view why) 84 | { 85 | http::response res{http::status::bad_request, req.version()}; 86 | res.set(http::field::server, BOOST_BEAST_VERSION_STRING); 87 | res.set(http::field::content_type, "text/html"); 88 | res.keep_alive(req.keep_alive()); 89 | res.body() = std::string(why); 90 | res.prepare_payload(); 91 | return res; 92 | }; 93 | 94 | // Returns a not found response 95 | auto const not_found = 96 | [&req](beast::string_view target) 97 | { 98 | http::response res{http::status::not_found, req.version()}; 99 | res.set(http::field::server, BOOST_BEAST_VERSION_STRING); 100 | res.set(http::field::content_type, "text/html"); 101 | res.keep_alive(req.keep_alive()); 102 | res.body() = "The resource '" + std::string(target) + "' was not found."; 103 | res.prepare_payload(); 104 | return res; 105 | }; 106 | 107 | // Returns a server error response 108 | auto const server_error = 109 | [&req](beast::string_view what) 110 | { 111 | http::response res{http::status::internal_server_error, req.version()}; 112 | res.set(http::field::server, BOOST_BEAST_VERSION_STRING); 113 | res.set(http::field::content_type, "text/html"); 114 | res.keep_alive(req.keep_alive()); 115 | res.body() = "An error occurred: '" + std::string(what) + "'"; 116 | res.prepare_payload(); 117 | return res; 118 | }; 119 | 120 | if (req.method() == http::verb::post && req.method() != http::verb::head) 121 | { 122 | on_post(req.body().data(), req.body().size()); 123 | } 124 | 125 | // Make sure we can handle the method 126 | if( req.method() != http::verb::get && 127 | req.method() != http::verb::head) 128 | { 129 | return send(bad_request("Unknown HTTP-method")); 130 | } 131 | 132 | // Request path must be absolute and not contain "..". 133 | if( req.target().empty() || 134 | req.target()[0] != '/' || 135 | req.target().find("..") != beast::string_view::npos) 136 | { 137 | return send(bad_request("Illegal request-target")); 138 | } 139 | 140 | // Build the path to the requested file 141 | std::string path = path_cat(doc_root, req.target()); 142 | path = path.substr(0, path.find('?')); // scrip off question mark and data after it 143 | if(path.back() == '/') 144 | path.append("index.html"); 145 | 146 | // Attempt to open the file 147 | beast::error_code ec; 148 | http::file_body::value_type body; 149 | body.open(path.c_str(), beast::file_mode::scan, ec); 150 | 151 | // Handle the case where the file doesn't exist 152 | if(ec == beast::errc::no_such_file_or_directory) 153 | return send(not_found(req.target())); 154 | 155 | // Handle an unknown error 156 | if(ec) 157 | return send(server_error(ec.message())); 158 | 159 | // Cache the size since we need it after the move 160 | auto const size = body.size(); 161 | 162 | // Respond to HEAD request 163 | if(req.method() == http::verb::head) 164 | { 165 | http::response res{http::status::ok, req.version()}; 166 | res.set(http::field::server, BOOST_BEAST_VERSION_STRING); 167 | res.set(http::field::content_type, mime_type(path)); 168 | res.content_length(size); 169 | res.keep_alive(req.keep_alive()); 170 | return send(std::move(res)); 171 | } 172 | 173 | // Respond to GET request 174 | http::response res{ 175 | std::piecewise_construct, 176 | std::make_tuple(std::move(body)), 177 | std::make_tuple(http::status::ok, req.version())}; 178 | res.set(http::field::server, BOOST_BEAST_VERSION_STRING); 179 | res.set(http::field::content_type, mime_type(path)); 180 | res.content_length(size); 181 | res.keep_alive(req.keep_alive()); 182 | return send(std::move(res)); 183 | } 184 | 185 | 186 | 187 | //------------------------------------------------------------------------------ 188 | 189 | // Handles an HTTP server connection. 190 | // This uses the Curiously Recurring Template Pattern so that 191 | // the same code works with both SSL streams and regular sockets. 192 | template 193 | class http_session 194 | { 195 | // Access the derived class, this is part of 196 | // the Curiously Recurring Template Pattern idiom. 197 | Derived& derived(); 198 | 199 | // This queue is used for HTTP pipelining. 200 | class queue 201 | { 202 | enum 203 | { 204 | // Maximum number of responses we will queue 205 | limit = 8 206 | }; 207 | 208 | // The type-erased, saved work item 209 | struct work 210 | { 211 | virtual ~work() = default; 212 | virtual void operator()() = 0; 213 | }; 214 | 215 | http_session& self_; 216 | std::vector> items_; 217 | 218 | public: 219 | explicit 220 | queue(http_session& self) 221 | : self_(self) 222 | { 223 | static_assert(limit > 0, "queue limit must be positive"); 224 | items_.reserve(limit); 225 | } 226 | 227 | // Returns `true` if we have reached the queue limit 228 | bool 229 | is_full() const 230 | { 231 | return items_.size() >= limit; 232 | } 233 | 234 | // Called when a message finishes sending 235 | // Returns `true` if the caller should initiate a read 236 | bool 237 | on_write() 238 | { 239 | BOOST_ASSERT(! items_.empty()); 240 | auto const was_full = is_full(); 241 | items_.erase(items_.begin()); 242 | if(! items_.empty()) 243 | (*items_.front())(); 244 | return was_full; 245 | } 246 | 247 | // TODO REMOVE ME. THIS IS SO HACKY. NOT EVEN THREAD SAFE 248 | size_t size() 249 | { 250 | return items_.size(); 251 | } 252 | 253 | // Called by the HTTP handler to send a response. 254 | template 255 | void 256 | operator()(http::message&& msg) 257 | { 258 | // This holds a work item 259 | struct work_impl : work 260 | { 261 | http_session& self_; 262 | http::message msg_; 263 | 264 | work_impl( 265 | http_session& self, 266 | http::message&& msg) 267 | : self_(self) 268 | , msg_(std::move(msg)) 269 | { 270 | } 271 | 272 | void 273 | operator()() 274 | { 275 | http::async_write( 276 | self_.derived().stream(), 277 | msg_, 278 | beast::bind_front_handler( 279 | &http_session::on_write, 280 | self_.derived().shared_from_this(), 281 | msg_.need_eof())); 282 | } 283 | }; 284 | 285 | // Allocate and store the work 286 | items_.push_back( 287 | boost::make_unique(self_, std::move(msg))); 288 | 289 | // If there was no previous work, start this one 290 | if(items_.size() == 1) 291 | (*items_.front())(); 292 | } 293 | }; 294 | 295 | // std::shared_ptr doc_root_; 296 | queue queue_; // part of the example, for get/post/etc 297 | std::deque send_queue_; // no mutex needed, only ever modified inside handlers, which are in a strand 298 | 299 | // The parser is stored in an optional container so we can 300 | // construct it from scratch it at the beginning of each new message. 301 | boost::optional> parser_; 302 | std::atomic queue_size_; 303 | 304 | protected: 305 | beast::flat_buffer buffer_; 306 | std::shared_ptr state_; 307 | std::vector nonhttp_buffer_; 308 | size_t nonhttp_buffer_size_{0}; // so we dont have to keep resizing our vector 309 | 310 | public: 311 | tcp::endpoint endpoint; 312 | 313 | // Construct the session 314 | http_session( 315 | beast::flat_buffer buffer, 316 | std::shared_ptr const& state, 317 | // std::shared_ptr const& doc_root, 318 | tcp::endpoint endpoint_in 319 | ); 320 | 321 | void do_read(); 322 | 323 | void on_nonhttp_read_header(const boost::system::error_code& error, size_t bytes_transferred); 324 | void on_nonhttp_read_body(size_t header_size, size_t body_size, const boost::system::error_code& error, size_t bytes_transferred); 325 | 326 | void on_read(beast::error_code ec, std::size_t bytes_transferred); 327 | 328 | void on_write(bool close, beast::error_code ec, std::size_t bytes_transferred); 329 | 330 | void sendAsync(const void* msgPtr, size_t msgSize, shared_state::CompletionHandlerT completionHandler, bool force_send, size_t max_queue_size ); 331 | 332 | void on_send(const void* msgPtr, size_t msgSize, shared_state::CompletionHandlerT &&completionHandler, bool force_send=false, size_t max_queue_size=std::numeric_limits::max()); 333 | 334 | void on_write2( beast::error_code ec, std::size_t bytes_transferred); 335 | 336 | void on_error(beast::error_code ec); 337 | 338 | size_t queue_size() const 339 | { 340 | return queue_size_; 341 | } 342 | }; 343 | 344 | //------------------------------------------------------------------------------ 345 | 346 | // Handles a plain HTTP connection 347 | class plain_http_session 348 | : public http_session 349 | , public std::enable_shared_from_this 350 | { 351 | beast::tcp_stream stream_; 352 | 353 | public: 354 | // Create the session 355 | plain_http_session( 356 | beast::tcp_stream&& stream, 357 | beast::flat_buffer&& buffer, 358 | std::shared_ptr const& state 359 | //std::shared_ptr const& doc_root 360 | ); 361 | 362 | ~plain_http_session(); 363 | 364 | // Start the session 365 | void run(); 366 | 367 | // Called by the base class 368 | beast::tcp_stream& stream(); 369 | boost::asio::ip::tcp::socket& socket(); 370 | 371 | // Called by the base class 372 | beast::tcp_stream release_stream(); 373 | 374 | // Called by the base class 375 | void do_eof(); 376 | }; 377 | 378 | //------------------------------------------------------------------------------ 379 | 380 | // Handles an SSL HTTP connection 381 | class ssl_http_session 382 | : public http_session 383 | , public std::enable_shared_from_this 384 | { 385 | beast::ssl_stream stream_; 386 | 387 | public: 388 | // Create the http_session 389 | ssl_http_session( 390 | beast::tcp_stream&& stream, 391 | ssl::context& ctx, 392 | beast::flat_buffer&& buffer, 393 | std::shared_ptr const& state 394 | //std::shared_ptr const& doc_root 395 | ); 396 | 397 | ~ssl_http_session(); 398 | 399 | // Start the session 400 | void run(); 401 | 402 | // Called by the base class 403 | beast::ssl_stream& stream(); 404 | boost::asio::ip::tcp::socket& socket(); 405 | 406 | // Called by the base class 407 | beast::ssl_stream release_stream(); 408 | 409 | // Called by the base class 410 | void do_eof(); 411 | 412 | private: 413 | void on_handshake( 414 | beast::error_code ec, 415 | std::size_t bytes_used); 416 | 417 | void on_shutdown(beast::error_code ec); 418 | }; 419 | 420 | //------------------------------------------------------------------------------ 421 | 422 | // Detects SSL handshakes 423 | class detect_session : public std::enable_shared_from_this 424 | { 425 | beast::tcp_stream stream_; 426 | ssl::context& ctx_; 427 | // std::shared_ptr doc_root_; 428 | std::shared_ptr state_; 429 | beast::flat_buffer buffer_; 430 | Security security_; 431 | 432 | public: 433 | explicit 434 | detect_session( 435 | tcp::socket&& socket, 436 | ssl::context& ctx, 437 | std::shared_ptr const& state, 438 | Security security 439 | // std::shared_ptr const& doc_root 440 | ); 441 | 442 | // Launch the detector 443 | void run(); 444 | 445 | void on_run(); 446 | 447 | void on_detect(beast::error_code ec, bool result); 448 | }; 449 | 450 | 451 | } // namespace 452 | 453 | #endif -------------------------------------------------------------------------------- /Networking/src/listener.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/vinniefalco/CppCon2018 8 | // 9 | // Modified by Keith Rausch 10 | 11 | #include "listener.hpp" 12 | // #include "websocket_session.h" 13 | // #include 14 | 15 | #include "http_session.hpp" 16 | #include 17 | #include 18 | 19 | 20 | namespace BeastNetworking 21 | { 22 | 23 | 24 | listener::listener( 25 | net::io_context& ioc, 26 | ssl::context& ctx, 27 | tcp::endpoint endpoint, 28 | std::shared_ptr const& state, 29 | Security security) 30 | : ioc_(ioc) 31 | , ctx_(ctx) 32 | , acceptor_(net::make_strand(ioc)) 33 | , state_(state) 34 | , security_(security) 35 | { 36 | beast::error_code ec; 37 | 38 | // Open the acceptor 39 | acceptor_.open(endpoint.protocol(), ec); 40 | if(ec) 41 | { 42 | fail(ec, "on_open"); 43 | return; 44 | } 45 | 46 | // Allow address reuse 47 | acceptor_.set_option(net::socket_base::reuse_address(true), ec); 48 | if(ec) 49 | { 50 | fail(ec, "set_option"); 51 | return; 52 | } 53 | 54 | // Bind to the server address 55 | acceptor_.bind(endpoint, ec); 56 | if(ec) 57 | { 58 | fail(ec, "on_bind"); 59 | return; 60 | } 61 | 62 | localEndpoint = acceptor_.local_endpoint(); 63 | 64 | // Start listening for connections 65 | acceptor_.listen( 66 | net::socket_base::max_listen_connections, ec); 67 | if(ec) 68 | { 69 | fail(ec, "on_listen"); 70 | return; 71 | } 72 | }; 73 | 74 | // Start accepting incoming connections 75 | void listener::run() 76 | { 77 | do_accept(); 78 | } 79 | 80 | void listener::do_accept() 81 | { 82 | // The new connection gets its own strand 83 | acceptor_.async_accept( 84 | net::make_strand(ioc_), 85 | beast::bind_front_handler( 86 | &listener::on_accept, 87 | shared_from_this())); 88 | } 89 | 90 | void listener::on_accept(beast::error_code ec, tcp::socket socket) 91 | { 92 | if(ec) 93 | { 94 | fail(ec, "on_accept"); 95 | 96 | // insert sleep to keep the system from spamming the termianl/log files 97 | std::this_thread::sleep_for(std::chrono::milliseconds(1000)); 98 | } 99 | else 100 | { 101 | // Create the detector http_session and run it 102 | std::make_shared( 103 | std::move(socket), 104 | ctx_, 105 | state_, 106 | security_)->run(); 107 | } 108 | 109 | // Accept another connection 110 | do_accept(); 111 | }; 112 | 113 | } // namespace -------------------------------------------------------------------------------- /Networking/src/listener.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/vinniefalco/CppCon2018 8 | // 9 | // Modified by Keith Rausch 10 | 11 | #ifndef BEASTWEBSERVERFLEXIBLE_LISTENER_HPP 12 | #define BEASTWEBSERVERFLEXIBLE_LISTENER_HPP 13 | 14 | #include "net.hpp" 15 | #include 16 | #include "shared_state.hpp" 17 | 18 | namespace BeastNetworking 19 | { 20 | 21 | // Accepts incoming connections and launches the sessions 22 | class listener : public std::enable_shared_from_this 23 | { 24 | net::io_context& ioc_; 25 | ssl::context& ctx_; 26 | tcp::acceptor acceptor_; 27 | // std::shared_ptr doc_root_; 28 | std::shared_ptr state_; 29 | Security security_; 30 | 31 | public: 32 | tcp::endpoint localEndpoint; 33 | 34 | listener( 35 | net::io_context& ioc, 36 | ssl::context& ctx, 37 | tcp::endpoint endpoint, 38 | std::shared_ptr const& state, 39 | Security security); 40 | 41 | // Start accepting incoming connections 42 | void run(); 43 | 44 | private: 45 | void do_accept(); 46 | 47 | void on_accept(beast::error_code ec, tcp::socket socket); 48 | }; 49 | 50 | 51 | } // namespace 52 | 53 | #endif -------------------------------------------------------------------------------- /Networking/src/net.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/vinniefalco/CppCon2018 8 | // 9 | // Modified by Keith Rausch 10 | 11 | #ifndef BEASTWEBSERVERFLEXIBLE_NET_HPP 12 | #define BEASTWEBSERVERFLEXIBLE_NET_HPP 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include 21 | 22 | namespace BeastNetworking 23 | { 24 | 25 | enum class Security { INSECURE, SECURE, BOTH }; 26 | 27 | namespace 28 | { 29 | namespace beast = boost::beast; // from 30 | namespace http = beast::http; // from 31 | namespace websocket = beast::websocket; // from 32 | namespace net = boost::asio; // from 33 | namespace ssl = boost::asio::ssl; // from 34 | using tcp = boost::asio::ip::tcp; // from 35 | } 36 | 37 | // namespace net = boost::asio; // from 38 | // using tcp = boost::asio::ip::tcp; // from 39 | 40 | 41 | // Report a failure 42 | static void fail(beast::error_code ec, char const* what) 43 | { 44 | // ssl::error::stream_truncated, also known as an SSL "short read", 45 | // indicates the peer closed the connection without performing the 46 | // required closing handshake (for example, Google does this to 47 | // improve performance). Generally this can be a security issue, 48 | // but if your communication protocol is self-terminated (as 49 | // it is with both HTTP and WebSocket) then you may simply 50 | // ignore the lack of close_notify. 51 | // 52 | // https://github.com/boostorg/beast/issues/38 53 | // 54 | // https://security.stackexchange.com/questions/91435/how-to-handle-a-malicious-ssl-tls-shutdown 55 | // 56 | // When a short read would cut off the end of an HTTP message, 57 | // Beast returns the error beast::http::error::partial_message. 58 | // Therefore, if we see a short read here, it has occurred 59 | // after the message has been completed, so it is safe to ignore it. 60 | 61 | if(ec == net::ssl::error::stream_truncated) 62 | return; 63 | 64 | std::cerr << "BeastNetworking::" << what << ": " << ec.message() << " (value="+std::to_string(ec.value())+")" "\n"; 65 | } 66 | 67 | 68 | static bool load_server_certificate(boost::asio::ssl::context& ctx, const std::string &cert_chain_file, const std::string &private_key_file, const std::string &tmp_dh_file ) 69 | { 70 | 71 | ctx.set_default_verify_paths(); 72 | ctx.set_options( 73 | boost::asio::ssl::context::default_workarounds 74 | | boost::asio::ssl::context::no_sslv2 75 | | boost::asio::ssl::context::no_sslv3 // https://stackoverflow.com/questions/43117638/boost-asio-get-with-client-certificate-sslv3-hand-shake-failed 76 | | boost::asio::ssl::context::single_dh_use 77 | ); 78 | 79 | // check these files exist 80 | 81 | 82 | bool hadError = false; 83 | 84 | std::error_code ec; 85 | 86 | if ( ! std::filesystem::exists(cert_chain_file, ec) || ec /* short circuit*/) 87 | { 88 | std::cerr << "BeastWebServer - cert_chain_file does not exist \"" + cert_chain_file + "\"\n"; 89 | std::cerr << ec << std::endl; 90 | hadError = true; 91 | } 92 | 93 | ec.clear(); 94 | if ( ! std::filesystem::exists(private_key_file, ec) || ec /* short circuit*/) 95 | { 96 | std::cerr << "BeastWebServer - private_key_file does not exist \"" + private_key_file + "\"\n"; 97 | std::cerr << ec << std::endl; 98 | hadError = true; 99 | } 100 | 101 | ec.clear(); 102 | if ( ! std::filesystem::exists(tmp_dh_file, ec) || ec /* short circuit*/) 103 | { 104 | std::cerr << "BeastWebServer - cert_chain_file does not exist \"" + tmp_dh_file + "\"\n"; 105 | std::cerr << ec << std::endl; 106 | hadError = true; 107 | } 108 | 109 | // // ssl_context.set_password_callback(std::bind(&server::get_password, this)); 110 | boost::system::error_code boost_error; 111 | ctx.use_certificate_chain_file(cert_chain_file, boost_error); 112 | if (boost_error) 113 | { 114 | std::cerr << boost_error << std::endl; 115 | hadError = true; 116 | } 117 | 118 | boost_error.clear(); 119 | ctx.use_private_key_file(private_key_file, boost::asio::ssl::context::pem, boost_error); 120 | if (boost_error) 121 | { 122 | std::cerr << boost_error << std::endl; 123 | hadError = true; 124 | } 125 | 126 | boost_error.clear(); 127 | ctx.use_tmp_dh_file(tmp_dh_file, boost_error); 128 | if (boost_error) 129 | { 130 | std::cerr << boost_error << std::endl; 131 | hadError = true; 132 | } 133 | 134 | // man this function really got away from me 135 | 136 | return hadError; 137 | } 138 | 139 | 140 | 141 | } // namespace 142 | 143 | #endif -------------------------------------------------------------------------------- /Networking/src/rate_limiter.cpp: -------------------------------------------------------------------------------- 1 | // Keith Rausch 2 | 3 | #include "rate_limiter.hpp" 4 | #include 5 | 6 | namespace BeastNetworking 7 | { 8 | namespace RateLimiting 9 | { 10 | 11 | RateTracker::RateTracker() : max_byterate(std::numeric_limits::max()) 12 | {} 13 | 14 | RateTracker::RateTracker(double max_byterate_in) : max_byterate(max_byterate_in) 15 | {} 16 | 17 | void RateTracker::set_max_byterate(double max_byterate_in) 18 | { 19 | max_byterate = max_byterate_in < 0 ? std::numeric_limits::max() : max_byterate_in; 20 | } 21 | 22 | double RateTracker::get_max_byterate() 23 | { 24 | return max_byterate; 25 | } 26 | 27 | 28 | size_t RateTracker::update_and_get_allowable(size_t new_rate, size_t id ) 29 | { 30 | double total = 0; 31 | 32 | { // lock guard scope 33 | std::shared_lock lock(shared_mutex); 34 | 35 | if (id < true_rates.size()) 36 | { 37 | true_rates[id] = new_rate; 38 | } 39 | 40 | for (auto& rate : true_rates) 41 | { 42 | total += rate.load(); 43 | } 44 | } 45 | 46 | return (new_rate / total) * std::min(total, max_byterate.load()); 47 | } 48 | 49 | void RateTracker::unregister(size_t id) 50 | { 51 | { // lock guard scope 52 | std::lock_guard lock(shared_mutex); 53 | 54 | if (id < true_rates.size()) 55 | { 56 | true_rates[id] = true_rates.back().load(); 57 | true_rates.pop_back(); 58 | } 59 | } 60 | } 61 | 62 | RateEnforcer RateTracker::make_enforcer(const RateEnforcer::Args &args) 63 | { 64 | std::shared_ptr this_block = shared_from_this(); 65 | 66 | size_t id = 0; 67 | 68 | { // lock guard scope 69 | std::lock_guard lock(shared_mutex); 70 | 71 | id = true_rates.size(); 72 | true_rates.emplace_back(0); 73 | } 74 | 75 | return RateEnforcer(this_block, args, id); 76 | } 77 | 78 | RateEnforcer::RateEnforcer() : unlimited_rate(0), allowed_rate(0), actual_rate(0), id(0) 79 | {} 80 | 81 | RateEnforcer::RateEnforcer( const std::shared_ptr & rate_tracker_in, const Args &args_in, size_t id_in) : unlimited_rate(0), allowed_rate(0), actual_rate(0), rate_tracker(rate_tracker_in), args(args_in), id(id_in) 82 | {} 83 | 84 | RateEnforcer::~RateEnforcer() 85 | { 86 | if (rate_tracker) 87 | { 88 | rate_tracker->unregister(id); 89 | } 90 | } 91 | 92 | void RateEnforcer::update_hypothetical_rate(size_t nBytes) 93 | { 94 | double last_unlimited_send = unlimited_timer.elapsed(); // seconds since last hypothetical send 95 | double instantaneous_unlimited_rate = nBytes / ( last_unlimited_send + std::numeric_limits::epsilon()); 96 | unlimited_timer.reset(); 97 | 98 | // this COULD be a race condition is accessed in a multi threaded context but thats unlikely 99 | unlimited_rate = unlimited_rate * (1.0-args.alpha) + args.alpha * instantaneous_unlimited_rate; 100 | 101 | if (rate_tracker) 102 | { 103 | allowed_rate = rate_tracker->update_and_get_allowable(unlimited_rate, id); 104 | // std::cout << "allowed_rate = "+std::to_string(allowed_rate)+", unlimited_rate = "+std::to_string(unlimited_rate)+"\n"; 105 | } 106 | } 107 | 108 | bool RateEnforcer::should_send(size_t nBytes) 109 | { 110 | bool may_send = true; // default in case we return early 111 | 112 | if ( ! rate_tracker) 113 | { 114 | may_send = true; 115 | return may_send; 116 | } 117 | 118 | double last_actual_send = actual_timer.elapsed(); // seconds since last actual send 119 | double instantaneous_actual_rate = nBytes / ( last_actual_send + std::numeric_limits::epsilon()); 120 | 121 | double thresh = allowed_rate.load(); // snap the atomic 122 | 123 | if (actual_rate > thresh * args.rate_recalculation_trigger) // the allowed rate has suddenly dropped, lets re compute our moving average 124 | { 125 | actual_rate = 0; 126 | } 127 | 128 | double temp_actual_rate = actual_rate * (1.0-args.alpha) + args.alpha * instantaneous_actual_rate; 129 | 130 | 131 | // get permissible rate 132 | may_send = (temp_actual_rate <= thresh) || (!args.limitable); 133 | 134 | if (may_send) 135 | { 136 | actual_timer.reset(); 137 | actual_rate = temp_actual_rate; 138 | } 139 | 140 | // std::cout << "actual_rate = " + std::to_string(actual_rate) + " last send time = " + std::to_string(last_actual_send)+ "\n"; 141 | 142 | return may_send; 143 | } 144 | 145 | 146 | } // namespace 147 | } // namespace 148 | -------------------------------------------------------------------------------- /Networking/src/rate_limiter.hpp: -------------------------------------------------------------------------------- 1 | // Keith Rausch 2 | 3 | #ifndef BEASTWEBSERVERFLEXIBLE_RATE_LIMITER_HPP 4 | #define BEASTWEBSERVERFLEXIBLE_RATE_LIMITER_HPP 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | 12 | namespace BeastNetworking 13 | { 14 | 15 | 16 | namespace RateLimiting 17 | { 18 | 19 | 20 | template 21 | struct CopyableAtomic 22 | { 23 | std::atomic atomic; 24 | 25 | CopyableAtomic() : atomic(T()) 26 | {} 27 | 28 | CopyableAtomic(const CopyableAtomic &other) : atomic(other.atomic.load()) 29 | {} 30 | 31 | CopyableAtomic(const T &other) : atomic(other) 32 | {} 33 | 34 | CopyableAtomic operator=( const CopyableAtomic &other ) 35 | { 36 | atomic = other.atomic.load(); 37 | return *this; 38 | } 39 | 40 | T operator=( const T &other ) 41 | { 42 | atomic = other; 43 | return other; 44 | } 45 | 46 | T load() 47 | { 48 | return atomic.load(); 49 | } 50 | }; 51 | 52 | class RateTracker; 53 | 54 | class RateEnforcer 55 | { 56 | 57 | public: 58 | 59 | struct Args 60 | { 61 | double alpha = 0.1; // exponential moving average. alpha is weighting of newest measurement. 1-alpha is weighting of previous knowledge 62 | // bool included_in_total_rate = true; 63 | bool limitable = true; 64 | double rate_recalculation_trigger = 1.1; // if the current byte rate is this multiple above the allowable rate, reset the byte rate back to 0 65 | }; 66 | 67 | private: 68 | 69 | class Timer 70 | { 71 | typedef std::chrono::steady_clock Clock; 72 | typedef std::chrono::duration> Second; 73 | std::chrono::time_point start; 74 | 75 | public: 76 | Timer() : start(Clock::now()) {} 77 | void reset() { start = Clock::now(); } 78 | double elapsed() const 79 | { 80 | return std::chrono::duration_cast(Clock::now() - start).count(); 81 | } 82 | 83 | static double since_epoch() 84 | { 85 | using namespace std::chrono; 86 | return duration_cast(Clock::now().time_since_epoch()).count() / 1000.0; 87 | } 88 | }; 89 | 90 | private: 91 | Timer unlimited_timer; 92 | double unlimited_rate; 93 | CopyableAtomic allowed_rate; 94 | Timer actual_timer; 95 | double actual_rate; 96 | std::shared_ptr rate_tracker; 97 | Args args; 98 | size_t id; 99 | 100 | friend class RateTracker; 101 | 102 | RateEnforcer( const std::shared_ptr & control_block_in, const Args &args_in, size_t id_in); 103 | 104 | public: 105 | 106 | RateEnforcer(); 107 | RateEnforcer(RateEnforcer&& rate_enforcer) = default; 108 | ~RateEnforcer(); 109 | void update_hypothetical_rate(size_t nBytes); 110 | bool should_send(size_t nBytes); 111 | }; 112 | 113 | class RateTracker : public std::enable_shared_from_this 114 | { 115 | friend class RateEnforcer; 116 | 117 | std::vector> true_rates; 118 | std::shared_mutex shared_mutex; 119 | std::atomic max_byterate; 120 | 121 | size_t update_and_get_allowable(size_t new_rate, size_t id ); 122 | void unregister(size_t id); 123 | 124 | public: 125 | 126 | RateTracker(); 127 | RateTracker(double max_byterate); 128 | void set_max_byterate(double byterate); 129 | double get_max_byterate(); 130 | 131 | RateEnforcer make_enforcer( const RateEnforcer::Args &args); 132 | }; 133 | 134 | typedef std::shared_ptr sRateTracker; 135 | 136 | 137 | 138 | 139 | } // namespace 140 | } // namespace 141 | 142 | 143 | #endif -------------------------------------------------------------------------------- /Networking/src/shared_state.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/boostorg/beast 8 | // 9 | // modified by Keith Rausch 10 | 11 | //------------------------------------------------------------------------------ 12 | // 13 | // Example: Advanced server, flex (plain + SSL) 14 | // 15 | //------------------------------------------------------------------------------ 16 | 17 | #include "shared_state.hpp" 18 | #include "websocket_session.hpp" 19 | #include "http_session.hpp" 20 | 21 | namespace BeastNetworking 22 | { 23 | 24 | shared_state::shared_state() 25 | : doc_root_(), 26 | server_is_tcp_instead_of_http_(false), 27 | callbacks() 28 | { 29 | } 30 | 31 | shared_state::shared_state(std::string doc_root ) 32 | : doc_root_(std::move(doc_root)), 33 | server_is_tcp_instead_of_http_(false), 34 | nonhttp_header_size_(0), 35 | callbacks() 36 | { 37 | } 38 | 39 | shared_state::shared_state(const Callbacks &callbacks_in) 40 | : doc_root_(), 41 | server_is_tcp_instead_of_http_(false), 42 | nonhttp_header_size_(0), 43 | callbacks(callbacks_in) 44 | { 45 | } 46 | 47 | shared_state::shared_state(std::string doc_root, const Callbacks &callbacks_in, bool server_is_tcp_instead_of_http_in, size_t nonhttp_header_size_in) 48 | : doc_root_(std::move(doc_root)), 49 | server_is_tcp_instead_of_http_(server_is_tcp_instead_of_http_in), 50 | nonhttp_header_size_(nonhttp_header_size_in), 51 | callbacks(callbacks_in) 52 | { 53 | } 54 | 55 | std::string const& shared_state::doc_root() const noexcept 56 | { 57 | return doc_root_; 58 | } 59 | 60 | uint64_t shared_state::read_message_max() 61 | { 62 | return read_message_max_; 63 | } 64 | 65 | void shared_state::read_message_max(uint64_t size) 66 | { 67 | read_message_max_ = size; 68 | } 69 | 70 | void shared_state::check_doc_root() 71 | { 72 | if (callbacks.callbackPrint) 73 | { 74 | if ( ! std::filesystem::exists(doc_root_)) 75 | { 76 | callbacks.callbackPrint("root directory does not exist \"" + doc_root_ + "\""); 77 | } 78 | else 79 | { 80 | callbacks.callbackPrint("starting server at root \"" + doc_root_ + "\""); 81 | } 82 | } 83 | } 84 | 85 | size_t shared_state::nSessions(size_t &insecure, size_t &secure) const 86 | { 87 | std::lock_guard lock(mutex_); 88 | insecure = ws_sessions_.size() + http_sessions_.size(); 89 | secure = wss_sessions_.size() + https_sessions_.size(); 90 | return insecure + secure; 91 | } 92 | 93 | size_t shared_state::queue_sizes_summed() const 94 | { 95 | std::lock_guard lock(mutex_); 96 | 97 | size_t queue_sizes_summed = 0; 98 | 99 | for (auto p : ws_sessions_) 100 | { 101 | auto strong = p->weak_from_this().lock(); 102 | if (strong) 103 | queue_sizes_summed += strong->queue_size(); 104 | } 105 | 106 | for (auto p : wss_sessions_) 107 | { 108 | auto strong = p->weak_from_this().lock(); 109 | if (strong) 110 | queue_sizes_summed += strong->queue_size(); 111 | } 112 | 113 | for (auto p : http_sessions_) 114 | { 115 | auto strong = p->weak_from_this().lock(); 116 | if (strong) 117 | queue_sizes_summed += strong->queue_size(); 118 | } 119 | 120 | for (auto p : https_sessions_) 121 | { 122 | auto strong = p->weak_from_this().lock(); 123 | if (strong) 124 | queue_sizes_summed += strong->queue_size(); 125 | } 126 | 127 | return queue_sizes_summed; 128 | } 129 | 130 | bool shared_state::server_is_tcp_instead_of_http() const 131 | { 132 | return server_is_tcp_instead_of_http_; 133 | } 134 | 135 | size_t shared_state::nonhttp_header_size() const 136 | { 137 | return nonhttp_header_size_; 138 | } 139 | 140 | void shared_state::upgrade(plain_websocket_session *ws_session) 141 | { 142 | { // callback functions may attempt a sendAsync which locks the mutex 143 | std::lock_guard lock(mutex_); 144 | ws_sessions_.insert(ws_session); 145 | } 146 | 147 | if (callbacks.callbackUpgrade) 148 | callbacks.callbackUpgrade(ws_session->endpoint); 149 | } 150 | 151 | void shared_state::downgrade(plain_websocket_session *ws_session) 152 | { 153 | { // callback functions may attempt a sendAsync which locks the mutex 154 | std::lock_guard lock(mutex_); 155 | ws_sessions_.erase(ws_session); 156 | } 157 | 158 | if (callbacks.callbackDowngrade) 159 | callbacks.callbackDowngrade(ws_session->endpoint); 160 | } 161 | 162 | void shared_state::upgrade(ssl_websocket_session *wss_session) 163 | { 164 | { // callback functions may attempt a sendAsync which locks the mutex 165 | std::lock_guard lock(mutex_); 166 | wss_sessions_.insert(wss_session); 167 | } 168 | 169 | if (callbacks.callbackUpgrade) 170 | callbacks.callbackUpgrade(wss_session->endpoint); 171 | } 172 | 173 | void shared_state::downgrade(ssl_websocket_session *wss_session) 174 | { 175 | { // callback functions may attempt a sendAsync which locks the mutex 176 | std::lock_guard lock(mutex_); 177 | wss_sessions_.erase(wss_session); 178 | } 179 | 180 | if (callbacks.callbackDowngrade) 181 | callbacks.callbackDowngrade(wss_session->endpoint); 182 | } 183 | 184 | 185 | 186 | void shared_state::join(plain_http_session *session) 187 | { 188 | { // callback functions may attempt a sendAsync which locks the mutex 189 | std::lock_guard lock(mutex_); 190 | http_sessions_.insert(session); 191 | } 192 | 193 | if (callbacks.callbackAccept) 194 | callbacks.callbackAccept(session->endpoint); 195 | } 196 | 197 | void shared_state::leave(plain_http_session *session) 198 | { 199 | { // callback functions may attempt a sendAsync which locks the mutex 200 | std::lock_guard lock(mutex_); 201 | http_sessions_.erase(session); 202 | } 203 | 204 | if (callbacks.callbackClose) 205 | callbacks.callbackClose(session->endpoint); 206 | } 207 | 208 | void shared_state::join(ssl_http_session *session) 209 | { 210 | { // callback functions may attempt a sendAsync which locks the mutex 211 | std::lock_guard lock(mutex_); 212 | https_sessions_.insert(session); 213 | } 214 | 215 | if (callbacks.callbackAccept) 216 | callbacks.callbackAccept(session->endpoint); 217 | } 218 | 219 | void shared_state::leave(ssl_http_session *session) 220 | { 221 | { // callback functions may attempt a sendAsync which locks the mutex 222 | std::lock_guard lock(mutex_); 223 | https_sessions_.erase(session); 224 | } 225 | 226 | if (callbacks.callbackClose) 227 | callbacks.callbackClose(session->endpoint); 228 | } 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | // Broadcast a message to all websocket client sessions 237 | void shared_state:: 238 | sendAsync(const void* msgPtr, size_t msgSize, const CompletionHandlerT &completionHandler, bool force_send, size_t max_queue_size, bool to_ws, bool to_tcp) 239 | { 240 | // checks for null msgPtr and 0 length are done lower down 241 | // if either of those things happen, call the completion handler with an abort token 242 | 243 | bool sent_to_any = false; 244 | 245 | // lock mutex here and pool all the different types of sessions we have 246 | // then unlock the mutex and send 247 | 248 | // Make a local list of all the weak pointers representing 249 | // the sessions, so we can do the actual sending without 250 | // holding the mutex: 251 | { 252 | std::lock_guard lock(mutex_); 253 | 254 | ws_sessionPointerPool.clear(); 255 | wss_sessionPointerPool.clear(); 256 | if (to_ws) 257 | { 258 | ws_sessionPointerPool.reserve(ws_sessions_.size()); 259 | for (auto p : ws_sessions_) 260 | ws_sessionPointerPool.emplace_back(p->weak_from_this()); 261 | 262 | wss_sessionPointerPool.reserve(wss_sessions_.size()); 263 | for (auto p : wss_sessions_) 264 | wss_sessionPointerPool.emplace_back(p->weak_from_this()); 265 | } 266 | 267 | http_sessionPointerPool.clear(); 268 | https_sessionPointerPool.clear(); 269 | if (to_tcp) 270 | { 271 | http_sessionPointerPool.reserve(http_sessions_.size()); 272 | for (auto p : http_sessions_) 273 | http_sessionPointerPool.emplace_back(p->weak_from_this()); 274 | 275 | https_sessionPointerPool.reserve(https_sessions_.size()); 276 | for (auto p : https_sessions_) 277 | https_sessionPointerPool.emplace_back(p->weak_from_this()); 278 | } 279 | } 280 | 281 | // WS 282 | for (auto const &wp : ws_sessionPointerPool) 283 | { 284 | if (auto sp = wp.lock()) 285 | { 286 | sp->sendAsync(msgPtr, msgSize, completionHandler, force_send, max_queue_size); 287 | sent_to_any = true; 288 | } 289 | } 290 | 291 | // WSS 292 | for (auto const &wp : wss_sessionPointerPool) 293 | { 294 | if (auto sp = wp.lock()) 295 | { 296 | sp->sendAsync(msgPtr, msgSize, completionHandler, force_send, max_queue_size); 297 | sent_to_any = true; 298 | } 299 | } 300 | 301 | // HTTP 302 | for (auto const &wp : http_sessionPointerPool) 303 | { 304 | if (auto sp = wp.lock()) 305 | { 306 | sp->sendAsync(msgPtr, msgSize, completionHandler, force_send, max_queue_size); 307 | sent_to_any = true; 308 | } 309 | } 310 | 311 | // HTTPS 312 | for (auto const &wp : https_sessionPointerPool) 313 | { 314 | if (auto sp = wp.lock()) 315 | { 316 | sp->sendAsync(msgPtr, msgSize, completionHandler, force_send, max_queue_size); 317 | sent_to_any = true; 318 | } 319 | } 320 | 321 | if ( ! sent_to_any && completionHandler) 322 | { 323 | completionHandler(boost::asio::error::not_connected, 0, endpointT()); 324 | } 325 | } 326 | 327 | // Broadcast a message to all websocket client sessions 328 | void shared_state:: 329 | sendAsync(const boost::asio::ip::tcp::endpoint &endpoint, const void* msgPtr, size_t msgSize, const CompletionHandlerT &completionHandler, bool force_send, size_t max_queue_size) 330 | { 331 | // checks for null msgPtr and 0 length are done lower down 332 | // if either of those things happen, call the completion handler with an abort token 333 | 334 | bool sent_to_any = false; 335 | 336 | // lock mutex here and pool all the different types of sessions we have 337 | // then unlock the mutex and send 338 | 339 | // Make a local list of all the weak pointers representing 340 | // the sessions, so we can do the actual sending without 341 | // holding the mutex: 342 | { 343 | std::lock_guard lock(mutex_); 344 | 345 | ws_sessionPointerPool.clear(); 346 | wss_sessionPointerPool.clear(); 347 | 348 | ws_sessionPointerPool.reserve(ws_sessions_.size()); 349 | for (auto p : ws_sessions_) 350 | ws_sessionPointerPool.emplace_back(p->weak_from_this()); 351 | 352 | wss_sessionPointerPool.reserve(wss_sessions_.size()); 353 | for (auto p : wss_sessions_) 354 | wss_sessionPointerPool.emplace_back(p->weak_from_this()); 355 | 356 | 357 | http_sessionPointerPool.clear(); 358 | https_sessionPointerPool.clear(); 359 | 360 | http_sessionPointerPool.reserve(http_sessions_.size()); 361 | for (auto p : http_sessions_) 362 | http_sessionPointerPool.emplace_back(p->weak_from_this()); 363 | 364 | https_sessionPointerPool.reserve(https_sessions_.size()); 365 | for (auto p : https_sessions_) 366 | https_sessionPointerPool.emplace_back(p->weak_from_this()); 367 | 368 | } 369 | 370 | 371 | // For each session in our local list, try to acquire a strong 372 | // pointer. If successful, then send the message on that session. 373 | 374 | // WS 375 | for (auto const &wp : ws_sessionPointerPool) 376 | { 377 | if (auto sp = wp.lock()) 378 | { 379 | if (sp->endpoint == endpoint) 380 | { 381 | sp->sendAsync(msgPtr, msgSize, completionHandler, force_send, max_queue_size); 382 | sent_to_any = true; 383 | } 384 | } 385 | } 386 | 387 | 388 | // WSS 389 | for (auto const &wp : wss_sessionPointerPool) 390 | { 391 | if (auto sp = wp.lock()) 392 | { 393 | if (sp->endpoint == endpoint) 394 | { 395 | sp->sendAsync(msgPtr, msgSize, completionHandler, force_send, max_queue_size); 396 | sent_to_any = true; 397 | } 398 | } 399 | } 400 | 401 | // HTTP 402 | for (auto const &wp : http_sessionPointerPool) 403 | { 404 | if (auto sp = wp.lock()) 405 | { 406 | if (sp->endpoint == endpoint) 407 | { 408 | sp->sendAsync(msgPtr, msgSize, completionHandler, force_send, max_queue_size); 409 | sent_to_any = true; 410 | } 411 | } 412 | } 413 | 414 | 415 | // HTTPS 416 | for (auto const &wp : https_sessionPointerPool) 417 | { 418 | if (auto sp = wp.lock()) 419 | { 420 | if (sp->endpoint == endpoint) 421 | { 422 | sp->sendAsync(msgPtr, msgSize, completionHandler, force_send, max_queue_size); 423 | sent_to_any = true; 424 | } 425 | } 426 | } 427 | 428 | if ( ! sent_to_any && completionHandler) 429 | { 430 | completionHandler(boost::asio::error::not_connected, 0, endpointT()); 431 | } 432 | } 433 | 434 | 435 | void shared_state::sendAsync(const std::string &str, bool force_send, size_t max_queue_size, bool to_ws, bool to_tcp) 436 | { 437 | std::shared_ptr strPtr = std::make_shared(str); 438 | 439 | sendAsync((void*)(strPtr->data()), strPtr->length(), [strPtr](beast::error_code, size_t, const endpointT &){}, force_send, max_queue_size, to_ws, to_tcp); 440 | } 441 | 442 | void shared_state::sendAsync(const boost::asio::ip::tcp::endpoint &endpoint, const std::string &str, bool force_send, size_t max_queue_size) 443 | { 444 | std::shared_ptr strPtr = std::make_shared(str); 445 | sendAsync(endpoint, (void*)(strPtr->data()), strPtr->length(), [strPtr](beast::error_code, size_t, const endpointT &){}, force_send, max_queue_size); 446 | } 447 | 448 | void shared_state::on_ws_read(const tcp::endpoint &endpoint, const void *msgPtr, size_t msgSize) 449 | { 450 | if (callbacks.callbackWSRead) 451 | callbacks.callbackWSRead(endpoint, msgPtr, msgSize); 452 | } 453 | 454 | void shared_state::on_http_read(const tcp::endpoint &endpoint, const void *msgPtr, size_t msgSize) 455 | { 456 | if (callbacks.callbackHTTPRead) 457 | callbacks.callbackHTTPRead(endpoint, msgPtr, msgSize); 458 | } 459 | 460 | void shared_state::on_error(const tcp::endpoint &endpoint, beast::error_code ec) 461 | { 462 | if (callbacks.callbackError) 463 | callbacks.callbackError(endpoint, ec); 464 | } 465 | 466 | std::vector shared_state::get_endpoints() 467 | { 468 | std::vector ret; 469 | 470 | std::lock_guard lock(mutex_); 471 | 472 | size_t nSessions = ws_sessions_.size() + wss_sessions_.size() + http_sessions_.size() + https_sessions_.size(); 473 | 474 | ret.reserve(nSessions); 475 | 476 | for (auto p : ws_sessions_) 477 | ret.emplace_back(p->endpoint); 478 | 479 | for (auto p : wss_sessions_) 480 | ret.emplace_back(p->endpoint); 481 | 482 | for (auto p : http_sessions_) 483 | ret.emplace_back(p->endpoint); 484 | 485 | for (auto p : https_sessions_) 486 | ret.emplace_back(p->endpoint); 487 | 488 | return ret; 489 | } 490 | 491 | } // namespace 492 | -------------------------------------------------------------------------------- /Networking/src/shared_state.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/vinniefalco/CppCon2018 8 | // 9 | // Modified by Keith Rausch 10 | 11 | #ifndef BEASTWEBSERVERFLEXIBLE_SHARED_STATE_HPP 12 | #define BEASTWEBSERVERFLEXIBLE_SHARED_STATE_HPP 13 | 14 | 15 | #include "net.hpp" 16 | #include "rate_limiter.hpp" 17 | 18 | 19 | // stuff for shared_state 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | 28 | namespace BeastNetworking 29 | { 30 | 31 | namespace 32 | { 33 | namespace beast = boost::beast; // from 34 | namespace http = beast::http; // from 35 | namespace websocket = beast::websocket; // from 36 | namespace net = boost::asio; // from 37 | namespace ssl = boost::asio::ssl; // from 38 | using tcp = boost::asio::ip::tcp; // from 39 | } 40 | 41 | 42 | 43 | // Forward declaration 44 | class plain_websocket_session; 45 | class ssl_websocket_session; 46 | class plain_http_session; 47 | class ssl_http_session; 48 | 49 | // Represents the shared server state 50 | class shared_state 51 | { 52 | std::string /*const*/ doc_root_; 53 | uint64_t read_message_max_ = 0; // 0 uses recomended size, 54 | bool server_is_tcp_instead_of_http_; 55 | size_t nonhttp_header_size_; 56 | 57 | // This mutex synchronizes all access to all sessions 58 | typedef std::recursive_mutex MutexT; 59 | mutable MutexT mutex_; 60 | 61 | // Keep a list of all the connected clients 62 | std::unordered_set ws_sessions_; 63 | std::unordered_set wss_sessions_; 64 | std::unordered_set http_sessions_; 65 | std::unordered_set https_sessions_; 66 | std::vector> ws_sessionPointerPool; // this is an attempt to find a slow-down in the code. consider removing 67 | std::vector> wss_sessionPointerPool; // this is an attempt to find a slow-down in the code. consider removing 68 | std::vector> http_sessionPointerPool; // this is an attempt to find a slow-down in the code. consider removing 69 | std::vector> https_sessionPointerPool; // this is an attempt to find a slow-down in the code. consider removing 70 | 71 | public: 72 | struct Callbacks 73 | { 74 | typedef boost::asio::ip::tcp::endpoint endpointT; 75 | typedef std::function CallbackReadT; 76 | typedef std::function CallbackSocketAcceptT; 77 | typedef std::function CallbackSocketUpgradeT; 78 | typedef std::function CallbackSocketDowngradeT; 79 | typedef std::function CallbackSocketCloseT; 80 | typedef std::function CallbackErrorT; 81 | typedef std::function CallbackPrintT; 82 | typedef std::function CallbackGetBodyLengthFromNonHTTPHeaderT; 83 | 84 | CallbackReadT callbackWSRead; 85 | CallbackReadT callbackHTTPRead; 86 | CallbackSocketAcceptT callbackAccept; 87 | CallbackSocketAcceptT callbackUpgrade; 88 | CallbackSocketAcceptT callbackDowngrade; 89 | CallbackSocketCloseT callbackClose; 90 | CallbackErrorT callbackError; 91 | CallbackPrintT callbackPrint = [](const std::string &str){ std::cout << str << std::endl; }; 92 | CallbackGetBodyLengthFromNonHTTPHeaderT callbackGetBodyLengthFromNonHTTPHeader; 93 | }; 94 | 95 | struct TimeoutSettings 96 | { 97 | int64_t ws_idle_timeout = 300; // integer seconds 98 | bool send_keep_alives = true; // set at intervals of half the idle time 99 | }; 100 | 101 | Callbacks callbacks; 102 | TimeoutSettings timeouts; 103 | RateLimiting::RateEnforcer::Args rate_enforcer_args; // default 104 | RateLimiting::sRateTracker rate_tracker; 105 | 106 | typedef boost::asio::ip::tcp::endpoint endpointT; 107 | typedef std::function CompletionHandlerT; 108 | typedef std::tuple SpanAndHandlerT; 109 | 110 | // std::vector queue_; // woah this is a vector, its even a vector in the beast example. weird. 111 | 112 | shared_state(); 113 | shared_state(std::string doc_root); 114 | shared_state(const Callbacks &callbacks_in); 115 | shared_state(std::string doc_root, const Callbacks &callbacks_in, bool server_is_tcp_instead_of_http_in_in=false, size_t nonhttp_header_size=0); 116 | 117 | std::string const& doc_root() const noexcept; 118 | 119 | uint64_t read_message_max(); 120 | void read_message_max(uint64_t size); 121 | 122 | void check_doc_root(); 123 | size_t nSessions(size_t &insecure, size_t &secure) const; 124 | size_t queue_sizes_summed() const; 125 | bool server_is_tcp_instead_of_http() const; 126 | size_t nonhttp_header_size() const; 127 | 128 | void upgrade(plain_websocket_session *ws_session); 129 | void downgrade(plain_websocket_session *ws_session); 130 | void upgrade(ssl_websocket_session *wss_session); 131 | void downgrade(ssl_websocket_session *wss_session); 132 | void join(plain_http_session *session); 133 | void leave(plain_http_session *session); 134 | void join(ssl_http_session *session); 135 | void leave(ssl_http_session *session); 136 | void on_ws_read(const boost::asio::ip::tcp::endpoint &endpoint, const void * msgPtr, size_t msgSize); 137 | void on_http_read(const boost::asio::ip::tcp::endpoint &endpoint, const void * msgPtr, size_t msgSize); 138 | void on_error(const boost::asio::ip::tcp::endpoint &endpoint, beast::error_code ec); 139 | 140 | 141 | void sendAsync(const void * msgPtr, size_t msgSize, const CompletionHandlerT &completionHandler = CompletionHandlerT(), bool force_send=false, size_t max_queue_size=std::numeric_limits::max(), bool to_ws=true, bool to_tcp=false); 142 | void sendAsync(const boost::asio::ip::tcp::endpoint &endpoint, const void * msgPtr, size_t msgSize, const CompletionHandlerT &completionHandler = CompletionHandlerT(), bool force_send=false, size_t max_queue_size=std::numeric_limits::max()); 143 | void sendAsync(const std::string &str, bool force_send=false, size_t max_queue_size=std::numeric_limits::max(), bool to_ws=true, bool to_tcp=false); 144 | void sendAsync(const boost::asio::ip::tcp::endpoint &endpoint, const std::string &str, bool force_send=false, size_t max_queue_size=std::numeric_limits::max()); 145 | 146 | std::vector get_endpoints(); 147 | }; 148 | 149 | } // namespace 150 | 151 | 152 | #endif -------------------------------------------------------------------------------- /Networking/src/websocket_session.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/vinniefalco/CppCon2018 8 | // 9 | // Modified by Keith Rausch 10 | 11 | #include "websocket_session.hpp" 12 | #include 13 | 14 | 15 | namespace BeastNetworking 16 | { 17 | 18 | 19 | template 20 | Derived& websocket_session::derived() 21 | { 22 | return static_cast(*this); 23 | } 24 | 25 | 26 | template 27 | void websocket_session::on_error(beast::error_code ec) 28 | { 29 | // Don't report these 30 | if (ec == net::error::operation_aborted || ec == websocket::error::closed) 31 | return; 32 | 33 | if (state_) 34 | state_->on_error(endpoint, ec); 35 | } 36 | 37 | 38 | 39 | 40 | 41 | 42 | template 43 | void websocket_session::on_resolve(beast::error_code ec, tcp::resolver::results_type results) 44 | { 45 | if (ec) 46 | return on_error(ec); 47 | 48 | // Set the timeout for the operation 49 | beast::get_lowest_layer(derived().ws()).expires_after(std::chrono::seconds(30)); 50 | 51 | // Make the connection on the IP address we get from a lookup 52 | beast::get_lowest_layer(derived().ws()).async_connect( 53 | results, 54 | beast::bind_front_handler( 55 | &Derived::on_connect, 56 | derived().shared_from_this())); 57 | } 58 | 59 | 60 | 61 | 62 | template 63 | void websocket_session::on_accept(beast::error_code ec) 64 | { 65 | // Handle the error, if any 66 | if (ec) 67 | return on_error(ec); 68 | 69 | // Add this session to the list of active sessions 70 | if (state_ && !upgraded.exchange(true)) 71 | state_->upgrade(&derived()); 72 | 73 | // Read a message 74 | derived().ws().async_read( 75 | buffer_, 76 | beast::bind_front_handler(&websocket_session::on_read, derived().shared_from_this())); 77 | } 78 | 79 | 80 | template 81 | void websocket_session::on_read( beast::error_code ec, std::size_t bytes_transferred) 82 | { 83 | // Handle the error, if any 84 | if (ec) 85 | return on_error(ec); 86 | 87 | // Send to all connections 88 | if (state_) 89 | state_->on_ws_read(endpoint, buffer_.data().data(), bytes_transferred); 90 | 91 | // Clear the buffer 92 | buffer_.consume(buffer_.size()); 93 | 94 | // Read another message 95 | derived().ws().async_read( 96 | buffer_, 97 | beast::bind_front_handler(&websocket_session::on_read, derived().shared_from_this())); 98 | } 99 | 100 | 101 | template 102 | void websocket_session::on_send(const void* msgPtr, size_t msgSize, shared_state::CompletionHandlerT &&completionHandler, bool force_send, size_t max_queue_size) 103 | { 104 | // update hypothetical send stats here 105 | rate_enforcer.update_hypothetical_rate(msgSize); 106 | 107 | // if we have more messages in the queue than allowed, we need to start blowing them away 108 | // BUT we cant blow away messages marked as 'must_send' nomatter what 109 | // se we are basically doing our best to honor the max_queue_size, but will exceed it if we need to 110 | // in order to not drop important messages 111 | max_queue_size = std::max(max_queue_size, (size_t)2); // the 1st element is being sent right now, cant replace it 112 | 113 | for (size_t i = 2; i < queue_.size() && queue_.size() >= max_queue_size ; ++i) 114 | { 115 | auto & msg = queue_[i]; 116 | auto & callback = std::get<2>(msg); 117 | auto force = std::get<3>(msg); 118 | if ( ! force) 119 | { 120 | if (callback) 121 | { 122 | callback(boost::asio::error::operation_aborted, 0, endpoint); 123 | callback = shared_state::CompletionHandlerT(); // not sure if this avoids move-sideeffects later 124 | } 125 | 126 | // queue_.pop_back(); 127 | queue_.erase(queue_.begin() + i); 128 | // queue size recorded further down 129 | --i; 130 | } 131 | } 132 | 133 | 134 | queue_.emplace_back(msgPtr, msgSize, std::move(completionHandler), force_send); 135 | queue_size_ = queue_.size(); 136 | 137 | // Are we already writing? 138 | if (queue_.size() > 1) 139 | return; 140 | 141 | bool may_send = rate_enforcer.should_send(msgSize) || force_send; 142 | may_send &= (msgPtr!=nullptr && msgSize>0); 143 | 144 | if ( may_send ) 145 | { 146 | // We are not currently writing, so send this immediately 147 | derived().ws().async_write( 148 | boost::asio::buffer(msgPtr, msgSize), 149 | beast::bind_front_handler(&websocket_session::on_write, derived().shared_from_this())); 150 | } 151 | else 152 | { 153 | net::post(derived().ws().get_executor(), 154 | beast::bind_front_handler(&websocket_session::on_write, derived().shared_from_this(), boost::asio::error::operation_aborted, 0) 155 | ); 156 | } 157 | 158 | } 159 | 160 | 161 | template 162 | void websocket_session::on_write( beast::error_code ec, std::size_t bytes_transferred) 163 | { 164 | // call the user's completion handler 165 | auto& handler = std::get<2>(queue_.front()); 166 | if (handler) 167 | handler(ec, bytes_transferred, endpoint); 168 | 169 | // Remove the string from the queue 170 | queue_.pop_front(); 171 | queue_size_ = queue_.size(); 172 | 173 | // Handle the error, if any 174 | if (ec) 175 | { 176 | on_error(ec); 177 | } 178 | 179 | // Send the next message if any 180 | if (!queue_.empty()) 181 | { 182 | auto &next = queue_.front(); 183 | const void *msgPtr = std::get<0>(next); 184 | size_t msgSize = std::get<1>(next); 185 | bool force = std::get<3>(next); 186 | 187 | bool may_send = rate_enforcer.should_send(msgSize) || force; 188 | may_send &= (msgPtr!=nullptr && msgSize>0); 189 | 190 | if (may_send) 191 | { 192 | derived().ws().async_write( 193 | boost::asio::buffer(msgPtr, msgSize), 194 | beast::bind_front_handler(&websocket_session::on_write, derived().shared_from_this())); 195 | } 196 | else 197 | { 198 | net::post(derived().ws().get_executor(), 199 | beast::bind_front_handler(&websocket_session::on_write, derived().shared_from_this(), boost::asio::error::operation_aborted, 0) 200 | ); 201 | } 202 | } 203 | } 204 | 205 | 206 | template 207 | void websocket_session::on_handshake(beast::error_code ec) 208 | { 209 | // 210 | // this function is only accessed when the user creates a websocket client 211 | // which goes through the RunClient, on_resolve, on_connect, on_handshake chain 212 | // 213 | // when creating a connection this way, the normal call to state_->upgrade(&derived()); 214 | // does not get executed, so we will call it here 215 | 216 | if (ec) 217 | return on_error(ec); 218 | 219 | derived().ws().binary(true); 220 | // https://stackoverflow.com/questions/7730260/binary-vs-string-transfer-over-a-stream 221 | // means bytes sent are bytes received, no UTF-8 text encode/decode 222 | 223 | // set max message length 224 | if (state_ && state_->read_message_max() > 0) 225 | { 226 | // NOTE: the following logic appeard to set the hint to 1536 despite what the documentation says 227 | // boost.org/doc/libs/develop/libs/beast/doc/html/beast/ref/boost__beast__websocket__stream/read_size_hint/overload1.html 228 | // uint64_t hint = (0 == ) ? derived().ws().read_size_hint() : state_->read_message_max(); 229 | // derived().ws().read_message_max(hint); 230 | 231 | 232 | derived().ws().read_message_max(state_->read_message_max()); 233 | } 234 | 235 | // Add this session to the list of active sessions 236 | if (state_ && ! upgraded.exchange(true)) 237 | state_->upgrade(&derived()); 238 | 239 | // Send the message 240 | derived().ws().async_read( 241 | buffer_, 242 | beast::bind_front_handler(&websocket_session::on_read, derived().shared_from_this())); 243 | } 244 | 245 | 246 | template 247 | websocket_session::websocket_session( 248 | std::shared_ptr const& state_in, 249 | const tcp::endpoint &endpoint_in) 250 | : state_(state_in), 251 | rate_enforcer(state_in && state_in->rate_tracker ? state_in->rate_tracker->make_enforcer(state_in->rate_enforcer_args) : RateLimiting::RateEnforcer()), 252 | serverPort(0), 253 | endpoint(endpoint_in), 254 | upgraded(false), queue_size_(0) 255 | { 256 | } 257 | 258 | template 259 | websocket_session::websocket_session(net::io_context &ioc, 260 | std::shared_ptr const& state_in, 261 | const std::string &serverAddress_in, 262 | unsigned short serverPort_in) 263 | : state_(state_in), 264 | resolver_(std::make_shared(net::make_strand(ioc))), 265 | rate_enforcer(state_in && state_in->rate_tracker ? state_in->rate_tracker->make_enforcer(state_in->rate_enforcer_args) : RateLimiting::RateEnforcer()), 266 | serverAddress(serverAddress_in), 267 | serverPort(serverPort_in), 268 | endpoint(), 269 | upgraded(false), queue_size_(0) 270 | { 271 | } 272 | 273 | 274 | template 275 | websocket_session::~websocket_session() 276 | { 277 | { 278 | // std::lock_guard guard(mutex_); 279 | while (queue_.size() > 0) 280 | { 281 | auto & callback = std::get<2>(queue_.front()); 282 | if (callback) 283 | callback(boost::asio::error::operation_aborted, 0, endpoint); 284 | queue_.pop_front(); 285 | queue_size_ = queue_.size(); 286 | } 287 | } 288 | 289 | // Remove this session from the list of active sessions 290 | if (state_ && upgraded.exchange(false)) 291 | { 292 | state_->downgrade(&derived()); 293 | } 294 | } 295 | 296 | 297 | template 298 | void websocket_session::sendAsync(const void* msgPtr, size_t msgSize, shared_state::CompletionHandlerT completionHandler /*mae copy*/, bool force_send, size_t max_queue_size) 299 | { 300 | // Post our work to the strand, this ensures 301 | // that the members of `this` will not be 302 | // accessed concurrently. 303 | 304 | net::post( 305 | derived().ws().get_executor(), 306 | beast::bind_front_handler(&websocket_session::on_send, derived().shared_from_this(), msgPtr, msgSize, std::move(completionHandler), force_send, max_queue_size)); 307 | } 308 | 309 | 310 | 311 | // Resolver and socket require an io_context 312 | template 313 | void websocket_session::RunClient() 314 | { 315 | // Look up the domain name 316 | resolver_->async_resolve( 317 | serverAddress, 318 | std::to_string(serverPort), 319 | beast::bind_front_handler( 320 | &websocket_session::on_resolve, 321 | derived().shared_from_this())); 322 | } 323 | 324 | 325 | template class websocket_session; 326 | template class websocket_session; 327 | 328 | 329 | 330 | 331 | 332 | 333 | //------------------------------------------------------------------------------ 334 | 335 | plain_websocket_session::plain_websocket_session( 336 | beast::tcp_stream&& stream, 337 | std::shared_ptr const& state, 338 | const tcp::endpoint &endpoint 339 | ) 340 | : websocket_session(state, endpoint), ws_(std::move(stream)) 341 | { 342 | } 343 | 344 | plain_websocket_session::plain_websocket_session( 345 | beast::tcp_stream&& stream, 346 | net::io_context &ioc, 347 | std::shared_ptr const& state, 348 | 349 | const std::string &serverAddress, 350 | unsigned short serverPort) 351 | : websocket_session(ioc, state, serverAddress, serverPort), ws_(std::move(stream)) 352 | { 353 | } 354 | 355 | // Called by the base class 356 | websocket::stream& 357 | plain_websocket_session::ws() 358 | { 359 | return ws_; 360 | } 361 | 362 | 363 | 364 | 365 | void plain_websocket_session::on_connect(beast::error_code ec, tcp::resolver::results_type::endpoint_type ep) 366 | { 367 | endpoint = ep; 368 | 369 | if (ec) 370 | return websocket_session::on_error(ec); 371 | 372 | // 373 | // TODO look at boost.org/doc/libs/1_75_0/libs/beast/example/websocket/server/fast/websocket_server_fast.cpp 374 | // for improved performance. It looks like they play with compression rations, etc 375 | // 376 | // 377 | 378 | // Turn off the timeout on the tcp_stream, because 379 | // the websocket stream has its own timeout system. 380 | beast::get_lowest_layer(ws_).expires_never(); 381 | 382 | // Set suggested timeout settings for the websocket 383 | ws_.set_option(get_timeout_settings_for_client()); 384 | 385 | // Set a decorator to change the User-Agent of the handshake 386 | ws_.set_option(websocket::stream_base::decorator( 387 | [](websocket::request_type &req) { 388 | req.set(http::field::user_agent, 389 | std::string(BOOST_BEAST_VERSION_STRING) + 390 | " websocket-client-async"); 391 | })); 392 | 393 | // Update the host_ string. This will provide the value of the 394 | // Host HTTP header during the WebSocket handshake. 395 | // See https://tools.ietf.org/html/rfc7230#section-5.4 396 | std::string host_ = serverAddress; 397 | host_ += ':' + std::to_string(ep.port()); 398 | 399 | // Perform the websocket handshake 400 | ws_.async_handshake(host_, "/", 401 | beast::bind_front_handler( 402 | &websocket_session::on_handshake, 403 | shared_from_this())); 404 | } 405 | 406 | 407 | //------------------------------------------------------------------------------ 408 | ssl_websocket_session::ssl_websocket_session( 409 | beast::ssl_stream&& stream, 410 | std::shared_ptr const& state, 411 | const tcp::endpoint &endpoint) 412 | : websocket_session(state, endpoint), ws_(std::move(stream)) 413 | { 414 | } 415 | 416 | 417 | ssl_websocket_session::ssl_websocket_session( 418 | beast::ssl_stream&& stream, 419 | net::io_context &ioc, 420 | std::shared_ptr const& state, 421 | const std::string &serverAddress, 422 | unsigned short serverPort) 423 | : websocket_session(ioc, state, serverAddress, serverPort), ws_(std::move(stream)) 424 | { 425 | } 426 | 427 | // Called by the base class 428 | websocket::stream>& 429 | ssl_websocket_session::ws() 430 | { 431 | return ws_; 432 | } 433 | 434 | 435 | void ssl_websocket_session::on_connect(beast::error_code ec, tcp::resolver::results_type::endpoint_type ep) 436 | { 437 | endpoint = ep; 438 | 439 | if(ec) 440 | { 441 | // fail(ec, "on_connect"); 442 | return; 443 | } 444 | 445 | // Update the host_ string. This will provide the value of the 446 | // Host HTTP header during the WebSocket handshake. 447 | // See https://tools.ietf.org/html/rfc7230#section-5.4 448 | // host_ += ':' + std::to_string(ep.port()); 449 | 450 | std::string host_ = serverAddress; 451 | host_ += ':' + std::to_string(ep.port()); 452 | 453 | // Set a timeout on the operation 454 | beast::get_lowest_layer(ws_).expires_after(std::chrono::seconds(30)); 455 | 456 | // Set SNI Hostname (many hosts need this to handshake successfully) 457 | if(! SSL_set_tlsext_host_name( 458 | ws_.next_layer().native_handle(), 459 | host_.c_str())) 460 | { 461 | ec = beast::error_code(static_cast(::ERR_get_error()), net::error::get_ssl_category()); 462 | // fail(ec, "on_connect"); 463 | return; 464 | } 465 | 466 | // Perform the SSL handshake 467 | ws_.next_layer().async_handshake( 468 | ssl::stream_base::client, 469 | beast::bind_front_handler( 470 | &ssl_websocket_session::on_ssl_handshake, 471 | shared_from_this())); 472 | } 473 | 474 | void ssl_websocket_session::on_ssl_handshake(beast::error_code ec) 475 | { 476 | 477 | if (ec) 478 | return on_error(ec); 479 | 480 | // 481 | // TODO look at boost.org/doc/libs/1_75_0/libs/beast/example/websocket/server/fast/websocket_server_fast.cpp 482 | // for improved performance. It looks like they play with compression rations, etc 483 | // 484 | // 485 | 486 | // Turn off the timeout on the tcp_stream, because 487 | // the websocket stream has its own timeout system. 488 | beast::get_lowest_layer(ws_).expires_never(); 489 | 490 | // Set suggested timeout settings for the websocket 491 | ws_.set_option(get_timeout_settings_for_client()); 492 | 493 | // Set a decorator to change the User-Agent of the handshake 494 | ws_.set_option(websocket::stream_base::decorator( 495 | [](websocket::request_type &req) { 496 | req.set(http::field::user_agent, 497 | std::string(BOOST_BEAST_VERSION_STRING) + 498 | " websocket-client-async"); 499 | })); 500 | 501 | // Update the host_ string. This will provide the value of the 502 | // Host HTTP header during the WebSocket handshake. 503 | // See https://tools.ietf.org/html/rfc7230#section-5.4 504 | std::string host_ = serverAddress; 505 | host_ += ':' + std::to_string(serverPort); // std::to_string(ep.port()); 506 | 507 | // Perform the websocket handshake 508 | ws_.async_handshake( host_ , "/", 509 | beast::bind_front_handler( 510 | &websocket_session::on_handshake, 511 | shared_from_this())); 512 | } 513 | 514 | 515 | 516 | 517 | 518 | 519 | } // namespace -------------------------------------------------------------------------------- /Networking/src/websocket_session.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | // 7 | // Official repository: https://github.com/vinniefalco/CppCon2018 8 | // 9 | // Modified by Keith Rausch 10 | 11 | #ifndef BEASTNETWORKING_WEBSOCKET_SESSION_HPP 12 | #define BEASTNETWORKING_WEBSOCKET_SESSION_HPP 13 | 14 | #include "net.hpp" 15 | // #include "beast.h" 16 | #include "shared_state.hpp" 17 | 18 | // #include 19 | // #include 20 | // #include 21 | // #include 22 | // #include 23 | 24 | #include 25 | #include "rate_limiter.hpp" 26 | 27 | 28 | namespace BeastNetworking 29 | { 30 | 31 | // Echoes back all received WebSocket messages. 32 | // This uses the Curiously Recurring Template Pattern so that 33 | // the same code works with both SSL streams and regular sockets. 34 | template 35 | class websocket_session 36 | { 37 | // Access the derived class, this is part of 38 | // the Curiously Recurring Template Pattern idiom. 39 | Derived& derived(); 40 | 41 | beast::flat_buffer buffer_; 42 | std::shared_ptr state_; 43 | std::deque queue_; // no mutex needed, only ever modified inside handlers, which are in a strand 44 | 45 | std::shared_ptr resolver_; 46 | RateLimiting::RateEnforcer rate_enforcer; 47 | std::atomic upgraded; 48 | std::atomic queue_size_; 49 | 50 | 51 | 52 | 53 | protected: 54 | void on_error(beast::error_code ec); 55 | void on_resolve(beast::error_code ec, tcp::resolver::results_type results); 56 | void on_accept(beast::error_code ec); 57 | void on_read( beast::error_code ec, std::size_t bytes_transferred); 58 | void on_send(const void* msgPtr, size_t msgSize, shared_state::CompletionHandlerT &&completionHandler, bool force_send=false, size_t max_queue_size=std::numeric_limits::max()); 59 | void on_write( beast::error_code ec, std::size_t bytes_transferred); 60 | 61 | public: 62 | std::string serverAddress; 63 | unsigned short serverPort; 64 | 65 | tcp::endpoint endpoint; 66 | 67 | 68 | 69 | void on_handshake(beast::error_code ec); 70 | 71 | // Start the asynchronous operation 72 | template 73 | void runServer(http::request> req) 74 | { 75 | // // Accept the WebSocket upgrade request 76 | // do_accept(std::move(req)); 77 | 78 | // Set suggested timeout settings for the websocket 79 | derived().ws().set_option(get_timeout_settings_for_server()); 80 | 81 | // Set a decorator to change the Server of the handshake 82 | derived().ws().set_option(websocket::stream_base::decorator( 83 | [](websocket::response_type& res) 84 | { 85 | res.set(http::field::server, 86 | std::string(BOOST_BEAST_VERSION_STRING) + 87 | " websocket-chat-multi"); 88 | })); 89 | 90 | // Accept the websocket handshake 91 | derived().ws().async_accept( 92 | req, 93 | beast::bind_front_handler( 94 | &websocket_session::on_accept, 95 | derived().shared_from_this())); 96 | 97 | 98 | derived().ws().binary(true); 99 | // https://stackoverflow.com/questions/7730260/binary-vs-string-transfer-over-a-stream 100 | // means bytes sent are bytes received, no UTF-8 text encode/decode 101 | } 102 | 103 | websocket_session( 104 | std::shared_ptr const& state_in, 105 | const tcp::endpoint &endpoint_in); 106 | 107 | websocket_session(net::io_context &ioc, 108 | std::shared_ptr const& state_in, 109 | const std::string &serverAddress_in, 110 | unsigned short serverPort_in) ; 111 | 112 | 113 | ~websocket_session(); 114 | 115 | 116 | void sendAsync(const void* msgPtr, size_t msgSize, shared_state::CompletionHandlerT completionHandler, bool force_send=false, size_t max_queue_size=std::numeric_limits::max()); 117 | 118 | // void replaceSendAsync(const void* msgPtr, size_t msgSize, shared_state::CompletionHandlerT &&completionHandler); 119 | 120 | // Resolver and socket require an io_context 121 | void RunClient(); 122 | 123 | websocket::stream_base::timeout get_timeout_settings_for_client() 124 | { 125 | // get default params and then tweak them 126 | auto timeout_params = websocket::stream_base::timeout::suggested(beast::role_type::client); 127 | 128 | if (state_) 129 | { 130 | auto ws_idle_timeout = state_->timeouts.ws_idle_timeout; // readability 131 | timeout_params.idle_timeout = ws_idle_timeout > 0 ? std::chrono::seconds(ws_idle_timeout) : websocket::stream_base::none(); 132 | timeout_params.keep_alive_pings = state_->timeouts.send_keep_alives; // ordinarily false 133 | } 134 | return timeout_params; 135 | } 136 | 137 | websocket::stream_base::timeout get_timeout_settings_for_server() 138 | { 139 | // get default params and then tweak them 140 | auto timeout_params = websocket::stream_base::timeout::suggested(beast::role_type::server); 141 | 142 | if (state_) 143 | { 144 | auto ws_idle_timeout = state_->timeouts.ws_idle_timeout; // readability 145 | timeout_params.idle_timeout = ws_idle_timeout > 0 ? std::chrono::seconds(ws_idle_timeout) : websocket::stream_base::none(); 146 | timeout_params.keep_alive_pings = state_->timeouts.send_keep_alives; // ordinarily true 147 | } 148 | return timeout_params; 149 | } 150 | 151 | size_t queue_size() const 152 | { 153 | return queue_size_; 154 | } 155 | }; 156 | 157 | //------------------------------------------------------------------------------ 158 | 159 | // Handles a plain WebSocket connection 160 | class plain_websocket_session 161 | : public websocket_session 162 | , public std::enable_shared_from_this 163 | { 164 | websocket::stream ws_; 165 | 166 | public: 167 | 168 | // Create the session 169 | explicit 170 | plain_websocket_session( 171 | beast::tcp_stream&& stream, 172 | std::shared_ptr const& state, 173 | const tcp::endpoint &endpoint 174 | ); 175 | 176 | explicit 177 | plain_websocket_session( 178 | beast::tcp_stream&& stream, 179 | net::io_context &ioc, 180 | std::shared_ptr const& state, 181 | 182 | const std::string &serverAddress, 183 | unsigned short serverPort); 184 | 185 | // Called by the base class 186 | websocket::stream& 187 | ws(); 188 | 189 | void on_connect(beast::error_code ec, tcp::resolver::results_type::endpoint_type ep); 190 | 191 | }; 192 | 193 | //------------------------------------------------------------------------------ 194 | 195 | // Handles an SSL WebSocket connection 196 | class ssl_websocket_session 197 | : public websocket_session 198 | , public std::enable_shared_from_this 199 | { 200 | websocket::stream> ws_; 201 | 202 | public: 203 | 204 | // Create the ssl_websocket_session 205 | explicit 206 | ssl_websocket_session( 207 | beast::ssl_stream&& stream, 208 | std::shared_ptr const& state, 209 | const tcp::endpoint &endpoint); 210 | 211 | explicit 212 | ssl_websocket_session( 213 | beast::ssl_stream&& stream, 214 | net::io_context &ioc, 215 | std::shared_ptr const& state, 216 | const std::string &serverAddress, 217 | unsigned short serverPort); 218 | 219 | // Called by the base class 220 | websocket::stream>& 221 | ws(); 222 | 223 | 224 | void on_connect(beast::error_code ec, tcp::resolver::results_type::endpoint_type ep); 225 | 226 | void on_ssl_handshake(beast::error_code ec); 227 | 228 | }; 229 | 230 | 231 | 232 | 233 | template 234 | static void 235 | make_websocket_session_server( 236 | beast::tcp_stream stream, 237 | std::shared_ptr const& state, 238 | const tcp::endpoint &endpoint, 239 | http::request> req) 240 | { 241 | std::make_shared( 242 | std::move(stream), state, endpoint)->runServer(std::move(req)); 243 | } 244 | 245 | template 246 | static void 247 | make_websocket_session_server( 248 | beast::ssl_stream stream, 249 | std::shared_ptr const& state, 250 | const tcp::endpoint &endpoint, 251 | http::request> req) 252 | { 253 | std::make_shared( 254 | std::move(stream), state, endpoint)->runServer(std::move(req)); 255 | } 256 | 257 | 258 | 259 | 260 | 261 | // template 262 | static std::shared_ptr 263 | make_websocket_session_client(net::io_context &ioc, 264 | std::shared_ptr const& state, 265 | const std::string &serverAddress, 266 | unsigned short serverPort) 267 | { 268 | return std::make_shared(beast::tcp_stream(net::make_strand(ioc)), ioc, state, serverAddress, serverPort); 269 | } 270 | 271 | // template 272 | static std::shared_ptr 273 | make_websocket_session_client(net::io_context &ioc, 274 | ssl::context& ctx, 275 | std::shared_ptr const& state, 276 | const std::string &serverAddress, 277 | unsigned short serverPort) 278 | { 279 | return std::make_shared(beast::ssl_stream(net::make_strand(ioc), ctx), ioc, state, serverAddress, serverPort); 280 | } 281 | 282 | 283 | 284 | } // namespace 285 | 286 | 287 | #endif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## About and Features 2 | This library offers a set of c++ tools for creating memory pools in interprocess-shared memory as well as a few utilities for facilitating interprocess communication and automatic device discovery. It's built on top of `Boost::IPC` and `Boost::asio` and is tested on Linux (primarily) and Windows. 3 | 4 | All IPC objects are encapsulated by shared pointers that track the usage counter not just in your app, but inside all other processes as well. The result is a smart pointer that that keeps its owned object _and its dependencies_ alive until references in every process naturally fall out of scope, just like a traditional `std::shared_ptr` would. Memory allocation is handled at the `Allocator` level, meaning that _even the shared pointer's control block is accounted for and included in the pool size_ - no dynamic allocation happens when creating new shared pointers (yeah, that was not fun). The Interprocess router supports timeouts for reading / writing, and memory pools support automatic growth when exhausted. 5 | 6 | The network transmission utilities use broadcast/ multicast for topic announcement (not required) and websockets for two-way data transmission in one-to-many and many-to-one configurations. Serialization is handled by the user, and callback functions / completion handlers can be set for message receipt, broadcast receipt, connection open / close, and much more. 7 | 8 | c++11 9 | 10 | ## Examples 11 | 12 | #### IPC Memory Pool: 13 | ```cpp 14 | // 15 | // process A (producer) 16 | // 17 | { 18 | typedef MyClass T; // type to pool and route 19 | typedef MemPoolIPC PoolT; 20 | typedef utils_ipc::RouterIPC RouterT; 21 | 22 | // ask OS for memory region and map it into address space 23 | auto segmentPtr = utils_ipc::open_or_create_mapping("MySharedMemory", nBytes); 24 | // get pool and router types. each hold references to segmentPtr to keep it in scope 25 | auto poolPtr = utils_ipc::find_or_create_shared_object(segmentPtr, "pool", segmentPtr->get_segment_manager(), poolSize); 26 | auto routerPtr = utils_ipc::find_or_create_shared_object(segmentPtr, "Router", segmentPtr->get_segment_manager()); 27 | 28 | 29 | // send data 30 | for (/**/) 31 | { 32 | // get element from the pool 33 | auto elementPtr = poolPtr->make_pooled(/* constructor args */); // holds poolPtr in scope 34 | // route it 35 | routerPtr->Send(elementPtr, RouterT::ENQUEUE_MODE::WAIT_IF_FULL, 1); 36 | } 37 | 38 | // when process exits, counters for elements, pool, router, and segment will decrement and 39 | // destruct if and only if all processes are done with them 40 | } 41 | 42 | ``` 43 | 44 | ```cpp 45 | // 46 | // process B (consumer) 47 | // 48 | { 49 | typedef MyClass T; // type to pool and route 50 | typedef MemPoolIPC PoolT; 51 | typedef utils_ipc::RouterIPC RouterT; 52 | 53 | // ask OS for memory region and map it into address space 54 | auto segmentPtr = utils_ipc::open_or_create_mapping("MySharedMemory", nBytes); 55 | // get pool and router types. each hold references to segmentPtr to keep it in scope 56 | auto routerPtr = utils_ipc::find_or_create_shared_object(segmentPtr, "Router", segmentPtr->get_segment_manager()); 57 | 58 | 59 | // receive data 60 | bool timedout = false; 61 | while (*pipePtr && !timedout) 62 | { 63 | RouterT::sPtrT elementPtr; 64 | timedout = pipePtr->Receive(elementPtr, 5); // timed_wait because we can 65 | if (timedout || ! elementPtr) 66 | continue; 67 | 68 | // do something with elementPtr 69 | } 70 | 71 | // when process exits, counters for elements, pool, router, and segment will decrement and 72 | // destruct if and only if all processes are done with them 73 | } 74 | 75 | ``` 76 | #### Automatic Network Discovery and Transmission: 77 | ```cpp 78 | // 79 | // process A (producer / sender) 80 | // 81 | { 82 | // the type to route 83 | typedef MyClassIPC T; 84 | typedef MemPoolIPC PoolT; 85 | 86 | // ask OS for memory region and map it into address space 87 | auto segmentPtr = utils_ipc::open_or_create_mapping("MySharedMemory", nBytes); 88 | auto poolPtr = utils_ipc::find_or_create_shared_object(segmentPtr, "pool", segmentPtr->get_segment_manager(), poolSize); 89 | 90 | // create io_context (work_guarded) 91 | utils_asio::GuardedContext guarded_context(nThreads); 92 | 93 | // the magic is right here 94 | MultiClientSenderArgs args; 95 | MultiClientSender sender(guarded_context.GetIOContext(), "myTopic", args, poolPtr->UniqueInstanceID()); 96 | 97 | // start heartbeat (UDP breadcast of topic name and other misc info). also starts sending server 98 | sender.StartHeartbeat(); 99 | 100 | for (/**/) 101 | { 102 | // make up some data 103 | auto ptr = poolPtr->make_pooled(/* constructor args */); 104 | // send it. serialize it somehow, maybe it's a plain structor and we can just send (&*ptr) and sizeof() 105 | sender.SendAsync(msgPtr, msgSize, [ptr](boost::beast::error_code, size_t){}); // callback handler keeps element and its pool in scope 106 | } 107 | } 108 | 109 | ``` 110 | ```cpp 111 | // 112 | // process B (consumer / listener / receiver) 113 | // 114 | { 115 | // the type to route 116 | typedef MyClassIPC T; 117 | typedef MemPoolIPC PoolT; 118 | typedef utils_ipc::RouterIPC RouterT; 119 | 120 | auto segmentPtr = utils_ipc::open_or_create_mapping("MySharedMemory", nBytes); 121 | auto poolPtr = utils_ipc::find_or_create_shared_object(segmentPtr, "pool", segmentPtr->get_segment_manager(), poolSize); 122 | auto routerPtr = utils_ipc::find_or_create_shared_object(segmentPtr, "Router", segmentPtr->get_segment_manager()); 123 | 124 | // 125 | // setup all the callbacks for each topic. whenever we receive a message for a topic, fire one of these 126 | // 127 | MultiClientReceiver::TopicCallbacksT receivableTopics; 128 | 129 | auto callbackRead = [poolPtr, routerPtr](const tcp::endpoint &endpoint, void *msgPtr, size_t msgSize) 130 | { 131 | // do something with the recieved element. or just route it to everyone else on your system 132 | auto elementPtr = poolPtr->make_pooled(msgPtr, msgSize); 133 | size_t nPipesHadError = routerPtr->Send(elementPtr, RouterT::ENQUEUE_MODE::SOFT_FAIL_IF_FULL); 134 | }; // lambda keeps pool and router in scope and therefore segment 135 | 136 | receivableTopics["myTopic"].callbackRead = callbackRead; 137 | 138 | // if you had more topics, you would add them here 139 | // receivableTopics["someOtherTopic"] = ... 140 | 141 | // create io_context (work_guarded) 142 | utils_asio::GuardedContext guarded_context(nThreads - 1); // use the last thread to block 143 | 144 | MultiClientReceiverArgs args; 145 | MultiClientReceiver receiver(guarded_context.GetIOContext(), receivableTopics, args, poolPtr->UniqueInstanceID()); 146 | receiver.ListenForTopics(); // start listening server running 147 | 148 | guarded_context.GetIOContext().run(); // block until the receiver timeout out 149 | } 150 | 151 | ``` 152 | 153 | ## Other Utilities 154 | - Memory Pool for local-process only usage (don`t need IPC if there's only one process) 155 | - Network utilities for automatic device discovery. Topic names are broadcast (or multicast), and a receiver will initiate a websocket connection to the sender if the topic name matches 156 | - Automatically grow memory pools if they are exhausted. You'll eat the dynamic memory allocation, but the old pool will self-destruct once all references to it fall out of scope 157 | - Array versions of the memory pools so that you can get batches of elements at once (think array of pixels for an image) 158 | 159 | 160 | ## TODO 161 | - Change some prints to `cerr` 162 | - General cleanup 163 | - Support multicast instead of only broadcast -------------------------------------------------------------------------------- /rights_release_MC-04228.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keithrausch/InterprocessMemPool/e5f4fdb26490e57b4682673cad0df38543a5688a/rights_release_MC-04228.pdf --------------------------------------------------------------------------------