epoll и reassembled TCP segments
От: lnkuser  
Дата: 18.04.15 17:31
Оценка:
Подскажите пожалуйста хоть в какую сторону рыть, сутки проб и поисков не дали результата...

Пишу простенький веб-сервер сугубо для своих нужно, нужно отправлять и получать JSON.

Тестил в Firefox, все отлично, запустил на Webkit-подобном движке и...
Обнаружил что tcp пакет с хедером и контент с json идут в разных tcp пакетах! (суммарный размер прмиерно 300 байт и оно почему -то разбивает на 2 пакета!)
В Firefox, опять таки, такого нет. Один пакет.



Мой код с epoll просыпается когда есть данные на сокете, но считывается только хедер без данных...

Код работы с epoll в целом стандартный:

    constexpr uint16_t EPOLL_SIZE {1024};

    static struct epoll_event events[EPOLL_SIZE];
    int sockfd {0};
    int epoll_events_count {0};

    struct sockaddr_in client_addr;
    socklen_t socklen = sizeof(struct sockaddr_in);

    epfd_ = epoll_create(EPOLL_SIZE);
    ev_.data.fd = sockfd_;
    ev_.events = EPOLLIN | EPOLLPRI | EPOLLERR | EPOLLHUP;
    epoll_ctl(epfd_, EPOLL_CTL_ADD, sockfd_, &ev_);

    while (true) {
        try {
            process_loop();

            unsigned int timeout = process_get_standby();

            epoll_events_count = epoll_wait(epfd_, events, EPOLL_SIZE, timeout);
            for (int n=0; n<epoll_events_count; n++) {

                int fd = events[n].data.fd;
                if (fd == sockfd_) {
                    sockfd = accept(sockfd_, (struct sockaddr *)&client_addr, &socklen);
                    if (sockfd < 0)
                        continue;

                    //tcout() << "[info] established connection from: " << inet_ntoa(client_addr.sin_addr) << ":" << ntohs(client_addr.sin_port) << ", socket: " << sockfd << std::endl;

                    uint32_t ip = ntohl(client_addr.sin_addr.s_addr);
                    uint16_t port = ntohs(client_addr.sin_port);

                    process_client_connect(sockfd, ip, port);
                }
                else {
                    if ((events[n].events & EPOLLERR) || (events[n].events & EPOLLHUP)) {
                        process_client_disconnect(fd);
                        continue;
                    }
                    else if (events[n].events & EPOLLIN || EPOLLPRI) {
                        process_data_received(fd);
                    }
                }
            }
        }
        catch (std::exception &e) {
            *lgr << LogLevel::error << "[exception] in server class: " << e.what();
        }
        catch (...) {
            *lgr << LogLevel::error << "[exception] in server class";
        }
    }


Код считывания с сокета, на котором есть данные:
нижеприведенный код создает буффер определенного размера, если считаны данный длинной с буффер — расширяем его еще и так пока считается меньше чем шаг буффера, потом ресайзим буффер

    int32_t rv {0};
    static constexpr uint32_t incoming_buffer_size {2048};
    try {
        buffer.clear();
        do {
            uint32_t current_position = buffer.size();
            buffer.resize(buffer.size() + incoming_buffer_size);
            rv = read(sockfd, buffer.data() + current_position, incoming_buffer_size);
            *lgr << LogLevel::info << __func__ << " rv " << rv;
            if (rv < 0) {
                *lgr << LogLevel::error << __func__ <<  "() broken connection detected during read";
                throw std::exception();
            }
            if ((rv < static_cast<int32_t>(incoming_buffer_size)) and (buffer.size() == incoming_buffer_size))
                buffer.resize(rv);
            else if (rv < static_cast<int32_t>(incoming_buffer_size))
                buffer.resize(current_position + rv);
        }
        while (rv == static_cast<int32_t>(incoming_buffer_size));
    }
    catch (...) {
        *lgr << LogLevel::error << __func__ << " exception";
        return -1;
    }

    *lgr << LogLevel::info << __func__ << " buffer.size() " << buffer.size();
    return buffer.size();



опция TCP_DEFER_ACCEPT не помогает, может она не для этого предназначена? вроде как должна

Тоесть мой код видит первый пакет и все, второй не видит. Wireshark говорит что пакет доставлен.


Запустил пример на асинхронном boost, тоже самое...

Подскажите пожалуйста
Re: epoll и reassembled TCP segments
От: Pzz Россия https://github.com/alexpevzner
Дата: 18.04.15 17:36
Оценка: +2
Здравствуйте, lnkuser, Вы писали:

