bebugs Blog


Willkommen auf meiner Homepage. Wer Lust hat kann sich gern über Messenger oder im gulli irc melden.

ICQ: 164621172
MSN: nuqer@hotmail.de

GreenfieldGrafix
-Affi-
-Affi-
-Affi-
-Affi-
-Affi-
-Affi-
-Affi-
-Affi-
Wer eingetragen werden möchte meldet sich einfach

Wir haben bezahlt!


Buttonnetzwerk für ein freies Internet

Disclaimer
Abonieren

Artikel

Lockerz invites

Atomexplosion
Barbie jeeptuning
Batman 1966
Geiler Klingelton
Der große blaue Bär in da House
Die Elite des Internets 1
Die Elite des Internets 2
Die Elite des Internets 3
eBooks
Eigenes Layout designen
Fahrradfahrer
Farbkombinationen
Finanzkrisengewinner
Fontstruct
Gema Rohlinge
Head Tracking
Herzblatt Synchro
ICQ-Spam
Informiert Wolfgang
Lars the Emo Kid
Lockpicks herstellen
Magnetisches Armband
Matrix Flash
Nerdwebsites
Tilt-Shift-Effekt
Online TV Recorder
Orisinal
Out Drink Santa
Out of Bounds
Sandboxie
Shepard-Skala
Stickman Exodus
Super Mario World
Ultimate Defrag 1
Ultimate Defrag 2
USB-Stick Autorun
Von wegen Klimaschwindel
WCF Tutorial 1
WCF Tutorial 2"
---

Mein neuer Blog

 

Gratis bloggen bei
myblog.de

Gratis bloggen bei
myblog.de

Blogverzeichnis - Blog Verzeichnis bloggerei.de
WCF-Tutorial 2

In meinem zweiten WCF-Tutorial schreibe ich darüber, wie man mehrere Clients mit einem Host kommunizieren lässt. Es geht im Prinzip darum, dass sich mehrere Client miteinander unterhalten können und der Host das ganze managend. Also eine Art Chatprogramm. Die Clients können sich dabei nach Wunsch ab- und anmelden. Man könnte das ganze natürlich wie in meinem ersten Tutorial machen und für beide Seiten einen Dienst definieren. Das ganze würde auch funktionieren, sogar mit einer dynamischen Anzahl an Clients. Einfacher ist es jedoch mit den in der WCF mitgelieferten Callbackfunktionen. Der Client muss sich letztendlich nur anmelden und wird in eine Liste bei dem Host aufgenommen.  Alles andere regelt WCF für uns.

Zu Beginn erstellen wir 3 Projekte. Eine Windows Forms-Anwendung für den Client, eine Konsolenanwendung für den Host und eine Dienstbibliothek für die gemeinsam genutzten Objekte. Ich hab die Projekte Client, Host und Datalib genannt. Für alle Projekte muss dabei ein Verweis auf System.ServiceModel hinzugefügt werden. Dem Host und dem Client wird zusätzlich noch ein Verweis auf das Datalib-Projekt hinzugefügt.
Beginnen wir mit dem Datalib-Projekt. Hier werden für Host und Client alle Objekte bereitgestellt, die beide kennen müssen um miteinander zu kommunizieren.
Jeder Client soll eine Nachricht an den Host schicken, in der sein Name und seine eigentliche Nachricht, die er geschrieben hat steht. Das ganze lagern wir in eine eigene Klasse aus, die ich ChatMessage genannt habe.

 

ChatMessage.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.Runtime.Serialization;

namespace Datalib
{
    [DataContract]
    public class ChatMessage
    {
        string m_Name;
        string m_Message;

        [DataMember]
        public string Name
        {
            set { m_Name = value; }
            get { return m_Name; }
        }

        [DataMember]
        public string Message
        {
            set { m_Message = value; }
            get { return m_Message; }
        }
    }
}

Jede Klasse, die über das Netzwerk verschickt werden soll muss zuerst serialisiert und auf der anderen Seite wieder deserialisiert werden. Um die Klasse Serialisierbar zu machen schreiben wir deshalb als Attribut [DataContract] darüber. Jeden Member der Klasse müssen wir nun explizit als serialisierbar deklarieren, wenn wir ihn auch serialisieren wollen. Es gibt beispielsweise auch Member, die nur für den Client oder Host wichtig sind. Diese müssen dann natürlich nicht serialisiert werden. Wir haben allerdings keine solchen Member und müssen alle unsere Member serialisieren. Das geschieht durch das Attribut [DataMember]. Das sollte am besten nicht direkt über die Variable, sondern über deren Getter- und Setter-Methoden geschrieben werden.
Als nächstes legen wir die Funktion fest, die der Host ausführt und mit der er an einen Client die entsprechende Nachricht schickt.

 

