asio formal review

At last! A networking library in the true spirit of Boost. I have a few thoughts regarding the design that is basically a follow-up to the discussion started in the asio-users mailing list: http://sourceforge.net/mailarchive/forum.php?thread_id=9196138&forum_id=42787 The library documents a Stream conecpt that represent a read/write stream interface over a connection. In addition to this interface classes that implement Stream contain methods for supporting establishment and management of the stream's underlying infrastructure - active and passive connection establishment, accessing socket options, handshakimg etc. I suggest to decouple this functionality from the Stream into a separate concept/interface for all the activities leading to the establishment of a stream as well as accessing connection properties. This IMO would lead to a more flexible and cleaner design. Streams could be layered (eg. SSL/TLS over TCP), and stream layering should be strictly enforced. Once the top level stream has been established there should be no access to lower level streams. When you have an established secure connection you don't want to allow direct read/write on the underlying TCP socket. Take a look at the Stream doc and notice the lowest_layer() public member function that exposes a lower layer stream. Interestinlgly, this method is not related to the documented stream functionality. It is used for connection establishemnt and accessing connection properties. The bottom line of my suggestion is to have 2 distinct interfaces. One is for stream establishment and connection properties management. It provides efficient protocol stack layer navigation and access (but no stream i/o operations). The other one is strictly layered streaming interface for data transfer. Do you think it makes sense? Thank you, Eugene

Hi Eugene, --- Eugene Alterman <eugalt@verizon.net> wrote: <snip>
The bottom line of my suggestion is to have 2 distinct interfaces. One is for stream establishment and connection properties management. It provides efficient protocol stack layer navigation and access (but no stream i/o operations). The other one is strictly layered streaming interface for data transfer.
Do you think it makes sense?
Could you sketch out some rough code illustrating what you are thinking of? I'm having trouble seeing how it would fit together, in particular how you would transition from one interface the other. Cheers, Chris

Christopher Kohlhoff wrote:
Hi Eugene,
--- Eugene Alterman <eugalt@verizon.net> wrote: <snip>
The bottom line of my suggestion is to have 2 distinct interfaces. One is for stream establishment and connection properties management. It provides efficient protocol stack layer navigation and access (but no stream i/o operations). The other one is strictly layered streaming interface for data transfer.
Do you think it makes sense?
Could you sketch out some rough code illustrating what you are thinking of? I'm having trouble seeing how it would fit together, in particular how you would transition from one interface the other.
I think Eugene's basic idea is to have separate factory classes that are not streams that do the accept and connect handling. Once a connection is established, the an operation on the factory returns (or initializes) a stream object. -- Jon Biggar Levanta jon@levanta.com

I think Eugene's basic idea is to have separate factory classes that are not streams that do the accept and connect handling. Once a connection is established, the an operation on the factory returns (or initializes) a stream object.
Well, asio does have acceptors and connectors...

"Giovanni P. Deretta" <gpderetta@gmail.com> wrote in message news:439DAEA5.4010507@gmail.com...
I think Eugene's basic idea is to have separate factory classes that are not streams that do the accept and connect handling. Once a connection is established, the an operation on the factory returns (or initializes) a stream object.
Do I look like Doug Schmidt? ;-)
Well, asio does have acceptors and connectors...
asio does not intend to strictly follow ACE patterns. It has socket wrapper class templates. There is an acceptor template but no connector template. ACE SOCK_Connector (unlike ACE_SOCK_Acceptor) does not wrap a socket.

asio does not intend to strictly follow ACE patterns. It has socket wrapper class templates. There is an acceptor template but no connector template. ACE SOCK_Connector (unlike ACE_SOCK_Acceptor) does not wrap a socket.
Oh, i didn't realize that asio didn't have a connector... I've actually looked only at the server-side stuff :). Anyway, i think having a connector would at least be symmetric with the acceptor, and it might actually make sense for some systems/transport type (not that i've any in mind right now)...