L>Тестил в Firefox, все отлично, запустил на Webkit-подобном движке и...

L>Обнаружил что tcp пакет с хедером и контент с json идут в разных tcp пакетах! (суммарный размер прмиерно 300 байт и оно почему -то разбивает на 2 пакета!)
L>В Firefox, опять таки, такого нет. Один пакет.

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

Соответственно, если пришел только хидер, то надо быть готовым дочитать данные, которые могут прийти позже. Кстати, и хидер может приехать частями, и данные тоже.
Re[2]: epoll и reassembled TCP segments
От: lnkuser  
Дата: 18.04.15 17:40
Оценка:
Здравствуйте, Pzz, Вы писали:

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


L>>Тестил в Firefox, все отлично, запустил на Webkit-подобном движке и...

L>>Обнаружил что tcp пакет с хедером и контент с json идут в разных tcp пакетах! (суммарный размер прмиерно 300 байт и оно почему -то разбивает на 2 пакета!)
L>>В Firefox, опять таки, такого нет. Один пакет.

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


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



Прикол в том, что после считывания первого пакета на сокете пусто, повторный read будет блокирующим.

Можно ли как-то принудительно ждать, пока сегменты соберуться? Ничего путного в поиске я не нашел...
Re[3]: epoll и reassembled TCP segments
От: Pzz Россия https://github.com/alexpevzner
Дата: 18.04.15 17:46
Оценка:
Здравствуйте, lnkuser, Вы писали:

L>Прикол в том, что после считывания первого пакета на сокете пусто, повторный read будет блокирующим.


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

L>Можно ли как-то принудительно ждать, пока сегменты соберуться? Ничего путного в поиске я не нашел...


Нет, нельзя.

А откуда TCP вообще может знать, что "сегменты собрались". Ему на стороне отправителя сказали два раза send(). Откуда он знает, что это связанные данные?
Re: epoll и reassembled TCP segments
От: landerhigh Пират  
Дата: 18.04.15 20:46
Оценка: +2
Здравствуйте, lnkuser, Вы писали:

L>Подскажите пожалуйста хоть в какую сторону рыть, сутки проб и поисков не дали результата...


рыть в сторону boost::asio, а то и вообще какого встраиваемого http сервера. Использовать сырые сокеты для такой задачи просто не следует.

L>Мой код с epoll просыпается когда есть данные на сокете, но считывается только хедер без данных...


Данные могут приезжать кусками какой угодно длины, и код должен быть к этому готов.
Re[3]: epoll и reassembled TCP segments
От: alex19  
Дата: 19.04.15 00:32
Оценка:
> повторный read будет блокирующим.

Только если сокет блокирующий

$ man read
...
ERRORS
EAGAIN The file descriptor fd refers to a file other than a socket and has been marked nonblocking (O_NONBLOCK), and the read
would block.

EAGAIN or EWOULDBLOCK
The file descriptor fd refers to a socket and has been marked nonblocking (O_NONBLOCK), and the read would block.
POSIX.1-2001 allows either error to be returned for this case, and does not require these constants to have the same
value, so a portable application should check for both possibilities.
Re: epoll и reassembled TCP segments
От: alex19  
Дата: 19.04.15 00:40
Оценка:
> Обнаружил что tcp пакет с хедером и контент с json идут в разных tcp пакетах!

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

Я это все к тому, что независимо от размера передаваемых данных они не факт, что придут в одном пакете и даже если в одном, то не факт, что вы их все получите за одни read, более того, размер read может быть как больше, так и меньше размера передаваемого по сети пакета.
Re[4]: epoll и reassembled TCP segments
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 19.04.15 05:59
Оценка:
Здравствуйте, Pzz, Вы писали:

Pzz>А откуда TCP вообще может знать, что "сегменты собрались". Ему на стороне отправителя сказали два раза send(). Откуда он знает, что это связанные данные?


Ну по спецификации TCP такое возможно (флаг PSH):

[quote]
Sometimes users need to be sure that all the data they have
submitted to the TCP has been transmitted. For this purpose a push
function is defined. To assure that data submitted to a TCP is
actually transmitted the sending user indicates that it should be
pushed through to the receiving user. A push causes the TCPs to
promptly forward and deliver data up to that point to the receiver.
The exact push point might not be visible to the receiving user and
the push function does not supply a record boundary marker.
[/quote]

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

Но на это все забили на практике ещё до 90-го...
The God is real, unless declared integer.
Re: epoll и reassembled TCP segments
От: Слава  
Дата: 19.04.15 06:19
Оценка:
Здравствуйте, lnkuser, Вы писали:

