Когато неблокиращ send() прехвърля само частични данни, можем ли да предположим, че ще върне EWOULDBLOCK при следващото извикване?

Два случая са добре документирани в man страниците за неблокиращи сокети:

  • Ако send() върне същата дължина като буфера за прехвърляне, цялото прехвърляне е приключило успешно и сокетът може или не може да бъде в състояние на връщане EAGAIN/EWOULDBLOCK на следващото извикване с >0 байта към трансфер.
  • Ако send() върне -1 и errno е EAGAIN/EWOULDBLOCK, нито едно прехвърляне не е приключило и програмата трябва да изчака, докато сокетът е готов за повече данни (EPOLLOUT в случай на epoll).

Това, което не е документирано за неблокиращи сокети е:

  • Ако send() връща положителна стойност, по-малка от размера на буфера.

Безопасно ли е да се предположи, че send() ще върне EAGAIN/EWOULDBLOCK дори още един байт данни? Или трябва неблокираща програма да опита да изпрати() още веднъж, за да получи окончателен EAGAIN/EWOULDBLOCK? Притеснявам се да поставя наблюдател на EPOLLOUT на сокета, ако той всъщност не е в състояние „ще блокира“, за да реагира на излизането му.

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


person David Timothy Strauss    schedule 15.10.2013    source източник
comment
@Damon вашата редакция напълно промени смисъла на въпроса.   -  person user207421    schedule 16.10.2013
comment
@EJP: OP очевидно е наясно с EWOULDBLOCK (или как обикновено работят неблокиращите сокети в по-голямата си част), така че според мен е сигурен залог, че формулировката ще блокира, което изглежда ви обърка, е просто лоша формулировка, но не и това, което е предвидено.   -  person Damon    schedule 16.10.2013
comment
@Damon Това изобщо не е очевидно за мен. Очевидно точно това е объркало ОП. Не съм аз. Това беше целият и пълен смисъл. Като премахнете това от въпроса, вие премахнахте целия му смисъл. недей така Ако искате да отговорите на въпроса, непременно го направете. Но не го променяйте просто според вашите нужди.   -  person user207421    schedule 16.10.2013
comment
Деймън е прав. Актуализирах въпроса, за да бъда по-точен. Наясно съм, че неблокиращите сокети всъщност никога не блокират, просто връщат, че биха го направили.   -  person David Timothy Strauss    schedule 17.10.2013
comment
Не можете да приемете нищо. Драйверът на nic картата вероятно е асинхронен, вашият компютър вероятно е асинхронен и т.н... Буферът за изпращане може да бъде изтощен от друго ядро, докато вашето изпращане е в процес и т.н.   -  person JimR    schedule 17.10.2013


Отговори (2)


Обаждането до send има три възможни резултата:

  1. Има поне един наличен байт в буфера за изпращане send успява и връща броя на приетите байтове (вероятно по-малко, отколкото сте поискали).
  2. Буферът за изпращане е напълно пълен по времето, когато извиквате send.
    ако гнездото блокира, send блокира
    ако гнездото не блокира, send се проваля с EWOULDBLOCK/EAGAIN
  3. Възникна грешка (напр. потребителят издърпа мрежов кабел, връзката се нулира от партньор) send се проваля с друга грешка

Ако броят на байтовете, приети от send, е по-малък от сумата, която сте поискали, това следователно означава, че буферът за изпращане сега е напълно пълен. Това обаче е чисто косвен и неавторитетен по отношение на всякакви бъдещи обаждания до send.
Информацията, върната от send, е просто "моментна снимка" на текущото състояние към момента, в който сте се обадили на send. Докато send се върне или когато се обадите отново на send, тази информация може вече да е остаряла. Мрежовата карта може да постави дейтаграма на кабела, докато програмата ви е вътре в send, или наносекунда по-късно, или по всяко друго време -- няма начин да разберете. Ще разберете кога следващото обаждане е успешно (или кога не).

С други думи, това не означава, че следващото извикване на send ще върне EWOULDBLOCK/EAGAIN (или ще блокира, ако сокетът не е неблокиращ). Опитването, докато това, което нарекохте „получаване на убедително EWOULDBLOCK“, е правилното нещо.

