.net удаленное взаимодействие, вызов делегата в неправильном процессе

Я делаю несколько тестов с удаленным взаимодействием .net и обнаружил, что при использовании делегата с ним возникает проблема.

У меня есть одно приложение, которое одновременно является сервером и клиентом. Когда пользователь запускает приложение из проводника в первый раз, оно запускается как сервер и запускает новый процесс как клиент. Оба работают нормально. Теперь, когда пользователь запускает его снова, пока процессы сервера и клиента все еще работают, он должен стать клиентом и отправить сообщение на сервер о новом запущенном процессе, а затем завершиться.

Все работает хорошо, за исключением того, что делегат выполняется в серверном процессе, а не в клиенте.

Вот код.

    const string PIPE_NAME = "testPipeName33";
    const string OBJECT_NAME = "test";
    static RemoteObject remoteObject;
    static void RegisterClient()
    {
        IpcClientChannel chan = new IpcClientChannel();
        ChannelServices.RegisterChannel(chan, false);

        remoteObject = (RemoteObject)Activator.GetObject(typeof(RemoteObject),
                string.Format("ipc://{0}/{1}", PIPE_NAME, OBJECT_NAME));
    }
    static void RegisterServer()
    {
        BinaryServerFormatterSinkProvider serverProvider = new BinaryServerFormatterSinkProvider();
        serverProvider.TypeFilterLevel = TypeFilterLevel.Full;

        IpcServerChannel chan = new IpcServerChannel("", PIPE_NAME, serverProvider);
        ChannelServices.RegisterChannel(chan, false);

        RemotingServices.Marshal(new RemoteObject(), OBJECT_NAME);
    }

    [STAThread]
    static void Main(string[] args)
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        if ((args.Length == 0 || args[0] == "s"))
        {
            try
            {
                RegisterServer();
            }
            catch (RemotingException)
            {
                // try to register it with the pipe name. If it fails, means server is already running.
                //bad idea, I know, but it's just for barebone quick test
                RegisterClient();
                remoteObject.OnNewProcessStarted("test");
                Application.Exit();
                return;
            }

            Process.Start(Application.ExecutablePath, "c");
            Application.Run(new Form1("Server"));

        }
        else
        {
            IsClient = true;
            RegisterClient();
            remoteObject.SetOnNewProcessStarted(OnNewProcessStarted);

            Application.Run(new Form1("Client"));

        }
    }


    static bool IsClient = false;
    static bool OnNewProcessStarted(string commandLine)
    {
        MessageBox.Show("Is Client : " + IsClient);//problem here, IsClient should be true
        return true;
    }

Класс удаленного объекта.

public delegate bool OnNewProcessStartedDelegate(string text);

internal class RemoteObject : MarshalByRefObject
{
    public OnNewProcessStartedDelegate OnNewProcessStartedHandler;
    public bool OnNewProcessStarted(string commandLine)
    {
        if (OnNewProcessStartedHandler != null)
            return OnNewProcessStartedHandler(commandLine);
        return false;
    }

    public void SetOnNewProcessStarted(OnNewProcessStartedDelegate onNewProcessStarted)
    {
        OnNewProcessStartedHandler = onNewProcessStarted;
    }

    public override object InitializeLifetimeService()
    {
        return null;
    }
}

PS: Может быть только один сервер и один клиент.


person J. Doe    schedule 27.09.2015    source источник
comment
это условие верно?? если ((args.Length == 0 || args[0] == s))   -  person Viru    schedule 27.09.2015
comment
Условие означает, что если нет аргумента или если первый аргумент равен s, то он выполнит server. Вы проверили, что аргументов было больше одного, причем первый аргумент не был s?   -  person Viru    schedule 27.09.2015
comment
Да аргументы и условия верны.   -  person J. Doe    schedule 27.09.2015
comment
Можете ли вы опубликовать аргумент, который вы передаете, когда вы вызываете свое приложение во второй раз?   -  person Viru    schedule 27.09.2015
comment
Весь код уже опубликован, пожалуйста, посмотрите.   -  person J. Doe    schedule 27.09.2015
comment
хорошо... вы уверены, что ваш клиент не запускается во второй раз? вы пришли к такому выводу, потому что в выводе вы не видите Client = true?   -  person Viru    schedule 27.09.2015
comment
Причина, по которой я задал этот вопрос, потому что я вижу, что в блоке catch вы не устанавливаете IsClient = true, как вы это делали в блоке else... Добавьте оператор isClient = true в блок catch и повторите попытку.   -  person Viru    schedule 27.09.2015
comment
Клиент может быть запущен только в том случае, если есть аргумент, отличный от s, или сервер уже запущен. Опубликованный код завершен, вы можете запустить его на своей стороне.   -  person J. Doe    schedule 27.09.2015
comment
Нет... Я говорю об этой строке. Теперь, когда пользователь запускает ее снова, пока процессы сервера и клиента все еще работают, она должна стать клиентом. Вы достигаете этого следующим образом: когда пользователь снова запускает программу... вы сначала вызываете RegisterServer, поскольку сервер уже работает... вы получаете исключение удаленного взаимодействия, а затем регистрируете клиент в блоке catch.... вы должны добавить IsClient = true, который используется функцией, на которую указывает делегат... поскольку для IsClient не установлено значение true.... вы видите ISClient false в выводе, что заставляет вас поверить, что клиент не работает.....   -  person Viru    schedule 27.09.2015
comment
Я добавил в ответ, который поможет вам понять мою точку зрения   -  person Viru    schedule 27.09.2015


