Kürzlich habe ich in C# einen schnellen Proof-of-Concept-Proxy-Server geschrieben, um eine Java-Webanwendung für die Kommunikation mit einer älteren VB6-Anwendung auf einem anderen Server zu erhalten. Es ist lächerlich einfach:Gibt es bekannte Muster für asynchronen Netzwerkcode in C#?
Der Proxy-Server und die Clients verwenden beide das gleiche Nachrichtenformat; im Code verwende ich eine ProxyMessage
Klasse beiden Anfragen von Kunden und Antworten vom Server generierten darzustellen:
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) { ... }
}
Die Nachrichten sind so einfach wie sein könnte: die Länge des Körpers + Nachrichtentextes.
Ich habe eine separate ProxyClient
Klasse, die eine Verbindung zu einem Client darstellt. Es behandelt die gesamte Interaktion zwischen dem Proxy und einem einzelnen Client.
Was ich frage mich, ist, sind sie Design-Muster oder Best Practices zur Vereinfachung der Boilerplate Code mit asynchronen Socket-Programmierung zugeordnet? Sie müssen zum Beispiel sorgfältig den Lesepuffer verwalten, damit Sie nicht versehentlich Bytes verlieren, und Sie müssen verfolgen, wie weit Sie in der Verarbeitung der aktuellen Nachricht sind. In meinem aktuellen Code mache ich all dies in meiner Callback-Funktion für TcpClient.BeginRead
, und den Zustand des Puffers und den aktuellen Status der Nachrichtenverarbeitung mit Hilfe einiger Instanzvariablen verwalten.
Der Code für meine Rückruffunktion, die ich an BeginRead
übergebe, ist unten, zusammen mit den relevanten Instanzvariablen für den Kontext. Der Code scheint gut zu funktionieren, "wie er ist", aber ich frage mich, ob er etwas überarbeitet werden kann, um ihn klarer zu machen (oder vielleicht schon?).
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 >= 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
}
}
Bisher mein einziger Gedanke ist, die Puffer-bezogenes in eine separate MessageBuffer
Klasse zu extrahieren und einfach meine Lese Rückruf neue Bytes anhang müssen, wie sie ankommen. Die MessageBuffer
würde sich dann über Dinge wie die aktuelle BufferState
Gedanken machen und ein Ereignis auslösen, wenn sie eine komplette Nachricht erhalten hat, die dann weiter bis zum Haupt-Proxy-Server-Code propagieren könnte, wo die Anfrage bearbeitet werden kann.
Sie haben nicht zufällig eine Open-Source-Version von dem, was Sie für diese Entwicklung entwickelt haben, würden Sie? – Maslow