#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
метод, чтобы исправить эту проблему. Не лучшее решение, но это работает.
Создание специального метода ожидания было совершенно неправильным, потому что это вызвало много проблем. Единственное нормальное решение — рефакторинг проекта.
В том же случае вы можете попробовать:
- Отказ от цикла while для чтения пакетов (
DispatchPacketType
). Теперь клиент отправляет байт типа пакета, его содержимое и считывает ответ сервера тем же методом (напримерFetchSomeData
), потому что сервер (в моей ситуации) отвечает последовательно. - Избегайте взаимодействия с одним и тем же
TCPClient
из нескольких потоков или используйте lockers, как указано @500-Внутренняя ошибка сервера в его комментарии. Я переписал много строк кода, и теперь он работает более безопасно и производительно.
Комментарии:
1. Мне это кажется странным: что, если
while
цикл, описанный выше, запускается сразу послеWaitPacket(5)
возврата вашего вызова, но доreader.ReadBoolean()
того, как получит шанс на выполнение?2. @500-InternalServerError Я знаю, что это ненадежно, но на данный момент я не видел другого пути. Было бы здорово, если бы вы могли дать мне совет, как правильно получить некоторые данные из сетевого потока в другом потоке.
3. Мой четкий совет — не читать из одного сокета из нескольких потоков — это не вызовет ничего, кроме головной боли. Вместо этого создайте выделенный поток, который считывает фрагменты и помещает их в буфер или несколько буферов, которые можно поместить в очередь. Затем несколько потоков могут заглянуть в очередь и / или выйти из нее после правильной ее блокировки (установить блокировки вокруг каждого доступа к очереди), чтобы потоки не перепутали последовательность данных друг для друга.