IClientFunctions.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;

namespace Datalib
{
    public interface IClientFunctions
    {
        [OperationContract(IsOneWay = true)]
        void ShowMessage(string message);
    }
}

 

Das ganze haben wir als Interface festgelegt, da dem Host später egal sein kann was der Client damit macht. Er muss nur wissen wie er ihn anzusprechen hat. Der Client kann die Methode ganz nach eigenen Wünschen benutzen und wird auch nichts festgelegt. Wie in meinem ersten Tutorial geben wir der Funktion auch hier das Attribut [OperationContract]. IsOneWay legt fest, dass diese Funktion nur in eine Richtung gültig ist. Das ist sinnvoll, da der Host diese Nachricht an alle Clients schickt, die Clients jedoch nicht zurück an den Host. Um mit dem Host zu kommunizieren bekommen die Clients nun eine eigene Klasse.

 

IHostFunctions.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;

namespace Datalib
{
    [ServiceContract(CallbackContract = typeof(IClientFunctions))]
    public interface IHostFunctions
    {
        [OperationContract]
        void Connect();

        [OperationContract]
        void Disconnect();

        [OperationContract]
        void Send(ChatMessage cm);
    }
}

 

Auch hier benutzen wir wieder ein Interface damit weder Host noch Client mit dem implementierten Ballast des anderen auskommen muss. Die Funktionen Connect und Disconnect können vom Client genutzt werden sich beim Host anzumelden. Mit der Funktion Send sendet der Client seine Nachricht an den Host. Er sendet dabei ein Objekt des Typs ChatMessage, das wir ja bereits implementiert haben.
Als nächstes wagen wir uns an den Host und erstellen eine Klasse HostMessageService die das Interface  IHostFunctions verwendet.

 

HostMessageService.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using Datalib;

namespace Host
{
    class HostMessageService : IHostFunctions
    {
        private static readonly List subscribers = new List();

        public void Connect()
        {
            IClientFunctions callback = OperationContext.Current.GetCallbackChannel();
            if (!subscribers.Contains(callback))
            {
                subscribers.Add(callback);
                Console.WriteLine("New client connected" );
            }
        }

        public void Disconnect()
        {
            IClientFunctions callback = OperationContext.Current.GetCallbackChannel();
            if (subscribers.Contains(callback))
            {
                subscribers.Remove(callback);
                Console.WriteLine("client disconnected" );
            }
        }

        public void Send(ChatMessage cm)
        {
            List subscribersToDelete = new List();
            IClientFunctions callback = OperationContext.Current.GetCallbackChannel();
            foreach (IClientFunctions client in subscribers)
            {
                if (client != callback)
                {
                    try
                    {
                        client.ShowMessage(cm.Name + ":t" + cm.Message);
                    }
                    catch(Exception e)
                    {
                        subscribersToDelete.Add(client);
                        Console.WriteLine("Client lost connection" + e.Message);
                    }
                }
            }
            foreach (IClientFunctions client in subscribersToDelete)
            {
                if (subscribers.Contains(client))
                {
                    subscribers.Remove(client);
                }
            }
        }
    }
}

 

