Page 1 of 1

Client Access to Server Objects Using An IPC Channel Giving client processes access to server process objects using a .Net

#1 StCroixSkipper  Icon User is offline

  • D.I.C Head
  • member icon

Reputation: 10
  • View blog
  • Posts: 121
  • Joined: 23-December 08

Posted 26 March 2009 - 01:47 PM

I remember the early days of 'Component Object Model" or COM when you had to write the server object, then the client "proxy" and server "stub" all of which were reference counted so when a client "released" an object the server could clean up its memory. The COM server also needed a message pump. All sorts of complications. Things got better toward the end but .Net makes things simple.

An IpcChannel combines functions of IpcClientChannel and IpcServerChannel to make things really easy.

First we need to create a "Class Library" project, call it 'IpcObjects'. Then create a class,'ServerObject' within the project. Here is the code for ServerObject. It only has one function, 'DisplayHelloWorldToConsole()' which takes no arguments. 'DisplayHelloWorldToConsole()' simply writes "Hello World!" to the console. Notice that ServerObject inherits from MarshalByRefObject.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace IpcObjects
{
	public class ServerObject : MarshalByRefObject
	{
		public ServerObject ()
		{

		}

		public void DisplayHelloWorldToConsole()
		{
			Console.WriteLine("DisplayHelloWorldToConsole() has been called");
			Console.WriteLine("Hello World!");
		}

	}
}



Now we need a server project to host 'ServerObject'. So in the same solution, create a new Console Application and call it 'IpcSvrChannelProject'. The main function of IpcSvrChannelProject has to initialize the IpcChannel, register it which publishes the channel uri and then register 'ServerObject' as a well known type. Here is the code for IpcServer. Note you need to add a reference to System.Runtime.Remoting and the associated using statements. You'll also need a reference to the project 'IpcObjects'. Also note the Console.ReadKey() statement at the end of the main function. This is simply to keep the server process alive, otherwise, it would simply exit.

using System;
using System.Collections.Generic;
using System.Text;

using System.IO;
using System.Runtime;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels.Ipc;
using System.Security.Permissions;
using System.Runtime.Remoting.Channels;
using IpcObjects;

namespace IpcSvrChannelProject
{
	class Server
	{
		static void Main(string[] args)
		{
			InitializeIpcChannel();

			Console.WriteLine();
			// Wait for the user input.
			Console.WriteLine("Type any key to exit IPC Server.");
			Console.ReadKey();
			Console.WriteLine("The server is exiting.");
		}

		private static void InitializeIpcChannel()
		{
			Console.WriteLine("IPC Server\n");

			IpcChannel _ipcSvrChannel = new IpcChannel("localhost:9090");
			ChannelServices.RegisterChannel(_ipcSvrChannel, false);
			Console.WriteLine("IPC Channel Name: {0}", _ipcSvrChannel.ChannelName);
			Console.WriteLine("IPC Channel Priority: {0}.", _ipcSvrChannel.ChannelPriority);
			ChannelDataStore channelData = (ChannelDataStore)_ipcSvrChannel.ChannelData;
			foreach (string uri in channelData.ChannelUris)
			{
				Console.WriteLine("Channel URI is {0}.", uri);
			}
			Console.WriteLine();
			//
			// Expose an object for remote calls.
			//
			RemotingConfiguration.RegisterWellKnownServiceType(
				typeof(ServerObject), "ServerObject",
				System.Runtime.Remoting.WellKnownObjectMode.Singleton);
			//
			// Parse the channel's URI.
			//
			string[] urls = _ipcSvrChannel.GetUrlsForUri("ServerObject");
			if (urls.Length > 0)
			{
				string objectUrl = urls[0];
				string objectUri;
				string channelUri = _ipcSvrChannel.Parse(objectUrl, out objectUri);
				Console.WriteLine("The object URI is {0}.", objectUri);
				Console.WriteLine("The channel URI is {0}.", channelUri);
				Console.WriteLine("The object URL is {0}.", objectUrl);
			}
		}
	}
}



You should now be able to build the solution and create an instance of the server by right mouse clicking on the IpcSvrChannelProject and selecting 'Debut->Start new instance' in the Solution Explorer pane.

Now for the Client. In the Solution pane, right mouse click on the IpcObjects solution and select Add->New project and name the new project IpcClientChannelProject. The Client is access the ServerObject hosted by the server and simply ask it to DisplayHelloWorldToConsole(). If the Server is hosting this object it should write Hello World! to the server's console.

So, what does the Client have to do? It needs to create the IpcChannel and register its interest in the type of object, in this case 'ServerObject' and give the url where objects of this type can be found or hosted. Here is the code for the Client.

using System;
using System.Collections.Generic;
using System.Text;

using System.IO;
using System.Runtime;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels.Ipc;
using System.Security.Permissions;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Messaging;

using IpcObjects;

