Как очистить буфер сокета

Обновлено: 06.07.2024

Проблема в следующем: приложение получает поток данных по UDP (скорость примерно 300 килобАЙт/сек). Есть сильное подозрение, что несмотря на то, что буфер приемного сокета приложения увеличен до максимума (setsockopt с опцией SO_RCVBUF), буфер все равно переполняется — иногда спустя некоторое время функция recv возвращает -1, а WSAGetLastError = 10055 (No buffer space available). Сокет работает в блокирующем режиме. Скажите, как нужно правильно прореагировать, если буфер сокета переполнился? Есть ли какие-то способы кроме как перезапустить программу или пересоздать сокет?

Если приемный буфер UDP сокета переполнится — вы этого просто не заметите, датаграммы начнут пропадать и все. По вашим симптомам происходит следующее: вы открываете сокет, задаете ему максимальный размер буфера ( на это расходуется довольно жестко лимитированная память ядра ). Потом не закрыв этот сокет, открываете следующий. Т.е причина — утечка системных ресурсов IMHO. Посмотрите на поведение памяти ядра при работе вашего приложения.

Здравствуйте, TarasCo, Вы писали:

TC>Посмотрите на поведение памяти ядра при работе вашего приложения.

Странно что это проявляется на recv. Хотя скорее всего симптомы описаны неверно.

MC>Странно что это проявляется на recv. Хотя скорее всего симптомы описаны неверно.

Если это все-таки следствие утечки ресурсов, то это может проявляться со странной на первый взгляд закономерностью закономерностью. Например, в неком абстрактном случае утекать может память блоками по 100кБ, а обламываться в 99% случаев будет при выделении 10 байт. Кроме того, автор наверняка не проверял, получилось ли увеличить размер буфера. Возможно там тоже была аналогичная ошибка, но ее просто не заметили.

>>Странно что это проявляется на recv. Хотя скорее всего симптомы >>описаны неверно.

>Если это все-таки следствие утечки ресурсов, то это может проявляться со странной на первый взгляд закономерностью закономерностью. Например, в неком абстрактном случае утекать может память блоками по 100кБ, а обламываться в 99% случаев будет при выделении 10 байт. Кроме того, автор наверняка не проверял, получилось ли увеличить размер буфера. Возможно там тоже была аналогичная ошибка, но ее просто не заметили.

Я не открываю в приложении новый сокет, не закрыв старого — тут утечек быть не должно.
Получилось ли увеличить размер буфера тоже проверяю с помощью getsockopt — судя по результату получается. Или вы имеете в виду, что закрывая сокет недо что-то сделать с его буфером?, сделав его буфер снова маленьким?
Код увеличения буфера:
int rcvbufsz(16000000),newrcvbufsz(16000000);
int nSizer(sizeof(int));
int nretValue(setsockopt(sock, SOL_SOCKET, SO_RCVBUF,(char *)(&rcvbufsz),sizeof(int)));
if (0!=nretValue)
printf("Error in setsockopt(SO_RCVBUF) (returned value=%d) with errorcode(WSAGetLastError)=%lu.",nretValue. WSAGetLastError());
return false;
>

getsockopt(sock,SOL_SOCKET,SO_RCVBUF,(char *)(&newrcvbufsz),&nSizer);
if (newrcvbufsz!=newrcvbufsz)
printf("%s","Error in setsockopt(SO_RCVBUF). Socket buffer's size is incorrect.");
return false;
>

Здравствуйте, deadka, Вы писали:

D>Код увеличения буфера:

Здравствуйте, deadka, Вы писали:

D>if (newrcvbufsz!=newrcvbufsz)

Скорее небо упадет на землю, чем условие сработает
И проверяйте на всякий случай результат getsockopt.

