C# Async TCP-сокеты: обработка размера буфера и огромные передачи
При использовании блокирующего сокета TCP мне не нужно указывать размер буфера. Например:
using (var client = new TcpClient())
{
client.Connect(ServerIp, ServerPort);
using (reader = new BinaryReader(client.GetStream()))
using (writer = new BinaryWriter(client.GetStream()))
{
var byteCount = reader.ReadInt32();
reader.ReadBytes(byteCount);
}
}
Обратите внимание, как удаленный хост мог отправить любое количество байтов.
Однако при использовании асинхронных TCP-сокетов мне нужно создать буфер и таким образом жестко закодировать максимальный размер:
var buffer = new byte[BufferSize];
socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, callback, null);
Я мог бы просто установить размер буфера, скажем, 1024 байта. Это сработает, если мне нужно только получать небольшие порции данных. Но что, если мне нужно получить сериализованный объект размером 10 МБ? Я мог бы установить размер буфера 10*1024*1024... но это потратило бы постоянные 10 МБ ОЗУ на время работы приложения. Это глупо.
Итак, мой вопрос: как я могу эффективно получать большие порции данных с помощью асинхронных TCP-сокетов?
2 ответа
Два примера не эквивалентны - ваш код блокировки предполагает, что удаленный конец отправляет 32-битную длину данных, чтобы следовать. Если тот же протокол действителен для асинхронного - просто прочитайте эту длину (блокирующую или нет), затем выделите буфер и инициируйте асинхронный ввод-вывод.
Изменить 0:
Позвольте мне также добавить, что выделение буферов размера, введенного пользователем, и особенно сетевого ввода, является квитанцией за катастрофу. Очевидной проблемой является атака типа "отказ в обслуживании", когда клиент запрашивает огромный буфер и удерживает его, скажем, отправляет данные очень медленно, и предотвращает другие распределения и / или замедляет работу всей системы.
Общепринятое мнение здесь - принимать фиксированный объем данных за раз и анализировать их на ходу. Это, конечно, влияет на ваш дизайн протокола уровня приложения.
ОТРЕДАКТИРОВАНО
Лучший подход к этой проблеме, найденный мной после длительного анализа, был следующим:
Во-первых, вам нужно установить размер буфера, чтобы получать данные с сервера/клиента.
Во-вторых, вам нужно найти скорость загрузки/выгрузки для этого соединения.
В-третьих, вам необходимо рассчитать, сколько секунд должно длиться время ожидания соединения в соответствии с размером пакета, который необходимо отправить или получить.
Установить размер буфера
Размер буфера может быть установлен двумя способами, произвольно или объективно. Если принимаемая информация имеет текстовую основу, она невелика и не требует сравнения символов, то оптимальным является произвольный заданный размер буфера. Если получаемая информация должна обрабатываться посимвольно и/или в больших объемах, оптимальным выбором является объективный размер буфера.
// In this example I used a Socket wrapped inside a NetworkStream for simplicity
// stability, and asynchronous operability purposes.
// This can be done by doing this:
//
// For server:
//
// Socket server= new Socket();
// server.ReceiveBufferSize = 18000;
// IPEndPoint iPEndPoint = new IPEndPoint(IPAddress.Any, port);
// server.Bind(iPEndPoint);
// server.Listen(3000);
//
//
// NetworkStream ns = new NetworkStream(server);
// For client:
//
// Socket client= new Socket();
// client.Connect("127.0.0.1", 80);
//
// NetworkStream ns = new NetworkStream(client);
// In order to set an objective buffer size based on a file's size in order not to
// receive null characters as extra characters because the buffer is bigger than
// the file's size, or a corrupted file because the buffer is smaller than
// the file's size.
// The TCP protocol follows the Sys, Ack and Syn-Ack paradigm,
// so within a TCP connection if the client or server began the
// connection by sending a message, the next message within its
// connection must be read, and if the client or server began
// the connection by receiving a message, the next message must
// be sent.
// [SENDER]
byte[] file = new byte[18032];
byte[] file_length = Encoding.UTF8.GetBytes(file.Length.ToString());
await Sender.WriteAsync(file_length, 0, file_length.Length);
byte[] receiver_response = new byte[1800];
await Sender.ReadAsync(receiver_response, 0, receiver_response.Length);
await Sender.WriteAsync(file, 0, file.Length);
// [SENDER]
// [RECEIVER]
byte[] file_length = new byte[1800];
await Receiver.ReadAsync(file_length, 0, file_length.Length);
byte[] encoded_response = Encoding.UTF8.GetBytes("OK");
await Receiver.WriteAsync(encoded_response, 0, encoded_response.Length);
byte[] file = new byte[Convert.ToInt32(Encoding.UTF8.GetString(file_length))];
await Receiver.ReadAsync(file, 0, file.Length);
// [RECEIVER]
Буферы, которые используются для получения длины полезной нагрузки, используют произвольный размер буфера. Длина отправляемой полезной нагрузки преобразуется в строку, а затем строка преобразуется в массив байтов в кодировке UTF-8. Полученная длина полезной нагрузки затем преобразуется обратно в формат строки, а затем преобразуется в целое число, чтобы установить длину буфера, который будет получать полезную нагрузку. Длина преобразуется в строку, затем в , а затем в , чтобы избежать повреждения данных из-за того, что информация, относящаяся к длине полезной нагрузки, не будет отправлена в буфер того же размера, что и информация. Когда приемник преобразуетbyte[]
содержимое в строку, а затем вint
, лишние символы будут удалены, а информация останется прежней.
Получите скорость загрузки/выгрузки соединения и рассчитайте размер буфера приема и отправки сокета.
- Во-первых, создайте класс, отвечающий за расчет размера буфера для каждого соединения.
// In this example I used a Socket wrapped inside a NetworkStream for simplicity
// stability, and asynchronous operability purposes.
// This can be done by doing this:
//
// For server:
//
// Socket server= new Socket();
// server.ReceiveBufferSize = 18000;
// IPEndPoint iPEndPoint = new IPEndPoint(IPAddress.Any, port);
// server.Bind(iPEndPoint);
// server.Listen(3000);
//
// NetworkStream ns = new NetworkStream(server);
// For client:
//
// Socket client= new Socket();
// client.Connect("127.0.0.1", 80);
//
// NetworkStream ns = new NetworkStream(client);
class Internet_Speed_Checker
{
public async Task<bool>> Optimum_Buffer_Size(System.Net.Sockets.NetworkStream socket)
{
System.Diagnostics.Stopwatch latency_counter = new System.Diagnostics.Stopwatch();
byte[] test_payload = new byte[2048];
// The TCP protocol follows the Sys, Ack and Syn-Ack paradigm,
// so within a TCP connection if the client or server began the
// connection by sending a message, the next message within its
// connection must be read, and if the client or server began
// the connection by receiving a message, the next message must
// be sent.
//
// In order to test the connection, the client and server must
// send and receive a package of the same size. If the client
// or server began the connection by sending a message, the
// client or server must do the this connection test by
// initiating a write-read sequence, else it must do this
// connection test initiating a read-write sequence.
latency_counter .Start();
await client_secure_network_stream.ReadAsync(test_payload, 0, test_payload.Length);
await client_secure_network_stream.WriteAsync(test_payload, 0, test_payload.Length);
latency_counter .Stop();
int bytes_per_second = (int)(test_payload.Length * (1000 / latency_time_counter.Elapsed.TotalMilliseconds));
int optimal_connection_timeout = (Convert.ToInt32(payload_length) / download_bytes_per_second) * 1000 + 1000;
double optimal_buffer_size_double = ((download_bytes_per_second / 125000) * (latency_time_counter.Elapsed.TotalMilliseconds / 1000)) * 1048576;
int optimal_buffer_size = (int)download_optimal_buffer_size_double + 1024;
// If you want to upload data to the client/server --> client.SendBufferSize = optimal_buffer_size;
// client.SendTimeout = optimal_connection_timeout;
// If you want to download data from the client/server --> client.ReceiveBufferSize = optimal_buffer_size;
// client.ReceiveTimeout = optimal_connection_timeout;
}
}
Вышеупомянутый метод гарантирует, что данные, передаваемые между буфером клиента и буфером сервера, используют соответствующий размер буфера сокета и время ожидания соединения сокета, чтобы избежать повреждения и фрагментации данных. Когда данные отправляются через сокет с асинхронной операцией чтения/записи, длина отправляемой информации будет сегментирована на пакеты. Размер пакета имеет значение по умолчанию, но не учитывает тот факт, что скорость загрузки/выгрузки соединения варьируется. Во избежание повреждения данных и обеспечения оптимальной скорости загрузки/выгрузки соединения размер пакета должен быть установлен в соответствии со скоростью соединения. В вышеупомянутом примере я также продемонстрировал, как рассчитать время ожидания в зависимости от скорости соединения. Размер пакета для загрузки/выгрузки можно установить с помощьюsocket.ReceiveBufferSize = ... / socket.SendBufferSize = ...
соответственно.
Для получения дополнительной информации, связанной с используемыми уравнениями и принципами, проверьте:
https://www.baeldung.com/cs/calculate-internet-speed-ping
. _