Boost logo

Boost :

From: Christopher Kohlhoff (chris_at_[hidden])
Date: 2006-04-26 03:21:24


Hello all,

I have just uploaded a draft version of Boost.Asio to the vault
(in the "Input - Output" folder). I'm particularly interested in
feedback on the handler-based custom memory allocation and
IPv4/v6 protocol independence.

Please note: this is not a properly tested release. Caveat
emptor. It has the following known issues:

- It uses getaddrinfo/getnameinfo, but these functions are not
  thread-safe on Mac OS X. They will be replaced by thread-safe
  emulation using getipnodebyname and getipnodebyaddr.

- Although builds using MSVC or Borland C++ automatically
  emulate getaddrinfo/getnameinfo on Windows 2000 and earlier,
  MinGW does not. Therefore MinGW currently only supports
  Windows XP and Windows Server 2003 targets.

- It has not been compiled or tested at all on Solaris. It has
  been tested on Mac OS X 10.4, Debian Sarge with 2.6 linux
  kernel and Windows XP.

Cheers,
Chris

Changes since 0.3.6
===================

(Excluding minor bug fixes and in no particular order.)

* Renamed demuxer to io_service.

* All services inherit from a common base io_service::service.

  As part of this change, the demuxer::get_service() member
  template function has been replaced with three free template
  functions: use_service(), add_service() and has_service().

* Resource classes inherit from basic_io_object.

  Sockets, timers, etc inherit from basic_io_object<Service>.
  This class automatically obtains the correct service object
  from the io_service, and provides the typedefs and functions
  that all objects must provide (e.g. an io_service() member
  function for obtaining the associated io_service object).

* All handler objects destroyed during io_service destruction.

  Previously, if you did not allow demuxer::run() to continue
  until it had no more work to do, and then destroyed the
  demuxer, uninvoked handlers would never be destroyed.

  All stored handler objects that are "owned" by the io_service
  or any associated services are now destroyed automatically
  from inside the io_service. This allows quick, clean shutdown
  of programs using io_service::interrupt().

  This handler cleanup has been implemented using "two-phase"
  service shutdown. Every service that inherits from
  io_service::service must implement a virtual function called
  shutdown_service(). The service's implementation of this
  function is required to destroy all copies of handlers that it
  owns (directly or indirectly). The cleanup must be done in a
  separate phase prior to the destruction of the service
  objects, since the handler destructors may try to access other
  services.

* Renamed locking_dispatcher to strand.

  A strand is defined as a strictly sequential invocation of
  event handlers (i.e. no concurrent invocation). Use of strands
  allows execution of code in a multithreaded program without
  the need for explicit locking (e.g. using mutexes).
  
  Strands may be either implicit or explicit, as illustrated by
  the following alternative approaches:

    - Calling io_service::run() from only one thread means all
      event handlers execute in an implicit strand, due to the
      io_service's guarantee that handlers are only invoked from
      inside run().

    - Where there is a single chain of asynchronous operations
      associated with a connection (e.g. in a half duplex
      protocol implementation like HTTP) there is no possibility
      of concurrent execution of the handlers. This is an
      implicit strand.

    - An explicit strand is an instance of asio::strand. All
      event handler function objects need to be wrapped using
      asio::strand::wrap() or otherwise posted/dispatched
      through the asio::strand object.

* Add macros to disable platform-specific implementations.

  The following macros can be used to disable platform-specific
  implementation code and revert to portable select-based
  asynchronous I/O emulation:

    - BOOST_ASIO_DISABLE_EPOLL
    - BOOST_ASIO_DISABLE_IOCP
    - BOOST_ASIO_DISABLE_KQUEUE

* Handler-based custom memory allocation.

  Many asynchronous operations need to allocate an object to
  store state associated with the operation. For example, the
  Win32 implementation needs OVERLAPPED-derived objects to pass
  to Win32 API functions.
  
  Furthermore, programs typically contain easily identifiable
  chains of asynchronous operations. A half duplex protocol
  implementation (e.g. an HTTP server) would have a single chain
  of operations per client (receives followed by sends). A full
  duplex protocol implementation would have two chains executing
  in parallel. Programs should be able to leverage this
  knowledge to reuse memory for all asynchronous operations in a
  chain.

  Given a copy of a user-defined Handler object h, if asio needs
  to allocate memory associated with that handler it will
  execute the code:

    void* pointer = asio_handler_allocate(size, &h);

  Similarly, to deallocate the memory it will execute:

    asio_handler_deallocate(pointer, &h);

  These functions are located using ADL. The asio library
  provides default implementations of the above functions in the
  global namespace:

    void* asio_handler_allocate(size_t, ...);
    void asio_handler_deallocate(void*, ...);

  which are implemented in terms of ::operator new() and
  ::operator delete() respectively.

  The implementation guarantees that the deallocation will occur
  before the associated handler is invoked, which means the
  memory is ready to be reused for any new asynchronous
  operations started by the handler.

  Custom memory allocation support is not yet used throughout
  the asio implementation. In particular, the reactor-based
  emulation still uses dynamically allocated objects. It is
  fully implemented for the Win32-specific I/O completion port
  code. The custom memory allocation support has also been
  disabled for Borland C++, since it generated obscure compiler
  errors.

  See libs/asio/example/allocation/server.cpp for an example of
  how to use the handler-based custom memory allocation.

