Recentemente ho scritto un server proxy proof-of-concept veloce e sporco in C# come parte di uno sforzo per ottenere un'applicazione Web Java per comunicare con un'applicazione VB6 legacy che risiede su un altro server. È ridicolmente semplice:Esistono modelli ben noti per il codice di rete asincrono in C#?
Sia il server proxy che i client utilizzano lo stesso formato di messaggio; nel codice che uso una classe ProxyMessage
per rappresentare entrambe le richieste da parte dei clienti e le risposte generati dal server:
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) { ... }
}
I messaggi sono semplici come potrebbe essere: la lunghezza del corpo del messaggio corpo +.
Ho una classe separata ProxyClient
che rappresenta una connessione a un client. Gestisce tutte le interazioni tra il proxy e un singolo client.
Quello che mi chiedo sono sono schemi di progettazione o best practice per la semplificazione del codice boilerplate associato alla programmazione di socket asincroni? Ad esempio, è necessario fare molta attenzione a gestire il buffer di lettura in modo da non perdere accidentalmente byte, e occorre tenere traccia di quanto tempo ci si trova nell'elaborazione del messaggio corrente. Nel mio codice corrente, eseguo tutto questo lavoro nella mia funzione di callback per TcpClient.BeginRead
e gestisco lo stato del buffer e lo stato di elaborazione del messaggio corrente con l'aiuto di alcune variabili di istanza.
Il codice per la funzione di richiamata che sto passando a BeginRead
è al di sotto, insieme alle variabili di istanza rilevanti per il contesto. Il codice sembra funzionare bene "così com'è", ma mi chiedo se può essere rifattorizzato un po 'per renderlo più chiaro (o forse lo è già?).
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
}
}
Finora, il mio unico pensiero è vera per estrarre il materiale tampone legati in una classe separata MessageBuffer
e semplicemente la mia lettura di callback aggiungere i nuovi byte ad esso man mano che arrivano. Lo MessageBuffer
si preoccuperebbe quindi di cose come l'attuale BufferState
e attiverebbe un evento quando riceveva un messaggio completo, che lo ProxyClient
poteva quindi propagare ulteriormente fino al codice del server proxy principale, dove la richiesta può essere elaborata.
non si dispone di una versione open source di ciò che si sta sviluppando per questo disponibile, vero? – Maslow