while (1)
n=recvfrom(sInputSock,(char*)acPkt,sizeof(acPkt),0,((struct sockaddr *)&addr),&nSizeOfSockAddr);
if (n<=0) // не пришли данные
printf("Error in recv(. ) — WSAGetLastError=%lu.\n Recreating socket. \n". WSAGetLastError());
// пересоздаем сокет функцией PrepareInputSocket
closesocket(sInputSock);
if (PrepareInputSocket(&sInputSock,wInputSocketPort,(struct sockaddr_in*)&addr,wRecvTimeOut))
printf("%s","Recreating InputSocket successfull. ");
n=recvfrom(sInputSock,(char*)acPkt,sizeof(acPkt),0,((struct sockaddr *)&addr),&nSizeOfSockAddr);
if (n<=0)
printf("Error in recv(. ) — WSAGetLastError=%lu.\n Program aborting. \n". WSAGetLastError());
break;
>
>
else
break;
>
>

Здравствуйте, Michael Chelnokov, Вы писали:

MC>Здравствуйте, deadka, Вы писали:

D>>if (newrcvbufsz!=newrcvbufsz)

MC>Скорее небо упадет на землю, чем условие сработает
MC>И проверяйте на всякий случай результат getsockopt.

Да-с насчет неба что верно, то верно — дурная привычка перебивать код по памяти.
сейчас сделаю ctrlC, ctrlV:

int rcvbufsz(16*1024*1024),newrcvbufsz(16*1024*1024);
int nSizer(sizeof(int));
int nretValue(setsockopt(sock, SOL_SOCKET, SO_RCVBUF,(char *)(&rcvbufsz),sizeof(int)));
if (0!=nretValue)
printf("Error in setsockopt(SO_RCVBUF) (returned value=%d) with errorcode(WSAGetLastError)=%lu.",nretValue. WSAGetLastError());
return false;
>

int nRetValue1(getsockopt(sock,SOL_SOCKET,SO_RCVBUF,(char *)(&newrcvbufsz),&nSizer));
if (0!=nRetValue1)
printf("Error in setsockopt(SO_RCVBUF) (returned value=%d) with errorcode(WSAGetLastError)=%lu.",nRetValue1. WSAGetLastError());
return false;
>
if (rcvbufsz!=newrcvbufsz)
printf("%s","Error in setsockopt(SO_RCVBUF). Socket buffer's size is incorrect.");
return false;
>
printf("%s","Socket of receive buffer changed. Ok\n");

Эта строчка бессмыслена — размер датаграммы не связан с размером ethernet фрейма и может быть больше.
D>char acPkt[1464*10]=""; // в 10 раз больше, чем размер приходящей датаграммы

А тут где closesocket ?


А вообще ситуция установки размера буфера в 16000000, довольно странная и необычная, особенно если учесть маленькую скорость передачи.

PS: пользуйтесь тегами

Здравствуйте, TarasCo, Вы писали:

TC>Эта строчка бессмыслена — размер датаграммы не связан с размером ethernet фрейма и может быть больше.
>>char acPkt[1464*10]=""; // в 10 раз больше, чем размер приходящей датаграммы

Я знаю размер датаграммы, которые ко мне приходит — 1464 байта.

closesocket и прочее освобождение ресурсов идет после выхода из цикла — просто я не привожу здесь весь код.

TC>А вообще ситуция установки размера буфера в 16000000, довольно странная и необычная, особенно если учесть маленькую скорость передачи.

Да нет, скорость передачи в полном порядке — примерно 300 килобайт в секунду.

Собственно, вопрос был не в том, как избежать такой ситуации, а в том — как правильно прореагировать. Подскажете?

TC>>Эта строчка бессмыслена — размер датаграммы не связан с размером ethernet фрейма и может быть больше.
>>>char acPkt[1464*10]=""; // в 10 раз больше, чем размер приходящей датаграммы
D>Я знаю размер датаграммы, которые ко мне приходит — 1464 байта.

тогда зачем буфер в 10 раз больше? Датаграммы все равно по одной читаются.

D>Да нет, скорость передачи в полном порядке — примерно 300 килобайт в секунду.

Эта не та скорость, для которой может потребоваться такой неимоверный буфер.

D>Собственно, вопрос был не в том, как избежать такой ситуации, а в том — как правильно прореагировать. Подскажете?

На такие ошибки реакция может быть одна — ExitProcess, потому что далее не может уже идти речь не только о стабильной работе приложения, но и всей системы. Причина ( если она конечно в приложении, если есть фаерволы/антивирусы — утечка может быть в них ) — утечка ресурсов. Вы так и не сказали, что у Вас система показывает о расходовании памяти.

