From f74f89734c32791d504c9a2762273df2b10e4d5a Mon Sep 17 00:00:00 2001 From: nojhan Date: Thu, 5 Aug 2021 10:08:49 +0200 Subject: [PATCH] Cleaner examples - Use ${CXX} instead of explicit clang in the Makefile. - More explanations. - Use socat's fork option instead of a bash loop. - Adds a run_* script for each example. - Remove the useless "sleep" option in service2. --- Makefile | 10 +++--- README.md | 68 ++++++++++++++++++++++++++++++------- build.sh | 0 run_pcat.sh | 8 +++++ run_service1.sh | 11 ++++++ run.sh => run_service2.sh | 0 run_socat_server.sh | 7 ++++ service.cpp => service1.cpp | 0 service2.cpp | 11 ++---- 9 files changed, 89 insertions(+), 26 deletions(-) mode change 100644 => 100755 build.sh create mode 100755 run_pcat.sh create mode 100755 run_service1.sh rename run.sh => run_service2.sh (100%) create mode 100755 run_socat_server.sh rename service.cpp => service1.cpp (100%) diff --git a/Makefile b/Makefile index ef64cf6..98d20dd 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,11 @@ -all: pcat service service2 +all: pcat service1 service2 pcat: pcat.cpp - clang++ pcat.cpp -o pcat + ${CXX} pcat.cpp -o pcat -service: service.cpp - clang++ service.cpp -o service +1service1: service1.cpp + ${CXX} service1.cpp -o service1 service2: service2.cpp - clang++ service2.cpp -l pthread -o service2 + ${CXX} service2.cpp -l pthread -o service2 diff --git a/README.md b/README.md index 4d942db..57697f1 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ Named pipes services ==================== -Examples of how to design C++ services that use Linux' named pipes FIFO as I/O. +Examples of how to design services that use Linux' named pipes FIFO as I/O. + Rationale --------- @@ -20,28 +21,48 @@ Once you made your service on top of named pipes, it is easy to wrap it within an interface made with other languages/tools. For instance, it is very easy to expose it on the network using common Linux tools like `socat`. +Be warned that this is not secure, though, you should only use this for testing +purpose in a secure local network. + Build and run ------------- ```sh ./build.sh -./run.sh +./run_service2.sh ``` Examples -------- -Two examples are provided. +To create the named pipes under Linux, use the `mkfifo` command, as shown in the `build.sh` +script. + +### Trivial example: a `cat` service + +The `pcat` executable implements a service that reads from a named pipe and +print its content on the standard output. +It's just like a `cat` command, but that would not end after the first read, but +will continue reading from the pipe instead. + +This kind of service is just a simple loop that iterates over blocking I/O calls +on the named pipes, thus having zero CPU cost for polling. + +The file `run_pcat.sh` shows how to run the example. + +Note: if this example prints "Hello World!" multiple times, that's because you +did not created the `data` file as a named pipe, but as a regular file. Hence, +instead of emptying its content after reading, it keeps reading the same +content. + +The `pcat.py` is the same example, but in Python instead of C++. + ### Simple service The first example `./service in out` implements a service that reads from a named pipe `in` and writes to another one `out`. -To create the named pipes, use the `mkfifo` command. - -The service is just a simple loop that iterates over blocking I/O calls -on the named pipes, thus having zero CPU cost for polling. Once launched, the service will wait for the pipes to be consummed, for instance with two commands. @@ -54,7 +75,9 @@ The second one reads the result: cat out ``` -Note that you can use the same pipe for input and output: `./service data data`. +The file `run_service1.sh` shows how to run this example. + +Note that you can use the same pipe for input and output: `./service1 data data`. ### Service with two dependent inputs @@ -66,23 +89,26 @@ after which it is possible to consume some "data". The service use two threads, one to poll the context and one to poll the data and do something with it. -The script `run.sh` shows how to test it. +The script `run_service2.sh` shows how to test it. Run it and it should show `Context: data` as a last line. +Use `Ctrl-C` to close the remaining `cat` process. + Furthermore ----------- If you want to expose such a service as a network server, just use socat. -For example, to get _data_ query from the network for `service2`: +For example, to get _data_ query from the network for `service1`: ```sh socat -v -u TCP-LISTEN:8423,reuseaddr PIPE:./data ``` +(see `run_socat_server.sh` for a complete example). You can test it by sending something on the connection: ```sh -echo "data" > /dev/tcp/127.0.0.1/8423 +echo "Hello World!" > /dev/tcp/127.0.0.1/8423 ``` Conversely, to send automatically back the answer to some server: @@ -91,12 +117,28 @@ socat -v -u PIPE:./out TCP2:8424:host ``` Be aware that `socat` will terminate as soon as it receives the end of the message. -Thus, if you want to establish a permanent gate, you will have to automatically restart it: +Thus, if you want to establish a permanent gate, you will have to use the `fork` +option: ```sh -while true; do socat TCP-LISTEN:8478,reuseaddr PIPE:/./data || break; done +socat TCP-LISTEN:8478,reuseaddr,fork PIPE:/./data ``` +Troubleshooting +=============== + +If you witness strange behavior while debugging your own services (like prints +that do not occur in the correct terminal), double check that yo don't have some +remaining detached processes that would not have been killed. + +For instance, if: +```sh +ps aux | grep pcat +``` +returns anything, you would need to `killall pcat`, or else several concurent +processes would read the same pipe, which leads to (almost) undefined behavior. + + Author & license ---------------- diff --git a/build.sh b/build.sh old mode 100644 new mode 100755 diff --git a/run_pcat.sh b/run_pcat.sh new file mode 100755 index 0000000..75ffa9e --- /dev/null +++ b/run_pcat.sh @@ -0,0 +1,8 @@ + +# Start the service and let it run in the background. +./pcat data & +PID_SERVICE=$! + +echo "Hello world!" > data & + +KILL $PID_SERVICE diff --git a/run_service1.sh b/run_service1.sh new file mode 100755 index 0000000..5a6d71d --- /dev/null +++ b/run_service1.sh @@ -0,0 +1,11 @@ + +./service1 data > out & +PID_SERVICE=$! + +echo "Hellow World!" > data & + +cat out & + +echo "exit" > data + +# kill $PID_SERVICE diff --git a/run.sh b/run_service2.sh similarity index 100% rename from run.sh rename to run_service2.sh diff --git a/run_socat_server.sh b/run_socat_server.sh new file mode 100755 index 0000000..aa655bd --- /dev/null +++ b/run_socat_server.sh @@ -0,0 +1,7 @@ + +./service1 data out & +PID_SERVICE=$! + +socat -v -u TCP-LISTEN:8423,reuseaddr,fork PIPE:./data + +kill $PID_SERVICE diff --git a/service.cpp b/service1.cpp similarity index 100% rename from service.cpp rename to service1.cpp diff --git a/service2.cpp b/service2.cpp index b3bd08f..1a3dbea 100644 --- a/service2.cpp +++ b/service2.cpp @@ -18,7 +18,6 @@ protected: std::string _file_current_context; std::string _file_data; std::string _out; - const unsigned int _sleep; std::string _current_context; bool has_current_context() @@ -38,14 +37,12 @@ public: Service( std::string context, std::string data, - std::string out, - unsigned int sleep = 100 + std::string out ) : _has_current_context(false), _file_current_context(context), _file_data(data), - _out(out), - _sleep(sleep) + _out(out) {} std::string strip(std::string s) @@ -74,7 +71,6 @@ public: this->has_current_context(true); std::clog << "\tReceived context: " << _current_context << std::endl; } - std::this_thread::sleep_for(std::chrono::milliseconds(_sleep)); } } @@ -109,7 +105,6 @@ public: std::clog << "\tdone" << std::endl; } // if not has_error } - std::this_thread::sleep_for(std::chrono::milliseconds(_sleep)); } // while true } }; @@ -137,7 +132,7 @@ int main(int argc, char** argv) std::clog << "Start server" << std::endl; std::clog.flush(); - Service server(argv[1], argv[2], argv[3], 100); + Service server(argv[1], argv[2], argv[3]); std::thread do_current_context(&Service::update_current_context, &server); std::thread do_tasks(&Service::handle_data, &server);