J'ai récemment écrit un serveur proxy de preuve de concept rapide et sale en C# dans le cadre d'un effort pour obtenir une application web Java pour communiquer avec une application VB6 héritée résidant sur un autre serveur. C'est ridiculement simple:Y a-t-il des modèles bien connus pour le code réseau asynchrone en C#?
Le serveur proxy et les clients utilisent tous deux le même format de message; dans le code que j'utilise une classe ProxyMessage
pour représenter les demandes des clients et des réponses générées par le serveur:
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) { ... }
}
Les messages sont aussi simples que pourrait être: la longueur du corps + le corps du message.
J'ai une classe distincte ProxyClient
qui représente une connexion à un client. Il gère toutes les interactions entre le proxy et un seul client. Je me demande si ce sont des modèles de conception ou des pratiques exemplaires pour simplifier le code standard associé à la programmation de sockets asynchrones? Par exemple, vous devez prendre soin de gérer le tampon de lecture de manière à ne pas perdre accidentellement d'octets, et vous devez suivre jusqu'à quel point vous êtes dans le traitement du message en cours. Dans mon code actuel, je fais tout ce travail dans ma fonction de rappel pour TcpClient.BeginRead
, et gère l'état de la mémoire tampon et l'état actuel du traitement des messages à l'aide de quelques variables d'instance.
Le code de ma fonction de rappel que je transmets à BeginRead
est ci-dessous, ainsi que les variables d'instance pertinentes pour le contexte. Le code semble fonctionner correctement "tel quel", mais je me demande s'il est possible de le refactoriser un peu pour le rendre plus clair (ou peut-être qu'il l'est déjà?).
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
}
}
Jusqu'à présent, ma seule pensée réelle est d'extraire la substance liée tampon dans une classe MessageBuffer
séparée et tout simplement mon rappel de lecture d'ajouter de nouvelles octets à ce qu'ils arrivent. Le MessageBuffer
s'inquiéterait alors de choses comme le BufferState
courant et déclencherait un événement quand il recevrait un message complet, que le ProxyClient
pourrait alors propager plus loin jusqu'au code principal de serveur de mandat, où la demande peut être traitée.
Vous ne disposez pas d'une version open source de ce que vous avez développé pour cela? – Maslow