Laden...

RemotingException: Der RemoteProxy hat keinen Channelempfänger...

Erstellt von Marius_ vor 17 Jahren Letzter Beitrag vor 17 Jahren 3.550 Views
M
Marius_ Themenstarter:in
6 Beiträge seit 2007
vor 17 Jahren
RemotingException: Der RemoteProxy hat keinen Channelempfänger...

Hallo zusammen,

ich hab angefangen mich mit Remoting zu beschäftigen und hab zu Beginn einfach mal nen kleinen Chat zusammengebastelt, an dem sich mehrere Clients anmelden können. Dazu hab ich eine Klassenbibliothek mit den gemeinsamen Interfaces etc. geschrieben und eine Anwendung die als Client als auch als Server eingesetzt werden kann.
Ich kann nun die Anwendung einmal als Server und einmal als Client ausführen und die beiden miteinander verbinden. Soweit funktionierts wunderbar. Doch wenn ich eine Nachricht vom Server zum Client schicke, dann erhalte ich eine RemoteException, die folgendermaßen lautet:
Der Remoteproxy hat keinen Channelempfänger, d.h. der Server besitzt keine registrierten Serverchannel oder die Anwendung hat keinen passenden Clientchannel, um mit dem Server zu kommunizieren.

Die Exception wird auf dem Weg vom Server in der Send-Methode (Z. 184 - 217) zum Client in der OnReceive-Methode (Z.89) ausgelöst.

Ich hab absolut keine Ahnung, was die Exception zu bedeuten hat. Vielleicht könnt ihr mir helfen...

Grüße
Marius

Hier mal der Code des Servers und des Clients:
(Wen das ganze Projekt interessiert, der kann sich die komplette Solution im Anhang runterladen)

Server.cs:


using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Runtime.Remoting.Channels;
using System.Runtime.Serialization.Formatters;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Remoting;

namespace MultiChat.Core
{
	[Serializable]
	public class Server : MarshalByRefObject, IServer
	{
		/// <summary>
		/// Die registrierten Clients
		/// </summary>
		List<IClient> _clients;
		/// <summary>
		/// Der Delegate, der das Senden an die Clients übernimmt
		/// </summary>
		MessageEventHandler _mutlicastSender;
		/// <summary>
		/// Der Remote Channel
		/// </summary>
		TcpChannel _remoteChannel;
		/// <summary>
		/// Der Remote-Name des Channels
		/// </summary>
		string _remoteName;
		/// <summary>
		/// Der Port, über den die Clients mit dem Server kommunizieren
		/// </summary>
		int _remotePort;
		/// <summary>
		/// Der Event-Channel, der alle Events kapselt (Event Channel Design Pattern)
		/// </summary>
		RemoteEventChannel _eventChannel;

		private Server()
		{
			_clients = new List<IClient>();
		}

		public Server(int port, string remoteName)
			: this()
		{
			//Dictionary mit den Channel Daten
			IDictionary channelProperties = new Hashtable();
			channelProperties["port"] = port;
			channelProperties["name"] = remoteName;

			BinaryClientFormatterSinkProvider clientSink =
				new BinaryClientFormatterSinkProvider();
			BinaryServerFormatterSinkProvider serverSink =
				new BinaryServerFormatterSinkProvider();
			
			serverSink.TypeFilterLevel = TypeFilterLevel.Full;

			//Channel erstellen und registrieren
			_remoteChannel = new TcpChannel(channelProperties,
				clientSink, serverSink);
			ChannelServices.RegisterChannel(_remoteChannel, false);

			//Diese Instanz des Servers bereitstellen
			RemotingServices.Marshal(this, "MultiChatRemote");

			_remoteName = remoteName;
			_remotePort = port;

			//Den EventChannel initialisieren
			_eventChannel = new RemoteEventChannel();
			_eventChannel.SendMessage += new MessageEventHandler(this.Client_SendMessageToAll);
		}

		~Server()
		{
			ChannelServices.UnregisterChannel(_remoteChannel);
		}

		#region IServer Member

		/// <summary>
		/// Liste mit allen registrierten Clients
		/// </summary>
		public List<IClient> Clients
		{
			get { return _clients; }
		}

