Networking TS + Beast, NEW Tutorials, Read this to learn std::net !!!
Fellow C++, Boost, and WG21 Enthusiasts, lend me your ear! I write to inform you about exciting developments in C++ networking. First, a bit of background. Networking comes in three flavors: * Networking TS https://cplusplus.github.io/networking-ts/draft.pdf * Boost.Asio https://www.boost.org/doc/libs/1_69_0/doc/html/boost_asio.html * Standalone Asio https://github.com/chriskohlhoff/asio These three are for the most part identical, except that Asio flavors have additional features like ssl::stream and signal_set which are not in the TS, but will very likely appear in a future update or version. We've had Asio for over a decade now, but there is a shortage of experts. Some people believe this shortage is because Asio in particular (and thus, Networking TS since they have identical interfaces) is "difficult to use." I believe it is wrong to blame Asio for this. Concurrent programs in general are hard to write. This is applicable: "Unfortunately, today's reality is that only thoughtful experts can write explicitly concurrent programs that are correct and efficient. This is because today's programming models for concurrency are subtle, intricate, and fraught with pitfalls that easily (and frequently) result in unforeseen races (i.e., program corruption) deadlocks (i.e., program lockup) and performance cliffs (e.g., priority inversion, convoying, and sometimes complete loss of parallelism and/or even worse performance than a single-threaded program). And even when a correct and efficient concurrent program is written, it takes great care to maintain — it's usually brittle and difficult to maintain correctly because current programming models set a very high bar of expertise required to reason reliably about the operation of concurrent programs, so that apparently innocuous changes to a working concurrent program can (and commonly do, in practice) render it entirely or intermittently nonworking in unintended and unexpected ways. Because getting it right and keeping it right is so difficult, at many major software companies there is a veritable priesthood of gurus who write and maintain the core concurrent code." - Herb Sutter, "The Trouble with Locks", http://www.drdobbs.com/cpp/the-trouble-with-locks/184401930 Although this was written in 2005 it is still relevant today. It is understandable that Asio will be the first target of anger and frustration when writing concurrent programs, since it is on the "front line" so to speak. There has also been a distinct shortage of *good* tutorials and examples for Asio. Articles or blog posts which teach you step by step, explaining everything, and giving example code which demonstrates best practices. Boost.Beast is my low-level HTTP/WebSocket library which builds on Boost.Asio: https://github.com/boostorg/beast In the original release of Beast, the documentation stated "prior understanding of Boost.Asio is required." However, field experience has shown that users ignore that requirement and attempt to write complex, concurrent programs as their first-time introduction to both Beast and Asio. Based on feedback from committee members, and to serve users better, the scope of Beast has been enlarged to include first-time users of networking. The upcoming Boost 1.70 release reflects this new scope and I am excited to announce some very nice things which you can access today. First of all, Beast documentation and examples no longer use the "boost::asio" namespace, they the namespace alias "net::". While this is cosmetic, it reinforces the notion when inspecting code that it is equally applicable to Boost.Asio, Asio, and Networking TS (identifiers which are not in the TS, such as signal_set, are still qualified with boost::asio). A new documentation page explains the three flavors of networking: https://www.boost.org/doc/libs/master/libs/beast/doc/html/beast/using_io.htm... This is also explained in my 2018 CppCon presentation: https://youtu.be/7FQwAjELMek?t=444 I have added a "Networking Refresher", a complete overview of networking from soup to nuts. No prior knowledge or understanding of networking is required, everything is explained in detail so if you want to learn this is the place to start. I also kept it short, but it is loaded with hyperlinks for further learning: https://www.boost.org/doc/libs/master/libs/beast/doc/html/beast/using_io/asi... There was a recent paper in Kona, P1269R0 ("Three Years with the Networking TS") about difficulty of implementing timeouts. To address this, Beast now has a stream class which implements configurable timeouts for you, and callers no longer have to fiddle with timers manually anymore. Everything "Just Works." It achieves the P1269R0 author's goal of having timeouts "built-in to asynchronous operations", but in a way that fits in with the design of the TS: https://www.boost.org/doc/libs/master/libs/beast/doc/html/beast/using_io/tim... I feel that this `beast::basic_stream` serves as an existence proof that the current design of Networking TS is sound - the TS offers a flexible toolbox which lets you build your own framework the way that you want it, without making odd choices for you. We are still discovering ways of leveraging it to maximum use. The beast::websocket::stream also has built-in timeouts, but they are enhanced to support "idle pings" (keeping client connections alive) and everything is fully configurable: https://www.boost.org/doc/libs/master/libs/beast/doc/html/beast/using_websoc... All you need to do to get sensible, suggested websocket timeouts is add one line of code after creating your stream: ws.set_option(websocket::stream_base::timeout::suggested( beast::role_type::server)); To address the cumbersome boilerplate of writing composed operations (specifically the need to propagate the associated allocator and associated executor, and to avoid invoking the completion handler from within the initiating function when the operation would complete immediately) Beast adds two new utility base classes, with plentiful documentation and examples throughout: https://www.boost.org/doc/libs/master/libs/beast/doc/html/beast/using_io/wri... There are two well-rounded examples which show you step by step how to write these things in a safe way: https://www.boost.org/doc/libs/master/libs/beast/doc/html/beast/using_io/wri... https://www.boost.org/doc/libs/master/libs/beast/doc/html/beast/using_io/wri... I have a big, new open source project which implements a server, that uses the `system_context`, taking full advantage of native Windows and Mac OS system-level execution context features. To support this use case and industry feedback, the examples in Beast now default to being always thread-safe. All examples use a "strand", and leverage P1322R0 ("Networking TS enhancement to enable custom I/O executors"). Yes, this paper which was approved in Kona, is now implemented in both Boost.Beast, and Boost.Asio, including all of the Beast examples, so if you pick up Boost 1.70 (or the master branches from github) you can start playing with this as soon as you're done reading this message!! http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1322r0.html We have an active #beast channel in the C++ Slack (https://slack.cpp.al) where experienced users can help, no question is too small! I hope you will join me and the rest of the Beast and Asio community in exploring what the very powerful Networking TS and Asio libraries have to offer, and build something great together! Regards P.S. Don't forget to star the repository! https://github.com/boostorg/beast
Here are comments from somebody who "knows C++" but does not know a lot about NW and has read only the 1st https://www.boost.org/doc/libs/master/libs/beast/doc/html/beast/using_io/asi... page. // get iterators representing the range of characters in the buffer auto begin = range::begin(buffer.data()); auto end = range::end(buffer.data()); Quite a weird pattern, where buffer does not have member begin, end. Maybe a sentence or two describing the rationale and complexity would be nice. I know it is O(1), but it is weird that begin/end are called on pointer. std::size_t read_lineI am tired, but is this function O(n^2)? cb += bytes_transferred; // adjust the pointer and sizeI would maybe switch to write instead of write_some to keep the code shorter, and mention write_some as another option in text. [](error_code ec, std::size_t bytes_transferred) For learning purposes I would capture something in lambda by value and note that reference capture would be buggy. using type = std::allocator<void>; Personal preference: not a fan of typedefs for relatively short types used once. All in all nice intro, unfortunately it requires users to understand templates quite well, but honestly not sure if it is avoidable, with a gazillion customization points in ASIO.
On Thu, Mar 14, 2019 at 5:09 PM Ivan Matek
Quite a weird pattern, where buffer does not have member begin, end. Maybe a sentence or two describing the rationale and complexity would be nice. I know it is O(1), but it is weird that begin/end are called on pointer.
Yeah, well... that's a sore subject. First of all I did not invent these buffer concepts, they are part of Asio and Networking TS. And they *used* to have begin() and end(), but then the C++ Standards Committee decided that they didn't like `const_buffers_1` and `mutable_buffers_1`, so they got rid of them in the TS draft. Then they realized that having `const_buffer::begin()` would be confusing. Does it refer to the beginning of the buffer sequence, or does it refer to the first byte of that buffer? They decided to get rid of begin/end entirely for the buffer types and add buffer_sequence_begin and buffer_sequence_end in its place. And this is why we can't have nice things.
std::size_t read_line I am tired, but is this function O(n^2)?
It starts over from the beginning of the range so there can be some overlap of the searching, yes.
cb += bytes_transferred; // adjust the pointer and size I would maybe switch to write instead of write_some to keep the code shorter, and mention write_some as another option in text.
Well the purpose of this example is to show the looping.
[](error_code ec, std::size_t bytes_transferred) For learning purposes I would capture something in lambda by value and note that reference capture would be buggy.
That is something I might consider!! Remember though I"m stuck in C++11 so no lambda capture assignment expressions. Appreciate the feedback! Regards
Hi, Am 15.03.2019 01:19, schrieb Vinnie Falco via Boost:
On Thu, Mar 14, 2019 at 5:09 PM Ivan Matek
wrote: Quite a weird pattern, where buffer does not have member begin, end. Maybe a sentence or two describing the rationale and complexity would be nice. I know it is O(1), but it is weird that begin/end are called on pointer.
Yeah, well... that's a sore subject. First of all I did not invent these buffer concepts, they are part of Asio and Networking TS. And they *used* to have begin() and end(), but then the C++ Standards Committee decided that they didn't like `const_buffers_1` and `mutable_buffers_1`, so they got rid of them in the TS draft. Then they realized that having `const_buffer::begin()` would be confusing. Does it refer to the beginning of the buffer sequence, or does it refer to the first byte of that buffer? They decided to get rid of begin/end entirely for the buffer types and add buffer_sequence_begin and buffer_sequence_end in its place. And this is why we can't have nice things.
Don't be sad :-) template <typename BufferSequenceType> class buffer_sequence_view { private: BufferSequenceType& sequence_; public: buffer_sequence_view(BufferSequenceType& s) : sequence_{s} {}; buffer_sequence_view(buffer_sequence_view& other) : sequence_{other.sequence_} {}; // ... auto begin() { return buffer_sequence_begin(sequence_); }; auto end() { return buffer_sequence_end(sequence_); }; // ... }; template <typename BufferType> class buffer_view { private: BufferType& buffer_; public: buffer_view(BufferType& b) : buffer_{b} {}; buffer_view(buffer_view& other) : buffer_{other.buffer_} {}; // ... auto begin() { return range::begin(buffer_.data()); }; auto end() { return range::end(buffer_.data()); }; // ... }; for( const auto& single_buffer : buffer_sequence_view(buffer_sequence) ) { // iterates over all elements of the buffer sequence ... } for( const auto& c : buffer_view(buffer) | view::take(42) ) { // iterates over the first 42 characters of a buffer (sequence) ... // I hope my interpretation of range::begin() and range::end() is correct; // this is completely untested. ;-) } So you can still have all the nice things with ranges based for loops, C++20 ranges, etc. Though I agree, that these views should be part of the standard. Otherwise everyone is going to implement them on their own with varying quality (you probably already have spotted some issues with my untested example code here), or even worse, works without them. Christof
On Fri, Mar 15, 2019 at 2:33 AM Christof Donat via Boost
... class buffer_sequence_view { ... template <typename BufferType> class buffer_view {
Right, or just use the one in beast :) https://www.boost.org/doc/libs/master/libs/beast/doc/html/beast/ref/boost__b...
Thank you for the reply, suggestions inline.
On Fri, Mar 15, 2019 at 1:19 AM Vinnie Falco
And this is why we can't have nice things.
Somebody(tm) (ASIO or Beast) maybe should make buffer that has has member fn content() that returns a range with begin and end. example: if buffer is 8 bytes and you write 3 bytes buff.content().begin() would return .data() and content().end() would return .data()+3
std::size_t read_line I am tired, but is this function O(n^2)?
It starts over from the beginning of the range so there can be some overlap of the searching, yes.
I beg you to fix this by remembering the index of last find(I assume
iterators are invalidated). I know in theory it is just an example, but
people blindly c/p code, and to make matters worse this thing is hard to
notice in tests since only very large total reads show O(n^2) behavior.
regards,
Ivan
On Fri, Mar 15, 2019 at 1:19 AM Vinnie Falco
On Thu, Mar 14, 2019 at 5:09 PM Ivan Matek
wrote: Quite a weird pattern, where buffer does not have member begin, end. Maybe a sentence or two describing the rationale and complexity would be nice. I know it is O(1), but it is weird that begin/end are called on pointer.
Yeah, well... that's a sore subject. First of all I did not invent these buffer concepts, they are part of Asio and Networking TS. And they *used* to have begin() and end(), but then the C++ Standards Committee decided that they didn't like `const_buffers_1` and `mutable_buffers_1`, so they got rid of them in the TS draft. Then they realized that having `const_buffer::begin()` would be confusing. Does it refer to the beginning of the buffer sequence, or does it refer to the first byte of that buffer? They decided to get rid of begin/end entirely for the buffer types and add buffer_sequence_begin and buffer_sequence_end in its place. And this is why we can't have nice things.
std::size_t read_line I am tired, but is this function O(n^2)?
It starts over from the beginning of the range so there can be some overlap of the searching, yes.
cb += bytes_transferred; // adjust the pointer and size I would maybe switch to write instead of write_some to keep the code shorter, and mention write_some as another option in text.
Well the purpose of this example is to show the looping.
[](error_code ec, std::size_t bytes_transferred) For learning purposes I would capture something in lambda by value and note that reference capture would be buggy.
That is something I might consider!! Remember though I"m stuck in C++11 so no lambda capture assignment expressions.
Appreciate the feedback!
Regards
Somebody(tm) (ASIO or Beast) maybe should make buffer that has has member fn content() that returns a range with begin and end.
Yes that is in Boost.Beast 1.70: https://www.boost.org/doc/libs/master/libs/beast/doc/html/beast/ref/boost__b...
participants (4)
-
Christof Donat
-
degski
-
Ivan Matek
-
Vinnie Falco