How to pass not simple types using .NET Remoting

I

can't count how many .NET remoting tutorials and book excerpts i read.

Every single time the example involves a chat client passing strings or

some other example where strings, ints, and simple user defined types

are begin passed. I am looking for an example which shows me how to

pass pointer types, like most things in .NET library. For example, I

want to pass a FileInfo or FileStream object from server to client. I

tried that, it didn't work. Someone told me that's because

System.IO.FileStream object, for example, is a pointer to a memory

location which would have meaning on the machine it was instantiated

on. If I pass that pointer to another machine than that pointer would

have no meaning on that machine.

But I did check and FileInfo

object derives from MarshalByRefObject, so I thought that means it

could be used accross machine boundries.

I am trying to write

the simplest and non-dirties of file transfer apps where the client

passes a FileStream to a server, server reads it and saves it localy.

Simple stuff. Cannot figure it out.

Here is a simple client server app where the server accepts a FileInfo object and shows its FileName property, simple. The server also accepts a string object and shows it. The server will have no problems displaying the string or the FileName of a FileInfo object when the programs are run on the same machine, using "tcp://localhost" for connection. Howerver, put the server and client on different machines, and server will show the string no problems, but it will hang when it tries to access FileInfo object:

Client:
-
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.IO;

namespace RemotingTest
{
class Program
{
static void Main(string[] args)
{
TcpChannel tcpChannel = new TcpChannel();
ChannelServices.RegisterChannel(tcpChannel, false);

RemotingConfiguration.RegisterWellKnownClientType(
typeof(ServerInterface),
"tcp://www.server-ip.com/Server"); // change to appropriate address here

ServerInterface server = new ServerInterface();

server.ShowFileInfo("C:\\test.txt"); // works

server.ShowFileInfo(new FileInfo("C:\\test.txt")); // hangs
}
}
}

Server interface:

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;

namespace RemotingTest
{
public class ServerInterface: MarshalByRefObject
{
public void ShowFileInfo(string filename)
{
Console.WriteLine(filename);
}

public void ShowFileInfo(FileInfo fileInfo)
{
Console.WriteLine(fileInfo.Name);
}
}
}

Server:

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

namespace RemotingTest
{
class Program
{
static void Main(string[] args)
{
TcpChannel tcpChannel = new TcpChannel(1024);
ChannelServices.RegisterChannel(tcpChannel, false);

RemotingConfiguration.RegisterWellKnownServiceType(
typeof(ServerInterface),
"Server", WellKnownObjectMode.SingleCall);
}
}
}

[3483 byte] By [nigor] at [2007-12-22]
# 1

Forgot to specify the port in the Client code:

RemotingConfiguration.RegisterWellKnownClientType(
typeof(ServerInterface),
"tcp://www.server-ip.com:1024/Server"); // change to appropriate address here

nigor at 2007-8-30 > top of Msdn Tech,.NET Development,.NET Remoting and Runtime Serialization...
# 2
You don't have to use remoting to pass a file from the client to the server (you can use FileWebRequest for example). But if you must, all you have to do is pass the contents of the file, like byte[]. If the files can be large you should chunk the contents.
LucianBargaoanu at 2007-8-30 > top of Msdn Tech,.NET Development,.NET Remoting and Runtime Serialization...
# 3
I am trying to understand why the above example fails. Yes, transfering files using remoting is not efficient, but just out curiosity, I want to make it work. And I could just send byte[] data from the file, but that wouldnt show off the wonders or .net remoting. This would be me passing another simple type, like string or int. I want a working example with a "pointer" type.
nigor at 2007-8-30 > top of Msdn Tech,.NET Development,.NET Remoting and Runtime Serialization...
# 4
Just for the sake of the conversation, probably you're not able to callback to the client (set port="0" on your client channel). You should set a timeout on your client channel to see the error.
LucianBargaoanu at 2007-8-30 > top of Msdn Tech,.NET Development,.NET Remoting and Runtime Serialization...
# 5
I am behind a router & using Windows Firewall on both client and server machines. I have port 1024 open on both machines. I was thinking that when the whole marchaling process takes place, i.e. when the server contacts the client, maybe some other ports get opened but my router has no port forwarding set up for that.
nigor at 2007-8-30 > top of Msdn Tech,.NET Development,.NET Remoting and Runtime Serialization...
# 6

I also changed the code as follows:

Client:
-

using System;

using System.Collections;

using System.Collections.Generic;

using System.Text;

using System.Runtime.Remoting;

using System.Runtime.Remoting.Channels;

using System.Runtime.Remoting.Channels.Tcp;

using System.IO;

namespace RemotingTest

