diff --git a/.gitignore b/.gitignore index 9bca0610..b8573713 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ ui_*.h *.jsc Makefile* *build-* +*build_* # Qt unit tests target_wrapper.* diff --git a/3rdparty/gtest/src/gtest-death-test.cc b/3rdparty/gtest/src/gtest-death-test.cc index da09a1cf..7419ba2c 100644 --- a/3rdparty/gtest/src/gtest-death-test.cc +++ b/3rdparty/gtest/src/gtest-death-test.cc @@ -1296,8 +1296,8 @@ static void StackLowerThanAddress(const void* ptr, bool* result) { GTEST_ATTRIBUTE_NO_SANITIZE_ADDRESS_ GTEST_ATTRIBUTE_NO_SANITIZE_HWADDRESS_ static bool StackGrowsDown() { - int dummy; - bool result; + int dummy {}; + bool result {}; StackLowerThanAddress(&dummy, &result); return result; } diff --git a/CMakeLists.txt b/CMakeLists.txt index 6d9b2a57..7e07dd3c 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,6 +59,13 @@ if (LIBIPC_BUILD_DEMOS) add_subdirectory(demo/chat) add_subdirectory(demo/msg_que) add_subdirectory(demo/send_recv) + if (MSVC) + add_subdirectory(demo/win_service/service) + add_subdirectory(demo/win_service/client) + else() + add_subdirectory(demo/linux_service/service) + add_subdirectory(demo/linux_service/client) + endif() endif() install( diff --git a/README.md b/README.md index eeb7f2f4..5f3e1198 100755 --- a/README.md +++ b/README.md @@ -1,22 +1,21 @@ -# cpp-ipc(libipc) - C++ IPC Library +# cpp-ipc (libipc) - C++ IPC Library -[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/mutouyun/cpp-ipc/blob/master/LICENSE) +[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/mutouyun/cpp-ipc/blob/master/LICENSE) [![Build Status](https://github.com/mutouyun/cpp-ipc/actions/workflows/c-cpp.yml/badge.svg)](https://github.com/mutouyun/cpp-ipc/actions) [![Build status](https://ci.appveyor.com/api/projects/status/github/mutouyun/cpp-ipc?branch=master&svg=true)](https://ci.appveyor.com/project/mutouyun/cpp-ipc) [![Vcpkg package](https://img.shields.io/badge/Vcpkg-package-blueviolet)](https://github.com/microsoft/vcpkg/tree/master/ports/cpp-ipc) - -A high-performance inter-process communication using shared memory on Linux/Windows. -使用共享内存的跨平台(Linux/Windows,x86/x64/ARM)高性能IPC通讯库。 - - * 推荐支持C++17的编译器(msvc-2017/gcc-7/clang-4) - * 除STL外,无其他依赖 - * 无锁(lock-free)或轻量级spin-lock - * 底层数据结构为循环数组(circular array) - * `ipc::route`支持单写多读,`ipc::channel`支持多写多读【**注意:目前同一条通道最多支持32个receiver,sender无限制**】 - * 默认采用广播模式收发数据,支持用户任意选择读写方案 - * 不会长时间忙等(重试一定次数后会使用信号量进行等待),支持超时 - * 支持[Vcpkg](https://github.com/microsoft/vcpkg/blob/master/README_zh_CN.md)方式安装,如`vcpkg install cpp-ipc` - + +## A high-performance inter-process communication library using shared memory on Linux/Windows. + + * Compilers with C++17 support are recommended (msvc-2017/gcc-7/clang-4) + * No other dependencies except STL. + * Only lock-free or lightweight spin-lock is used. + * Circular array is used as the underline data structure. + * `ipc::route` supports single read and multiple write. `ipc::channel` supports multiple read and write. (**Note: currently, a channel supports up to 32 receivers, but there is no such a limit for the sender.**) + * Broadcasting is used by default, but user can choose any read/ write combinations. + * No long time blind wait. (Semaphore will be used after a certain number of retries.) + * [Vcpkg](https://github.com/microsoft/vcpkg/blob/master/README.md) way of installation is supported. E.g. `vcpkg install cpp-ipc` + ## Usage See: [Wiki](https://github.com/mutouyun/cpp-ipc/wiki) @@ -31,7 +30,7 @@ See: [Wiki](https://github.com/mutouyun/cpp-ipc/wiki) OS | Windows 7 Ultimate x64 Compiler | MSVC 2017 15.9.4 -UT & benchmark test function: [test](test) +Unit & benchmark tests: [test](test) Performance data: [performance.xlsx](performance.xlsx) ## Reference @@ -41,3 +40,42 @@ Performance data: [performance.xlsx](performance.xlsx) * [Lock-Free 编程 | 匠心十年 - 博客园](http://www.cnblogs.com/gaochundong/p/lock_free_programming.html) * [无锁队列的实现 | 酷 壳 - CoolShell](https://coolshell.cn/articles/8239.html) * [Implementing Condition Variables with Semaphores](https://www.microsoft.com/en-us/research/wp-content/uploads/2004/12/ImplementingCVs.pdf) + +------ + + +## 使用共享内存的跨平台(Linux/Windows,x86/x64/ARM)高性能IPC通讯库 + + * 推荐支持C++17的编译器(msvc-2017/gcc-7/clang-4) + * 除STL外,无其他依赖 + * 无锁(lock-free)或轻量级spin-lock + * 底层数据结构为循环数组(circular array) + * `ipc::route`支持单写多读,`ipc::channel`支持多写多读【**注意:目前同一条通道最多支持32个receiver,sender无限制**】 + * 默认采用广播模式收发数据,支持用户任意选择读写方案 + * 不会长时间忙等(重试一定次数后会使用信号量进行等待),支持超时 + * 支持[Vcpkg](https://github.com/microsoft/vcpkg/blob/master/README_zh_CN.md)方式安装,如`vcpkg install cpp-ipc` + +## 使用方法 + +详见:[Wiki](https://github.com/mutouyun/cpp-ipc/wiki) + +## 性能 + +| 环境 | 值 | +| -------- | -------------------------------- | +| 设备 | 联想 ThinkPad T450 | +| CPU | 英特尔® Core™ i5-4300U @ 2.5 GHz | +| 内存 | 16 GB | +| 操作系统 | Windows 7 Ultimate x64 | +| 编译器 | MSVC 2017 15.9.4 | + +单元测试和Benchmark测试: [test](test) +性能数据: [performance.xlsx](performance.xlsx) + +## 参考 + + * [Lock-Free Data Structures | Dr Dobb's](http://www.drdobbs.com/lock-free-data-structures/184401865) + * [Yet another implementation of a lock-free circular array queue | CodeProject](https://www.codeproject.com/Articles/153898/Yet-another-implementation-of-a-lock-free-circular) + * [Lock-Free 编程 | 匠心十年 - 博客园](http://www.cnblogs.com/gaochundong/p/lock_free_programming.html) + * [无锁队列的实现 | 酷 壳 - CoolShell](https://coolshell.cn/articles/8239.html) + * [Implementing Condition Variables with Semaphores](https://www.microsoft.com/en-us/research/wp-content/uploads/2004/12/ImplementingCVs.pdf) diff --git a/demo/linux_service/client/CMakeLists.txt b/demo/linux_service/client/CMakeLists.txt new file mode 100644 index 00000000..b7d83920 --- /dev/null +++ b/demo/linux_service/client/CMakeLists.txt @@ -0,0 +1,8 @@ +project(linux_client) + +file(GLOB SRC_FILES ./*.cpp) +file(GLOB HEAD_FILES ./*.h) + +add_executable(${PROJECT_NAME} ${SRC_FILES} ${HEAD_FILES}) + +target_link_libraries(${PROJECT_NAME} ipc) diff --git a/demo/linux_service/client/main.cpp b/demo/linux_service/client/main.cpp new file mode 100644 index 00000000..9839be92 --- /dev/null +++ b/demo/linux_service/client/main.cpp @@ -0,0 +1,28 @@ +/// \brief To create a basic command line program. + +#include +#include +#include + +#include "libipc/ipc.h" + +int main(int argc, char *argv[]) { + printf("My Sample Client: Entry\n"); + ipc::channel ipc_r{"service ipc r", ipc::receiver}; + ipc::channel ipc_w{"service ipc w", ipc::sender}; + while (1) { + auto msg = ipc_r.recv(); + if (msg.empty()) { + printf("My Sample Client: message recv error\n"); + return -1; + } + printf("My Sample Client: message recv: [%s]\n", (char const *)msg.data()); + while (!ipc_w.send("Copy.")) { + printf("My Sample Client: message send error\n"); + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + printf("My Sample Client: message send [Copy]\n"); + } + printf("My Sample Client: Exit\n"); + return 0; +} diff --git a/demo/linux_service/service/CMakeLists.txt b/demo/linux_service/service/CMakeLists.txt new file mode 100644 index 00000000..a8773377 --- /dev/null +++ b/demo/linux_service/service/CMakeLists.txt @@ -0,0 +1,8 @@ +project(linux_service) + +file(GLOB SRC_FILES ./*.cpp) +file(GLOB HEAD_FILES ./*.h) + +add_executable(${PROJECT_NAME} ${SRC_FILES} ${HEAD_FILES}) + +target_link_libraries(${PROJECT_NAME} ipc) diff --git a/demo/linux_service/service/main.cpp b/demo/linux_service/service/main.cpp new file mode 100644 index 00000000..cf8dec37 --- /dev/null +++ b/demo/linux_service/service/main.cpp @@ -0,0 +1,34 @@ +/// \brief To create a basic command line program. + +#include +#include +#include +#include + +#include "libipc/ipc.h" + +int main(int argc, char *argv[]) { + printf("My Sample Service: Main: Entry\n"); + + ipc::channel ipc_r{"service ipc r", ipc::sender}; + ipc::channel ipc_w{"service ipc w", ipc::receiver}; + + while (1) { + if (!ipc_r.send("Hello, World!")) { + printf("My Sample Service: send failed.\n"); + } + else { + printf("My Sample Service: send [Hello, World!]\n"); + auto msg = ipc_w.recv(1000); + if (msg.empty()) { + printf("My Sample Service: recv error\n"); + } else { + printf("%s\n", (std::string{"My Sample Service: recv ["} + msg.get() + "]").c_str()); + } + } + std::this_thread::sleep_for(std::chrono::seconds(3)); + } + + printf("My Sample Service: Main: Exit\n"); + return 0; +} diff --git a/demo/win_service/client/CMakeLists.txt b/demo/win_service/client/CMakeLists.txt new file mode 100644 index 00000000..2dc03d5a --- /dev/null +++ b/demo/win_service/client/CMakeLists.txt @@ -0,0 +1,8 @@ +project(win_client) + +file(GLOB SRC_FILES ./*.cpp) +file(GLOB HEAD_FILES ./*.h) + +add_executable(${PROJECT_NAME} ${SRC_FILES} ${HEAD_FILES}) + +target_link_libraries(${PROJECT_NAME} ipc) diff --git a/demo/win_service/client/main.cpp b/demo/win_service/client/main.cpp new file mode 100644 index 00000000..44a9ba62 --- /dev/null +++ b/demo/win_service/client/main.cpp @@ -0,0 +1,41 @@ +/// \brief To create a basic Windows command line program. + +#include +#include +#include + +#include "libipc/ipc.h" + +int _tmain (int argc, TCHAR *argv[]) { + _tprintf(_T("My Sample Client: Entry\n")); + ipc::channel ipc_r{ipc::prefix{"Global\\"}, "service ipc r", ipc::receiver}; + ipc::channel ipc_w{ipc::prefix{"Global\\"}, "service ipc w", ipc::sender}; + while (1) { + if (!ipc_r.reconnect(ipc::receiver)) { + Sleep(1000); + continue; + } + auto msg = ipc_r.recv(); + if (msg.empty()) { + _tprintf(_T("My Sample Client: message recv error\n")); + ipc_r.disconnect(); + continue; + } + printf("My Sample Client: message recv: [%s]\n", (char const *)msg.data()); + for (;;) { + if (!ipc_w.reconnect(ipc::sender)) { + Sleep(1000); + continue; + } + if (ipc_w.send("Copy.")) { + break; + } + _tprintf(_T("My Sample Client: message send error\n")); + ipc_w.disconnect(); + Sleep(1000); + } + _tprintf(_T("My Sample Client: message send [Copy]\n")); + } + _tprintf(_T("My Sample Client: Exit\n")); + return 0; +} diff --git a/demo/win_service/service/CMakeLists.txt b/demo/win_service/service/CMakeLists.txt new file mode 100644 index 00000000..b7bb3574 --- /dev/null +++ b/demo/win_service/service/CMakeLists.txt @@ -0,0 +1,8 @@ +project(win_service) + +file(GLOB SRC_FILES ./*.cpp) +file(GLOB HEAD_FILES ./*.h) + +add_executable(${PROJECT_NAME} ${SRC_FILES} ${HEAD_FILES}) + +target_link_libraries(${PROJECT_NAME} ipc) diff --git a/demo/win_service/service/main.cpp b/demo/win_service/service/main.cpp new file mode 100644 index 00000000..ce522280 --- /dev/null +++ b/demo/win_service/service/main.cpp @@ -0,0 +1,189 @@ +/// \brief To create a basic Windows Service in C++. +/// \see https://www.codeproject.com/Articles/499465/Simple-Windows-Service-in-Cplusplus + +#include +#include +#include + +#include "libipc/ipc.h" + +SERVICE_STATUS g_ServiceStatus = {0}; +SERVICE_STATUS_HANDLE g_StatusHandle = NULL; +HANDLE g_ServiceStopEvent = INVALID_HANDLE_VALUE; + +VOID WINAPI ServiceMain (DWORD argc, LPTSTR *argv); +VOID WINAPI ServiceCtrlHandler (DWORD); +DWORD WINAPI ServiceWorkerThread (LPVOID lpParam); + +#define SERVICE_NAME _T("My Sample Service") + +int _tmain (int argc, TCHAR *argv[]) { + OutputDebugString(_T("My Sample Service: Main: Entry")); + + SERVICE_TABLE_ENTRY ServiceTable[] = { + {SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION) ServiceMain}, + {NULL, NULL} + }; + + if (StartServiceCtrlDispatcher (ServiceTable) == FALSE) { + OutputDebugString(_T("My Sample Service: Main: StartServiceCtrlDispatcher returned error")); + return GetLastError (); + } + + OutputDebugString(_T("My Sample Service: Main: Exit")); + return 0; +} + +VOID WINAPI ServiceMain (DWORD argc, LPTSTR *argv) { + DWORD Status = E_FAIL; + + OutputDebugString(_T("My Sample Service: ServiceMain: Entry")); + + g_StatusHandle = RegisterServiceCtrlHandler (SERVICE_NAME, ServiceCtrlHandler); + + if (g_StatusHandle == NULL) { + OutputDebugString(_T("My Sample Service: ServiceMain: RegisterServiceCtrlHandler returned error")); + goto EXIT; + } + + // Tell the service controller we are starting + ZeroMemory (&g_ServiceStatus, sizeof (g_ServiceStatus)); + g_ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + g_ServiceStatus.dwControlsAccepted = 0; + g_ServiceStatus.dwCurrentState = SERVICE_START_PENDING; + g_ServiceStatus.dwWin32ExitCode = 0; + g_ServiceStatus.dwServiceSpecificExitCode = 0; + g_ServiceStatus.dwCheckPoint = 0; + + if (SetServiceStatus (g_StatusHandle, &g_ServiceStatus) == FALSE) { + OutputDebugString(_T("My Sample Service: ServiceMain: SetServiceStatus returned error")); + } + + /* + * Perform tasks neccesary to start the service here + */ + OutputDebugString(_T("My Sample Service: ServiceMain: Performing Service Start Operations")); + + // Create stop event to wait on later. + g_ServiceStopEvent = CreateEvent (NULL, TRUE, FALSE, NULL); + if (g_ServiceStopEvent == NULL) { + OutputDebugString(_T("My Sample Service: ServiceMain: CreateEvent(g_ServiceStopEvent) returned error")); + + g_ServiceStatus.dwControlsAccepted = 0; + g_ServiceStatus.dwCurrentState = SERVICE_STOPPED; + g_ServiceStatus.dwWin32ExitCode = GetLastError(); + g_ServiceStatus.dwCheckPoint = 1; + + if (SetServiceStatus (g_StatusHandle, &g_ServiceStatus) == FALSE) { + OutputDebugString(_T("My Sample Service: ServiceMain: SetServiceStatus returned error")); + } + goto EXIT; + } + + // Tell the service controller we are started + g_ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; + g_ServiceStatus.dwCurrentState = SERVICE_RUNNING; + g_ServiceStatus.dwWin32ExitCode = 0; + g_ServiceStatus.dwCheckPoint = 0; + + if (SetServiceStatus (g_StatusHandle, &g_ServiceStatus) == FALSE) { + OutputDebugString(_T("My Sample Service: ServiceMain: SetServiceStatus returned error")); + } + + // Start the thread that will perform the main task of the service + HANDLE hThread = CreateThread (NULL, 0, ServiceWorkerThread, NULL, 0, NULL); + + OutputDebugString(_T("My Sample Service: ServiceMain: Waiting for Worker Thread to complete")); + + // Wait until our worker thread exits effectively signaling that the service needs to stop + WaitForSingleObject (hThread, INFINITE); + + OutputDebugString(_T("My Sample Service: ServiceMain: Worker Thread Stop Event signaled")); + + + /* + * Perform any cleanup tasks + */ + OutputDebugString(_T("My Sample Service: ServiceMain: Performing Cleanup Operations")); + + CloseHandle (g_ServiceStopEvent); + + g_ServiceStatus.dwControlsAccepted = 0; + g_ServiceStatus.dwCurrentState = SERVICE_STOPPED; + g_ServiceStatus.dwWin32ExitCode = 0; + g_ServiceStatus.dwCheckPoint = 3; + + if (SetServiceStatus (g_StatusHandle, &g_ServiceStatus) == FALSE) { + OutputDebugString(_T("My Sample Service: ServiceMain: SetServiceStatus returned error")); + } + +EXIT: + OutputDebugString(_T("My Sample Service: ServiceMain: Exit")); + return; +} + +VOID WINAPI ServiceCtrlHandler (DWORD CtrlCode) { + OutputDebugString(_T("My Sample Service: ServiceCtrlHandler: Entry")); + + switch (CtrlCode) { + case SERVICE_CONTROL_STOP : + + OutputDebugString(_T("My Sample Service: ServiceCtrlHandler: SERVICE_CONTROL_STOP Request")); + + if (g_ServiceStatus.dwCurrentState != SERVICE_RUNNING) + break; + + /* + * Perform tasks neccesary to stop the service here + */ + + g_ServiceStatus.dwControlsAccepted = 0; + g_ServiceStatus.dwCurrentState = SERVICE_STOP_PENDING; + g_ServiceStatus.dwWin32ExitCode = 0; + g_ServiceStatus.dwCheckPoint = 4; + + if (SetServiceStatus (g_StatusHandle, &g_ServiceStatus) == FALSE) { + OutputDebugString(_T("My Sample Service: ServiceCtrlHandler: SetServiceStatus returned error")); + } + + // This will signal the worker thread to start shutting down + SetEvent (g_ServiceStopEvent); + + break; + + default: + break; + } + + OutputDebugString(_T("My Sample Service: ServiceCtrlHandler: Exit")); +} + +DWORD WINAPI ServiceWorkerThread (LPVOID lpParam) { + OutputDebugString(_T("My Sample Service: ServiceWorkerThread: Entry")); + ipc::channel ipc_r{ipc::prefix{"Global\\"}, "service ipc r", ipc::sender}; + ipc::channel ipc_w{ipc::prefix{"Global\\"}, "service ipc w", ipc::receiver}; + + // Periodically check if the service has been requested to stop + while (WaitForSingleObject(g_ServiceStopEvent, 0) != WAIT_OBJECT_0) { + /* + * Perform main service function here + */ + if (!ipc_r.send("Hello, World!")) { + OutputDebugString(_T("My Sample Service: send failed.")); + } + else { + OutputDebugString(_T("My Sample Service: send [Hello, World!]")); + auto msg = ipc_w.recv(1000); + if (msg.empty()) { + OutputDebugString(_T("My Sample Service: recv error")); + } else { + OutputDebugStringA((std::string{"My Sample Service: recv ["} + msg.get() + "]").c_str()); + } + } + Sleep(3000); + } + + OutputDebugString(_T("My Sample Service: ServiceWorkerThread: Exit")); + + return ERROR_SUCCESS; +} diff --git a/include/libipc/def.h b/include/libipc/def.h index 8c1a72ba..45cd7801 100755 --- a/include/libipc/def.h +++ b/include/libipc/def.h @@ -65,4 +65,9 @@ struct relat_trait> { template