person Damon    schedule 16.10.2013
comment
Предполагам, че това е правилно само за неблокиране: изпращането е успешно и връща броя на приетите байтове (вероятно по-малко, отколкото сте поискали). Блокиращият сокет трябва да блокира, докато не завърши изпращането на всички данни или не успее поради други причини. Или бих добавил блокиращия сценарий към #1, или бих премахнал блокиращия сценарий от #2 (правейки отговора единствено за неблокиращи сокети). - person David Timothy Strauss; 17.10.2013
comment
@DavidTimothyStrauss: Изненадващо, вашето предположение е в пълно съответствие както с формулировката на страницата на ръководството, така и с POSIX (формулировката в аналогичния write syscall изрично споменава частични записи). И докато тази формулировка има смисъл за сокети за дейтаграми (които не можете да използвате с send!), тя няма смисъл за сокети, ориентирани към свързване. Изпращане напр. 100kB наведнъж със сигурност е допустимо, но при типичен размер на буфера от 64kB, ако цялото съобщение трябва да се побере, това би означавало, че вашата програма ще блокира завинаги (защото никога няма да побере цялото съобщение в буфера). - person Damon; 17.10.2013
comment
За сокетите за дейтаграми, разбира се, има смисъл, тъй като може да се изпрати само цялото съобщение, цялото съобщение трябва да се побере в буфера. Но гнездата за дейтаграми трябва да имат буфер, който може да съдържа поне една дейтаграма с максимален размер. Което означава, че sendto няма да блокира завинаги. За сокети за потоци частичните изпращания са реалност от 3 десетилетия (независимо от объркващата формулировка в страниците на ръководството), вижте например Ръководството на Beej по темата (Ръководството за работа в мрежа на Beej като цяло е много добро четиво). - person Damon; 17.10.2013
comment
Имаше дискусия по този въпрос в news:comp.protocols.tcp-IP преди няколко години, където беше общоприето, че блокирането на send()s блокира, докато всички данни не бъдат буферирани. Тази дискусионна група се попълва от всички внедряващи TCP. Те трябва да знаят. - person user207421; 18.10.2013
comment
@EJP: Това е страшна мисъл, но това, което е още по-страшно, е фактът, че истинското, наблюдавано поведение, напр. под Linux е нещо съвсем различно. В моя Debian box (3.2 ядро) буферът за изпращане по подразбиране е 84 kiB и send с радост ще приеме буфери от 64 MiB в едно send и ще върне изпратените 64 MiB. Със сигурност 64MiB › 84kiB? Изходен код: pastebin.com/T6N7hQky -- Програмата стартира сървър, който консумира целия трафик на порт 12345, и се разклонява клиент, който записва увеличаващи се парчета (64 kiB до 64 MiB, увеличаващи се на стъпки от 16 kiB) към този порт, като брои не. на изпращания. - person Damon; 18.10.2013
comment
@Damon Blocking send() гарантира, че данните са били поне буферирани. Може да премине през много цикли на буфер-изпращане-буфер и да се върне, след като всичките 64MiB са напълно изпратени или буферирани. - person David Timothy Strauss; 19.10.2013
comment
@DavidTimothyStrauss: Да, разбира се. Но ако човек приеме формулировката на POSIX (или това, което EJP каза) буквално, тогава и в двата случая трябва да блокира, ако цялото съобщение от 64 MiB не се побира в буфера за изпращане, който е винаги и завинаги случай за такова огромно изпращане. Не съм тествал колко send действително ще приеме без блокиране, но парче от 65 MiB вече е зашеметяващо. Няма начин да направите 64MiB WriteFile под Windows например (не и при нормални условия, това би било срещу квотата на вашия процес от заключващи се страници). - person Damon; 19.10.2013
comment
А сега, моля, затегнете предпазните колани... Току-що промених горния код, за да се опитам да блокирам изпращания от 512MiB на 1GiB, за смях. Познайте какво, send също приема изпращане от 1GiB на парче. Няма частични изпращания и от факта, че процесът отнема 97,4% CPU, докато работи, изглежда, че и той не блокира. - person Damon; 19.10.2013
comment
@Damon: разбира се, че блокира. Получателят просто консумира данните бързо (или може да имате огромен прозорец за изпращане), незабавно събуждайки подателя. По-специално, в Linux/TCP, цикълът за изпращане в tcp_sendmsg() извиква sk_stream_wait_memory()sk_wait_event()schedule_timeout(), когато няма налично място в паметта/буфера за изпращане. - person ninjalj; 10.03.2014
comment
@Damon Съгласни сме. Всички данни се буферират преди send() да се върне. Ако данните, които трябва да бъдат изпратени, са по-големи от буфера за изпращане, очевидно буферът за изпращане трябва да бъде изпратен, за да се направи място за повече от данните, и така непрекъснато, докато всички данни бъдат отчетени. След това send() се връща. - person user207421; 13.09.2014
comment
@Damon BTW send() наистина може да се използва с свързани UDP сокети. - person user207421; 19.05.2016

Ако send() върне същата дължина като буфера за прехвърляне, цялото прехвърляне е приключило успешно и сокетът може или не може да бъде в състояние на блокиране.

Не. Сокетът остава в режима, в който е бил: в този случай неблокиращ режим, който се предполага по-долу навсякъде.

Ако send() върне -1 и errno е EAGAIN/EWOULDBLOCK, нищо от прехвърлянето не е завършило и програмата трябва да изчака, докато сокетът вече не блокира.

Докато буферът за изпращане вече не е пълен. Гнездото остава в неблокиращ режим.

Ако send() връща положителна стойност, по-малка от размера на буфера.

Имаше само толкова място в буфера за изпращане на сокет.

Безопасно ли е да се предположи, че send() ще блокира дори още един байт данни?

Изобщо не е „безопасно“ да се „предполага, че [то] ще блокира“. Няма да стане. В режим без блокиране е. EWOULDBLOCK означава, че щеше да бъде блокиран в режим на блокиране.

Или трябва неблокираща програма да опита да изпрати() още веднъж, за да получи окончателен EAGAIN/EWOULDBLOCK?

Това зависи от теб. API работи, както решите.

Притеснявам се да поставя наблюдател на EPOLLOUT на сокета, ако той всъщност не блокира това.

Това не е „блокиране на това“. Не блокира нищо. В режим без блокиране е. Буферът за изпращане се запълни в този момент. Миг по-късно може да е напълно празен.

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

Сокетите обикновено са с възможност за запис, освен ако буферът им за изпращане не е пълен, така че не избирайте възможност за запис през цялото време, тъй като просто получавате цикъл на въртене.

person user207421    schedule 15.10.2013