WCF多客户端双工 [英] WCF Multiple Client Duplex

查看:69
本文介绍了WCF多客户端双工的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经完成了我只能解释为详尽无遗地寻找我的问题的答案,并且我发现了许多我认为会指向正确方向并且似乎总是不足的例子。我确信我的问题有一个简单的解决方案,但它正在逃避我。



我想要完成的任务:



我有一台服务器在WPF应用程序中托管WCF服务(双工绑定)。服务器还通过IIS托管WPF浏览器应用程序。目的是客户端将下载并运行浏览器应用程序。浏览器应用程序应连接到服务器上的服务。客户端将向服务提交连接请求。该服务会记住每个连接的客户端的回调。然后,每当服务器上的应用程序内的信息发生变化时,它就会通过存储的回调将该信息传递给客户端。



我遇到的问题:



当我存储回调时,似乎每个回调都是相同的。因此,如果我在服务将更新传递给客户端时从两个客户端连接,则会将更新发送到最后一个客户端以进行两次连接。如果我然后从第三个客户端连接,该服务将更新发送到该客户端三次。然而,无论我使用多少台不同的机器运行浏览器(客户端)应用程序,我都会继续使用这个服务器。



服务端



.xaml代码

I have done what I can only explain to be exhaustive searching for an answer to my problem and I have found numerous examples that I thought would point me in the right direction and seem to always fall short. I am sure that there is a simple solution to my problem but it is escaping me.

What I am trying to accomplish:

I have a server that is hosting a WCF Service (duplex binding) within a WPF application. The server is also hosting a WPF Browser application through IIS. The intent is a client will download and run the browser application. The browser application should connect to the service on the server. The client will submit a request to the service to connect. The service remembers the callback to each client that connects. Then, whenever information within the application on the server changes it passes that information to the clients through the stored callbacks.

The issue that I am having:

When I store the callbacks it appears that each callback is identical. Therefore, if I connect from two clients when the service passes updates to the clients it sends the update to the last client to connect twice. If I then connect from a third client, the service sends the update to that client three times. This continues on for however many clients I connect to the service with from however many different machines I run the browser (client) application from.

SERVICE SIDE

.xaml code

<Window x:Class="WCF_Service.MainWindow"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        Title="MainWindow" Height="350" Width="525">
    <Grid Loaded="Window_Loaded">
        <Button Content="Call Clients" Height="23" HorizontalAlignment="Left" Margin="12,0,0,12" Name="btnCall" VerticalAlignment="Bottom" Width="75" Click="btnCall_Click" />
        <TextBox Margin="12,12,12,41" Name="txtActivity" TextWrapping="Wrap" />
    </Grid>
</Window>





服务源代码



Service Source Code

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ServiceModel;
using System.ServiceModel.Description;

