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.
This commit is contained in:
Johann Dreo 2021-08-05 10:08:49 +02:00
commit f74f89734c
9 changed files with 89 additions and 26 deletions

View file

@ -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

View file

@ -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
----------------

0
build.sh Normal file → Executable file
View file

8
run_pcat.sh Executable file
View file

@ -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

11
run_service1.sh Executable file
View file

@ -0,0 +1,11 @@
./service1 data > out &
PID_SERVICE=$!
echo "Hellow World!" > data &
cat out &
echo "exit" > data
# kill $PID_SERVICE

7
run_socat_server.sh Executable file
View file

@ -0,0 +1,7 @@
./service1 data out &
PID_SERVICE=$!
socat -v -u TCP-LISTEN:8423,reuseaddr,fork PIPE:./data
kill $PID_SERVICE

View file

@ -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);