asio networking proposal

As discussed in the "surveying async network io libraries" thread, I'd like to turn asio (asio.sourceforge.net) into a proposal for a boost networking library. As promised, here is a list of areas where asio could use some refinement. I'd appreciate input on: - What people think of the relative importance of the items. For example, what would be required for an initial implementation vs what could or should be left until a later revision. - Whether the proposed changes are of any value. - Any other things that should be on the list. What's Missing ============== * Asynchronous DNS operations. I think it would be reasonable to say that any operation that can block should have an asynchronous equivalent. Should these operations be cancellable (not necessarily portably implementable anyway)? * IPv6 support. I have no experience with IPv6, so it would be great if someone with experience could help out with the design here. * Asynchronous socket close. In certain circumstances closing a socket can block, so it would be reasonable to expect an asynchronous close operation. * Scatter-gather operations (sendv and recvv). * The BSD socket functions sendmsg/recvmsg (which I believe are necessary to support protocols such as SCTP). * SSL/TLS and DTLS support. This would require an external library, and how does that fit in with boost? * Other protocols (PF_UNIX, ...)? * Some type of integration (or support for) a higher-level iostreams-based socket library. * More examples. * Documentation on design and rationale. What Should Be Included and What Shouldn't ========================================== * Obviously asio::thread should not be part of a boost proposal, since that functionality is already covered by Boost.Thread. * The asio::time class should probably not be included, and wherever it's used replaced by Boost.Date-Time. Naming and Structure ==================== * The use of the word "Stream" can be confusing because its existing association in C++ with iostreams. Can anyone suggest an alternative? * Should stream_socket and dgram_socket be merged into a single socket class? There other types of sockets (e.g. sequenced packet) that would require additional classes if the current approach is retained. This would not be required if the BSD socket APIs were covered by a single class. Alternatively, should additional classes be added for the other socket types? * The current separate socket_connector implementation does not correspond to an OS resource, unlike socket_acceptor. This can be problematic, e.g. it prevents a Windows implementation using ConnectEx, and doesn't allow you to set socket options before the connection is established. Therefore I propose adding connect and async_connect operations to socket classes. Should the separate socket_connector be retained? * In designing asio I considered that people may want to customise the demultiplexing strategy associated with each resource. For example the basic_stream_socket template looks like: template <typename Service> class basic_stream_socket; where Service is a "facet" of the demuxer that provides demultiplexing facilities for the socket. The typedef for stream_socket specifies the Service type based on the target platform. However in practice I don't believe this level of customisation is required (to the best of my knowledge no existing asio user customises it), particularly if asio uses the "best" demultiplexing mechanism for each platform. Or another way of looking at it: developers who need this level of control might not use a general purpose networking library anyway. An alternative approach would to have the only template parameter be an allocator (since there is often memory allocation associated with asynchronous operations). For example: template <typename Allocator> class basic_demuxer; template <typename Allocator> class basic_stream_socket; where a stream_socket can only be used with a demuxer that has the same allocator type. The typedefs for demuxer and stream_socket would just use the default allocator. Cheers, Chris

Christopher Kohlhoff <chris@kohlhoff.com> writes:
As discussed in the "surveying async network io libraries" thread, I'd like to turn asio (asio.sourceforge.net) into a proposal for a boost networking library.
Fantastic!
As promised, here is a list of areas where asio could use some refinement.
I'd appreciate input on:
- What people think of the relative importance of the items. For example, what would be required for an initial implementation vs what could or should be left until a later revision.
- Whether the proposed changes are of any value.
- Any other things that should be on the list.
What's Missing ==============
All I can say is that the fact that you know you want to implement more features later shouldn't be an obstacle to inclusion now. -- Dave Abrahams Boost Consulting www.boost-consulting.com

"Christopher Kohlhoff" <chris@kohlhoff.com> wrote in message news:20050818142216.76498.qmail@web32610.mail.mud.yahoo.com...
As discussed in the "surveying async network io libraries" thread, I'd like to turn asio (asio.sourceforge.net) into a proposal for a boost networking library.
Sound great :-) I skimmed the docs and your tutorial seems to be very nice so far. (the links are good) I havn't followed the discussion, so this is just my random input. 1. I hate cryptic short names like dgram_socket. recv why not write datagram and receive? (the other short names seems ok and quite standard) Looking at the Daytime1 example, I wounder why 2. asio::ipv4::host host; host_resolver.get_host_by_name(host, argv[1]); is not written asio::ipv4::host host = host_resolver.host_by_name(argv[1]); 3. why is everything public in a host? 4. why does both the stream_socket and the socket_connector need to know about the demuxer? Can't the socket_connector ask the stream_socket for its demuxer (I want to simplify usage if possible) 5. can't socket.recv() use something more highlevel than void* and size_t arguments? Why not std::vector<char> ? (The same applies to all the interface functions) best regards Thorsten

"Thorsten Ottosen" <nesotto@cs.aau.dk> wrote in message news:de2lb2$d6u$1@sea.gmane.org...
"Christopher Kohlhoff" <chris@kohlhoff.com> wrote in message
5. can't socket.recv() use something more highlevel than void* and size_t arguments? Why not std::vector<char> ? (The same applies to all the interface functions)
Furthermore, if you do want array arguments, then you might just say template< class T, std:size_t N > void receive( T (&array)[N] ) { /* call void ptr version here */ } or perhaps just template< std::size_t N > void receive( char (&array)[N] ) { ... } br Thorsten

"Thorsten Ottosen" <nesotto@cs.aau.dk> writes:
"Thorsten Ottosen" <nesotto@cs.aau.dk> wrote in message news:de2lb2$d6u$1@sea.gmane.org...
"Christopher Kohlhoff" <chris@kohlhoff.com> wrote in message
5. can't socket.recv() use something more highlevel than void* and size_t arguments? Why not std::vector<char> ? (The same applies to all the interface functions)
std::vector<char> (even with additional offset and length parameters) introduces significant overhead for users that do not otherwise have any reason to use std::vector. I do not think the additional safety provided is worth the cost of this overhead.
Furthermore, if you do want array arguments, then you might just say
template< class T, std:size_t N > void receive( T (&array)[N] ) { /* call void ptr version here */ }
or perhaps just
template< std::size_t N > void receive( char (&array)[N] ) { ... }
Requiring that the size be known at compile-time, and that an actual static-sized array be used in order to call the receive function, is definitely not a good idea. -- Jeremy Maitin-Shepard

"Jeremy Maitin-Shepard" <jbms@cmu.edu> wrote in message news:87iry3c9cn.fsf@jbms.ath.cx...
"Thorsten Ottosen" <nesotto@cs.aau.dk> writes:
"Thorsten Ottosen" <nesotto@cs.aau.dk> wrote in message news:de2lb2$d6u$1@sea.gmane.org...
"Christopher Kohlhoff" <chris@kohlhoff.com> wrote in message
5. can't socket.recv() use something more highlevel than void* and size_t arguments? Why not std::vector<char> ? (The same applies to all the interface functions)
std::vector<char> (even with additional offset and length parameters) introduces significant overhead for users that do not otherwise have any reason to use std::vector. I do not think the additional safety provided is worth the cost of this overhead.
doesn't the overhead depend on the amount of data sent?
Furthermore, if you do want array arguments, then you might just say
template< class T, std:size_t N > void receive( T (&array)[N] ) { /* call void ptr version here */ }
or perhaps just
template< std::size_t N > void receive( char (&array)[N] ) { ... }
Requiring that the size be known at compile-time, and that an actual static-sized array be used in order to call the receive function, is definitely not a good idea.
Then why do all the examples work on statically sized arrays? If we're not working on statically sized arrays, then why not use vector? I'm not convinced about any overhead noticable associated wirh vector. Besides, both versions could be provided. -Thorsten

"Thorsten Ottosen" <nesotto@cs.aau.dk> writes:
Then why do all the examples work on statically sized arrays?
If we're not working on statically sized arrays, then why not use vector?
I'm not convinced about any overhead noticable associated wirh vector.
Besides, both versions could be provided.
Why not just provide a standard iterator interface so that you can write to anything you like? -- Dave Abrahams Boost Consulting www.boost-consulting.com

David Abrahams <dave@boost-consulting.com> writes:
"Thorsten Ottosen" <nesotto@cs.aau.dk> writes:
Then why do all the examples work on statically sized arrays?
If we're not working on statically sized arrays, then why not use vector?
I'm not convinced about any overhead noticable associated wirh vector.
Besides, both versions could be provided.
Why not just provide a standard iterator interface so that you can write to anything you like?
All operating systems with which I am familiar only support reading and writing from memory ranges. Thus, if the iterators are actually pointers, this can be implemented efficiently. If they are not pointers, though, a temporary buffer will have to be allocated, the actual read performed using this temporary buffer, and then the data copied to the output iterator provided by the user. Because of this inefficiency, I doubt anyone would actually use the iterator interface with non-pointers. -- Jeremy Maitin-Shepard

Jeremy Maitin-Shepard <jbms@cmu.edu> writes:
All operating systems with which I am familiar only support reading and writing from memory ranges. Thus, if the iterators are actually pointers, this can be implemented efficiently. If they are not pointers, though, a temporary buffer will have to be allocated, the actual read performed using this temporary buffer, and then the data copied to the output iterator provided by the user. Because of this inefficiency, I doubt anyone would actually use the iterator interface with non-pointers.
In that case I'm strongly in favor of a low-level interface using pointers. -- Dave Abrahams Boost Consulting www.boost-consulting.com

