9.3 Windows Services

only for RuBoard

It is possible to write a server that runs within a console window in a few lines of code. However, it is neither realistic nor practical because there is no easy way of administering a server running in this environment. Using a Windows Service provides more of a real-world scenario, so considerable time will be spent developing one. The classes in the next few examples form the basis of a service that can be used to host all kinds of objects generically. The code is completely reusable and extensible.

To make building a Windows Service easier, Example 9-2 contains the listing for an abstract base class called ObjectServer that is specifically designed to build services that remote objects. Like all Windows Services, it inherits from System.ServiceProcess.ServiceBase . The class also provides both an HttpChannel and a TCPChannel object instance so objects can be remoted with each protocol.

Example 9-2. Windows Service base class for remotable objects
 'Windows Service Base Class for Remoting Objects '  'Compile: 'vbc /t:library /r:system.dll /r:System.Serviceprocess.dll  '/r:System.Runtime.Remoting.dll objectserver.vb  ' 'Install: 'installutil objectserver.exe ' 'Uninstall: 'installutil /u objectserver.exe     Imports System Imports System.Diagnostics Imports System.Runtime.Remoting Imports System.Runtime.Remoting.Channels Imports System.Runtime.Remoting.Channels.Http Imports System.Runtime.Remoting.Channels.Tcp Imports System.ServiceProcess     Namespace ObjectServerSpace       Public MustInherit Class ObjectServer : Inherits ServiceBase         Private name As String     Protected tcp As TcpChannel     Protected http As HttpChannel     Protected httpPort As Integer     Protected tcpPort As Integer         'This method will be used to register objects     Public MustOverride Sub RegisterObjects( )         'Configure server properties     Public Sub New(ByVal ServiceName As String)           'Save service name       Me.name = ServiceName           'Set default service parameters       ConfigureService( )         End Sub         Protected Overridable Sub ConfigureService( )       'Automatically log event on start, stop, pause, and continue       Me.AutoLog = False       'Set service name - must match name used in installer class       Me.ServiceName = name       'Service can be stopped       Me.CanStop = True       'Service can be paused and continued       Me.CanPauseAndContinue = False       'Notify when power status changes       Me.CanHandlePowerEvent = False       'Notify service on system shut down       Me.CanShutdown = False     End Sub         Protected Overrides Sub OnStart(ByVal args( ) As String)           'Register objects that will be served       RegisterObjects( )           'Default port       If (args.Length < 2) Then         tcpPort = 1969         httpPort = 1970       Else         tcpPort = Convert.ToInt32(args(0))         httpPort = Convert.ToInt32(args(1))       End If           'Create and register channel       tcp = New TcpChannel(tcpPort)       ChannelServices.RegisterChannel(tcp)       http = New HttpChannel(httpPort)       ChannelServices.RegisterChannel(http)           Dim log As EventLog = New EventLog( )       log.WriteEntry(ServiceName, "Service started")       Dim sLogEntry As String       sLogEntry = String.Format("TCP - {0} HTTP - {1}", tcpPort, httpPort)       log.WriteEntry(ServiceName, sLogEntry)         End Sub         Protected Overrides Sub OnStop( )       'Called when service is stopped       ChannelServices.UnregisterChannel(tcp)       ChannelServices.UnregisterChannel(http)     End Sub         'Protected Overrides Sub OnPause( )     '  CanPauseAndContinue must be True     'End Sub         'Protected Overrides Sub OnContinue( )     '  CanPauseAndContinue must be True     'End Sub         'Protected Overrides Function OnPowerEvent _     '    (ByVal ps As PowerBroadcastStatus) As Boolean     'CanHandlePowerEvent must be true     'End Function         'Protected Overrides Sub OnShutdown( )     ' CanShutdown must be True     'End Sub       End Class     End Namespace 

The constructor to ObjectServer takes the name of the service as it should appear in the SCM. In the constructor, a call is made to ConfigureService , which initializes the service. The method is overridable, so derived classes can change the behavior if necessary. Table 9-1 summarizes the various properties (inherited from ServiceBase ) that can be configured here.

Table 9-1. ServiceBase configuration properties

Property

Description

AutoLog

When True , a log entry is made every time the service is started, stopped, paused, or continued.

CanStop

The service can be stopped. The stop button is available in the SCM, and the OnStop method is called in the service object when it is clicked.

CanPauseAndContinue

The service can be paused or continued, causing the OnPause and OnContinue methods , respectively, to be called within the service object.

CanHandlePowerEvent

The service can handle power notification events through the OnPowerEvent method.

CanShutdown

The service is notified when the system shuts down.

ServiceName

The name of the service as it appears in the SCM.

The OnStart method registers a TcpChannel and an HttpChannel , as well as calls RegisterObjects , the method that eventually configures the remotable objects provided by the service. This method has no base implementation and must be overridden by any class that wishes to derive from it.

