I got a question the other day about what data goes on the wire when you Open(), Close(), or Abort() our various transport channels. Here are the details in a nutshell (all the comments about net.tcp apply to net.pipe, simply substitute “named pipe” for “socket”):
HTTP Request (Client) Channels
- Open() — Nothing goes on the wire for HTTP Request channels, as there is no context that they need to establish. HTTP channels use Open() to setup their identity contexts and Token Providers (if necessary).
- Abort() — Abort will immediately terminate any outstanding calls to channel.SendRequest(). This in turn aborts any underlying TCP sockets (by sending an RST packet).
- Close() — Close will wait (up to the given timeout) for any outstanding calls to channel.SendRequest() to complete. If they don’t complete within the given timeout then we will abort the requests a la httpChannel.Abort().
HTTP Reply (Server) Channels
- Open() — As a datagram channel, HTTP reply channels don’t perform any work at Open() time.
- Abort()/Close() — If the associated HTTP Channel Listener has been disposed, then channel.Abort will in turn call HttpRequestContext.Abort() on any RequestContexts that have not been replied to. This will cause http.sys to abort the underlying TCP socket. If the HTTP Channel Listener has not been disposed then channel.Abort() simply transfers ownership of these outstanding request contexts back to the channel listener. Close() actually performs in the same fashion, since calling requestContext.Close() in this case would have the unexpected side-effect of sending a 202 Accepted response.
Net.Tcp Duplex (Buffered) Channels
Net.Tcp duplex channels have a 1-1 relationship with their associated network resource, and as such there is a more direct correlation between the channel operations and their effect on network traffic.
- Open() — On the client, channel.Open() will first look for a socket in our connection pool (which reminds me I need to post about our connection pooling behavior). If none exists, then we will create a new client socket (by connecting to the service IP address+port number). Once we have a connected socket, we will perform a handshake using a custom .Net framing protocol. This is where we will validate that the requested endpoint both exists and uses the requested content-type. We will also perform any transport-security handshake here.
On the server, channel.Open() signals that the server side channel should (if required) respond to any transport-security handshake, and then ACK that the message framing connection has been successfully established.
- Abort() — Abort will immediately terminate the TCP socket associated with the channel (again by sending an RST packet).
- Close() — Close will send a framing level “FIN”-style packet, and then wait (up to the given timeout) for a framing level “FIN-ACK”. If the FIN-ACK is received before the timeout expires then we will return the socket into our connection pool. If the receive times out then we will abort the socket.
Net.Tcp Request-Reply (Streamed) Channels
Net.Tcp streaming messages have similar wire handshake requirements, however those manifestations on the wire are associated with individual request-reply operations rather than to channel operations. For the channel operations, Net.Tcp request-reply channels behave in the same manner as HTTP channels.
I haven’t mentioned anything about net.msmq or UDP channels in this post. This is because as datagram channels they don’t have any on-the-wire associations with channel.Open/Close/Abort.