用对象作为参数编写TCP侦听器

时间:2011-10-28 17:16:31

标签: c# tcp streamreader streamwriter

我理解如何通过C#中的TCP连接发送对象;我正在使用StreamWriter

如何从另一方接收此物品?

我尝试了StreamReader,但它不包含object类型的参数。

如何做到这一点?

3 个答案:

答案 0 :(得分:1)

IMO最佳解决方案是使用BinaryReader从流中读取。此外,您应该使用BinaryWriter类编写流。

如果您的对象不是基本类型之一,则必须在通过TCP连接发送之前进行序列化。

答案 1 :(得分:0)

我真的可以建议使用ProtoBuf将对象转换为可以通过网络发送的字节(即序列化)。它比内置的二进制序列化器(速度,版本控制)要好得多。

它还有友好的帮助器,用于在写入的数据中包含长度/大小前缀,这在您需要读取接收方的数据时很有用。

一旦建立了连接,以下是我用来进行序列化和网络通信的辅助类:

public class Messenger
{
    private readonly TcpClient client;

    private readonly NetworkStream stream;

    public IPEndPoint RemoteEndPoint { get { return (IPEndPoint) client.Client.RemoteEndPoint; } }

    public Messenger( TcpClient client )
    {
        this.client = client;
        stream = client.GetStream();
    }

    #region Send and Receive
    public TResponse SendReceive<TRequest, TResponse>( TRequest request ) where TRequest : Message where TResponse : Message
    {
        Send( request );
        return Receive<TResponse>();
    }

    public void Send<TRequest>( TRequest request ) where TRequest : Message
    {
        using( var ms = new MemoryStream())
        {
            Serializer.SerializeWithLengthPrefix( ms, request, PrefixStyle.Fixed32 );
            stream.Write( ms.GetBuffer(), 0, (int) ms.Length );
            stream.Flush();
        }
    }

    public TResponse Receive<TResponse>() where TResponse : Message
    {
        try
        {
            return GetMessage<TResponse>();
        }
        catch (Exception ex)
        {
            if (ex is IOException || ex is InvalidOperationException)
            {
                stream.Dispose();
            }
            throw;
        }
    }
    #endregion

    #region Helpers
    private TMessage GetMessage<TMessage>() where TMessage : Message
    {
        int messageLength = BitConverter.ToInt32(GetBytes(stream, 4), 0);
        byte[] data = GetBytes(stream, messageLength);
        using (var ms = new MemoryStream(data))
        {
            return Serializer.Deserialize<TMessage>(ms);
        }
    }

    private static byte[] GetBytes(NetworkStream stream, int length)
    {
        int bytesRequired = length;
        int bytesRead = 0;
        var bytes = new byte[length];
        do
        {
            while( !stream.DataAvailable )
                Thread.Sleep( 100 );                    
            int read = stream.Read(bytes, bytesRead, bytesRequired);
            bytesRequired -= read;
            bytesRead += read;
        }
        while (bytesRequired > 0);
        return bytes;
    }
    #endregion
}

注意:Serializer类来自ProtoBuf库。

答案 2 :(得分:0)

如果您不想或不能使用BinaryFormatter,则必须反序列化对象yoursekf

二进制格式化程序的msdn修改示例:

假设你有模特儿:

[Serializable] //you could also make the class implement ISerializable
class SomeModel
{
    public String Name
    {
        get;
        set;
    }
}

你有你的NetworkStream:

NetworkStream ns;

如何进行序列化部分的基本示例:

void Serialize() 
{
    SomeModel myModel = new SomeModel()
    {
        Name = "mooo"
    };

    // Construct a BinaryFormatter and use it to serialize the data to the stream.
    BinaryFormatter formatter = new BinaryFormatter();
    try 
    {
        formatter.Serialize(ns, myModel);
    }
    catch (SerializationException e) 
    {
        throw e;
    }
}

和反序列化

void Deserialize() 
{
    SomeModel myModel;
    try 
    {
        BinaryFormatter formatter = new BinaryFormatter();

        // Deserialize the object from the stream and 
        // assign the reference to the local variable.
        myModel = (SomeModel) formatter.Deserialize(ns);
    }
    catch (SerializationException e) 
    {
        throw e;
    }
}

有一件事你必须要小心。 您必须知道何时反序列化,您不能只是将流放入其中并获得某些东西,您必须知道您要阅读的内容实际上是序列化的SomeModel类。 我不知道你的代码是怎么样的,所以我不知道这是不是一个问题,如果它是如何避免它。

无论如何,你的第二选择是使用BinaryReader。 您必须创建对象的实例,并手动读取数据。

using(BinaryWriter writer =
    new BinaryWriter(ns))
{
    binWriter.Write(myModel.Name);
}

读数:

 using(BinaryReader reader =
        new BinaryReader(ns))
 {
    try
    {
        SomeModel myModel = new SomeModel();

        myModel.Name = binReader.ReadString();
    }

    // If the end of the stream is reached before reading
    // the four data values, ignore the error and use the
    // default settings for the remaining values.
    catch(EndOfStreamException e)
    {
        Console.WriteLine("{0} caught and ignored. " +
            "Using default values.", e.GetType().Name);
    }
}