In dieser Klasse wird schon alles geregelt, was wir für unsere Kommunikation brauchen. In der Liste subscribers werden alle angemeldeten Clients gespeichert. Diese ist vom Typ IClientFunktions. Wie gesagt muss der Host diese Klasse nicht implementieren, er muss nur wissen, wie er die Clients anzusprechen hat.
In der Funktion Connect wird zuerst ausgelesen, welcher Client sich anmelden möchte. Das geschieht durch :
IClientFunctions callback = OperationContext.Current.GetCallbackChannel();
Anschließend wird der Client in die Liste aufgenommen, falls er nicht schon dort vorhanden ist.
In der Funktion Disconnect wird genau das gleiche gemacht. Jedoch wird der Client hier nicht der Liste hinzugefügt, sondern entfernt.
Die Funktion Send ist auch nicht schwer zu verstehen. Zuerst schauen wir einmal welcher Client eine Nachricht senden möchte und merken ihn uns. In der foreach-Schleife wird die Nachricht nun allen angemeldeten Clients gesendet. Hier benötigen wir auch den Client den wir uns gemerkt haben. Da er uns die Nachricht gesendet hat brauchen wir ihm diese nicht zustellen.  Das ganze kontrollieren wir mit if(client != callback). Der Try-Catch-Block dient zur Fehlererkennung. Falls ein Client seine Verbindung verloren kann die Nachricht nicht an ihn gesendet werden und eine Exception wir ausgelöst. Wir fangen diese ab und tragen den Client, der seine Verbindung verloren hat in eine zweite Liste „subscribersToDelete“ ein. Nachdem allen Clients eine Nachricht zugestellt wurde gehen wir diese Liste durch und löschen alle Clients, die ihre Verbindung verloren haben aus unserer Liste mit angemeldeten Clients.
Nun muss wieder eine App.config dem Host-Projekt hinzugefügt werden. Mit Hilfe des Dienstkonfigurationsedotors erstellt ihr diesmal einen Service an der Adresse net.tcp://localhost:8500/MyHostService  und einem MetaBehavior an http://localhost:8501/MyHostServiceMeta. Anschließend könnt ihr mit dem svcutil wie gewohnt eine Proxy.cs erstellen.

 


 

Proxy.cs

namespace Datalib
{
    using System.Runtime.Serialization;
    
    
    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "3.0.0.0" )]
    [System.Runtime.Serialization.DataContractAttribute(Name="ChatMessage", Namespace="http://schemas.datacontract.org/2004/07/Datalib" )]
    public partial class ChatMessage : object, System.Runtime.Serialization.IExtensibleDataObject
    {
        
        private System.Runtime.Serialization.ExtensionDataObject extensionDataField;
        
        private string MessageField;
        
        private string NameField;
        
        public System.Runtime.Serialization.ExtensionDataObject ExtensionData
        {
            get
            {
                return this.extensionDataField;
            }
            set
            {
                this.extensionDataField = value;
            }
        }
        
        [System.Runtime.Serialization.DataMemberAttribute()]
        public string Message
        {
            get
            {
                return this.MessageField;
            }
            set
            {
                this.MessageField = value;
            }
        }
        
        [System.Runtime.Serialization.DataMemberAttribute()]
        public string Name
        {
            get
            {
                return this.NameField;
            }
            set
            {
                this.NameField = value;
            }
        }
    }
}