namespace WCF_Service
{
    [ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IMyServiceCallback))]
    public interface IMyServiceContract
    {
        [OperationContract(IsOneWay = true)]
        void Connect(string user);
    }

    // Callback Contract for the server to call on the clients
    public interface IMyServiceCallback
    {
        [OperationContract(IsOneWay = true)]
        void MyCallback();
    }

    /// <summary>
    /// Class object containing pertinent information about the Client that has
    /// called on the SiteStatus Service including the callback channel, user name,
    /// and TNAs being displayed at the remote site
    /// </summary>
    public class MyClient
    {
        public MyClient()
        {
            _clientCallback = null;
            _username = "";
        }

        // Callback to the client.
        private static IMyServiceCallback _clientCallback;
        public IMyServiceCallback ClientCallback
        {
            get { return _clientCallback; }
            set { _clientCallback = value; }
        }

        private string _username;
        public string Username
        {
            get { return _username; }
            set { _username = value; }
        }
    }

    [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple, InstanceContextMode = InstanceContextMode.Single)]
    public class MyService : IMyServiceContract
    {
        # region Events
        public class MessageEventArgs : EventArgs
        {
            public string Message;

            public MessageEventArgs(string msg)
            {
                Message = msg;
            }
        }

        public delegate void MessageEventHandler(object obj, MessageEventArgs e);

        public event MessageEventHandler ProcessedAction;
        #endregion

        List<MyClient> _clients;
        object locker = new object();

        // Default constructor
        public MyService()
        {
            _clients = new List<MyClient>();
        }


        /// <summary>
        /// This function is used to obtain the current callback for the calling
        /// client
        /// </summary>
        /// <returns>Current callback for the calling client</returns>
        IMyServiceCallback GetCurrentCallback()
        {
            return OperationContext.Current.GetCallbackChannel<IMyServiceCallback>();
        }

        public void Connect(string user)
        {
            if ((user != null) && (user != ""))
            {
                // Incoming connection from a client.
                MyClient client = new MyClient();

                try
                {
                    // Get the user name
                    client.Username = user;

                    // Get the callback from the client attempting to connect
                    client.ClientCallback = this.GetCurrentCallback();
                }
                catch
                {
                    // Failed to read the callback
                    // Exception Code
                }

                lock (locker)
                {
                    // Check to see if the user already exists
                    foreach (MyClient c in _clients)
                    {
                        if (c.Username == user)
                        {
                            // Remove the old client
                            _clients.Remove(c);
                            break;
                        }
                    }
                    // Add the new client to the list
                    _clients.Add(client);
                }

                if (ProcessedAction != null)
                {
                    string msg = "Connection received from: " + user;
                    ProcessedAction(this, new MessageEventArgs(msg));
                }

            }
        }

        /// <summary>
        /// This handles sending information to the client(s) regarding site
        /// changes.
        /// </summary>
        public void SiteChanged()
        {
            List<MyClient> missingClients = new List<MyClient>();
            string msg;

            if ((_clients != null) && (_clients.Count > 0))
            {
                foreach (MyClient c in _clients)
                {
                    if ((c.ClientCallback != null))
                    {
                        try
                        {
                            c.ClientCallback.MyCallback();
                            msg = "Called " + c.Username;
                        }
                        catch (Exception e)
                        {
                            // Exception Code...
                            msg = c.Username + " failed to respond";
                            missingClients.Add(c);
                        }
                        if (ProcessedAction != null)
                        {
                            ProcessedAction(this, new MessageEventArgs(msg));
                        }
                    }
                }

                if (missingClients.Count > 0)
                {
                    // At least one client connection has been
                    // lost since the last time we sent
                    // information. They need to be purged from
                    // the list of clients.
                    foreach (MyClient c in missingClients)
                    {
                        _clients.Remove(c);
                        msg = "Removed " + c.Username;
                        if (ProcessedAction != null)
                        {
                            ProcessedAction(this, new MessageEventArgs(msg));
                        }
                    }
                }
            }
        }
    }


    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        ServiceHost selfHost;
        MyService _wcfService;

        public MainWindow()
        {
            InitializeComponent();
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            WSDualHttpBinding dualHttpBinding = new WSDualHttpBinding(WSDualHttpSecurityMode.None);
            dualHttpBinding.Security.Message.ClientCredentialType = MessageCredentialType.None;
            dualHttpBinding.Security.Message.NegotiateServiceCredential = false;

            _wcfService = new MyService();
            _wcfService.ProcessedAction += new MyService.MessageEventHandler(_wcfService_ProcessedAction);

            // Create the ServiceHost for the SiteMonitor
            Uri _baseAddress = new Uri("http://localhost:8000/WCFService/Service/");
            selfHost = new ServiceHost(_wcfService, _baseAddress);

            // Set the credentials for logging in
            selfHost.Credentials.WindowsAuthentication.AllowAnonymousLogons = true;

            // Add endpoints
            ServiceEndpoint endpointAdmin = selfHost.AddServiceEndpoint(typeof(IMyServiceContract), dualHttpBinding, "Administrator");
            ServiceEndpoint endpointClient1 = selfHost.AddServiceEndpoint(typeof(IMyServiceContract), dualHttpBinding, "Client1");


#if DEBUG
            // Enable metadata exchange. This is temporary for development. Needs to be
            // removed for production
            ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
            smb.HttpGetEnabled = true;
            smb.HttpGetUrl = new Uri("http://localhost:8732/WCFService");
            selfHost.Description.Behaviors.Add(smb);
#endif
            try
            {
                selfHost.Open();
            }
            catch (Exception exc)
            {
                // Exception Code
            }
        }

        void _wcfService_ProcessedAction(object obj, MyService.MessageEventArgs e)
        {
            txtActivity.AppendText(e.Message + "\r");
        }

        private void btnCall_Click(object sender, RoutedEventArgs e)
        {
            if (_wcfService != null)
            {
                txtActivity.AppendText("Call button pressed\r");
                _wcfService.SiteChanged();
            }
        }
    }
	
}







