Following is what I understood while writing a Tornado like server implementation in PHP. Please correct me if I'm wrong.

-----

If you consider a normal application/process, the computation done there is usually very fast. For example, take a blog post being displayed. Once everything(data) is there, wrapping the content in a template, creating the HTTP requests only takes few milliseconds. However, disk IO, database IO, network IO, etc take a quite lot of time compared to computing. The process is usually idle while waiting for those resources.

Now, consider Apache. It's creating a TCP socket and starts listening. Once a connection is made by a client, it spawns a new thread for handling this connection. But since it's waiting for resources like network IO, the thread stays idle for a while. Now, imagine if you are getting lot of requests - like 1000 requests per second. If finishing one web request(processing+waiting) takes about 500ms, apache will need 500 threads per second. Now, creating a thread contains a certain amount overhead. When the thread count goes higher, it becomes much more slower.

Now, consider Node.js as a webserver. First, Node.js server creates a listening socket. In unix platforms, there is a kernel mechanism called "poll"(I think epoll is used in Node.js). Instead of waiting for a connection to send data, you can just register the connection with poll API and continue with your program. That's what happens in Node.js - it just creates the first listening socket, registers it and drops into a loop. Now, inside the loop, it's calling the poll API again with a very low(may be even 0, not sure) waiting time. Now, if there is any data in a registered socket, this API will notify it. So, you take new data and starts processing it. As for the handling of first socket, it creates a new Socket connection and registers that with poll API too and continues the process normally. Now, when HTTP request data comes to the second socket, the main loop gets notified by the epoll API. So, the main loop just jumps into the HTTP request handling function. This function takes the data available, append it with any old data sent from that socket and checks whether if there is enough data for a complete request. If there is not, it simply saves the data in memory and returns to the main loop. If there is data, it simply computes the response and send it back.

A quite similar happens if the response generation need any disk content or database content. Those functions just say I want access to these resources, and tell me once the data is back - and then the main loop takes over.

So, the main difference is that Async model eliminates the overhead created by thousands of threads/processes and handles many connections in single thread. The trick here is that understanding computing only takes a really small amount of time (comparatively). As I understand it, you can't do heavy processing inside the request handlers(like sort 100,000 numbers using buble sort) - this will halt all other requests. Of course, doing the same on a threaded server will cause severe lag in other requests - but that's because of the heavy CPU usage. In Node.js case, it's simply not even trying to process other requests because it's counting on the request handler to finish up quickly, or return to main loop when it's waiting.