"Christopher Kohlhoff" <chris@kohlhoff.com> wrote in message news:20051212114532.54650.qmail@web32604.mail.mud.yahoo.com...
Hi Eugene,
--- Eugene Alterman <eugalt@verizon.net> wrote: <snip>
The bottom line of my suggestion is to have 2 distinct interfaces. One is for stream establishment and connection properties management. It provides efficient protocol stack layer navigation and access (but no stream i/o operations). The other one is strictly layered streaming interface for data transfer.
Do you think it makes sense?
Could you sketch out some rough code illustrating what you are thinking of? I'm having trouble seeing how it would fit together, in particular how you would transition from one interface the other.
Hi Chris, I will call this interfaces core_socket and stream_socket respectively. You always start with an object of a stream_socket class and then obtain a reference to core_socket from it. Let us first start with a design that does not change the current interface of stream_socket. Define a new core_socket class template, and derive stream_socket from it adding the stream functionality. Then we can define lowest_layer_type as core_socket. This will solve the stream layering problem since having a reference to core_socket of a lower layer would not allow performing stream operations on it. The next step would be to derive stream_socket privately from core_socket and provide a public member function returning a reference to core_socket. This would deal with any temptation to downcast a core_socket reference to stream_socket. I hope I made myself clear now, Eugene.

Hi Eugene, --- Eugene Alterman <eugalt@verizon.net> wrote:
I will call this interfaces core_socket and stream_socket respectively. You always start with an object of a stream_socket class and then obtain a reference to core_socket from it.
Let us first start with a design that does not change the current interface of stream_socket. Define a new core_socket class template, and derive stream_socket from it adding the stream functionality. Then we can define lowest_layer_type as core_socket. This will solve the stream layering problem since having a reference to core_socket of a lower layer would not allow performing stream operations on it.
The next step would be to derive stream_socket privately from core_socket and provide a public member function returning a reference to core_socket. This would deal with any temptation to downcast a core_socket reference to stream_socket.
I think I understand what you want now, however I propose a few modifications, especially to preserve source compatibility with existing code and keep the common use cases simple. The core_socket template (don't particularly like the name, will think about it) could be used as the base for both stream_socket and datagram socket since they share everything except the read_some/write_some/send*/receive* functions. E.g.: template <typename Service> class core_socket { public: ... typedef core_socket<Service> lowest_layer_type; lowest_layer_type& lowest_layer() { return *this; } protected: service_type& service_; impl_type impl_; }; template <typename Service> class basic_stream_socket : private core_socket<Service> { public: ... typedef core_socket<Service> lowest_layer_type; lowest_layer_type& lowest_layer() { return *this; } }; template <typename Service> class basic_datagram_socket : private core_socket<Service> { public: ... typedef core_socket<Service> lowest_layer_type; lowest_layer_type& lowest_layer() { return *this; } }; The next_layer_type typedefs and next_layer() functions would be removed. The derived type should bring all public names from core_socket into its own public interface. Are using declarations OK in this case? If not, then via forwarding functions. That way existing uses of stream_socket continue to work as-is. Uses of stream templates would lose access to the underlying socket's I/O functions, as you want. Note there is still a way to circumvent the layering but it is now an explicit choice, e.g.: stream_socket inner(demuxer); buffered_stream<stream_socket&> outer(inner); Would anyone else care to comment on this modification? Cheers, Chris

template <typename Service> class core_socket { public: ... typedef core_socket<Service> lowest_layer_type; lowest_layer_type& lowest_layer() { return *this; }
protected: service_type& service_; impl_type impl_; };
template <typename Service> class basic_stream_socket : private core_socket<Service> { public: ... typedef core_socket<Service> lowest_layer_type; lowest_layer_type& lowest_layer() { return *this; } };
template <typename Service> class basic_datagram_socket : private core_socket<Service> { public: ... typedef core_socket<Service> lowest_layer_type; lowest_layer_type& lowest_layer() { return *this; } };
The next_layer_type typedefs and next_layer() functions would be removed.
The derived type should bring all public names from core_socket into its own public interface. Are using declarations OK in this case? If not, then via forwarding functions.
That way existing uses of stream_socket continue to work as-is. Uses of stream templates would lose access to the underlying socket's I/O functions, as you want.
Note there is still a way to circumvent the layering but it is now an explicit choice, e.g.:
stream_socket inner(demuxer); buffered_stream<stream_socket&> outer(inner);
Would anyone else care to comment on this modification?
I think that there should be NO portable way to get the underlying core_socket from a stream_socket. In fact, while the inheritance holds for BSD sockets (i.e posix sockets and winapi SOCKETS), it probably does not make any sense for any other kind of transport. Generic code should NOT assume that datagram sockets, acceptor sockets, stream sockets of a given domain can be converted to the same type. On the other hand there should definitely be a way to access the underlying base type for domain/system specific code for example using friend global functions, i.e. core_socket& posix_get_core_socket(<derived socket type>&), core_socket& winapi_get_core_socket(<derived socket type>&) Code that uses them must understand that they are specific and not a generic interface. Comments? gdp

--- "Giovanni P. Deretta" <gpderetta@gmail.com> wrote:
I think that there should be NO portable way to get the underlying core_socket from a stream_socket. In fact, while the inheritance holds for BSD sockets (i.e posix sockets and winapi SOCKETS), it probably does not make any sense for any other kind of transport. Generic code should NOT assume that datagram sockets, acceptor sockets, stream sockets of a given domain can be converted to the same type.
The changes being proposed in this thread don't introduce inheritance so that they can be treated as the same type. The core_socket template would be a private base class, *and* it's a template on the service type, i.e. the base class for stream_socket is: core_socket<stream_socket_service<> > and the base for datagram_socket is: core_socket<datagram_socket_service<> > So no conversion to the same type possible here. Note that I only suggest using the same core_socket template as a base for both to eliminate code duplication. If there's not sufficient merit in avoiding that duplication, separate bases (core_stream_socket<...> and core_datagram_socket<...>) can be used. Cheers, Chris

"Christopher Kohlhoff" <chris@kohlhoff.com> wrote in message news:20051213113710.76328.qmail@web32615.mail.mud.yahoo.com...
The derived type should bring all public names from core_socket into its own public interface. Are using declarations OK in this case? If not, then via forwarding functions.
That way existing uses of stream_socket continue to work as-is. Uses of stream templates would lose access to the underlying socket's I/O functions, as you want.
That's precisely what I've anticipated to come up next . :-) I just want to provide an illustrating example. This is an actual situation I encountered while experimenting with asio that prompted me to advocate these changes. I was trying to write a server that would run over a regular tcp connection or a secure connection depending on a run-time program option. I created an abstract base session class that contained all the transport-independent processing logic. It had a pure virtual function that I misleadingly named socket() returning a tcp socket to be used by the acceptor. Then I made a stupid mistake - instead of also providing virtual functions for i/o I started using socket() for that. You can imagine what I ended up with on the secure server!. The proposed changes would have prevented me from making such a mistake.

