Приложение на основе TcpListener, которое плохо масштабируется
У меня есть серверное приложение ECHO на основеTCPListener
, Он принимает клиентов, читает данные и возвращает те же данные. Я разработал его, используя подход async / await, используяXXXAsync
методы, предоставляемые структурой.
Я установил счетчики производительности, чтобы измерить количество входящих и исходящих сообщений и количество подключенных сокетов.
Я создал тестовое приложение, которое запускает 1400 асинхронныхTCPClient
и отправляйте сообщения размером 1 Кб каждые 100-500 мс. Клиенты имеют случайное начало ожидания в начале 10-1000 мс, поэтому они не пытаются подключиться одновременно. У меня хорошо работает, я вижу в PerfMonitor 1400 подключенных, отправляющих сообщения с хорошей скоростью. Я запускаю клиентское приложение с другого компьютера. Процессор сервера и использование памяти очень мало, это Intel Core i7 с 8 ГБ оперативной памяти. Клиент кажется более загруженным, это i5 с 4 Гб оперативной памяти, но все равно даже не 25%.
Проблема в том, что я запускаю другое клиентское приложение. Соединения начинают терпеть неудачу в клиентах. Я не вижу значительного увеличения количества сообщений в секунду (более или менее увеличенного на 20%), но я вижу, что число подключенных клиентов составляет всего около 1900-2100, а не 2800 ожидаемых. Производительность немного снижается, и график показывает большие различия между максимальным и минимальным количеством сообщений в секунду, чем раньше.
Тем не менее, загрузка процессора не составляет даже 40%, а использование памяти все еще мало. Я попытался увеличить количество или пул потоков как на клиенте, так и на сервере:
ThreadPool.SetMaxThreads(5000, 5000);
ThreadPool.SetMinThreads(2000, 2000);
На сервере соединения принимаются в цикле:
while(true)
{
var client = await _server.AcceptTcpClientAsync();
HandleClientAsync(client);
}
HandleClientAsync
функция возвращаетTask
, но, как вы видите, цикл не ожидает обработки, просто продолжает принимать другого клиента. Эта функция обработки выглядит примерно так:
public async Task HandleClientAsync(TcpClient client)
{
while(ws.Connected && !_cancellation.IsCancellationRequested)
{
var msg = await ReadMessageAsync(client);
await WriteMessageAsync(client, msg);
}
}
Эти две функции только читают и записывают поток асинхронно.
Я видел, я могу начатьTCPListener
указывая наbacklog
сумма, но каково значение по умолчанию?
Почему может быть причина, по которой приложение не масштабируется, пока не достигнет максимальной загрузки ЦП?
Каким будет подход и инструменты, чтобы выяснить, в чем собственно проблема?
ОБНОВИТЬ
Я попробовалTask.Yield
а такжеTask.Run
подходы, и они не помогли.
Это также происходит с сервером и клиентом, работающими локально на одном компьютере. Увеличение количества клиентов или сообщений в секунду фактически снижает пропускную способность сервиса. 600 клиентов, отправляющих сообщение каждые 100 мс, генерируют большую пропускную способность, чем 1000 клиентов, отправляющих сообщение каждые 100 мс.
Исключений, которые я вижу на клиенте при подключении более чем 2000 клиентов, два. Приблизительно с 1500 я вижу исключения в начале, но клиенты наконец соединяются. С более чем 1500 я вижу много подключения / отключения:
«Существующее соединение было принудительно закрыто удаленным узлом» (System.Net.Sockets.SocketException) A System.Net.Sockets.SocketException было обнаружено: «Существующее соединение было принудительно закрыто удаленным узлом»
«Невозможно записать данные в транспортное соединение: существующее соединение было принудительно закрыто удаленным хостом». (System.IO.IOException) Возникло исключение System.IO.IOException: «Невозможно записать данные в транспортное соединение: существующее соединение было принудительно закрыто удаленным хостом».
ОБНОВЛЕНИЕ 2
Я создал оченьпростой проект с сервером и клиентом с использованием async / await и он масштабируется, как и ожидалось.
Проект, где у меня есть проблема масштабируемостиэтот сервер WebSocketи даже когда он использует тот же подход, очевидно, что-то вызывает споры. Eстьконсольное приложение, содержащее компоненти консольное приложение длягенерировать нагрузку (хотя для этого требуется как минимум Windows 8).
Обратите внимание, что я прошу не ответ, чтобы решить проблему напрямую, а методы и подходы, чтобы выяснить, что является причиной этого спора.