OnStart is called when the service is started from the SCM. If the service is configured to start automatically, it is called when the machine boots. The SCM provides a properties dialog that allows the start parameters to be defined. However, if no arguments are specified, the TcpChannel uses port 1969 and the HttpChannel defaults to 1970.

The OnStop method merely unregisters each channel, effectively prohibiting any objects from being remoted. If a client tries to attach to the service at this point, a SocketException is thrown.

Note that an object does not own a channel. A server can provide any number of channels that can remote an object. Two incoming requests could ask for the same object but use different channels, one for TCP and another for HTTP. The server handles these requests by using separate threads, so blocking is not an issue.

Example 9-2 should be compiled to a library called ObjectServer.dll , which allows the class to be more readily reusable.

9.3.1 Building the Windows Service

Example 9-3 contains the listing for the RemotingObjectService class that comprises the actual service. It also contains three other classes:

  • WindowsServiceInfo

  • RemotingObjectInstaller

  • Application

WindowsServiceInfo contains a public constant that defines the name of the Windows Servicein this case, "Remoting Object Server." This is done because the service name must be used in the three other classes, and if the name doesn't match, the service won't work.

Example 9-3. Remote Object Service
 'Object Server '  'Compile: 'vbc /t:exe /r:system.dll /r:System.Serviceprocess.dll  '/r:System.Configuration.Install.dll '/r:System.Runtime.Remoting.dll  '/r:ObjectServer.dll /r:serverinfo.dll remoteservice.vb  ' 'Install: 'installutil remoteservice.exe ' 'Uninstall: 'installutil /u remoteservice.exe     Imports System Imports System.ComponentModel Imports System.Configuration Imports System.Configuration.Install Imports System.Runtime.Remoting Imports System.ServiceProcess     Namespace ObjectServerSpace       Friend Class WindowsServiceInfo     Public Const Name As String = "Remoting Object Server"   End Class       Public Class RemotingObjectService : Inherits ObjectServer          Public Sub New( )       MyBase.New(WindowsServiceInfo.Name)     End Sub         'This method will be used to register objects     Public Overrides Sub RegisterObjects( )         'Implementation forthcoming         End Sub       End Class       <RunInstallerAttribute(True)> _   Public Class RemotingObjectInstaller : Inherits Installer     Public Sub New( )       Dim pi As New ServiceProcessInstaller( )       pi.Account = ServiceAccount.LocalSystem           Dim si As New ServiceInstaller( )       si.StartType = ServiceStartMode.Manual       si.ServiceName = WindowsServiceInfo.Name           Installers.Add(si)       Installers.Add(pi)     End Sub   End Class       Public Class Application     Public Shared Sub Main( )       ServiceBase.Run(New RemotingObjectService( ))     End Sub   End Class     End Namespace 
9.3.1.1 Windows Service installation

The RemotingObjectInstaller is a class that is used to install the service. Installer classes are derived from System.Configuration.Install.Installer , and, in terms of Windows Services, they define what account the service runs under (services run whether or not someone is logged in) and how the service should be started: disabled, manual, or automatic. The classes used in the installer write the appropriate entries into the registry to make the service available from the SCM.

To install the service from the command line, use installutil.exe like this:

 installutil remoteservice.exe To uninstall the service, use the /u switch: installutil /u remoteservice.exe 

The utility is not located with the .NET Framework SDK; rather, it is in the framework folder located within the System directory. The path is similar to this:

<%windir%> \Microsoft.NET\Framework\ <version>

It is definitely a good idea to add this path to the environment variables of any machine used for .NET development, since it contains several useful utilities.

When installutil.exe is run, it scans the assembly looking for the class that has a RunInstaller attribute applied (with a True value) and creates an instance of it.

The constructor of the installer class should contain everything needed to configure the service. In the case of Example 9-2, ServiceProcessInstaller specifies that the service will run under the local system account. If necessary, a specific user account can be used; there are properties for specifying users and passwords.

ServiceInstaller declares how the service is started (manually) and the name of the service. This property represents the minimal information that the installer class must supply to get the service up and running.

Finally, after the service is installed, it is loaded. The Application class contains the service startup code. This code is just a call to ServiceBase.Run , which takes an instance of the service class. The method is overloaded to take an array of ServiceBase , which provides the means for one executable to host several services.

9.3.1.2 Remote Object Service

There really isn't much to RemotingObjectService , the service class. Most of the work is done in the ObjectServer base class.

The first point of interest is the constructor. In ObjectServer , a default constructor was purposefully not supplied, which forces the derived RemotingObjectService class to call MyBase.New and pass the name of the service to the base class.

All that remains is the override of RegisterObjects . The remote objects provided by the server are configured in this method.

9.3.2 Registration

You can register well-known objects either programmatically or through the use of a configuration file. This section discusses both methods.

9.3.2.1 Programmatic registration