Здравствуйте, TarasCo, Вы писали:

TC>>>Эта строчка бессмыслена — размер датаграммы не связан с размером ethernet фрейма и может быть больше.
>>>>char acPkt[1464*10]=""; // в 10 раз больше, чем размер приходящей датаграммы
D>>Я знаю размер датаграммы, которые ко мне приходит — 1464 байта.

TC>тогда зачем буфер в 10 раз больше? Датаграммы все равно по одной читаются.

Да, это я лопухнулся, спасибо.

D>>Собственно, вопрос был не в том, как избежать такой ситуации, а в том — как правильно прореагировать. Подскажете?

TC>На такие ошибки реакция может быть одна — ExitProcess, потому что далее не может уже идти речь не только о стабильной работе приложения, но и всей системы. Причина ( если она конечно в приложении, если есть фаерволы/антивирусы — утечка может быть в них ) — утечка ресурсов. Вы так и не сказали, что у Вас система показывает о расходовании памяти.

Насчет расходования памяти — я сам пока не очень разобрался, буду копать. Но, все объяснили уже, спасибо всем за участие!

Я пишу TCP-сервер, чтобы открыть порт и поговорить с некоторым оборудованием, которое просто отправляет строковые данные в виде байтового массива.

Как работает асинхронный цикл? Я не понимаю, почему этот код не завершает цикл и не очищает буфер. Я потратил довольно много времени, пытаясь понять процесс и не могу понять, почему этот код не должен работать.

Снова, соединения действительны (код не показан), и связь определенно работает. Я вижу, как данные успешно приходят и уходят с обеих сторон, но я не контролирую, что делает другое оборудование. Требуется ли некоторая реализация для получения этих вызовов и подтверждения того, что буфер может очиститься?

4 ответа

Непонятно, что вы ожидаете. Ничто в Winsock API, равно как и тонкий слой .NET над этим API, не "очистит" любой буфер, который вы ему предоставите. Все, что делает API - это копирует байты из операций чтения в буфер или копирует байты для операций записи из буфера.

Глядя на ваш EndReceive() обратный вызов, вы, похоже, неправильно поняли некоторые аспекты этого. Вы обрабатываете содержимое ReadBuffer еще до того, как завершите операцию чтения (вызывая EndReceive() ), и не предпринимаете никаких действий, чтобы учесть фактическое количество полученных байтов. Без хорошего примера минимального, полного и проверяемого кода для начала невозможно точно знать, что должен делать ваш код, но лучшая реализация вашего метода будет выглядеть примерно так:

Обратите внимание, что вы можете фактически обрабатывать кодировки, отличные от ASCII, не беспокоясь о частичном чтении. Чтобы сделать это, вы должны отслеживать состояние декодирования символов от одной операции чтения к следующей. Самый простой способ сделать это - использовать Decoder объект, который имеет внутренний буфер, который будет удерживать частичные символы до тех пор, пока вы не выполните следующую операцию декодирования.

Вы полностью игнорируете результат EndReceive, который сообщит вам, сколько байтов вы получили.

Измените свой EndReceive следующим образом:

Есть некоторые оптимизации, но я не могу написать их, так как у меня нет полного кода:

-Modify ByteArrayToString , чтобы избежать создания временного массива.

-Если исключение возникает при выполнении SimNetSocket.EndReceive(async) , это означает, что соединение закрыто, было бы неплохо обработать случай.

- Остерегайтесь того, что вы объединяете полученные данные в MessageBuffer, вы обязаны очистить эту переменную при использовании данных.

Вы не рассматриваете возможность чтения фрагментированных команд (по крайней мере, в коде, который вы представили).

Столкнулся с проблемой: Если вызывать функцию send - то через некоторое время ( если на второй стороне нет цикла приема данных) буфер отправки на сокете переполняется и поток переходит в режим ожидания(ждет пока данные не будут доставлены). Можно ли как-нибудь узнать состояние буфера отправки и если он переполнен - произвести очистку?

