Note that there are some explanatory texts on larger screens.

plurals
  1. POAre there well-known patterns for asynchronous network code in C#?
    text
    copied!<p>I recently wrote a quick-and-dirty proof-of-concept proxy server in C# as part of an effort to get a Java web application to communicate with a legacy VB6 application residing on another server. It's ridiculously simple:</p> <p>The proxy server and clients both use the same message format; in the code I use a <code>ProxyMessage</code> class to represent both requests from clients and responses generated by the server:</p> <pre><code>public class ProxyMessage { int Length; // message length (not including the length bytes themselves) string Body; // an XML string containing a request/response // writes this message instance in the proper network format to stream // (helper for response messages) WriteToStream(Stream stream) { ... } } </code></pre> <p>The messages are as simple as could be: the length of the body + the message body.</p> <p>I have a separate <code>ProxyClient</code> class that represents a connection to a client. It handles all the interaction between the proxy and a single client.</p> <p>What I'm wondering is are they are design patterns or best practices for simplifying the boilerplate code associated with asynchronous socket programming? For example, you need to take some care to manage the read buffer so that you don't accidentally lose bytes, and you need to keep track of how far along you are in the processing of the current message. In my current code, I do all of this work in my callback function for <code>TcpClient.BeginRead</code>, and manage the state of the buffer and the current message processing state with the help of a few instance variables.</p> <p>The code for my callback function that I'm passing to <code>BeginRead</code> is below, along with the relevant instance variables for context. The code seems to work fine "as-is", but I'm wondering if it can be refactored a bit to make it clearer (or maybe it already is?). </p> <pre><code>private enum BufferStates { GetMessageLength, GetMessageBody } // The read buffer. Initially 4 bytes because we are initially // waiting to receive the message length (a 32-bit int) from the client // on first connecting. By constraining the buffer length to exactly 4 bytes, // we make the buffer management a bit simpler, because // we don't have to worry about cases where the buffer might contain // the message length plus a few bytes of the message body. // Additional bytes will simply be buffered by the OS until we request them. byte[] _buffer = new byte[4]; // A count of how many bytes read so far in a particular BufferState. int _totalBytesRead = 0; // The state of the our buffer processing. Initially, we want // to read in the message length, as it's the first thing // a client will send BufferStates _bufferState = BufferStates.GetMessageLength; // ...ADDITIONAL CODE OMITTED FOR BREVITY... // This is called every time we receive data from // the client. private void ReadCallback(IAsyncResult ar) { try { int bytesRead = _tcpClient.GetStream().EndRead(ar); if (bytesRead == 0) { // No more data/socket was closed. this.Dispose(); return; } // The state passed to BeginRead is used to hold a ProxyMessage // instance that we use to build to up the message // as it arrives. ProxyMessage message = (ProxyMessage)ar.AsyncState; if(message == null) message = new ProxyMessage(); switch (_bufferState) { case BufferStates.GetMessageLength: _totalBytesRead += bytesRead; // if we have the message length (a 32-bit int) // read it in from the buffer, grow the buffer // to fit the incoming message, and change // state so that the next read will start appending // bytes to the message body if (_totalBytesRead == 4) { int length = BitConverter.ToInt32(_buffer, 0); message.Length = length; _totalBytesRead = 0; _buffer = new byte[message.Length]; _bufferState = BufferStates.GetMessageBody; } break; case BufferStates.GetMessageBody: string bodySegment = Encoding.ASCII.GetString(_buffer, _totalBytesRead, bytesRead); _totalBytesRead += bytesRead; message.Body += bodySegment; if (_totalBytesRead &gt;= message.Length) { // Got a complete message. // Notify anyone interested. // Pass a response ProxyMessage object to // with the event so that receivers of OnReceiveMessage // can send a response back to the client after processing // the request. ProxyMessage response = new ProxyMessage(); OnReceiveMessage(this, new ProxyMessageEventArgs(message, response)); // Send the response to the client response.WriteToStream(_tcpClient.GetStream()); // Re-initialize our state so that we're // ready to receive additional requests... message = new ProxyMessage(); _totalBytesRead = 0; _buffer = new byte[4]; //message length is 32-bit int (4 bytes) _bufferState = BufferStates.GetMessageLength; } break; } // Wait for more data... _tcpClient.GetStream().BeginRead(_buffer, 0, _buffer.Length, this.ReadCallback, message); } catch { // do nothing } } </code></pre> <p>So far, my only real thought is to extract the buffer-related stuff into a separate <code>MessageBuffer</code> class and simply have my read callback append new bytes to it as they arrive. The <code>MessageBuffer</code> would then worry about things like the current <code>BufferState</code> and fire an event when it received a complete message, which the <code>ProxyClient</code> could then propagate further up to the main proxy server code, where the request can be processed.</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload