[en] Setting up an IPC client/server connection (solved)

Topics: Technical Support
Jul 23, 2013 at 6:47 AM
Hello guys,
You helped me out a while ago setting up duplex TCP comms based on the WhsiperChat example. This is working perfectly on one section of my solution - thanks.

I now need to connect two components which need to pass serialised objects in a similar way but on the same machine, using IPC (for performance).

Do you have any example code to get me started with this? And are there any traps I need to look out for - like starting the server side first before registering a client, and so on?

Any help would be gratefully received.

Thanks again,
Dave
Coordinator
Jul 23, 2013 at 9:29 PM
Edited Jul 25, 2013 at 10:46 AM
Hello Dave,

everything is basically just the same.
The only difference is that you use IpcBinaryClient/ServerProtocolSetup instead of TcpDuplexClient/ServerProtocolSetup.
Here is the code: https://gist.github.com/yallie/6065750

It's a server and a client in one application.
Double-click the executable to start server, double-click again to start client.

A few gotchas:
  • IPC port name must be a valid filename (IPC uses memory-mapped files)
  • Only one application may listen on each unique IPC port name
  • Server must start before a client, as you already mentioned
// Compile this code using: csc ipctest.cs /r:Zyan.Communication.dll
// First run — starts server.
// Second run — starts client.

using System;
using System.Linq;
using System.Text;
using Zyan.Communication;
using Zyan.Communication.Protocols.Ipc;

struct Program
{
  const string IpcPortName = "AnyValidFilename";

  const string ZyanHostName = "SampleService";

  static void Main()
  {
    try
    {
      RunServer();
    }
    catch // can't start two servers on the same port
    {
      RunClient();
    }
  }

// ------------------------------- Shared code --------

  public interface ISampleService
  {
    string GeneratePassword(int length);
  }

// ------------------------------- Client code --------

  static void RunClient()
  {
    var protocol = new IpcBinaryClientProtocolSetup();
    var url = protocol.FormatUrl(IpcPortName, ZyanHostName);

    using (var conn = new ZyanConnection(url, protocol))
    {
      Console.WriteLine("Connected to server.");
      var proxy = conn.CreateProxy<ISampleService>();

      while (true)
      {
        Console.Write("Enter password length (empty line to quit): ");
        var line = Console.ReadLine().Trim(' ', '\r', '\n', '.', '-');
        if (string.IsNullOrEmpty(line) || line.Any(c => !char.IsNumber(c)))
        {
          Console.WriteLine("Bye.");
          break;
        }

        var length = Convert.ToInt32(line);
        var password = proxy.GeneratePassword(length);

        Console.WriteLine("Server generated password: {0}", password);
      }
    }
  }

// ------------------------------- Server code --------

  static void RunServer()
  {
    var protocol = new IpcBinaryServerProtocolSetup(IpcPortName);

    using (var host = new ZyanComponentHost(ZyanHostName, protocol))
    {
      host.RegisterComponent<ISampleService, SampleService>();
      Console.WriteLine("Server started. Press ENTER to quit.");
      Console.ReadLine();
    }
  }

  internal class SampleService : ISampleService
  {
    public string GeneratePassword(int length)
    {
      var sb = new StringBuilder();
      var rnd = new Random();
      for (var i = 0; i < length; i++)
      {
        var @char = (char)('a' + rnd.Next('z' - 'a'));
        sb.Append(@char);
      }

      Console.WriteLine("Generated password: {0}, length = {1}",
        sb, length);

      return sb.ToString();
    }
  }
}
Jul 25, 2013 at 7:20 AM
Thanks Yallie - I'm almost there with this. Looking good.

Just got to deal with "Method not found:" exception when calling a function on the proxy at the client. Intellisense shows the function on there but the call fails.
I'll persevere checking everything, but if you have any quick ideas...?

Cheers for now.
Jul 25, 2013 at 9:49 AM
More info for you:
I have 2 Functions and one Sub (I'm using vb.net) on the proxy/interface. The functions return 1) a Boolean and 2) a serializable object. They take no parameters and work fine.
The Sub takes one of the serializable objects as a parameter and it is only this one that fails with 'method not found'.

InvokeRemoteMethod in ZyanProxyc.cs calls:
returnValue = _remoteDispatcher.Invoke(trackingID, _uniqueName, correlationSet, methodCallMessage.MethodName, genericArgs, paramTypes, checkedArgs);

...with...
trackingID = {408aeab1-b7d6-45ee-8ba6-81a9e15fae96}
_uniqueName = "SSRemoting.IEngineLink"
correlationSet = (Count = 0)
methodCallMessage.MethodName = "sendEngineCommand"
genericArgs = null
paramTypes = element [0] is "SSRemoting.CommsInfoEx" - correct but expanding paramTypes[0] in a watch window looks to contain some errors relating to - {"Method may only be called on a Type for which Type.IsGenericParameter is true."} - relevant ??
checkedArgs = element [0] is a correctly formatted object of the previous type

return InnerSink.SyncProcessMessage(msg);in MessageSinkWrapper.cs then throws the exception.

I'm a bit stuck now - but I think this is something really simple that I'm overlooking.
Coordinator
Jul 25, 2013 at 10:55 AM
Edited Jul 25, 2013 at 10:56 AM
Hi Dave,

looks like your component class doesn't match the interface.
Make sure your class implements the interface:
Public Interface IEngineLink
    Sub SendEngineCommand ...
End Interface

Public Class EngineLink
    Implements IEngineLink `<--- this line is important!
    ...
    Public Sub SendEngineCommand
      ...
    End Sub
End Class
Also, I think that the problem may be related to case sensitivity.
Visual Basic is not case sensitive, but C# and Zyan are case-sensitive.
In your debug session I see camel-cased method name, which is not common in .NET code:
MethodName = "sendEngineCommand" 
Make sure that this method is named exactly the same in your interface and component class
so it's either sendEngineCommand or SendEngineCommand in both of them.

Let me know if it helps.
yallie
Jul 25, 2013 at 12:18 PM

Damn it !! What an idiot!

I’d never realised that the function/sub names in the implementation had to be spot on with the function/sub names in the interface…

So I was using : Public Sub execEngineCommand(commsPacket As CommsInfoEx) Implements IEngineLink.sendEngineCommand

Instead of: Public Sub sendEngineCommand(commsPacket As CommsInfoEx) Implements IEngineLink.sendEngineCommand

Thanks for your help yallie – I’m off and running again now.

Dave

Coordinator
Jul 25, 2013 at 1:18 PM
Edited Jul 25, 2013 at 1:29 PM
Ah, that's very interesting.

Your implementation explicitly renames interface methods, we've never dealt with that before.
I think that it's possible for Zyan to remap methods using implementation class metadata.
I'll investigate this issue as soon as I have a bit of free time.

Thanks Dave for reporting!

UPD. Issue: https://zyan.codeplex.com/workitem/2088
Coordinator
May 28, 2014 at 11:08 PM
This bug has been solved in the last checkin.
The fix will be included in the upcoming release 2.6.