[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0" )]
[System.ServiceModel.ServiceContractAttribute(ConfigurationName="IHostFunctions", CallbackContract=typeof(IHostFunctionsCallback))]
public interface IHostFunctions
{
    
    [System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/IHostFunctions/connect", ReplyAction="http://tempuri.org/IHostFunctions/connectResponse" )]
    void connect();
    
    [System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/IHostFunctions/disconnect", ReplyAction="http://tempuri.org/IHostFunctions/disconnectResponse" )]
    void disconnect();
    
    [System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/IHostFunctions/send", ReplyAction="http://tempuri.org/IHostFunctions/sendResponse" )]
    void send(Datalib.ChatMessage cm);
}

[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0" )]
public interface IHostFunctionsCallback
{
    
    [System.ServiceModel.OperationContractAttribute(IsOneWay=true, Action="http://tempuri.org/IHostFunctions/ShowMessage" )]
    void ShowMessage(string message);
}

[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0" )]
public interface IHostFunctionsChannel : IHostFunctions, System.ServiceModel.IClientChannel
{
}

[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0" )]
public partial class HostFunctionsClient : System.ServiceModel.DuplexClientBase, IHostFunctions
{
    
    public HostFunctionsClient(System.ServiceModel.InstanceContext callbackInstance) :
            base(callbackInstance)
    {
    }
    
    public HostFunctionsClient(System.ServiceModel.InstanceContext callbackInstance, string endpointConfigurationName) :
            base(callbackInstance, endpointConfigurationName)
    {
    }
    
    public HostFunctionsClient(System.ServiceModel.InstanceContext callbackInstance, string endpointConfigurationName, string remoteAddress) :
            base(callbackInstance, endpointConfigurationName, remoteAddress)
    {
    }
    
    public HostFunctionsClient(System.ServiceModel.InstanceContext callbackInstance, string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) :
            base(callbackInstance, endpointConfigurationName, remoteAddress)
    {
    }
    
    public HostFunctionsClient(System.ServiceModel.InstanceContext callbackInstance, System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :
            base(callbackInstance, binding, remoteAddress)
    {
    }
    
    public void connect()
    {
        base.Channel.connect();
    }
    
    public void disconnect()
    {
        base.Channel.disconnect();
    }
    
    public void send(Datalib.ChatMessage cm)
    {
        base.Channel.send(cm);
    }
}

 

Nachdem die Proxy.cs dem Client-Projekt hinzugefügt wurde können  wir mit der Oberfläche beginnen. Folgende Elemente fügen wir unserer Form1 hinzu:

  1. TextBox
    Name = txtBx_Chat
    Multiline = true
    Read only = true
    ScrollBars = Vertical
  2. TextBox
    Name = txtBx_Message
  3. TextBox
    Name = txtBx_Name
  4. Button
    Name = btn_Send
    Enabled  = false
  5. Button
    Name = btn_Connect
  6. Button
    Name = btn_Disconnect
    Enabled = false

 
Der Code von Form1.cs sieht wie folgt aus:

Form1.cs

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;
using System.ServiceModel;

namespace Client
{
    public partial class Form1 : Form, IHostFunctionsCallback
    {
        HostFunctionsClient client = null;

        public Form1()
        {
            InitializeComponent();

            InstanceContext context = new InstanceContext(this);
            client = new HostFunctionsClient(context);
        }

        private void btn_Connect_Click(object sender, EventArgs e)
        {
            client.connect();
            this.txtBx_Name.Enabled = false;
            this.btn_Send.Enabled = true;
            this.btn_Connect.Enabled = false;
            this.btn_Disconnect.Enabled = true;
        }

        private void btn_Disconnect_Click(object sender, EventArgs e)
        {
            client.disconnect();
            this.txtBx_Name.Enabled = true;
            this.btn_Send.Enabled = false;
            this.btn_Connect.Enabled = true;
            this.btn_Disconnect.Enabled = false;
        }

        private void btn_Send_Click(object sender, EventArgs e)
        {
            Datalib.ChatMessage cm = new Datalib.ChatMessage();
            cm.Name = this.txtBx_Name.Text;
            cm.Message = this.txtBx_Message.Text;
            if (client.State == CommunicationState.Opened)
            {
                client.send(cm);
            }
            DisplayMessage(this.txtBx_Name.Text + ":t" + this.txtBx_Message.Text);
            this.txtBx_Message.Clear();
        }

        #region IHostFunctionsCallback Member

        public void ShowMessage(string message)
        {
            DisplayMessage(message);
        }

        public void DisplayMessage(string message)
        {
            this.txtBx_chat.Text = this.txtBx_chat.Text + "rn" + message;
            this.txtBx_chat.SelectionStart = this.txtBx_chat.Text.Length;
            this.txtBx_chat.ScrollToCaret();
        }

        #endregion
    }
}

 

Zuerst benutzen wir in unserer Klasse das Interface IHostFunctionsCallback. Dieses wurde in der Proxy.cs aus unserem Interface IHostFunctions generiert und bietet uns die Möglichkeit die Funktionen aus IHostFunctions für unser Callback zu benutzen. Unsere Klasse hat zudem ein Member client vom Typ HostFunctionsClient. Auch diese Klasse wurde in der Proxy.cs generiert und bietet uns später Zugriff auf unseren Host.
Im Konstruktor von Form1 erstellen wir eine Variable der Klasse InstanceContext. Diese Klasse erwartet beim erstellen eine Klasse, die ein Callback implementiert. Da unsere Form1 das bereits macht, können wir diese einfach mit this zuweisen. Das InstanceContext object geben wir nun dem Client mit, damit dieser auch weiß welches Callback wir benutzen wollen.
Mit dem Event btn_Connect_Click melden wir uns nach einem Klick auf unseren Connect-Button bei dem Host an. Wie gesagt können wir den Host über unseren Member client ansprechen.
Bei dem Event btn_Disconnect_Click verhält es sich genauso. Hier melden wir uns allerdings bei dem Host wieder ab.
Mit dem Event btn_Send_Click senden wir unsere Nachricht an den Client. Dazu erstellen wir erst einmal eine Instanz vom Typ ChatMessage, kontrollieren ob eine Verbindung mit dem Host besteht und Senden unsere Nachricht mit client.send an den Host.
Die Funktion ShowMessage ist nun unser Callback. Der Host kann genau diese Funktion für uns aufrufen, wenn er von einem anderen Client eine Nachricht empfangen hat. In der Funktion machen wir letztendlich nicht viel, sondern schreiben diese Nachricht nur in unser Chat-Fenster.





 

 

 

 

29.11.09 11:48