asio networking proposal 0.3.3

Hello all, Thanks to the feedback on the previous thread, I have just released asio 0.3.3. You can download it, or view the documentation online, at http://asio.sourceforge.net. In addition to some general cleanup, the changes include: - dgram_socket has been renamed to datagram_socket - send and recv have been replaced by write and read respectively. Functions called send and receive have been retained on the socket classes with an additonal flags argument. On the datagram_socket, sendto has been renamed to send_to, and recvfrom to receive_from. - timer has been renamed to deadline_timer and redesigned to use Boost.Date_Time classes. - socket_connector has been dropped in favour of connect/async_connect functions on both stream_socket and datagram_socket. - async_get_host_by_address and async_get_host_by_name functions have been added to ipv4::host_resolver. The implementation is simple and only simulates asynchronicity (using one background thread per-demuxer to execute the corresponding blocking call). - The ipv4::host structure has been turned into a class. - The asio::arg namespace has been renamed to asio::placeholders. The bytes_recvd and bytes_sent placeholders have been replaced with bytes_transferred. Similarly, the last_bytes_recvd/sent and total_bytes_recvd/sent have been replaced with last_bytes_transferred and total_bytes_transferred. - The "services" have been made part of the public interface of asio. E.g. for basic_stream_socket there is a stream_socket_service. These services are also templates and have an allocator as the template parameter (although this currently has no effect on the implementation). - Requires Boost 1.33. I still have much to do in terms of examples and design documentation. I think the one remaining design issue to address for now is that of safety (i.e. that some people don't like a void*+size_t interface) and reconciling that with the others' need for efficiency (sending data structures directly and avoiding copying). After pondering for some time, I think I have devised an approach that meets both requirements. The approach is loosely based on Symbian's TPtr/TPtrC classes. Here is an outline of the idea: - Define two new concepts, Buffer and Const_Buffer: class Buffer { public: size_t segment_count() const; void* segment_data(size_t index) const; size_t segment_size(size_t index) const; size_t total_size() const; // sum of all segment sizes Buffer sub_buffer(size_t start) const; Buffer sub_buffer(size_t start, size_t length) const; }; class Const_Buffer { public: size_t segment_count() const; const void* segment_data(size_t index) const; size_t segment_size(size_t index) const; size_t total_size() const; // sum of all segment sizes Const_Buffer sub_buffer(size_t start) const; Const_Buffer sub_buffer(size_t start, size_t length) const; }; These concepts are Assignable and CopyConstructible. I included the support for multiple segments as it provides a neat solution to doing scatter-gather I/O. - Change all read/write/send/receive functions to only take a buffer concept, e.g. template <typename Const_Buffer> size_t send(Const_Buffer b); template <typename Buffer> size_t receive(Buffer b); - Provide helper functions to create buffer objects for various types: impl_defined_buffer make_buffer(void* data, size_t len); impl_defined_const_buffer make_buffer(const void* data, size_t len); template <typename T, size_t N> impl_defined_buffer make_buffer(T[N] data); template <typename T, size_t N> impl_defined_const_buffer make_buffer(const T[N] data); template <typename T> impl_defined_buffer make_buffer(std::vector<T>& data); template <typename T> impl_defined_const_buffer make_buffer(const std::vector<T>& data); // For scatter-gather... impl_defined_buffer make_buffer(void* data0, size_t len0, ..., void* dataN, size_t lenN); impl_defined_const_buffer make_buffer(const void* data0, size_t len0, ..., const void* dataN, size_t lenN); The important thing to note is that the buffer objects do not own the data they represent. They are intended to be cheap-to-copy value objects that simply represent a region of memory. Here are some usage examples: demuxer d; socket s(d); // ... mystruct m = { ... }; s.send(make_buffer(&m, sizeof(m))); char s[] = "Hello world"; s.send(make_buffer(s)); std::vector<char> v(1024); s.receive(make_buffer(v)); Thoughts? I'm not really happy with the name Buffer, but couldn't think of anything else. I'm open to any suggestions. Cheers, Chris

