URL: https://www.opennet.me/cgi-bin/openforum/vsluhboard.cgi
Форум: vsluhforumID9
Нить номер: 8543
[ Назад ]

Исходное сообщение
"Первый многопользовательский серевер с использованием epoll(..."

Отправлено niXman , 09-Дек-09 23:38 
Всем привет.
Пытаюсь освоить сетевое программирование на низком уровне(POSIX API). Интересна задача реализовать многопользовательский сервер использующий механизм epoll().
В программировании не новичек. В сетевом программировании есть пробелы :)
Вот что я набросал. Но не все понятно. Если не затруднит, прокоментируйте код, может что-то не правильно сделал, возможно есть более правильное решение.

#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 ZEROPOD(var) memset(&var, 0, sizeof(var))

const int MYPORT = 1234;
const int qsize = 10;
const int epsize= 10;

/** устанавливает неблокирующий режим для указанного сокета */
int setnonblocking(int sock) {
    int opts = fcntl(sock,F_GETFL);
    if (opts < 0) {
        perror("fcntl(F_GETFL)");
        return -1;
    }
    opts = (opts | O_NONBLOCK);
    if (fcntl(sock,F_SETFL,opts) < 0) {
        perror("fcntl(F_SETFL)");
        return -1;
    }

    return 0;
}

/** печатает адрес в консоль */
void printf_address(const struct sockaddr_in* sa) {
    char buff[INET_ADDRSTRLEN+1] = "\0";
    const char* p = inet_ntop(AF_INET, &sa->sin_addr, buff, sizeof(buff));
    if ( !p ) {
        perror("printf_address()");
        return;
    }
    in_port_t port = ntohs(sa->sin_port);
    fprintf(stderr, "%s:%d\n", p, port);
}

/** инициализирует серверный сокет */
int initserver(int type, const struct sockaddr* sa, socklen_t alen, int qlen) {
    int sock_fd = 0;
    int value = 1;

    if ( (sock_fd=socket(sa->sa_family, type, 0)) < 0 ) {
        return -1;
    }

    if ( setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value)) < 0 ) {
        close(sock_fd);
        return -1;
    }

    if ( setsockopt(sock_fd, SOL_SOCKET, SO_KEEPALIVE, &value, sizeof(value)) < 0 ) {
        close(sock_fd);
        return -1;
    }

    if ( setnonblocking(sock_fd) < 0 ) {
        close(sock_fd);
        return -1;
    }

    if ( bind(sock_fd, sa, alen) < 0 ) {
        close(sock_fd);
        return -1;
    }

    if ( type == SOCK_STREAM || type == SOCK_SEQPACKET ) {
        if ( listen(sock_fd, qlen) < 0 ) {
            close(sock_fd);
            return -1;
        }
    }

    return sock_fd;
}

/***************************************************************************/

int main() {
    /** дескрипторы */
    int count_fds = 0;
    int base_socket_fd = 0;
    int epoll_fd = 0;

    /** адреса */
    struct sockaddr_in my_addr;
    struct sockaddr_in their_addr;
    socklen_t addrlen = sizeof(sockaddr_in);
    ZEROPOD(my_addr);
    ZEROPOD(their_addr);

    /** epoll */
    struct epoll_event epoll_event;
    struct epoll_event static_events[epsize];
    struct epoll_event* events = &static_events[0];
    ZEROPOD(epoll_event);
    ZEROPOD(static_events);

    /** инициализирую адреса */
    my_addr.sin_family = AF_INET;
    my_addr.sin_port = htons(MYPORT);
    my_addr.sin_addr.s_addr = INADDR_ANY;

    /** создаю слушающий сокет */
    base_socket_fd=initserver(SOCK_STREAM, (struct sockaddr*)&my_addr, addrlen, qsize);
    if ( base_socket_fd < 0) {
        perror("initserver()");
        return EXIT_FAILURE;
    }

    /** создаю epoll с начальным размером очереди epsize */
    epoll_fd = epoll_create(epsize);
    if (epoll_fd == -1) {
         perror("epoll_create()");
         return EXIT_FAILURE;
    }

    /** создается елемент события для слушающего сокета */
    epoll_event.events = EPOLLIN|EPOLLRDHUP|EPOLLET;
    epoll_event.data.fd = base_socket_fd;
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, base_socket_fd, &epoll_event) == -1) {
         perror("epoll_ctl()");
         return EXIT_FAILURE;
    }

    while ( 1 ) {
        /** ожидаю события на дескрипторе epoll */
        count_fds = epoll_wait(epoll_fd, events, epsize, -1);
        if (count_fds == -1) {
            perror("epoll_wait()");
            return EXIT_FAILURE;
        }
        /** цикл повторяется "count_fds" раз */
        for ( int n = 0; n < count_fds; ++n) {
            /* если (events[n].data.fd == base_socket_fd) истинно -
             * значит запрос от клиента на подключение
             */
            if ( events[n].data.fd == base_socket_fd ) {
                addrlen = sizeof(struct sockaddr_in);
                /** принимаем запрос на подключение */
                int new_socket_fd = accept(base_socket_fd, (struct sockaddr*)&my_addr, &addrlen);
                printf_address(&my_addr);
                if (new_socket_fd == -1) {
                     perror("accept()");
                     return EXIT_FAILURE;
                }
                if ( setnonblocking(new_socket_fd) < 0 ) {
                    perror("setnonblocking()");
                    close(new_socket_fd);
                    continue;
                }
                /** указываем какие события на сокете мониторить */
                epoll_event.events = EPOLLIN|EPOLLET;
                epoll_event.data.fd = new_socket_fd;
                /** добавляем в epoll */
                if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, new_socket_fd, &epoll_event) == -1) {
                     perror("epoll_ctl()");
                     return EXIT_FAILURE;
                }
            } else {
                int32_t sock_fd = events[n].data.fd;
                uint32_t event = events[n].events;
                /* этот дескриптор уже есть в epoll. */
                if ( event & EPOLLIN ) {
                    /* сокет готов для чтения */
                    printf("descriptor %d ready for read\n", sock_fd);
                    char buff[32] = "\0";
                    int rd = read(sock_fd, &buff, sizeof(buff));
                    fprintf(stderr, "%s\n", buff);
                }
                if ( event & EPOLLOUT ) {
                    /* сокет готов для записи */
                    printf("descriptor %d ready for write\n", sock_fd);
                }
                if ( event & EPOLLRDHUP ) {
                    /* как я понял из документации(http://linux.die.net/man/2/epoll_ctl)
                     * это событие закрытия удаленного сокета. так?
                     */
                    printf("descriptor %d disconnect\n", sock_fd);
                }
                if ( event & EPOLLPRI ) {
                    /* срочные данные готовы для считывания. так? */
                    printf("descriptor %d ready for read urgent data\n", sock_fd);
                }
                if ( event & EPOLLERR ) {
                    /* какая-то ошибка произошла на сокете. так?
                     * какие ошибки могут происходить? и почему?
                     */
                    printf("on descriptor %d error condition\n", sock_fd);
                }
                if ( event & EPOLLHUP ) {
                    /* какое-то событие произошло на сокете. так? */
                    printf("on descriptor %d ???\n", sock_fd);
                }
                if ( event & EPOLLET ) {
                    /* как я понял, эта константа используется для установки
                     * какого-то правила наблюдения за сокетом. и не понимаю,
                     * о чем это сыбытие может говорить(если оно происходит).
                     */
                    printf("event & EPOLLET\n");
                }
                if ( event & EPOLLONESHOT ) {
                    /* по моему, так же как и в предыдущем блоке, это не событие.
                     * но суть константы понятна.
                     */
                    printf("event & EPOLLONESHOT\n");
                }
                close(sock_fd);
            }
        }
    }

    close(epoll_fd);
    close(base_socket_fd);

    return 0;
}



Содержание

Сообщения в этом обсуждении
"Первый многопользовательский серевер с использованием epoll(..."
Отправлено BigHO , 13-Дек-09 16:12 
>    if ( event & EPOLLIN ) {
>     /* сокет готов для чтения */
>     printf("descriptor %d ready for read\n", sock_fd);
>     char buff[32] = "\0";

тут не надо инициализации буфера как "\0" - все равно перезапишется.

>     int rd = read(sock_fd, &buff, sizeof(buff));

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

тобишь будет строка как:
int rd = read(sock_fd, buff, sizeof(buff));

>     fprintf(stderr, "%s\n", buff);
>    }
>    if ( event & EPOLLOUT ) {
>     /* сокет готов для записи */
>     printf("descriptor %d ready for write\n", sock_fd);

вот это лишнее - контроль выходного потока нужно ставить если данных в ответе накопилось больше, чем на одну операцию write. Некоторые предпочитают дождаться EBUSY ошибки, прежде чем подписываться на информирование об этом сигнале.

>    }

<кусь> - ибо неважно

>    if ( event & EPOLLERR ) {
>     /* какая-то ошибка произошла на сокете. так?
>
>      * какие ошибки могут происходить? и
>почему?
>      */

наиболее суперская ошибка - "ошибочная ошибка - никаких ошибок нет", или "превышено число допустимых ошибок", можно поймать в винде - хто его знает какие еще хитроумные сплетения возможны. Но наиболее часто имеется в виду ошибка по потоку, например, если нечего больше мониторить - поток закрылся, разорвалось соединение (характено для SOCK_STREAM - TCP). Или какая-то часть кода не хочет мира во всем мире и злономерено закрыла исходный код дескриптор, посчитав, что номер 666 будет смотреться лучше, чем 13 (close, dup, dup2).

Это же относится к EPOLLHUP и прочих непонятностей - не хочется лезть в хэндбук.