Самый простой способ отправить объект с помощью Tcpcl inet c# - PullRequest
2 голосов
/ 18 февраля 2020

я новичок в 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?

Ответы [ 2 ]

3 голосов
/ 18 февраля 2020

Основная проблема здесь связана с тем, что вы смешиваете два разных механизма для чтения и записи в поток, а именно: с использованием StreamReader и отдельного анализатора на основе потока. Это также плохая идея сделать это с StreamWriter, но ... я думаю, что вам это сойдет с рук, хотя это все еще плохая идея.

Проблема в том, что StreamReader является жадным . Когда вы запрашиваете строку, он не читает из потока побайтно, ища \r или \n - он захватывает буфер данных из потока, а затем обрабатывает это , как вы просите об этом . Таким образом, предполагается, что теперь он является единственным владельцем потока.

So; когда вы делаете это:

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
    }
}

1 голос
/ 18 февраля 2020

Есть лот причин не использовать 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);
...