Introduction
The Server Side published a beta version of "Improving .NET Application Performance and Scalability" by Microsoft Pattern and Practice Group. In chapter 8 about remoting, Windows Service Host for remoting is labeled not optimized since it uses Workstation GC (Garbage Collection). Also according to Gregor Noriskin in this article: "The Server GC is optimized for throughput and multi-processor scalability" and "If you are building a server application that is going to run on multiprocessor machines then it is highly recommended that you use the Server GC".
This article intends to implement a TCP Remoting Host with Server GC. The source code is included for developers in this community to comment and improve. I will try to explain step by step how this host is built, and hopefully generate some interest in writing a truly scalable TCP Remoting Host.
This article primarily targets developers with C# and .NET Framework experience (Remoting). The usage of ATL is at beginner level and is for the sole purpose of creating CLR host.
How to specify Server GC
Server GC has to be specified before AppDomain gets loaded and user code gets executed. So we will have to use unmanaged code. The following is the API from MSDN documentation to do just that:
HRESULT hr =
CorBindToRuntimeEx(pszVer, pszFlavor, Flags, CLSID_CorRuntimeHost,
IID_ICorRuntimeHost, (void **)&pHost);
According to this article, pszFlavor
= "svr
" for CLR Server Build and "wks
" for CLR workstation build, and CLR Server build is designed to use multiple processors and is more scalable. As for Flags
, STARTUP_CONCURRENT_GC
stands for Concurrent GC, and everything else stands for non-concurrent GC. Non-concurrent GC does collection on the same thread as user code, and the server application would be less responsive to a particular request to execute user code, but overall GC Collection performs better. Therefore, for TCP Remoting host that runs as Server Application, I chose to use the API as follows:
LPWSTR pszVer = L"v1.1.4322";
LPWSTR pszFlavor = L"svr";
ICorRuntimeHost *pHost = NULL;
HRESULT hr = CorBindToRuntimeEx(pszVer, pszFlavor,
STARTUP_LOADER_OPTIMIZATION_SINGLE_DOMAIN, CLSID_CorRuntimeHost,
IID_ICorRuntimeHost, (void **)&pHost);
Clearly, I have defined Server GC as "CLR Server Build with non-concurrent GC" and I believe Server GC has better scalability and can utilize multiple CPU better.
How to get to Default AppDomain:
After creating CLR Host as above, we need to access its default AppDomain to load our Remoting host code. In fact, all managed code must reside in an AppDomain, and Default AppDomain for the CLR Host is the easiest choice:
pHost->Start();
mscorlib::_AppDomain *pDefaultDomain = NULL;
IUnknown *pAppDomainPunk = NULL;
hr = pHost->GetDefaultDomain(&pAppDomainPunk);
hr = pAppDomainPunk->QueryInterface(__uuidof(mscorlib::_AppDomain),
(void**) &pDefaultDomain);
Notice that pDefaultDomain
is a COM Pointer type from mscorlib.tlb and it allows us to get access to the Default AppDomain from the unmanaged CLR Host. Next, we transit into managed code and load our managed remoting hosting code into this Default AppDomain as follows:
JQD::RemoteObjectLoader *pRemotingHost=NULL;
mscorlib::_ObjectHandle *pObjHandle = NULL;
hr = pDefaultDomain->CreateInstance(_bstr_t("RemoteObjectLoader"),
_bstr_t("JQD.RemoteObjectLoader"),
&pObjHandle);
VARIANT v;
VariantInit(&v);
hr = pObjHandle->Unwrap(&v);
hr =v.pdispVal->QueryInterface(
__uuidof(RemoteObjectLoader::_RemoteObjectLoader),
(void**) &pRemotingHost);
pRemotingHost->Load();
Notice that pointer to default AppDomain creates an instance of a managed object with a unmanaged handle, which can be unwrapped and cast into a managed pointer pRemotingHost
. This pointer could execute the following C# code:
public class RemoteObjectLoader
{
public void Load()
{
ChannelServices.RegisterChannel(new TCPChannel(10002));
RemotingConfiguration.RegisterWellKnownServiceType(
typeof (JQD.MyClass),
"MyClassURI", WellKnownObjectMode.SingleCall);
}
}
This completes the starting of TCP Remoting using Server GC. As illustrated in the following diagram, this process utilizes both managed and unmanaged code to build the CLR host and eventually load Remoting Hosting code
Finally, I have hard coded Remote classes in Load()
for illustration. In real world applications, you would use RemotingConfiguration.Configure
to expose your objects, and that is what I did in the sample download.
How to run the sample code:
After downloading the zip file, extract it to a directory such as c:\working\CLRMHost. You need to do the following few things:
- Open the solution (clrmhost.sln) and compile the release build.
- Copy MyClass.dll from MyClass\Bin\Release to CLRMHost\release.
- In a Command prompt, change to CLRMHost\Release and type "CLRMHost.exe -i" (this will install the service).
- If you wish to uninstall the service, run CLRMHost\uninstall.vbs.
- Open Services MMC and start the service "TCPRemotingHostServerGC".
- Type http://localhost:10000/MyClassTCPURI?wsdl in IE Browser and you should see WSDL document as a test. You can then write a TCP Remoting client against port 10003 to test TCP Channel.
Some important details
This .NET Solution consists mainly of two projects: a C++.NET Windows Service Project and a Remoting Loader .NET Class Library Project. All unmanaged C++ code resides in the Windows Service Project, which has included important header file mscoree.h for accessing CorBindToRuntimeEx
, etc., and atlbase.h for accessing ATL. Furthermore, mscorlib.tlb and RemoteObjectLoader.tlb are imported for transition from unmanaged process to managed code. RemoteObjectLoader.tlb can be generated by command "tlbexp". Note that there is a bug where Config file for Windows Service exe does not get copied to release or debug directory upon compilation. So I have utilized BaseDirectory
property for AppDomain to set Remoting Configuration file path instead. Finally, installUtil.exe cannot be used to install this particular Windows Service since that service contains unverifiable C++ unmanaged code. Fortunately, C++ IDE generated code to do installation as "CLRMHost.exe -i". Still to uninstall, you have to run the included VB Script file uninstall.vbs. This script uses WMI to delete the Windows Service "TCPRemotingHostServerGC".
Conclusion
This article provides a reasonably complete implementation for TCP Remoting host using Windows Service and Server GC. Anyone can just download it and add your own object for TCP remoting. Although it does not have proven scalability like IIS hosted HTTP Remoting, overtime developers in this community should be able to get a concrete and real understanding of TCP remoting host, and improving its scalability and throughput.