Ответы (2)


Делегат работает на сервере, потому что новый клиент вызывает делегат RemoteObject на сервере. Между сервером и первым клиентом не зарегистрирован обратный вызов. сервер не будет знать, какой процесс вызывать, поскольку в первом клиенте нет никого, кто слушает запрос. Как правило, с такими технологиями, как WCF, мы можем иметь DualHttpBindings, которые разрешают дуплексную связь, но здесь у нас нет никаких обратных вызовов. Итак, мы должны зарегистрировать канал обратного вызова с RemoteObject в первом клиенте, который обеспечит вызов делегата в первом клиентском процессе. Я пробовал, и он работает. Я добавил некоторый идентификатор процесса для целей отладки.

  static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main(string[] args)
        {
            const string PIPE_NAME = "testPipeName33";
            const string OBJECT_NAME = "test";
            const string CALLBACK_PIPE_NAME = "testPipeName34";
            const string CALLBACK_OBJECT_NAME = "testclient";

            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            if ((args.Length == 0 || args[0] == "s"))
            {
                try
                {
                   IPCRegistration.RegisterServer(PIPE_NAME,OBJECT_NAME);
                }
                catch (RemotingException)
                {
                                        remoteObject = IPCRegistration.RegisterClient(typeof(RemoteObject),PIPE_NAME,OBJECT_NAME);
                    remoteObject.OnNewProcessStarted("test");
                    Application.Exit();
                    return;
                }
                MessageBox.Show("Server:" + Process.GetCurrentProcess().Id);
                Process.Start(Application.ExecutablePath, "c");
                Application.Run(new Form1("Server"));

            }
            else
            {
                IsClient = true;
                remoteObject = IPCRegistration.RegisterClient(typeof(RemoteObject), PIPE_NAME, OBJECT_NAME);
                IPCRegistration.RegisterServer(CALLBACK_PIPE_NAME, CALLBACK_OBJECT_NAME); // Here Client will listen on this channel.
                remoteObject.SetOnNewProcessStarted(OnNewProcessStarted,Process.GetCurrentProcess().Id.ToString());
                MessageBox.Show("Client:" + Process.GetCurrentProcess().Id);
                Application.Run(new Form1("Client"));

            }
        }


        static RemoteObject remoteObject;

        static bool IsClient = false;
        static bool OnNewProcessStarted(string commandLine)
        {
            MessageBox.Show("saved:"+commandLine+" Currrent:"+Process.GetCurrentProcess().Id);
            MessageBox.Show("Is Client : " + IsClient);//problem here, IsClient should be true
            return true;
        }
    }

    public delegate bool OnNewProcessStartedDelegate(string text);

    internal class RemoteObject : MarshalByRefObject
    {
        public OnNewProcessStartedDelegate OnNewProcessStartedHandler;
        public string value;
        public bool isCallBack = false;
        const string PIPE_NAME = "testPipeName33";
        const string OBJECT_NAME = "test";
        const string CALLBACK_PIPE_NAME = "testPipeName34";
        const string CALLBACK_OBJECT_NAME = "testclient";
        RemoteObject remoteObject;
        public bool OnNewProcessStarted(string commandLine)
        {
            if (!isCallBack)
            {
                remoteObject.isCallBack = true;
                return remoteObject.OnNewProcessStarted(commandLine);
            }

            if (OnNewProcessStartedHandler != null)
                return OnNewProcessStartedHandler(value);
            return false;
        }



        public void SetOnNewProcessStarted(OnNewProcessStartedDelegate onNewProcessStarted,string value)
        {
            this.value = value;
            OnNewProcessStartedHandler = onNewProcessStarted;
            if (!isCallBack)
            {
                remoteObject = IPCRegistration.RegisterClient(typeof(RemoteObject), CALLBACK_PIPE_NAME, CALLBACK_OBJECT_NAME);
                remoteObject.isCallBack = true;
                remoteObject.SetOnNewProcessStarted(onNewProcessStarted, Process.GetCurrentProcess().Id.ToString());
            }
        }

        public override object InitializeLifetimeService()
        {
            return null;
        }
    }

    internal class IPCRegistration
    {
       public  static RemoteObject RegisterClient(Type remoteObject,string PIPE_NAME,string OBJECT_NAME)
        {
            IpcClientChannel chan = new IpcClientChannel();
            ChannelServices.RegisterChannel(chan, false);

            RemoteObject remoteObjectInstance = (RemoteObject)Activator.GetObject(remoteObject,
                    string.Format("ipc://{0}/{1}", PIPE_NAME, OBJECT_NAME));
            return remoteObjectInstance;
        }
        public static void RegisterServer(string pipeName, string objectName)
        {
            BinaryServerFormatterSinkProvider serverProvider = new BinaryServerFormatterSinkProvider();
            serverProvider.TypeFilterLevel = TypeFilterLevel.Full;

            IpcServerChannel chan = new IpcServerChannel("", pipeName, serverProvider);
            ChannelServices.RegisterChannel(chan, false);

            RemotingServices.Marshal(new RemoteObject(), objectName);
        }
    }
