When using poll or select kernel functions, your thread "yields" and allows other processes to do some work while your process waits for i/o data. Fibers work the same way but the waiting happens within your process.
Basically, when all fibers are waiting, there's going to be events in the kernel (using epoll or iocp) with the associated fiber pointer to wake it up when something comes up. The fiber (aka task) is called with Fiber.call() when there's news and automatically restores its stack and context, but maybe it has more data in the circular buffer or the manual event you were waiting for was triggered, so the routine can go forward (otherwise, Fiber.yield() is called again and waits for an event from the kernel).
This allows the code to stay linear somewhat. Low-level buffers are all meant to yield when data is not available. Every time a new connection happens, a new fiber is created, same goes when you call runTask.
There was a talk on dconf 2013 about vibe: http://dconf.org/2013/talks/panteleev.html Notice the slides and video links. It focuses on web development, but you'll get some insight into vibe.d that's valid for TCP too.
http://vibed.org/features#fibers
It still doesn't go into full detail, though, and a dedicated article would probably be worthwhile to write.