客户端



.xaml代码




CLIENT SIDE

.xaml Code

<Page x:Class="MyBrowserApp.MainPage"

      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 

      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 

      mc:Ignorable="d" 

      d:DesignHeight="300" d:DesignWidth="300"

      Title="MainPage" Loaded="pgMain_Loaded">
    <Grid>
        <TextBox Margin="12,68,12,12" Name="txtActivity" TextWrapping="Wrap" />
        <Button Content="Login" DataContext="{Binding}" Height="23" HorizontalAlignment="Left" Margin="10,39,0,0" Name="btnLogin" VerticalAlignment="Top" Width="75" Click="btnLogin_Click" />
        <TextBox Height="23" HorizontalAlignment="Left" Margin="10,10,0,0" Name="txtUser" Text="Enter Username..." VerticalAlignment="Top" Width="120" GotFocus="txtUser_GotFocus" />
    </Grid>
</Page>





Source Code for Client



Source Code for Client

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ServiceModel;

using MyBrowserApp.ServiceReference1;

namespace MyBrowserApp
{
    /// <summary>
    /// Interaction logic for MainPage.xaml
    /// </summary>
    public partial class MainPage : Page
    {
        string _userName, host;
        MyServiceCallbackHandler myCallback;

        public MainPage()
        {
            InitializeComponent();
        }

        private void pgMain_Loaded(object sender, RoutedEventArgs e)
        {
            Uri source = System.Windows.Interop.BrowserInteropHelper.Source;
            host = source.Host; // Hostname of the client executing the program

            if ((host == null) || (host == ""))
            {
                host = "localhost";
            }

            myCallback = new MyServiceCallbackHandler();
            myCallback.ProcessedActivity += new MyServiceCallbackHandler.MessageEventHandler(myCallback_ProcessedActivity);
        }

        void myCallback_ProcessedActivity(object obj, MyServiceCallbackHandler.MessageEventArgs e)
        {
            txtActivity.AppendText(e.Message + "\r");
        }

        private void btnLogin_Click(object sender, RoutedEventArgs e)
        {
            _userName = txtUser.Text;
            try
            {
                txtActivity.AppendText("Open connection\r   Host: " + host + "\r   User: " + _userName + "\r");
                myCallback.Open(host, _userName);
            }
            catch (Exception exc)
            {
                // There was an error sending the service command
                txtActivity.AppendText(exc.Message + "\r");
            }
        }

        private void txtUser_GotFocus(object sender, RoutedEventArgs e)
        {
            if (txtUser.Text == "Enter Username...")
            {
                txtUser.Text = "";
            }
        }
    }


