0

I'm a beginner in boost::asio.

I need to code a module which reads from a pipe and puts the data into a ring buffer (I've no problem in how to implement this part).

Another part of the module waits for a consumer to open a new TCP connection or unix domain socket and when the connection is made it sends the full ring buffer contents and then it will send any new data as soon as it is pushed into the ring buffer. Multiple consumers are allowed and one consumer can open a new connection at any time.

The first naive implementation I thought of is to keep a separate asio::streambuf for every connection and push the entire ring buffer into it on connection and then every new data, but it seems a very sub-optimal method to do it both in memory and cpu cycles as data has to be copyed for every connection, maybe multiple times as I don't know if boost::asio::send (or the linux tcp/ip stack) does a copy of the data.

As my idea is to use no multi threading at all, I'm thinking of using some form of custom asio::streambuf derived class which shares the actual buffer with the ring buffer, but keeps a separate state of the read pointer without the need of any lock.

It seems mine it is a pretty unusual need, because I'm unable to find any related documentation/question which deals with a similar subject and the boost documentation seems pretty brief and scarce to me (see e.g.: http://www.boost.org/doc/libs/1_57_0/doc/html/boost_asio/reference/basic_streambuf.html).

It would be nice if someone could point me to some ideas that I could take as starting point to implement my design or point me to an alternative design if he/she considers mine bad, un-implementable and/or improvable.

sehe
  • 374,641
  • 47
  • 450
  • 633
Patxitron
  • 435
  • 4
  • 8
  • _"boost documentation seems pretty rude and scarce to me"_ I'm going to assume ["rude"](http://dictionary.reference.com/browse/rude) doesn't mean what you thought it does. Can you link to the documentation you don't understand? – sehe Feb 08 '15 at 10:43
  • I've seen two downvotes. I've edited the question to add the linux tag and then I've seen one downvote, then I thought it was because I've leave empty the edit brief explanation so I've edited again only to justify the previous edit and I've seen the second downvote. I don't know if I get an authomatic downvote for every edit or there is people downvoting it no explaining his/her reasons. – Patxitron Feb 08 '15 at 10:44
  • 1
    It's the latter. Your question is very broad, and doesn't show any particular effort. I mean this exactly as I said. You don't show /what/ it is that's giving you trouble. You can't really expect us to come up with a working example, but without the concrete code you're stuck with, the question doesn't seem like a good fit for SO. (I'm in the process of writing an answer by the way) – sehe Feb 08 '15 at 10:46
  • Some of your down votes might come from the fact that you did not post any code snippet at all about your attempts. "rude" -> "rudimentary" also might improve the questions acceptance ;) Also, it might not be clear to many why you do not simply read from your pipe and serve both your "endpoints" from 1 function. But maybe that is due to me not really knowing about boost:asio. If you want something optimized, do it yourself (instead of using a library) ;) – BitTickler Feb 08 '15 at 10:49
  • @sehe: Sorry if I use the rude word in a wrong way. I am not an English speaking person and in spanish "rudo" may be a softer term. I'm refering to http://www.boost.org/doc/libs/1_57_0/doc/html/boost_asio/reference/basic_streambuf.html and I want to say it seems beginer unfriendly to me as it seems very brief and It explains how to use it but I can not get how to derive from it a custom class. – Patxitron Feb 08 '15 at 10:53
  • @Patxitron You can always refer to the many samples: http://www.boost.org/doc/libs/1_57_0/doc/html/boost_asio/examples.html – sehe Feb 08 '15 at 10:54
  • @user2225104 I can no post any code snippet because my question is more a design related one. You are right, it is a pretty broad question, but I can't even get any useful seach parameter to narrow it. – Patxitron Feb 08 '15 at 11:00
  • What I still cannot see in your question is, when you eventually discard the information you read from your pipe so far. When the first consumer connects or do you want to keep your information "since the beginning" for all new consumers to arrive? – BitTickler Feb 08 '15 at 11:08
  • @user2225104 My intention is the ring buffer keeps the last N bytes received from pipe. New bytes received overwrite the oldest ones. Then when a consumer connects those N newest bytes are sent to it and then every new byte received as soon as it is received. So a consumer does not discard data from the ring buffer and its full contents (history with limited size) is available for new consumers. – Patxitron Feb 08 '15 at 11:14
  • _"If you want something optimized, do it yourself (instead of using a library) ;)"_ - @user2225104 That sounds rather upside-down to me. It sounds more about [NIH syndrome](http://en.wikipedia.org/wiki/Not_invented_here) or potentially bad choice of library. I'm pretty certain the balance doesn't tip that way with Boost libraries. They might be /more than you need/ but they're rarely limiting in any respect. – sehe Feb 08 '15 at 11:22
  • @sehe It all depends on whether the problem is simple enough to be handled or if it is beyond what one can do. I would rather use some optimized mathematical algorithms from a lib. But for writing a queue, I would rather not (std::queue would be considered "no library" here). Nor for opening a socket or reading from a pipe. Not if I had to spend more time on learning the library than it would take me to write it myself. – BitTickler Feb 08 '15 at 13:30
  • @user2225104 the point being that it needs to be extremely complicated for the typical boost library to no longer be able to keep up. But I won't argue about taste - because that's card you're actually pulling. – sehe Feb 08 '15 at 14:03

2 Answers2

1

You should just do what you intend to.

You absolutely don't need a streambuf to use with Boost Asio: http://www.boost.org/doc/libs/release/doc/html/boost_asio/reference/buffer.html

If the problem is how to avoid having the producer "wait" until all consumers (read: connections) are done transmitting the data, you can always use ye olde trick of alternating output buffers.

Many ring buffer implementations allow direct splicing of a complete sequence of elements at once, (e.g. boost lockfree spsc_queue cache memory access). You could use such an operation to your advantage.

Also relevant:

Community
  • 1
  • 1
sehe
  • 374,641
  • 47
  • 450
  • 633
  • Added a link to more background (plus complete design ideas and links to implementations/presentations) about zero copy using Boost Asio. – sehe Feb 08 '15 at 10:57
0

It appears, that performance is a topic here. Independent of whether boost::asio is used or some hand knitted solution, performance (throughput) might be down the the drain already by the fact (as stated in the comment section of the OP), that single bytes are being traded (read from the pipe).
After the initial "burst phase" when a consumer connects, single bytes trickle from the pipe to the connected consumer sockets with read() and write() operations per byte (or a few bytes, if the application is not constantly polling).
Given that (the fact that the price for system calls read() and write() is paid for small amounts of data), I dare theorize that anything about multiple queues or single queue etc. is already in the shadow of that basic "design flaw". I put "design flaw" in quotes as it cannot always be avoided to have to handle exactly such a situation.

So, if throughput cannot be optimized anyway, I would recommend the most simple and straightforward solution which can be conceived.

The "no threads" statement in the OP implies non-blocking file descriptors for both the accept socket, the consumer data sockets and the pipe. Will this be another 100% CPU/core eating polling application? If this is not some kind of special ops hyper-optimized problem, I would rather not advice to use non-blocking file descriptors. Also, I would not worry about zero-copy or not.

One easy approach with threads would be to have the consumer sockets non-blocking, while pipe is in blocking mode. The thread which reads the pipe then pumps the data into a queue and calls the function which services all currently connected consumers. The listen socket (the one calling accept()) is in signaled state, when new client connections are pending. With mechanisms like kqueue (bsd) or epoll (linux etc.) or WaitForMultipleObjects (windows), the pipe reader thread can react to that situation as well.

In the times when nothing is to be done, your application is sleeping/blocking and friendly to our environment :)

BitTickler
  • 10,905
  • 5
  • 32
  • 53
  • I assume that the posix file descritor functionality of boost::asio does the epoll thing for me without having to consume cpu cycles in an active polling myself as in http://www.boost.org/doc/libs/1_49_0/doc/html/boost_asio/example/chat/posix_chat_client.cpp in fact I've already experimented with that feature: https://github.com/patxitron/BattleyeRconToolLinux – Patxitron Feb 09 '15 at 11:21
  • You're right, once the buffer is uploaded, the data will be sent not exactly in a byte by byte sequence, but in a few bytes a time (most ususally less than 100 at a time). I am now thinking about send those updates by iterating over an array of registered sockets. Thank you for your help. – Patxitron Feb 09 '15 at 11:45