		/// <summary>
		/// Registriert einen neuen Client und gibt eine Client-Instanz zurück
		/// </summary>
		/// <param name="address">Die IP-Adresse des Clients</param>
		/// <param name="name">Der Username des Clients</param>
		/// <param name="client">[out] Der durch die Registrierung erstellte Client</param>
		/// <returns>Wenn die Registrierung am Server erfolgreich war, true, andernfalls false</returns>
		public bool Register(System.Net.IPAddress address, string name, out IClient client)
		{
			//neue Client-Instanz
			Client c = new Client(address, name);

			//Send-Event durch EventChannel kapseln
			c.SendMessage += new MessageEventHandler(_eventChannel.OnSendMessage);
			//Am Send-Delegate, der die Nachricht an die Clients schickt, anmelden
			_mutlicastSender += new MessageEventHandler(c.Receive);

			c.Name = name;
			
			//Neue Client-Instanz an den out-Parameter zuweisen
			client = c;

			_clients.Add(c);

			//Nachricht senden, dass sich ein Client angemeldet hat
			Send(this, new MessageEventArgs(string.Format("{0} ({1}) has registered!",
				c.Name, c.Address), "Server Notification"));
			Console.WriteLine("{0} ({1}) has registered!",
				c.Name, c.Address);

			//erfolgreich
			return true;
		}

		/// <summary>
		/// Entfernt einen Client aus der Empfängerliste
		/// </summary>
		/// <param name="client">Der Client, der entfernt werden soll</param>
		/// <returns>true, falls der Client erfolgreich entfernt wurde, false, falls er gar nicht in der Liste enthalten war</returns>
		public bool Unregister(IClient client)
		{
			if (_clients.Contains(client))
			{
				//Events unregistern
				client.SendMessage -= new MessageEventHandler(Client_SendMessageToAll);
				_mutlicastSender -= new MessageEventHandler(client.Receive);
				//und client aus liste entfernen
				_clients.Remove(client);

				return true;
			}
			else
				return false;
		}

		/// <summary>
		/// Entfernt einen Client aus der Empfängerliste
		/// </summary>
		/// <param name="address">Die IP-Adresse des Clients</param>
		/// <returns></returns>
		public bool Unregister(System.Net.IPAddress address)
		{
			//Alle IP-Adressen vergleichen
			foreach (IClient c in _clients)
			{
				if (c.Address.ToString() == address.ToString())
				{
					//IP-Adresse gefunden, Client entfernen
					return Unregister(c);
				}
			}

			//Client war gar nicht vorhanden
			return false;
		}

		/// <summary>
		/// Tritt ein, wenn ein Client eine Nachricht an alle gesendet hat
		/// </summary>
		/// <param name="sender">Der Client, der die Nachricht abgeschickt hat</param>
		/// <param name="e">Die Event-Daten</param>
		void Client_SendMessageToAll(object sender, MessageEventArgs e)
		{
			//Send-Methode asynchron aufrufen, damit keine Blockade auftritt
			MessageEventHandler callback = new MessageEventHandler(this.Send);
			callback.Invoke(sender, e);
		}

		/// <summary>
		/// Sendet eine Nachricht an alle Clients
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		public void Send(object sender, MessageEventArgs e)
		{
			//HIER NIMMT DAS GANZE ÜBEL SEINEN LAUF.
			/**
			 * Die Nachricht wird an alle Clients, ausser dem der sie los geschickt hat, gesendet.
			 * Sobald dann der Client die Nachricht weiter leiten will, tritt dann in der
			 * OnReceive-Methode des Clients (Client.cs, ca. Zeile 89) eine RemoteException mit 
			 * folgendem Inhalt auf:
			 * 
			 * Der Remoteproxy hat keinen Channelempfänger, 
			 * d.h. der Server besitzt keine registrierten Serverchannel 
			 * oder die Anwendung hat keinen passenden Clientchannel, 
			 * um mit dem Server zu kommunizieren.
			 **/



			//Eigentlich sollte der multicast hier verwendet werden, der schickt
			//aber die Nachricht auch an den Sender zurück.
			//Wenn ihr SCHNELLER ZUR EXCEPTION GELANGEN WOLLLT, einfach die
			// foreach-Schleife kommentieren und den multicast auskommentieren
			
			
			//_mutlicastSender(sender, e);

			//Nachricht an alle Clients schicken, bis auf den der sie abgeschickt hat
			foreach (IClient c in _clients)
			{
				if (c != sender)
				{
					//Client empfängt nachricht
					c.Receive(sender, e);
				}
			}
		}

