#c# #sockets #asynchronous #networkstream
#c# #сокеты #асинхронный #networkstream
Вопрос:
В данный момент я работаю над проектом C # server / client и столкнулся со странной проблемой, на решение которой мне потребовалось некоторое время, но я не совсем доволен решением или, точнее, зачем оно мне нужно. По сути, я обнаружил, что для отправки сообщений tcp-сокета объемом более 10 кб целевому локальному хосту требуется временная задержка в 1 мс.
В качестве фона у меня есть сервер на компьютере, к которому подключаются многие клиенты и передают информацию туда и обратно, и все, кажется, работает нормально. У меня также есть клиенты, которые являются локальными для сервера, которые также подключаются. Проблема, с которой я столкнулся, заключалась в том, что когда размеры сообщений превышали ~ 10 кб, сообщения не проходили. Я только сейчас заметил это, потому что размер большинства отправляемых сообщений составлял около 1-2 кб, но клиент, подключенный на том же компьютере, что и сервер (localhost), является скорее клиентом управления и, следовательно, отправляет / получает больше данных.
Реальная проблема заключалась в том, что сервер (отправитель) возвращал true / success из команд отправки C #, которые отправляли данные, однако получатель указал, что дойдет только первый фрагмент (если я буферизую) или вообще ничего. В итоге я включил wireshark и увидел, что, хотя вызовы отправки завершились успешно, никакие данные фактически не будут отправлены по проводу (помня, что это в локальном интерфейсе обратной связи).
Я попытался поиграть с тем, как я отправляю данные, и попробовать все разные вызовы ( NetworkStream.Write / NetworkStream.WriteAsync / Socket.Send / Socket.SendAsync / Socket.BeginSend
) вместе с буферизацией данных или отправкой всего этого за один заход. Казалось, ничто не имело значения, пока я не установил временную задержку между вызовами отправки в моем цикле, тогда все работало отлично (я протестировал поток данных объемом до 1,5 ГБ без проблем).
Я также обнаружил, что задержка менее 1 мс / 10000 тактов снова вызовет проблемы, я бы пропустил так много вызовов send для работы, после чего они просто остановились бы снова. Установка TcpClient.NoDelay
значения true также, похоже, не оказала большого влияния.
Ниже приведен отрывок из моего кода отправки в качестве примера с различными командами отправки, которые я пробовал, и все они имеют абсолютно одинаковое поведение.
// _client is an abstracted/based off a TcpClient object (the target)
byte[] dataBytes = Serializer.SerializeMessage(message);
int bytesSent = 0;
while (bytesSent < dataBytes.Length) {
int bytesToSend = ((dataBytes.Length - bytesSent) < 8192) ? (dataBytes.Length - bytesSent) : 8192;
//_client.Socket.Send(dataBytes, bytesSent, bytesToSend, SocketFlags.Partial);
//_client.NetworkStream.Write(dataBytes, bytesSent, bytesToSend);
//_client.Socket.BeginSend(dataBytes, bytesSent,bytesToSend,SocketFlags.None, ar => {int bytes = ((Socket)ar.AsyncState).EndSend(ar);}, _client.Socket);
_ = _client.NetworkStream.WriteAsync(dataBytes, bytesSent, bytesToSend);
bytesSent = bytesToSend;
Thread.Sleep(TimeSpan.FromTicks(10000));
}
Итак, хотя сейчас это работает нормально, мой вопрос в том, зачем это вообще нужно, исходя из предыдущего опыта, я всегда нахожу, что если вам нужно «отложить», есть что-то еще, что неправильно. Кроме того, без задержки ничего не выдает ошибок, все вызовы сообщают, что они успешно отправили байты, но wireshark показывает, что данные не передаются. Моей единственной мыслью было бы, что что-то в библиотеке обнаруживает, что это локальная цель, и использует какой-то внутренний канал с именем, абстрагированный от меня, и буфер где-то заполняется?
Я пытался искать любую проблему, подобную этой, более недели и не мог найти ничего очевидного, поэтому любая информация была бы высоко оценена.
Ответ №1:
исходя из предыдущего опыта, я всегда нахожу, что если вам нужно «отложить», есть что-то еще, что неправильно.
Правильно. Здесь я подозреваю, что вы предполагаете, что чтение в сокете вернет полное сообщение.
Но TCP / IP является потоковым протоколом и не включает в себя никакого понятия сообщений. Чтобы успешно использовать TCP / IP напрямую, вы должны определить «протокол кадрирования сообщений», чтобы сообщать удаленному хосту, сколько байтов ожидать. Я не вижу, чтобы вы что-то писали перед сообщением, поэтому я подозреваю, что этого не хватает.
Простое решение, которое используют многие люди, — это всегда отправлять 4-байтовое целое число в начале сообщения, указывающее количество байтов, которые будут отправлены. Затем считыватель может выполнить 4-байтовое чтение, за которым следует цикл чтения, пока не будет считано ожидаемое количество байтов.
Комментарии:
1. Таким образом, проблема, похоже, не имеет ничего общего с читающей / принимающей стороной, поскольку вызовы send просто ничего не отправляют по проводам в соответствии с wireshark. Но просто чтобы подтвердить, что мой
Serializer.SerializeMessage(message)
метод фактически сериализует мой объект message в поток битов, который имеет заголовок, включающий идентификатор и байты длины полезной нагрузки спереди. Стресс-тестирование с 1000 подключенными клиентами, отправляющими сообщения одновременно, работает нормально.2. Можете ли вы выполнить минимальное воспроизведение? Я не уверен, что WireShark может видеть трафик локального хоста, поскольку он никогда не попадает на сетевой адаптер.
Ответ №2:
Итак, проблема оказалась проблемой с чем-то на самом компьютере (или, скорее, на ряде наших блоков разработки), что повлияло на любую службу, размещенную / подключенную к привязке локального хоста. Это включало в себя ряд сетевых служб, включая даже IISExpress и IIS, если хостинг был локальным. Это приводит к тому, что любые данные размером более ~ 10 кб никогда не отправляются / не принимаются.
После нескольких дней попыток выяснить, что влияло на подключения к локальному хосту, мы закончили перестройкой наших блоков разработки, и проблема теперь устранена (хотя мы все еще не знаем, почему / что вызвало проблему).