{

class Program

{

static void Main(string[] args)

{

IDictionary properties = new Hashtable();

BinaryClientFormatterSinkProvider clientSinkProvider = new BinaryClientFormatterSinkProvider();

BinaryServerFormatterSinkProvider serverSinkProvider = new BinaryServerFormatterSinkProvider();

serverSinkProvider.TypeFilterLevel = System.Runtime.Serialization.Formatters.TypeFilterLevel.Full;

properties["name"] = "";

properties["port"] = 0;

properties["typeFilterLevel"] = "Full";

TcpChannel tcpChannel = new TcpChannel(properties, clientSinkProvider, serverSinkProvider);

ChannelServices.RegisterChannel(tcpChannel, false);

RemotingConfiguration.RegisterWellKnownClientType(

typeof(ServerInterface),

"tcp://igor.no-ip.ca:1024/Server"); // change to appropriate address here

ServerInterface server = new ServerInterface();

string text = "";

while((text = Console.ReadLine()) != "Quit")

{

Console.WriteLine(server.ShowText("testing string"));

Console.WriteLine(server.ShowFileInfo(text).Name);

}

Console.Read();

}

}

}

Server Interface:
-

using System;

using System.Collections.Generic;

using System.Text;

using System.IO;

namespace RemotingTest

{

public class ServerInterface: MarshalByRefObject

{

public FileInfo ShowFileInfo(string filename)

{

Console.WriteLine("Filename " + filename + " requested");

return new FileInfo(filename);

}

public string ShowText(string text)

{

Console.WriteLine(text);

return "\"" + text + "\" show on the server.";

}

}

}

Server:

using System;

using System.Collections;

using System.Collections.Generic;

using System.Text;

using System.Runtime.Remoting;

using System.Runtime.Remoting.Channels;

using System.Runtime.Remoting.Channels.Tcp;

namespace RemotingTest

{

class Program

{

static void Main(string[] args)

{

IDictionary properties = new Hashtable();

BinaryServerFormatterSinkProvider serverSinkProvider = new BinaryServerFormatterSinkProvider();

serverSinkProvider.TypeFilterLevel = System.Runtime.Serialization.Formatters.TypeFilterLevel.Full;

properties["name"] = "";

properties["port"] = 1024;

properties["typeFilterLevel"] = "Full";

TcpChannel tcpChannel = new TcpChannel(properties, null, serverSinkProvider);

ChannelServices.RegisterChannel(tcpChannel, false);

RemotingConfiguration.RegisterWellKnownServiceType(

typeof(ServerInterface),

"Server", WellKnownObjectMode.SingleCall);

Console.Read();

}

}

}

Whats interesting is this, server.ShowText works, while server.ShowFileInfo fails. If this was a firewall issue, wouldnt both fail?

P.S. zipped VS2005 code here: http://igor.no-ip.ca/remoting%20test.zip

nigor at 2007-8-30 > top of Msdn Tech,.NET Development,.NET Remoting and Runtime Serialization...
# 7
I've made a test with TcpChannel and it works. Your setup is the problem. Set timeouts on both channels, get the exception, try to debug :). A tool like TcpTrace or Fiddler might help, even more so with the SoapFormatter. If you want to callback on the client you should consider that the system will choose a port. You could temporarily disable the firewall on both machines to make testing easier.
LucianBargaoanu at 2007-8-30 > top of Msdn Tech,.NET Development,.NET Remoting and Runtime Serialization...
# 8
Ahh, indeed. The problem is router's port forwarding is the problem (firewalls were turned off). When I run the code across two machines on a local network everything work fine. My server and client computers are both behind routers and have port 1024 forwarded. So I will poke around and see what other ports get opened. The question I have now is why?

Consider the two methods from ServerInterface:

string ShowText(string text);
FileInfo ShowFileInfo(string filename);

So ShowText() works over network and ShowFileInfo() fails. If additional ports are needed to be opened for ShowFileInfo() method, why?

Thanks!

nigor at 2007-8-30 > top of Msdn Tech,.NET Development,.NET Remoting and Runtime Serialization...
# 9
The difference is in the fact that strings are remoted by value, while FileInfo instances are remoted by reference, i.e. the server needs to call back into the client (where the FileInfo object actually resides) to access any members or properties.
bcristian at 2007-8-30 > top of Msdn Tech,.NET Development,.NET Remoting and Runtime Serialization...
# 10
Mk, after further examintation and tracing using Wireshack (formerly Ethereal) here is what I found.

Firs the setup.

Home computer which has the server:

address: igor.no-ip.ca
local address of nic: 10.0.0.1

Work computer which has the client:

address: pennylane.no-ip.ca
local nic address: 192.168.0.101

Both computers are behind routers with port 1024 open and forwarded.

Basicaly, looking at the soap messages, it looks like when ShowText() method is called, the soap message is relatively small. When ShowFileInfo() method is called, the soap message is big, and one of the entries in that message is address 10.0.0.1, which is the local address of the server, and to the client has no meaning. But, after receiving that message, the client tries to connect to 10.0.0.1 on port 1024!

Here is the trace:


