Получение всей информации из сокета перед чтением содержимого - PullRequest
1 голос
/ 13 октября 2019

В настоящее время я пытаюсь настроить сервер, который принимает несколько клиентов и может получать и отвечать на сообщения.

Клиент и сервер используют общую библиотеку, которая содержит класс Request, которыйсериализуется и отправляется от клиента к серверу и аналогично в обратном порядке.

Сервер асинхронно прослушивает клиентов на каждом из их сокетов и пытается принять полученные данные и десериализовать данные в класс Request.

Данные отправляются через NetworkStream, используя BinaryFormatter для отправки непосредственно в сокет. Затем полученные данные анализируются с помощью Network Stream на другом конце.

Я пытался использовать MemoryStream для сохранения данных в буфере, а затем десериализовать их, как показано ниже, однако это нет работал. Непосредственная десериализация NetworkStream тоже не сработала.

Поиск вокруг Я не нашел много информации, которая бы работала для моего варианта использования.

Это активный код после сокетовуспешно подключено:

По классу запросов, отправка от клиента:

public void SendData(Socket socket)
{
        IFormatter formatter = new BinaryFormatter();
        Stream stream = new NetworkStream(socket, false);
        formatter.Serialize(stream, this);
        stream.Close();
}

Код сервера, получающий эти данные:

public void Receive(Socket socket)
{
    try
    {
        ReceiveState state = new ReceiveState(socket);
        state.Stream.BeginRead(state.Buffer, 0, ReceiveState.BUFFER_SIZE, new AsyncCallback(DataReceived), state);
    }
    catch (Exception e)
    {
        Logger.LogError(e.ToString());
    }
}

private void DataReceived(IAsyncResult ar)
{
    ReceiveState state = (ReceiveState)ar.AsyncState;
    int bytesRead = state.Stream.EndRead(ar);
    //Resolve Message
    try
    {
        IFormatter formatter = new BinaryFormatter();
        MemoryStream memoryStream = new MemoryStream(state.Buffer, 0, bytesRead);
        Request request = (Request)formatter.Deserialize(memoryStream);
        Logger.Log("Request Received Successfully");
        ResolveRequest(request, state.Socket);
    }
    catch (Exception e)
    {
        Logger.LogError(e.ToString());
    }
    //Resume listening
    Receive(state.Socket);
}

public class ReceiveState
{
    public byte[] Buffer;
    public const int BUFFER_SIZE = 1024;
    public Socket Socket;
    public NetworkStream Stream;

    public ReceiveState(Socket socket)
    {
        Buffer = new byte[BUFFER_SIZE];
        Socket = socket;
        Stream = new NetworkStream(Socket, false);
    }
}

В настоящее время, когда BeginRead()вызывается на NetworkStream Я получаю один байт данных, а затем остальные данные при вызове следующего BeginRead().

например. Сериализованные данные должны быть: 00-01-00-00-00-FF-FF-FF-FF-01-...

Ireceive: 00, за которым следует 01-00-00-00-FF-FF-FF-FF-01-..., который не удается десериализовать.

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

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

1 Ответ

1 голос
/ 13 октября 2019

TCP - это протокол stream , а не пакетный протокол. Это означает, что вы гарантированно получите те же байты в том же порядке (или сбой сети);вы не гарантированно получите их в той же конфигурации чанка. Итак: вам нужно реализовать собственный протокол frameming . Фрейм - это то, как вы разделяете сообщения. Для двоичных сообщений простым протоколом кадрирования может быть «длина = 4 байта с прямым порядком байтов int32, за которым следует {длина} байтов полезной нагрузки», и в этом случае правильный декодер должен буферизоваться до тех пор, пока у вас не будет 4 байта, декодировать длину, буфер{длина} байта, затем декодируйте полезную нагрузку. ВАМ НЕОБХОДИМО НАПИСАТЬ код, который буферизует правильные суммы, и в любой момент вам приходится иметь дело с перечитыванием, обратным буфером и т. Д. Это сложная тема. Честно говоря, многие нюансы решаются с помощью API «конвейеры» (у меня есть многочастное обсуждение этого API здесь ).

Однако, дополнительные указания:

  • никогда используйте BinaryFormatter, особенно для подобных сценариев; будет причинять вам боль, и не хорошо подходит для большинства случаев использования (он также не особенно хорош для сериализатора);моя рекомендация была бы что-то вроде protobuf (возможно, protobuf-net), но я, возможно, предвзятый
  • сетевой код является тонким и сложным, а RPC является в значительной степени «решенной» проблемой;подумайте о том, чтобы попробовать такие инструменты, как gRPC, вместо того, чтобы использовать его самостоятельно; это может быть очень просто
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...