Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Программирование под Unix/Linux > использование epoll


Автор: slesh2000 24.6.2010, 13:45
Разбираюсь сейчас с 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) перед закрытием сокета, надо ли его переводить обратно в блокируемый режим?

Автор: MAKCim 24.6.2010, 15:30
Цитата(slesh2000 @  24.6.2010,  13:45 Найти цитируемый пост)
// какое желательное значение ставить для EPOLL_SIZE.

предположительно максимальное число клиентов
однако этот параметр все равно в текущей реализации не используется, так что по факту - любое количество

Цитата(slesh2000 @  24.6.2010,  13:45 Найти цитируемый пост)
// какое максимальное кол-во может быть добавлено сокетов?

ограничено памятью


Цитата(slesh2000 @  24.6.2010,  13:45 Найти цитируемый пост)
// может ли следующий участок вызвать ошибку при большой загруженности

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


Цитата(slesh2000 @  24.6.2010,  13:45 Найти цитируемый пост)
// какие желательно обрабатывать события?

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


Цитата(slesh2000 @  24.6.2010,  13:45 Найти цитируемый пост)
// или достаточно просто его закрыть?

достаточно закрыть через close()


Цитата(slesh2000 @  24.6.2010,  13:45 Найти цитируемый пост)
/ должен ли я тут закрывать все сокеты которые были добавлены ранее?

должен


Цитата(slesh2000 @  24.6.2010,  13:45 Найти цитируемый пост)
2) echo сервер это довольно простой вариант, а если требуется более сложные вычисления которые требуют распараллеливание запросов клиентов. т.е. допустим клиент послал запрос, сервер обработал его и ответил. Какая схема многопоточности подойдет?

1 аккептор (тот, что слушает сокет в ожидании клиентов)
N рабочих потоков, каждый со своим epoll (N = числу процессоров/ядер на тачке)


Цитата(slesh2000 @  24.6.2010,  13:45 Найти цитируемый пост)
3) что можно еще использовать для увеличения скорости работы с сетью. Помимо TCP_NODELAY

потюнить можно через /proc/sys/net, более детально смотри соответствующий ман


Цитата(slesh2000 @  24.6.2010,  13:45 Найти цитируемый пост)
4) появление каких сигналов желательно обрабатывать в данном случае?
потому как столкнулся с проблемой - если записать в сокет отключенного клиента, то появляется сигнал об это.

главный процесс (который попрождает остальные потоки) используй в качестве advisor'а, т. е. он будет являться неким монитором, который за всем следит, перехватывает _все_ возможные сигналы (в остальных потоках их надо заблокировать), а дальше уже смотрит, что с ними делать


Цитата(slesh2000 @  24.6.2010,  13:45 Найти цитируемый пост)
5) как я понял при добавлении сокета в структуре epoll_event поле fd используется для удобства, а на реале я могу в ptr записать адрес структуры, которая более детально описывает клиента?

да


Цитата(slesh2000 @  24.6.2010,  13:45 Найти цитируемый пост)
6) на Windows системах для автоматической проверки соединения использовал функцию WSAIoctl с флагом SIO_KEEPALIVE_VALS, т.е. при не активности клиента автоматически система посылала пустой пакет с данными на который клиент должен был ответить, по прошествии таймауте (если не было ответа) то соединение считалось потеряным. Если подобие такой функции на Linux системах?

man 7 tcp


Цитата(slesh2000 @  24.6.2010,  13:45 Найти цитируемый пост)
7) перед закрытием сокета, надо ли его переводить обратно в блокируемый режим? 

нет

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