"Thorsten Ottosen" <nesotto@cs.aau.dk> writes:
"Jeremy Maitin-Shepard" <jbms@cmu.edu> wrote in message news:87iry3c9cn.fsf@jbms.ath.cx... [snip]
std::vector<char> (even with additional offset and length parameters) introduces significant overhead for users that do not otherwise have any reason to use std::vector. I do not think the additional safety provided is worth the cost of this overhead.
doesn't the overhead depend on the amount of data sent?
I suppose that is true. If a vector must be passed to the receive function, then programs using this library will either have to use an std::vector for any buffers used for receive, which is annoying for users that would prefer to use a different data structure for a buffer, and has the additional overhead of a dynamically allocated buffer and the additional storage std::vector requires for the size, capacity, etc. Alternatively, the user could create an std::vector just for each receive call, then copy the data out after it completes. In either case, though, although the additional cost may be rather small compared to the cost of network I/O, it is an additional cost which provides only some additional safety, while introducing a more complex and less generic interface. The ideal interface would be an output iterator and a size, but this cannot be used efficiently; an arbitrary memory range seems like the next most generic interface, and this is also precisely the interface supported by operating systems. [snip]
Requiring that the size be known at compile-time, and that an actual static-sized array be used in order to call the receive function, is definitely not a good idea.
Then why do all the examples work on statically sized arrays?
Well, there aren't many examples, and I didn't write them in any case ;) I'll admit it is a common network I/O paradigm to read into (large) fixed-size buffers, but there are certainly other usage patterns (such as reading a fixed-size ``packet'' length specification, and then after ensuring that the buffer is large enough, reading the entire ``packet'' of the specified length with a single read/recv call. Furthermore, even a fixed-size buffer that is used might have its size specified at runtime.
If we're not working on statically sized arrays, then why not use vector?
There are many possible reasons, but the point is, there is no reason to force the user to use vector, when doing so provides no real benefit.
I'm not convinced about any overhead noticable associated wirh vector.
Besides, both versions could be provided.
Well, I think the critical version to provide would be the memory range version (which already exists). The other two versions could be provided, but the std::vector version, which would be implemented as a very simple wrapper around the memory range version, would only be slightly more convenient than using the memory range version directly, and the static array version would probably not be used much at all, because it is often useful to read into only a portion of an array, rather than always reading into the entire array. As a side note, perhaps the names read and write should be used rather than send and receive/recv if the library will at some point support I/O on non-sockets, such as pipes and fifos. -- Jeremy Maitin-Shepard

"Jeremy Maitin-Shepard" <jbms@cmu.edu> wrote in message news:87acjedijx.fsf@jbms.ath.cx...
"Thorsten Ottosen" <nesotto@cs.aau.dk> writes:
reason to use std::vector. I do not think the additional safety provided is worth the cost of this overhead.
doesn't the overhead depend on the amount of data sent?
I suppose that is true. If a vector must be passed to the receive function, then programs using this library will either have to use an std::vector for any buffers used for receive, which is annoying for users that would prefer to use a different data structure for a buffer, and has the additional overhead of a dynamically allocated buffer and the additional storage std::vecetor requires for the size, capacity, etc.
the space required by vector is 3 words. that is not much for many purposes.
some additional safety, while introducing a more complex and less generic interface.
how is passing 1 agument instead of two going to be more complex? is it because you can't get the size wrong? :-)
Requiring that the size be known at compile-time, and that an actual static-sized array be used in order to call the receive function, is definitely not a good idea.
Then why do all the examples work on statically sized arrays?
Well, there aren't many examples, and I didn't write them in any case ;) I'll admit it is a common network I/O paradigm to read into (large) fixed-size buffers, but there are certainly other usage patterns (such as reading a fixed-size ``packet'' length specification, and then after ensuring that the buffer is large enough, reading the entire ``packet'' of the specified length with a single read/recv call. Furthermore, even a fixed-size buffer that is used might have its size specified at runtime.
right. so by passing char (&)[N] you can check that the size argument is not too big. with void* you're just lost. -Thorsten

"Thorsten Ottosen" <nesotto@cs.aau.dk> writes:
"Jeremy Maitin-Shepard" <jbms@cmu.edu> wrote in message news:87acjedijx.fsf@jbms.ath.cx...
"Thorsten Ottosen" <nesotto@cs.aau.dk> writes:
reason to use std::vector. I do not think the additional safety provided is worth the cost of this overhead.
doesn't the overhead depend on the amount of data sent?
I suppose that is true. If a vector must be passed to the receive function, then programs using this library will either have to use an std::vector for any buffers used for receive, which is annoying for users that would prefer to use a different data structure for a buffer, and has the additional overhead of a dynamically allocated buffer and the additional storage std::vecetor requires for the size, capacity, etc.
the space required by vector is 3 words. that is not much for many purposes.
Well, I agree it is not a large amount of overhead given certain usage patterns. But note that requiring either a vector or a static array prevents reading directly into a memory-mapped region. It is also not nearly as convenient to directly read a structure or non-char type without the memory-range interface.
some additional safety, while introducing a more complex and less generic interface.
how is passing 1 agument instead of two going to be more complex? is it because you can't get the size wrong? :-)
Well, more complex in the sense that the operation itself just needs a memory range, but with this interface you can't just pass it a memory range, but rather must pass it something that consists of more than just a memory range. I suppose complex is not a great term to use. [snip]
right. so by passing char (&)[N] you can check that the size argument is not too big. with void* you're just lost.
Well, I admit this safety is rather handy, but I doubt this interface would be used much, since it is not very common to pass arrays around as a reference to static-sized array, so even if the user has a static-sized array, it might not be of the appropriate type at the point where receive/read is called. -- Jeremy Maitin-Shepard

On Aug 19, 2005, at 1:58 AM, Jeremy Maitin-Shepard wrote:
"Thorsten Ottosen" <nesotto@cs.aau.dk> writes:
"Jeremy Maitin-Shepard" <jbms@cmu.edu> wrote in message news:87acjedijx.fsf@jbms.ath.cx...
"Thorsten Ottosen" <nesotto@cs.aau.dk> writes:
reason to use std::vector. I do not think the additional safety provided is worth the cost of this overhead.
doesn't the overhead depend on the amount of data sent?
I suppose that is true. If a vector must be passed to the receive function, then programs using this library will either have to use an std::vector for any buffers used for receive, which is annoying for users that would prefer to use a different data structure for a buffer, and has the additional overhead of a dynamically allocated buffer and the additional storage std::vecetor requires for the size, capacity, etc.
the space required by vector is 3 words. that is not much for many purposes.
Well, I agree it is not a large amount of overhead given certain usage patterns. But note that requiring either a vector or a static array prevents reading directly into a memory-mapped region. It is also not nearly as convenient to directly read a structure or non-char type without the memory-range interface.
I've done a lot of work with NIO in the Java world, and what we use there is a little collection of 'Buffer' objects. The main advantage is that the buffers are more network oriented and setup for common tasks that you do in protocol handling. For example, it is possible to set the byte order for a buffer. You can simply then push ints on it. Or you could get the first 10 bytes in a new buffer and then 'compress' the original buffer. That way you can have sort of a ring buffer where you can take stuff of the beginning when you have received a full packet. Always when I use asio, I end up implementing something similar in my code It would be nice if it were part of the standard asio library. http://en.wikipedia.org/wiki/Java.nio#NIO_buffers (Not saying that we should copy this, I'm just saying that it is good functionality to have in a frmework that does network io) S.

Hi Stefan, --- Stefan Arentz <stefan.arentz@norad.org> wrote:
Or you could get the first 10 bytes in a new buffer and then 'compress' the original buffer. That way you can have sort of a ring buffer where you can take stuff of the beginning when you have received a full packet.
asio has the fixed_buffer template which does a similar sort of thing. It is used to implement the buffered_* templates that add buffering to sockets while retaining the interface.
Always when I use asio, I end up implementing something similar in my
code It would be nice if it were part of the standard asio library.
http://en.wikipedia.org/wiki/Java.nio#NIO_buffers
(Not saying that we should copy this, I'm just saying that it is good
functionality to have in a frmework that does network io)
Would you be able to share the C++ interface you have in mind for this? Cheers, Chris

Stefan Arentz <stefan.arentz@norad.org> writes: [snip]
I've done a lot of work with NIO in the Java world, and what we use there is a little collection of 'Buffer' objects.
[snip] I think this sort of interface is less useful in C++ because it is possible to cast directly between pointer types in C++, e.g. between (int *) and (char *). In any case though, it seems like it can easily be provided as a separate library from asio itself, since it would seem that no integration is really needed. -- Jeremy Maitin-Shepard

--- Jeremy Maitin-Shepard <jbms@cmu.edu> wrote:
As a side note, perhaps the names read and write should be used rather than send and receive/recv if the library will at some point support I/O on non-sockets, such as pipes and fifos.
Yes, I agree that read and write are better, because of the "portability" of the names to non-sockets that it gives. I've wanted to make that change for some time, but the thing that has been preventing me is what to call the datagram functions sendto and recvfrom, as writeto and readfrom don't seem quite right. Any suggestions? Cheers, Chris

Christopher Kohlhoff <chris@kohlhoff.com> writes:
--- Jeremy Maitin-Shepard <jbms@cmu.edu> wrote:
As a side note, perhaps the names read and write should be used rather than send and receive/recv if the library will at some point support I/O on non-sockets, such as pipes and fifos.
Yes, I agree that read and write are better, because of the "portability" of the names to non-sockets that it gives. I've wanted to make that change for some time, but the thing that has been preventing me is what to call the datagram functions sendto and recvfrom, as writeto and readfrom don't seem quite right. Any suggestions?
Well, one possibility is to just use the names `read' and `write'. If necessary, these functions can be distinguished from the connected-socket versions by the extra parameter. Alternatively, you could use the names `unconnected_read' and `unconnected_write'. -- Jeremy Maitin-Shepard

--- Jeremy Maitin-Shepard <jbms@cmu.edu> wrote:
Well, one possibility is to just use the names `read' and `write'. If necessary, these functions can be distinguished from the connected-socket versions by the extra parameter. Alternatively, you could use the names `unconnected_read' and `unconnected_write'.
After pondering this for a while, I think what i'm going to do is: - Rename the Sync_Recv_Stream concept to Sync_Read_Stream, and change it's recv() to be read(). - Rename the Sync_Send_Stream concept to Sync_Write_Stream, and change it's send() to be write(). - Rename the Async_Recv_Stream concept to Async_Read_Stream, and change it's async_recv() to be async_read(). - Rename the Async_Send_Stream concept to Async_Write_Stream, and change it's async_send() to by async_write(). - The free functions asio::send*, asio::async_send*, asio::recv* and asio::async_recv* will be renamed to use write and read. - Add new send and receive functions to the stream_socket which also allow "flags" to be passed (something missing at the moment). - Rename dgram_socket's functions sendto to send_to, async_sendto to async_send_to, recvfrom to receive_from, and async_recvfrom to async_receive_from. These functions will also be changed to allow the flags to be passed. - Rename dgram_socket to datagram_socket. These concepts and the stream_socket class will then mirror the way sockets and other resources can be used on UNIX systems (where you can call read() or write() on a socket just as with a file, or recv() and send() if you need to pass flags). Cheers, Chris