L>Подскажите пожалуйста хоть в какую сторону рыть, сутки проб и поисков не дали результата...


Пакет от отправителя в 300 байт размером, вам может придти в виде 300 пактов по 1 байту. Собирайте у себя.
Re[2]: epoll и reassembled TCP segments
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 19.04.15 06:27
Оценка:
Здравствуйте, Слава, Вы писали:

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


L>>Подскажите пожалуйста хоть в какую сторону рыть, сутки проб и поисков не дали результата...


С>Пакет от отправителя в 300 байт размером, вам может придти в виде 300 пактов по 1 байту. Собирайте у себя.


Это как раз вряд ли. С минимальным IP4 MTU, равным 68 байт, минимум порции данных TCP на реальных стеках — 16 байт. Так что 300 байт нарежется на 19 порций. Но в живом мире на Ethernet MTU менее 1300 не встречается.
The God is real, unless declared integer.
Re[3]: epoll и reassembled TCP segments
От: Слава  
Дата: 19.04.15 07:05
Оценка: +2
Здравствуйте, netch80, Вы писали:

N>Это как раз вряд ли. С минимальным IP4 MTU, равным 68 байт, минимум порции данных TCP на реальных стеках — 16 байт. Так что 300 байт нарежется на 19 порций.


Я про теорию. Сказано же в писании, что tcp есть поток, стало быть не следует от него ожидать каких-то границ пакетов. Это вообще надо в FAQ раздела вывесить.

N>Но в живом мире на Ethernet MTU менее 1300 не встречается.


Существует еще связь по GPRS и тому подобным каналам.
Отредактировано 19.04.2015 7:07 Слава . Предыдущая версия .
Re[4]: epoll и reassembled TCP segments
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 19.04.15 07:47
Оценка: 8 (1)
Здравствуйте, Слава, Вы писали:

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


N>>Это как раз вряд ли. С минимальным IP4 MTU, равным 68 байт, минимум порции данных TCP на реальных стеках — 16 байт. Так что 300 байт нарежется на 19 порций.


С>Я про теорию. Сказано же в писании, что tcp есть поток, стало быть не следует от него ожидать каких-то границ пакетов. Это вообще надо в FAQ раздела вывесить.


С этим полностью согласен. Но примеры для объяснения должны быть реальными. А особенно — должно быть сказано, что это как раз тот случай, когда банально пройти через сито собственных функциональных тестов, но нарваться на проблему в реальном мире.
А ещё я бы в тот же FAQ запихнул жёсткое "нужен собственный протокол — начинайте с SCTP и переходите на TCP только если первый недоступен".

N>>Но в живом мире на Ethernet MTU менее 1300 не встречается.


С>Существует еще связь по GPRS и тому подобным каналам.


GPRS даёт обычно цифры типа 1400.
The God is real, unless declared integer.
Re[5]: epoll и reassembled TCP segments
От: kaa.python Ниоткуда РСДН профессионально мёртв и завален ватой.
Дата: 19.04.15 08:12
Оценка:
Здравствуйте, netch80, Вы писали:

N>А ещё я бы в тот же FAQ запихнул жёсткое "нужен собственный протокол — начинайте с SCTP и переходите на TCP только если первый недоступен".


А он разве где-то за пределами UNIX-ов поддерживается? Насколько знаю, ни в OS X ни в Windows нет поддержки. Да и не без своих проблем, вроде как. Но в целом, идея безусловно здравая, не надо придумывать новый протокол, если можно обойтись существующими.
Re[6]: epoll и reassembled TCP segments
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 19.04.15 08:39
Оценка:
Здравствуйте, kaa.python, Вы писали:

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


N>>А ещё я бы в тот же FAQ запихнул жёсткое "нужен собственный протокол — начинайте с SCTP и переходите на TCP только если первый недоступен".


KP>А он разве где-то за пределами UNIX-ов поддерживается? Насколько знаю, ни в OS X ни в Windows нет поддержки.


OS X это Unix, конкретно, в основном BSD.
Для Windows есть минимум 2 доступные бесплатные реализации и несколько платных.
The God is real, unless declared integer.
Re: epoll и reassembled TCP segments
От: smeeld  
Дата: 19.04.15 09:13
Оценка:
Здравствуйте, lnkuser, Вы писали:


L>Мой код с epoll просыпается когда есть данные на сокете, но считывается только хедер без данных


