Reputation: 438
I am trying to write a server that will listen on a port, accept an incoming client connection and then put that client connection in a stack to wait for a future message to arrive. Ideally when a message arrives, I'd like to fire an event that then allows the server to perform an action.
I've written almost all of that...I am using AcceptTCPClient()
to actually pick up a new client connection which is fine. I then create a thread, pass the socket to a class that holds the client state and some other data. However the only way I can think to block and wait for a future incoming connection on that thread is to call something like NetworkStream.Read() which then blocks until bytes arrive.
So here is the fundamental problem - I am using Protobuff-net which allows me to deserialise a network stream rather than an individual byte array. So by reading the first couple of bytes, I've got to reset the read, pass the networkstream to the protobuff deserialize method and then continue.
All I really want is a method that blocks until some bytes are detected but doesn't require me to actually read the bytes until I'm ready.
Anyone have any ideas how I could achieve this?
Update
As the comments below suggested, this is not something that seems to be supported by .Net therefore the simplest solution seems to be to use Tsukasa example below which uses async read/write.
I've written it to consume all the bytes on the wire and then pass those bytes to the protobuff Deserialize method.
Not what I wanted but it works fine. Thanks all for the assitance.
private byte[] buffer = new byte[256];
private Socket socket;
private NetworkStream networkStream;
private AsyncCallback callbackRead;
private AsyncCallback callbackWrite;
public Socket Socket
{
get { return socket; }
}
public ClientProxy(Socket clientSocket)
{
socket = clientSocket;
networkStream = new NetworkStream(clientSocket);
callbackRead = new AsyncCallback(OnReadComplete);
callbackWrite = new AsyncCallback(OnWriteComplete);
}
public void ReadAsync()
{
networkStream.BeginRead(buffer, 0, buffer.Length, callbackRead, null);
}
private void OnReadComplete(IAsyncResult ar)
{
int bytesRead = networkStream.EndRead(ar);
if (bytesRead > 0)
{
MemoryStream stream = new MemoryStream(buffer);
Message data;
data = Serializer.DeserializeWithLengthPrefix<Message>(stream, PrefixStyle.Fixed32);
if (data.Type == Chat.Type.User && data.Action == Chat.Action.Add)
{
Communication.RegisterClient(data.From, socket.Handle.ToString());
}
Communication.readMessage(data);
ReadAsync();
}
else
{
networkStream.Close();
socket.Close();
networkStream = null;
socket = null;
}
}
Upvotes: 2
Views: 4038
Reputation: 6552
BeginRead() which would allow you to have an event called when data is ready, that way your process can be in a blocked state where the OS will only awake it when the resource is ready
class Client
{
private byte[] buffer = new byte[256];
private Socket socket;
private NetworkStream networkStream;
private AsyncCallback callbackRead;
private AsyncCallback callbackWrite;
public Client(Socket clientSocket)
{
socket = clientSocket;
networkStream = new NetworkStream(clientSocket);
callbackRead = new AsyncCallback(OnReadComplete);
callbackWrite = new AsyncCallback(OnWriteComplete);
}
public void StartRead()
{
networkStream.BeginRead(buffer, 0, buffer.Length, callbackRead, null);
}
private void OnReadComplete(IAsyncResult ar)
{
int bytesRead = networkStream.EndRead(ar);
if (bytesRead > 0)
{
string s = System.Text.Encoding.ASCII.GetString(buffer, 0, bytesRead);
//do something with complete data here
networkStream.BeginWrite(buffer, 0, bytesRead, callbackWrite, null);
}
else
{
networkStream.Close();
socket.Close();
networkStream = null;
socket = null;
}
}
private void OnWriteComplete(IAsyncResult ar)
{
networkStream.EndWrite(ar);
networkStream.BeginRead(buffer, 0, buffer.Length, callbackRead, null);
}
}
Usage
bool running = true;
IPAddress localAddr = IPAddress.Parse("127.0.0.1");
TcpListener tcpListener = new TcpListener(localAddr, 3000);
tcpListener.Start();
while (running)
{
while (!tcpListener.Pending())
{
Thread.Sleep(10);
}
Socket socket = tcpListener.AcceptSocket();
Client client = new Client(socket);
client.StartRead();
}
Upvotes: 2
Reputation: 101140
You can poll the DataAvailable
property but it's unreliable and inefficient.
A better way is to prefix the protobuf stream with a length prefix. It also allows to do a read without reading the protobuf message. By using a prefix you can invoke a asynchronous read which will return as soon as there is something available (just configure the Read to read only 4 bytes if you are using a length header).
If you don't want to take care of the network operations yourself you can use my apache licensed library. Here is a sample using protobuf-net: http://blog.gauffin.org/2014/06/easy-and-perfomant-clientserver-communication-with-protobuf-net-griffin-framework/
Upvotes: 2