2012-07-03 15 views
6

Estoy tratando de implementar una robusta biblioteca TCP que permita a los usuarios seleccionar un protocolo de aplicación o implementar uno propio y simplemente "conectarlos" al cliente/servidor.¿Cuál es el patrón correcto para implementar el encuadre TCP? ¿Es una pila de filtros?

Por protocolo me refiero simplemente a la capacidad de definir cómo la secuencia debe enmarcarse en mensajes.

Estoy utilizando las bibliotecas integradas de asynch TCP para el resto de la pila y he desarrollado un cliente que genera eventos cada vez que se establece una conexión, se leen o escriben datos o se produce una excepción.

Tengo dos opciones para implementar el protocolo de trama. El primero, que ya está funcionando, es ampliar la clase de cliente y anular el evento de datos recibidos, de modo que esto solo se produzca cuando se haya recibido un mensaje completo. (Es decir, bajo el capó guardo los datos brutos del zócalo y, según el protocolo, decido cuándo tengo un mensaje completo y luego elevo el evento de datos recibidos). Esto es similar a cómo funciona la biblioteca Nito.Asynch.

El problema con este enfoque es que significa que cada nuevo protocolo requiere una nueva implementación del cliente. Prefiero que el cliente mantenga una pila interna de filtros que se pueden agregar o eliminar.

Cuando los datos se reciben en el socket, se pasan al primer filtro que almacena en búfer hasta que se haya decidido transmitir un mensaje completo con el encabezado o los metadatos eliminados. Esto se pasa luego al siguiente filtro en la pila, etc.

De esta manera los filtros se pueden definir/desarrollar independientemente de la biblioteca y se pueden inyectar en el cliente según la configuración (en tiempo de ejecución).

Para lograr esto, pensé en definir los filtros como pares de implementaciones de System.IO.Stream (entrantes y salientes) que son mantenidas internamente por el cliente.

La lectura de datos del socket se escribiría en la secuencia entrante inferior en la pila. Los datos leídos de esa secuencia se escribirían en la siguiente secuencia, etc. hasta que la última transmisión (parte superior de la pila) devuelva datos y el cliente los devuelva. (Mi plan era usar la función CopyTo() de Stream).

Los datos escritos en el cliente se escribirían en la secuencia saliente superior y se copiarían en la pila hasta que la secuencia saliente inferior escriba en el socket subyacente.

Obviamente, hay mucho que considerar y estoy tratando de entender la forma correcta de comportarme como un objeto Stream. Ejemplo: ¿Qué hago cuando alguien llama a Flush() ...?

¿Es esta una buena manera de lograr esto o estoy reinventando la rueda aquí?

La biblioteca Nito.Asynch

Respuesta

1

estoy respondiendo a mi propia pregunta con la esperanza de que mi solución va a conseguir una buena crítica y posiblemente ayudar a alguien más.

Definí dos interfaces para el filtro de protocolo y el marco de datos. (Para ser claro en la terminología evité el paquete de palabras para evitar confusiones con los paquetes como se define en los protocolos de nivel inferior.)

Aunque no es mi intención, creo que esto podría usarse sobre cualquier protocolo de transporte (p. Ej. TCP, serial).

Primero está la definición de un marco de datos. Esto consiste en los "datos" (carga útil) y también cualquier octeto que enmarque los datos del transporte como un "mensaje" atómico.

/// <summary> 
/// A packet of data with some form of meta data which frames the payload for transport in via a stream. 
/// </summary> 
public interface IFramedData 
{ 
    /// <summary> 
    /// Get the data payload from the framed data (excluding any bytes that are used to frame the data) 
    /// i.e. The received data minus protocl specific framing 
    /// </summary> 
    public readonly byte[] Data { get; } 

    /// <summary> 
    /// Get the framed data (payload including framing bytes) ready to send 
    /// </summary> 
    /// <returns>Framed data</returns> 
    public byte[] ToBytes(); 
} 