    [CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)]
    class MyServiceCallbackHandler : IMyServiceContractCallback
    {
        #region Events
        public class MessageEventArgs : EventArgs
        {
            public string Message;

            public MessageEventArgs(string msg)
            {
                Message = msg;
            }
        }

        public delegate void MessageEventHandler(object obj, MessageEventArgs e);

        public event MessageEventHandler ProcessedActivity;
        #endregion


        private MyServiceContractClient myServiceClient;

        private string _username;
        public string Username
        {
            get
            {
                return _username;
            }
            set
            {
                _username = value;
            }
        }

        public void Open(string host, string user)
        {
            string endpoint = "http://"+host+":8000/WCFService/Service/"+user;
            string clientBaseAddress = "http://localhost:8000/WCFService/Client/" + user;
 
            InstanceContext instanceContext = new InstanceContext(this);
 
            WSDualHttpBinding dualHttpBinding = new WSDualHttpBinding(WSDualHttpSecurityMode.None);
            dualHttpBinding.ClientBaseAddress = new Uri(clientBaseAddress);
            
            myServiceClient = new MyServiceContractClient(instanceContext, dualHttpBinding, new EndpointAddress(endpoint));
            myServiceClient.ClientCredentials.SupportInteractive = true;
 
            _username = user;

            string msg;
            if (_username != null)
            {
                try
                {
                    myServiceClient.Connect(_username);
                    msg = "Connect as user: " + _username;
                }
                catch (Exception exc)
                {
                    msg = exc.Message;
                }
                if (ProcessedActivity != null)
                {
                    ProcessedActivity(this, new MessageEventArgs(msg));
                }
            }
            else
            {
                if (ProcessedActivity != null)
                {
                    msg = "Username is null!";
                    ProcessedActivity(this, new MessageEventArgs(msg));
                }
            }
        }

        #region IMyServiceContractCallback Members
        public void MyCallback()
        {
            //... Code to process callback
            string msg = "Callback received";

            if (ProcessedActivity != null)
            {
                ProcessedActivity(this, new MessageEventArgs(msg));
            }
        }
        #endregion
    }
}





Note: In order to get \"ServiceReference1\" you need to run the service side. Then, add a service reference to the client side and leave it named as the default ServiceReference1.



Also, to fully understand the environment I am trying to execute it is necessary to publish the client application and then run two instances of it. The first instance type \"Administrator\" into the text box. In the second instance type \"Client1\"



The examples that I have found indicate that keeping a list of callbacks should give me the functionality I need but I only ever report to the last client to connect by the number times directly proportional to the number of clients to connect.



I originally wanted to use NetTcp binding, which means that the clientBaseAddress does not apply in the code I provided above. However, it gave the same results as described above. So, I switched to using the WSDualHttp binding thinking that being able to define the clientBaseAddress would resolve my issue and it did not.



I’m sorry for the long winded question but I am at a loss and really need to figure this out. I will be happy to provide any additional information anyone needs to get to the bottom of this behavior.



Note: In order to get "ServiceReference1" you need to run the service side. Then, add a service reference to the client side and leave it named as the default ServiceReference1.

Also, to fully understand the environment I am trying to execute it is necessary to publish the client application and then run two instances of it. The first instance type "Administrator" into the text box. In the second instance type "Client1"

The examples that I have found indicate that keeping a list of callbacks should give me the functionality I need but I only ever report to the last client to connect by the number times directly proportional to the number of clients to connect.

I originally wanted to use NetTcp binding, which means that the clientBaseAddress does not apply in the code I provided above. However, it gave the same results as described above. So, I switched to using the WSDualHttp binding thinking that being able to define the clientBaseAddress would resolve my issue and it did not.

I'm sorry for the long winded question but I am at a loss and really need to figure this out. I will be happy to provide any additional information anyone needs to get to the bottom of this behavior.

推荐答案

Hi,



Could it be that the static IMyServiceCallback _clientCallback is causing the unexpected behaviour?



Good luck.



Hi,

Could it be that the static IMyServiceCallback _clientCallback is causing the unexpected behaviour?

Good luck.

public class MyClient
   {
       public MyClient()
       {
           _clientCallback = null;
           _username = "";
       }

       // Callback to the client.
       private /*static*/ IMyServiceCallback _clientCallback;
       public IMyServiceCallback ClientCallback
       {
           get { return _clientCallback; }
           set { _clientCallback = value; }
       }

       private string _username;
       public string Username
       {
           get { return _username; }
           set { _username = value; }
       }
   }


这篇关于WCF多客户端双工的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