WCF多客户端双工

编程入门 行业动态 更新时间:2024-10-09 22:21:13
本文介绍了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="schemas.microsoft/winfx/2006/xaml/presentation" xmlns:x="schemas.microsoft/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("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("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="schemas.microsoft/winfx/2006/xaml/presentation" xmlns:x="schemas.microsoft/winfx/2006/xaml" xmlns:mc="schemas.openxmlformats/markup-compatibility/2006" xmlns:d="schemas.microsoft/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 = ""+host+":8000/WCFService/Service/"+user; string clientBaseAddress = "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多客户端双工

本文发布于:2023-11-30 22:33:52,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1651674.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:双工   客户端   WCF

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!