http://img394.imageshack.us/img394/3109/remotingtestyk0.png

-

If you look at server's soap response message (the last one) you will see that item id="ref-17" is set to http://10.0.0.1:1024. The client must be reading this value because the next thing it does is it tries to connect to 10.0.0.1:1024, which, ofcourse, fails.

Here are complete trace files in case you are interested: http://igor.no-ip.ca/remoting%20trace%20test.zip (tcpdump / ethereal format)

VS2005 Solution: http://igor.no-ip.ca/remoting%20test.zip

Any ideas?
nigor at 2007-8-30 > top of Msdn Tech,.NET Development,.NET Remoting and Runtime Serialization...
# 11
I actually figured out the problem. May not be a problem after all, just the way .NET developers designed the library. There is a peculiar channel property called "machineName" which by default you shouln't have to worry about: http://msdn2.microsoft.com/en-us/library/kw7c6kwc(d=ide).aspx#machineName

Setting the property on the server to the current "outside" address solves the problem !!! Here is what I think happens:

When ShowText() is called, nothing special takes place b/c the client knows what to pass and what to get back. Done deal. With ShowFileInfo(), the FileInfo object can incurr additional calls from the client side. That is why in that case the server will return a message with a bunch of stuff and one of them being the server's ip address. Dont know whats the need for that, since the client already knows the server's ip address. But the server is "dumb" enough to put its local address in message, not the wan address. So setting "machineName" to the wan address of the server does the trick. Here is the soap message returned by the server to the client upon ShowFileInfo():

http://img185.imageshack.us/img185/1772/remotingtestworksfu1.png

Note how item id="ref-17 is now set to "igor.no-ip.ca:1024" as opposed to "10.0.0.1:1024". It makes sence now.


So here, in all glory is a working code:
-

Client:

using System;

using System.Collections;

using System.Collections.Generic;

using System.Text;

using System.Runtime.Remoting;

using System.Runtime.Remoting.Channels;

using System.Runtime.Remoting.Channels.Http;

using System.IO;

namespace RemotingTest

{

class Program

{

static void Main(string[] args)

{

IDictionary properties = new Hashtable();

SoapClientFormatterSinkProvider clientSinkProvider = new SoapClientFormatterSinkProvider();

SoapServerFormatterSinkProvider serverSinkProvider = new SoapServerFormatterSinkProvider();

serverSinkProvider.TypeFilterLevel =

System.Runtime.Serialization.Formatters.TypeFilterLevel.Full;

properties["name"] = "";

properties["port"] = 0;

properties["typeFilterLevel"] = "Full";

HttpChannel commChannel = new HttpChannel(properties, clientSinkProvider, serverSinkProvider);

ChannelServices.RegisterChannel(commChannel, false);

RemotingConfiguration.RegisterWellKnownClientType(

typeof(ServerInterface),

"http://igor.no-ip.ca:1024/Server"); // change to appropriate address here

ServerInterface server = new ServerInterface();

string text = "";

while((text = Console.ReadLine()) != "Quit")

{

Console.WriteLine(server.ShowText("testing string"));

Console.WriteLine(server.ShowFileInfo(text).Name);

}

Console.Read();

}

}

}

Server Interface:
--


using System;

using System.Collections.Generic;

using System.Text;

using System.IO;

namespace RemotingTest

{

public class ServerInterface: MarshalByRefObject

{

public FileInfo ShowFileInfo(string filename)

{

Console.WriteLine("Filename " + filename + " requested");

return new FileInfo(filename);

}

public string ShowText(string text)

{

Console.WriteLine(text);

return "\"" + text + "\" show on the server.";

}

}

}



Server:
-


using System;

using System.Collections;

using System.Collections.Generic;

using System.Text;

using System.Runtime.Remoting;

using System.Runtime.Remoting.Channels;

using System.Runtime.Remoting.Channels.Http;

namespace RemotingTest

{

class Program

{

static void Main(string[] args)

{

IDictionary properties = new Hashtable();

SoapServerFormatterSinkProvider serverSinkProvider = new SoapServerFormatterSinkProvider();

serverSinkProvider.TypeFilterLevel =

System.Runtime.Serialization.Formatters.TypeFilterLevel.Full;

properties["name"] = "";

properties["port"] = 1024;

properties["typeFilterLevel"] = "Full";

properties["machineName"] = "igor.no-ip.ca"; // THE MAGIC LINE !!!!

HttpChannel commChannel = new HttpChannel(properties, null, serverSinkProvider);

ChannelServices.RegisterChannel(commChannel, false);

RemotingConfiguration.RegisterWellKnownServiceType(

typeof(ServerInterface),

"Server", WellKnownObjectMode.SingleCall);

Console.Read();

}

}

}

nigor at 2007-8-30 > top of Msdn Tech,.NET Development,.NET Remoting and Runtime Serialization...

.NET Development

Site Classified