【问题标题】:simplest way to send an object with Tcpclinet c#使用 Tcpclinet c# 发送对象的最简单方法
【发布时间】:2020-02-18 19:24:52
【问题描述】:

我是 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...。它引起问题。不过,问题;你只发送一个有效载荷吗?还是有效载荷流? (这实际上很重要)即你会发送一个对象然后关闭套接字吗?或者您是否正在尝试与多个对象(帧)进行持续对话?
  • 你需要TCP Listener吗?
  • 是的,我需要一个 tcp 侦听器,如果 icant 使用 binaryFormatter,我应该使用什么,它只有一个客户端和一个服务器,我也只发送一个有效负载,即该事务对象,我将更新揭示完整代码的问题
  • 啊,编辑显示问题;给我一秒钟

标签: c# .net tcpclient


【解决方案1】:

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

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

所以;当你这样做时:

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

阅读器消耗超过只是"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
    }
}

【讨论】:

  • 好吧,我会尝试改用 protobuf,非常感谢您抽出宝贵时间,您是我的英雄
  • @Ezio 请记住:这里的真正问题StreamReader 与...其他任何东西的组合;一旦你解决了这一点,any 序列化程序应该可以工作,对于不同的“工作”值
  • 我试着按照你说的做,但这次似乎问题出在反序列化中,在我的事务类中,我有另一个名为“Output”的对象,当 ideszerialize 显示此错误消息时:找不到 myproject.Output 的无参数构造函数,请帮忙!!
  • 找不到 myproject.Output 的无参数构造函数 那么,你有吗?如果没有,你需要添加一个!
  • @Ezio 请记住,我从这里看不到你的!但是:如果有帮助,您可以在 ProtoContractAttribute 上启用 SkipConstructor 标志
【解决方案2】:

不使用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);

【讨论】:

  • @Ezio 这不会解决这里的根本问题 - 给我 2 分钟的时间来完成第二个答案,解释原因(来自您的编辑);但是,我想说这仍然是个好主意!
  • 是强制为所有成员添加[ProtoMember(x)],还是可选
  • 是强制为所有成员添加[ProtoMember(x)],还是可选
  • @Ezio protobuf 需要提示来理解如何对数据进行编码,这就是我们在这里所做的(基本上:它不发送名称,因此它需要一种可靠地将数字映射到字段的方法);你可以也使用[DataContract]/[DataMember(1)]而不是protobuf-net自己的属性,但这给你更少的控制。然而!任何序列化器都应该没问题;如果你不想使用 protobuf-net,那好吧 - 即使 BinaryFormatter 也应该可以工作(但是:请不要!)如果你可以停止做 StreamReader/StreamWriter 的事情 - 这是这里的实际问题(见其他答案)
猜你喜欢
  • 1970-01-01
  • 2013-06-22
  • 1970-01-01
  • 2011-01-21
  • 2010-09-19
  • 1970-01-01
  • 2016-07-13
  • 1970-01-01
  • 2011-09-11
相关资源
最近更新 更多