Redesign protocol handling around a task.

It used to be driven by coroutines reading frames. That made it
impossible to call close() while recv() was running without ending up
with two coroutines attempting to read a frame in parallel.

The new design delegates the handling of incoming frames to a task.
Received messages are added to a queue. This doesn't change the external
API, except that it removes the need for wait_close().