[ANN] asio - yet another networking library!

Hi all, In light of some of the recent discussion about sockets (e.g. the "behond IOStreams" thread), I'd like to take this opportunity to announce "asio". asio will be going live in a commercial system in a little over a month, and I think it's now at a point where it is suitable for use by a wider audience. I would like to think that asio has potential as the basis of a boostified networking library, or that at least it can inject some ideas into the discussion. asio is a cross-platform library for doing network programming, using a "modern" c++ style. It is designed to use an asynchronous I/O model for virtually all operations, even when the OS lacks native asynchronous I/O. The library is intended to ease the development of scalable network applications through the use of a proactive event-driven architecture. You can download asio from: http://tenermerx.com/programming/cpp/asio/asio-0.1.12.tar.gz I have also put the generated documentation online at: http://tenermerx.com/programming/cpp/asio/asio-0.1.12/doc/html/ asio supports Win32 and Linux, and has been infrequently tested on Solaris. It is free for any use. Here is a sample usage of asio showing how to receive data on a socket: char buf[BUFSIZ]; void handle_receive(asio::error& e, size_t bytes_recvd) { if (!e) { /*...*/ } } int start_receiving(asio::socket& s) { s.async_recv(buf, BUFSIZ, boost::bind( handle_receive, asio::arg::error, asio::arg::bytes_recvd)); } I have been working on asio for well over a year and, as I said, it will be going into production in a commercial system in a little over a month. For commercial reasons I can't give too much detail, but I can say that asio has been used to develop a custom HTTP proxy that supports hundreds of concurrent connections. The inspiration for asio was the work of Alex Libman in developing a "portable Proactor framework" for ACE (see http://www.terabit.com.au). This is a Proactor that can use select or a similar synchronous event demultiplexer if no more appropriate OS mechanism is available. Regards, Chris

Christopher, a few quick questions (call me lazy ;)... - will asio use epoll on modern linux boxes? - will asio use kqueue when that is available on other OS? - What does asio use on windows? Is that also scalable to 20,000 socket descriptors, like epoll and kqueue are? -- Carlo Wood <carlo@alinoe.com>

Hi Carlo, --- Carlo Wood <carlo@alinoe.com> wrote:
a few quick questions (call me lazy ;)...
- will asio use epoll on modern linux boxes?
- will asio use kqueue when that is available on other OS?
At the moment, the answer to both the previous questions is no. The only implementation it currently uses on Linux is select-based. However, the API has been designed to transparently support other mechanisms such as epoll, for when I get the time to add them :)
- What does asio use on windows? Is that also scalable to 20,000 socket descriptors, like epoll and kqueue are?
It uses IO completion ports. I'm afraid I don't recall any numbers, but certainly they scale to the thousands. I believe Microsoft pushes them as the most scalable way to write servers on Windows. Regards, Chris