namespace IpcClientChannelProject
{
	class Client
	{
		[SecurityPermission(SecurityAction.Demand)]
		static void Main(string[] args)
		{
			Console.WriteLine("IPC Client\n");

			//
			// Create the ipc channel
			// 
			IpcChannel _ipcClientChannel = new IpcChannel();

			//
			// Register the channel
			//
			WellKnownClientTypeEntry remoteType = new WellKnownClientTypeEntry(
				typeof(ServerObject),
				"ipc://localhost:9090/ServerObject");
			RemotingConfiguration.RegisterWellKnownClientType(remoteType);
			//
			// create a message sink
			//
			string objectUri;
			IMessageSink msgSink = _ipcClientChannel.CreateMessageSink("ipc://localhost:9090/ServerObject", null, out objectUri);
			if (msgSink != null)
			{
				Console.WriteLine("Type of message sink is {0}", msgSink.ToString());
			}
			//
			// now create an instance of the remote object
			//
			ServerObject svrObject = new ServerObject();
			//
			// and call its method
			//
			svrObject.DisplayHelloWorldToConsole();

			Console.WriteLine();
			// Wait for the user input.
			Console.WriteLine("Type any key to exit IPC Client");
			Console.ReadKey();
		}
	}
}



You should be able to build the solution and create new instances of first IpcSvrChannelProject and then IpcClientChannelProject. I like to change the colors of the console windows so I can more easily recognize the client and server. Simply right mouse click on the window header and select properties. I change the color of the Screen Background and the color of the Screen Text.

So, what have we done, not much. But we have an object hosted in a server process that we can access from a client process. But all we have asked it to do is to execute its DisplayHelloWorldToConsole() function which takes no arguments and returns void.

What happens when we send it the string from the client to display?

Exit the client and server and add another function to ServerObject that looks like this:

	   public void DisplayClientMessage(string msg)
		{
			Console.WriteLine("DisplayClientMessage(string msg) has been called");
			Console.WriteLine(msg);
		}



And modify the Client code to call this new function by adding the following lines of code below the call to DisplayHelloWorldToConsole().

//
// Send the server object a string from the client to be displayed on the server's console
//
svrObject.DisplayClientMessage("Now is the time for all good men to come to the aid of their country");



Start a new instance of the server first and then the client and look at the output on the server's console.
Now the message that originates at the client is being marshaled across the process boundary from the client to the server and reconstituted at the server so it can display it on its console.

Let's try to create another type of object in a ServerObject function and return the object to this new object to the client.

Exit both the client and server.

Add the following class to ServerObject.cs.

	public class NewType : MarshalByRefObject
	{
		private DateTime _timestamp;

		public NewType()
		{
			_timestamp = DateTime.Now;
		}

		public void DisplayDataOnConsole()
		{
			Console.WriteLine("Object Type: {0}, timestamp: {1} {2}",
				this.GetType().ToString(), _timestamp.ToLongDateString(), _timestamp.ToLongTimeString());
		}
	}



Add the following function to ServerObject.

		public NewType CreateAndReturnAnObject()
		{
			NewType nt = new NewType();
			return nt;
		}



And add the following call below the call to DisplayClientMessage() in the Client's main function.

			//
			// ask svrObject for an object of type NewType
			//
			NewType nt = svrObject.CreateAndReturnAnObject();
			nt.DisplayDataOnConsole();



Now build the solution and start a new instance of the server, then start a new instance of the client. This new object of NewType writes its information to the server's Console window. It, too, is 'hosted' by the server.

Let's try to ask ServerObject to create a list of strings, fill it with some strings and return it to the client.

Exit both the client and server.

Add the following function to ServerObject.cs.

		public List<string> CreateAndReturnAList()
		{
			List<string> list = new List<string>();
			list.Add("one");
			list.Add("two");
			list.Add("three");
			return list;
		}



And add the following lines of code below the client's call to nt.DisplayDataOnConsole().

			//
			// ask svrObject to return a list of strings
			//
			List<string> list = svrObject.CreateAndReturnAList();
			foreach (string s in list)
			{
				Console.WriteLine(s);
			}



Rebuild the solution and start new instances of the server and then the client and see what happens.

IpcChannels are powerful mechanisms for creating servers that serve objects to multiple clients. I've used it for building a simple but powerful search engine and many other things. You can also use it to 'gain access to another 2gb address space' on 32 bit machines by splitting your app into a client and server.

There is one caveat. In the server's function InitializeIpcChannel() there is a statement:
			ChannelServices.RegisterChannel(_ipcSvrChannel, false);



The last argument, "false", is "ensureSecurity" and as you can see, it is false which nullifies the security. It means that no security check is performed. It also means that you cannot encrypt and decrypt the information passing across the process boundary. This is more significant if you are using a TCP channel and going across the net.

When I set this argument to "true" on my system I always get an Access Denied exception. And I have yet to find a .Net class that allows me to provide a string which represents the "Autorized Group" and set up a secure channel. I have code to do it but it takes "[DllImport]" statements for the advapi32.dll to get the information I need to set up a secure channel. Maybe that is a subject for another tutorial.

Is This A Good Question/Topic? 0
  • +

Page 1 of 1