Протокол TCP/IP , winsock2, windows xp.

send при каждом вызове возвращает либо кол-во "отправленных" байт, либо ошибку. Получаешь её через WSAGetLastError и смотришь что там случилось. Соответственно, узнать можно без проблем.

Что касается очистки - с ходу мне в голову ничего не приходит, рой инфу в хелпе.

slava_mib: send возвращает не кол-во "отправленных" байт, а кол-во переданных байт в буфер отправки. Далее с этим буфером работает сокет. А вот как узнать состояние этого буфера - вопрос.

Semson, потому я и написал в кавычках. А что бы узнать состояние этого буфера - надо всего лишь прочитать хелп, сенд, вроде как, возвращает соответствующий код ошибки, когда буфер забит. Потому - меньше вопросов, больше чтения доков )))

чистить буфер , это нарушить целостность данных которые ты отправляешь, а так как речь идет о TCP это нарушение основополагающих принципов, так что я думаю у тебя не будет возможности почистить буфер.
Так что как написал slava_mib, при запихивании новой порции следишь за тем что возвращает send и если произошло переполнение, ждешь какой-то таймают, а дальше наверное стоит отключить этот сокет, не может же он бесконечно занят быть.

KpeHDeJIb
> что коннект не потерян и ждем и пробуем снова

вообще, насколько мне известно, потерю конекта в TCP не так просто проверить. Если сокет был криво закрыт или приложение упало, то тсп ( на стороне приёмника ) только через несколько минут/часов сообщит об этом пользователю через WSAGetLastError. В таких случаях надо пинговать.
1) сторона отправителя может отсылать данные или пинг пакет.
2) если у отправителя данных нет, то переодически с определённым промежутком времени, отправляем пинг пакет.
3) если принимающая сторона не получает данных или пингов - рубит коннект.

т.е по хорошему сервак и клиент переодически если у них нет данных обмениваются пингами.
т.е в твоём случае ничего не придёт и коннект отрубится.

kolobokspb
не знаю, сколько не проводил тестов в том числе когда клиент или сервер аварийно останавливаются событие дисконекта всегда получаю

такая ситуацию как ты описал видел только при не корректном использовании блокируемых сокетов.

kolobokspb
> т.е в твоём случае ничего не придёт и коннект отрубится.
Толи ты читать не умеешь, толи я не понимаю смысла собственных слов.
У меня было хоть слово про проверку "если что-нибудь пришло"?

1) на TCP при асинхронных сокетах можно и не узнать о отключении клиента если тот упал.
2)
KpeHDeJIb
> проверяем, что коннект не потерян и ждем и пробуем снова.
я к тому, что эта проверка не всегда проста. Нам сенд должен вернуть 0 или придёт дисконнект если мы на него подписались. А если другая сторона упала, а наша сторона ничего не пишет, то мы можем от нескольких минут до нескольких часов ждать дисконекта. если я не ошибаюсь.

Teranozavr_REX
> не знаю, сколько не проводил тестов в том числе когда клиент или сервер
> аварийно останавливаются событие дисконекта всегда получаю
> такая ситуацию как ты описал видел только при не корректном использовании
> блокируемых сокетов.

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

pentagra
ты хочешь сказать что запрос selecta на состояния сокета, не даст ответа что соединение порвалось?

pentagra
> такая ситуация возникает достаточно часто из-за сбоев в сети, а не работы
> приложения

я настаивал на этом примере, как на наиболее лёгком в реализации проверки. Причём если закрыть приложение не закрывая сокета дисконнект дойдёт, а если заставить упасть не дойдёт.

Существует еще таймаут TCP-сессии, который всегда можно настроить.

Semson
> Можно ли как-нибудь узнать состояние буфера отправки
Вроде нельзя. если хочешь чтобы поток не засыпал - используй неблокирующие сокеты
> и если он переполнен - произвести очистку?
вы уверены, что вам нужен TCP? обратите свой взов в сторону UDP - там по идее такой проблемы быть не может. а так как, очевидно, надежная достатвка данных вам не нужна - значит удп - то, что доктор прописал. (еслибы была нужна надежная доставка, полагаю, что мыслей вроде "почистить буфер" не возниклобы. и вообще задумайтесь, что будет если из потока данных вырвать кусок. как потом можно вообще после этого продолжить работу?)

