How WebSocket server handles multiple incoming connection requests?

Your question is great!

I would like to try to answer it from the point of view involving the system calls listen() and accept(). Understanding the behavior of these two calls I think is quite insightful and sufficient to answer your question.

Spoiler: we can answer your question by looking into how TCP/IP works :-)

For the core part of the question there really is no difference depending on HTTP or WebSocket. The common ground is TCP over IP. Sending an HTTP request requires an established TCP/IP connection between two parties (I have tried to elaborate on that a bit more here).

In case of a simple web browser / web server scenario

  1. first, a TCP connection is established between both (initiated by the client)
  2. then an HTTP request is sent through that TCP connection (from the client to the server)
  3. then an HTTP response is sent through the same TCP connection (in the other direction, from the server to the client)

After this exchange, the underlying TCP connection is not needed anymore and usually becomes destroyed/disconnected. In case of a so-called "HTTP Upgrade request" (which can be thought of as: "hey, server! Please upgrade this to a WebSocket connection!"), the underlying TCP connection just goes on living, and the WebSocket communication goes through the very same TCP connection that was created initially (step (1) above).

This hopefully clarifies that the key difference between WebSocket and HTTP is a switch in a high-level protocol (from HTTP toward WebSocket) without changing the underlying transport channel (a TCP/IP connection).

Handling multiple IP connection attempts through the same socket, how?

This is a topic I was once struggling with myself and that many do not understand because it is a little non-intuitive. However, the concept actually is quite simple when one understands how the basic socket-related system calls provided by the operating system are working.

First, one needs to appreciate that an IP connection is uniquely defined by five pieces of information:

IP:PORT of Machine A and IP:PORT of Machine B and the protocol (TCP or UDP)

Now, socket objects are often thought to represent a connection. But that is not entirely true. They may represent different things: they can be active or passive. A socket object in passive/listen mode does something pretty special, and that is important to answer your question.

http://linux.die.net/man/2/listen says:

listen() marks the socket referred to by sockfd as a passive socket, that is, as a socket that will be used to accept incoming connection requests using accept(2).

That is, we can create a passive socket that listens for incoming connection requests. By definition, such a socket can never represent a connection. It just listens for connection requests.

Let's head over to accept() (http://linux.die.net/man/2/accept):

The accept() system call is used with connection-based socket types (SOCK_STREAM, SOCK_SEQPACKET). It extracts the first connection request on the queue of pending connections for the listening socket, sockfd, creates a new connected socket, and returns a new file descriptor referring to that socket. The newly created socket is not in the listening state. The original socket sockfd is unaffected by this call.

Let's digest this carefully, I think this now really answers your question.

accept() does not change the state of the passive socket created before. It returns an active (connected) socket (such a socket then represents the five pieces of information states above -- simple, right?).

Usually, this newly created active socket object is then handed off to another process or thread or just "entity" that takes care of the connection. After accept() has returned this connected socket object, accept() can be called again on the passive socket, and again and again -- something that is known as accept loop.

But calling accept() takes time, right? Can't it miss incoming connection requests? There is more essential information in the just-quoted help text: there is a queue of pending connection requests! It is handled automatically by the TCP/IP stack of your operating system.

That means that while accept() can only deal with incoming connection requests one-by-one, no incoming request will be missed even when they are incoming at a high rate or (quasi-)simultaneously. One could say that the behavior of accept() is rate-limiting the frequency of incoming connection requests your machine can handle. However, this is a fast system call and in practice, other limitations hit in first -- usually those related to handling all the connections that have been accepted so far.


The relatively straightforward thing you seem to be missing here is that each connection to a server (in particular to your HTTP server here) creates it's own socket and then runs on that socket. What happens on one socket is completely independent of what happens on any other socket that is currently connected. So, when one socket is switched to the webSocket protocol, that does not change what happens to other current or incoming socket connections. Those get to decide on their own how they will be processed.

So, open sockets can be using the webSocket protocol while other incoming connections can be regular HTTP requests or requests to create a new webSocket connection.

So, you can have this type of sequence:

  1. Client A connects to server on port 80 with HTTP request to initiate a webSocket connection. This process creates a socket between the two.
  2. Server responds yes, to the upgrade to webSocket request and both client and server switch the protocol for this socket only to the webSocket protocol.
  3. Client A and Server start exchanging packets using the webSocket protocol and continue to do so for the next several hours.
  4. Client B connects to the same server on port 80 with a regular HTTP request. This process creates a new socket between the two.
  5. Server sees the incoming request is a normal HTTP request and sends the response.
  6. When client B receives the response, the socket is closed.
  7. Client C connects to the same server on port 80 with an HTTP request to upgrade to a webSocket.
  8. Server responds yes, to the upgrade to webSocket request and both client and server switch the protocol for this socket only to the webSocket protocol.
  9. At this point, there are two open sockets using the webSocket protocol that can be communicating and the server is still accepting new connections that can be either regular HTTP requests or can be requests for an updrade to the webSocket protocol.

So, at all times, the Server is still accepting new connections on port 80 and those new connections can either be regular HTTP requests or can be HTTP requests that are a request to upgrade to the webSocket protocol (and thus start a webSocket connection). And, while all this is going on, webSocket connections that have already been established are communicating over their own socket using the webSocket protocol.

The webSocket connection and communication scheme was very carefully designed to have these charateristics:

  1. No new port was required. The incoming port (most commonly port 80) could be used for both regular HTTP requests and for webSocket communication.
  2. Because no new port was required, changes in firewalls or other networking infrastructure were "usually" not required. As it turns out this isn't always the case because some proxies or caches that are expecting HTTP traffic may have to be modified to handle (or avoid) the webSocket protocol traffic.
  3. The same server process could easily handle both HTTP requests and webSocket requests.
  4. HTTP cookies and/or other HTTP-based authentication means could be used during setup of a webSocket connection.

Answers to your further questions:

1) Why choose 80 as the default port? Does the designer want to make WebSocket communication look like normal HTTP communication from the transport level's perspective? (i.e. the server port is the good old 80).

Yes, see my points 1-4 immediately above. webSockets can be established over existing HTTP channels so they generally require no networking infrastructure changes. I'd add to those points that no new server or server process is required either as an existing HTTP server can simply have webSocket support added to it.

2) I am trying to picture how the protocol shift happens on server. I image there're different software modules for handling HTTP or WebSocket traffics. First server uses the HTTP module to handle the normal HTTP requests. When it finds an Upgrade request, it will switch to use WebSocket module.

Different server architectures will handle the division between webSocket packets and HTTP requests differently. In some, webSocket connections might even be forwarded off to a new process. In others, it may just be a different event handler in the same process that is registered for incoming packet traffic on the socket which is now been switched over to the webSocket protocol. This is entirely dependent upon the web server architecture and how it chooses to process webSocket traffic. A developer implementing the server end of a webSocket app will most likely select an existing webSocket implementation that is compatible with their particular web server architecture and then write code that works within that framework.

In my case, I selected the socket.io library that works with node.js (which is my server architecture). That library gives me an object that supports events for newly connecting webSockets and then a set of other events for reading incoming messages or sending outgoing messages. The details of the initial webSocket connection are all handled by the library and I don't have to worry about any of that. If I want to require authentication before the connection is established, the socket.io library has a way for me to plug that in. I can then receive messages from any client, send messages to any single client or broadcast info to all clients. I'm mostly using it for broadcast to keep some info in a web page "live" so that web page display is always current. Anytime the value changes on the server, I broadcast the new value to all connected clients.