|
|
OK, so I've got a small but fairly complicated client/server program,
using a trivial custom communications protocol that I wrote myself.
Basically I want the client program to remotely execute methods in the
server program. And, as you might suspect, it turns out there are 3rd
part libraries for doing that kind of thing automatically.
One such library is Microsoft's "WCF". (No, I have no idea what that
stands for.) The idea, according to what little documentation I can
find, is that you write a C# interface, write some code that implements
that interface, and then you can hook up a client so it can call the
interface methods just as if they were local.
Trouble is, WCF is a huge, sprawling framework with a bazillion bells
and whistles. It's almost impossible to make any sense out of the API
documentation. (For one thing, it's mostly extremely terse.) What's
really lacking is something to explain the high-level abstractions and
give you somewhere to *start*...
Oh, there *is* documentation, mind you. Both from Microsoft themselves
and on various blogs and so on. But they all say something along the
lines of "open Visual Studio, select the WCF Class Library template,
watch it generate eleventy-billion lines of XML configuration file which
isn't explained or documented anywhere on the face of the Internet".
Most horrifyingly of all, it seems that absolutely everything to do with
WCF defaults to turning method calls into XML data [which seems
reasonable enough] and then transmitting it via HTTP [which is the most
absurd thing I've ever heard of]. Almost nowhere does it tell you how to
turn off the completely unnecessary HTTP layer and just run on plain TCP
sockets.
(Seriously. HTTP provides *nothing* which is useful for transferring
transient data. It's just pointless overhead. What the hell is *wrong*
with the world? *Why* must *everything* be kludged to work over HTTP
these days? Sheesh...)
If you walk through the examples, it does eventually work. But the way
it works is bizarre: You've written a C# interface, which lists exactly
what methods your server exposes. And yet, what they want you to do is
enable "metadata exchange (mex)", and then run a command-line tool which
connects to the server, downloads the interface description, and
generates C# code for talking to the server.
Even though the necessary information is ALREADY sitting RIGHT THERE in
that interface you just wrote! WTF?
(I suppose if the server was written by somebody on the other side of
the planet and you don't have the source code, being able to dynamically
look up its API is nice. But for any other use-case... why this much
complexity??)
Sadly, it seems that mex cannot ever be made to operate over TCP, only
HTTP. (Christ knows why...) Also disturbing is that if you use the magic
WCF template, Visual Studio somehow "knows" if your service is running
or not, and automatically starts and stops it when you try to run
clients that use it. This makes me very, very nervous!
Well, you know what? All that stuff I just said? You can completely
ignore that bunch of nonsense. Because it turns out that all you
*actually* need to do is this:
1. Write a C# interface, with the right attributes on it.
2. Write a class in your server that implements this interface.
3. Execute THREE LINES OF CODE in your server's Main() function, telling
it what class implements the interface, and where to listen for clients.
4. Write THREE LINES OF CODE in the client, using the interface type as
a generic parameter. You get passed back a proxy object which implements
the interface - but whenever you call a method, it actually executes on
the server. Transparently. With no effort from you other than writing
THREE LINES OF CODE.
So, yeah. The client and the server both reference the same C# interface
type, and WCF automagically transports arguments and results between
client and server. Other than figuring out how to connect to each other,
there's almost no code involved. In particular, you DO NOT need a
200-line XML file with undocumented contents to make this work!
Now, if something somewhere on the Internet would have just *said* all
that, I could have saved myself many, many hours of head-banging...
Of course, this all means much less code needs to be written. But on the
other hand, I just surrendered control to an external library, so now
it's much harder to work out why things aren't working. ;-)
It seems that, by default, opening a connection creates your proxy
object, but doesn't actually *connect* to the server until you try to
call a method. Which means that if anything goes wrong, instead of
throwing an exception in the connection method, it throws an exception
in some other random place in your application, depending on where you
just happen to try to use the server for the first time. :-S
It's easy enough to fix that one. It's not documented, but there's an
Open() method which forces the connection to be opened *right now*. You
know, so I can decide whether to proceed or not based on whether the
server is *actually* listening to me or not.
Unfortunately, it seems by default, if the server is *not* listening,
the Open() method simply waits forever. This is not very helpful.
Fortunately, you can trivially specify a timeout as a parameter to the
method. So that's OK.
Now the *real* fun begins! You see, I actually want to use this stuff on
a mixture of .NET and Mono. I wrote a small test project to check that I
could get this stuff to work, but I only tried it on .NET, and only on
the local machine. When I tried to modify the *real* application to use
WCF, suddenly I ran into several problems.
The first problem is that Mono *refuses* to listen on all network
interfaces. Won't do it. You can easily to it with .NET, but Mono won't.
https://bugzilla.xamarin.com/show_bug.cgi?id=275
To sumarise: If you ask .NET to listen on "localhost", it will accept
any and all incoming network connections. If you ask Mono to listen to
"localhost", it will *only* accept connections from the local machine.
Under .NET, you can listen on "0.0.0.0" to allow connections from all
configured IP addresses (including loopback). But on Mono, this THROWS
AN EXCEPTION. (!!) Finally, you can listen on one of your machine's
configured IP addresses (assuming you can FIND one...), in which case
only connections to that address are accepted.
To sumarise the summary: Mono can listen to internal connections OR
external connections, but not both at the same time [which is the
*default* action under .NET] Oh, well, unless you explicitly configure
TWO listening addresses...
So it seems an easy hack for that is to listen on whatever
Dns.GetHostName() returns. Now remote connections are allowed. (But not
local ones under Mono. But hey, the real application happens to not need
local connections...)
Next, a rather more strange issue. It seems that if you run my client
and server under .NET, everything is fine. If you run them both under
Mono, everything is fine. But if you run one under .NET and one under
Mono... the connection just hangs forever. (Or times out if you
specified a timeout value.) What the...?
OK, so that this point I'm guessing defaulting shenanigans. And it turns
out I'm right:
https://bugzilla.xamarin.com/show_bug.cgi?id=276
By default, .NET runs TCP with SSL turned on, while Mono defaults to
having SSL turned off. So presumably the client (.NET) is sitting there
waiting for some SSL handshake data, while the server (Mono) has no
intention of sending any such data - and has no idea the client is
waiting for it.
What, no feature negotiation step? :-P
The solution is pretty simple; just explicitly specify whether SSL
should be on or off. (And specify it the same way for both client and
server.) But man, do you have *any idea* how many protocol configuration
options there are?? It's a damned good thing this is the only one who's
default value differs between .NET and Mono, because otherwise I'd have
to explicitly configure every single one of them...!
Hey, wait a sec. Bug #275 is a WCF bug in Mono, and bug #276 is
*another* WCF bug in Mono. Do you suppose #277 is...
https://bugzilla.xamarin.com/show_bug.cgi?id=277
...yes, of course it is. It's *another* WCF bug in Mono. Oh goodie!
Essentially, .NET allows multiple services to listen on the same TCP
port, provided they each have a different URL. (Presumably one component
listens, then when it sees the URL it routes the connection to the
appropriate handler or something...) If you attempt to do this under
Mono, it simply throws an exception at you. Let me say that again: A
perfectly acceptable, working program on .NET summarily throws an
exception if you run it under Mono.
Oh dear. I was actually contemplating *using* that particular feature...
oh well, I guess not! (Makes that part of the URL _completely_ pointless
now though, doesn't it?)
So I deploy my new WCF code, and I hit upon *another* problem: It seems
that after a while, the server disconnects for no apparent reason.
Searching the Internet gives me nothing, until *eventually* I stumble
upon a vague reference to ReceiveTimeout.
Reading the scant documentation, it appears that if the server doesn't
hear anything for this number of seconds, it disconnects the client. The
intention seems to be that a client can't hog server resources by just
connecting and then saying nothing.
OK, a couple of things.
1. I can see how this might be bad if you were offering this service to
the entire world (or maybe just a large internal network). However, I'm
using this for 1 server with 1 client. This is an internal test
framework. It will never be exposed to the outside world. I don't *care*
about resource usage!
2. Doesn't TCP *already* send periodic heartbeat messages and close the
connection if it doesn't get a reply for a while? Why is WCF trying to
duplicate this functionality when it's already present??
Well anyway, all you have to do is go set ReceiveTimeout to some higher
value. (The documentation is inconsistent, but I believe this defaults
to 10 minutes - which is too soon, considering some of my tests take
half an hour.)
Oh, wait a sec... That works on .NET, but under Mono is has ABSOLUTELY
NO EFFECT. Wuh?! O_O
OK, so under Mono it appears to be _impossible_ to stop the server
disconnecting. So there's that. Also, while in the process of doing
this, I found that on .NET you can say AddDefaultEndpoints(), and it
does what the documentation says. However, when you run this under Mono,
it THROWS AN EXCEPTION saying the required method DOES NOT EXIST! :-O
Fortunately, it turns out I don't really need this method. It's trivial
enough to generate the default endpoint by hand. But sheesh... the
entire experience of WCF on Mono just seems to be a steaming pile of
broken. And I really don't know how to fix this disconnect issue; it's
looking like I'll have to write a pile of code to manually send
heartbeat messages to the server just to hold the connection open. (You
know - the EXACT SAME THING that the TCP layer is ALREADY DOING!!)
When you have to write MORE CODE to disable a supposed "feature" of the
framework you're using... yeah, what can you say?
On the plus side, if I ever get this stuff to work, it'll be beautiful.
I especially like the way I can make it so if the server throws an
exception, the client gets the exception (rather than just hang forever
or spit out a socket exception). I suspect this is going to make things
far more reliable...
Post a reply to this message
|
|