first commit
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/build
|
||||
__pycache__
|
||||
19
CMakeLists.txt
Normal file
19
CMakeLists.txt
Normal file
@@ -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)
|
||||
6
include/ipc/api.hpp
Normal file
6
include/ipc/api.hpp
Normal file
@@ -0,0 +1,6 @@
|
||||
// Copyright (c) 2023 - present, Anton Anikin <anton@anikin.xyz>
|
||||
// All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../src/ipc.hpp"
|
||||
6
include/ipc/c_api.h
Normal file
6
include/ipc/c_api.h
Normal file
@@ -0,0 +1,6 @@
|
||||
// Copyright (c) 2023 - present, Anton Anikin <anton@anikin.xyz>
|
||||
// All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../../src/ipc_c.h"
|
||||
140
python/ipc.py
Normal file
140
python/ipc.py
Normal file
@@ -0,0 +1,140 @@
|
||||
# Copyright (c) 2023 - present, Anton Anikin <anton@anikin.xyz>
|
||||
# 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
|
||||
34
python/test_server.py
Executable file
34
python/test_server.py
Executable file
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Copyright (c) 2023 - present, Anton Anikin <anton@anikin.xyz>
|
||||
# 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");
|
||||
20
src/CMakeLists.txt
Normal file
20
src/CMakeLists.txt
Normal file
@@ -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
|
||||
)
|
||||
126
src/ipc.cpp
Normal file
126
src/ipc.cpp
Normal file
@@ -0,0 +1,126 @@
|
||||
// Copyright (c) 2023 - present, Anton Anikin <anton@anikin.xyz>
|
||||
// All rights reserved.
|
||||
|
||||
#include <cassert>
|
||||
#include <fmt/core.h>
|
||||
#include <thread>
|
||||
|
||||
#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);
|
||||
}
|
||||
|
||||
}
|
||||
95
src/ipc.hpp
Normal file
95
src/ipc.hpp
Normal file
@@ -0,0 +1,95 @@
|
||||
// Copyright (c) 2023 - present, Anton Anikin <anton@anikin.xyz>
|
||||
// All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <windows.h>
|
||||
#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<typename T>
|
||||
void
|
||||
write(const T& data)
|
||||
{
|
||||
write(&data, 1, sizeof(T));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
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<typename T>
|
||||
void
|
||||
read(T& data)
|
||||
{
|
||||
read(&data, 1, sizeof(T));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
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
|
||||
};
|
||||
|
||||
}
|
||||
121
src/ipc_c.cpp
Normal file
121
src/ipc_c.cpp
Normal file
@@ -0,0 +1,121 @@
|
||||
// Copyright (c) 2023 - present, Anton Anikin <anton@anikin.xyz>
|
||||
// All rights reserved.
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#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<typename T>
|
||||
void
|
||||
ipc_write(void* ipc, const T* data, size_t count)
|
||||
{
|
||||
assert(ipc);
|
||||
|
||||
auto i = (ipc::Ipc*)ipc;
|
||||
i->write(data, count);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
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);
|
||||
58
src/ipc_c.h
Normal file
58
src/ipc_c.h
Normal file
@@ -0,0 +1,58 @@
|
||||
// Copyright (c) 2023 - present, Anton Anikin <anton@anikin.xyz>
|
||||
// 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
|
||||
75
src/ipc_nix.cpp
Normal file
75
src/ipc_nix.cpp
Normal file
@@ -0,0 +1,75 @@
|
||||
// Copyright (c) 2023 - present, Anton Anikin <anton@anikin.xyz>
|
||||
// All rights reserved.
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <fmt/core.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
|
||||
}
|
||||
189
src/ipc_win.cpp
Normal file
189
src/ipc_win.cpp
Normal file
@@ -0,0 +1,189 @@
|
||||
// Copyright (c) 2023 - present, Anton Anikin <anton@anikin.xyz>
|
||||
// All rights reserved.
|
||||
|
||||
#include <chrono>
|
||||
#include <fmt/core.h>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <windows.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
}
|
||||
23
test/CMakeLists.txt
Normal file
23
test/CMakeLists.txt
Normal file
@@ -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
|
||||
)
|
||||
77
test/test_ipc.cpp
Normal file
77
test/test_ipc.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <fmt/core.h>
|
||||
#include <thread>
|
||||
#include <unistd.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
67
test/test_ipc_c_api.cpp
Normal file
67
test/test_ipc_c_api.cpp
Normal file
@@ -0,0 +1,67 @@
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <thread>
|
||||
|
||||
#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;
|
||||
}
|
||||
47
test/test_ipc_python.cpp
Normal file
47
test/test_ipc_python.cpp
Normal file
@@ -0,0 +1,47 @@
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <thread>
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
Reference in New Issue
Block a user