Использование Binary Formatter с асинхронными сокетами

Я пытаюсь использовать преимущества асинхронных сокетов в С# с сериализацией/десериализацией с помощью BinaryFormatter, но сталкиваюсь с серверными проблемами. В основном, я продолжаю получать сообщение об ошибке:

No map for object 1953724755

при попытке десериализовать сетевой поток.

Я пытался перейти на синхронный сокет, и это, кажется, работает нормально, но при использовании асинхронного (с BeginRecieve и EndRecieve и т. д.) я все еще получаю эту ошибку.

Вот код, в котором я десериализую:

private void BeginDictionaryRecieve(IAsyncResult ar)
{
    Stream socketStream = new NetworkStream(socketState.workSocket);
    IFormatter dataFormatter = new BinaryFormatter();
    socketState.workSocket.EndReceive(ar);
    List<string> rackthatWasClicked = (List<string>) 
        dataFormatter.Deserialize(socketStream);
    this.recievedListForRackDisplay = rackthatWasClicked;
    OnDataRecieved.Invoke();
    socketState.workSocket.BeginReceive(socketState.buffer, 0, StateObject.BufferSize, 0,new AsyncCallback(BeginDictionaryRecieve), socketState);
}

А вот и код сериализации (синхронно):

IPAddress ipAdd = IPAddress.Parse("127.0.0.1");
TcpClient myClient = new TcpClient();
myClient.Connect(ipAdd, 8002);
Stream clientStream = myClient.GetStream();
IFormatter f = new BinaryFormatter();
f.Serialize(clientStream, ShelfDataToSend);
clientStream.Dispose();
myClient.Dispose();

Я должен отметить, что я использую BinaryFormatter, потому что я не могу найти способ сериализовать объект, отличный от строки, с обычными операциями .Write в сокете.


person Tmello225    schedule 24.05.2019    source источник
comment
Вы получили уведомление о том, что байты были получены в socketState.buffer, но Deserialize() их не использует. Поэтому он переворачивается, когда пытается десериализовать отсутствующие байты. Рассмотрите возможность использования MemoryStream для хранения полученных данных, но вы должны создать его кадр, чтобы знать, когда вы их все получили. Или используйте задачу, чтобы вам не нужен BeginReceive, немного расточительный, но, безусловно, более легкий для начала.   -  person Hans Passant    schedule 24.05.2019
comment
@HansPassant, если я использую MemoryStream, буду ли я собирать в него все полученные байты, а затем пытаться десериализовать MemoryStream? Разрешает ли BinaryFormatter такую ​​​​операцию? Я не могу найти что-либо в документации об этом   -  person Tmello225    schedule 24.05.2019
comment
BinaryFormatter.Deserialize может принимать любой поток. Но вы должны быть уверены, что MemoryStream содержит точное количество байтов, требуемое BinaryFormatter, вот в чем проблема. Фреймирование данных с помощью TCP выполняется путем первой передачи размера byte[], чтобы получатель знал, как часто вызывать Receive, чтобы получить их все. Чтобы узнать этот размер, вы также должны использовать MemoryStream на стороне передачи. Использование Task, чтобы сделать его асинхронным, будет намного проще.   -  person Hans Passant    schedule 24.05.2019
comment
@HansPassant Извините, но не могли бы вы опубликовать пример в качестве ответа? у меня возникли проблемы с отслеживанием   -  person Tmello225    schedule 24.05.2019
comment
Есть много примеров, google c# tcp отправляет массив байтов, чтобы найти их.   -  person Hans Passant    schedule 24.05.2019
comment
@HansPassant Спасибо, чувак, ты очень помог. Я опубликовал ответ, который в итоге сработал для меня, я ценю помощь!   -  person Tmello225    schedule 24.05.2019
comment
Отметьте, что это может работать только в том случае, если вы закрываете/удаляете сокет после вызова BinaryFormatter.Serialize(). Это не обычный сценарий.   -  person Hans Passant    schedule 24.05.2019


Ответы (1)


Благодаря указаниям Ганса я смог найти решение, которое прекрасно работает. Теперь я получаю такие данные:

try
{
    StateObject state = (StateObject) ar.AsyncState;
    Socket client = state.workSocket;

    int bytesRead = client.EndReceive(ar);

    if (bytesRead > 0)
    {
        stream.Write(state.buffer, 0, bytesRead);
        client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(BeginDictionaryRecieve), state);
    }
    else
    {
        if (stream.Length > 1)
        {
            BinaryFormatter formatter = new BinaryFormatter();
            stream.Position = 0;
            this.recievedListForRackDisplay =(List<string>)formatter.Deserialize(stream);
            OnDataRecieved.Invoke();

            client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
                            new AsyncCallback(BeginDictionaryRecieve), state);
        }
    }

}
catch (Exception e)
{
    throw e;
}

Следует также отметить, что это решение работает только в том случае, если клиентский Socket удаляется после отправки его данных.

person Tmello225    schedule 24.05.2019