On Tue, Sep 14, 2004 at 12:17:20AM +1000, Christopher Kohlhoff wrote:
- will asio use epoll on modern linux boxes?
- will asio use kqueue when that is available on other OS?
At the moment, the answer to both the previous questions is no. The only implementation it currently uses on Linux is select-based. However, the API has been designed to transparently support other mechanisms such as epoll, for when I get the time to add them :)
Ok, np. It will easy to add it.
- What does asio use on windows? Is that also scalable to 20,000 socket descriptors, like epoll and kqueue are?
It uses IO completion ports. I'm afraid I don't recall any numbers, but certainly they scale to the thousands. I believe Microsoft pushes them as the most scalable way to write servers on Windows.
Can someone explain to me how it is possible that libACE doesn't use IO completion ports by default?! (Or at all) :) Anyway - I am glad to hear that. Next question that comes to mind: is asio creating any threads itself in order to achieve its normal functionality (on windows)? I am still reading your documentation, but I already have the following questions / remarks that I'd like you reaction on: - The timer resolution of 1 second is too low (see below). Would you be willing to revise/change that part of the interface? [ A resolution of 1 second means that you can request 1 second and get 0 seconds (ie, you call now() 1 microsecond before it would return the next integer, add 1 and request the time out with that). A better timer interface, one that I use myself in my networking library, is to always work with "times" as offsets relative to a function returning 'now()'. The value returned by 'now()' should not change in between calls to the system function that actually waits for events (it is only updated once per mainloop loop thus). When requesting a time event you would for example do: asio::timer t(d, seconds(5)); instead of asio::timer t(d, asio::time::now() + 5); The advantage is that if you request 1 second this way, you will get a much more accurate 'second' because internally the clock has microsecond accuracy (ie, select(2) on linux) or at least milisecond accuracy. On top if that, I think that you should allow miliseconds precision timeouts, ie: asio::timer t(d, miliseconds(5)); A timer of 100 ms and even 10 ms is not an imaginary need in some cases. ] - What happens when asio::demuxer::run is running (and waiting/sleeping for events) and then the application receives a signal? - Is it possible to treat signals as events that are dispatched from the mainloop? If not, then I think direct support for this has to be added. A signal handler can be called at almost any moment in the code and can therefore not use system resources, or shared resources, of any kind. You can't even have it wait for a mutex. Basically all a signal handler can do is set an atomic flag that there has been a signal. The actual actions that need to be taken cannot be done from within the signal handler but need to be done from the mainloop. Therefore it is a requirement that a signal causes either a callback - or at least causes a return from asio::demuxer::run() (at which point you need to be able to detect that it returned because of a signal, and not because 'no more work' was available). And a last remark so far, - asio::thread doesn't belong in the library (its a handy thing, but just doesn't belong HERE). -- Carlo Wood <carlo@alinoe.com>

Carlo Wood wrote:
If not, then I think direct support for this has to be added. A signal handler can be called at almost any moment in the code and can therefore not use system resources, or shared resources, of any kind. You can't even have it wait for a mutex. Basically all a signal handler can do is set an atomic flag that there has been a signal.
The trouble here is that you lose information if two signals arrive back-to-back (which unfortunately POSIX allows the implementation to lose anyway, but that doesn't mean you can be as lazy as the laziest implementation!) Also, some signals will not interrupt select() on some POSIX-compliant UNIXes. I searched long and hard for a perfect generic signal handler, and unfortunately, concluded there was no such thing. The best implementation I came up with was using an atomic add to increment a counter in a table of signals, and then writing to a pipe (which can also be used as a hint) to signal select(). Specific operating systems, such as Linux, will still require something more to decode extra information within exceptions. Aaron W. LaFramboise

On Mon, Sep 13, 2004 at 04:02:29PM -0500, Aaron W. LaFramboise wrote:
The best implementation I came up with was using an atomic add to increment a counter in a table of signals,
That is how I solved this too in libcw.
and then writing to a pipe (which can also be used as a hint) to signal select(). Specific operating systems, such as Linux, will still require something more to decode extra information within exceptions.
Hmm, like what? My experience so far is that "it worked". select() returns with EINTR and the table provided the number of times a signal of a given kind was received. I didn't try too many signals though - just SIGINT, SIGTERM, SIGARLM, SIGHUP .. the usual stuff. -- Carlo Wood <carlo@alinoe.com>

Carlo Wood wrote:
On Mon, Sep 13, 2004 at 04:02:29PM -0500, Aaron W. LaFramboise wrote:
and then writing to a pipe (which can also be used as a hint) to signal select(). Specific operating systems, such as Linux, will still require something more to decode extra information within exceptions.
Hmm, like what? My experience so far is that "it worked". select() returns with EINTR and the table provided the number of times a signal of a given kind was received. I didn't try too many signals though - just SIGINT, SIGTERM, SIGARLM, SIGHUP .. the usual stuff.
I mean the extended, system-specific information that comes along with many signals, usually using siginfo_t. Older pre-siginfo systems often use an undocumented second parameter to the signal handler to convey additional information also. Aaron W. LaFramboise

Hi Carlo, --- Carlo Wood <carlo@alinoe.com> wrote:
Can someone explain to me how it is possible that libACE doesn't use IO completion ports by default?! (Or at all) :)
ACE does support IO completion ports, it just does it through the Proactor pattern. I.e. ACE supports two different patterns for event demultiplexing: Reactor and Proactor.
Next question that comes to mind: is asio creating any threads itself in order to achieve its normal functionality (on windows)?
Yes, it has an internal thread for doing timers and simulating asynchronous connect/accept. However asio will *never* deliver a callback to application code from such a thread. There is a rule that callbacks will only be invoked from a thread that calls demuxer::run().
- The timer resolution of 1 second is too low (see below). Would you be willing to revise/change that part of the interface?
The timers use an asio::time class that includes both a seconds and a microseconds component. I opted to just show the seconds in the tutorial for simplicity.
[ A resolution of 1 second means that you can request 1 second and get 0 seconds (ie, you call now() 1 microsecond before it would return the next integer, add 1 and request the time out with that). A better timer interface, one that I use myself in my networking library, is to always work with "times" as offsets relative to a function returning 'now()'. The value returned by 'now()' should not change in between calls to the system function that actually waits for events (it is only updated once per mainloop loop thus). When requesting a time event you would for example do:
asio::timer t(d, seconds(5));
instead of
asio::timer t(d, asio::time::now() + 5);
The advantage is that if you request 1 second this way, you will get a much more accurate 'second' because internally the clock has microsecond accuracy (ie, select(2) on linux) or at least milisecond accuracy.
First, it has been my experience that, in network applications, working with "absolute" times (i.e. relative to the epoch) is a more useful approach. Further, as I said above, the timers do also use microseconds. So to set a timer for half a second from now: asio::timer t(d, asio::time::now() + asio::time(0, 500000)); or to "bump" a timer along by a further .1 seconds say, you would go: t.expiry(t.expiry() + asio::time(0, 100000));
- What happens when asio::demuxer::run is running (and waiting/sleeping for events) and then the application receives a signal?
Good question :) At the moment I do not do any special handling of signals (and in general I avoid their use), and it's up to the program to do its own signal handling. It's something on my todo list.
- Is it possible to treat signals as events that are dispatched from the mainloop?
If not, then I think direct support for this has to be added. A signal handler can be called at almost any moment in the code and can therefore not use system resources, or shared resources, of any kind. You can't even have it wait for a mutex. Basically all a signal handler can do is set an atomic flag that there has been a signal.
The approach I have been considering is to have an internal thread doing a sigwait, and then have it post the events through the demuxer. Once doing this I would consider that all signals should be treated like other events and that they should not have any special effect on the demuxer (although they might have a default event handler which calls demuxer.interrupt()).
- asio::thread doesn't belong in the library (its a handy thing, but just doesn't belong HERE).
I totally agree. Initially I took a purist approach and did not provide any thread class, instead telling people to use something else, like boost::thread or ACE_Task. However, one of my goals was to allow people to use asio without linking to any non-system libraries (i.e. header-files only) to minimise barriers to using asio, and neither boost::thread nor ACE fit the bill here. And as spawning a thread to run the demuxer was such a common requirement, a minimal asio::thread was created. Regards, Chris

Hi Carlo, --- Carlo Wood <carlo@alinoe.com> wrote:
Can someone explain to me how it is possible that libACE doesn't use IO completion ports by default?! (Or at all) :)
ACE does support IO completion ports, it just does it through the Proactor pattern. I.e. ACE supports two different patterns for event demultiplexing: Reactor and Proactor.
Next question that comes to mind: is asio creating any threads itself in order to achieve its normal functionality (on windows)?
Yes, it has an internal thread for doing timers and simulating asynchronous connect/accept. However asio will *never* deliver a callback to application code from such a thread. There is a rule that callbacks will only be invoked from a thread that calls demuxer::run().
- The timer resolution of 1 second is too low (see below). Would you be willing to revise/change that part of the interface?
The timers use an asio::time class that includes both a seconds and a microseconds component. I opted to just show the seconds in the tutorial for simplicity.
[ A resolution of 1 second means that you can request 1 second and get 0 seconds (ie, you call now() 1 microsecond before it would return the next integer, add 1 and request the time out with that). A better timer interface, one that I use myself in my networking library, is to always work with "times" as offsets relative to a function returning 'now()'. The value returned by 'now()' should not change in between calls to the system function that actually waits for events (it is only updated once per mainloop loop thus). When requesting a time event you would for example do:
asio::timer t(d, seconds(5));
instead of
asio::timer t(d, asio::time::now() + 5);
The advantage is that if you request 1 second this way, you will get a much more accurate 'second' because internally the clock has microsecond accuracy (ie, select(2) on linux) or at least milisecond accuracy.
First, it has been my experience that, in network applications, working with "absolute" times (i.e. relative to the epoch) is a more useful approach. Further, as I said above, the timers do also use microseconds. So to set a timer for half a second from now: asio::timer t(d, asio::time::now() + asio::time(0, 500000)); or to "bump" a timer along by a further .1 seconds say, you would go: t.expiry(t.expiry() + asio::time(0, 100000));
- What happens when asio::demuxer::run is running (and waiting/sleeping for events) and then the application receives a signal?
Good question :) At the moment I do not do any special handling of signals (and in general I avoid their use), and it's up to the program to do its own signal handling. It's something on my todo list.
- Is it possible to treat signals as events that are dispatched from the mainloop?
If not, then I think direct support for this has to be added. A signal handler can be called at almost any moment in the code and can therefore not use system resources, or shared resources, of any kind. You can't even have it wait for a mutex. Basically all a signal handler can do is set an atomic flag that there has been a signal.
The approach I have been considering is to have an internal thread doing a sigwait, and then have it post the events through the demuxer. Once doing this I would consider that all signals should be treated like other events and that they should not have any special effect on the demuxer (although they might have a default event handler which calls demuxer.interrupt()).
- asio::thread doesn't belong in the library (its a handy thing, but just doesn't belong HERE).
I totally agree. Initially I took a purist approach and did not provide any thread class, instead telling people to use something else, like boost::thread or ACE_Task. However, one of my goals was to allow people to use asio without linking to any non-system libraries (i.e. header-files only) to minimise barriers to using asio, and neither boost::thread nor ACE fit the bill here. And as spawning a thread to run the demuxer was such a common requirement, a minimal asio::thread was created. Regards, Chris