"Thorsten Ottosen" <nesotto@cs.aau.dk> wrote in message news:de2lb2$d6u$1@sea.gmane.org...
"Christopher Kohlhoff" <chris@kohlhoff.com> wrote in message news:20050818142216.76498.qmail@web32610.mail.mud.yahoo.com...
As discussed in the "surveying async network io libraries" thread, I'd like to turn asio (asio.sourceforge.net) into a proposal for a boost networking library.
Sound great :-)
I skimmed the docs and your tutorial seems to be very nice so far. (the links are good)
I havn't followed the discussion, so this is just my random input.
1. I hate cryptic short names like
dgram_socket. recv
why not write datagram and receive?
I second Thorsten's comment. Remember that English is a second language for many Boost users, and cryptic abreviations may be particularly troublesome to them. Furthermore, over time the library will be used by many programmers who are not familiar with other networking libraries. Thus clear names are a great asset. --Beman

Hi Thorsten, --- Thorsten Ottosen <nesotto@cs.aau.dk> wrote:
1. I hate cryptic short names like
dgram_socket. recv
why not write datagram and receive? (the other short names seems ok and quite standard)
Ok, fair enough :)
Looking at the Daytime1 example, I wounder why
2. asio::ipv4::host host; host_resolver.get_host_by_name(host, argv[1]);
is not written
asio::ipv4::host host = host_resolver.host_by_name(argv[1]);
Basically for consistency with the rest of the library, in particular: - Consistency with a future asynchronous version of the same function. As a rule, in async versions of the functions complex structures or buffers need to be passed as arguments, andthe only difference between the sync and async functions is the callback handler, i.e.: void operation(parameters); template <typename Handler> void async_operation(parameters, Handler handler); - Consistency with other functions that take a complex structure argument (where those functions might need to be member templates and deduce the argument type, e.g. socket.get_local_endpoint).
3. why is everything public in a host?
Only because the host represented data without behaviour, but if a class with member functions is preferred I can do it that way.
4. why does both the stream_socket and the socket_connector need to know about the demuxer? Can't the socket_connector ask the stream_socket for its demuxer (I want to simplify usage if possible)
This may be moot if asio is changed to use connect/async_connect functions on the socket itself. What do you think of that change? However I originally had it this way so that you can have a pool of demuxers (e.g. one demuxer per CPU) and "load balance" your objects across them. So you might have one demuxer where all connections are performed, but once the connection is established the socket is run on a different demuxer.
5. can't socket.recv() use something more highlevel than void* and size_t arguments? Why not std::vector<char> ? (The same applies to all the interface functions)
As others have commented, vector<char> can introduce a performance hit due to default construction of the elements. Also, some applications need to send data structures directly, to avoid additional copies. So I'd prefer to keep the operations on the socket class itself using void*. However, IMHO the place for supporting high level types is in non-member functions like asio::send and asio::recv. E.g. template <typename SyncStream, typename InputIterator, typename Handler> size_t send_range( SyncStream& s, InputIterator begin, InputIterator end, size_t* total_bytes_sent = 0); template <typename AsyncStream, typename InputIterator, typename Handler> void async_send_range( AsyncStream& s, InputIterator begin, InputIterator end, Handler handler); I believe that it's in these sorts of functions that asio offers the most extensibility. Would it be useful to add functions like this to the library itself, and what form should they take? Cheers, Chris

"Christopher Kohlhoff" <chris@kohlhoff.com> wrote in message news:20050818224433.68637.qmail@web32607.mail.mud.yahoo.com...
3. why is everything public in a host?
Only because the host represented data without behaviour, but if a class with member functions is preferred I can do it that way.
it is not much that the data is without behavior...is the data without invariants...do all posssible configurations of those data rmain legal values for the program? (I doubt they do)
4. why does both the stream_socket and the socket_connector need to know about the demuxer? Can't the socket_connector ask the stream_socket for its demuxer (I want to simplify usage if possible)
This may be moot if asio is changed to use connect/async_connect functions on the socket itself. What do you think of that change?
I'm not quailfied to comment on that.
As others have commented, vector<char> can introduce a performance hit due to default construction of the elements. Also, some applications need to send data structures directly, to avoid additional copies. So I'd prefer to keep the operations on the socket class itself using void*.
please consider the other mails too. there are too many drawback in void* IMO. I have seen/used code where a statically allocated data was far slower than putting it on the heap. This wsa not stack-allocated, but static data. So I'm a bit concerned about performance claims. With a vector it should't be difficult to pull the construction out-side the loop. From then on it would have a fixd size.
However, IMHO the place for supporting high level types is in non-member functions like asio::send and asio::recv. E.g.
template <typename SyncStream, typename InputIterator, typename Handler> size_t send_range( SyncStream& s, InputIterator begin, InputIterator end, size_t* total_bytes_sent = 0);
template <typename AsyncStream, typename InputIterator, typename Handler> void async_send_range( AsyncStream& s, InputIterator begin, InputIterator end, Handler handler);
I believe that it's in these sorts of functions that asio offers the most extensibility. Would it be useful to add functions like this to the library itself, and what form should they take?
If the only performance sensible iterator types are T*, I would't mind simply template< unsigned N > void receive( char (&)[N] ); template< unsigned N > void receive( char (&)[N], unsigned size ); void recieve( vector<char>& ); void recieve( vector<char>&, unsigned size ); or something. I have a hard time imagining any overhead imposed by vector<char> compared to a heap-allocated char*. br Thorsten

Hi Thorsten, --- Thorsten Ottosen <nesotto@cs.aau.dk> wrote:
please consider the other mails too. there are too many drawback in void* IMO.
I have seen/used code where a statically allocated data was far slower than putting it on the heap. This wsa not stack-allocated, but static data. So I'm a bit concerned about performance claims.
The performance problems of requiring vector<char> or char[N] exist on several levels: - For vector<char>, there is the initialisation of the chars to 0 on construction or when you do a resize. Note that this is proportional to the size of the vector, not necessarily to the amount of data transferred. I have seen this have a noticable cost in a CPU-bound server handling thousands of connections. - Requiring a copy from a native data structure into vector<char> or char[N]. If I have an array of a doubles say, I should be able to send it as-is to a peer that has identical architecture and compiler. Avoiding unnecessary data copying is a vital part of implementing high performance protocols. I also believe that the argument that the cost is not significant compared to network I/O does not hold when you are developing an application using asynchronous I/O, since the time spent sending/receiving no longer blocks the application.
If the only performance sensible iterator types are T*, I would't mind simply
template< unsigned N > void receive( char (&)[N] );
template< unsigned N > void receive( char (&)[N], unsigned size );
void recieve( vector<char>& );
void recieve( vector<char>&, unsigned size );
or something.
I just realised that you cannot have such functions on the socket class due to the possibility of a short send or receive. Since you can send or receive fewer bytes than requested, how would you implement something like asio::send_n or asio::recv_n on top of the above functions? Again, I believe that adding safety is best done in layers, in accordance with the don't pay for what you don't need principle: asio::socket::send/recv etc taking void* + size_t. These functions can result in a short send or receive. ^ | asio::send_n/recv_n etc taking void* + size_t. These functions attempt to transfer the all of the requested data. ^ | asio::send_?/recv_? etc. New functions that take safe types like vector<char> and char[N]. Cheers, Chris

Christopher Kohlhoff wrote: [...]
The performance problems of requiring vector<char> or char[N] exist on several levels:
- For vector<char>, there is the initialisation of the chars to 0 on [...] - Requiring a copy from a native data structure into vector<char> or [...] Avoiding unnecessary data copying is a vital part of implementing high performance protocols.
I also believe that the argument that the cost is not significant compared to network I/O does not hold when you are developing an application using asynchronous I/O, since the time spent sending/receiving no longer blocks the application. [...] Again, I believe that adding safety is best done in layers, in accordance with the don't pay for what you don't need principle:
asio::socket::send/recv etc taking void* + size_t. These functions can result in a short send or receive. ^ | asio::send_n/recv_n etc taking void* + size_t. These functions attempt to transfer the all of the requested data.
I've been skimming this conversation so far... But I thought I'd chime in with some encouragement :-) I'm *very* glad to see that you are not falling into the "it's IO so it doesn't need to fast" trap. The particular application I'm going to use asio in involves writing a low level, as in on top of UDP, protocol and software router. So I can't stress the important of having an interface that adds minimal abstraction as I'll be doing everything else. It is a rather common practice to design client server applications exclusive for high speed LAN based operation. -- -- Grafik - Don't Assume Anything -- Redshift Software, Inc. - http://redshift-software.com -- rrivera/acm.org - grafik/redshift-software.com -- 102708583/icq - grafikrobot/aim - Grafik/jabber.org

From: Christopher Kohlhoff <chris@kohlhoff.com>
The performance problems of requiring vector<char> or char[N] exist on several levels:
- For vector<char>, there is the initialisation of the chars to 0 on construction or when you do a resize. Note that this is proportional to the size of the vector, not necessarily to the amount of data transferred. I have seen this have a noticable cost in a CPU-bound server handling thousands of connections.
Don't construct a vector of a given size or use resize(), then. Rely on reserve() instead.
- Requiring a copy from a native data structure into vector<char> or char[N]. If I have an array of a doubles say, I should be able to send it as-is to a peer that has identical architecture and compiler. Avoiding unnecessary data copying is a vital part of implementing high performance protocols.
Agreed. OTOH, using swap(), *if* a user used a vector<double> instead of the array you mention, then vector won't add overhead.
I believe that adding safety is best done in layers, in accordance with the don't pay for what you don't need principle:
asio::socket::send/recv etc taking void* + size_t. These functions can result in a short send or receive. ^ | asio::send_n/recv_n etc taking void* + size_t. These functions attempt to transfer the all of the requested data. ^ | asio::send_?/recv_? etc. New functions that take safe types like vector<char> and char[N].
std::vector takes an allocator template and constructor argument. A suitable allocator type could be constructed to "allocate" from a user-supplied buffer and fail when that buffer is exhausted. The problem here is that the Standard allows all instances of the same type to be considered equivalent, so this won't work. Instead, how about a std::vector-like class that takes a user-defined, fixed-size block of memory? Then, instead of using an allocator, it uses its own memory management class that doles out elements from the fixed, user-supplied memory. Such a class could even avoid the default construction worry altogether by eliminating resize() and making the constructor that takes a number simply reserve that much space. (Deviating that much from std::vector means you wouldn't want to make it too similar, or at least you'd want the name to be quite dissimilar.) Using a user-supplied block of memory means that the memory can come from a stack allocation, static memory, the free store, a memory mapped file, etc. That class cannot be copyable, but it could be movable and swappable. The advantage is that all of the asio functions can be written in terms of that, getting buffer overrun protection for free, and there's no (or extremely little) added overhead. -- Rob Stewart stewart@sig.com Software Engineer http://www.sig.com Susquehanna International Group, LLP using std::disclaimer;

