Hi Shak,
Inline:
Do you mean NOT to use the Async callback to reestablish the async?
Actually, I should have written "don't call Begin*" while on a ThreadPool
Thread since it is the Begin* methods that spawn a ThreadPool Thread, not
the End* methods. The reason you shouldn't call into an asynchronous method
from an asynchronous method where both threads will use the ThreadPool is
that it can cause dead-locks because there is a limit to the number of
ThreadPool Threads provided to your application (related to the number of
physical processors). However, for a simple RPC app, especially if there
will be a limited number of client connections, it is acceptable to call
BeginAsync from the AsyncCallback.
Your point 3 in the RPC method below suggests I should loop with a
synchronous method instead of calling BeginAsync again.
If you were to use a synchronous loop you could spawn your own Thread using
a ThreadStart object to prevent dead-locks, but there may be a slight
performance cost. I have not compared the two methods. A problem that you
must work out when using a synchronous loop is that one client should not
have to wait for another client's RPC to complete before it can establish a
connection, so some asynchronous code is still required. Preferably, code
not executing on a ThreadPool Thread.
What I wanted to do was to create a BinaryReader around a NetworkStream
and then call ReadInt32() and then go away, whether there is an int to be
read or not, and get notified when it would be. The solution I've come to
is to use a networkstream's BeginRead to do the wait and then wrap a
BinaryReader around the same networkstream within the callback, since I
now expect there to be data - pretty much how you describe above.
The problem I had with using a BinaryReader.Read* call to block is that it
doesn't work! The BinaryReader doesn't seem to want to wait. You have to
block, as you said, before using the BinaryReader. I use the
TcpClient.Client Socket's Poll method in my framework instead of calling
Stream.ReadBytes.
I was just afraid of blocking the the threadpool callback with ReadInt32,
but it's beginning to seem that some kind of blocking is inevitable
anyway!
Yea, you have to assume that you'll never know how long the delay will be
before a client attempts to connect, and therefore some Thread, somewhere,
must block until it's activated by a client.
I appreciate that but I think that's a bit too advanced for now! I will
look to use the various concepts in there eventually so it is useful.
NP
OK. I do have another question though: What happens if, while reading
bytes off of a network stream more data gets sent? Abstracting away from
the above, say you are expecting a particular message of unknown length.
How do you know when to stop reading bytes? All the examples I've seen
seem to wait till the number of bytes read<=0. Couldn't we get more than
one message accedentally in that scenario? If at all it stops?
A few things here...
1. Only one client will use a any given socket at one time. Therefore, all
asynchronous calls received by the server on a Socket will originate from
the same object instance on the client.
2. If more than one message is sent to the server asynchronously there must
be a mechanism to buffer the messages on the client-side so they are not
mixed together. In TCP you can't sign each packet. The best way around
this in a simple RPC app is to only allow synchronous calls to be made by
the client, enforced on the client, or else you'll have to create a buffer.
3. Many examples and protocols expect the Socket to be closed when the call
is complete. In other words, the client sends a message and closes the
Socket, and the server reads until NetworkStream.Read() returns 0,
indicating that the Socket has been closed and reading is complete. Not a
very robust solution. Your app would have to open and close a Socket on
every call to the server, and the server would have no way of responding to
the client. Some protocols have an end-of-message mark that is sent after
the entire message. This works for simple, text-only protocols where the
range of input is limited and controlled.
4. In my original post I mentioned that my framework sends an Int32 that
describes the incoming message length before the client-side code serializes
each message. This way the server expects exactly 4 bytes describing the
length of the remaining message and 2147483647 bytes maximum for the
remaining message size. The socket does not need to be closed in order to
signal to the reader that the end of the message has been reached.
5. The client must always know the length of the message, in my example. If
you are using serialized objects for communication then it's possible to
just serialize the object and take the Stream's length as the length of the
message. Write the length to the NetworkStream as an Int32 and then write
the MemoryStream that contains the serialized message. If you do not know
the length of the message then you must use one of the methods I mentioned
in point 3 above.
Serialization is quite easy. Here's a simple example that I have written
for you that illustrates a client transport mechanism:
using System;
using System.Collections.Generic;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Net.Sockets;
static class NetworkTransport
{
public static void SendMessage(Message message, NetworkStream
transportStream)
{
if (message == null)
throw new ArgumentNullException("message");
// I recommend encapsulating the transportStream
// within a class instead of using a static class
// as in this example
if (transportStream == null)
throw new ArgumentNullException("transportStream");
// create the stream that will contain the serialized message
using (MemoryStream serializedMessageStream = new MemoryStream(1024))
{
BinaryFormatter formatter = new BinaryFormatter();
// serialize the message to the stream as binary
formatter.Serialize(serializedMessageStream, message);
// get the length of the stream as an Int32
int length = (int) serializedMessageStream.Length;
// convert the length of the stream to an array of bytes
byte[] lengthInBytes = BitConverter.GetBytes(length);
// write the length of the stream to the socket (4 bytes from index 0,
which is 32 bits)
transportStream.Write(lengthInBytes, 0, 4);
// write the entire serialized message stream to the socket
transportStream.Write(serializedMessageStream.ToArray(), 0, length);
// the server must read 4 bytes, convert it to an Int32
// and then read the length of the Int32 from the socket
}
}
}
[Serializable]
class Message
// see System.Runtime.Remoting.Messaging.IMessage for the framework
interface
{
public readonly Dictionary<string, object> Properties = new
Dictionary<string, object>(8);
}
HTH