Christopher Kohlhoff <chris@kohlhoff.com> writes: [snip]
- send and recv have been replaced by write and read respectively. Functions called send and receive have been retained on the socket classes with an additonal flags argument.
Why not use the same names (read and write) for the flags versions, either by giving the flags parameter a default value, or by adding an additional overload?
On the datagram_socket, sendto has been renamed to send_to, and recvfrom to receive_from.
Perhaps sendto and recvfrom functionality could be provided by additional function overloads using the same names read and write. This would allow better naming consistency.
- timer has been renamed to deadline_timer and redesigned to use Boost.Date_Time classes.
- socket_connector has been dropped in favour of connect/async_connect functions on both stream_socket and datagram_socket.
It seems that it might be useful to design the interface in regards to connect such that the user does not have access to methods, such as read and write, when the cannot be used. This could be achieved by providing the user with an object that provides the read/write interface only after connect completes. The socket class itself would not have these functions. I haven't thought this through very much, but it seems like something worth considering. Another related idea is to provide two separate `stream' objects for regular and out-of-band data for TCP. It seems like this could potentially allow for a cleaner interface for sending out-of-band data, but I have also not thought this idea through fully.
- async_get_host_by_address and async_get_host_by_name functions have been added to ipv4::host_resolver. The implementation is simple and only simulates asynchronicity (using one background thread per-demuxer to execute the corresponding blocking call).
What about using platform-specific asynchronous name resolution facilities? [snip]
- The "services" have been made part of the public interface of asio. E.g. for basic_stream_socket there is a stream_socket_service. These services are also templates and have an allocator as the template parameter (although this currently has no effect on the implementation).
In what case would some other `service' be used for basic_stream_socket? [snip]
After pondering for some time, I think I have devised an approach that meets both requirements. The approach is loosely based on Symbian's TPtr/TPtrC classes.
[snip] One comment is that the types returned by make_buffer should be known to the user and easy to type, because the sub-buffer methods seem to be primarily useful in the case that the result of make_buffer is stored in a variable rather than passed directly to read, write, and not everyone wants to use templates to avoid having to type types. -- Jeremy Maitin-Shepard

Hi Jeremy, --- Jeremy Maitin-Shepard <jbms@cmu.edu> wrote:
Why not use the same names (read and write) for the flags versions, either by giving the flags parameter a default value, or by adding an additional overload?
I'll investigate adding them as overloads, but I think from memory it may cause problems on some compilers due to the existing member template overloads for the custom error handler: void read(void* d, size_t l); template <typename Error_Handler> void read(void* d, size_t l, Error_Handler h);
Perhaps sendto and recvfrom functionality could be provided by additional function overloads using the same names read and write. This would allow better naming consistency.
Using overloads for this would definitely cause problems with the error handler overloads, because both the endpoint and error handler have to be template parameters.
It seems that it might be useful to design the interface in regards to connect such that the user does not have access to methods, such as read and write, when the cannot be used. This could be achieved by providing the user with an object that provides the read/write interface only after connect completes. The socket class itself would not have these functions. I haven't thought this through very much, but it seems like something worth considering.
It might be something that could be accomplished using some non-member functions similar to read/async_read and friends. It only makes sense for a stream socket too, because you can use a datagram socket in an unconnected state, and then connect it and continue to use it. I'll leave this as something to think about after the rest of the interface settles down.
Another related idea is to provide two separate `stream' objects for regular and out-of-band data for TCP. It seems like this could potentially allow for a cleaner interface for sending out-of-band data, but I have also not thought this idea through fully.
Is out-of-band data always (i.e. for all protocols) a stream on a stream_socket? Anyway, it should be easy enough to write a template implementation of the Stream concept (a la buffered_stream) that simply forwards all read/write calls so that they use the appropriate flags for out-of-band data.
What about using platform-specific asynchronous name resolution facilities?
I wanted to get the interface defined first and agreed on. I'll leave platform-specific implementations for a later version.
In what case would some other `service' be used for basic_stream_socket?
I might want to use a custom allocator: typedef basic_stream_socket< stream_socket_service<myallocator> > my_stream_socket; or might want to use a custom demuxing mechanism.
One comment is that the types returned by make_buffer should be known to the user and easy to type, because the sub-buffer methods seem to be primarily useful in the case that the result of make_buffer is stored in a variable rather than passed directly to read, write, and not everyone wants to use templates to avoid having to type types.
The problem with having them return a known type is that the buffer type then has to support an unknown number of segments. IMHO that would impose an unnecessary cost on the case where you just want to send raw memory. If I leave the return type unspecified then: impl_defined_buffer make_buffer(void*, size_t); can return an object that is optimised for the single segment case. Perhaps asio could provide a dynamic buffer class to cater for your use case, which has the same relationship to make_buffer as boost::function has to boost::bind. I.e. buffer b = make_buffer(data, length); Would that be acceptable? Cheers, Chris
participants (2)
-
Christopher Kohlhoff
-
Jeremy Maitin-Shepard