【问题标题】:How do I send an arbitrary object across network in C#如何在 C# 中通过网络发送任意对象
【发布时间】:2019-06-28 09:49:56
【问题描述】:

我正在尝试使用 C# 中的套接字通过网络发送任意对象。

我已经习惯了 Java 以及它是如何工作的,对于 C# 来说是新手。 在 Java 中,我可以创建一个 Socket,创建一个 OutputObjectStream,然后发送对象。我不需要关心序列化。

另一方面,我也有一个套接字,我创建一个 ObjectInputStream,读取对象,然后我可以将对象转换回原始类型。

Java 示例:

ObjectInputStream inFromClient = new ObjectInputStream (socket.getInputStream());
Object read = inFromClient.readObject();
if(read instanceof bool) {
    // cast read to bool, and call relevant method
} else if(read instanceof UserInfo) {
    // cast read to UserInfo...
} else if(read instanceof CarInfo) {
    // cast read to CarInfo...
}

但是在 C# 中(我使用的是 JSON),据我所知,我需要先将我的 Object 转换为 JSON,然后再转换为 byte[] 然后发送它。另一方面,我从 byte[] 转换为字符串,然后再转换为 JSON。我使用 DataContractJsonSerializer,它需要知道我希望再次取出哪个对象。但我没有那个信息。

我目前的解决方案是每次发送两次。发送的第一个对象是方法名称(表示为枚举),发送的第二个对象是实际对象。根据第一个收到的方法名称,我知道第二个对象应该是什么,并且可以告诉我的 JSON 序列化程序包装器。像这样的:

public static T FromJSON<T>(string json) {
var ms1 = new MemoryStream(Encoding.ASCII.GetBytes(json));
DataContractJsonSerializer dataContractJsonSerializer = new DataContractJsonSerializer(typeof(T));
T sm1 = (T) dataContractJsonSerializer.ReadObject(ms1);
return  sm1;

我可能会优化一点,并创建一个包装类,例如SocketMessage,它将保存枚举方法名称,以及一个字符串类型的参数来保存 JSON 字符串。但这真的是这样做的方法吗?

没有像 Java 那样“简单”的解决方案吗?

不一定非得是 Sockets,但这是我所熟悉的。我只需要一台运行服务器的计算机,以及一台运行客户端的计算机,所以除了 Sockets 之外的其他东西也可以工作。他们将在同一个网络上,所以我认为没有花哨的网络。我需要一个开放的双向连接。

【问题讨论】:

  • 如果你有 JSON 字符串,为什么你只是通过通道发送字符串?
  • 我需要知道将 JSON 字符串反序列化成什么样的对象。我可以把它作为另一个论点。我想知道是否有更简单的方法。
  • 你可以使用 XML,在这种情况下,根元素名称和命名空间将暗示根对象类型。但是,如果您允许输入流完全指定类型,您的应用程序很容易受到小工具注入攻击,如TypeNameHandling caution in Newtonsoft Jsonblackhat.com/docs/us-17/thursday/… 中所述。 (除非ObjectInputStream 以某种方式清理传入的类型,否则您的 Java 应用程序也可能容易受到攻击。)
  • @ste-fu 需要考虑的危险信号:BinaryFormatter 已过时、危险,并且有关于正式将其从 .NET vNext 中删除的讨论
  • “我使用 DataContractJsonSerializer,它需要知道我希望再次取出哪个对象。但我没有那个信息。” - 来自花费大量时间处理网络代码、序列化、RPC 等的人的意见:如果你不知道你在期待什么,你不应该反序列化它;有很多 RPC 堆栈等可以让您以安全的方式执行此操作;坦率地说,这是一个看似复杂的领域——很容易让某些东西运行得足够好,从而成为真正的危险(例如:RCE 危险)

标签: c# json sockets serialization


【解决方案1】:

您可以做一些事情来使这更容易:

  • 将套接字包裹在NetworkStream 中。这使您可以读取/写入流,并使序列化接口更容易一些。
  • 使用Newtonsoft.Json 将您的对象序列化为json。我发现它比DataContractSerializer 更容易使用。此外,您还可以:
  • 在序列化对象中包含类型信息。使用Newtonsoft.Json时,如果序列化中包含类型信息,反序列化时不需要提前知道类型。为此,例如将TypeNameHandling 设置为TypeNameHandling.Object。 (出于安全原因,您不应使用它在网络外进行通信)

把它放在一起:

// 'client' side
using (var socket = new Socket(SocketType.Stream, ProtocolType.IPv6))
using (var networkStream = new NetworkStream(socket))
using (var writer = new StreamWriter(networkStream))
using (var jsonWriter = new JsonTextWriter(writer)) 
{
    socket.Connect("localhost", 8888);
    var user = new UserInfo { Name = "Jesse de Wit" }; // That's me
    var settings = new JsonSerializerSettings()
    {
        TypeNameHandling = TypeNameHandling.Objects
    };
    var serializer = JsonSerializer.Create(settings);
    serializer.Serialize(jsonWriter, user);
}

// 'server' side
using (var socket = new Socket(SocketType.Stream, ProtocolType.IPv6))
using (var networkStream = new NetworkStream(socket))
using (var reader = new StreamReader(networkStream))
using (var jsonReader = new JsonTextReader(reader))
{
    socket.Accept();
    var settings = new JsonSerializerSettings()
    {
        TypeNameHandling = TypeNameHandling.Objects
    };
    var serializer = JsonSerializer.Create(settings);

    // Note that obj will be a JObject if no type information is included.
    object obj = serializer.Deserialize(jsonReader);
    if (obj is UserInfo user)
    {
        // Jesse de Wit 
        Console.WriteLine(user.Name);
    }
}

【讨论】:

【解决方案2】:

假设在c#中你甚至可以使用dynamic作为JSon Parse的结果,通过使用newtonsoft库,所以序列化字符串可能是一个选项,另一方面你可以理解什么是类型通过查看对象的关键属性。

【讨论】:

  • 需要考虑的危险信号:BinaryFormatter 已过时、危险,并且有关于正式将其从 .NET vNext 中删除的讨论
  • @MarcGravell 刚刚删除
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-05
  • 1970-01-01
  • 2011-03-16
  • 2013-08-21
  • 2015-11-13
  • 2013-03-14
相关资源
最近更新 更多