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"});