Example 9-4 contains the long-awaited implementation of RegisterObjects , the code for which can be incorporated into the source code from Example 9-3. This version of RegisterObjects uses the programmatic method for registration. After the application name is defined (think of this as a virtual directory under IIS), an instance of type WellKnownServiceTypeEntry is created for each object the service will expose.

Example 9-4. Registering well-known objects in code
 Public Overrides Sub RegisterObjects( )       RemotingConfiguration.ApplicationName = "RemotingTest"       Dim entry1 As New WellKnownServiceTypeEntry( _     GetType(ServerInfo), "ServerInfo", WellKnownObjectMode.Singleton)      RemotingConfiguration.RegisterWellKnownServiceType(entry1)       Dim entry2 As New WellKnownServiceTypeEntry( _     GetType(ServerInfo), "ServerInfo2", WellKnownObjectMode.SingleCall)          RemotingConfiguration.RegisterWellKnownServiceType(entry2)         End Sub 

The constructor of the WellKnownServiceTypeEntry class takes the type of service, the URI of the object, and the activation method (Singleton or SingleCall). Ultimately, two objects are exposed: a Singleton instance of ServerInfo and a SingleCall version. The machine name of the service, the application name, and the object URI form the URL where the objects are located. Because the service exposes both a TCPChannel and an HttpChannel , clients can refer to these objects in several ways:

 tcp://192.168.1.100/RemotingTest/ServerInfo tcp:// 192.168.1.100/RemotingTest/ServerInfo2 http:// 192.168.1.100/RemotingTest/ServerInfo http:// 192.168.1.100/RemotingTest/ServerInfo2 
9.3.2.2 Using configuration files

Using programmatic configuration is less than ideal. The server must be recompiled every time a new object is exposed. By using configuration files, you can implement RegisterObjects in a completely generic fashion. This implementation allows any number of objects (from one or more assemblies) to be exposed without coupling any specific type information to the service.

Example 9-5 contains a version of RegisterObjects that uses a configuration file for registration purposes. Normally, configuration files are kept in the same directory as the executable, which is the case here. However, when Windows Services run, their executing directory is <%windir%>/System32 . To load the configuration file correctly, the current application domain is used to derive a path to the file, which is called RemoteService.exe.config . For conventional reasons, configuration files have the same name as the executable, followed by .config .

Example 9-5. Final implementation of RegisterObjects
 Public Overrides Sub RegisterObjects( )   Dim ad As AppDomain = AppDomain.CurrentDomain( )   Dim path As String = _     String.Format("{0}\RemoteService.exe.config", ad.BaseDirectory)   RemotingConfiguration.Configure(path) End Sub 

This method of configuration has obvious advantagesthe primary one being that this version of RegisterObjects will never have to be rewritten again; everything is defined in an external file. This advantage makes the RemotingObjectService class much more reusable and extensible. To remote new objects, you only need to drop a new assembly in the services directory, add a <wellknown> entry for the new class in the configuration file, and restart the server.

The framework documentation contains the full schema for the remoting configuration file. To show it here would detract from the discussion at hand, because only a small portion of the schema pertains to server activated objects. Example 9-6 contains the configuration file, which is basically the XML version of Example 9-4. The type element contains the object's full type name, followed by the assembly name (minus the file extension).

One noticeable difference is the fact that the objectUri attribute contains the extension .rem (it can also be .soap ). Theoretically, this configuration file could be reused under IIS. These extensions are for the benefit of the HTTP remoting handlers used in this circumstance.

Example 9-6. RemoteService.exe.config
 <configuration>   <system.runtime.remoting>     <application name="RemotingTest">       <service>         <wellknown mode="Singleton"                      type="ObjectServerSpace.ServerInfo,ServerInfo"                    objectUri="ServerInfo.rem" />         <wellknown mode="SingleCall"                type="ObjectServerSpace.ServerInfo,ServerInfo"                objectUri="ServerInfo2.rem" />       </service>     </application>   </system.runtime.remoting> </configuration> 

Make sure the service, ServerInfo.dll , and the RemoteService.exe.config are in the same directory.

The server is complete, so start it up from the SCM. The only things that will ever need to be changed (in terms of this chapter) are the <wellknown> entries in the configuration file.

Make sure the server is configured correctly and the remote object is available. You can do this by using a browser. Just enter in the URL of the object (and don't forget to use the port for the registered HttpChannel ). Also, make sure ?wsdl (web service description language) is appended to the address, as in the following example:

 http://192.168.1.100:1970/RemotingTest/ServerInfo.rem?wsdl 

This should generate a SOAP message similar to the one shown in Figure 9-2.

Figure 9-2. SOAP definition of remote object
figs/oop_0902.gif
only for RuBoard


Object-Oriented Programming with Visual Basic. Net
Object-Oriented Programming with Visual Basic .NET
ISBN: 0596001460
EAN: 2147483647
Year: 2001
Pages: 112
Authors: J.P. Hamilton

Similar book on Amazon

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net