Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Сети > Сокеты Беркли, recv


Автор: mad_lollipop 16.11.2008, 15:13
Недавно начал  работать с сокетами, использую их в чистом виде, т.е. Сокеты Беркли+AF_INET, SOCK_STREAM, IPPROTO_TCP, потоковые, TCP. Возник вопрос, связанный с функцией recv. Если наш сокет не асинхронный, тогда при вызове этой функции мы ждем, пока во входном буфере сокета не появятся данные присланного пакета. При появлении данных в буфере мы можем обрабатывать полученное. Но вот что интересует больше всего: гарантируется ли полнота присланных данных, т.е. если нам послали 50 байт, то нам их столько и прийдет, или может возникнуть ситуация, когда нам эти 50 байт придут по частям, причем после получения первой части байтов наша программа получит возможность работы не дожидаясь получения следующих частей??? Возможно, все это реализуется на уровне TCP? Или как?


Автор: jonie 16.11.2008, 18:28
описанная ситуация вполне возможна, и зависит не только от настроек ОС, но и от устройства стека TCP\IP в системе...
а почему это вам должны давать сразу все присланные данные? может их там будет 20 мегабайт...

Автор: J0ker 16.11.2008, 19:47
для UDP пакет приходит целиком, если на recv запрошено меньше, то остаток теряется
для TCP возможна фрагментация пакетов (т.к. фактически пакетов нет - это потоковый протокол), при этом данный не теряются, даже когда в recv запрошено меньше имеющегося

Автор: MAKCim 16.11.2008, 22:26
Цитата(mad_lollipop @  16.11.2008,  15:13 Найти цитируемый пост)
или может возникнуть ситуация, когда нам эти 50 байт придут по частям, причем после получения первой части байтов наша программа получит возможность работы не дожидаясь получения следующих частей??? 

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

Автор: SVN74 17.11.2008, 00:20
Я был столкнулся с подобной проблемой при пересылке больших размеров данных (в МАССИВАХ), 
так как массив надо заполнить за один присест, пришлось смастерить свою функцию для полного заполнения массива:
Где:
 RecivedData - Массив данных
 SizeOfData - Размер массива
 SizeDataForSpeed - Можно устанавливать любую скорость но не  больше размера массива

///////////////////////////////////////////////////////////
Код

// Функция получения больших размеров данных через сеть
int  BigRecieve(SOCKET sck, char *RecivedData, int SizeOfData, int SizeDataForSpeed)
{
 int c,x=0; void *Ptr;
 try
 {
  char *TempAccp = new char[SizeDataForSpeed];
  do {
    c=recv(sck,&TempAccp[0],SizeDataForSpeed,0);
   if(c<0)
   {
   delete [] TempAccp;
   return -1;
   }
    Ptr = RecivedData+x;
    if(Ptr)
    x=x+c;
     if((c<0) || (SizeOfData < x) || (!Ptr))
    {
     delete [] TempAccp;
     return -1;
    }
     memcpy(Ptr,(void*)&TempAccp[0],c);
     } while(SizeOfData > x);
   delete [] TempAccp;
  }
  catch(...)
  {
   return -1;
  }
 return x;
}

Да... При ошибках будет возвращать -1

Автор: jonie 19.11.2008, 01:22
вспоминается тот незабываемый код, что когда-то писал наш офис в тайланде....
опишу пошагово (без обид, с юмором):
Цитата

Код

int  BigRecieve(SOCKET sck, char *RecivedData, int SizeOfData, int SizeDataForSpeed)
{
 int c,x=0; void *Ptr;
 try
 {
  char *TempAccp = new char[SizeDataForSpeed];  //завели буферок для скачивания (размер его скорость не даст 99%)

  do {
    c=recv(sck,&TempAccp[0],SizeDataForSpeed,0);  //начнем качать, неважно, что RecivedData может быть равен NULL -- хитроумно проверим дальше, а данные потеряем

   if(c<0)
   {
   delete [] TempAccp;
   return -1;
   }

//заведем магическую Х. Просто, нам лень делать было 
//"Ptr = RecivedData; Ptr += c;" - так могут все.
    Ptr = RecivedData+x;   

    if(Ptr)  //проверим на всякий случай (вдруг в первый раз RecivedData==NULL был?)
      x=x+c;

//
//и еще на всякий случай проверим Ptr (а вдруг RecivedData всеже был NULL?) 
//заодно проверим предоставленный размер буфера (про уже скаченные данные тупо забудем - они никому не нужны)
//на всякий случай перепроверим что C<0 (ведь "семь раз проверь - один отрежь")
     if((c<0) || (SizeOfData < x) || (!Ptr)) 
    {
     delete [] TempAccp;
     return -1;
    }
     memcpy(Ptr,(void*)&TempAccp[0],c); //пишем мы не зная что приводить к void* нет нужды...
     } while(SizeOfData > x);
   delete [] TempAccp;
  }
  catch(...)
  {
   return -1;  //а тут отчищать память от TempAccp не надо. "пусть всегда будет Солнце".
  }
 return x;

}


