使用 Tcpclinet c# 发送对象的最简单方法

simplest way to send an object with Tcpclinet c#

我是 c# 的新手,我想知道使用 tcpClient 发送对象的最简单方法是什么,我尝试了以下代码,但它抛出了一个奇怪的错误 客户端代码:

TcpClient client = new TcpClient(ip, port);
StreamWriter writer = new StreamWriter(client.GetStream());
NetworkStream strm = client.GetStream();
BinaryFormatter formatter = new BinaryFormatter();
Transaction tx = new transaction();

string msg = string.Empty;

msg = "transaction";
writer.WriteLine(msg);
writer.Flush();

formatter.Serialize(strm,tx);

在接收端

服务器代码:

while(true){
TcpClient client = server.AcceptTcpClient();
IFormatter formatter = new BinaryFormatter();
NetworkStream strm = client.GetStream();
StreamReader reader = new StreamReader(client.GetStream());
string msg = string.Empty;
while (!((msg = reader.ReadLine()).Equals("exit"))){

Transaction tx = (Transaction)formatter.Deserialize(strm);

}

它在服务器上产生这个错误

input stream is not a valid ibinary format intital content is :0c-02-00-00 .....

有人可以帮助我吗,或者是否有另一种使用 tcpclient 发送对象的简单干净的方法?

很多 的理由不使用 BinaryFormatter如果这是我(我对此有偏见),我会切换到类似 protobuf-net 的东西,它与 BinaryFormatter 没有相同的问题,但是: 它确实需要对你的类型进行一些调整——通常用一些属性来注释它们以帮助库,例如:

public class Transaction {
    public int Id {get;set;}
    public string Name {get;set;}
}

可能会变成

[ProtoContract]
public class Transaction {
    [ProtoMember(1)]
    public int Id {get;set;}
    [ProtoMember(2)]
    public string Name {get;set;}
}

之后,代码应该只是:

Serializer.Serialize(strm, tx); // this is ProtoBuf.Serializer
// and now close the "send" pipe; fine to leave the "receive" pipe, though

Serializer.Deserialize<Transaction>(strm); // again, ProtoBuf.Serializer

但是! 据我所知,问题中显示的代码应该可以正常工作,除非发生奇怪的事情。


注意:如果您要发送 多个 有效载荷(即您需要将其分成帧),或者您不想担心关闭发送管道, 那么:

Serializer.SerializeWithLengthPrefix(strm, tx, PrefixStyle.Base128);

Serializer.DeserializeWithLengthPrefix<Transaction>(strm, PrefixStyle.Base128);

这里的根本问题与您混合使用两种不同的机制来读取和写入流的方式有关,特别是:使用 StreamReader 一个单独的基于流的解析器。使用 StreamWriter 执行此操作 是一个坏主意,但是......我认为你可以侥幸逃脱,尽管它仍然是一个坏主意。

这里的问题就是StreamReader贪心。当您向它请求一行时,它不会逐字节从流中读取以查找 \r\n - 它会获取 缓冲区 来自流的数据,然后根据您的要求处理它。通过这种方式,它假定它现在是流的 唯一所有者

所以;当你这样做时:

while (!((msg = reader.ReadLine()).Equals("exit"))){
   Transaction tx = (Transaction)formatter.Deserialize(strm);
}

reader 消耗 多于 "transaction\r\n" - 它消耗那行 和一些未定义的字节数在 之后。然后,当 BinaryFormatter 尝试读取流时,它发现自己读了一半的消息,并在一阵火花中爆炸。

理想情况下,限制自己使用一种序列化机制。意思:输StreamReader/StreamWriter完全这里

如果我可以提出一个使用 protobuf-net 和继承的替代机制:

[ProtoContract]
[ProtoInclude(1, typeof(ShutdownMessage))]
[ProtoInclude(2, typeof(TransactionMessage))]
public abstract class MessageBase {} 

[ProtoContract]
public sealed class ShutdownMessage : MessageBase {}

[ProtoContract]
public sealed class TransactionMessage : MessageBase {
    // your data here
}

现在您可以发送任意数量的消息:

public void Send(MessageBase message) {
    Serializer.SerializeWithLengthPrefix(strm, message, PrefixStyle.Base128);
}

收到任意数量的消息:

while (true) {
    var msg = Serializer.DeserializeWithLengthPrefix<MessageBase>(strm, PrefixStyle.Base128);
    if (msg is null || msg is ShutdownMessage) break; // all done
    switch (msg)
    {
        case TransactionMessage tx: ProcessTransaction(tx); break;
        // etc
    }
}