Can a process be both a Client and Server?

duckjonesduckjones Member Dennis PelhamOrganization: PersonalProject: Home Automation controller
Hello,
I'm new to Ice and trying to stumble my way through creating some two way communications between two processes. The solution I'm trying to create has a "server" and multiple "clients", but in terms of how I'm trying to use Ice, I'd both processes to operate as both Ice servers and clients, thereby allowing me to send data back and forth freely instead of having the clients have to constantly poll the server for new messages. Unfortunately I've come up against an issue I can't figure out, the answer is probably in the documentation somewhere, but I haven't come across it yet.

I initialize both the Client and Server programs with the Ice runtime, create the ObjectAdapters and add Ice Objects to them. The Client process creates a proxy for the Server and is able to call in just fine, passing in the address to the Server program to respond to. On the Server, I use the return address to create a proxy to call the Client, but when I attempt to do a checked cast to the appropriate type, the program hangs (I traced it to the "ice_isA" call). I assumed the Client was blocking until it's call was completed, so I tried using an async call which failed. I tried passing a proxy that was instantiated by the Client to the Server during my initial call, then using that proxy on the Server to call the Client, but that failed with the same result. Am I attempting something that shouldn't be done? Or should I be using Icebox? Below is the code from my test project. I'm using Ice 3.4.1 and Visual Studio 2010 on a Windows 7 machine.

Thanks very much for any advice, I'm really enjoying using Ice so far!
module TestModule
{
	sequence<string> TestStringSequence;

	interface TestDataType
	{
		void setName(string name);
		string getName(); 
	};

	interface StringCollectionDataType extends TestDataType
	{
		void setStrings(TestStringSequence testData);
		idempotent TestStringSequence getStrings();
	};

	struct TestStructure
	{
		string testString;
		TestDataType* rawData;
	};

	interface TwoWayCommunication
	{
		void SendMessage(TestStructure message);
	};
};

Server Implementation:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace TestServerProgram
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            try
            {
                m_Communicator = Ice.Util.initialize();
                m_Adapter = m_Communicator.createObjectAdapterWithEndpoints("TestServerAdapter", "default -p 9080");

                Ice.Object obj = new StringCollectionDataTypeI();
                m_Adapter.add(obj, m_Communicator.stringToIdentity("TestStringData"));

                TwoWayCommunicationI twoWayObj = new TwoWayCommunicationI();
                twoWayObj.MessageReceived += new EventHandler(Form1_MessageReceived);
                m_Adapter.add(twoWayObj, m_Communicator.stringToIdentity("ServerTwoWay"));

                m_Adapter.activate();
            }
            catch(Exception ex)
            {
                throw ex;
            }
        }

        private Ice.Communicator m_Communicator;
        private Ice.ObjectAdapter m_Adapter;

        void Form1_MessageReceived(object sender, EventArgs e)
        {
            TestModule.TestDataTypePrx rawProxy = ((TestModule.TestStructure)sender).rawData;
            TestModule.StringCollectionDataTypePrx stringProxy = TestModule.StringCollectionDataTypePrxHelper.checkedCast(rawProxy);
            string[] stringData = stringProxy.getStrings();
                
            this.Invoke(new SetMessageDelegate(SetMessage), stringData[0]);
            stringProxy.setStrings(stringData.Reverse().ToArray());

            Ice.ObjectPrx objPrx = m_Communicator.stringToProxy(((TestModule.TestStructure)sender).returnAddress);
            TestModule.TwoWayCommunicationPrx twoWayProxy = TestModule.TwoWayCommunicationPrxHelper.checkedCast(objPrx); // Process hangs here

            twoWayProxy.SendMessage(new TestModule.TestStructure("Test Complete!", stringProxy));
        }

        delegate void SetMessageDelegate(string message);
        void SetMessage(string message)
        {
            label1.Text = message;
        }

        private void Form1_Closing(object sender, EventArgs e)
        {
            if(m_Communicator != null)
            {
                m_Communicator.destroy();
            }
        }
    }
}