Считывайте с сокета по мере прихода пакетов. Сделать сокеты неблокирующими, буфер для чтения фиксированным.
Количество данных для чтения в recv/read/recvmsg назначаем равным размеру фиксированного
буфера. Если чтение вернуло -1, и error выставился в EAGAIN, ждём пакетов на сокете, которые, когда придут,
просигнализируют о себе появлением в возврате из epoll_wait. Если прочитали размер фиксированного буфера, значит
на сокете, возможно, есть непрочитанные данные, после обработки прочитанных данных нужно будет вызвать read/recv/recvmsg
снова. Если чтение вернуло количество, меньшее запрошенного, то на сокете данных больше нет, ждём дальше, если в прочитанных
данных только хедер без тела, например. Разные буфера-куски фиксированного размера, прочитаные в разное время, можно собирать в
распределёный буфер-список буферов фиксированного размера, представляющий собой абстракцию непрерывного буфера. Хотя в Вашем
resize, может, именно это. Такими являются буфера приёмных и отправных пакетов в ядрах ОС.
Re[7]: epoll и reassembled TCP segments
От: smeeld  
Дата: 19.04.15 09:40
Оценка:
Здравствуйте, netch80, Вы писали:

N>OS X это Unix, конкретно, в основном BSD.


Это не Unix, как и BSD. Это Unix-like.
Re[7]: epoll и reassembled TCP segments
От: kaa.python Ниоткуда РСДН профессионально мёртв и завален ватой.
Дата: 19.04.15 09:45
Оценка:
Здравствуйте, netch80, Вы писали:

N>OS X это Unix, конкретно, в основном BSD.


ПОловина BSD, половина Mach. Сеть – старая BSD с сильными изменениями.

N>Для Windows есть минимум 2 доступные бесплатные реализации и несколько платных.


Которые нужно еще отдельно установить. Однозначно не вариант, если приложение не для гиков, конечно.
Re[5]: epoll и reassembled TCP segments
От: v_andal Германия  
Дата: 20.04.15 08:32
Оценка:
Здравствуйте, netch80, Вы писали:

N>А ещё я бы в тот же FAQ запихнул жёсткое "нужен собственный протокол — начинайте с SCTP и переходите на TCP только если первый недоступен".


Я бы этого точно этого не рекомендовал. Это противоречит KISS. TCP элементарен в использовании. Чтобы использовать расширенные возможности SCTP нужно разобраться с кучей доков, а в конце выясняется, что всех удовольствий — это возможность указать системе границы пакетов которые нужно отдавать пользователю. Вся остальная дребедень типа мультихома нужна только в специфических случаях и требует ещё кучу сложностей.

Так что начинать стоит с TCP, и обращаться к другим вариантам только если через TCP ну никак не выходит.

В данном же случае, HTTP вообще не предполагает пакетных границ. Он уже заточен под использование TCP. Так что ожидание данных в рамках одного пакета — это классические грабли всех начинающих сетевых программистов. Нужно изначально проектировать решение как систему обработчиков событий. Нашёл в буфере ASCII 10, передай управление обработчику строк. Обработчик обнаружил хидер дающий длину тела, пусть запустит ожидание нужного количество байт тела и так далее. Наличие epoll вообще не играет особой роли. Лучше вообще от него абстрагироваться, использовав какой-нибудь libevent или собственную библиотеку. Есть даже люди умудряющиеся читать-писать-обрабатывать в разных потоках. Только и в этом случае всё подчиняется тому же принципу обработки событий.
Re[5]: epoll и reassembled TCP segments
От: Mr.Delphist  
Дата: 21.04.15 12:20
Оценка:
Здравствуйте, netch80, Вы писали:

N>А ещё я бы в тот же FAQ запихнул жёсткое "нужен собственный протокол — начинайте с SCTP и переходите на TCP только если первый недоступен".


Хм, а что изменит SCTP? Ну, будет информация ходить через другой транспорт, и чего? Каких-то кардинальных упрощений в прикладном смысле это человеку не даст. Лучше тогда уж в самом деле всякий boost::asio, Protobuf и всё такое прочее.
Re[6]: epoll и reassembled TCP segments
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 22.04.15 04:47
Оценка:
Здравствуйте, Mr.Delphist, Вы писали:

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


N>>А ещё я бы в тот же FAQ запихнул жёсткое "нужен собственный протокол — начинайте с SCTP и переходите на TCP только если первый недоступен".


MD>Хм, а что изменит SCTP?


Границы сообщений, гарантированные транспортом.

MD> Ну, будет информация ходить через другой транспорт, и чего? Каких-то кардинальных упрощений в прикладном смысле это человеку не даст. Лучше тогда уж в самом деле всякий boost::asio, Protobuf и всё такое прочее.


Условие данного совета — собственный протокол. Что там внутри у этого протокола — вопрос следующий.
The God is real, unless declared integer.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.