kb
03.10.2011 08:37 c8541125
Вот объясните мне, пожалуйста. Если я делаю с одной стороны send() какой-то строки, а с другой стороны делается recv(), сообщение может дойти не полностью, а в несколько recv()'ов? Или, всё же, можно надеяться, что оно либо дойдет, либо не дойдёт?
И второй вопрос: если я делаю асинхронный recv() (можно через select, можно через libevent/epoll), при этом с большой вероятностью зная, что может быть клиент уже ничего не пошлет, но сокет не закроет. Ничего ведь страшного? Это ведь нормально? (чтоб не городить контрольные суммы переговоров и их длинну и т.п.)
Спасибо.
Recommended by:
@DZhon
может не поместиться, поэтому recv должен работать в цикле, пока есть что считывать
почитай Стивенсона и его Разработка сетевых приложений под UNIX — там это очень хорошо расписано
да, я уже скачал, обязательно почитаю. но пока так хотел бы, вроде вопросо не очень много :-)
ок, но если я хочу сделать "обмен сообщениями в рамках одного открытого сокета", — мне придется свой протокол городить? (ну, или хотя бы сказать, что сообщение — это строка и дальше хуе-мое катаморфизмы, епта)
и еще вопрос: у меня в экспериментах, в recv() ты укахываешь макс. длинну сообщения, и если его послать с большей длинной — оно просто обрезается. то есть твоё "не поместится", — получается я сам должен заботиться о разбиении сообщения и т.п.?
если ты работаешь на уровен сокетов, то ты, считай, сам пишешь свой протокол. Тоесть ты должен заботиться о передаче всякой фигни, для корректного приема. Например передавать длинну всего сообщения заранее, что бы на той стороне могли зарезервировать память под это сообщение
вот говно. а какое самое популярное решение, чтоб не заниматься фигней? или каждый свой велосипед пишет?
не работать с сокетами, а юзать готовые либы.
ага. и что, к примеру, для Си самое популярное?
zeromq, например (не самое популярное, но довольно удобное).
из того какие вопросы ты задаёшь, предполагаю что сокет у тебя блокирующий:
1. recv в этом случае работает так же как и fread, читай из него блоками. покуда сокет не закрыт, он пытается прочитать из него столько данных, сколько ты запросил на чтение. если вернёт количество прочитанных байт меньше запрошенного — значит удалённый хост послал всё что хотел и закрыл сокет.
2. зачем ты через select делаешь асинхронный recv, если можно просто сделать сокет неблокирующим? случае отсутствия данных выставит errno в EAGAIN и EWOULDBLOCK
Ага, а в той же винде еще лучше заюзать Overlapped I/O. И тут вспоминаем ненавистные кресты и boost::asio, где под каждой платформой выбрано оптимальное демультиплексирование.
Хмм. То есть мне прийдёт, с одной стороны, не больше, чем я запросил, а с другой, — не больше, чем размер пакета, но если я знаю, что сообщение вместится и туда и туда — значит оно _гарантировано_ отдастся мне за один read?
Ну, через select — чтоб сразу много было. Но лучше вообще libevent, потому что при select или ручной обработке, на сколько я понял, сложность (из-за того, что на каждый чих пробегаешься по списку клиентов) сильно растёт.
1. ЕМНИП, «не больше, чем размер пакета» — не имеет под собой оснований. можно запросить мегабайт прочесть и оно действительно прочтёт разом мегабайт, хотя пакеты восьмикилобайтные ходить будут. да, гарантированно за один read, если сокет неблокирующий.
2. не думаю что select настолько туп. насколько мне известно, все нормальные люди им пользуются для детекта активности в сокетах.
нет, select используется потому, что ты не хуячишь в цикле по неблокирующему сокету, сжирая весь CPU, а он уснёт, пока активность не проявится хотя бы в одном из запрошеных сокетов. но когда она появится — ты бегом побежишь по всем массивам в поисках нужных/ответивших, чтоб из них уже читать. может я неправильно понял, но вроде именно об этом пишет intro по libevent http://www.wangafu.net/~nickm/libevent-b...
по поводу мегабайта --
по поводу мегабайта — это именно то, о чем я спрашивал, спасибо. только вот magog в этом треде утверждает обратное, что надо делать recv() в цикле. какой из ваших ответов правильный?)
и еще к тебе оффтопик-вопрос: в вашей группе SePe (надежности и производительности поиска) работа/задачи интересные? или говно? или ты хз и не спрашивать об этом? спасибо.
хм, ну да, только вот «бежать по всем массивам» мне кажется довольно шустрой операцией при помощи FD_ISSET
очень интересные задачи там. серьёзно
имхо, он сказал то же самое что и я
при большом количестве сокетов эта операция не шустрая хотя бы чисто алгоритмически, собственно из-за этого libevent и был придуман, насколько я понял (точнее был придуман epoll с его коллбеками, а libevent — просто композиция всех этих kqueue). но я тут не специалист пока, только хочу им стать.
нет, есть очень важный момент/отличие, о котором я и спрашивал (хотя может я просто не понял): я могу при помощи send("bla-bla-bla") быть увереным, что при любой размерности "bla-bla-bla" оно придёт в один recv? Если да — я могу считать одну строку одним сообщением. Если нет — мне придётся городить свой маркер конца сообщения и делать recv пока я его не увижу (ну, еще вариант — заранее говорить длинну сообщения и т.п.).
и действительно, насколько я понял ZeroMQ именно так и работает, он предоставляет интерфейс, подобный всем этим send/recv, но там он гарантирует, что сообщение дойдёт и прочитается полностью.
zeromq вешает маркеры.
ну, это логично (если всё именно так работает, что ты не можешь быть уверенным, что тебе "прислали всё" за один recv)
В том, что мне доводилось пописывать, я отправлял сначала uint32_t с размером, а потом сообщение. Самый удобный вариант, на мой взгляд.
если в recv ты будешь передавать буфер размера большего "bla-bla-bla", то оно действительно пройдёт в один recv
какой-то борланд паскаль получается (длинна данных, потом данные). хотя если uint32_t хватит всем, почему бы и нет :-)
а ты думаешь все по другому устроено?
думаю, что можно маркер окончания строки сделать
как ты собираешь принимать данные на той стороне?
ну, возможно вот здесь мне и необходимо просветление. сейчас я бы делал recv(1024) пока не получу данные, в которых есть маркер. где я не прав?
и еще. так всё-таки @analizer в /25 прав или нет? гарантируется, что мой .recv(100500) всегда примет чей-то .send("bla-bla-...-bla"), если этот bla-bla-bla будет меньше 100500?
(в один .recv(), имеется в виду, а не в цикл из .recv()'ов)
Маркер окончания строки не освободит тебя от костылей по проверке текущего принятого размера (ты же не хочешь переполнений?!)
гарантируется при выставлении флага MSG_WAITALL
ну как, оно ведь гарантирует, что не даст мне больше, чем 100500 прочесть, оно ж обрежет просто большее сообщение? или не?
Насколько я понимаю, оно вполне может дочитаться в следующий раз.
И если у тебя бинарный протокол (ты шлешь по сети файлы неизвестного содержимого, например), в котором может быть маркер окончания строки с другим смыслом, то опять же...
хмм, похоже на правду.
MSG_WAITALL (since Linux 2.2)
This flag requests that the operation block until the full request is satisfied. However, the call may still return less data than requested if a signal is caught,
an error or disconnect occurs, or the next data to be received is of a different type than that returned.
при этом, правда, всё равно доверять нельзя ему. говно, в общем.
да, для бинарных данных — говно получается, согласен, проще (чтоб не лопатить) обернуть в свой header/размер.
доверять можно. вернул меньше чем ожидалось — значит всё, пиздец. сообщай об ошибке и переоткрывай соединение
аа, точно. ну всё, кажется осознал как-то. осталось понять вот ту большую простыню с gunicorn и можно, видимо, идти и читать/практиковать стивенса не торопясь никуда. спасибо еще раз.