Client Implementation
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace TestClientProgram
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            try
            {
                m_Communicator = Ice.Util.initialize();
                m_Adapter = m_Communicator.createObjectAdapterWithEndpoints("TestClientAdapter", "default -p 9090");

                Ice.Object obj = new TwoWayCommunicationI();
                ((TwoWayCommunicationI)obj).MessageReceived += new EventHandler(Form1_MessageReceived);
                Ice.ObjectPrx objPrx = m_Adapter.add(obj, m_Communicator.stringToIdentity("ClientTwoWay"));
            }
            catch(Exception ex)
            {
                // Log
                throw ex;
            }
        }

        private Ice.Communicator m_Communicator;
        private Ice.ObjectAdapter m_Adapter;

        private void Form1_Load(object sender, EventArgs e)
        {
            Ice.ObjectPrx obj = m_Communicator.stringToProxy("ServerTwoWay:default -p 9080");
            TestModule.TwoWayCommunicationPrx twoWayProxy = TestModule.TwoWayCommunicationPrxHelper.checkedCast(obj);

            obj = m_Communicator.stringToProxy("TestStringData:default -p 9080");
            TestModule.StringCollectionDataTypePrx stringProxy = TestModule.StringCollectionDataTypePrxHelper.checkedCast(obj);

            stringProxy.setName("StringCollectionDataType Test");
            stringProxy.setStrings(new string[] { "test1", "test2", "test3" });

            twoWayProxy.begin_SendMessage(new TestModule.TestStructure("ClientTwoWay:default -p 9090", stringProxy));
        }

        void Form1_MessageReceived(object sender, EventArgs e)
        {
            TestModule.TestDataTypePrx rawProxy = ((TestModule.TestStructure)sender).rawData;
            TestModule.StringCollectionDataTypePrx stringProxy = TestModule.StringCollectionDataTypePrxHelper.checkedCast(rawProxy);
            string[] stringData = stringProxy.getStrings();
            this.Invoke(new SetMessageDelegate(SetMessage), stringData[0]);
        }

        delegate void SetMessageDelegate(string message);
        void SetMessage(string message)
        {
            label1.Text = message;
        }

        private void Form1_Closing(object sender, EventArgs e)
        {
            if(m_Communicator != null)
            {
                m_Communicator.destroy();
            }
        }
    }
}

Comments

  • matthewmatthew NL, CanadaMember Matthew NewhookOrganization: ZeroC, Inc.Project: Internet Communications Engine ✭✭✭
    It is very common for applications to be both clients & servers. What you are doing is known in Ice parlance as a nested invocation. See 32.10.5 for details in the Ice manual The Ice Threading Model. For concrete example look at demo/Ice/nested in the Ice demos.

    In general use you also may not encounter this issue as your server will be pushing the events to the client upon receiving events, not upon invocations from the client itself and therefore the calls will not be nested. That is your invocation stream will be more akin to:

    event generator -> server -> client

    and not

    client -> server -> client

    which is where nested invocations come into play.

    Since you are also building a GUI application I'd caution you that the invocations from the server to the client will by default arrive in a thread other than main. You should ensure that your application is suitably protected when dealing with this situation.
  • duckjonesduckjones Member Dennis PelhamOrganization: PersonalProject: Home Automation controller
    Fantastic! That's precisely the info I was looking for. Looks like there were a few things I tried that came close to avoiding nested invocations, but not quite. This should get me going on the right path. Much appreciated!

    Dennis
  • duckjonesduckjones Member Dennis PelhamOrganization: PersonalProject: Home Automation controller
    I think I must be doing something wrong. I'm still not able to get around my deadlock issue. I tried setting up an event driven model by placing a button on the GUI for my Server program and using its Click event to place a call to the Client program. When the Client program sends its first message to the Server, I store its returnAddress field in a member on the Server, then use it to create a proxy during the button's Click event handler.
    private void button1_Click(object sender, EventArgs e)
            {
                Ice.ObjectPrx objPrx = m_Communicator.stringToProxy("TestStringData:default -p 9080");
                TestModule.StringCollectionDataTypePrx stringProxy = TestModule.StringCollectionDataTypePrxHelper.checkedCast(objPrx);
    
                stringProxy.setName("StringCollectionDataType Test");
                stringProxy.setStrings(new string[] { "test1", "test2", "test3" });
    
                objPrx = m_Communicator.stringToProxy(m_ReturnAddress);
                TestModule.TwoWayCommunicationPrx clientProxy = TestModule.TwoWayCommunicationPrxHelper.checkedCast(objPrx);
                clientProxy.SendMessage(new TestModule.TestStructure("Test Success!", stringProxy)); 
            }
    

    I've verified on the Client program that the thread resumes after making its initial call to the Server, but when I click the button on the Server's GUI to send a message back to the Client, the Server's thread hangs like its deadlocked still. I tried making the Client's proxy oneway, but that didn't change the result either. Am I going about this wrong?
  • matthewmatthew NL, CanadaMember Matthew NewhookOrganization: ZeroC, Inc.Project: Internet Communications Engine ✭✭✭
    Normally you would have the client provide the proxy in the registration step, and then use that proxy in the callback. It is not recommended to construct proxies like you have done. Typically stringToProxy is only called to bootstrap communications (that is to establish initial contact with the server). See ZeroC - How should I pass proxies? for a FAQ on this very topic.

    That being said, its likely that is not the issue in this case. I would guess from looking at the original sample code you forgot to activate the object adapter in the client.
  • duckjonesduckjones Member Dennis PelhamOrganization: PersonalProject: Home Automation controller
    Thanks Matthew! Turns out my issue was a combination of both your points. I completely missed calling activate on my Client's Object Adapter, but it still didn't operate correctly until I quit instantiating a proxy with an Identity from the Server process on the Client. Ice wasn't preventing me from doing it, but I think under the hood it was causing some kind of blocking issue. When I implemented the same data type interface in my Client program and created a proxy for that to send to the Server program, it started working like a charm. Thanks for the help!

    Dennis
Sign In or Register to comment.