On Mon, 13 Sep 2004 16:12:45 +1000 (EST), Christopher Kohlhoff wrote
Hi all,
In light of some of the recent discussion about sockets (e.g. the "behond IOStreams" thread), I'd like to take this opportunity to announce "asio". asio will be going live in a commercial system in a little over a month, and I think it's now at a point where it is suitable for use by a wider audience. I would like to think that asio has potential as the basis of a boostified networking library, or that at least it can inject some ideas into the discussion.
asio is a cross-platform library for doing network programming, using a "modern" c++ style. It is designed to use an asynchronous I/O model for virtually all operations, even when the OS lacks native asynchronous I/O. The library is intended to ease the development of scalable network applications through the use of a proactive event-driven architecture.
I've had a quick look thru the docs and I'll say this looks very promising. Most of what I see is the sort of approach I would expect for a 'modern c++' networking library. Anyway, I don't have too much time to play with it now, but I hope that you can bring this forward. A good networking library is sorely needed in boost.
The inspiration for asio was the work of Alex Libman in developing a "portable Proactor framework" for ACE (see http://www.terabit.com.au)
This link isn't resolving for me... Jeff

Hi Jeff, --- Jeff Garland <jeff@crystalclearsoftware.com> wrote:
The inspiration for asio was the work of Alex Libman in developing a "portable Proactor framework" for ACE (see http://www.terabit.com.au)
This link isn't resolving for me...
I'm not sure what's up with that server. I'll contact Alex and see, if he can't fix his server, whether I can get a copy of his docs so I can put them on my website. Regards, Chris
participants (5)
-
Aaron W. LaFramboise
-
Carlo Wood
-
Christopher Kohlhoff
-
Christopher Kohlhoff
-
Jeff Garland