Linux — ioctl с FIONREAD всегда 0

Я пытаюсь узнать, сколько байтов доступно для чтения в моем TCP-сокете. Я вызываю ioctl с флагом «FIONREAD», который должен фактически дать мне это значение. Когда я вызываю функцию, я получаю как return val 0 (поэтому нет ошибки), но также и мой целочисленный аргумент получает значение 0. Это не проблема, но когда я вызываю метод recv(), я фактически считываю несколько байтов из сокета. Что я делаю неправильно?

// здесь какой-то код:

char recBuffer[BUFFERLENGTH] = {0};
int bytesAv = 0;
int bytesRead = 0;
int flags = 0;
if ( ioctl (m_Socket,FIONREAD,&bytesAv) < 0 )
{
    // Error
}
if ( bytesAv < 1 )
{
    // No Data Available
}
bytesRead = recv(m_Socket,recBuffer,BUFFERLENGTH,flags);

Когда я вызываю функцию recv, я действительно читаю некоторые действительные данные (что я и ожидал)


person Toby    schedule 08.08.2011    source источник


Ответы (4)


Это происходит очень быстро, поэтому вы ничего не видите. Что ты делаешь:

  • ioctl: Есть данные для меня? Нет, пока ничего
  • recv: Блокировать, пока не будет данных для меня. Некоторое (непродолжительное) время спустя: Вот ваши данные

Так что, если вы действительно хотите увидеть FIONREAD, просто подождите.

/* Try FIONREAD until we get *something* or ioctl fails. */
while (!bytesAv && ioctl (m_Socket,FIONREAD,&bytesAv) >= 0)
    sleep(1);
person cnicutar    schedule 08.08.2011
comment
примечание: это пример кода для игрушек, не делайте этого в реальном коде :) - person Karoly Horvath; 08.08.2011
comment
@y_H Эй, я только передаю ему пистолет :-)) - person cnicutar; 08.08.2011
comment
Работает! Спасибо! Работал даже без цикла (просто дайте ему время, чтобы получить данные ^^) Но теперь у меня есть еще один вопрос... - person Toby; 08.08.2011
comment
... без боеприпасов этого недостаточно;) установите неблокирующий ввод-вывод, поэтому его вызов recv тоже не работает. - person Karoly Horvath; 08.08.2011
comment
Если мой сокет блокируется, не мог бы я просто вызвать recv с флагом MSG_PEEK - поэтому моя программа ждет, пока не появятся доступные данные (или тайм-аут сокета...), но с MSG_PEEK он не отбрасывает их. После этого я могу проверить размер, а затем снова вызвать recv без флага MSG_PEEK...? - person Toby; 08.08.2011
comment
@yi_H Верно. FIONREAD редко имеет смысл без O_NONBLOCK. - person cnicutar; 08.08.2011
comment
@Toby Тебе действительно нужно заглянуть в select(2). - person cnicutar; 08.08.2011
comment
Правильно. Сначала вы должны выбрать() для чтения в сокете; как только select вернет true, вы можете безопасно использовать FIONREAD. - person apenwarr; 20.10.2012
comment
Мне интересно, почему никто не предложил использовать poll() или epoll(). И уже после этого пытаться получить размер доступных для чтения данных. - person user1415536; 03.11.2013

Реальный ответ здесь - использовать select (2), как сказал cnicutar. Тоби, ты не понимаешь, что у тебя состояние гонки. Сначала вы смотрите на сокет и спрашиваете, сколько там байтов. Затем, пока ваш код обрабатывает блок «нет данных», аппаратное обеспечение и ОС получают байты асинхронно с вашим приложением. Таким образом, к моменту вызова функции recv() ответ «нет доступных байтов» уже не соответствует действительности...

if ( ioctl (m_Socket,FIONREAD,&bytesAv) < 0 )
{ // Error 
}

// BYTES MIGHT BE RECEIVED BY HARDWARE/OS HERE!