entonces no es el filtro de protocolo que lee los datos de una fuente (un socket TCP, por ejemplo, o incluso otro filtro si se utilizan en una pila) y escribe datos de nuevo.

El filtro debe leer los datos (incluido el encuadre) y generar un evento DataReceived para cada lectura completa del fotograma. Se accede a la carga a través de la propiedad "Datos" de la instancia IFramedData.

Cuando los datos se escriben en el filtro, deben "enmarcar" adecuadamente y luego elevar el evento DataToSend cada vez que un marco de datos completo está listo para enviar. (En mi caso esto sería inmediata, sino que trató de permitir un protocolo que tal vez envía mensajes de una longitud o tampones de entrada fijado por alguna otra razón, antes de regresar un marco completo listo para enviar.

/// <summary> 
/// A protocol filter can be used to read and write data from/to a Stream and frame/deframe the messages. 
/// </summary> 
/// <typeparam name="TFramedData">The data frame that is handled by this filter</typeparam> 
public interface IProtocolFilter<TFramedData> where TFramedData : IFramedData 
{ 
    /// <summary> 
    /// Should be raised whenever a complete data frame is ready to send. 
    /// </summary> 
    /// <remarks> 
    /// May be raised after a call to <see cref="FlushSend()"/> 
    /// </remarks> 
    public event Action<TFramedData> DataToSend; 

    /// <summary> 
    /// Should be raised whenever a complete data frame has been received. 
    /// </summary> 
    /// <remarks> 
    /// May be raised after a call to <see cref="FlushReceive()"/> 
    /// </remarks> 
    public event Action<TFramedData> DataReceived; 

    /// <summary> 
    /// Should be raised if any data written or read breaks the protocol. 
    /// This could be due to any asynchronous operation that cannot be raised by the calling function. 
    /// </summary> 
    /// <remarks> 
    /// Behaviour may be protocol specific such as flushing the read or write cache or even resetting the connection. 
    /// </remarks> 
    public event Action<Exception> ProtocolException; 

    /// <summary> 
    /// Read data into the recieve buffer 
    /// </summary> 
    /// <remarks> 
    /// This may raise the DataReceived event (possibly more than once if multiple complete frames are read) 
    /// </remarks> 
    /// <param name="buffer">Data buffer</param> 
    /// <param name="offset">Position within the buffer where data must start being read.</param> 
    /// <param name="count">Number of bytes to read.</param> 
    /// <returns></returns> 
    public int Read(byte[] buffer, int offset, int count); 

    /// <summary> 
    /// Write data to the send buffer. 
    /// </summary> 
    /// <remarks> 
    /// This may raise the DataToSend event (possibly more than once if the protocl requires the data is broken into multiple frames) 
    /// </remarks> 
    /// <param name="buffer">Data buffer</param> 
    /// <param name="offset">Position within the buffer where data must start being read.</param> 
    /// <param name="count">Number of bytes to read from the buffer</param> 
    public void Write(byte[] buffer, int offset, int count); 

    /// <summary> 
    /// Flush any data from the receive buffer and if appropriate, raise a DataReceived event. 
    /// </summary> 
    public void FlushReceive(); 

    /// <summary> 
    /// Flush any data from the send buffer and if appropriate, raise a DataToSend event. 
    /// </summary> 
    public void FlushSend(); 
} 

tengo continuación, escribió un contenedor muy simple alrededor de TcpClient que realiza lecturas asynch, escribe y genera eventos cada vez que el filtro en la parte superior de la pila de protocolos genera el evento DataReceived o el filtro en la parte inferior genera el evento DataToSend (también escribo los datos en el socket pero esto permite que la aplicación controle cuándo se envían realmente los datos que escribió al cliente).

+0

Lo único que puedo señalar aquí es que el "IProtocolFilter" no sigue el estándar .net de e Normalmente, utilizas EventHandler y creas una clase que hereda EventArgs. – Peter

Cuestiones relacionadas