C # `BinaryReader.ReadBoolean` застрял

#c# #tcp

#c# #tcp

Вопрос:

На стороне клиента:

Что-то вроде моего кода:

 var socket = Connect();
var reader = new BinaryReader(socket.GetStream());
var writer = new BinaryWriter(socket.GetStream());

// In other thread
writer.Write((byte) 5);
writer.Write("C:\no-file.exe");
var exists = reader.ReadBoolean();
 

Застрял в System.Net.Sockets.Socket.Receive :

 // Decompiled with JetBrains decompiler
UnsafeNclNativeMethods.OSSOCK.recv(this.m_Handle.DangerousGetHandle(), numPtr   offset, size, socketFlags);
 

Я вижу пакет с моим логическим значением в WireShark, но ReadBoolean он все еще выполняется и socket.Available равен нулю.

Прототип сервера:

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

 var server = new TcpListener(IPAddress.Any, 2386);
server.Start();

var socket = server.AcceptTcpClient();
socket.NoDelay = true;
Console.WriteLine("Connected: "   socket.Client.RemoteEndPoint);

var stream = socket.GetStream();
var reader = new BinaryReader(stream);
var writer = new BinaryWriter(stream);

while (socket.Connected) {
    while (!stream.DataAvailable);

    if (stream.ReadByte() != 5) {
        break;
    }

    var path = reader.ReadString();
    Console.WriteLine("Requested file "   path);

    var exists = File.Exists(path);
    writer.Write(exists);

    writer.Flush();
    Console.WriteLine(exists ? "File exists" : "Not found");
}

Console.WriteLine("Disconnected: "   socket.Client.RemoteEndPoint);
 

Комментарии:

1. Что именно отправил вам другой конец и как они его сформировали? BinaryReader / BinaryWriter — очень упрямые API, которые в основном совместимы только со своим собственным двойником (т.Е. BinaryReader совместим с BinaryWriter); они не предназначены для обработки потоков общего назначения произвольных протоколов. Кроме того, показанный код создает 2 устройства чтения — это опечатка?

2. Сервер, также использующий BinaryReader и BinaryWriter, ожидает строку и отправляет логическое значение.

3. В каком порядке? И он сбросил? Включен ли NoDelay в сокете? И т.д.; здесь важно много деталей

4. (честно говоря, практически нет сценариев, когда я рекомендую BinaryReader / BinaryWriter; я бы всегда имел дело с необработанным двоичным файлом напрямую, хотя для удобства управления буфером и обратного давления я бы, вероятно, использовал API «pipelines» через сокет)

5. Я скажу, что это проблема сброса… ReadBoolean Настолько простой, насколько это возможно: bool ReadBoolean() => InternalReadByte() != 0; . Большие проблемы с этими классами были, string если я правильно помню

Ответ №1:

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

 // TcpClient thread inside Connect method
while (client.Connected) {
    DispatchPacketType(stream.ReadByte());
}

// Thread that calls Connect
reader.ReadBoolean(); // stuck!
 

Редактировать:

Поэтому я создал WaitPacket метод, чтобы исправить эту проблему. Не лучшее решение, но это работает.

Создание специального метода ожидания было совершенно неправильным, потому что это вызвало много проблем. Единственное нормальное решение — рефакторинг проекта.

В том же случае вы можете попробовать:

  1. Отказ от цикла while для чтения пакетов ( DispatchPacketType ). Теперь клиент отправляет байт типа пакета, его содержимое и считывает ответ сервера тем же методом (например FetchSomeData ), потому что сервер (в моей ситуации) отвечает последовательно.
  2. Избегайте взаимодействия с одним и тем же TCPClient из нескольких потоков или используйте lockers, как указано @500-Внутренняя ошибка сервера в его комментарии. Я переписал много строк кода, и теперь он работает более безопасно и производительно.

Комментарии:

1. Мне это кажется странным: что, если while цикл, описанный выше, запускается сразу после WaitPacket(5) возврата вашего вызова, но до reader.ReadBoolean() того, как получит шанс на выполнение?

2. @500-InternalServerError Я знаю, что это ненадежно, но на данный момент я не видел другого пути. Было бы здорово, если бы вы могли дать мне совет, как правильно получить некоторые данные из сетевого потока в другом потоке.

3. Мой четкий совет — не читать из одного сокета из нескольких потоков — это не вызовет ничего, кроме головной боли. Вместо этого создайте выделенный поток, который считывает фрагменты и помещает их в буфер или несколько буферов, которые можно поместить в очередь. Затем несколько потоков могут заглянуть в очередь и / или выйти из нее после правильной ее блокировки (установить блокировки вокруг каждого доступа к очереди), чтобы потоки не перепутали последовательность данных друг для друга.