LINUX.ORG.RU

парочка вопросов по сети и epoll


0

1

Разбираюсь сейчас с epoll. Для теста навоял простенький echo сервер
Все вопросы по ходу кода (в комментариях)

#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <sys/socket.h>


#define SERVER_ADDR INADDR_ANY
#define SERVER_PORT 12345
#define MAX_EVENTS  100
#define EPOLL_SIZE  100


int SetNonBlocking(int sock)
{
    int ret = -1;
    int opts = fcntl(sock, F_GETFL);

    if (opts >= 0)
    {
        opts = (opts | O_NONBLOCK);
        if (fcntl(sock, F_SETFL, opts) >= 0)
        {
            ret = 0;
        }
    }

    return ret;
}

int main()
{
    int MainSock;
    struct sockaddr_in saddr;
    int x;
    int cnt;
    int len;
    int epfd;
    struct epoll_event ev;
    struct epoll_event events[MAX_EVENTS];
    char buf[256];
    int Work = 1;

    MainSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (MainSock != -1)
    {
        saddr.sin_family = AF_INET;
        saddr.sin_addr.s_addr = SERVER_ADDR;
        saddr.sin_port = htons(SERVER_PORT);
        x = 1;
        setsockopt(MainSock, SOL_SOCKET, SO_REUSEADDR, &x, sizeof (x));
        if (bind(MainSock, (struct sockaddr*) &saddr, sizeof (struct sockaddr_in)) != -1)
        {
            listen(MainSock, 100);
            // какое желательное значение ставить для EPOLL_SIZE.
            // да и вообще какое максимально возможное?
            // в одних источниках пишется что задается максимальное
            // в других что желаемое (но не предельное)
            if ((epfd = epoll_create(EPOLL_SIZE)) != -1)  
            {
                ev.events = EPOLLIN;
                ev.data.fd = MainSock;
                // какое максимальное кол-во может быть добавлено сокетов?
                // по описанию - неограниченное. Но всё же лимиты должны быть
                // или пока есть память?
                if (epoll_ctl(epfd, EPOLL_CTL_ADD, MainSock, &ev) != -1)
                {
                    while (Work)
                    {
                        // может ли следующий участок вызвать ошибку при большой загруженности
                        // или же он вызывает ошибку только в реально плачевном случае
                        // когда уже ничего не исправишь?
                        if ((cnt = epoll_wait(epfd, events, MAX_EVENTS, -1)) == -1)
                        {
                            Work = 0;
                        }

                        for (x = 0; x < cnt; x++)
                        {
                            if (events[x].data.fd == MainSock) // if new client
                            {
                                ev.data.fd = accept(MainSock, NULL, 0);
                                if (ev.data.fd == -1)
                                {
                                    Work = 0;
                                }
                                else
                                {
                                    SetNonBlocking(ev.data.fd);
                                    // какие желательно обрабатывать события?
                                    // если необходимо сервер только отвечает на запросы клиента 
                                    //(т.е. сам не шлет данные пока их не запросят)
                                    ev.events = EPOLLIN | EPOLLET;

                                    if (epoll_ctl(epfd, EPOLL_CTL_ADD, ev.data.fd, &ev) == -1)
                                    {
                                        close(ev.data.fd);
                                    }
                                }
                            }
                            else // if clients event
                            {
                                if (events[x].events & EPOLLIN) // if client sended data
                                {
                                    len = recv(events[x].data.fd, buf, 256, 0);
                                    if (len > 0)
                                    {
                                        if (!strncmp(buf, "EXIT", 4))
                                        {
                                            send(events[x].data.fd, "CLOSED\n", 7, 0);
                                            close(events[x].data.fd);
                                            // надо ли тут удалять сокет из epfd (через EPOLL_CTL_DEL)?
                                            // или достаточно просто его закрыть?
                                        }
                                        else if (!strncmp(buf, "TERM", 4))
                                        {
                                            Work = 0;
                                        }
                                        else
                                        {
                                            send(events[x].data.fd, buf, len, 0);
                                        }
                                    }
                                    else // client disconnected
                                    {
                                        close(events[x].data.fd);
                                        // аналогично вышеуказанному вопросу
                                    }
                                }
                                else if (events[x].events & (EPOLLHUP | EPOLLERR)) // if client error
                                {
                                    close(events[x].data.fd);
                                    // аналогично вышеуказанному вопросу
                                }
                            }
                        }
                    }
                }
                close(epfd);
                // должен ли я тут закрывать все сокеты которые были добавлены ранее?
                // или закрытие epfd автоматически вызовет это??
            }
            close(MainSock);
        }
    }

    return 0;
}