Автор: SVN74 19.11.2008, 23:34
Цитата(jonie @  19.11.2008,  01:22 Найти цитируемый пост)
//начнем качать, неважно, что RecivedData может быть равен NULL -- хитроумно проверим дальше, а данные потеряем

Ну во первых чтобы использовать эту функцию буфер "RecivedData" заранее должен быть проверен и подготовлен
Цитата(jonie @  19.11.2008,  01:22 Найти цитируемый пост)
char *TempAccp = new char[SizeDataForSpeed];  //завели буферок для скачивания (размер его скорость не даст 99%)

Как ни странно на скорость влияет ...  smile 




Цитата(jonie @  19.11.2008,  01:22 Найти цитируемый пост)
    if(Ptr)  //проверим на всякий случай (вдруг в первый раз RecivedData==NULL был?)
      x=x+c;

//
//и еще на всякий случай проверим Ptr (а вдруг RecivedData всеже был NULL?) 
//заодно проверим предоставленный размер буфера (про уже скаченные данные тупо забудем - они никому не нужны)
//на всякий случай перепроверим что C<0 (ведь "семь раз проверь - один отрежь")
     if((c<0) || (SizeOfData < x) || (!Ptr)) 

Признаю... Лишнее... Можно вычеркнуть.  smile 




Цитата(jonie @  19.11.2008,  01:22 Найти цитируемый пост)
memcpy(Ptr,(void*)&TempAccp[0],c); //пишем мы не зная что приводить к void* нет нужды..

Здесь Ptr изначально void* - приводить не обязательно, иначе была бы ошибка, (проверено).


Цитата(jonie @  19.11.2008,  01:22 Найти цитируемый пост)
catch(...)
  {
   return -1;  //а тут отчищать память от TempAccp не надо. "пусть всегда будет Солнце".
  }


  smile  А тут я конечно “лоханулся”, спасибо добавлю очистку...
.....................................................................................................................
Несмотря на насмешки, этот код критических ошибок не имеет, конечно эго можно упростить вообще.
У меня эта функция скачивает сотнями Ггб в сети и ошибок не возникало, даже когда я ее принудительно садил на ошибки.
.....................................................................................................................
Что интересно, как только кто ни будь просит помощи в написании кода - помогать никто практически не хочет, тут я поддерживаю людей, которые помогают, тратя свое время...
Но вот (блеснуть своим умом) обсудить  чужой код , пускай не доскональный желающих довольно много, что интересно это чаще относится к людям у кого сообщений переваливает за 1000  - такой народ обычно ничего своего не выставляет...
БЕЗ ОБИД  smile 


Автор: REZiaMIX 19.11.2008, 23:43
Цитата(SVN74 @ 19.11.2008,  23:34)
Что интересно, как только кто ни будь просит помощи в написании кода - помогать никто практически не хочет, тут я поддерживаю людей, которые помогают, тратя свое время...
Но вот (блеснуть своим умом) обсудить  чужой код , пускай не доскональный желающих довольно много, что интересно это чаще относится к людям у кого сообщений переваливает за 1000  - такой народ обычно ничего своего не выставляет...
БЕЗ ОБИД  smile

Не в тему но:
Поддерживаю!

Автор: vinick 20.11.2008, 00:40
Цитата(SVN74 @  19.11.2008,  23:34 Найти цитируемый пост)
Цитата(jonie @  19.11.2008,  01:22 Найти цитируемый пост)
Код

  catch(...)
  {
   return -1;  //а тут отчищать память от TempAccp не надо. "пусть всегда будет Солнце".
  }


  smile  А тут я конечно “лоханулся”, спасибо добавлю очистку...


У тебя исключения бросает только new. Так что никакой очистки не надо.
Код

        memcpy(Ptr,(void*)&TempAccp[0],c);
     } while(SizeOfData > x);

А теперь представь ситуацию SizeOfData=100, SizeDataForSpeed = 40 и у тебя recv 3 раза подряд прочитает по 40 байт. Потеря 20 байт и выход за границы массива в твоём приложении не критично ;)

Цитата(SVN74 @  19.11.2008,  23:34 Найти цитируемый пост)
Как ни странно на скорость влияет ...  smile 

Влияет, но очень опосредовано.

Автор: jonie 20.11.2008, 00:58
Цитата

Ну во первых чтобы использовать эту функцию буфер "RecivedData" заранее должен быть проверен и подготовлен
есть такой анти-паттерн : слепая вера зовется. использовать его надо с умом.

Цитата

У тебя исключения бросает только new. Так что никакой очистки не надо.
recv легко может дать exception (который, при опред махинациях с компилятором) будет ловиться catch-ем. ну и в общем-то не очень принято кушать exceptions все подряд. ну это так, к слову...

Цитата

Цитата

memcpy(Ptr,(void*)&TempAccp[0],c); //пишем мы не зная что приводить к void* нет нужды..
Здесь Ptr изначально void* - приводить не обязательно, иначе была бы ошибка, (проверено).
я про TempAccp говорил.

Добавлено через 3 минуты и 3 секунды
Цитата

Что интересно, как только кто ни будь просит помощи в написании кода - помогать никто практически не хочет, тут я поддерживаю людей, которые помогают, тратя свое время...
Но вот (блеснуть своим умом) обсудить  чужой код , пускай не доскональный желающих довольно много, что интересно это чаще относится к людям у кого сообщений переваливает за 1000  - такой народ обычно ничего своего не выставляет...
БЕЗ ОБИД   
не видел просьб написать код закачки данных.

Автор: SVN74 20.11.2008, 20:35
С учетом выше сказанных замечаний упростил свою функцию.
Работает также без проблем. 
Какие замечания будут?
Код

int BigRecieve(SOCKET sck, char *RecivedData, int SizeOfData)
{
 int X=0,rc;
     while(X<SizeOfData)
     {
       rc=recv(sck,&RecivedData[0]+X,SizeOfData-X,0);
       if(rc<=0) return -1;
       X+=rc;
     }
 return X;
}

Автор: J0ker 20.11.2008, 21:05
о
а теперь как мы отличим - сокет закрывают или у нас ошибка?  smile 

Автор: SVN74 20.11.2008, 21:27
Цитата(J0ker @  20.11.2008,  21:05 Найти цитируемый пост)
о
а теперь как мы отличим - сокет закрывают или у нас ошибка?    

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


Автор: J0ker 20.11.2008, 21:49
вы меня не поняли
как определить - сокет закрыли с той стороны или у нас чего-то сломалось?

Автор: SVN74 20.11.2008, 21:53
Цитата(J0ker @  20.11.2008,  21:49 Найти цитируемый пост)
вы меня не поняли
как определить - сокет закрыли с той стороны или у нас чего-то сломалось? 

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

Автор: J0ker 20.11.2008, 22:03
еще раз
ваша функция вернула -1
что это означает? что пользователю-то сказать?

Автор: MAKCim 20.11.2008, 22:09
SVN74, 
в общем
Код

if (rc <= 0) return rc;

Автор: SVN74 20.11.2008, 22:10
Цитата(J0ker @  20.11.2008,  22:03 Найти цитируемый пост)
еще раз
ваша функция вернула -1
что это означает? что пользователю-то сказать? 

Это означает - ошибка приема данных, если все в порядке вернет количество полученных байт.

Автор: MAKCim 20.11.2008, 22:12
Цитата(SVN74 @  20.11.2008,  22:10 Найти цитируемый пост)
Это означает - ошибка приема данных

все ясно
литературу мы не читаем  smile 

Автор: SVN74 20.11.2008, 22:13
Цитата(MAKCim @  20.11.2008,  22:09 Найти цитируемый пост)
SVN74, 
в общем

код C++
1:

if (rc <= 0) return rc;

 В принципе можно и так, просто все равно ошибку надо определять через WSAGetLastError()

Автор: J0ker 20.11.2008, 22:15
Цитата(SVN74 @ 20.11.2008,  22:13)
Цитата(MAKCim @  20.11.2008,  22:09 Найти цитируемый пост)
SVN74, 
в общем

код C++
1:

if (rc <= 0) return rc;

 В принципе можно и так, просто все равно ошибку надо определять через WSAGetLastError()

все равно не получится
WSAGetLastError не определена в случае успешного завершения операции

Автор: SVN74 20.11.2008, 22:20
Цитата(J0ker @  20.11.2008,  22:15 Найти цитируемый пост)
все равно не получится
WSAGetLastError не определена в случае успешного завершения операции 

Да, а вот почему то автор "Йон Снейдер Эффективное программирование TCP/IP " определил возврат именно так ...
rc = recv( s1, buf, 1, 0 );
40        if ( rc <= 0 )

41        {

42          perror( "ошибка вызова recv" );

43          exit ( 1 );

44        }



Автор: MAKCim 20.11.2008, 22:24
Цитата(SVN74 @  20.11.2008,  22:20 Найти цитируемый пост)
Да, а вот почему то автор "Йон Снейдер Эффективное программирование TCP/IP " определил возврат именно так ...

значит он тоже литературу не читает  smile 

Автор: SVN74 20.11.2008, 22:29
Хотя я раньше писал  if ( rc < 0 ) ...
А теперь так...
rc = recv( s1, buf, 1, 0 );
40        if ( rc <= 0 )