		#endregion

		#region MarshalByRefObject Member

		public override object InitializeLifetimeService()
		{
			//Unendliche Lebensdauer des Servers
			return null;
		}

		#endregion

		/// <summary>
		/// Der Remote-Name des Channels
		/// </summary>
		public string RemoteName
		{
			get { return _remoteName; }
		}

		/// <summary>
		/// Der Port, über den die Clients mit dem Server kommunizieren
		/// </summary>
		public int RemotePort
		{
			get { return _remotePort; }
		}
	}
}

Clients.cs:


using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Runtime.Remoting.Messaging;

namespace MultiChat.Core
{
	[Serializable]
	public class Client : MarshalByRefObject, IClient
	{
		#region IClient Events

		/// <summary>
		/// Event für den Client, wenn eine Nachricht empfangen wurde
		/// </summary>
		public event MessageEventHandler MessageReceived;
		/// <summary>
		/// Event für den Server, wenn der Client eine Nachricht abgeschickt hat
		/// </summary>
		public event MessageEventHandler SendMessage;

		#endregion

		/// <summary>
		/// Addresse des Clients
		/// </summary>
		IPAddress _address;
		/// <summary>
		/// Username des Clients
		/// </summary>
		string _name;

		public Client(IPAddress address, string name)
		{
			_name = name;
			_address = address;
		}

		/// <summary>
		/// Löst das Receive Event aus
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e">Event-Daten mit Username und Nachricht des Senders</param>
		protected virtual void OnReceive(object sender, MessageEventArgs e)
		{
			//Eine Nachricht wurde empfangen, ggf. weiterleiten
			if (MessageReceived != null)
			{
				/**
				 * HIER SETZT SICH DAS GANZE ÜBEL FORT
				 * 
				 * In der folgenden Zeile gibt's dann die RemoteException:
				 * Der Remoteproxy hat keinen Channelempfänger, 
				 * d.h. der Server besitzt keine registrierten Serverchannel 
				 * oder die Anwendung hat keinen passenden Clientchannel, 
				 * um mit dem Server zu kommunizieren.
				 * 
				 **/

				MessageReceived(sender, e);
			}
		}

		/// <summary>
		/// Löst das Send Event aus
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e">Event-Daten mit Username und Nachricht des Senders</param>
		protected virtual void OnSend(object sender, MessageEventArgs e)
		{
			//Eine Nachricht wurde gesendet, an Server weiterleiten
			if (SendMessage != null)
			{
				SendMessage(sender, e);
			}
		}

		#region IClient Member

		/// <summary>
		/// Leitet eine Nachricht vom Server an den Client weiter
		/// </summary>
		/// <param name="sender">Der Sender</param>
		/// <param name="e">Event-Daten mit Username und Nachricht des Senders</param>
		public void Receive(object sender, MessageEventArgs e)
		{
			//Receive-Event auslösen, d.h. Daten werden zum Client weitergeleitet
			OnReceive(sender, e);
		}

		/// <summary>
		/// Leitet eine Nachricht vom Client an den Server weiter
		/// </summary>
		/// <param name="sender">Der Sender</param>
		/// <param name="e">Event-Daten mit Username und Nachricht des Senders</param>
		public void Send(object sender, MessageEventArgs e)
		{
			//Send-Event auslösen, d.h. Daten werden zum Server weitergeleitet
			OnSend(sender, e);
		}

		/// <summary>
		/// Addresse des Clients
		/// </summary>
		public IPAddress Address
		{
			get { return _address; }
		}

		/// <summary>
		/// Username des Clients
		/// </summary>
		public string Name
		{
			get { return _name; }
			set { _name = value; }
		}

		#endregion

		#region MarshalByRefObject Member

		public override object InitializeLifetimeService()
		{
			//Unendliche Lebensdauer rausholen
			return null;
		}

		#endregion
	}
}

T
512 Beiträge seit 2006
vor 17 Jahren

