boost::asio изпраща данни по-бързо от получаването през TCP. Или как да деактивирате буферирането

Създадох програма клиент/сървър, клиентът стартира екземпляр на клас Writer, а сървърът стартира екземпляр на клас Reader. След това Writer ще записва DATA_SIZE байта данни асинхронно в Reader на всеки USLEEP мили секунди.

Всяка следваща заявка за async_write от Writer се извършва само ако манипулаторът "при запис" от предишната заявка е бил извикан.

Проблемът е, че ако Writer (клиентът) записва повече данни в сокета, отколкото Reader (сървърът) може да получи, това изглежда е поведението:

  • Writer ще започне да записва в (мисля) системен буфер и въпреки че данните все още не са получени от Reader, той ще извика манипулатора "on write" без грешка.

  • Когато буферът е пълен, boost::asio вече няма да активира манипулатора "при запис", докато буферът стане по-малък.

  • Междувременно Reader все още получава малки части от данни.

  • Фактът, че Reader продължава да получава байтове, след като затворя програмата Writer, изглежда доказва, че тази теория е правилна.

Това, което трябва да постигна, е да предотвратя това буфериране, тъй като данните трябва да бъдат "в реално време" (доколкото е възможно).

Предполагам, че трябва да използвам някаква комбинация от опциите за сокет, които asio предлага, като no_delay или send_buffer_size, но тук просто предполагам, тъй като не съм имал успех в експериментирането с тях.

Мисля, че първото решение, за което човек може да се сети, е да използва UDP вместо TCP. Това ще е така, тъй като ще трябва да премина към UDP и по други причини в близко бъдеще, но първо бих искал да разбера как да го направя с TCP само за имам го направо в главата си, в случай че някой друг ден в бъдеще ще имам подобен проблем.

ЗАБЕЛЕЖКА 1: Преди да започна да експериментирам с асинхронни операции в asio библиотеката, бях внедрил същия този сценарий с помощта на нишки, ключалки и asio::sockets и по това време нямах такова буфериране. Трябваше да премина към асинхронния API, защото asio изглежда не позволява прекъсвания във времето на синхронни повиквания.

ЗАБЕЛЕЖКА 2: Ето работещ пример, който демонстрира проблема: http://pastie.org/3122025

РЕДАКТИРАНЕ: Направих още един тест, в моята ЗАБЕЛЕЖКА 1 споменах, че когато използвах asio::iosockets, не изпитвах това буфериране. Затова исках да съм сигурен и създадох този тест: http://pastie.org/3125452 Оказва се, че < strong>събитието за буфериране е налице с asio::iosockets, така че трябва да е имало нещо друго, което е причинило гладкото протичане, вероятно по-нисък FPS.


person Peter Jankuliak    schedule 04.01.2012    source източник
comment
Звучи като сървър със слаба производителност или бавна/претоварена/дефектна мрежа. Когато получа проблем като този, се опитвам да разделя проблема, като стартирам сървъра и клиента на две машини, свързани с един, усукан кабел (без рутери, без допълнителен трафик), просто за да видя какво ще се случи.   -  person Martin James    schedule 04.01.2012


Отговори (1)


TCP/IP определено е насочен към максимизиране на пропускателната способност, тъй като намерението на повечето мрежови приложения е да прехвърлят данни между хостове. При такива сценарии се очаква, че прехвърлянето на N байта ще отнеме T секунди и очевидно няма значение дали приемникът е малко бавен за обработка на данни. Всъщност, както забелязахте, TCP/IP протоколът прилага плъзгащ се прозорец, който позволява на подателя да буферира някои данни, така че те винаги да са готови за изпращане, но оставя крайния контрол на ограничаването на получателя. Приемникът може да работи на пълна скорост, да се движи сам или дори да постави на пауза предаването.

Ако не се нуждаете от пропускателна способност и вместо това искате да гарантирате, че данните, които подателят ви предава, са възможно най-близки до реалното време, тогава това, от което се нуждаете, е да сте сигурни, че подателят няма да напише следващия пакет, докато не получи потвърждение от приемника, че е обработил предишния пакет данни. Така че вместо сляпо да изпращате пакет след пакет, докато бъдете блокирани, дефинирайте структура на съобщението за контролни съобщения, които да се изпращат обратно от получателя обратно към подателя.

Очевидно при този подход вашият компромис е, че всеки изпратен пакет е по-близък до реалното време на подателя, но вие ограничавате колко данни можете да прехвърлите, докато леко увеличавате общата честотна лента, използвана от вашия протокол (т.е. допълнителни контролни съобщения). Също така имайте предвид, че „близко до реално време“ е относително, защото пак ще се сблъскате със закъснения в мрежата, както и със способността на приемника да обработва данни. Така че можете също така да разгледате ограниченията на дизайна на вашето конкретно приложение, за да определите колко „близко“ наистина трябва да бъдете.

Ако трябва да сте много близо, но в същото време не ви пука дали пакетите се губят, защото старите пакети данни се заместват от нови данни, тогава UDP/IP може да е по-добра алтернатива. Въпреки това, a) ако имате изисквания за надеждна доставка, може да се окаже, че преоткривате част от колелото на tcp/ip и b) имайте предвид, че определени мрежи (корпоративни защитни стени) са склонни да блокират UDP/IP, като същевременно позволяват TCP/IP трафик и c ) дори UDP/IP няма да бъде точно в реално време.

person DXM    schedule 04.01.2012