person Viru    schedule 27.09.2015
comment
Это неправильно. Установка IsClient true для этого процесса неверна. Потому что, когда будет вызываться OnNewProcessStarted(), он запустит делегат, который должен выполняться в первом клиенте, созданном сразу после серверного процесса. Вот что происходит, когда приложение запускается в первый раз, оно запускается как сервер, а также запускает новый процесс для клиента. Теперь, если какой-либо другой процесс, который будет запущен пользователем, он просто станет клиентским вызовом OnNewProcessStarted на удаленном объекте и уничтожит себя. Пока сервер и первый клиент продолжают работать. Надеюсь, теперь я ясно выразился. - person J. Doe; 27.09.2015
comment
он просто станет клиентским вызовом OnNewProcessStarted для удаленного объекта и убьет себя. Здесь вы получаете Remoteobject, вызывая RegisterClient, а затем вызывая OnNewProcessStartedMethod, но ваш isClient является статической переменной, которая будет ложной при повторном запуске программы. значение true, но при запуске делегата isClient отображается как false - person Viru; 27.09.2015
comment
Почему вы хотите вызвать событие OnNewProcessStart во второй раз? Я вижу, после этого вы немедленно выходите из приложения... почему бы просто не выйти из приложения, не вызывая OnNewProcessStart? - person Viru; 27.09.2015
comment
Вы, кажется, неправильно поняли структуру app. Вот попробую еще раз. Пользователь запускает приложение в первый раз, приложение становится сервером и запускает новый процесс в качестве своего клиента. И клиент, и сервер продолжают работать. Но теперь снова пользователь снова запускает приложение, теперь, поскольку оно не может быть сервером, потому что сервер уже запущен. Так что он просто становится клиентом, отправляет сообщение на сервер и умирает. Затем сервер передает это сообщение клиенту, которого он запустил. Его клиент-сервер-клиентское сообщение. Но этого не происходит, это становится сообщением между клиентом и сервером. - person J. Doe; 27.09.2015
comment
Я полагаю, что вы неправильно поняли концепцию делегатов... Когда вы запускаете свое приложение во второй раз, оно не будет вызывать метод делегата в первом клиентском процессе, вместо этого он будет вызывать новый клиентский процесс.... Попробуйте это. Запустите свое приложение в первый раз. .как сервер, так и клиент будут работать, затем закройте клиент и снова запустите приложение, теперь делегат первого клиента не может быть вызван правильно, но вы все равно увидите окно сообщения.... вы можете добавить дополнительную информацию в свое окно сообщения, чтобы понять, что Процесс, в котором он находится, чтобы полностью понять. Я думаю, что код работает нормально только ... он НЕ работает под сервером ... - person Viru; 27.09.2015
comment
То, чего вы хотите достичь, является совершенно другой вещью и не может быть достигнуто с помощью делегатов... вам нужно использовать именованные каналы для межпроцессного взаимодействия... это то, на что вы смотрите.... и если ваш клиент и сервер работаете под другим сервером, тогда ваш первый клиент будет привязан к определенному порту, который вы отправляете информацию с сервера на этот порт, чтобы первый клиент забрал ее ..... Надеюсь, теперь у вас есть какое-то представление .... - person Viru; 27.09.2015
comment
Привет еще раз, Виру, пожалуйста, прочитайте код, который я разместил. Я уже использую удаленное взаимодействие .net. Делегат вызывается на сервере, это 100% уверенность. Пожалуйста, прочтите об удаленном взаимодействии .net и событии. Это прояснит ситуацию. Спасибо :) - person J. Doe; 27.09.2015
comment
@J.Doe Я взял ваш код, немного отладил и обнаружил, что делегат вызывается на стороне сервера. Похоже, что IPC не собирается автоматически вызывать делегата в клиентском процессе. вы должны придумать какой-то механизм обратного вызова. Пожалуйста, посмотрите мой обновленный ответ. Я проверил его и теперь вижу isClient true в окне сообщения, как вы и ожидали. - person Viru; 27.09.2015
comment
Это работает хорошо. Я имел в виду что-то подобное в качестве последнего средства, но вы сделали очень хорошо. Хотел бы я проголосовать за вас, если бы у меня было достаточно репутации. Большое спасибо, Виру, и хорошего дня или ночи. - person J. Doe; 27.09.2015

при втором запуске приложения вы не устанавливаете делегат в RemoteObject, поэтому используется ранее установленный объект делегата (он был установлен при первом запуске вашего приложения).

Я думаю, что если вы запустите свое второе приложение с аргументом команды, например «c», делегат будет запущен в клиентском процессе.

person Юрий Кузьменко    schedule 27.09.2015
comment
Я не понимаю вашего ответа, делегат должен выполняться в клиентском процессе. - person J. Doe; 27.09.2015