Das Server und Client in der Exception darf man nicht auf die Struktur des Programms übertragen. Für das Framework ist der Sender ein Client und der Empfänger ein Server, egal wie du das bei dir benennst.

Dein Server öffnet ja bereits einen Channel, auf dem dann die Daten empfangen werden. Aber dein Client öffnet seinerseits keinen Channel, auf dem der Server ihm etwas schicken könnte. Die Aktionen gehen also bloß in eine Richtung: Client -> Server

Damit das auch in die andere Richtung klappt, musst du auch auf dem Client einen Channel registrieren. Das müsste dann schon ausreichen.

e.f.q.

Aus Falschem folgt Beliebiges

M
Marius_ Themenstarter:in
6 Beiträge seit 2007
vor 17 Jahren

Hallo Traumzauberbaum,

danke für Deine Antwort!
Ich habs mal ausprobiert, in der Client-Application nochmal nen TcpChannel aufzumachen und zu registrieren. Der Fehler tritt aber immer noch auf.
Reicht es demnach nicht, wenn ich den Channel einfach so aufmach?


private TcpChannel _channel;
[...]
_channel = new TcpChannel();
ChannelServices.RegisterChannel(_channel, false);

T
512 Beiträge seit 2006
vor 17 Jahren

Ok habs jetzt eben nochmal durchgetestet. Dein Fehler ist nur, dass du den TcpChannel ohne Port erstellst. Ich hätte jetzt auch gedacht, dass er sich dann automatisch einen nimmt.
Also benutz einfach new TcpChannel( 0 ) und es sollte funktionieren, und er sollte sich selbst einen freien Port suchen.

e.f.q.

Aus Falschem folgt Beliebiges

M
Marius_ Themenstarter:in
6 Beiträge seit 2007
vor 17 Jahren

Danke für Dein Tipp! Hab den Channel auf dem Port 0 registriert und vorher noch das TypeFilterLevel auf Full gesetzt:


IDictionary dic = new Hashtable();
dic["port"] = 0;
BinaryServerFormatterSinkProvider sinkProvider =
	new BinaryServerFormatterSinkProvider();
sinkProvider.TypeFilterLevel = TypeFilterLevel.Full;
_channel = new TcpChannel(dic,
	new BinaryClientFormatterSinkProvider(),
	sinkProvider);
_channel.IsSecured = false;
ChannelServices.RegisterChannel(_channel, false);

Jetzt funktionierts perfekt!

Vielen Dank
Marius

M
29 Beiträge seit 2005
vor 17 Jahren

Ich hab versucht das lokal zum Laufen zu kriegen, aber ich bekome trotzdem diesen Fehler.

@Marius - Ich hab deine Codeergänzung im Konstruktor der Client.cs und im OnReceive-Event ausprobiert, aber bei beidem der gleiche Effekt bzw. die gleiche Fehlermeldung:
Der Remoteproxy hat keinen Channelempfänger, d.h. der Server besitzt keine registrierten Serverchannel oder die Anwendung hat keinen passenden Clientchannel, um mit dem Server zu kommunizieren.

Oder läuft diese Anwendung lokal vielleicht gar nicht?

M
Marius_ Themenstarter:in
6 Beiträge seit 2007
vor 17 Jahren

Hallo masmin,
also bisher hab ichs nur lokal ausprobiert. ich werds heute mal noch übers netzwerk ausprobieren. Aber eigentlich müsste es funktionieren. Starte einfach eine Programminstanz als Server (wenn links "Work as: Server" da steht, dann auf "Connect" klicken, andernfalls einmal auf "Client" klicken, dann auf "Connect") und eine Programminstanz als Client und gib mal als Server-Alias "localhost" oder der PC-Name ein. So müsste es eigentlich funktionieren.
Ich hab mal noch die aktuelle Version angehängt....

Grüße
Marius

P.S.: Ich such noch jemand, der mit mir eine Remoting-Application programmieren würde. Der Chat war bisher nur der Einstieg, der lässt sich aber noch verfeinern. Eigentlich hab ich bisher an ein Programm gedacht, dass Musik übers Netzwerk abspielt, d.h. ein Server der Musik hostet und Clients, die die Musik anfragen. Das ist nur so ne Vorstellung, andere Ideen und Vorschläge sind herzlich willkommen. Also wer Lust hat, darf sich gerne melden.