This post is really just a collection of random notes I
made the other day when I was staring at the simplicity of the following Indigo
code;
[ServiceContract]
public class
Printer
{
[OperationContract]
public void Print(string s)
{
Console.WriteLine(s);
}
}
class Program
{
static void Main(string[] args)
{
ServiceHost<Printer> p = new ServiceHost<Printer>(
new Uri("net.tcp://localhost:9090"));
p.Open();
Console.ReadLine();
}
}
Along with this configuration file;
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<system.serviceModel>
<services>
<service serviceType="Printer">
<endpoint address="printer" bindingSectionName="netProfileTcpBinding"
contractType="Printer"/>
</service>
</services>
</system.serviceModel>
</configuration>
and wondering what's actually going on when we make that
call to ServiceHost<T>.Open() ?
Expect some errors and omissions
here – as I say, these are just some rough notes.
Somewhere at the bottom of the stack I know that I’ve got a
transport specific Listener picking up SOAP messages for me and somewhere
at the top of the stack I know that I’ve got my class Printer but what
happens in the middle to tie these things together?
I've had a play around with Reflector to see if I could
understand where things happen and how they’re connected I’ve got a little bit
of it but not all of it by any means right now. The stuff below is probably not
anything you’d ever need to know but it might be interesting.
Construction
The ServiceHost<T>
constructor chains up to the ServiceHost base class constructor which
sets up a number of members;
Base Addresses (UriSchemeKeyedCollection)
Close Manager
(CloseManager)
Listener Factories
(Dictionary<string, IListenerFactory>)
Endpoint Listeners
(EndpointListenerCollection)
Extensions -
(ExtensionCollection)
Instances -
(ServiceSite collection)
Throttle -
(ServiceThrottle)
And then the derived ServiceHost<T>
constructor does a bunch of additional work, namely;
ServiceLoader.LoadType(typeof(T)) uses a TypeLoader
and creates a new ServiceDescription containing Behaviours, Validators
and Endpoints.
A new set of fairly vanilla looking security settings
ServiceSecuritySettings get created.
ServiceLoader.InitializeDescription uses a ServiceReflector to build
up the contractual information that’s buried in the code. There’s a lot of work
done here and I haven’t explored it in any depth. The TypeLoader looks to
help out a lot with reflection-heavy methods like CreateContractDescription,
CreateOperationDescription, CreateMessageDescription.
The behaviors are also populated
here by TypeLoader.AddBehaviors which again reflects around looking for
IServiceBehavior attributes. Validators are also added with a bunch of
validators baked into the code - MessagePatternValidator,
InstancingValidator and so on. If a ServiceMetadataBehavior attribute
is not already present somewhere then we add one so if you’re looking to add
code to remove metadata generation then it looks like you should expect this
attribute to already be there in the list.
With the service description
initialized, it’s then extended/modified based upon the information in the
configuration file. A ConfigLoader seems to take care of this in
ConfigLoader.LoadServiceDescription which pulls information from the
configuration file for the service.
ConfigLoader.LookupService
is used to go from our service type (T) to a ServiceElement in the
configuration file based upon assembly qualified and full names.
That element’s then opened up
and all the endpoints within it are pulled out and for each one that endpoint
tuple of (address, binding, contract) gets created. Additional behaviors look to
be loaded from the config file if there’s any in there.
ServiceHost<T>.Open
This falls back into the base
class implementation which is CommunicationObject.Open and essentially
calls OnOpening(), OnOpen(), OnOpened(). A lot of things
are CommunicationObjects in Indigo so you quickly get used to seeing them
which is a nice part of the design.
Down in ServiceHost there’s a
chain of activity. The big methods that I wandered across would be;
OnInitialize
Performs some validation. Then
uses ServiceDescription.InitializeServiceHost which creates a
DispatcherBuilder and calls InitializeServiceHost on it with the
ServiceDescription and the ServiceHost.
This is an interesting method.
Essentially it does something like;
foreach endpoint in the service
description
Build an IListenerFactory
implementation. This wanders through a number of classes like Binding,
ChannelBuildContext but ultimately we get an implementation of
IChannelBuilder and calling BuildListenerFactory on that.
IChannelBuilder is implemented on all of the binding elements such as
HttpTransportBindingElement and TcpTransportBindingElement. So, if
you've configured an HttpTransportBinding then this framework will walk
up that binding's implementation of IChannelBuilder and ask it to make a
listener factory using IChannelBuilder.BuildListenerFactory and
that’s how listeners will get made.
Create a new
EndpointListener.
Use
DispatcherBuilder.BuildDispatcher to create a dispatcher from the
ServiceHost, ServiceDescription, ContractDescription. For each
operation on the endpoint BuildDispatchOperation builds a
DispatchOperation which looks to represent the mechanism used to ultimately
invoke a method with a message - particularly through
IOperationInvoker.Invoke.
Set the EndpointListener's
Dispatcher property to the newly created dispatcher. As part of the set
operation here the Dispatcher does an Attach to the EndpointListener
and one of the things that happens there is that the Dispatcher hooks
itself in to the Opened event on the EndpointListener.
OnBeginOpen
Loop through all the
ListenerFactories and Open() on them.
Look through all the
EndpointListeners and Open() on them.
Calling Open on an
EndpointListener causes the Opened event to fire which the attached
Dispatcher syncs up to. At the point where this happens (in the
Dispatcher.EndpointOpened event handler) the Dispatcher wires up a
couple of objects, a ListenerBinder and a ListenerHandler and
ListenerHandler.OnOpen runs and calls
ListenerHandler.NewChannelPump.
This makes use of a class called
IOThreadScheduler to manage threading - IOThreadScheduler looks at
first to be a reasonably simple work item queue with a routine which spins and
pulls items off that queue and executes them. I haven't quite understood where
this fits with respect to the .NET Thread Pool.
There’s a ChannelPump
method which looks like a classic message loop for accepting incoming channels;
while (connections coming in &&
throttling conditions allow accept)
Dispatch();
The Dispatch method looks
to do a bunch of behavioural work and ultimately ends up handing the new channel
to a ChannelHandler instance and calling Register on it.
In a very similar pattern to the
Dispatcher the ChannelHandler.Register method uses the
IOThreadScheduler to run the MessagePump method on another thread.
It looks like the common path
calls Receive which does either synchronous or asynchronous receiving
from the channel. Either way we messages end up in HandleRequest which
checks for throttling, dispatches the message through Dispatch and
continues pumping.
What does Dispatch do?
Firstly, it releases "ownership" on the pump, does some tracing and logging and
then gets hold of the ProxyBehavior on the channel and grabs the
DuplexCallbackDispatchBehavior, calls GetOperation on it to get a
DispatchOperationRuntime and then calls Dispatch on that passing it
an instance of MessageRpc that it's just created using the message, the
runtime, the channel, the host and the thread. The dispatcher then seems to call
MessageRpc.Process which is interesting.
MessageRpc.Process seems
to have a chain of delegates that it's willing to invoke in order to get the
message processed. Haven't quite worked this out yet but it sets up the first
delegate in the chain and invokes it and, presumably, the next delegate in the
chain can carry the chain on.
The implementation that I
wandered into lives in DispatchRuntime in methods ProcessMessage1,
2, 3 through to 6. These seem to correspond to different stages in a message
pipeline and you'd guess that they might get replaced by some kind of plug-in in
the future if they're not already. Stages 4,5 and 6 look to be the one that gets
an instance of the class providing the service through
ServiceSite.GetInstance and DispatchOperationRuntime.
Haven't quite figured this out
yet. But DispatchOperationRuntime.InvokeBegin calls into
SyncMethodInvoker.Invoke and, in my case, that drops into my operation's
implementation.
Posted
Mon, May 2 2005 10:57 AM
by
mtaulty