commit 4abe6bf73837e9559f88b6e8734d45b4030b3fde Author: Anton ANikin Date: Thu Jan 26 13:56:18 2023 +0800 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f284d11 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/build +__pycache__ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..8090d54 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 3.11) # FetchContent + +project(fifo_ipc) + +if (NOT DEFINED OPTCON_LOCAL_BUILD) + include(FetchContent) + FetchContent_Declare( + fmt + GIT_REPOSITORY https://github.com/fmtlib/fmt + GIT_TAG a33701196adfad74917046096bf5a2aa0ab0bb50 # 9.1.0 + ) + + FetchContent_MakeAvailable(fmt) +endif() + +add_compile_options(-Wall -Wextra -Wpedantic) + +add_subdirectory(src) +add_subdirectory(test) diff --git a/include/ipc/api.hpp b/include/ipc/api.hpp new file mode 100644 index 0000000..9be189a --- /dev/null +++ b/include/ipc/api.hpp @@ -0,0 +1,6 @@ +// Copyright (c) 2023 - present, Anton Anikin +// All rights reserved. + +#pragma once + +#include "../../src/ipc.hpp" diff --git a/include/ipc/c_api.h b/include/ipc/c_api.h new file mode 100644 index 0000000..61ffc24 --- /dev/null +++ b/include/ipc/c_api.h @@ -0,0 +1,6 @@ +// Copyright (c) 2023 - present, Anton Anikin +// All rights reserved. + +#pragma once + +#include "../../src/ipc_c.h" diff --git a/python/ipc.py b/python/ipc.py new file mode 100644 index 0000000..932d581 --- /dev/null +++ b/python/ipc.py @@ -0,0 +1,140 @@ +# Copyright (c) 2023 - present, Anton Anikin +# All rights reserved. + +import numpy as np +import platform +import struct +import sys + +from typing import Optional + +is_windows = (platform.system() == "Windows") +if is_windows: + import win32file + import win32pipe + from time import sleep + + +class Ipc: + def __init__(self, name: str, is_server: bool, verbose: int = 0): + self.verbose = verbose + + if is_windows: + full_name = f"\\\\.\\pipe\\{name}" + + if is_server: + # https://mhammond.github.io/pywin32/win32pipe__CreateNamedPipe_meth.html + self.pipe = win32pipe.CreateNamedPipe( + full_name, # pipeName : PyUnicode + win32pipe.PIPE_ACCESS_DUPLEX, # openMode : int + win32pipe.PIPE_TYPE_BYTE, # pipeMode : int + 1, # nMaxInstances : int + 0, # nOutBufferSize : int + 0, # nInBufferSize : int + 0, # nDefaultTimeOut : int + None # sa : PySECURITY_ATTRIBUTES + ) + + # https://mhammond.github.io/pywin32/win32pipe__ConnectNamedPipe_meth.html + win32pipe.ConnectNamedPipe(self.pipe) + + else: + time_step = 0.5 + time_current = 0 + while time_current < 10: + try: + # https://mhammond.github.io/pywin32/win32file__CreateFile_meth.html + self.pipe = win32file.CreateFile( + full_name, # fileName : PyUnicode + win32file.GENERIC_READ | + win32file.GENERIC_WRITE, # desiredAccess : int + 0, # shareMode : int + None, # attributes : PySECURITY_ATTRIBUTES + win32file.OPEN_EXISTING, # CreationDisposition : int + win32file.FILE_ATTRIBUTE_NORMAL, # flagsAndAttributes : int + None # hTemplateFile : PyHANDLE + ) + break + + except Exception: + time_current += time_step + sleep(time_step) + + else: # unix-like systems ------------------------------------------------ + if is_server: + self._reader = open(name + "_c", "rb") + self._writer = open(name + "_s", "wb") + else: + self._writer = open(name + "_c", "wb") + self._reader = open(name + "_s", "rb") + + def _write(self, data): + if is_windows: + # https://mhammond.github.io/pywin32/win32file__WriteFile_meth.html + errCode, nBytesWritten = win32file.WriteFile(self.pipe, data) + + else: + self._writer.write(data) + self._writer.flush() + + def _read(self, len): + if is_windows: + try: + # https://mhammond.github.io/pywin32/win32file__ReadFile_meth.html + errCode, data = win32file.ReadFile(self.pipe, len) + except Exception: + data = None + + return data + + else: + return self._reader.read(len) + + def write_int(self, value: int): + data = struct.pack("i", value) + self._write(data) + + def write_double(self, value: float): + data = struct.pack("d", value) + self._write(data) + + def write_array(self, array: np.ndarray): + data = array.tobytes(order="C") + self._write(data) + + def read_int(self) -> int: + data = self._read(4) + return struct.unpack("i", data)[0] + + def read_int_opt(self) -> Optional[int]: + data = self._read(4) + if data: + return struct.unpack("i", data)[0] + else: + return None + + def read_double(self) -> float: + data = self._read(8) + return struct.unpack("d", data)[0] + + def read_array(self, size: int, dtype: np.dtype) -> np.ndarray: + data = self._read(size * dtype(0).itemsize) + array = np.frombuffer(data, dtype=dtype) + + # fix for 'ValueError: assignment destination is read-only' on windows OS + array = np.copy(array) + + return array + + def read_str(self) -> str: + len = self.read_int() + data = self._read(len) + return data.decode(sys.getdefaultencoding()) + + def read_str_opt(self) -> Optional[str]: + len = self.read_int_opt() + if len: + data = self._read(len) + return data.decode(sys.getdefaultencoding()) + else: + return None diff --git a/python/test_server.py b/python/test_server.py new file mode 100755 index 0000000..093626b --- /dev/null +++ b/python/test_server.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2023 - present, Anton Anikin +# All rights reserved. + +import numpy as np +import sys + +from ipc import Ipc + + +if __name__ == '__main__': + assert len(sys.argv) >= 3 + + ipc_name = sys.argv[1] + is_server = sys.argv[2] == "1" + + print("[python] pre-start") + ipc = Ipc(ipc_name, is_server) + print("[python] start") + + while True: + value = ipc.read_int_opt() + if value is None: + break + + print(f"[python] read int({value})") + + # result = value * value + result = np.sqrt(value) + ipc.write_double(result) + print(f"[python] write double({result:.2e})") + + print("[python] finish the work\n"); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..e77d17d --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,20 @@ +set(FIFO_IPC_SOURCES + ipc.cpp + ipc_c.cpp +) + +if (WIN32) + list(APPEND FIFO_IPC_SOURCES ipc_win.cpp) +else() + list(APPEND FIFO_IPC_SOURCES ipc_nix.cpp) +endif() + +add_library(fifo_ipc STATIC ${FIFO_IPC_SOURCES}) + +target_link_libraries(fifo_ipc + fmt +) + +target_include_directories(fifo_ipc + INTERFACE ${PROJECT_SOURCE_DIR}/include +) diff --git a/src/ipc.cpp b/src/ipc.cpp new file mode 100644 index 0000000..2fc59f8 --- /dev/null +++ b/src/ipc.cpp @@ -0,0 +1,126 @@ +// Copyright (c) 2023 - present, Anton Anikin +// All rights reserved. + +#include +#include +#include + +#include "ipc.hpp" + +namespace ipc +{ + +#define set_message(verbose, s, ...) { \ + m_message = fmt::format(s __VA_OPT__(,) __VA_ARGS__); \ + if (verbose < 0) { \ + throw std::runtime_error(m_message); \ + } else if (verbose <= m_verbose) { \ + fmt::print(m_message); \ + fmt::print("\n"); \ + } \ +} + +std::string +default_name(); + +void +init() +{ + init(default_name(), 0600); +} + +void +start_external(const std::string& executable, bool is_server) +{ + start_external(executable, default_name(), is_server); +} + +void +start_external(const std::string& executable, const std::string& ipc_name, bool is_server) +{ + auto command = fmt::format("{} {} {}", executable, ipc_name, is_server ? "1" : "0"); + fmt::print("execute: {}\n", command); + + auto thread_func = [command] { + system(command.c_str()); + }; + + std::thread(thread_func).detach(); +} + +Ipc::Ipc(bool is_server, int verbose) + : Ipc(default_name(), is_server, verbose) +{ +} + +void +Ipc::write(const void* buffer, size_t count, size_t size) +{ + // TODO simplfy the code, remove loop and use single-write operation only ? + + assert(buffer); + + const ssize_t bytes_required = size * count; + + char* bytes_buffer = (char*)buffer; + ssize_t bytes_current; + ssize_t bytes_sent = 0; + + do { + bytes_current = _write(&bytes_buffer[bytes_sent], bytes_required - bytes_sent); + bytes_sent += bytes_current; + // set_message(2, "write {} -> {}/{}\n", bytes_current, bytes_sent, bytes_required); + } while ((bytes_current > 0) && (bytes_sent != bytes_required)); + + if (bytes_sent != bytes_required) { + set_message(-1, "c : (-) write ({} * {}) = {} bytes error: only {} bytes are sent", + count, size, bytes_required, bytes_sent); + } + + set_message(1, "c : (+) write ({} * {}) = {} bytes", count, size, bytes_required); +} + +void +Ipc::read(void* buffer, size_t count, size_t size) +{ + // TODO simplfy the code, remove loop and use single-read operation only ? + + assert(buffer); + + const ssize_t bytes_required = size * count; + + char* bytes_buffer = (char*)buffer; + ssize_t bytes_current = 0; + ssize_t bytes_received = 0; + + do { + bytes_current = _read(&bytes_buffer[bytes_received], bytes_required - bytes_received); + bytes_received += bytes_current; + set_message(2, "read {} -> {}/{}\n", bytes_current, bytes_received, bytes_required); + } while ((bytes_current > 0) && (bytes_received != bytes_required)); + + if (bytes_received != bytes_required) { + set_message(-1, "c : (-) read ({} * {}) = {} bytes error: only {} bytes are recieved", + count, size, bytes_required, bytes_received); + } + + set_message(1, "c : (+) read ({} * {}) = {} bytes", size, count, bytes_required); +} + +void +Ipc::write(const char* str) +{ + int len = strlen(str); + write(&len, 1, sizeof(len)); + write(str, 1, len); +} + +void +Ipc::read(char* str) +{ + int len; + read(&len, 1, sizeof(len)); + read(str, 1, len); +} + +} diff --git a/src/ipc.hpp b/src/ipc.hpp new file mode 100644 index 0000000..e4d5b31 --- /dev/null +++ b/src/ipc.hpp @@ -0,0 +1,95 @@ +// Copyright (c) 2023 - present, Anton Anikin +// All rights reserved. + +#pragma once + +#include + +#if defined(_WIN32) + #include +#endif + +namespace ipc +{ + +void +init(); + +void +init(const std::string& name, unsigned int mode); + +void +start_external(const std::string& executable, bool is_server); + +void +start_external(const std::string& executable, const std::string& ipc_name, bool is_server); + +class Ipc +{ +public: + Ipc(bool is_server, int verbose = 0); + + Ipc(const std::string& name, bool is_server, int verbose = 0); + + ~Ipc(); + + void + write(const char* str); + + template + void + write(const T& data) + { + write(&data, 1, sizeof(T)); + } + + template + void + write(const T* data, size_t count) + { + write(data, count, sizeof(T)); + } + + void + write(const void* buffer, size_t count, size_t size); + + void + read(char* str); + + template + void + read(T& data) + { + read(&data, 1, sizeof(T)); + } + + template + void + read(T* data, size_t count) + { + read(data, count, sizeof(T)); + } + + void + read(void* buffer, size_t count, size_t size); + +protected: + ssize_t + _write(const void* buffer, size_t bytes); + + ssize_t + _read(void* buffer, size_t bytes); + +protected: + int m_verbose; + std::string m_message; + +#if defined(_WIN32) + HANDLE m_pipe; +#else + int m_writer_fd; + int m_reader_fd; +#endif +}; + +} diff --git a/src/ipc_c.cpp b/src/ipc_c.cpp new file mode 100644 index 0000000..b2e54c6 --- /dev/null +++ b/src/ipc_c.cpp @@ -0,0 +1,121 @@ +// Copyright (c) 2023 - present, Anton Anikin +// All rights reserved. + +#include + +#include "ipc.hpp" +#include "ipc_c.h" + +void +ipc_init_default() +{ + ipc::init(); +} + +void +ipc_init(const char* name, unsigned int mode) +{ + ipc::init(name, mode); +} + +void* +ipc_create_default(int is_server, int verbose) +{ + return new ipc::Ipc(is_server, verbose); +} + +void* +ipc_create(const char* name, int is_server, int verbose) +{ + return new ipc::Ipc(name, is_server, verbose); +} + +void +ipc_destroy(void* ipc) +{ + assert(ipc); + + auto i = (ipc::Ipc*)ipc; + delete i; +} + +void +ipc_start_external_default(const char* executable, int is_server) +{ + ipc::start_external(executable, is_server); +} + +void +ipc_start_external(const char* executable, const char* ipc_name, int is_server) +{ + ipc::start_external(executable, ipc_name, is_server); +} + +void +ipc_write_any(void* ipc, const void* data, size_t count, size_t size) +{ + assert(ipc); + + auto i = (ipc::Ipc*)ipc; + i->write(data, count, size); +} + +void +ipc_read_any(void* ipc, void* data, size_t count, size_t size) +{ + assert(ipc); + + auto i = (ipc::Ipc*)ipc; + i->read(data, count, size); +} + +void +ipc_write_string(void* ipc, const char* str) +{ + assert(ipc); + + auto i = (ipc::Ipc*)ipc; + i->write(str); +} + +void +ipc_read_string(void* ipc, char* str) +{ + assert(ipc); + + auto i = (ipc::Ipc*)ipc; + i->read(str); +} + +template +void +ipc_write(void* ipc, const T* data, size_t count) +{ + assert(ipc); + + auto i = (ipc::Ipc*)ipc; + i->write(data, count); +} + +template +void +ipc_read(void* ipc, T* data, size_t count) +{ + assert(ipc); + + auto i = (ipc::Ipc*)ipc; + i->read(data, count); +} + +#define _IPC_WRITE_FN(data_type) _IPC_WRITE_FN_NAME(data_type) { return ipc_write(ipc, data, count); } +#define _IPC_READ_FN(data_type) _IPC_READ_FN_NAME (data_type) { return ipc_read (ipc, data, count); } + +_IPC_WRITE_FN(char); +_IPC_WRITE_FN(int); +_IPC_WRITE_FN(long); +_IPC_WRITE_FN(double); + +_IPC_READ_FN(char); +_IPC_READ_FN(int); +_IPC_READ_FN(long); +_IPC_READ_FN(double); diff --git a/src/ipc_c.h b/src/ipc_c.h new file mode 100644 index 0000000..ff441dd --- /dev/null +++ b/src/ipc_c.h @@ -0,0 +1,58 @@ +// Copyright (c) 2023 - present, Anton Anikin +// All rights reserved. + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +void +ipc_init_default(); + +void +ipc_init(const char* name, unsigned int mode); + +void* +ipc_create_default(int is_server, int verbose); + +void* +ipc_create(const char* name, int is_server, int verbose); + +void +ipc_destroy(void* ipc); + +void +ipc_start_external_default(const char* executable, int is_server); + +void +ipc_start_external(const char* executable, const char* ipc_name, int is_server); + +void +ipc_write_any(void* ipc, const void* data, size_t count, size_t size); + +void +ipc_read_any(void* ipc, void* data, size_t count, size_t size); + +void +ipc_write_string(void* ipc, const char* str); + +void +ipc_read_string(void* ipc, char* str); + +#define _IPC_WRITE_FN_NAME(type) void ipc_write_ ## type (void* ipc, const type * data, size_t count) +#define _IPC_READ_FN_NAME(type) void ipc_read_ ## type (void* ipc, type * data, size_t count) + +_IPC_WRITE_FN_NAME(char); +_IPC_WRITE_FN_NAME(int); +_IPC_WRITE_FN_NAME(long); +_IPC_WRITE_FN_NAME(double); + +_IPC_READ_FN_NAME(char); +_IPC_READ_FN_NAME(int); +_IPC_READ_FN_NAME(long); +_IPC_READ_FN_NAME(double); + +#ifdef __cplusplus +} +#endif diff --git a/src/ipc_nix.cpp b/src/ipc_nix.cpp new file mode 100644 index 0000000..3be94de --- /dev/null +++ b/src/ipc_nix.cpp @@ -0,0 +1,75 @@ +// Copyright (c) 2023 - present, Anton Anikin +// All rights reserved. + +#include +#include +#include +#include + +#include "ipc.hpp" + +namespace ipc +{ + +std::string +default_name() +{ + return fmt::format("/tmp/fifo_ipc_{}", getpid()); +} + +void +make_fifo(const std::string& name, unsigned int mode) +{ + if (mkfifo(name.c_str(), mode) < 0) + { + auto message = fmt::format("mkfifo '{}' : {}", name, strerror(errno)); + throw std::runtime_error(message); + } +} + +void +init(const std::string& name, unsigned int mode) +{ + make_fifo(name + "_s", mode); + make_fifo(name + "_c", mode); +} + +Ipc::Ipc(const std::string& name, bool is_server, int verbose) + : m_verbose(verbose) +{ + auto fifo_open = [](std::string name, int flags) -> int { + int fd = open(name.c_str(), flags); + if (fd == -1) { + auto message = fmt::format("open '{}' : {}", name, strerror(errno)); + throw std::runtime_error(message); + } + + return fd; + }; + + if (is_server) { + m_reader_fd = fifo_open(name + "_c", O_RDONLY); + m_writer_fd = fifo_open(name + "_s", O_WRONLY); + } else { + m_writer_fd = fifo_open(name + "_c", O_WRONLY); + m_reader_fd = fifo_open(name + "_s", O_RDONLY); + } +} + +Ipc::~Ipc() +{ +} + +ssize_t +Ipc::_write(const void* buffer, size_t bytes) +{ + return ::write(m_writer_fd, buffer, bytes); +} + +ssize_t +Ipc::_read(void* buffer, size_t bytes) +{ + return ::read(m_reader_fd, buffer, bytes); +} + +} diff --git a/src/ipc_win.cpp b/src/ipc_win.cpp new file mode 100644 index 0000000..3382084 --- /dev/null +++ b/src/ipc_win.cpp @@ -0,0 +1,189 @@ +// Copyright (c) 2023 - present, Anton Anikin +// All rights reserved. + +#include +#include +#include +#include +#include + +#include "ipc.hpp" + +#define UNUSED(x) (void)(x) + +namespace ipc +{ + +// https://stackoverflow.com/a/17387176 +// Returns the last Win32 error, in string format. +// Returns an empty string if there is no error. +std::string +getLastErrorAsString() +{ + // Get the error message ID, if any. + DWORD errorMessageID = ::GetLastError(); + if(errorMessageID == 0) { + return std::string(); //No error message has been recorded + } + + LPSTR messageBuffer = nullptr; + + // Ask Win32 to give us the string version of that message ID. + // The parameters we pass in, tell Win32 to create the buffer that holds the + // message for us (because we don't yet know how long the message string will be). + // + // https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-formatmessagea + // + size_t size = FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, // dwFlags + + NULL, // lpSource + errorMessageID, // dwMessageId + MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), // dwLanguageId + (LPSTR)&messageBuffer, // lpBuffer + 0, // nSize + NULL // *Arguments + ); + + // Copy the error message into a std::string. + std::string message(messageBuffer, size); + + // Free the Win32's string's buffer. + LocalFree(messageBuffer); + + return message; +} + +std::string +default_name() +{ + return fmt::format("fifo_ipc_{}", getpid()); +} + +void +init(const std::string& name, unsigned int mode) +{ + UNUSED(name); + UNUSED(mode); +} + +// https://learn.microsoft.com/en-us/windows/win32/ipc/named-pipe-operations +// https://peter.bloomfield.online/introduction-to-win32-named-pipes-cpp/ +// https://learn.microsoft.com/en-us/windows/win32/ipc/named-pipe-client +// https://habr.com/ru/post/166155/ + +Ipc::Ipc(const std::string& name, bool is_server, int verbose) + : m_verbose(verbose) +{ + using namespace std::chrono_literals; + + auto full_name = fmt::format("\\\\.\\pipe\\{}", name); + // fmt::print("full name = {}\n", full_name); + + auto throw_failed = [](const std::string& name) { + auto message = fmt::format("{}() failed: {}", name, getLastErrorAsString()); + throw std::runtime_error(message); + }; + + if (is_server) { + // https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createnamedpipea + m_pipe = CreateNamedPipe( + (LPSTR)full_name.c_str(), // lpName + PIPE_ACCESS_DUPLEX, // dwOpenMode + PIPE_TYPE_BYTE, // dwPipeMode + 1, // nMaxInstances + 0, // nOutBufferSize + 0, // nInBufferSize + 0, // nDefaultTimeOut + NULL // lpSecurityAttributes + ); + + if (m_pipe == INVALID_HANDLE_VALUE) { + throw_failed("CreateNamedPipe"); + } + + auto result = ConnectNamedPipe(m_pipe, NULL); + if (!result) { + throw_failed("ConnectNamedPipe"); + } + + return; + } + + auto time_step = 500ms; + auto time_current = 0ms; + while (time_current < 10s) { + // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea + m_pipe = CreateFile( + (LPSTR)full_name.c_str(), // lpFileName + GENERIC_READ | GENERIC_WRITE, // dwDesiredAccess + 0, // dwShareMode + NULL, // lpSecurityAttributes + OPEN_EXISTING, // dwCreationDisposition + FILE_ATTRIBUTE_NORMAL, // dwFlagsAndAttributes + NULL // hTemplateFile + ); + + if (m_pipe != INVALID_HANDLE_VALUE) { + break; + } + + std::this_thread::sleep_for(time_step); + time_current += time_step; + } + + if (m_pipe == INVALID_HANDLE_VALUE) { + throw_failed("CreateFile"); + } +} + +Ipc::~Ipc() +{ + CloseHandle(m_pipe); +} + +ssize_t +Ipc::_write(const void* buffer, size_t bytes) +{ + // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-writefile + + DWORD numBytesWritten = 0; + auto result = WriteFile( + m_pipe, // hFile + buffer, // lpBuffer + bytes, // nNumberOfBytesToWrite + &numBytesWritten, // lpNumberOfBytesWritten + NULL // lpOverlapped + ); + + if (!result) { + // FIXME + } + + return numBytesWritten; +} + +ssize_t +Ipc::_read(void* buffer, size_t bytes) +{ + // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile + + DWORD numBytesRead = 0; + auto result = ReadFile( + m_pipe, // hFile + buffer, // lpBuffer + bytes, // nNumberOfBytesToRead + &numBytesRead, // lpNumberOfBytesRead + NULL // lpOverlapped + ); + + if (!result) { + // FIXME + } + + return numBytesRead; +} + +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..5b802cd --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,23 @@ +add_executable(test_ipc + test_ipc.cpp +) + +target_link_libraries(test_ipc + fifo_ipc +) + +add_executable(test_ipc_c_api + test_ipc_c_api.cpp +) + +target_link_libraries(test_ipc_c_api + fifo_ipc +) + +add_executable(test_ipc_python + test_ipc_python.cpp +) + +target_link_libraries(test_ipc_python + fifo_ipc +) diff --git a/test/test_ipc.cpp b/test/test_ipc.cpp new file mode 100644 index 0000000..9188123 --- /dev/null +++ b/test/test_ipc.cpp @@ -0,0 +1,77 @@ +#include +#include +#include +#include +#include + +#include "ipc/api.hpp" + +int +main(int /* argc */, char** /* argv */) +{ + using ipc::Ipc; + using fmt::format; + using fmt::print; + using namespace std::chrono_literals; + + int verbose = 0; + + // custom name +#if defined(_WIN32) + auto name = format("custom_fifo_ipc_{}", getpid()); +#else + auto name = format("/tmp/custom_fifo_ipc_{}", getpid()); +#endif + print("\n{}\n\n", name); + ipc::init(name, 0600); + + std::thread server_thread([name, verbose] { + print("[server] pre-start\n"); + std::this_thread::sleep_for(1s); + + auto server = Ipc(name, true, verbose); + print("[server] start\n"); + + int value; + double result; + while(1) { + { + server.read(value); + print("[server] read int({})\n", value); + } + + { + // result = value * value; + result = std::sqrt(value); + server.write(result); + print("[server] write double({:.2e})\n", result); + } + } + }); + + print("[client] pre-start\n"); + auto client = Ipc(name, false, verbose); + print("[client] start\n"); + + double answer; + auto hline = "\n------------------------\n"; + for (int i = 2; i <= 5; ++i) { + std::this_thread::sleep_for(500ms); + print(hline); + + { + client.write(i); + print("[client] write int({})\n", i); + } + + { + client.read(answer); + print("[client] read double({:.2e})\n", answer); + } + } + + print(hline); + print("[client] finish the work\n\n"); + + return 0; +} diff --git a/test/test_ipc_c_api.cpp b/test/test_ipc_c_api.cpp new file mode 100644 index 0000000..d674f9c --- /dev/null +++ b/test/test_ipc_c_api.cpp @@ -0,0 +1,67 @@ +#include +#include +#include +#include + +#include "ipc/c_api.h" + +int +main(int /* argc */, char** /* argv */) +{ + using namespace std::chrono_literals; + + int verbose = 0; + + printf("\n"); + ipc_init_default(); // default name + + std::thread server_thread([verbose] { + printf("[c-server] pre-start\n"); + std::this_thread::sleep_for(1s); + + auto server = ipc_create_default(1, verbose); + printf("[c-server] start\n"); + + int value; + double result; + while(1) { + { + ipc_read_int(server, &value, 1); + printf("[c-server] read int(%d)\n", value); + } + + { + result = value * value; + result = std::sqrt(value); + ipc_write_double(server, &result, 1); + printf("[c-server] write double(%.2e)\n", result); + } + } + }); + + printf("[c-client] pre-start\n"); + auto client = ipc_create_default(0, verbose); + printf("[c-client] start\n"); + + double answer; + auto hline = "\n------------------------\n"; + for (int i = 2; i <= 5; ++i) { + std::this_thread::sleep_for(500ms); + printf(hline); + + { + ipc_write_int(client, &i, 1); + printf("[c-client] write int(%d)\n", i); + } + + { + ipc_read_double(client, &answer, 1); + printf("[c-client] read double(%.2e)\n", answer); + } + } + + printf(hline); + printf("[c-client] finish the work\n\n"); + + return 0; +} diff --git a/test/test_ipc_python.cpp b/test/test_ipc_python.cpp new file mode 100644 index 0000000..5fb69e4 --- /dev/null +++ b/test/test_ipc_python.cpp @@ -0,0 +1,47 @@ +#include +#include +#include + +#include + +#include "ipc/api.hpp" + +int +main(int /* argc */, char** /* argv */) +{ + using ipc::Ipc; + using fmt::format; + using fmt::print; + using namespace std::chrono_literals; + + int verbose = 0; + ipc::init(); // default name + + ipc::start_external("python test_server.py", true); + + print("[client] pre-start\n"); + auto client = Ipc(false, verbose); + print("[client] start\n"); + + double answer; + auto hline = "\n------------------------\n"; + for (int i = 2; i <= 5; ++i) { + std::this_thread::sleep_for(500ms); + print(hline); + + { + client.write(i); + print("[client] write int({})\n", i); + } + + { + client.read(answer); + print("[client] read double({:.2e})\n", answer); + } + } + + print(hline); + print("[client] finish the work\n\n"); + + return 0; +}