#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 будет надежно выполняться. Поэтому я думаю, мы можем сделать вывод, что этот код не должен протекать.