kolobokspb
> вообще, насколько мне известно, потерю конекта в TCP не так просто проверить
врешь! вызываешь send передавая 0 байт. и смотришь результат. (либо recv сечайс неохото в мсдн лезть - сами смотрите)

kolobokspb
> Если сокет был криво закрыт или приложение упало, то тсп ( на стороне приёмника
> ) только через несколько минут/часов сообщит об этом пользователю через
> WSAGetLastError. В таких случаях надо пинговать.
врешь. если приложение упало - то удаленная сторона моментально получит уведомление ERRCONNRESET (или както так называется вобщем). пинговать вообще ничего не надо - открой для себя keep-alive, оно какраз борится с такими ошибками, когда ни удаленная сторона по каким-либо причинам не может принять сообещение о сбое (например если ёкнулся шлюз какой-нибудь промежуточный)
а понятия "криво закрыт сокет" вообще не существует. он либо закрыт либо не закрыт. код по работе с сетью благо не вы писали, молодой человек, а посему он работает корректно. а ежели вы забываете вызывать closesocket, то это уж ваша криворукость и ради нее городить пинги - это глупость несусветная надо просто читать доки (хотяь как можно это забывать вызвать closesocket вообще непонятно. вообще удаленная сторона получает уведомление о закрытии соединения после вызова shutdown и не обязательно даже closesocket)

kolobokspb
> я настаивал на этом примере, как на наиболее лёгком в реализации проверки.
бред бред и еще раз бред. ты предложил велосипед, который врядли сам сделаешь грамотно и красиво, хотя в ТСП этот механизм уже давно есть и годами отлажен.

KpeHDeJIb
> Существует еще таймаут TCP-сессии, который всегда можно настроить.
что такоее "таймаут TCP-сессии" ?

Мне нужно очистить данные в сокете (убедившись, что получать нечего). К сожалению, для этого нет функции в модуле сокета python.

я реализовал что-то так:

что вы думаете? Есть ли лучший способ сделать это?

Update: я не хочу изменять тайм-аут сокета. Почему я предпочитаю выбор чтению.

Update: исходный вопрос использовал термин "флеш". Кажется, что "пустой" - лучший термин.

обновление - 2010-02-27 : Я заметил ошибку, когда пара закрылась. Inputready всегда заполняется сокетами. Я исправил это, добавив максимальное количество циклов. Есть ли лучшее решение?

Если под "флеш" вы имеете в виду выбросить любые ожидающие входящие данные, то вы можете либо использовать select (), как вы это делаете, либо установить сокет на неблокирующийся и читать в цикле, пока вы не закончите данные.

также обратите внимание, что (из manpage Linux):

в Linux select () может сообщить о дескрипторе файла сокета как " готов для чтения", хотя тем не менее последующие блоки чтения. Этот может, например, произойти, когда данные прибыли, но по экспертиза неправильная контрольная сумма и отбрасывается. Могут быть и другие обстоятельства, при которых файловый дескриптор ложно сообщается как готовый. Таким образом, может быть безопаснее использовать O_NONBLOCK на сокетах, которые должны не перекрывать.

и, как было отмечено другими, "флеш"обычно относится к выходу.

используя select.select хорошая практика, как указано в Программирование сокетов HOWTO. Вам нужно будет установить сокет как неблокирующий, используя sock.setblocking(0) .

просто комментарий о номенклатуре: flush обычно ассоциируется с выход операции.

для UDP-пакетов я сделал следующее:

после создания сокета, настройки и привязки, я использую socket.settimeout() . Примечание документация setblocking() дает некоторую информацию, которая из settimeout() не делает-если вы хотите, чтобы ваши операции сокета блокировать, вы должны просто использовать settimeout() установить тайм-аут. setblocking() просто ставит бесконечный тайм-аут на него. (У меня была ошибка вызова settimeout() следовал по setblocking(1) .)

мой функция "опорожнение буфера" - это просто ("слушатель" - мой сокет):