if ( bytesAv < 1 ) // AND HERE!
{
    // No Data Available
    // BUT BYTES MIGHT BE RECEIVED BY HARDWARE/OS HERE!
}

// AND MORE BYTES MIGHT BE RECEIVED BY HARDWARE/OS HERE!

bytesRead = recv(m_Socket,recBuffer,BUFFERLENGTH,flags);
// AND NOW bytesRead IS NOT EQUAL TO 0!

Конечно, небольшой сон, вероятно, исправил вашу программу два года назад, но он также научил вас ужасной практике кодирования, и вы упустили возможность научиться правильно использовать сокеты с помощью select().

Кроме того, как сказала Кароли Хорват, вы можете указать recv не читать больше байтов, чем вы можете сохранить в буфере, который передал пользователь. Тогда ваш функциональный интерфейс станет следующим: «Эта fn вернет столько байтов, сколько доступно в сокете, но не более [размер буфера, который вы передали]».

Это означает, что этой функции больше не нужно беспокоиться об очистке буфера. Вызывающий может вызывать вашу функцию столько раз, сколько необходимо, чтобы очистить ее от всех байтов (или вы можете предоставить отдельную функцию fn, которая полностью отбрасывает данные и не связывает эту функциональность с какой-либо конкретной функцией сбора данных). Ваша функция более гибкая, если вы не делаете слишком много вещей. Затем вы можете создать функцию-оболочку, которая соответствует вашим потребностям в передаче данных конкретного приложения, и что fn вызывает fn get_data и fn clear_socket по мере необходимости для этого конкретного приложения. Теперь вы создаете библиотеку, которую сможете носить с собой из проекта в проект и, возможно, с работы на работу, если вам так повезло, что у вас есть работодатель, который позволяет вам брать код с собой.

person Tim    schedule 14.08.2013
comment
Состояние гонки не обязательно верно: если сокет блокируется, вся обработка может быть выполнена задолго до поступления каких-либо данных. Затем вызов recv просто ждет, пока данные наконец не поступят... - person Aconcagua; 05.10.2015

Используйте select(), затем ioctl(FIONREAD), затем recv()

person brian beuning    schedule 09.02.2013

Вы не делаете ничего плохого, если вы используете блокировку ввода-вывода, recv() будет блокироваться до тех пор, пока данные не будут доступны.

person Karoly Horvath    schedule 08.08.2011
comment
Но проблема в том, что данные уже должны быть там, когда я вызываю функцию ioctl. Это не значит, что я вызываю recv(), а затем просто приходят данные. Я знаю это, потому что данные, которые я хочу получить, являются ответом на команду, которую я отправляю несколькими строками ранее в коде. - person Toby; 08.08.2011
comment
несколько строк раньше? Вы ожидаете немедленного ответа? Этого не произойдет. Поместите спящий режим или выполните select/poll/epoll/kqueue в сокете, и вызов ioctl будет в порядке. Почему ты вообще хочешь сделать этот звонок? - person Karoly Horvath; 08.08.2011
comment
Я на самом деле тоже только что подумал об этом - просто попробую немного поспать между командой send и ioctl - person Toby; 08.08.2011
comment
Я до сих пор не знаю, почему вы хотите сделать этот вызов ioctl. в чем смысл? - person Karoly Horvath; 08.08.2011
comment
Хорошо работает!! спасибо - хорошо, я пишу интерфейс с буфером, динамически предоставляемым пользователем. Я хочу знать, достаточно ли велик предоставленный буфер без многократного вызова recv! - person Toby; 08.08.2011
comment
а что делать, если буфер недостаточно велик? что ты хочешь делать? вы можете ограничить recv размером буфера. - person Karoly Horvath; 08.08.2011
comment
Просто хочу дать пользователю информацию о том, что предоставленный буфер был слишком маленьким... на самом деле... есть ли способ отказаться от данных, хранящихся в recv? - person Toby; 08.08.2011
comment
Используйте выбор или опрос ... и я понятия не имею, что вы подразумеваете под сбросом. - person Karoly Horvath; 08.08.2011