diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4b13bc72..079ae9e4 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -92,7 +92,7 @@ set(PIP_UTILS_LIST)
set(PIP_TESTS_LIST)
set(PIP_EXPORTS)
-set(PIP_SRC_MODULES "console;crypt;compress;usb;fftw;opencl;io_utils;cloud;lua")
+set(PIP_SRC_MODULES "console;crypt;compress;usb;fftw;opencl;io_utils;client_server;cloud;lua")
foreach(_m ${PIP_SRC_MODULES})
set(PIP_MSG_${_m} "no")
endforeach()
@@ -392,6 +392,7 @@ if (NOT CROSSTOOLS)
pip_find_lib(sodium)
if(sodium_FOUND)
pip_module(crypt "sodium" "PIP crypt support" "" "" "")
+ pip_module(client_server "pip_io_utils" "PIP client-server helper" "" "" "")
pip_module(cloud "pip_io_utils" "PIP cloud support" "" "" "")
endif()
@@ -480,7 +481,7 @@ if (NOT CROSSTOOLS)
#target_link_libraries(pip_plugin pip)
add_executable(pip_test "main.cpp")
- target_link_libraries(pip_test pip pip_io_utils)
+ target_link_libraries(pip_test pip pip_io_utils pip_client_server)
if(sodium_FOUND)
add_executable(pip_cloud_test "main_picloud_test.cpp")
target_link_libraries(pip_cloud_test pip_cloud)
@@ -585,7 +586,7 @@ if ((NOT PIP_FREERTOS) AND (NOT CROSSTOOLS))
find_package(Doxygen)
if(DOXYGEN_FOUND)
set(DOXY_DEFINES "${PIP_EXPORTS}")
- foreach (_m "console" "usb" "compress" "crypt" "cloud" "fftw" "opencl" "io_utils" "lua")
+ foreach (_m "console" "usb" "compress" "crypt" "client_server" "cloud" "fftw" "opencl" "io_utils" "lua")
string(TOUPPER "${_m}" _mdef)
list(APPEND DOXY_DEFINES "PIP_${_mdef}_EXPORT")
endforeach()
diff --git a/cmake/FindPIP.cmake b/cmake/FindPIP.cmake
index 8d7a295b..002c92c7 100644
--- a/cmake/FindPIP.cmake
+++ b/cmake/FindPIP.cmake
@@ -9,6 +9,7 @@ Create imported targets:
* PIP::FFTW
* PIP::OpenCL
* PIP::IOUtils
+ * PIP::ClientServer
* PIP::Cloud
* PIP::Lua
@@ -22,7 +23,7 @@ include(SHSTKMacros)
shstk_set_find_dirs(PIP)
-set(__libs "usb;crypt;console;fftw;compress;io_utils;opencl;cloud;lua")
+set(__libs "usb;crypt;console;fftw;compress;opencl;io_utils;client_server;cloud;lua")
if (BUILDING_PIP)
#set(_libs "pip;pip_usb;pip_console;pip_crypt;pip_fftw;pip_compress;pip_opencl;pip_io_utils;pip_cloud;pip_lua")
@@ -83,15 +84,16 @@ if(PIP_FIND_VERSION VERSION_GREATER PIP_VERSION)
message(FATAL_ERROR "PIP version ${PIP_VERSION} is available, but ${PIP_FIND_VERSION} requested!")
endif()
-set(__module_usb USB )
-set(__module_console Console )
-set(__module_crypt Crypt )
-set(__module_fftw FFTW )
-set(__module_compress Compress )
-set(__module_opencl OpenCL )
-set(__module_io_utils IOUtils )
-set(__module_cloud Cloud )
-set(__module_lua Lua )
+set(__module_usb USB )
+set(__module_console Console )
+set(__module_crypt Crypt )
+set(__module_fftw FFTW )
+set(__module_compress Compress )
+set(__module_opencl OpenCL )
+set(__module_io_utils IOUtils )
+set(__module_client_server ClientServer)
+set(__module_cloud Cloud )
+set(__module_lua Lua )
foreach (_l ${__libs})
set( __inc_${_l} "")
@@ -99,8 +101,9 @@ foreach (_l ${__libs})
set(__libs_${_l} "")
endforeach()
-set(__deps_io_utils "PIP::Crypt")
-set(__deps_cloud "PIP::IOUtils")
+set(__deps_io_utils "PIP::Crypt" )
+set(__deps_client_server "PIP::IOUtils")
+set(__deps_cloud "PIP::IOUtils")
if (BUILDING_PIP)
diff --git a/libs/client_server/piclient_server_client.cpp b/libs/client_server/piclient_server_client.cpp
new file mode 100644
index 00000000..2b97a01c
--- /dev/null
+++ b/libs/client_server/piclient_server_client.cpp
@@ -0,0 +1,116 @@
+/*
+ PIP - Platform Independent Primitives
+ Ivan Pelipenko peri4ko@yandex.ru
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program. If not, see .
+*/
+
+#include "piclient_server_client.h"
+
+#include "piethernet.h"
+#include "piliterals_time.h"
+#include "pitime.h"
+
+
+PIClientServer::Client::Client() {}
+
+
+PIClientServer::Client::~Client() {
+ stop();
+ if (own_tcp) piDeleteSafety(tcp);
+}
+
+
+void PIClientServer::Client::createNew() {
+ if (tcp) return;
+ tcp = new PIEthernet(PIEthernet::TCP_Client);
+ tcp->setParameter(PIEthernet::KeepConnection, true);
+ own_tcp = true;
+ init();
+}
+
+
+void PIClientServer::Client::connect(PINetworkAddress addr) {
+ if (!tcp || !own_tcp) return;
+ stop();
+ tcp->connect(addr, true);
+ tcp->startThreadedRead();
+ piCout << "Connect to" << addr.toString();
+}
+
+
+void PIClientServer::Client::stop() {
+ if (!tcp) return;
+ can_write = false;
+ tcp->interrupt();
+ tcp->stopAndWait(10_s);
+ if (tcp->isThreadedRead()) tcp->terminateThreadedRead();
+ stream.clear();
+}
+
+
+int PIClientServer::Client::write(const void * d, const size_t s) {
+ if (!tcp) return -1;
+ if (!can_write) return 0;
+ PIMutexLocker guard(write_mutex);
+ piCout << "... send ...";
+ stream.send(PIByteArray(d, s));
+ piCout << "... send ok";
+ return s;
+}
+
+
+void PIClientServer::Client::createForServer(PIEthernet * tcp_) {
+ tcp = tcp_;
+ tcp->setParameter(PIEthernet::KeepConnection, false);
+ init();
+}
+
+
+void PIClientServer::Client::init() {
+ if (!tcp) return;
+ CONNECTL(&stream, sendRequest, [this](const PIByteArray & ba) {
+ if (!can_write) return;
+ tcp->send(ba);
+ // piMSleep(1);
+ });
+ CONNECTL(&stream, packetReceiveEvent, [this](PIByteArray & ba) {
+ if (readed_func) readed_func(ba);
+ readed(ba);
+ });
+ CONNECTL(tcp, threadedReadEvent, [this](const uchar * readed, ssize_t size) {
+ if (!can_write) return;
+ stream.received(readed, size);
+ });
+ CONNECTL(tcp, connected, [this]() {
+ can_write = true;
+ piCout << "Connected";
+ connected();
+ });
+ CONNECTL(tcp, disconnected, [this](bool) {
+ can_write = false;
+ stream.clear();
+ piCout << "Disconnected";
+ disconnected();
+ });
+}
+
+
+void PIClientServer::Client::destroy() {
+ can_write = false;
+ write_mutex.lock();
+ piDeleteSafety(tcp);
+ aboutDelete();
+ piCout << "Destroyed";
+}
diff --git a/libs/client_server/piclient_server_server.cpp b/libs/client_server/piclient_server_server.cpp
new file mode 100644
index 00000000..cb1d4aa3
--- /dev/null
+++ b/libs/client_server/piclient_server_server.cpp
@@ -0,0 +1,106 @@
+/*
+ PIP - Platform Independent Primitives
+ Ivan Pelipenko peri4ko@yandex.ru
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program. If not, see .
+*/
+
+#include "piclient_server_server.h"
+
+#include "piclient_server_client.h"
+#include "piethernet.h"
+#include "piliterals_time.h"
+
+
+PIClientServer::Server::Server() {
+ tcp_server = new PIEthernet(PIEthernet::TCP_Server);
+ clean_thread = new PIThread();
+ client_factory = [] { return new Client(); };
+ CONNECTL(tcp_server, newConnection, [this](PIEthernet * c) {
+ PIMutexLocker guard(clients_mutex);
+ if (clients.size_s() >= max_clients) {
+ piCout << "Server::newConnection overflow clients count";
+ delete c;
+ return;
+ }
+ auto sc = client_factory();
+ if (!sc) {
+ piCout << "ClientFactory returns nullptr!";
+ return;
+ }
+ sc->createForServer(c);
+ newClient(sc);
+ });
+
+ clean_thread->start(
+ [this]() {
+ PIVector to_delete;
+ clients_mutex.lock();
+ for (auto c: clients) {
+ const PIEthernet * eth = c->getTCP();
+ if (!eth) continue;
+ if (eth->isConnected()) continue;
+ c->can_write = false;
+ to_delete << c;
+ }
+ for (auto c: to_delete)
+ clients.removeOne(c);
+ clients_mutex.unlock();
+ for (auto c: to_delete) {
+ c->destroy();
+ delete c;
+ }
+ },
+ 5_Hz);
+}
+
+
+PIClientServer::Server::~Server() {
+ clean_thread->stopAndWait();
+ piDeleteSafety(clean_thread);
+ stopServer();
+ for (auto c: clients) {
+ c->destroy();
+ delete c;
+ }
+ piDeleteSafety(tcp_server);
+}
+
+
+void PIClientServer::Server::listen(PINetworkAddress addr) {
+ if (!tcp_server) return;
+ stopServer();
+ tcp_server->listen(addr, true);
+ // piCout << "Listen on" << addr.toString();
+}
+
+
+void PIClientServer::Server::setMaxClients(int new_max_clients) {
+ max_clients = new_max_clients;
+}
+
+
+void PIClientServer::Server::stopServer() {
+ if (!tcp_server) return;
+ tcp_server->stopAndWait();
+}
+
+
+void PIClientServer::Server::newClient(Client * c) {
+ c->readed_func = [this, c](PIByteArray ba) { readed(c, ba); };
+ clients << c;
+ c->tcp->startThreadedRead();
+ c->connected();
+ piCout << "New client";
+}
diff --git a/libs/main/client_server/piclient_server_client.h b/libs/main/client_server/piclient_server_client.h
new file mode 100644
index 00000000..660e5539
--- /dev/null
+++ b/libs/main/client_server/piclient_server_client.h
@@ -0,0 +1,78 @@
+/*! \file piclient_server_client.h
+ * \ingroup ClientServer
+ * \~\brief
+ * \~english
+ * \~russian
+ */
+/*
+ PIP - Platform Independent Primitives
+ Ivan Pelipenko peri4ko@yandex.ru
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program. If not, see .
+*/
+
+#ifndef piclient_server_client_H
+#define piclient_server_client_H
+
+#include "pinetworkaddress.h"
+#include "pip_client_server_export.h"
+#include "pistreampacker.h"
+
+class PIEthernet;
+
+namespace PIClientServer {
+
+class Server;
+
+class PIP_CLIENT_SERVER_EXPORT Client: public PIObject {
+ friend class Server;
+ NO_COPY_CLASS(Client);
+ PIOBJECT(Client);
+
+public:
+ Client();
+ virtual ~Client();
+
+ void createNew();
+
+ void connect(PINetworkAddress addr);
+ const PIEthernet * getTCP() const { return tcp; }
+
+ void stop();
+
+ int write(const void * d, const size_t s);
+ int write(const PIByteArray & ba) { return write(ba.data(), ba.size()); }
+
+protected:
+ virtual void readed(PIByteArray data) {}
+ virtual void connected() {}
+ virtual void disconnected() {}
+ virtual void aboutDelete() {}
+
+private:
+ void createForServer(PIEthernet * tcp_);
+ void init();
+ void destroy();
+
+ bool own_tcp = false;
+ std::atomic_bool can_write = {true};
+ PIEthernet * tcp = nullptr;
+ PIStreamPacker stream;
+ mutable PIMutex write_mutex;
+ std::function readed_func = nullptr;
+};
+
+} // namespace PIClientServer
+
+#endif
diff --git a/libs/main/client_server/piclient_server_server.h b/libs/main/client_server/piclient_server_server.h
new file mode 100644
index 00000000..070c8b0d
--- /dev/null
+++ b/libs/main/client_server/piclient_server_server.h
@@ -0,0 +1,72 @@
+/*! \file piclient_server_server.h
+ * \ingroup ClientServer
+ * \~\brief
+ * \~english
+ * \~russian
+ */
+/*
+ PIP - Platform Independent Primitives
+ Ivan Pelipenko peri4ko@yandex.ru
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program. If not, see .
+*/
+
+#ifndef piclient_server_server_H
+#define piclient_server_server_H
+
+#include "pimutex.h"
+#include "pinetworkaddress.h"
+#include "pip_client_server_export.h"
+
+class PIEthernet;
+class PIThread;
+
+namespace PIClientServer {
+
+class Client;
+
+class PIP_CLIENT_SERVER_EXPORT Server {
+public:
+ Server();
+ virtual ~Server();
+
+ void listen(PINetworkAddress addr);
+ void listenAll(ushort port) { listen({0, port}); }
+
+ int getMaxClients() const { return max_clients; }
+ void setMaxClients(int new_max_clients);
+
+ void setClientFactory(std::function f) { client_factory = f; }
+
+ void write(Client * c, const PIByteArray & data);
+
+protected:
+ virtual void readed(Client * c, PIByteArray data) {}
+
+private:
+ void stopServer();
+ void newClient(Client * c);
+
+ std::function client_factory;
+ PIEthernet * tcp_server = nullptr;
+ PIThread * clean_thread = nullptr;
+ PIVector clients;
+ PIMutex clients_mutex;
+
+ int max_clients = 1000;
+};
+
+} // namespace PIClientServer
+
+#endif
diff --git a/libs/main/console/pikbdlistener.h b/libs/main/console/pikbdlistener.h
index 165ed9ad..27080497 100644
--- a/libs/main/console/pikbdlistener.h
+++ b/libs/main/console/pikbdlistener.h
@@ -29,9 +29,10 @@
#include "pithread.h"
#include "pitime.h"
-#define WAIT_FOR_EXIT \
- while (!PIKbdListener::exiting) \
- piMSleep(PIP_MIN_MSLEEP * 5); // TODO: rewrite with condvar
+#define WAIT_FOR_EXIT \
+ while (!PIKbdListener::exiting) \
+ piMSleep(PIP_MIN_MSLEEP * 5); \
+ if (PIKbdListener::instance()) PIKbdListener::instance()->stopAndWait();
class PIP_EXPORT PIKbdListener: public PIThread {
diff --git a/libs/main/io_devices/piethernet.cpp b/libs/main/io_devices/piethernet.cpp
index 4aa86b93..796d67d0 100644
--- a/libs/main/io_devices/piethernet.cpp
+++ b/libs/main/io_devices/piethernet.cpp
@@ -777,6 +777,9 @@ ssize_t PIEthernet::writeDevice(const void * data, ssize_t max_size) {
closeSocket(sock);
init();
disconnected(true);
+ if (params[KeepConnection]) {
+ connect();
+ }
}
};
if (!isOptionSet(BlockingWrite)) {
diff --git a/main.cpp b/main.cpp
index c748f73f..5f62cd3e 100644
--- a/main.cpp
+++ b/main.cpp
@@ -1,4 +1,6 @@
#include "pibytearray.h"
+#include "piclient_server_client.h"
+#include "piclient_server_server.h"
#include "picodeparser.h"
#include "piiostream.h"
#include "pijson.h"
@@ -16,22 +18,51 @@ enum MyEvent {
meIntString,
};
-PITimeMeasurer tm;
-std::atomic_int cnt = {0};
-void tfunc(int delim) {
- // piCout << "tick with delimiter" << delim;
- ++cnt;
-};
-void tfunc4(int delim) {
- piCout << "tick4 with delimiter" << delim;
+PIKbdListener kbd;
+
+
+class MyClient: public PIClientServer::Client {
+protected:
+ void readed(PIByteArray data) override { piCout << "readed" << (data.size()); }
+ void connected() override {
+ send_thread.start(
+ [this] {
+ // write((PIString::fromNumber(++counter)).toUTF8());
+ PIByteArray ba(64_MiB);
+ write(ba);
+ },
+ 2_Hz);
+ }
+ PIThread send_thread;
+ int counter = 0;
};
int main(int argc, char * argv[]) {
- uint v = 0xaabbccdd;
- piCout << Hex << v << piChangedEndian(v);
- piChangeEndianBinary(&v, sizeof(v));
- piCout << Hex << v << piChangedEndian(v);
+ kbd.enableExitCapture();
+
+ piCout << argc;
+
+ PIClientServer::Server * s = nullptr;
+ PIClientServer::Client * c = nullptr;
+
+ if (argc > 1) {
+ // server
+ s = new PIClientServer::Server();
+ s->setClientFactory([] { return new MyClient(); });
+ s->listenAll(12345);
+ } else {
+ // client
+ c = new MyClient();
+ c->createNew();
+ c->connect(PINetworkAddress::resolve("127.0.0.1", 12345));
+ }
+
+ WAIT_FOR_EXIT;
+
+ piDeleteSafety(s);
+ piDeleteSafety(c);
+
return 0;
/*PIPackedTCP * tcp_s =
PIIODevice::createFromFullPath("ptcp://s::8000")->cast(); // new PIPackedTCP(PIPackedTCP::Server, {"0.0.0.0:8000"});