def FlushListen(self): while 1: try: PacketBytes = self.__Listener.recv(1024) except: break;

С тайм-аутом 1 секунды, это прочитает все UDP-пакеты, а затем вернет 1 секунду после того, как нет данных.

в моем случае я использую его, чтобы просто говорить между двумя программами на одном ПК, чтобы я мог легко снизить свой тайм-аут, но скорость не проблема, поэтому я в порядке с этим.

согласно некоторым из ссылок, опубликованных другими, это должно работать с потоками данных.

Как очистить входной буфер (если он вообще существует) UDP-сокета в C?

Вот его псевдокод:

Я ожидаю и хочу работать с этим кодом:

На самом деле это работает так:

Я хочу, чтобы эти широковещательные рассылки UDP игнорировались, поэтому я подумал о том, чтобы очистить буфер приема (очевидно, не тот, который я даю в качестве параметра для метода recvfrom), если он существует до вызова recvfrom. Как я могу это сделать? или по какому пути мне идти?


3060 4 4 золотых значка 28 год 28 серебряных значков 42 42 бронзовых знака

Обратите внимание, что понятие «промывка» применимо только к выходным данным. Флеш очищает буфер и гарантирует, что все в нем было отправлено по назначению. Что касается входного буфера, данные уже находятся в месте назначения. Буферы ввода могут быть прочитаны или очищены, но не «сброшены».

Если вы просто хотите убедиться, что вы прочитали все во входном буфере, вам нужна неблокирующая операция чтения. Если вы попробуете, но нет ввода, он должен вернуть ошибку.

40,5 км 5 5 золотых значков 68 68 серебряных знаков 92 92 бронзовых знака Уточнение: в момент, когда вы хотите «очистить входной буфер», выполните циклическое неблокирующее чтение. Если есть данные, игнорируйте их и продолжайте читать. Если данных нет (где @bta говорит «вернуть ошибку»), вы игнорируете возвращаемую «ошибку» и ваш буфер «очищается». Если вы хотите игнорировать ввод-вывод только во время периода ожидания, установите переменную условия, когда период ожидания начнется, и очистите ее, когда режим ожидания закончится. Ваш поток ввода-вывода может проверять condvar всякий раз, когда ваше блокирующее чтение возвращается, и если condvar установлен, он может отбросить данные и дождаться следующего ввода. Чтобы сделать это, используя предоставленный вами код, вы должны создать новый поток, представляющий собой простой цикл ввода-вывода, а в том месте, где вы вызвали recvfrom , вместо этого вызовите функцию, которая будет извлекать данные из цикла ввода-вывода (сохраните их во внутреннем цикле ввода-вывода). очередь, например). «Если вы хотите игнорировать ввод-вывод только во время периода сна . » Именно! Но; дело в том, что мой период сна и поток ввода-вывода - это одни и те же потоки (см. networkListenerFunction в OP). Хотя разделить их на 2 потока и реализовать свою идею можно; Мне любопытно, как это соотносится с идеей «destruct-socket-at-endOfWhile-reconate-at-startOfWhile» из ответа Николая Н. Фетисова .

Сокет имеет единственный приемный буфер внутри стека TCP / IP. По сути, это FIFO полученных дейтаграмм. Однако TCP и UDP обрабатывают эту очередь по-разному. Когда вы вызываете recv(2) UDP-сокет, вы удаляете одну дейтаграмму из этого буфера. TCP упорядочивает дейтаграммы в поток байтов в соответствии с порядковыми номерами. При переполнении приемного буфера дейтаграмма отбрасывается стеком. В этом случае TCP пытается выполнить повторную отправку. UDP - нет. Для приемного буфера нет явной функции «сброса», кроме чтения сокета или его закрытия.

Редактировать:

Ваше приложение имеет неотъемлемое состояние гонки, и похоже, что вы пытаетесь решить его с помощью неправильного инструмента (стека TCP / IP). Что я думаю , что вы должны делать это определение чистой государственной машины для приложения. Обрабатывайте события, которые имеют смысл в текущем состоянии, игнорируйте события, которые не имеют смысла.

Читайте также: