есть ли у этого кода c утечки памяти?

#c #libevent

#c #libevent

Вопрос:

Я пытаюсь понять этот Libevent код на c , который я получил с этой страницы.
Я немного в замешательстве — правильно ли я думаю, что у этого кода могут быть утечки памяти?

Похоже ConnectionData , что указатель создается при on_connect() обратном вызове, но delete() вызывается только при неправильном чтении или после завершения записи.
Что, если соединение было accept() удалено, но не было операций чтения или записи? так этот указатель просто остается в памяти демона?

 #include <event.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <iostream>

// Read/write buffer max length
static const size_t MAX_BUF = 512;

typedef struct {
    struct event ev;
    char         buf[MAX_BUF];
    size_t       offset;
    size_t       size;
} ConnectionData;

void on_connect(int fd, short event, void *arg);
void client_read(int fd, short event, void *arg);
void client_write(int fd, short event, void *arg);

int main(int argc, char **argv)
{
    // Check arguments
    if (argc < 3) {
        std::cout << "Run with options: <ip address> <port>" << std::endl;
        return 1;
    }
    // Create server socket
    int server_sock = socket(AF_INET, SOCK_STREAM, 0);
    if (server_sock == -1) {
        std::cerr << "Failed to create socket" << std::endl;
        return 1;
    }

    sockaddr_in sa;
    int         on      = 1;
    char      * ip_addr = argv[1];
    short       port    = atoi(argv[2]);

    sa.sin_family       = AF_INET;
    sa.sin_port         = htons(port);
    sa.sin_addr.s_addr  = inet_addr(ip_addr);

    // Set option SO_REUSEADDR to reuse same host:port in a short time
    if (setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, amp;on, sizeof(on)) == -1) {
        std::cerr << "Failed to set option SO_REUSEADDR" << std::endl;
        return 1;
    }

    // Bind server socket to ip:port
    if (bind(server_sock, reinterpret_cast<const sockaddr*>(amp;sa), sizeof(sa)) == -1) {
        std::cerr << "Failed to bind server socket" << std::endl;
        return 1;
    }

    // Make server to listen
    if (listen(server_sock, 10) == -1) {
        std::cerr << "Failed to make server listen" << std::endl;
        return 1;
    }

    // Init events
    struct event evserver_sock;
    // Initialize
    event_init();
    // Set connection callback (on_connect()) to read event on server socket
    event_set(amp;evserver_sock, server_sock, EV_READ, on_connect, amp;evserver_sock);
    // Add server event without timeout
    event_add(amp;evserver_sock, NULL);

    // Dispatch events
    event_dispatch();

    return 0;
}

// Handle new connection {{{
void on_connect(int fd, short event, void *arg) 
{
    sockaddr_in client_addr;
    socklen_t   len = 0;

    // Accept incoming connection
    int sock = accept(fd, reinterpret_cast<sockaddr*>(amp;client_addr), amp;len);
    if (sock < 1) { 
        return; 
    }

    // Set read callback to client socket
    ConnectionData * data = new ConnectionData;
    event_set(amp;data->ev, sock, EV_READ, client_read, data);
    // Reschedule server event
    event_add(reinterpret_cast<struct event*>(arg), NULL);
    // Schedule client event
    event_add(amp;data->ev, NULL);
}
//}}}

// Handle client request {{{
void client_read(int fd, short event, void *arg)
{
    ConnectionData * data = reinterpret_cast<ConnectionData*>(arg);
    if (!data) {
        close(fd);
        return;
    }
    int len = read(fd, data->buf, MAX_BUF - 1);
    if (len < 1) {
        close(fd);
        delete data;
        return;
    }
    data->buf[len] = 0;
    data->size     = len;
    data->offset   = 0;
    // Set write callback to client socket
    event_set(amp;data->ev, fd, EV_WRITE, client_write, data);
    // Schedule client event
    event_add(amp;data->ev, NULL);
}
//}}}

// Handle client responce {{{
void client_write(int fd, short event, void *arg)
{
    ConnectionData * data = reinterpret_cast<ConnectionData*>(arg);
    if (!data) {
        close(fd);
        return;
    }
    // Send data to client
    int len = write(fd, data->buf   data->offset, data->size - data->offset);
    if (len < data->size - data->offset) {
        // Failed to send rest data, need to reschedule
        data->offset  = len;
        event_set(amp;data->ev, fd, EV_WRITE, client_write, data);
        // Schedule client event
        event_add(amp;data->ev, NULL);
    }
    close(fd);
    delete data;
}
//}}}
 

Комментарии:

1. да — отлично — почему все отрицательные голоса. Человек задал правильный вопрос?

2. Прочитайте текст кнопки downvote, особенно ту часть, в которой упоминается усилие

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

4. для второго мнения есть codereview.SE . Если у вас есть определенная проблема, например, утечки памяти, SO ожидает, что вы проведете свое исследование и, по крайней мере, попытаетесь найти способ проверить, есть ли у чего-то утечки памяти (и было бы удивительно, если бы с помощью Google вы не нашли способ проверить наличие утечек памяти). Если вы затем примените их и спросите здесь: «почему этот код [SSCCE ] имеет утечку памяти, как показано valgrind», более уместно.

5.Традиционно, когда сокет закрывается, read происходит и возвращается 0 , вызывая delete . В основном все select read циклы / в POSIX-подобном коде делают это.

Ответ №1:

В документации для event_set говорится, что единственными допустимыми типами событий являются EV_READ или EV_WRITE , но обратный вызов будет вызываться с EV_TIMEOUT помощью , EV_SIGNAL , EV_READ , или EV_WRITE . Документация неясна, но я ожидаю, что обратный вызов read будет вызван, когда сокет будет закрыт клиентом. Я ожидаю delete , что в ветке сбоя в client_read будет обрабатывать эту ситуацию.

Обратите внимание, что это происходит только в том случае, если клиент отправляет FIN RST пакет or. Клиент может установить соединение и оставить его открытым навсегда. По этой причине этот код следует изменить, чтобы иметь тайм-аут (возможно, через event_once ) и потребовать, чтобы клиент отправил сообщение в течение этого тайм-аута.

Комментарии:

1. Я только что спросил на канале #libevent — и они сказали, что EV_READ всегда запускает событие, когда клиент перерезает шнур после accept() . Таким образом, обратный вызов EV_READ будет надежно выполняться. Поэтому я думаю, мы можем сделать вывод, что этот код не должен протекать.