И парочка вопросов дополнительных:
1) какое наиболее оптимальное значение для MAX_EVENTS может быть, если учесть что сервер должен обрабатывать много клиентов сразу и много данных передавать (при этом вычисления минимальны)
2) echo сервер это довольно простой вариант, а если требуется более сложные вычисления которые требуют распараллеливание запросов клиентов. т.е. допустим клиент послал запрос, сервер обработал его и ответил. Какая схема многопоточности подойдет? Вообще в голову пришла идея чтобы как только пришли данные от клиента, так сразу запускать поток для обработки или же уже иметь предварительно запущенные потоки которые ожидают вызова? Или же для linux систем чуть по другому всё? При этом fork недопустим в данном случае из-за особенностей обработки данных.
3) что можно еще использовать для увеличения скорости работы с сетью. Помимо TCP_NODELAY
4) появление каких сигналов желательно обрабатывать в данном случае? потому как столкнулся с проблемой - если записать в сокет отключенного клиента, то появляется сигнал об это.
5) как я понял при добавлении сокета в структуре epoll_event поле fd используется для удобства, а на реале я могу в ptr записать адрес структуры, которая более детально описывает клиента?
6) на Windows системах для автоматической проверки соединения использовал функцию WSAIoctl с флагом SIO_KEEPALIVE_VALS, т.е. при не активности клиента автоматически система посылала пустой пакет с данными на который клиент должен был ответить, по прошествии таймауте (если не было ответа) то соединение считалось потеряным. Если подобие такой функции на Linux системах?
7) перед закрытием сокета, надо ли его переводить обратно в блокируемый режим?


> // какое желательное значение ставить для EPOLL_SIZE.
// да и вообще какое максимально возможное?
// в одних источниках пишется что задается максимальное
// в других что желаемое (но не предельное)

Как я понимаю любое положительное значение, так как оно проверяется, но реально больше не используется epoll'ом после перехода на RB-trees:

It was used to determine the initial hashtable size in the kernel's epoll implementation, but newer versions use a RB-tree, so it's ignored.

Krivenok_Dmitry
()

1) 4) 5) юзай libev или libevent и будет тебе счастье в сто раз проще. И кроссплатформенно
2) http://www.opennet.ru/base/dev/server_way.txt.html
3) буфера крутить и man tcp, там посмотришь. Ну и sysctl посмотри, в гугле всё легко гуглится
6) есть
7) нет

true_admin ★★★★★
()

> // надо ли тут удалять сокет из epfd (через EPOLL_CTL_DEL)? // или достаточно просто его закрыть?

Q6 Will closing a file descriptor cause it to be removed from all epoll sets automatically?

A6 Yes, but be aware of the following point. A file descriptor is a reference to an open file description (see open(2)). Whenever a descriptor is duplicated via dup(2), dup2(2), fcntl(2) F_DUPFD, or fork(2), a new file descriptor referring to the same open file description is created. An open file description continues to exist until all file descriptors referring to it have been closed. A file descriptor is removed from an epoll set only after all the file descriptors referring to the underlying open file description have been closed (or before if the descriptor is explicitly removed using epoll_ctl() EPOLL_CTL_DEL). This means that even after a file descriptor that is part of an epoll set has been closed, events may be reported for that file descriptor if other file descriptors referring to the same underlying file description remain open.

Krivenok_Dmitry
()

Не понял вопрос про ошибки в epoll_wait.
А вообще дока по epoll весьма приличная:

