可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
According to here:
The HTTP Upgrade header requests that the server switch the
application-layer protocol from HTTP to the WebSocket protocol.
The client handshake established a HTTP-on-TCP connection between IE10
and server. After the server returns its 101 response, the
application-layer protocol switches from HTTP to WebSockets which uses
the previously established TCP connection.
HTTP is completely out of the picture at this point. Using the
lightweight WebSocket wire protocol, messages can now be sent or
received by either endpoint at any time.
So, my understanding is, after the 1st client finished handshake with the server, the server's 80 port will be monopolized by the WebSocket protocol. And the HTTP is no longer working on 80 port.
So how could the 2nd client exchange handshake with the server. After all the WebSocket handshake is in HTTP format.
ADD 1
Thanks for all the answers so far. They are really helpful.
Now I understand that the same server's 80 port is shared by multiple TCP
connections. And this sharing is totally OK because TCP
connections are identified by a 5-element tuple as Jan-Philip Gehrcke
pointed out.
I'd like to add a few thoughts.
Both WebSocket
and HTTP
are merely application level protocols. Usually they both rely on the TCP
protocol as their transport.
Why choose port 80?
WebSocket design intentionally choose server's port 80 for both handshake and following communication. I think the designer wants to make WebSocket communication look like normal HTTP communication from the transport level's perspective (i.e. the server port number is still 80). But according to jfriend00
's answer, this trick doesn't always fool the network infrastructures.
How does the protocol shift from HTTP to WebSocket happen?
From RFC 6455 - WebSocket protocol
Basically it is intended to be as close to just exposing raw TCP to
script as possible given the constraints of the Web. It’s also
designed in such a way that its servers can share a port with HTTP
servers, by having its handshake be a valid HTTP Upgrade request. One
could conceptually use other protocols to establish client-server
messaging, but the intent of WebSockets is to provide a relatively
simple protocol that can coexist with HTTP and deployed HTTP
infrastructure (such as proxies) and that is as close to TCP as is
safe for use with such infrastructure given security considerations,
with targeted additions to simplify usage and keep simple things
simple (such as the addition of message semantics).
So I think I am wrong on the following statement:
The handshake request mimic HTTP request but the communication that
follows don't. The handshake request arrives at the server on port 80.
Because it's 80 port, server will treat it with HTTP protocol. And that's why the WebSocket handshake request must be in HTTP format.
If so, I think the HTTP protocol MUST be modified/extended to
recognize those WebSocket-specific things. Otherwise it won't realize
it should yield to WebSocket protocol.
I think it should be understood like this:
WebSocket communication starts with a valid HTTP request from
client to server. So it is the server that follows the HTTP protocol
to parse the handshake request and identify the begging for
protocol change. And it is the server that switches the protocol. So
HTTP protocol doesn't need to change. HTTP protocol doesn't even need
to know about WebSocket.
WebSocket and Comet
So WebSocket is different from Comet technologies in that WebSoket doesn't limit itself within the current HTTP realm to solve the bi-directional communication issue.
ADD 2
A related question: How does a browser establish connection with a web server on 80 port? Details?
回答1:
The other answers are already helpful. I want to point out that your question is a very good one, and want to answer it from the point of view involving listen()
and accept()
. The behavior of these two system calls should be sufficient to answer your question.
You are interested in 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 and that is sufficient to answer your question. Still, you deserve an answer of how WebSocket relates to TCP (I have tried to elaborate on that a bit more here): sending an HTTP request requires an established TCP/IP connection between two parties. In case of a simple web browser / web server scenario
- first, a TCP connection is established between both (initiated by the client)
- then an HTTP request is sent through that TCP connection (from the client to the server)
- 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 an HTTP Upgrade request, 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).
As you can see, the only difference between WebSocket and standard HTTP is a switch in a high-level protocol (from HTTP to 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. However, the concept actually is very 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 very 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).
So, 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.
That is all we need to know in order to answer 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.
回答2:
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:
- Client A connects to server on port 80 with HTTP request to initiate a webSocket connection. This process creates a socket between the two.
- 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.
- Client A and Server start exchanging packets using the webSocket protocol and continue to do so for the next several hours.
- Client B connects to the same server on port 80 with a regular HTTP request. This process creates a new socket between the two.
- Server sees the incoming request is a normal HTTP request and sends the response.
- When client B receives the response, the socket is closed.
- Client C connects to the same server on port 80 with an HTTP request to upgrade to a webSocket.
- 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.
- 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:
- No new port was required. The incoming port (most commonly port 80) could be used for both regular HTTP requests and for webSocket communication.
- 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.
- The same server process could easily handle both HTTP requests and webSocket requests.
- 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.
回答3:
To answer your question: simultaneous Websocket and HTTP connections to port 80 are handled...
Exactly the same way simultaneous HTTP connections to port 80 are handled!
That means: Upon satisfactory TCP handshake, the service listening on serviceip:80 proceeds to spawn a new process or thread and handover all the communications for that connection to it (or just serves the request by executing the callback associated with that event as the asynchronous nodejs does, as jfriend00 correctly pointed out).
Then waits or handles the next incoming request in queue.
If you want to know what part HTTP 1.1 and the UPGRADE request play on all of this, this MSDN article on it leaves it very clear:
The WebSocket Protocol has two parts: a handshake to establish the
upgraded connection, then the actual data transfer. First, a client
requests a websocket connection by using the "Upgrade: websocket" and
"Connection: Upgrade" headers, along with a few protocol-specific
headers to establish the version being used and set-up a handshake.
The server, if it supports the protocol, replies with the same
"Upgrade: websocket" and "Connection: Upgrade" headers and completes
the handshake. Once the handshake is completed successfully, data
transfer begins.
Only Websocket services are usually not built into the web server so not really intended to listen in port 80, just accessible through it thanks to the web server's transparent forwarding. Apache Web server does that using mod_proxy_wstunnel.
Of course you can also have a web server with a built in web sockets implementation: Apache Tomcat, for example.
The main thing here is: Websocket protocol is not HTTP. It serves a different purpose. It is an independent application-layer communication protocol also built on top of TCP (although TCP isn't necessary the requirement, but a transport layer protocol that fits the requirements of the Websockets application layer protocol).
A Websocket service is a PARALLEL service running along with a web server service.
It uses the Websocket protocol, for which modern web browsers have support, implementing the client part of the interface.
You set up or build a Websocket service in order to establish persistent, non-HTTP connections between Websocket clients (usually web browsers) and that service.
The main advantage is: The Websocket service can SEND a message to the client whenever it needs to ("one of you buddies has connected!" "your team just scored a goal!"), instead of having to wait for the client's explicit REQUEST for an update.
You can establish a persistent connection using HTTP 1.1, but HTTP is not meant for anything other than serving a set of resources UPON REQUEST and then close the connection.
Up until recently, before Websockets' support was available in all major browsers, you had only two choices for implementing real time updates on a web application:
Implementing AJAX long polling requests, which is a painful and inefficient process.
Using / building a browser plugin (for example a Java applet support plugin) in order to be able to establish a non-HTTP connection with you updates service, which is more efficient but even more painful than long polling.
In terms of the service's shared listening port (which can be any TCP port, and it doesn't even have to be open to the internet, since most web servers support transparent forwarding of web socket connections), it works exactly as any other TCP service: the service just listens on it and when the TCP handshake is over a TCP socket exists for the service to communicate with the client.
As usual, all connections to a service listening on a particular TCP socket (server_ip:service_TCP_port) will be differentiated when assigned a unique client_ip:client_TCP_port pair, the client_TCP_port being randomly chosen by the client amongst its available TCP ports).
If you are still in doubt about the HTTP->Websocket application protocol handover happening upon Websocket connection handshake and how it doesn't have anything to do with the underlying TCP connection, I refer you to Jan-Philip Gehrcke's answer, which is very clear an instructive and probably what you were actually looking for.
回答4:
It's quite a simple concept, so let me try to describe it in simple terms in case some other lost soul wants to grasp it without having to read all those long explanations.
Multiple connections
The web server starts listening for connections. This happens:
- The main process of the web server opens a passive socket in state
listen
on port 80
, e.g. 9.9.9.9:80
(9.9.9.9
is the server IP and 80
is the port).
The browser makes a request to port 80
on the server. This happens:
The Operating System (in short OS) allocates a random outbound port on the client, say: 1.1.1.1:6747
(1.1.1.1
is the client IP and 6747
is the random port).
The OS sends a data packet with the source address 1.1.1.1:6747
and destination address 9.9.9.9:80
. It goes through various routers and switches and arrives at the destination server.
The server receives the packet. This happens:
The server OS sees that the packet's destination address is one of its own IP addresses and based on the destination port passes it on to the application associated with port 80
.
The main process of the web server accepts the connection creating a new active socket. Then it usually forks a new child process, which takes over the active socket. The passive socket stays open to accept new incoming connections.
Now every packet send from the server to the client will have those addresses:
- source:
9.9.9.9:1553
; destination: 1.1.1.1:80
And every packet send from the client to the server will have those addresses:
- source:
1.1.1.1:80
; destination: 9.9.9.9:1553
HTTP -> WebSocket handshake
HTTP is a text-based protocol. See the HTTP wiki for the list of available commands. The browser sends one of those commands and the web server responds accordingly.
WebSocket is not based on HTTP. It's a binary protocol where multiple streams of messages can be send in both directions at the same time (full-duplex mode). Because of that it would not be possible to establish a WebSocket connection directly without introducing a new HTTP standard, like for example HTTP/2. But that would be only possible if:
WebSocket supported HTTP verbs/requests
There was a new dedicated port different than 80
for WebSocket-specific communication.
The first wasn't in scope for the protocol and the second would break the existing web infrastructure. Because a client/browser can establish multiple HTTP connections with the same server, switching some of them from HTTP to WebSocket is the best of both worlds - keep the same port 80
but allow a different protocol than HTTP. The switch happens through protocol handshake initiated by the client.