On Fri, 19 Aug 2005 09:08:59 -0400, Rob Stewart <stewart@sig.com> wrote:
The performance problems of requiring vector<char> or char[N] exist on several levels:
- For vector<char>, there is the initialisation of the chars to 0 on construction or when you do a resize. Note that this is proportional to the size of the vector, not necessarily to the amount of data transferred. I have seen this have a noticable cost in a CPU-bound server handling thousands of connections.
Don't construct a vector of a given size or use resize(), then. Rely on reserve() instead.
Then size() will return the wrong value, and relying on capacity() is not a good idea. If you're thinking about pushing data onto the vector as it's read, that's also bad, because then you'd have to read into a temporary buffer first and copy it to the vector after. (Or do multiple resizes.) I think it's a very bad idea to require vector<char> or a static array. Christopher does a good job of explaining the drawbacks, and I agree with him. I also do high performance asynchronous networking in my server and client applications, and a library requiring vector<char> or a static array would be completely useless to me. Most of the time I don't have the data I want to send in a vector or in a static array, and most of the time the amount of data is too big to send or receive a whole buffer at a time.
- Requiring a copy from a native data structure into vector<char> or char[N]. If I have an array of a doubles say, I should be able to send it as-is to a peer that has identical architecture and compiler. Avoiding unnecessary data copying is a vital part of implementing high performance protocols.
Agreed. OTOH, using swap(), *if* a user used a vector<double> instead of the array you mention, then vector won't add overhead.
Why would a swap be necessary?
I believe that adding safety is best done in layers, in accordance with the don't pay for what you don't need principle:
asio::socket::send/recv etc taking void* + size_t. These functions can result in a short send or receive. ^ | asio::send_n/recv_n etc taking void* + size_t. These functions attempt to transfer the all of the requested data. ^ | asio::send_?/recv_? etc. New functions that take safe types like vector<char> and char[N].
std::vector takes an allocator template and constructor argument. A suitable allocator type could be constructed to "allocate" from a user-supplied buffer and fail when that buffer is exhausted. The problem here is that the Standard allows all instances of the same type to be considered equivalent, so this won't work.
Instead, how about a std::vector-like class that takes a user-defined, fixed-size block of memory?
No, that would still require a copy if the data isn't already in such a buffer. void * (or unsigned char *, or char *, or whatever) HAS to be there, otherwise the library is useless. Such a class could be an option (and I would like to see it as an option), but not a requirement. In my applications I can't afford to copy data from my internal buffers to whatever the networking library requires. I also can't put the data in such buffers to begin with. -- Be seeing you.

From: Thore Karlsen <sid@6581.com>
On Fri, 19 Aug 2005 09:08:59 -0400, Rob Stewart <stewart@sig.com> wrote:
The performance problems of requiring vector<char> or char[N] exist on several levels:
- For vector<char>, there is the initialisation of the chars to 0 on construction or when you do a resize. Note that this is proportional to the size of the vector, not necessarily to the amount of data transferred. I have seen this have a noticable cost in a CPU-bound server handling thousands of connections.
Don't construct a vector of a given size or use resize(), then. Rely on reserve() instead.
Then size() will return the wrong value, and relying on capacity() is
No, size() will correctly indicate that there are no objects in the vector.
not a good idea. If you're thinking about pushing data onto the vector
Why is relying on capacity() a bad idea?
as it's read, that's also bad, because then you'd have to read into a temporary buffer first and copy it to the vector after. (Or do multiple resizes.)
At some point, the vector has to have sufficient elements in it so that you can copy new values onto them. Otherwise, the vector won't know it has real elements. Yes, that means you do pay for the initialization at some point and, yes, that isn't desirable.
I think it's a very bad idea to require vector<char> or a static array. Christopher does a good job of explaining the drawbacks, and I agree with him. I also do high performance asynchronous networking in my server and client applications, and a library requiring vector<char> or a static array would be completely useless to me. Most of the time I don't have the data I want to send in a vector or in a static array, and most of the time the amount of data is too big to send or receive a whole buffer at a time.
I understand.
- Requiring a copy from a native data structure into vector<char> or char[N]. If I have an array of a doubles say, I should be able to send it as-is to a peer that has identical architecture and compiler. Avoiding unnecessary data copying is a vital part of implementing high performance protocols.
Agreed. OTOH, using swap(), *if* a user used a vector<double> instead of the array you mention, then vector won't add overhead.
Why would a swap be necessary?
If asio used a vector, the user could swap it's contents into his own vector. (That's not the current interface, but then neither is vector in the current interface. It was just informational for the discussion.)
Instead, how about a std::vector-like class that takes a user-defined, fixed-size block of memory?
No, that would still require a copy if the data isn't already in such a buffer. void * (or unsigned char *, or char *, or whatever) HAS to be there, otherwise the library is useless. Such a class could be an option (and I would like to see it as an option), but not a requirement.
I don't think you understand what I'm suggesting. Notice that I used the word "takes." Furthermore, I think you snipped the details about how that class would use the buffer handed to it. The data has to be in some memory somewhere. The class I'm suggesting can be told to use that memory and can be told how much memory is available. Then, whenever asio needs to read data into the caller's buffer, or write data from the caller's buffer, it is taken from that preexisting buffer.
In my applications I can't afford to copy data from my internal buffers to whatever the networking library requires. I also can't put the data in such buffers to begin with.
So wrap your internal buffers with the class I'm suggesting. No copy is needed. The class simply provides a standard interface, complete with push_back(), iterators, random access, etc., and prevents buffer overruns. -- Rob Stewart stewart@sig.com Software Engineer http://www.sig.com Susquehanna International Group, LLP using std::disclaimer;

On Fri, 19 Aug 2005 10:47:11 -0400, Rob Stewart <stewart@sig.com> wrote:
- For vector<char>, there is the initialisation of the chars to 0 on construction or when you do a resize. Note that this is proportional to the size of the vector, not necessarily to the amount of data transferred. I have seen this have a noticable cost in a CPU-bound server handling thousands of connections.
Don't construct a vector of a given size or use resize(), then. Rely on reserve() instead.
Then size() will return the wrong value, and relying on capacity() is
No, size() will correctly indicate that there are no objects in the vector.
not a good idea. If you're thinking about pushing data onto the vector
Why is relying on capacity() a bad idea?
I thought you might have meant that you reserve() the space you need, and write into the allocated space, thus bypassing the default element initialization you get with resize(). I'm glad you're not suggesting that. :)
as it's read, that's also bad, because then you'd have to read into a temporary buffer first and copy it to the vector after. (Or do multiple resizes.)
At some point, the vector has to have sufficient elements in it so that you can copy new values onto them. Otherwise, the vector won't know it has real elements. Yes, that means you do pay for the initialization at some point and, yes, that isn't desirable.
OK, then we understand each other.
- Requiring a copy from a native data structure into vector<char> or char[N]. If I have an array of a doubles say, I should be able to send it as-is to a peer that has identical architecture and compiler. Avoiding unnecessary data copying is a vital part of implementing high performance protocols.
Agreed. OTOH, using swap(), *if* a user used a vector<double> instead of the array you mention, then vector won't add overhead.
Why would a swap be necessary?
If asio used a vector, the user could swap it's contents into his own vector. (That's not the current interface, but then neither is vector in the current interface. It was just informational for the discussion.)
Yes, true, but what Christopher was talking about was the need to copy from a native data structure into the vector. In the case where the user does use vectors, I would assume that the asio call would just read the elements directly into the vector the user supplies to the read call. (Thus no need for swapping.)
Instead, how about a std::vector-like class that takes a user-defined, fixed-size block of memory?
No, that would still require a copy if the data isn't already in such a buffer. void * (or unsigned char *, or char *, or whatever) HAS to be there, otherwise the library is useless. Such a class could be an option (and I would like to see it as an option), but not a requirement.
I don't think you understand what I'm suggesting. Notice that I used the word "takes." Furthermore, I think you snipped the details about how that class would use the buffer handed to it.
The data has to be in some memory somewhere. The class I'm suggesting can be told to use that memory and can be told how much memory is available. Then, whenever asio needs to read data into the caller's buffer, or write data from the caller's buffer, it is taken from that preexisting buffer.
Ah, yes, I did misunderstand you. That would be perfect, if you could have the option of either wrapping already allocated memory or having the class manage the memory directly. That's how I do it in my higher-level network classes. I still think a raw void * read/write should be there, but I also think this wrapper class should be an option.
In my applications I can't afford to copy data from my internal buffers to whatever the networking library requires. I also can't put the data in such buffers to begin with.
So wrap your internal buffers with the class I'm suggesting. No copy is needed. The class simply provides a standard interface, complete with push_back(), iterators, random access, etc., and prevents buffer overruns.
Yes, I agree. This is the approach I've taken, and it works very well. -- Be seeing you.

Hi Rob, --- Rob Stewart <stewart@sig.com> wrote:
From: Christopher Kohlhoff <chris@kohlhoff.com> Don't construct a vector of a given size or use resize(), then. Rely on reserve() instead.
It's my understanding that you cannot access the address &vec[N] (or &vec[0] + N) unless 0 <= N < size(), and reserve() doesn't change the size of the vector.
OTOH, using swap(), *if* a user used a vector<double> instead of the array you mention, then vector won't add overhead.
But Thorsten was proposing only the use of vector<char>, so you would still need to convert from vector<double> to vector<char>.
Instead, how about a std::vector-like class that takes a user-defined, fixed-size block of memory? Then, instead of using an allocator, it uses its own memory management class that doles out elements from the fixed, user-supplied memory. Such a class could even avoid the default construction worry altogether by eliminating resize() and making the constructor that takes a number simply reserve that much space. (Deviating that much from std::vector means you wouldn't want to make it too similar, or at least you'd want the name to be quite dissimilar.) Using a user-supplied block of memory means that the memory can come from a stack allocation, static memory, the free store, a memory mapped file, etc. That class cannot be copyable, but it could be movable and swappable.
I'd be happy to look at a proposed interface for it, if you'd like to sketch it out. However it would have to provide a straightforward way of sending an existing application data structure (and it may be hard to beat void* for that) that doesn't add time or space overhead in an optimised build. Basically I'm still of the opinion that the operations on the socket class should deal in void* to allow for users that need to be close to the metal. But since safe, efficient buffer management seems to be a common need/desire, perhaps there is a place for that in asio with a buffer class (or should it be a Buffer concept?) and non-member send/recv functions. Cheers, Chris