epoll (4) - I/O event notification facility
epoll (7) - I/O event notification facility
epoll_create (2) - open an epoll file descriptor
epoll_ctl (2) - control interface for an epoll descriptor
epoll_pwait (2) - wait for an I/O event on an epoll file descriptor
epoll_wait (2) - wait for an I/O event on an epoll file descriptor

По поводу второго вопроса могу порекомендовать посмотреть на http://www.kegel.com/c10k.html
Пожалуй лучшая компиляция материалов на тему.

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

Огромное спасибо! И вот еще один вопрос: Если не нужна очень сложная обработка данных(в основном только пересылка) и допустим процессор имеет N ядер. То могу я создать один epoll, затем запустить N*2 потоков и в каждом мучать через epoll_wait один и тот же дискриптор epoll? т.е. чтобы обработкой были загружены сразу все ядра процессоров. Или же лучше для каждого потока создать свой epoll и потом в них раскидывать сокеты в зависимости от загруженности?

slesh
() автор топика
Ответ на: комментарий от Boy_from_Jungle

[quote]

listen(MainSock, 100);

?! [/quote] Чтобы это значило? То что я забыл проверить результат функции? или само значение 100? В различной литературе приводятся значения от 5 до 128

slesh
() автор топика

Ну когда же люди научатся читать man?!

Итак, учу читать маны. Берем, например, вопрос

какое желательное значение ставить для EPOLL_SIZE?

man epoll_create (NOTES)

Since Linux 2.6.8, the size argument is unused. (The kernel dynamically sizes the required data structures without needing this initial hint.)

Еще:

может ли следующий участок вызвать ошибку при большой загруженности

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

man epoll_wait (ERRORS)

EBADF epfd is not a valid file descriptor.

EFAULT The memory area pointed to by events is not accessible with write permissions.

EINTR The call was interrupted by a signal handler before any of the requested events occurred or the timeout expired; see signal(7).

EINVAL epfd is not an epoll file descriptor, or maxevents is less than or equal to zero.

Отсюда однозначный непротиворечивый вывод: если входные данные корректны, ошибки (тем более критической) быть не может.

А также

надо ли тут удалять сокет из epfd (через EPOLL_CTL_DEL)?

или достаточно просто его закрыть?

man epoll (Questions and answers):

Q6 Will closing a file descriptor cause it to be removed from all epoll sets automatically?

A6 Yes, but be aware of the following point...

На большинство твоих вопросов несложно найти правильные (!) ответы в man epoll, man epoll_ctl, man epoll_wait, man 7 socket, man 7 tcp

Разве что на

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

должна проснуться элементарная логика с вопросом: «Зачем?». Правильный ответ: «нафиг не нужно, потому что close не возвращает ошибки EAGAIN» --, значит блокирующий сокет или неблокирующий для него абсолютно все равно. Но зато man 7 socket расскажет об опции SO_LINGER, которую надо сбрасывать, иначе будет плохо.

linuxfan
()
Ответ на: Ну когда же люди научатся читать man?! от linuxfan

Отсюда однозначный непротиворечивый вывод: если входные данные корректны, ошибки (тем более критической) быть не может.


В 99.9% случаев все так.
Но все же полагаться на список ошибок в man'е не очень надежно.
У меня был случай, когда sendto возвращал EPERM при большом рэйте вызовов оного.
При этом согласно докам эту ошибку он возвратить не может.

Поиск в гугле показал, что sendto все же иногда возвращает EPERM если
1) Отсылка датаграмм закрыта на _локальном_ файрволе (не мой случай).
2) Когда активирован SELinux (вот это как раз мой случай был :)).

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

>Но все же полагаться на список ошибок в man'е не очень надежно.

Боюсь, что надежнее только исходный код смотреть, но это очень долго.

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

> Боюсь, что надежнее только исходный код смотреть, но это очень долго.

Согласен, но он не всегда доступен (например если запускаешь программу на внешней системе с другим или пропатченным ядром, включенным SELinux, etc).
Поэтому лучше не закладываться на перечисленные в мане коды ошибок и всегда иметь «else» для «всех остальных» ошибок.

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