* Allocator template parameter removed.

  Removing the Allocator template parameter reduces the number
  of core types in asio and makes it easier to develop custom
  services (i.e. you don't have to worry what Allocator object
  the io_service has been templated on). It should also make it
  simpler to create a shared or static library-based
  implementation.

  A combination of handler-based custom memory allocation, and
  the ability for the asio implementation to embed objects
  directly into object internals (e.g. socket implementations),
  should minimise dynamic memory allocations.
  
  If there is a demonstrable need, I may consider adding an
  io_service::service-based allocator to allow memory allocation
  to be further customised on a per-io_service-object basis.
  
* Add support for building without thread support.

  POSIX platforms only. The Windows implementation still
  requires threads.

* Async waits cancelled by deadline_timer expires_*() setters.

  Outstanding asynchronous wait operations are now automatically
  cancelled if you call expires_at() or expires_from_now() to
  modify the expiry time of a deadline_timer. The previous
  documented behaviour was that it was "undefined" to modify the
  expiry time while there were outstanding waits.

* Support for reference-counted buffers.

  The Const_Buffers and Mutable_Buffers concepts have been
  modified to allow reference counted buffers to be used. In
  particular:

    - The {Const|Mutable}_Buffers::iterator typedef and
      non-const begin()/end() functions have been removed from
      the concepts' requirements. Implementations of the
      concepts now need only provide const_iterator support, and
      do not need to allow in-place modification of the
      elements.

    - The asio implementation guarantees that at least one copy
      of the buffers object will be maintained for as long as
      the system may need access to the data.

    - The asio implementation explicitly converts the
      dereferenced const_iterator into mutable_buffer or
      const_buffer as needed.

  See the libs/asio/example/buffers/reference_counted.cpp
  example program.

* Strongly-typed socket classes.

  Rather than asio::stream_socket, asio::datagram_socket and
  asio::socket_acceptor classes that were used for all
  protocols, these socket classes are now specific to each
  protocol. For example, we now have:

    ip::tcp::socket
    ip::tcp::acceptor
    ip::udp::socket

  and in the future we might have:

    unix::stream_protocol::socket
    bluetooth::rfcomm::socket

  and so on. These protocol-specific classes are simply typedefs
  for templates instantiated on the corresponding Protocol
  class. E.g.:

    template <typename Protocol>
    class stream_socket_service;

    template <typename Service>
    class basic_stream_socket;

    class tcp
    {
      ...
      typedef stream_socket_service<tcp> socket_service;
      typedef basic_stream_socket<socket_service> socket;
      ...
    };

  New protocols can be added by creating classes like tcp above.

* Use inheritance in socket types to prevent "layer violations".

  The basic_stream_socket and basic_datagram_socket templates
  now inherit from basic_socket<Service>. This class includes
  all functions that are common to stream and datagram sockets
  (everything except send/receive and friends). Stream layers
  such as ssl::stream return a reference to basic_socket<> from
  the lowest_layer() function.

* IPv4 and IPv6 protocol independence.

  Socket, endpoint and address classes for IP (i.e. TCP and UDP)
  are independent of the particular IP version (4 or 6) used.
  The ipv4 namespace has been removed. For example:

    - The ipv4::address class has become IP version-independent
      ip::address. If you need to manipulate addresses for a
      specific IP version use ip::address_v4 or ip::address_v6.

    - The protocol classes are now ip::tcp and ip::udp, and the
      corresponding endpoint types are ip::tcp::endpoint and
      ip::udp::endpoint.

    - To open a socket you must specify the protocol version,
      e.g.:

        ip::tcp::socket socket(io_service);
        ...
        socket.open(ip::tcp::v4()); // IPv4
        ...
        socket.open(ip::tcp::v6()); // IPv6

      Note: if you already have an endpoint you can avoid this
      by using the protocol associated with the endpoint object:

        socket.open(endpoint.protocol());

    - When constructing an endpoint with just a port number, you
      must specify the IP version:

        ip::tcp::endpoint(ip::tcp::v4(), 12345);

  The development of programs that are independent of the IP
  version is further aided by the new resolver interface
  outlined below.