"Christopher Kohlhoff" <chris@kohlhoff.com> wrote in message news:20050819143228.41797.qmail@web32604.mail.mud.yahoo.com...
Hi Rob,
--- Rob Stewart <stewart@sig.com> wrote:
OTOH, using swap(), *if* a user used a vector<double> instead of the array you mention, then vector won't add overhead.
But Thorsten was proposing only the use of vector<char>, so you would still need to convert from vector<double> to vector<char>.
I'm proposing a lot. In one mail I wrote vector<T>. I don't know what's required, if you can only send chars or not. So if you need to send arbitrary data, then just allow both T (&)[N] and vector<T>. (so no copy needed) Having these two variants don't prohibit you from providing an overload based on void* which is then used internally in the two other variants. -Thorsten

"Thorsten Ottosen" <nesotto@cs.aau.dk> writes: [snip]
I'm proposing a lot. In one mail I wrote vector<T>. I don't know what's required, if you can only send chars or not. So if you need to send arbitrary data, then just allow both T (&)[N] and vector<T>. (so no copy needed)
Aside from all the other reasons for not requiring the use of vector, which I believe are sufficient, there are also the following problems: If reading a type other than char is allowed, it is difficult to have a useful interface for the simple "recv" and "recv_at_leaast_n" versions, because the total number of bytes read might not be divisible by sizeof(T). Thus, the library would still need to tell the user the number of bytes read, rather than the number of T read, and the `offset' parameter would also need to be specified in bytes, rather than in number of T, so that these partial reads could be completed; these limitations would surely negate any benefit of allowing reading T rather than just char. (Trying to ensure that the number of bytes read is divisible by sizeof(T) is not really feasible.) Another issue is that in the case that T is a large structure, the user may only want to read a single one, in which case it would be undesirable to have to use either an array or a vector. [snip] -- Jeremy Maitin-Shepard

Christopher Kohlhoff <chris@kohlhoff.com> writes:
From: Christopher Kohlhoff <chris@kohlhoff.com> Don't construct a vector of a given size or use resize(), then. Rely on reserve() instead.
It's my understanding that you cannot access the address &vec[N] (or &vec[0] + N) unless 0 <= N < size(), and reserve() doesn't change the size of the vector.
That's correct. There's been some discussion of providing a vector ctor (and assign member) that takes a number of elements and function object that can be used to construct the elements into its raw memory. Until we have that, std::vector is probably the wrong tool for this job. -- Dave Abrahams Boost Consulting www.boost-consulting.com

"David Abrahams" <dave@boost-consulting.com> wrote in message news:uslx5zzp4.fsf@boost-consulting.com...
There's been some discussion of providing a vector ctor (and assign member) that takes a number of elements and function object that can be used to construct the elements into its raw memory. Until we have that, std::vector is probably the wrong tool for this job.
I'll consider to put this on my STL enhancements paper. br -Thorsten

On 8/18/05, Christopher Kohlhoff <chris@kohlhoff.com> wrote:
- What people think of the relative importance of the items. For example, what would be required for an initial implementation vs what could or should be left until a later revision.
Reading back through my comments (below), I don't think any of the Missing Features is a show-stopper. I'd say rationalizing interfaces for inclusion in Boost (e.g. perhaps using boost::xtime instead of asio::time, etc) and expanding the documentation and example code would be the top priority. All IMHO of course.
- Whether the proposed changes are of any value.
Yes they all have merit.
- Any other things that should be on the list.
What's Missing ==============
* Asynchronous DNS operations. I think it would be reasonable to say that any operation that can block should have an asynchronous equivalent. Should these operations be cancellable (not necessarily portably implementable anyway)?
The async DNS calls would be a nice-to-have feature, and I'd give that a Medium priority. Making the operations cancel-able would be Low.
* IPv6 support. I have no experience with IPv6, so it would be great if someone with experience could help out with the design here.
Another nice to have, but the way in which you have placed the existing IPv4 code into its own namespace leads me to believe this can be added later with little pain. For some it may be a top priority, but I'd give this a Low priority personally.
* Asynchronous socket close. In certain circumstances closing a socket can block, so it would be reasonable to expect an asynchronous close operation.
Would this be just a matter of adding an asio::async_close free function? Not sure how you implement this on various platforms, but it seems like a Low to Medium priority.
* Scatter-gather operations (sendv and recvv).
I'd give this Low priority.
* The BSD socket functions sendmsg/recvmsg (which I believe are necessary to support protocols such as SCTP).
Again, Low.
* SSL/TLS and DTLS support. This would require an external library, and how does that fit in with boost?
You can probably look at the ICU support in Boost.Regex and the Gzip/Bzip2 support in Iostreams as examples. The extra libraries enable additional functionality, but without them the libraries still function. Low to Medium priority.
* Other protocols (PF_UNIX, ...)?
Low to Medium.
* Some type of integration (or support for) a higher-level iostreams-based socket library.
It ought to be quite simple to provide this using Boost.Iostreams. Low priority.
* More examples. * Documentation on design and rationale.
These two taken together merit High priority.
What Should Be Included and What Shouldn't ==========================================
* Obviously asio::thread should not be part of a boost proposal, since that functionality is already covered by Boost.Thread.
I do very much enjoy the 100% header implementation you have now, but having to link to a library would not be a show-stopper. It does seem a bit of a shame that a number of Boost libraries that are thread-safe/aware (SharedPtr, Pool, Regex) have provided their own e.g. mutex classes instead of using the ones in Boost.Threads. This is not a value judgement, just an observation.
* The asio::time class should probably not be included, and wherever it's used replaced by Boost.Date-Time.
I think boost::xtime is the right choice there.
Naming and Structure ====================
* The use of the word "Stream" can be confusing because its existing association in C++ with iostreams. Can anyone suggest an alternative?
I personally think that "stream sockets" are (or should be) a well-known term and that to call them something else would be more confusing.
* Should stream_socket and dgram_socket be merged into a single socket class? There other types of sockets (e.g. sequenced packet) that would require additional classes if the current approach is retained. This would not be required if the BSD socket APIs were covered by a single class. Alternatively, should additional classes be added for the other socket types?
This is an interesting point that has arisen before. Do the different types of sockets warrant different classes? I'm not sure I have the Design Fu to answer this one well.
* The current separate socket_connector implementation does not correspond to an OS resource, unlike socket_acceptor. This can be problematic, e.g. it prevents a Windows implementation using ConnectEx, and doesn't allow you to set socket options before the connection is established. Therefore I propose adding connect and async_connect operations to socket classes. Should the separate socket_connector be retained?
Seems like a sensible change, and once done I don't see the value in socket_connector.
* In designing asio I considered that people may want to customise the demultiplexing strategy associated with each resource. For example the basic_stream_socket template looks like:
template <typename Service> class basic_stream_socket;
where Service is a "facet" of the demuxer that provides demultiplexing facilities for the socket. The typedef for stream_socket specifies the Service type based on the target platform.
However in practice I don't believe this level of customisation is required (to the best of my knowledge no existing asio user customises it), particularly if asio uses the "best" demultiplexing mechanism for each platform. Or another way of looking at it: developers who need this level of control might not use a general purpose networking library anyway.
It also prevents runtime-pluggable demuxing facilities, which might be useful on some platforms. For example, what if I compile my application on a Linux 2.6 machine that supports epoll, but I want to run it on a 2.4 machine that doesn't support this API? Being able to select the best mechanism at *runtime* would be very handy here. This is analagous to the Reactor implementation in ACE, which is pluggable at runtime.
An alternative approach would to have the only template parameter be an allocator (since there is often memory allocation associated with asynchronous operations). For example:
template <typename Allocator> class basic_demuxer;
template <typename Allocator> class basic_stream_socket;
where a stream_socket can only be used with a demuxer that has the same allocator type. The typedefs for demuxer and stream_socket would just use the default allocator.
This seems to make more sense to me than making the socket type dependent on the demuxing strategy. Thanks again for such a nice library! -- Caleb Epstein caleb dot epstein at gmail dot com

Caleb Epstein wrote:
Reading back through my comments (below), I don't think any of the Missing Features is a show-stopper. I'd say rationalizing interfaces for inclusion in Boost (e.g. perhaps using boost::xtime instead of asio::time, etc) and expanding the documentation and example code would be the top priority. All IMHO of course. {snip}
* The asio::time class should probably not be included, and wherever it's used replaced by Boost.Date-Time.
I think boost::xtime is the right choice there.
Am I the only one who dislikes boost::xtime? IMO it makes much more sense to replace boost::xtime with a convenient class like asio::time.

-----Original Message----- From: boost-bounces@lists.boost.org [mailto:boost-bounces@lists.boost.org] On Behalf Of Peter Petrov Sent: Thursday, August 18, 2005 2:55 PM To: boost@lists.boost.org Subject: Re: [boost] asio networking proposal
Caleb Epstein wrote:
Reading back through my comments (below), I don't think any of the Missing Features is a show-stopper. I'd say rationalizing interfaces for inclusion in Boost (e.g. perhaps using boost::xtime instead of asio::time, etc) and expanding the documentation and example code would be the top priority. All IMHO of course. {snip}
* The asio::time class should probably not be included, and wherever it's used replaced by Boost.Date-Time.
I think boost::xtime is the right choice there.
Am I the only one who dislikes boost::xtime? IMO it makes much more sense to replace boost::xtime with a convenient class like asio::time.
I don't like it either, but what's wrong with Boost.Date-Time? As the documentation for xtime says: "This is a temporary solution that will be replaced by a more robust time library once available in Boost."

Caleb Epstein wrote:
* Obviously asio::thread should not be part of a boost proposal, since that functionality is already covered by Boost.Thread.
I do very much enjoy the 100% header implementation you have now, but having to link to a library would not be a show-stopper. It does seem a bit of a shame that a number of Boost libraries that are thread-safe/aware (SharedPtr, Pool, Regex) have provided their own e.g. mutex classes instead of using the ones in Boost.Threads. This is not a value judgement, just an observation.
I guess they would not, if required part of functionality of Boost.Thread was available as a header-only implementation (ie. could be used without introducing dependency on library files) B.

Chris, how would the iostreams source/sink fit into your design and would you consider using signal/slots to handle the events. Minh Christopher Kohlhoff <chris@kohlhoff.com> wrote: As discussed in the "surveying async network io libraries" thread, I'd like to turn asio (asio.sourceforge.net) into a proposal for a boost networking library. As promised, here is a list of areas where asio could use some refinement. I'd appreciate input on: - What people think of the relative importance of the items. For example, what would be required for an initial implementation vs what could or should be left until a later revision. - Whether the proposed changes are of any value. - Any other things that should be on the list. What's Missing ============== * Asynchronous DNS operations. I think it would be reasonable to say that any operation that can block should have an asynchronous equivalent. Should these operations be cancellable (not necessarily portably implementable anyway)? * IPv6 support. I have no experience with IPv6, so it would be great if someone with experience could help out with the design here. * Asynchronous socket close. In certain circumstances closing a socket can block, so it would be reasonable to expect an asynchronous close operation. * Scatter-gather operations (sendv and recvv). * The BSD socket functions sendmsg/recvmsg (which I believe are necessary to support protocols such as SCTP). * SSL/TLS and DTLS support. This would require an external library, and how does that fit in with boost? * Other protocols (PF_UNIX, ...)? * Some type of integration (or support for) a higher-level iostreams-based socket library. * More examples. * Documentation on design and rationale. What Should Be Included and What Shouldn't ========================================== * Obviously asio::thread should not be part of a boost proposal, since that functionality is already covered by Boost.Thread. * The asio::time class should probably not be included, and wherever it's used replaced by Boost.Date-Time. Naming and Structure ==================== * The use of the word "Stream" can be confusing because its existing association in C++ with iostreams. Can anyone suggest an alternative? * Should stream_socket and dgram_socket be merged into a single socket class? There other types of sockets (e.g. sequenced packet) that would require additional classes if the current approach is retained. This would not be required if the BSD socket APIs were covered by a single class. Alternatively, should additional classes be added for the other socket types? * The current separate socket_connector implementation does not correspond to an OS resource, unlike socket_acceptor. This can be problematic, e.g. it prevents a Windows implementation using ConnectEx, and doesn't allow you to set socket options before the connection is established. Therefore I propose adding connect and async_connect operations to socket classes. Should the separate socket_connector be retained? * In designing asio I considered that people may want to customise the demultiplexing strategy associated with each resource. For example the basic_stream_socket template looks like: template class basic_stream_socket; where Service is a "facet" of the demuxer that provides demultiplexing facilities for the socket. The typedef for stream_socket specifies the Service type based on the target platform. However in practice I don't believe this level of customisation is required (to the best of my knowledge no existing asio user customises it), particularly if asio uses the "best" demultiplexing mechanism for each platform. Or another way of looking at it: developers who need this level of control might not use a general purpose networking library anyway. An alternative approach would to have the only template parameter be an allocator (since there is often memory allocation associated with asynchronous operations). For example: template class basic_demuxer; template class basic_stream_socket; where a stream_socket can only be used with a demuxer that has the same allocator type. The typedefs for demuxer and stream_socket would just use the default allocator. Cheers, Chris _______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost Minh Phanivong +64 2 136 7118 (NZ) "Instead of living in the shadows of yesterday, walk in the light of today and the hope of tomorrow. " Â–Ă©Â’Â·Â–Â²Â‘Â½ Send instant messages to your online friends http://uk.messenger.yahoo.com

Mr Minh wrote:
Chris,
how would the iostreams source/sink fit into your design and would you consider using signal/slots to handle the events.
Minh
Christopher Kohlhoff <chris@kohlhoff.com> wrote: As discussed in the "surveying async network io libraries" thread, I'd
[cut complete reply quote] FYI... http://www.boost.org/more/discussion_policy.htm#effective Don't Overquote Please prune extraneous quoted text from replies so that only the relevant parts are included. [...] -- -- Grafik - Don't Assume Anything -- Redshift Software, Inc. - http://redshift-software.com -- rrivera/acm.org - grafik/redshift-software.com -- 102708583/icq - grafikrobot/aim - Grafik/jabber.org

(Sorry for quoting wrong, but I just subscribed and did not get Christopher's original message) Christopher Kohlhoff <chris@kohlhoff.com> wrote:
As discussed in the "surveying async network io libraries" thread, I'd like to turn asio (asio.sourceforge.net) into a proposal for a boost networking library. As promised, here is a list of areas where asio could use some refinement.
I'd appreciate input on:
- What people think of the relative importance of the items. For example, what would be required for an initial implementation vs what could or should be left until a later revision.
- Whether the proposed changes are of any value.
- Any other things that should be on the list.
What's Missing ==============
* Asynchronous DNS operations. I think it would be reasonable to say that any operation that can block should have an asynchronous equivalent. Should these operations be cancellable (not necessarily portably implementable anyway)?
Maybe I'm wrong, but I thought most resolvers included with the operating systems are not thread safe and synchronous. Maybe only OS X has it's own async implementation. There are of course third party libraries to do this. What is the Boost policy of linking with foreign libraries? If everything is supposed to happen under the boost umbrella, under the boost source tree then maybe it is an idea to write our own async resolver? Could be a fun spare time project (for me :-).
* IPv6 support. I have no experience with IPv6, so it would be great if someone with experience could help out with the design here.
It is mostly about abstracting addresses and endpoints. Something asio already has right?
* Asynchronous socket close. In certain circumstances closing a socket can block, so it would be reasonable to expect an asynchronous close operation.
* Scatter-gather operations (sendv and recvv).
For efficiency.
* The BSD socket functions sendmsg/recvmsg (which I believe are necessary to support protocols such as SCTP).
* SSL/TLS and DTLS support. This would require an external library, and how does that fit in with boost?
* Other protocols (PF_UNIX, ...)?
Yeah it would be really nice to be able to also hook PF_UNIX sockets in the demuxer. That would be great for local ipc.
* Some type of integration (or support for) a higher-level iostreams-based socket library.
* More examples.
I sort of have a http server example ready. Needs a little bit more love though, then i'll release it or send it to you to see if you want to include it in the distribution. S.

Hi Stefan, --- Stefan Arentz <stefan.arentz@norad.org> wrote:
Maybe I'm wrong, but I thought most resolvers included with the operating systems are not thread safe and synchronous. Maybe only OS X has it's own async implementation. There are of course third party libraries to do this.
I was thinking that an initial implementation could just use an internal thread pool to perform the operations using the existing blocking functions provided by the OS (unless the OS already provided a straightforward way to do it).
What is the Boost policy of linking with foreign libraries? If everything is supposed to happen under the boost umbrella, under the boost source tree then maybe it is an idea to write our own async resolver? Could be a fun spare time project (for me :-).
Sounds like fun ;)
* IPv6 support. I have no experience with IPv6, so it would be great if someone with experience could help out with the design here.
It is mostly about abstracting addresses and endpoints. Something asio already has right?
Yes, but it would probably be best for someone who is familiar with using IPv6 in real apps to oversee the design of the asio::ipv6::* classes.
I sort of have a http server example ready. Needs a little bit more love though, then i'll release it or send it to you to see if you want to include it in the distribution.
That would be excellent! Cheers, Chris

On 8/19/05, Christopher Kohlhoff <chris@kohlhoff.com> wrote:
Hi Stefan,
--- Stefan Arentz <stefan.arentz@norad.org> wrote:
Maybe I'm wrong, but I thought most resolvers included with the operating systems are not thread safe and synchronous. Maybe only OS X has it's own async implementation. There are of course third party libraries to do this.
I was thinking that an initial implementation could just use an internal thread pool to perform the operations using the existing blocking functions provided by the OS (unless the OS already provided a straightforward way to do it).
Recent Windows, Mac and Linux (glibc 2.2.4) provide the async functions so it's probably good judgement and less work to wrap those in the short term. The other option (better from a multiplatform standpoint) is to implement RFC 1035 in c++. Worth checking asyncresolv.sf.net Async DNS is probably the second priority after polishing the existing interfaces (considering that this library is a core library which you can use to build a web library on top of it)
I sort of have a http server example ready. Needs a little bit more love though, then i'll release it or send it to you to see if you want to include it in the distribution.
That would be excellent!
Great!

Stefan Arentz wrote:
* Asynchronous DNS operations. I think it would be reasonable to say that any operation that can block should have an asynchronous equivalent. Should these operations be cancellable (not necessarily portably implementable anyway)?
Maybe I'm wrong, but I thought most resolvers included with the operating systems are not thread safe and synchronous. Maybe only OS X has it's own async implementation. There are of course third party libraries to do this.
What is the Boost policy of linking with foreign libraries? If everything is supposed to happen under the boost umbrella, under the boost source tree then maybe it is an idea to write our own async resolver? Could be a fun spare time project (for me :-).
Last time I checked, GNU libc had asynchronous name resolution functions. I don't know if those are standard or extensions. -- Pedro LamarĂ£o Desenvolvimento Intersix Technologies S.A. SP: (55 11 3803-9300) RJ: (55 21 3852-3240) www.intersix.com.br Your Security is our Business

Stefan Arentz <stefan.arentz@norad.org> writes: [snip]
What is the Boost policy of linking with foreign libraries? If everything is supposed to happen under the boost umbrella, under the boost source tree then maybe it is an idea to write our own async resolver? Could be a fun spare time project (for me :-).
I think in practice it is not feasible to attempt to resolve hostnames using something other than the platform-provided facility. Users tend to expect and desire that name resolution to work identically in all programs, and to mimic the behavior of the platform-provided facility, it is necessary to use the same mechanisms specified in a platform-specific way by the user. For example, the user may want some names resolved used a hosts file, NIS, LDAP, or WINS, in addition to DNS via DNS servers also specified in a platform-specific manner. [snip] -- Jeremy Maitin-Shepard

Hi Minh, --- Mr Minh <m_phanivong@yahoo.com.au> wrote:
how would the iostreams source/sink fit into your design
I'm not particular familiar with the Boost.Iostreams library, but it seems that template adapters could be used to turn the Sync_Recv_Stream concept into a Source, and a Sync_Send_Stream into a Sink. There does not seem to be an equivalent for the async concepts though.
and would you consider using signal/slots to handle the events.
The current design makes the callback function object (aka Handler) type a template parameter to give maximum scope for optimisation. However there is no reason that I can see why you can't pass a signal to be the handler. For example: //------------------------------ #include <boost/signal.hpp> #include <boost/bind.hpp> #include <asio.hpp> #include <iostream> void func(int value) { std::cout << value << std::endl; } int main() { boost::signal<void()> sig; sig.connect(boost::bind(func, 1)); sig.connect(boost::bind(func, 2)); asio::demuxer d; d.post(boost::bind(boost::ref(sig))); d.run(); return 0; } //------------------------------ Cheers, Chris

using signal/slots to handle the events.
The current design makes the callback function object (aka Handler) type a template parameter to give maximum scope for optimisation. However there is no reason that I can see why you can't pass a signal to be the handler.
I recall reading in the Boost.Signal documentation that the library is not thread-safe; meaning, it will probably do weird things when several threads register/unregister a callback simultaneously, etc. Has this limitation been addressed in the more recent versions of the library? Peter

On Aug 19, 2005, at 7:07 AM, Peter Simons wrote:
I recall reading in the Boost.Signal documentation that the library is not thread-safe; meaning, it will probably do weird things when several threads register/unregister a callback simultaneously, etc.
Has this limitation been addressed in the more recent versions of the library?
No, it hasn't :( Signals has fallen way behind in its development. I've hardly even had the time to maintain it adequately, much less implement new features. Doug

Mr Minh <m_phanivong@yahoo.com.au> writes:
Chris,
how would the iostreams source/sink fit into your design and would you consider using signal/slots to handle the events.
Minh
Christopher Kohlhoff <chris@kohlhoff.com> wrote: As discussed in the "surveying async network io libraries" thread, I'd like to turn asio (asio.sourceforge.net) into a proposal for a boost
<snip> an entire long message http://www.boost.org/more/discussion_policy.htm#quoting -- Dave Abrahams Boost Consulting www.boost-consulting.com

Christopher Kohlhoff wrote: [snipped parts irrelevant to my reply]
As discussed in the "surveying async network io libraries" thread, I'd like to turn asio (asio.sourceforge.net) into a proposal for a boost networking library. As promised, here is a list of areas where asio could use some refinement.
Great!
- Any other things that should be on the list.
I experimented with the library for a few days now. Maybe I'm misusing it, but it seemed nice to have demuxer::run() thread(s) running before any (meaningful) asio operation is performed. My solution was a demuxer::run() thread pool management class (using boost::thread_group to manage the threads themselves). The class has a timer that keeps queueing itself in its handler, unless the class's client requested it to stop. The stop operation also joins the thread(s). If it's a legitimate use, a helper class to do that can be useful. If not, why?
* More examples.
* Documentation on design and rationale.
These are the two most important improvements.
* The use of the word "Stream" can be confusing because its existing association in C++ with iostreams. Can anyone suggest an alternative?
The use of "stream" in this context seems natural enough for me. I suspect most people writing networking code will agree.
* Should stream_socket and dgram_socket be merged into a single socket class? There other types of sockets (e.g. sequenced packet) that would require additional classes if the current approach is retained. This would not be required if the BSD socket APIs were covered by a single class. Alternatively, should additional classes be added for the other socket types?
My instinct tells me that they shouldn't be merged: they are very different concepts, and I wouldn't like this specific abstraction eliminated in asio.

Hi Ariel, --- Ariel Badichi <abadichi@bezeqint.net> wrote:
I experimented with the library for a few days now. Maybe I'm misusing it, but it seemed nice to have demuxer::run() thread(s) running before any (meaningful) asio operation is performed.
My solution was a demuxer::run() thread pool management class (using boost::thread_group to manage the threads themselves). The class has a timer that keeps queueing itself in its handler, unless the class's client requested it to stop. The stop operation also joins the thread(s).
There's a simpler way to do what you want. Just call demuxer.work_started() before calling demuxer.run(), and then call demuxer.work_finished() when you're ready to shut down. These functions are provided to ensure that demuxer.run() does not exit when an asynchronous operation (the "work") is being carried out by something external to the demuxer (such as a thread).
My instinct tells me that they shouldn't be merged: they are very different concepts, and I wouldn't like this specific abstraction eliminated in asio.
Fair enough. I'll leave them as separate for now. Cheers, Chris

Christopher Kohlhoff wrote:
There's a simpler way to do what you want. Just call demuxer.work_started() before calling demuxer.run(), and then call demuxer.work_finished() when you're ready to shut down.
These functions are provided to ensure that demuxer.run() does not exit when an asynchronous operation (the "work") is being carried out by something external to the demuxer (such as a thread).
I see, thanks!

<snip> These are the two most important improvements.
* The use of the word "Stream" can be confusing because its existing association in C++ with iostreams. Can anyone suggest an alternative?
The use of "stream" in this context seems natural enough for me. I suspect most people writing networking code will agree.
* Should stream_socket and dgram_socket be merged into a single socket class? There other types of sockets (e.g. sequenced packet) that would require additional classes if the current approach is retained. This would not be required if the BSD socket APIs were covered by a single class. Alternatively, should additional classes be added for the other socket types?
My instinct tells me that they shouldn't be merged: they are very different concepts, and I wouldn't like this specific abstraction eliminated in asio. would it be more logical to have a basic_socket_stream class and then have socket_type_traits for tcp_socket_trait, udp_socket_trait and seq_socket_trait.. a tcp_socket_stream can be a a basic_socket_stream with the trait of tcp_socket_trait.. aka basic_string -> string just an idea. Minh Minh Phanivong +64 2 136 7118 (NZ) "Instead of living in the shadows of yesterday, walk in the light of today and the hope of tomorrow. " Â–Ă©Â’Â·Â–Â²Â‘Â½ Send instant messages to your online friends http://uk.messenger.yahoo.com

On 8/18/05, Christopher Kohlhoff <chris@kohlhoff.com> wrote:
As discussed in the "surveying async network io libraries" thread, I'd like to turn asio (asio.sourceforge.net) into a proposal for a boost networking library. As promised, here is a list of areas where asio could use some refinement.
[snip]
What's Missing ==============
I think it would be interesting to have some kind of "QoS". A way to give different priorities to handles/operations. I think it would be possible through more than one impl_reactor.
Cheers, Chris
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
-- Felipe Magno de Almeida Developer from synergy and Computer Science student from State University of Campinas(UNICAMP). Unicamp: http://www.ic.unicamp.br Synergy: http://www.synergy.com.br "There is no dark side of the moon really. Matter of fact it's all dark."

Hi Felipe, --- Felipe Magno de Almeida <felipe.m.almeida@gmail.com> wrote:
I think it would be interesting to have some kind of "QoS". A way to give different priorities to handles/operations. I think it would be possible through more than one impl_reactor.
I presume you mean like in realtime systems, perhaps with an API using concepts similar to RTCORBA? I had thought before that something like this might be accomplished through having multiple demuxers where each is assigned a priority and each has its own pool of threads, e.g. rt_demuxer d1(high_priority); rt_demuxer d2(low_priority); rt_stream_socket s1(d1); // high priority socket rt_stream_socket s2(d2); // low priority socket ... thread th1(bind(&rt_demuxer::run, &d1)); thread th2(bind(&rt_demuxer::run, &d2)); Or another approach having a single parent demuxer, with multiple child demuxers, each assigned different priorities, but they all share the parent demuxer's pool of threads (I believe this is more like what RTCORBA does): rt_demuxer d; rt_child_demuxer d1(d, high_priority); rt_child_demuxer d2(d, low_priority); rt_stream_socket s1(d1); // high priority socket rt_stream_socket s2(d2); // low priority socket ... thread th1(bind(&rt_demuxer::run, &d)); thread th2(bind(&rt_demuxer::run, &d)); However I am definitely *not* an expert in anything realtime, so this is just a very rough idea of how I think it might be accomplished. Cheers, Chris

On 8/18/05, Christopher Kohlhoff <chris@kohlhoff.com> wrote: [snip]
I'd appreciate input on:
[snip]
- Any other things that should be on the list.
win_iocp_operation is a base class that takes a pointer to a function and when called(when the IO operation is completed) it executes its pointer. The pointer is normally a static function to a derived class that calls the apropriate handler. There's a comment above win_iocp_operation that is this: // Base class for all IOCP operations. A function pointer is used instead of // virtual functions to avoid the associated overhead. But I dont quite follow the virtual function overhead in this case. I think that asio is reinventing the wheel and with the same overhead. The only way to have less overhead would be to make win_iocp_operation calls the apropriate handler directly, but I dont see that as very possible. The derived function must do some interpretation of the arguments before calling the handler. I think the best solution is to make win_iocp_operation be an abstract class with a virtual function and have the derived functions overwrite the method. I know it is a very implementation detail that dont even add much. But it helps a little to understand asio code and writing more code for it.
Cheers, Chris
-- Felipe Magno de Almeida Developer from synergy and Computer Science student from State University of Campinas(UNICAMP). Unicamp: http://www.ic.unicamp.br Synergy: http://www.synergy.com.br "There is no dark side of the moon really. Matter of fact it's all dark."

Hi Felipe, --- Felipe Magno de Almeida <felipe.m.almeida@gmail.com> wrote:
win_iocp_operation is a base class that takes a pointer to a function and when called(when the IO operation is completed) it executes its pointer. The pointer is normally a static function to a derived class that calls the apropriate handler. There's a comment above win_iocp_operation that is this:
// Base class for all IOCP operations. A function pointer is used instead of // virtual functions to avoid the associated overhead.
But I dont quite follow the virtual function overhead in this case. I think that asio is reinventing the wheel and with the same overhead.
The overhead is not the virtual function call itself, but all the other stuff that gets generated by the compiler as soon as you add a virtual function to a class (e.g. vtable, RTTI etc). Since you can very easily end up with many win_iocp_operation-derived classes in your application (one for each callback handler) using virtual functions can contribute to significant code bloat on some compilers (e.g. MSVC6, although with MSVC7.1 it appears to optimise unused RTTI away). By doing this little workaround, I can guarantee for all compilers that the overhead for each handler is limited to a single function, which is called through a function pointer. Cheers, Chris

From: Christopher Kohlhoff <chris@kohlhoff.com>
--- Felipe Magno de Almeida <felipe.m.almeida@gmail.com> wrote:
win_iocp_operation is a base class that takes a pointer to a function and when called(when the IO operation is completed) it executes its pointer. The pointer is normally a static function to a derived class that calls the apropriate handler. [snip] But I dont quite follow the virtual function overhead in this case. I think that asio is reinventing the wheel and with the same overhead.
The overhead is not the virtual function call itself, but all the other stuff that gets generated by the compiler as soon as you add a virtual function to a class (e.g. vtable, RTTI etc).
Should that interface take an extra argument (void *, for example) that permits the client to get back some context? The client may need to provide a this pointer or allocate a structure on the free store and get back data needed to correctly react. If you provide for context, then you might want to combine the function pointer and context information into an object that will keep the values together not only in the win_iocp_operation invocation mentioned above, but in data members and other function calls that follow. That helps the client to combine them properly and it helps asio to keep them correctly associated. (If there are no other arguments to the win_iocp_operation invocation, then the client needn't be bothered with creating such a structure, but the implementation might still want to.) -- Rob Stewart stewart@sig.com Software Engineer http://www.sig.com Susquehanna International Group, LLP using std::disclaimer;

Hi Rob, --- Rob Stewart <stewart@sig.com> wrote:
Should that interface take an extra argument (void *, for example) that permits the client to get back some context? The client may need to provide a this pointer or allocate a structure on the free store and get back data needed to correctly react.
The context is the pointer to the win_iocp_operation object. The way it works is like this: - The class to perform the asynchronous handling is derived from win_iocp_operation, which is in turn derived from OVERLAPPED. Any context information (e.g. the application's handler for the operation) is stored as a data member in this class: OVERLAPPED ^ | win_iocp_operation ^ | send_operation<HANDLER> - When an async operation is started, a new instance of the derived class is created, and it is passed as the OVERLAPPED* argument of the asynchronous operation (e.g. ::WSASend). - In win_iocp_demuxer_service::run(), the call to ::GetQueuedCompletionStatus retrieves the OVERLAPPED* value, which it then casts to win_iocp_operation and then calls the associated function pointer. - The class derived from win_iocp_operation invokes the application's handler.
(If there are no other arguments to the win_iocp_operation invocation, then the client needn't be bothered with creating such a structure, but the implementation might still want to.)
Please remember that all this stuff is right down in the bowels of asio. A user of asio never needs to deal with it. Cheers, Chris

On 8/31/05, Christopher Kohlhoff <chris@kohlhoff.com> wrote:
Hi Felipe,
Hi Christopher,
The overhead is not the virtual function call itself, but all the other stuff that gets generated by the compiler as soon as you add a virtual function to a class (e.g. vtable, RTTI etc).
Now I see. But.. Does it worth rewriting virtual mechanisms? I think that boost libraries rely a lot on compilers doing the right job and write code that, when possible, are elegant. I think it is a lot more readable with virtual functions. And it's not only with win_iocp_operation that this happens I think.
Since you can very easily end up with many win_iocp_operation-derived classes in your application (one for each callback handler) using virtual functions can contribute to significant code bloat on some compilers (e.g. MSVC6, although with MSVC7.1 it appears to optimise unused RTTI away).
I can only enumerate two classes deriving from win_iocp_operation, no matter how the client uses the normal interface of library. The two I think are: win_iocp_demuxer_service::read_operation and win_iocp_demuxer_service::write_operation.
By doing this little workaround, I can guarantee for all compilers that the overhead for each handler is limited to a single function, which is called through a function pointer.
AFAICS, this workaround is only effective with VC6. I'm only saying all this because I'd like to have all libraries in boost rewriting virtual mechanisms.
Cheers, Chris
Thanks Chris, Felipe. -- Felipe Magno de Almeida Developer from synergy and Computer Science student from State University of Campinas(UNICAMP). Unicamp: http://www.ic.unicamp.br Synergy: http://www.synergy.com.br "There is no dark side of the moon really. Matter of fact it's all dark."

Sorry replying to myself, but I needed to make this correction. On 8/31/05, Felipe Magno de Almeida <felipe.m.almeida@gmail.com> wrote:
AFAICS, this workaround is only effective with VC6. I'm only saying all this because I *wouldnt* like to have all libraries in boost rewriting virtual mechanisms.
-- Felipe Magno de Almeida Developer from synergy and Computer Science student from State University of Campinas(UNICAMP). Unicamp: http://www.ic.unicamp.br Synergy: http://www.synergy.com.br "There is no dark side of the moon really. Matter of fact it's all dark."

From: Felipe Magno de Almeida <felipe.m.almeida@gmail.com>
On 8/31/05, Christopher Kohlhoff <chris@kohlhoff.com> wrote:
The overhead is not the virtual function call itself, but all the other stuff that gets generated by the compiler as soon as you add a virtual function to a class (e.g. vtable, RTTI etc).
Now I see. But.. Does it worth rewriting virtual mechanisms? I think that boost libraries rely a lot on compilers doing the right job and write code that, when possible, are elegant. I think it is a lot more readable with virtual functions. And it's not only with win_iocp_operation that this happens I think.
If this is only intended to be "overridden" via the function pointer in a few derived classes, I agree with you that avoiding virtual functions is confusing at best. (I jumped into the middle of the discussion and thought that unrelated functions might be passed as arguments.) I'd say that unless you know that using virtual functions causes problems, you shouldn't presume that they do. If users say the library is too big and this technique significantly affects the size, then it may be worth considering at that time. You might also consider whether the Strategy pattern applies here. -- Rob Stewart stewart@sig.com Software Engineer http://www.sig.com Susquehanna International Group, LLP using std::disclaimer;

Hi Rob, --- Rob Stewart <stewart@sig.com> wrote:
I'd say that unless you know that using virtual functions causes problems, you shouldn't presume that they do. If users say the library is too big and this technique significantly affects the size, then it may be worth considering at that time.
As I said in another reply, I did find a need to do this change to eliminate code bloat with several compilers. As an example, Borland C++ 5.6.4 seems to add approximately 100 bytes to the executable for each asynchronous call if a virtual function is used in win_iocp_operation. As the change didn't impact the public interface of asio, it seemed like a win. Cheers, Chris

From: Christopher Kohlhoff <chris@kohlhoff.com>
--- Rob Stewart <stewart@sig.com> wrote:
I'd say that unless you know that using virtual functions causes problems, you shouldn't presume that they do. If users say the library is too big and this technique significantly affects the size, then it may be worth considering at that time.
As I said in another reply, I did find a need to do this change to eliminate code bloat with several compilers. As an example, Borland C++ 5.6.4 seems to add approximately 100 bytes to the executable for each asynchronous call if a virtual function is used in win_iocp_operation. As the change didn't impact the public interface of asio, it seemed like a win.
Was that 100 bytes per async call or per type of call? As I understand it, you have a class template derived from win_iocp_operation. Is it 100 bytes for each class created by specializing that template? Is there a reasonable upper bound on the number of such types in a given application? -- Rob Stewart stewart@sig.com Software Engineer http://www.sig.com Susquehanna International Group, LLP using std::disclaimer;

Hi Rob, --- Rob Stewart <stewart@sig.com> wrote:
Was that 100 bytes per async call or per type of call?
Per type of call/callback handler combination.
As I understand it, you have a class template derived from win_iocp_operation.
Several class templates, yes.
Is it 100 bytes for each class created by specializing that template?
Yes, for each class that instantiates the template.
Is there a reasonable upper bound on the number of such types in a given application?
No, I don't think so. An app can make as many asynchronous calls as it wants using any combination of types for a callback handler. This even includes things like call demuxer.post(...) to cause a deferred invocation of a function object. One of the strengths of an async I/O approach is that it makes it relatively easy to compose new asynchronous operations from others. For example, you might define a function: template <typename Stream, typename Handler> void async_read_mymsg(Stream& s, mymsg& m, Handler h); which is is implemented internally using a chain of calls to async_read or async_read_n. Each one of these internal async calls would require its own win_iocp_operation-derived template instantiation, and each one of those types would also be dependent on the Handler type. So in that case the number of win_iocp_operation-derived template instantiations would be the number of internal handlers x the number of different handlers used with async_read_mymsg. And there could be further layers of composition... you get the idea. Cheers, Chris

Hi Felipe, --- Felipe Magno de Almeida <felipe.m.almeida@gmail.com> wrote:
Does it worth rewriting virtual mechanisms? I think that boost libraries rely a lot on compilers doing the right job and write code that, when possible, are elegant. I think it is a lot more readable with virtual functions. And it's not only with win_iocp_operation that this happens I think.
I think it was worth doing (otherwise I wouldn't have done it :) ). It reduces code bloat with several compilers and did not affect the public interface of asio.
I can only enumerate two classes deriving from win_iocp_operation, no matter how the client uses the normal interface of library. The two I think are: win_iocp_demuxer_service::read_operation and win_iocp_demuxer_service::write_operation.
I used the technique where the derived classes are templates, and you are likely to end up with hundreds of them in a typical program. Each of those win_iocp_operation-derived classes takes a Handler as a template argument, so whenever you write: socket_.async_send(buf, length, boost::bind(...)); you would create a new win_iocp_operation-derived class.
AFAICS, this workaround is only effective with VC6. I'm only saying all this because I'd like to have all libraries in boost rewriting virtual mechanisms.
And Borland C++ too, if I remember correctly. Boost.Function takes a similar approach: boost_1_33_0/doc/html/function/misc.html#id1215202 Cheers, Chris

Christopher Kohlhoff <chris@kohlhoff.com> writes:
Hi Felipe,
The overhead is not the virtual function call itself, but all the other stuff that gets generated by the compiler as soon as you add a virtual function to a class (e.g. vtable, RTTI etc).
...
By doing this little workaround, I can guarantee for all compilers that the overhead for each handler is limited to a single function, which is called through a function pointer.
That technique was first used in Boost.Function, IIUC. I'm not trying to direct credit, just letting the list know there's ample precedent for this kind of thing. -- Dave Abrahams Boost Consulting www.boost-consulting.com

I'm writing asynchronous file I/O with the aio_* functions for asio. I'm using libaio library for this. But linux only have support for AIO for files opened with O_DIRECT flag. IMO the allocation and deallocation of aligned buffers should not be visible to users, since it would be a little awkward, but I dont see anyway to have it hidden from the user and not obligate one more copy of the entire buffer. What are your opinions? -- Felipe Magno de Almeida Developer from synergy and Computer Science student from State University of Campinas(UNICAMP). Unicamp: http://www.ic.unicamp.br Synergy: http://www.synergy.com.br "There is no dark side of the moon really. Matter of fact it's all dark."
participants (20)
-
Ariel Badichi
-
Beman Dawes
-
Brock Peabody
-
Bronek Kozicki
-
Caleb Epstein
-
Christopher Kohlhoff
-
David Abrahams
-
Douglas Gregor
-
Felipe Magno de Almeida
-
Jeremy Maitin-Shepard
-
Jose
-
Mr Minh
-
Pedro LamarĂ£o
-
Peter Petrov
-
Peter Simons
-
Rene Rivera
-
Rob Stewart
-
Stefan Arentz
-
Thore Karlsen
-
Thorsten Ottosen