Hi Eugene, --- Eugene Alterman wrote:
That's precisely what I've anticipated to come up next . :-)
So I take it your happy with the changes then? I'll try to make the change for the next version of asio. I'm still not happy with the naming, but assuming those names I would have: typedef basic_stream_socket< stream_socket_service<> > stream_socket; typedef basic_core_socket< stream_socket_service<> > core_stream_socket;
I just want to provide an illustrating example. This is an actual situation I encountered while experimenting with asio that prompted me to advocate these changes.
I was trying to write a server that would run over a regular tcp connection or a secure connection depending on a run-time program option. I created an abstract base session class that contained all the transport-independent processing logic. It had a pure virtual function that I misleadingly named socket() returning a tcp socket to be used by the acceptor. Then I made a stupid mistake - instead of also providing virtual functions for i/o I started using socket() for that. You can imagine what I ended up with on the secure server!.
The proposed changes would have prevented me from making such a mistake.
So under the new scheme you would instead write something like: class session : public abstract_session: { public: ... virtual core_stream_socket& socket() { return socket_.lowest_layer(); } ... private: ssl::stream<stream_socket> socket_; }; Of course it occurs to me that if the socket() function on your class was instead named lowest_layer(), the socket_acceptor would be able to operate on your session class directly. I'm also starting to think that if next_layer() is going, lowest_layer() is not the best name. Any suggestions for an alternative? It has to be something independent of sockets since this concept should apply to other things like files too. Cheers, Chris

"Christopher Kohlhoff" <chris@kohlhoff.com> wrote in message news:20051214113813.75629.qmail@web32613.mail.mud.yahoo.com...
Hi Eugene,
--- Eugene Alterman wrote:
That's precisely what I've anticipated to come up next . :-)
So I take it your happy with the changes then? I'll try to make the change for the next version of asio. I'm still not happy with the naming, but assuming those names I would have:
I just picked the first name that came to my mind to illustrate the idea. Now I am thinking that the first approach of just deriving stream_socket publicly might be quite adequate and would provide reasonable safety without going all the way of putting the user on a suicide watch.
participants (4)
-
Christopher Kohlhoff
-
Eugene Alterman
-
Giovanni P. Deretta
-
Jonathan Biggar