* Resolver replaces ipv4::host_resolver.

  The ipv4::host_resolver and ipv4::host classes have been
  removed in favour of an endpoint-oriented interface that
  can support different protocols. The functionality of this new
  interface is based on getaddrinfo and getnameinfo.

  Use the resolver by constructing a query, calling resolve()
  (or async_resolve()), and using the returned forward-only
  iterator to enumerate the endpoints that match the query. For
  example:

    ip::tcp::resolver resolver(io_service);
    ip::tcp::resolver::query query("foobar.com", "http");
    ip::tcp::resolver::iterator iter = resolver.resolve(query);
    ip::tcp::resolver::iterator end;
    while (iter != end)
    {
      ip::tcp::endpoint ep = *iter;
      std::cout << iter->endpoint() << std::endl;
      std::cout << iter->host_name() << std::endl;
      std::cout << iter->service_name() << std::endl;
      ++iter;
    }

  The asynchronous equivalent is:

    void my_class::start_resolve()
    {
      ip::tcp::resolver resolver(io_service);
      ip::tcp::resolver::query query("foobar.com", "http");
      resolver.async_resolve(query,
          boost::bind(&my_class::resolve_handler, this,
            asio::placeholders::error,
            asio::placeholders::iterator));
    }

    void my_class::resolve_handler(const asio::error& e,
        ip::tcp::resolver::iterator iter)
    {
      if (!e)
      {
        ip::tcp::resolver::iterator end;
        while (iter != end)
        {
          ...
          ++iter;
        }
      }
    }

  There are overloads of resolve()/async_resolve() that take an
  endpoint and return an iterator. These are intended for
  reverse lookups.

  Since the ipv4::host_resolver had a local_host() function that
  returned information about the local system, a new function
  ip::host_name() has been added which returns a string
  containing the name of the system.

* Multicast socket options renamed.

  The IP-specific socket options for multicast have been
  renamed. They are now also independent of the IP version. The
  name changes are:

    - add_membership is now join_group
    - drop_membership is now leave_group
    - time_to_live is now hops

  These multicast socket options are now found in the
  ip::multicast namespace.

* String-to-address conversion now uses from_string function.

  This change was driven by three things:

    - The need to indicate an error without throwing if a
      conversion from string to address fails.

    - String literal addresses would not be common and so
      support for them does not need to be convenient.

    - It is more common to use a resolver to convert strings
      into complete endpoints.

  To convert a string into an address you would now write:

    ip::address address = ip::address::from_string("1.2.3.4");

  which throws an asio::error on failure, or:

    asio::error error;
    ip::address address = ip::address::from_string(
        "1.2.3.4", asio::assign_error(error));

  to convert without throwing. Similar functions exist on the
  ip::address_v4 and ip::address_v6 classes.

* IPv4 and IPv6 addresses can be converted to and from bytes.

  The ip::address_v4 and ip::address_v6 classes contain:

    - A typedef called bytes_type, which is a
      boost::array<unsigned char, N>. For IPv4, N is 4. For
      IPv6, N is 16.

    - A constructor that takes a bytes_type value.

    - A function to_bytes() that returns a bytes_type value.

* Order of endpoint constructor params reversed.

  The old ipv4::tcp::endpoint class (and udp equivalent) had a
  constructor that took a port and address in that order. The
  new ip::tcp::endpoint and ip::udp::endpoint classes have the
  order reversed, since that is more idiomatic.

* Change position of flags param on send_to, receive_from.

  The order of parameters on the basic_datagram_socket's
  send_to, async_send_to, receive_from and async_receive_from
  functions have been changed to put the endpoint before the
  message flags.
  
  Overloads of these functions have been added that do not
  include the flags parameter, which gives the flags a default
  value of 0. A similar change has been made for the send,
  async_send, receive and async_receive functions on
  basic_datagram_socket and basic_stream_socket.

* Don't globally block SIGPIPE if an alternative is available.

  On Linux, the MSG_NOSIGNAL flag is now passed to the sendmsg
  function. On Mac OS X, the SOL_SOCKET/SO_NOSIGPIPE socket
  option is set on all sockets when they are created. The
  SIGPIPE signal is globally ignored on Solaris.

* Reactor implementations run operations until complete.

  The reactor socket implementation sets non-blocking I/O for
  asynchronous emulation (non-blocking mode is enabled for a
  socket the first time an async call is made on that socket).
  Operations are now restarted automatically if the asynchronous
  operation returns would_block or try_again, meaning that
  asynchronous operations are guaranteed never to return these
  errors.

Changes planned for 0.3.7
=========================

* Fix tutorials/examples to not use "C style" code.

* Emulation of getaddrinfo/getnameinfo on platforms where it is
  unavailable or broken.

Changes planned for post-0.3.7
==============================

* Iostreams support.

* Split_error handler function object.

* Overloads of io_service::run() that take a timeout parameter.

  This is to allow calls to run() to be interleaved with other
  application code. The run() function will return an enum value
  indicating why it finished (e.g. timed_out, out_of_work,
  interrupted, etc).

* Polymorphic wrapper for the Dispatcher concept.

* Polymorphic wrapper for the Stream concept.

* Investigate handler-invocation hooking.

* Develop a performance test framework.

* Rework reactor implementations to support handler-based custom
  memory allocation.

* New reactor implementations, e.g. /dev/poll and Solaris 10
  ports.

* Use the Windows API call ConnectEx where available for true
  asynchronous socket connect operations.

* Add better support for line-based protocols.

  This might take the form of an {async_}read_line function or
  equivalent.

* Add UNIX domain socket support.

* Documentation, documentation, documentation.

I hope that's everything!


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk