LINUX.ORG.RU

Знатокам UDP (+)


0

0

Понадобилось тут написать софтину
что по UDP работает......
В ходе экспериментов выяснилось
что в UDP нельзя принимать информацию кусками
сразу целиком и все тут.....
И возник вопрос, если датаграмма неопределенной длинны
но размер ее указан в хедере пакета, то как принять грамотно ?
Получается надо выделять буфер под размер максимального пакета
и уже потом разбирать данные, есть ли другой способ ?
64 кб как бы тоже память :)

anonymous
Ответ на: комментарий от erDiZz

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

anonymous
()
Ответ на: комментарий от anonymous

Вот еще глянул спеки IP
он как базовый протокол отвечает за фрагментацию
посему все должно быть все путем......
Запустил свою тестовую софтину в инете
тоже все прошло успешно, да начался дроп пакетов
когда размер пакета стал больше чем мой канал успевал
пропустить физически в моем случае 40кб\сек.....
но это несколько другое.
Откуда 1500 ?

anonymous
()
Ответ на: комментарий от Pi

Это кому и про что ?
Несколько мысль не понял :)

anonymous
()
Ответ на: комментарий от anonymous

именно. MTU. UDP не допускает фрагментации пакета. Дастаточно посмотреть RFC768.

Более того, UDP не гарантирует порядок доставки датаграмм => они могут перемешаться, и UDP-пакеты могут собираться в буфер и отдаваться оттуда кучками.

Кучка перемешанных пакетов.. Наличие буфера говорит о том, что ограничения на минимальный размер приёмного буфера не должно быть.

erDiZz
()
Ответ на: комментарий от erDiZz

Млин совсем попутался :)
А почему тогда мои эксперименты прошли успешно ?
Хотя на интерфейсах MTU 1500
Однако по RFC IP протокола именно он отвечает за
фрагментацию пакетов, и вроде как я прав и все
должно быть нормально...
Есть еще тут кто-нить кто реально писал под UDP
или хотя-бы эксперименты проводил.....
?

anonymous
()
Ответ на: комментарий от anonymous

RFC я посмотрел в первую очередь
однако что-то ничего про фрагментацию там
не увидел.....
Плиз можно точнее где там что....

anonymous
()
Ответ на: комментарий от anonymous

Советую посмотреть man 7 udp и man 7 ip на предмет IP_MTU_DISCOVER. Там как раз формально описывается, что делает стек UDP с большими пакетами (а именно, фрагментирует их, если они превышают MTU). Я погонял тесты на локальной машине, проходят пакеты размером до 65507 байт, но через ethernet, думаю, результаты будут другими. Можно просто посмотреть на вещи: через ethernet гарантированно не пройдёт больше 1500 байт на пакет => такого буфера достаточно.

erDiZz
()
Ответ на: комментарий от erDiZz

В том то и дело что тестил
и между 2мя серваками на разных хостерах
и между своим локальным серваком в нет через DSL
ходящим и тем что в нете стоит....
пока упора в канал нету все работает
40кб датаграммы без проблем летают туда-сюда...

anonymous
()
Ответ на: комментарий от erDiZz

что-то вы странное говорите.

> UDP не допускает фрагментации пакета. Дастаточно посмотреть RFC768.

а в другом месте:

> а именно,  фрагментирует их, если они превышают MTU

так да или нет?

вот именно, фрагментирует, ну и что? reassabling вообще
на уровне ip происходит.

по поводу IP_MTU_DISCOVER, так оно же dontfragment
включает, в этом все и дело.

idle ★★★★★
()
Ответ на: комментарий от idle

ok, вижу, что не прав. Получается, что благодаря IP вполне может собраться 64k-датаграмма, которую обязательно надо принять "за раз", то есть в 64k-буфер?

erDiZz
()
Ответ на: комментарий от erDiZz

> которую обязательно надо принять "за раз", то есть в 64k-буфер?

можно узнать размер датаграммы: ioctl(SIOCINQ).

если данные не нужно никуда копировать, можно просто разместить
64k буфер в стеке (да, многим это не нравится, и у этого подхода
есть недостатки). это самый эффективный подход во многих смыслах.

idle ★★★★★
()
Ответ на: комментарий от idle

Только опять же надо пояснить, что надо делать:

char buffer [64k];
for (;;)
{
    // receive
}

а не так:

for (;;)
{
    char buffer [64k];
    // receive
}

Различия в бысродействии огромны

mumg
()
Ответ на: комментарий от mumg

> Различия в бысродействии огромны

хмм... почему вы так думаете?

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

нет, я не спорю, первый вариант лучше (хотя бы потому,
что естественнее), но разница в быстродействии?

idle ★★★★★
()
Ответ на: комментарий от idle

Проверил на последних версиях компайлеров, такой проблемы уже нет. когда использовали 2.96 то разница в быстродействии различалась в 10 раз. Исследований "почему", не проводились, после оптимизации кода все стало работать быстро. Возможно не только это давало side effects, на самом деле по смыслу различий быть не должно. Текущая разница на примере где то 5%.

mumg
()
Ответ на: комментарий от cvv

только вот как ? в любом случае нужно иметь буфер больше размера пакета.

     The recv() function returns the length of the message  writ-
     ten  to  the  buffer pointed to by the buffer argument.

У меня есть предложения по поводу решения этой проблемы, но она 
решается не на уровне сокетов, а на уровне протокола лежащего поверх.
НЕ ЗАБЫВАЙТЕ что есть LITTLE ENDIAN и BIG ENDIAN системы.
Для оценщиков качества кода, напоминаю что это псевдо код :)
призванный показать алгоритм.

typedef struct
{
      uint32_t length;
      // something else
} udp_packet_header_t;


udp_packet_headert_t udp_packet_header;
recv ( in_socket , udp_packet_headert_t , sizeof (udp_packet_headert_t) , MSG_PEEK );
if ( udp_packet_header.length > 0 )
{
     char * buffer = malloc ( udp_packet_header.length );
     recv ( in_socket , buffer , udp_packet_header.length , 0 );
     //buffer + sizeof (udp_packet_headert_t)  и будут данные
}

mumg
()
Ответ на: комментарий от mumg

естественно должно быть так :)

typedef struct
{
      uint32_t length;
      // something else
} udp_packet_header_t;


udp_packet_headert_t udp_packet_header;
recv ( in_socket , &udp_packet_header , sizeof (udp_packet_headert_t) , MSG_PEEK );
if ( udp_packet_header.length > 0 )
{
     char * buffer = malloc ( udp_packet_header.length );
     recv ( in_socket , buffer , udp_packet_header.length , 0 );
     //buffer + sizeof (udp_packet_headert_t)  и будут данные
}

mumg
()
Ответ на: комментарий от mumg

>только вот как ? в любом случае нужно иметь буфер больше размера пакета. > > The recv() function returns the length of the message writ- > ten to the buffer pointed to by the buffer argument.

а в стивенсе кажись нечто иное написано... завтра посмотрю

cvv ★★★★★
()
Ответ на: комментарий от mumg

> только вот как ? в любом случае нужно иметь буфер больше
> размера пакета.

не понимаю, о чем вы. еще раз, ioctl(SIOCINQ) вернет
размер датаграммы.

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

> The recv() function returns the length of the message  writ-
> ten  to  the  buffer pointed to by the buffer argument.

так ведь не length written, а message written.

idle ★★★★★
()
Ответ на: комментарий от idle

>код приведенный вами, разумеется, работать не будет. >с чего вы взяли, что MSG_PEEK запишет вам размер ?

>> The recv() function returns the length of the message writ- >> ten to the buffer pointed to by the buffer argument.

>так ведь не length written, а message written.

он кажись предположил что конкретный udp протокол содержит хидер с полем "длина пакета"

cvv ★★★★★
()
Ответ на: комментарий от cvv

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

пакет поверх UDP:

|udp header|------------udp body-------------------| transport layer
           |udp_packet_header_t|---message body----| application layer

я думаю ioctl(SIOCINQ) не является портируемой вешью. 
Я же к любому коду отношусь с точки зрения портируемости, прогу меня извинить :)

mumg
()
Ответ на: комментарий от mumg

> вглядитесь в код, я предложил использовать поддержку длины пакета
> как на стороне источника так и на стороне приемника

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

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

> я думаю ioctl(SIOCINQ) не является портируемой вешью.

думаете или знаете? я - не знаю, буду рад информации
на этот счет. но, вообще-то, FIONREAD довольно стандартная
вещь.

idle ★★★★★
()
Ответ на: комментарий от idle

> думаете или знаете? но, вообще-то, FIONREAD довольно стандартная вещь Да, так и есть, вещь стандартная, только для Linux system. На SunOs 5.8 и в Win32 я не нашел этого. Для SunOs я могу ошибаться, для винды вроде точно нет.

mumg
()
Ответ на: комментарий от cvv

>а в стивенсе кажись нечто иное написано... завтра посмотрю

не нашёл. возможно ошибся но кажись в нём встречал что при указании MSG_PEEK для recvfrom возвращается общая длина пакета независимо от размера приёмного буфера.

ещё похожая инфа содержится в "netwoking programming guide" для irix.

до sus && posix не добрался

cvv ★★★★★
()
Ответ на: комментарий от cvv

> но кажись в нём встречал что при указании MSG_PEEK для recvfrom 
> возвращается общая длина пакета независимо от размера приёмного буфера
тогда каким образом определить сколько же мы прочитали в буфер ?

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>


int main ( int argc , char ** argv )
{
    int server_SID = 0;
    int client_SID = 0;
    sockaddr_in server_parms;
    if ( ( server_SID = socket ( AF_INET , SOCK_DGRAM , 0 ) ) > 0 )
    {
        server_parms.sin_family = AF_INET;
        server_parms.sin_addr.s_addr = inet_addr("127.0.0.1");
        server_parms.sin_port = htons ( 20000 );
        if ( bind ( server_SID , (sockaddr*)&server_parms , sizeof (server_parms)) == 0 )
        {
                if ( ( client_SID = socket ( AF_INET , SOCK_DGRAM , 0 )) > 0 )
                {
                        pid_t server_process = fork ();
                        if ( server_process == 0 )
                        {
                                sockaddr_in sender_info;
                                socklen_t sender_length = sizeof (sender_info) ;
                                char buffer [8];
                                int length = 0;
                                // child process

                                for (;;)
                                {
                                        length = recvfrom ( server_SID , buffer , sizeof (buffer) , MSG_PEEK , (sockaddr*)&sender_info , &sender_length);
                                        if ( length < 0 )
                                        {
                                                break;
                                        }
                                        else
                                        {
                                                fprintf ( stdout , "Received %d bytes, buffer length is %d\n" , length , sizeof (buffer) );
                                                fflush ( stdout );
                                                break;
                                        }
                                }
                                exit (0);
                        }
                        else if ( server_process > 0 )
                        {
                                // parent process
                                int waitpid_result = 0;
                                const char string [] = "String to send";
                                fprintf ( stdout , "Trying to send string with length %d\n" , strlen ( string ));
                                fflush ( stdout );
                                if ( sendto ( client_SID  , string , strlen ( string ) , 0 , (sockaddr*)&server_parms , sizeof (server_parms) ) <= 0 )
                                {
                                        close ( server_SID );
                                        server_SID = 0;
                                }
                                fprintf ( stdout , "waitpid returns %d\n" , waitpid ( server_process , &waitpid_result , 0 ));
                                fflush ( stdout );
                        }
                }

        } 
    }
    if ( server_SID > 0 )
    {
        close ( server_SID );
    }
    if ( client_SID > 0 )
    {
        close ( client_SID );
    }
    return 0;
}

результат:

./test.x 
Trying to send string with length 14
Received 8 bytes, buffer length is 8
waitpid returns 23064

mumg
()
Ответ на: комментарий от mumg

>результат:

>./test.x >Trying to send string with length 14 >Received 8 bytes, buffer length is 8 >waitpid returns 23064

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

ЗЫ:вот к чему приводит чтение книг по ночам.

cvv ★★★★★
()
Ответ на: комментарий от cvv

бывает :)

Кстати вот тот вариант который я предлагал выше:

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>


typedef struct
{
        unsigned int length;
        // something else
} udp_packet_t;

int main ( int argc , char ** argv )
{
    int server_SID = 0;
    int client_SID = 0;
    sockaddr_in server_parms;
    if ( ( server_SID = socket ( AF_INET , SOCK_DGRAM , 0 ) ) > 0 )
    {
        server_parms.sin_family = AF_INET;
        server_parms.sin_addr.s_addr = inet_addr("127.0.0.1");
        server_parms.sin_port = htons ( 20000 );
        if ( bind ( server_SID , (sockaddr*)&server_parms , sizeof (server_parms)) == 0 )
        {
                if ( ( client_SID = socket ( AF_INET , SOCK_DGRAM , 0 )) > 0 )
                {
                        pid_t server_process = fork ();
                        if ( server_process == 0 )
                        {
                                sockaddr_in sender_info;
                                socklen_t sender_length = sizeof (sender_info) ;
                                char *  buffer = 0;
                                int length = 0;
                                udp_packet_t udp_packet;
                                // child process

                                for (;;)
                                {
                                        length = recvfrom ( server_SID , &udp_packet , sizeof (udp_packet) , MSG_PEEK , (sockaddr*)&sender_info , &sender_length);
                                        if ( length < 0 )
                                        {
                                                break;
                                        }
                                        else
                                        {
                                                if ( udp_packet.length > 0 )
                                                {
                                                        length = udp_packet.length + sizeof (udp_packet_t);
                                                        buffer = (char *)malloc ( length );
                                                        length = recvfrom ( server_SID , buffer , length , 0 , (sockaddr*)&sender_info , &sender_length);
                                                        fprintf ( stdout , "Buffer: '%s' , length %d\n" , buffer +  sizeof (udp_packet_t) , udp_packet.length );
                                                        free ( buffer );
                                                        fflush ( stdout );
                                                }
                                                break;
                                        }
                                }
                                exit (0);
                        }
                        else if ( server_process > 0 )
                        {
                                // parent process
                                int waitpid_result = 0;
                                const char string [] = "String to send";
                                int packet_length = strlen (string) + 1 + sizeof (udp_packet_t);
                                char * packet = (char*)malloc ( packet_length );
                                ((udp_packet_t*)packet)->length = strlen ( string ) + 1;
                                strcpy ( packet +  sizeof (udp_packet_t) , string );
                                fprintf ( stdout , "Trying to send string with length %d\n" , strlen (string) + 1);
                                fflush ( stdout );

                                if ( sendto ( client_SID  , packet , packet_length , 0 , (sockaddr*)&server_parms , sizeof (server_parms) ) <= 0 )
                                {
                                        close ( server_SID );
                                        server_SID = 0;
                                }
                                free ( packet );
                                fprintf ( stdout , "waitpid returns %d\n" , waitpid ( server_process , &waitpid_result , 0 ));
                                fflush ( stdout );
                        }
                }

        } 
    }
    if ( server_SID > 0 )
    {
        close ( server_SID );
    }
    if ( client_SID > 0 )
    {
        close ( client_SID );
    }
    return 0;
}

mumg
()
Ответ на: комментарий от cvv

cvv:
> но кажись в нём встречал что при указании MSG_PEEK для recvfrom
> возвращается общая длина пакета независимо от размера приёмного
> буфера.

нет, нет. MSG_PEEK только означает не удалять skb
из ->sk_receive_queue.

idle ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.