Автор: J0ker 20.11.2008, 23:22
Цитата(SVN74 @  20.11.2008,  22:20 Найти цитируемый пост)
Да, а вот почему то автор "Йон Снейдер Эффективное программирование TCP/IP " определил возврат именно так ...

в утиль значит снейдера

Автор: vinick 21.11.2008, 00:29
Цитата

Да, а вот почему то автор "Йон Снейдер Эффективное программирование TCP/IP " определил возврат именно так ...

Ну в водной главе, в примере простейшего  tcp-клиента глупо было бы писать полноценную обработку ошибок ;)
А вот дальше... 

стр 68, листинг 2.12 "Функция readn"
Код

int readn(SOCKET fd, char * bp, size_t len)
{
    int cnt;
    int rc;
    cnt = len;
    while(cnt > 0)
       {
            rc = recv(fd,bp,cnt,0);
            if(rc < 0)
             {
                   if (errno == EINTR)
                        continue;
                   return -1;
             }
             if(rc == 0)
                 return len - cnt;
             bp += rc;
             cnt -= rc;
        }
     return len;
}


Не надо тут на Снейдера поклеп возводить, он хорошую книжку написал.

Автор: SVN74 21.11.2008, 00:38
Согласен, книга хорошая.
Код

static void client ( SOCKET s, struct sockaddr_in *peerp )

{

 int rc;

 char buf[120];

 for ( ; ; )

 {

  rc = recv( s, buf, sizeof( buf ), 0 );

  if ( rc <= 0 )

   break;

  write( 1, buf, rc );

 }

}

Этот клиент читает из сокета данные и выводит их на стандартный вывод до тех пор, пока сервер не пошлет конец файла (EOF). 

 --- Это 58 страница ---


Автор: jonie 21.11.2008, 00:43
SVN74 знать надо дочитывать книжки-то дальше введения...

Автор: vinick 21.11.2008, 01:56
Цитата(SVN74 @  21.11.2008,  00:38 Найти цитируемый пост)
 --- Это 58 страница ---

Русские умирают, но не сдаются smile 
Читаем двумя строками выше.
Цитата

Протестировать клиент можно, скопировав  каркас в файл helloc.c и дописав в заглушку следующий код:
Код

static void client ( SOCKET s, struct sockaddr_in *peerp )
...


Это не пример правильного кода, это заглушка для тестирования каркаса tcpclient.skel.

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

ЗЫЖ Да, я зануда smile

Автор: J0ker 21.11.2008, 03:47
Цитата(vinick @  21.11.2008,  01:56 Найти цитируемый пост)
прикрываться авторитетом автора

не нервничайте - для нас нет авторитетов   smile 
ну хочется человеку мазохизма - зачем ему мешать  smile 

Автор: Олег2005 24.11.2008, 19:11
Цитата(J0ker @  16.11.2008,  18:47 Найти цитируемый пост)
для TCP возможна фрагментация пакетов (т.к. фактически пакетов нет - это потоковый протокол)

В TCP нет никаких пакетов - и тем более фрагментации
Фрагментация осуществляется для IP-v4 только на маршрутизаторах (при определенных условиях - в частности, длина Ip-пакета меньше MTU данной сети)
В TCP существует только понятие сегмента.

Добавлено @ 19:15
Цитата(SVN74 @  20.11.2008,  21:20 Найти цитируемый пост)
perror( "ошибка вызова recv" );

Ошибка вызова функции и ошибка приема данных - это совсем не одно и тоже. Снайдер знал что писал smile

Добавлено через 7 минут и 11 секунд
Цитата(J0ker @  20.11.2008,  22:22 Найти цитируемый пост)
в утиль значит снейдера

Ну ну, не замахивайтесь на святое smile 

Автор: J0ker 29.11.2008, 02:08
Цитата(Олег2005 @  24.11.2008,  19:11 Найти цитируемый пост)
В TCP нет никаких пакетов - и тем более фрагментации

скажем так
TCP сегмент заключен в IP пакет (который в свою очередь заключен в ethernet пакет (обычно)). Возможно разбиение такого пакета на 2 и более - в зависимости от MTU на шлюзах
далее
в теории никакой фрагментации нет - есть поток
но исходя из того, что обработка данных на принимающей стороне выполняется обычно намного быстрее пересылки данных, то фрагментация безусловно ощутима и накладывает определенные условия на функцию приема таким образом, что для новичка очевидное применение recv на самом деле не является правильным.

Добавлено @ 02:12
Цитата(Олег2005 @  24.11.2008,  19:11 Найти цитируемый пост)
в частности, длина Ip-пакета меньше MTU данной сети

не меньше, а больше
не IP, а ethernet (либо другого, канального) (сори ступил)

Автор: Олег2005 30.11.2008, 14:47
Согласен - тут я просто перепутал, что больше что меньше. smile 
Спасибо за коррекцию..

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)