2010-02-23 12 views
9

que tienen una aplicación cliente-servidor en el que el servidor y el cliente tienen que enviar y recibir objetos de una clase personalizada través de la red. Estoy usando la clase TcpClient para transmitir los datos. Estoy serializando el objeto en el lado del remitente y enviando la secuencia resultante de bytes al receptor. Pero en el receptor, cuando trato de des-serializar los bytes recibidos, se lanza excepción de serialización y los detalles son:Envío y recepción de objetos personalizados utilizando la clase TCPClient en C#

flujo La entrada no es un formato binario válido . Los contenidos iniciales (en bytes) son: 0D-0A-00-01-00-00-00-FF-FF-FF-01-00-00-00-00-00 ...

Mi código de servidor que serializa el objeto es:

byte[] userDataBytes; 
MemoryStream ms = new MemoryStream(); 
BinaryFormatter bf1 = new BinaryFormatter(); 
bf1.Serialize(ms, new DataMessage()); 
userDataBytes = ms.ToArray(); 
netStream.Write(userDataBytes, 0, userDataBytes.Length); 

el código de cliente que el dE-serializa es:

readNetStream.Read(readMsgBytes, 0, (int)tcpServer.ReceiveBufferSize); 
MemoryStream ms = new MemoryStream(readMsgBytes); 
BinaryFormatter bf1 = new BinaryFormatter(); 
ms.Position = 0; 
object rawObj = bf1.Deserialize(ms); 
DataMessage msgObj = (DataMessage)rawObj; 

por favor me ayude a resolver este problema y posiblemente sugerir cualquier otro método para transmitir objetos de clases personalizadas acr red OSS utilizando TcpClient en C#.

Gracias, Rakesh.

Respuesta

11

Al recibir el del lado del cliente, no sabe la cantidad de datos que desea leer. Sólo está confiando en la ReceiveBufferSize, mientras que sus datos pueden ser más grandes o más pequeños que eso.

creo que el mejor enfoque aquí es el envío de 4 bytes que indica al cliente acerca de la longitud de los datos de entrada:

byte[] userDataLen = BitConverter.GetBytes((Int32)userDataBytes.Length); 
netStream.Write(userDataLen, 0, 4); 
netStream.Write(userDataBytes, 0, userDataBytes.Length); 

y en el extremo recieving que lea primero la longitud de datos y luego leer cantidad exacta de datos.

byte[] readMsgLen = new byte[4]; 
readNetStream.Read(readMsgLen, 0, 4); 

int dataLen = BitConverter.ToInt32(readMsgLen); 
byte[] readMsgData = new byte[dataLen]; 
readNetStream.Read(readMsgData, 0, dataLen); 

De hecho, me acabo de dar cuenta, que usted puede ser que tenga que hacer un poco más para asegurar que lee todos los datos (sólo una idea porque yo no lo he probado, pero sólo en caso de que se encuentra con un problema nuevo se puede prueba esto).

El método NetworkStream.Read() devuelve un número que indica la cantidad de datos que ha leído. Es posible que los datos entrantes sean más grandes que el RecieveBuffer.En ese caso, debe realizar un ciclo hasta que lea todos los datos. Usted tiene que hacer algo como esto:

SafeRead(byte[] userData, int len) 
{ 
    int dataRead = 0; 
    do 
    {  
     dataRead += readNetStream.Read(readMsgData, dataRead, len - dataRead); 

    } while(dataRead < len); 
} 
7

Tenga una mirada en this code. Toma un enfoque ligeramente diferente.

Ejemplo propuesta por el enlace de arriba: - Nota: había otro problema que se enfrentaba la que resuelve aquí (keep-alive). Está en el enlace después del código de muestra inicial.

clase de objetos para enviar (recordar la [Serializable]):

[serializable] 
public class Person { 
    private string fn; 
    private string ln; 
    private int age; 
    ... 
    public string FirstName { 
     get { 
     return fn; 
     } 
     set { 
     fn=value; 
     } 
    } 
    ... 
    ... 
    public Person (string firstname, string lastname, int age) { 
     this.fn=firstname; 
     ... 
    } 
} 

Clase de Objeto de envío:

using System; 
using System.Net; 
using System.Net.Sockets; 
using System.Runtime.Serialization; 
using System.Runtime.Serialization.Formatters.Binary; 

class DataSender 
{ 
    public static void Main() 
    { 
    Person p=new Person("Tyler","Durden",30); // create my serializable object 
    string serverIp="192.168.0.1"; 

    TcpClient client = new TcpClient(serverIp, 9050); // have my connection established with a Tcp Server 

    IFormatter formatter = new BinaryFormatter(); // the formatter that will serialize my object on my stream 

    NetworkStream strm = client.GetStream(); // the stream 
    formatter.Serialize(strm, p); // the serialization process 

    strm.Close(); 
    client.Close(); 
    } 
} 

Clase recibir objeto:

using System; 
using System.Net; 
using System.Net.Sockets; 
using System.Runtime.Serialization; 
using System.Runtime.Serialization.Formatters.Binary; 

class DataRcvr 
{ 
    public static void Main() 
    { 
    TcpListener server = new TcpListener(9050); 
    server.Start(); 
    TcpClient client = server.AcceptTcpClient(); 
    NetworkStream strm = client.GetStream(); 
    IFormatter formatter = new BinaryFormatter(); 

    Person p = (Person)formatter.Deserialize(strm); // you have to cast the deserialized object 

    Console.WriteLine("Hi, I'm "+p.FirstName+" "+p.LastName+" and I'm "+p.age+" years old!"); 

    strm.Close(); 
    client.Close(); 
    server.Stop(); 
    } 
} 
1

TCP es un protocolo basado en flujo (en comparación con el protocolo de datagrama) por lo que es posible recibir sólo una parte de los datos a través de Read Posteados llamada al método.

Para resolver este problema, puede usar el campo DataLength (como cornerback84 sugerido) o puede usar su propia estructura de "paquete a nivel de aplicación".

Por ejemplo, se puede usar algo como esto

|-------------------------------| 
|Begin|DataLength| Data |End| 
| 4b | 4b  | 1..MaxLen|4b | 
|-------------------------------| 

donde Begin - iniciar identificador de paquete (por ejemplo, 0x0A, 0x0B, 0x0C, 0x0D) DATALENGTH - longitud de bloque de datos (por ejemplo, de 0 a MaxLength) Datos: datos reales (clase de persona serializada u otros datos) Identificador de paquete de extremo final (por ejemplo, 0x01, 0x05, 0x07, 0x0F).

Es decir, en el lado del cliente, no solo esperaría los datos entrantes, después de recibir los datos, buscaría en los paquetes de nivel de Aplicación, y podría deserializar la Parte de Datos solo después de recibir un paquete válido.

Cuestiones relacionadas