Чтение сокета UNIX происходит, даже если клиент не отправляет данные

Я столкнулся с очень странной проблемой: у меня есть простой код сервера/клиента сокетов UNIX STREAM, работающий в Linux. клиент иногда отправляет сообщение на сервер (я также тестировал отправку только один раз), однако после получения первого сообщения сервер продолжает печатать одно и то же сообщение, даже если клиент не отправил ни одного сообщения.

Все сообщение создается заново, в нем нет статических данных и т. д., что может привести к сохранению одного и того же сообщения при нескольких вызовах.

Код на стороне клиента:

int g_fd = -1;
#define SERVER_SOCK   "/tmp/server_sock"

int init_fd(void) {
    g_fd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (g_fd < 0) {
        log_info("alloc", "socket() failed with error (%d:%s)", errno, strerror(errno));
        return -1;
    }

    struct sockaddr_un sa;
    memset(&sa, 0, sizeof(sa));
    sa.sun_family = AF_UNIX;
    snprintf(sa.sun_path, sizeof(sa.sun_path), SERVER_SOCK);

    if (connect(g_fd, (struct sockaddr *) &sa, strlen(sa.sun_path) + sizeof(sa.sun_family)) < 0) {
        log_info("alloc", "connect() failed with error (%d:%s)", errno, strerror(errno));
        return -1;
    }
    int flags = fcntl(g_fd, F_GETFL, 0);
    fcntl(g_fd, F_SETFL, flags | O_NONBLOCK);
    return 0;
}

void send_event(void) {
    if (g_fd < 0) {
        if (init_fd() < 0) {
            log_info("alloc", "failed to connect to server");
            return;
        }
    }
    json_t *jtc = json_object();
    json_object_set_new(jtc, "msgType", json_integer(650));
    json_t *jtype = json_object();
    json_object_set_new(jtype, "type", json_string("MESSAGE_CHANGE"));
    json_object_set_new(jtc, "data", jtype);
    char *j_dump_string = NULL;
    j_dump_string = json_dumps(jtc, JSON_PRESERVE_ORDER);
    if (write_a_msg(g_fd, (uint8_t*)j_dump_string, strlen(j_dump_string)+1) == -1) {
        close(g_fd);
        g_fd = -1;
        log_info("alloc", "failed to send message to server");
    }
    log_info("alloc", "GNA: da_send: %s", j_dump_string);
    free(j_dump_string);
    json_decref(jtc);
}

int write_a_msg(int fd, const uint8_t *ptr, size_t nbytes) {
    uint8_t *write_buf = malloc(nbytes + MSG_LEN_SIZE);
    if (!write_buf)
        return -1;
    write_buf[0] = (nbytes >> 24);
    write_buf[1] = (nbytes >> 16);
    write_buf[2] = (nbytes >> 8);
    write_buf[3] = (nbytes);
    memcpy(write_buf + MSG_LEN_SIZE, ptr, nbytes);
    if (write_loop(fd, write_buf, nbytes + MSG_LEN_SIZE) < 0) {
        int save_err = errno;
        free(write_buf);
        errno = save_err;
        return -1;
    }
    free(write_buf);
    return 0;
}

static int write_loop(int fd, const uint8_t *ptr, size_t nbytes) {
  ssize_t nleft, nwritten;
  nleft = nbytes;
  while (nleft) {
        nwritten = write(fd, ptr, nleft);
        if (nwritten <= 0) {
            if (errno != EAGAIN) return -1;
            continue;
        }
        nleft -= nwritten;
        ptr += nwritten;
    }
    return 0;
}

Клиентская часть использует libev для обработки сокетов, серверная использует select:

static int server_init(void) {
    struct sockaddr_un sa;
    memset(&sa, 0, sizeof(sa));
    int fd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (fd < 0) {
        DebugLog(ERROR, "Could not create socket - error (%d:%s)", errno, strerror(errno));
        return -1;
    }
    unlink(SERVER_SOCK);
    sa.sun_family = AF_UNIX;
    strcpy(sa.sun_path, SERVER_SOCK);
    if (bind(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0) {
        DebugLog(ERROR, "Bind failed with error (%d:%s)\n", errno, strerror(errno));
        close(fd);
        return -1;
    }
    if (listen(fd, 5)) {
        DebugLog(ERROR, "Listen failed with error (%d:%s)\n", errno, strerror(errno));
        close(fd);
        return -1;
    }
    DebugLog(INFO, "GNA: Create server ready to accept\n");
    ev_add_fd(fd, EV_READ, connection_callback, NULL);
    return RET_OK;
}

static void connection_callback(int fd, int flags, void *data) {
    struct sockaddr_un remote;
    socklen_t rlen = sizeof(struct sockaddr_un);

    int connection_fd = accept(fd, (struct sockaddr *) &remote, &rlen);
    if (connection_fd < 0) {
        DebugLog(ERROR, "%s:Accept failed", __func__);
        return;
    }
    DebugLog(INFO, "GNA: received new connection\n");
    ev_add_fd(connection_fd, EV_READ, request_callback, NULL);
}

static void request_callback(int fd, int flags, void *data) {
    char *msg = NULL;
    size_t msglen = 0;
    if (read_a_msg(fd, (uint8_t **) & msg, &msglen) < 0) {
        DebugLog(ERROR, "%s:read failed (pid:%d). error %d:%s", __func__, getpid(), errno,
                 strerror(errno));
        ev_del_fd(fd);
        close(fd);              // close the FD we'll reopen a new one next time
        if (msg)
            free(msg);
        return;
    }
    msg[msglen] = '\0';         // ensure NULL termination
    handle_msg(msg, msglen, fd);
    if (msg)
        free(msg);
    return;
}

static void handle_msg(char *msg, int msglen, int fd) {
    DebugLog(INFO, "%s: GNA: Received msg: %s", __func__, msg);
}

int read_a_msg(int fd, uint8_t **ptr, size_t *nbytes) {
    uint8_t hd[4];
    if (read_loop(fd, hd, 4) < 0)
        return -1;
    size_t payload_len = (hd[0]<<24)|(hd[1]<<16)|(hd[2]<<8)|(hd[3]);
    (*ptr) = malloc(payload_len + 1); // allocate one extra byte in case the user is reading strings and wants to add a NULL in the end. the length wont include this, its essential padding for convenience.
    *nbytes = payload_len;
    if (read_loop(fd, *ptr, *nbytes) < 0)
        return -1;
    return 0;
}

static int read_loop(int fd, uint8_t *ptr, size_t nbytes) {
    ssize_t nleft, nread;
    nleft = nbytes;
    while (nleft) {
        nread = read(fd, ptr, nleft);
        if (nread < 0) {
            if (errno != EAGAIN) return -1;
            continue;
        }
        if (nread == 0)
            break;
        nleft -= nread;
        ptr += nread;
    }
    if (nleft != 0) return -1;
    return 0;
}

Этот журнал печатается каждые несколько секунд, хотя никто ничего не отправляет.

DebugLog(INFO, "%s: GNA: Received msg: %s", __func__, msg);

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

log_info("alloc", "GNA: da_send: %s", j_dump_string);

Может ли кто-нибудь подсказать, почему это происходит. и как избежать?

Обновление 1: После предложений по strace на клиенте и сервере я обнаружил, что сервер устанавливает fd в select и периодически получает событие чтения чтения.

08:19:26.031031 _newselect(16, [4 5 6 7 8 9 10 11 15], [], NULL, {tv_sec=16, tv_usec=1049544}) = 1 (in [11], left {tv_sec=17, tv_usec=44972})
08:19:26.035737 read(11, "\0\0\0005", 4) = 4
08:19:26.035814 read(11, "{\"msgType\": 650, \"data\": {\"type\""..., 53) = 53
08:19:38.982049 _newselect(16, [4 5 6 7 8 9 10 11 15], [], NULL, {tv_sec=3, tv_usec=1000564}) = 1 (in [11], left {tv_sec=3, tv_usec=946001})
08:19:39.036745 read(11, "\0\0\0005", 4) = 4
08:19:39.036816 read(11, "{\"msgType\": 650, \"data\": {\"type\""..., 53) = 53
08:19:50.117012 _newselect(18, [4 5 6 7 8 9 10 11 15 16 17], [], NULL, {tv_sec=4, tv_usec=1276752}) = 1 (in [17], left {tv_sec=5, tv_usec=276586})
08:19:50.117251 read(17, "\0\0\0005", 4) = 4
08:19:50.117308 read(17, "{\"msgType\": 650, \"data\": {\"type\""..., 53) = 53
08:19:51.910855 _newselect(16, [4 5 6 7 8 9 10 11 15], [], NULL, {tv_sec=2, tv_usec=1070595}) = 1 (in [11], left {tv_sec=2, tv_usec=943831})
08:19:52.037758 read(11, "\0\0\0005", 4) = 4
08:19:52.037841 read(11, "{\"msgType\": 650, \"data\": {\"type\""..., 53) = 53
08:20:05.031834 _newselect(16, [4 5 6 7 8 9 10 11 15], [], NULL, {tv_sec=0, tv_usec=1000335}) = 1 (in [11], left {tv_sec=0, tv_usec=993545})
08:20:05.038758 read(11, "\0\0\0005", 4) = 4
08:20:05.038838 read(11, "{\"msgType\": 650, \"data\": {\"type\""..., 53) = 53

Я убил клиента и вначале подключил strace, чтобы он мог получить fd сокета, клиент ничего не записывает на fd 14, за исключением одного раза:

08:10:02.362615 socket(AF_UNIX, SOCK_STREAM, 0) = 14
08:10:02.363091 connect(14, {sa_family=AF_UNIX, sun_path = "/tmp/server_sock"}, 21) = 0
08:10:02.363251 fcntl64(14, F_GETFL)    = 0x2 (flags O_RDWR)
08:10:02.363297 fcntl64(14, F_SETFL, O_RDWR|O_NONBLOCK) = 0
08:10:02.363837 write(14, "\0\0\0005{\"msgType\": 650, \"data\": {\"t"..., 57) = 57

Это единственная запись для fd 14.

Обновление 2: Вышеуказанная функция send_event компилируется как часть библиотеки libabc.so. Поместив getpid() и gettimeofday() в свое сообщение, я обнаружил, что другой демон использует эту библиотеку и вызывает функцию-оболочку, которая вызывает функцию send_event, поэтому я вижу, что сервер периодически получает сообщение, хотя клиент этого не делает. отправка любого сообщения. Сейчас я создаю файл .pid, в котором хранится pid клиента, а затем в send_event я сравниваю текущий pid процесса с pid клиента.

Пробовали ли вы отлаживать серверную программу? Например, использовать отладчик для пошагового выполнения кода, отслеживая его переменные и их значения? Как насчет клиента, вы пытались его отладить? Возможно, он на самом деле отправляет много сообщений? Или, возможно, у вас работает несколько клиентов?

Some programmer dude 29.09.2023 12:58

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

Some programmer dude 29.09.2023 13:00

и клиент, и сервер являются частью пакета buildroot, в этом пакете у меня нет GDB.

RootPhoenix 29.09.2023 13:01

Затем добавьте больше журналов. Гораздо больше журналов. Особенно в клиенте.

Some programmer dude 29.09.2023 13:02

В клиенте есть только одна функция для отправки сообщения «send_change_event», где присутствует журнал, который печатается ровно один раз, когда я отправляю его ровно один раз. Я также удалил клиентский пакет, чтобы убедиться, что ни один другой клиент не отправляет сообщения, кроме моего пакета.

RootPhoenix 29.09.2023 13:43

@RootPhoenix 1) являются ли handle_mgs и handle_daemon_mgs псевдонимами одной и той же функции? 2) И ваш клиент, и ваш сервер закрывают соединение в случае EAGAIN, это выглядит странно. 3) вы блокируете сигнал SIG_HUP? Может ли быть так, что клиент выходит из строя после SIG_HUP (или SIG_PIPE), и ваша система перезагружает его, поэтому сервер получает сообщение снова и снова?

Misha T 29.09.2023 14:28

@MishaT Я исправил опечатку, они одинаковые, клиент и сервер не выходят из строя, поэтому перезагрузки не происходит. API-интерфейсы чтения и записи, доступные в какой-то библиотеке, также используются где-то еще. SIGHUP не блокируется, но его никто не отправляет.

RootPhoenix 29.09.2023 14:40

Запустите клиент и сервер с помощью strace. Из вывода strace вы можете увидеть, что отправляет клиент и что получает сервер.

pts 29.09.2023 15:19

@pts согласно предложению, запустил strace на клиенте и сервере, обновил результаты, очень странно, клиент пишет только один раз, но сервер newselect получает данные несколько раз.

RootPhoenix 30.09.2023 10:29

@pts Я вижу, что значение времени ожидания для newselect уменьшается при каждом следующем вызове.

RootPhoenix 30.09.2023 10:33

Работает ли клиентский процесс в фоновом режиме? Запустите lsof на сокет или ps -ef и внимательно изучите вывод. Или просто перезагрузитесь и посмотрите, исчезнет ли проблема.

Andrew Henle 30.09.2023 11:54

Такое поведение, обнаруживаемое strace, является странным. Для дальнейшей отладки добавьте выходные данные getpid() и gettimeofday() в тело сообщения в клиенте, а затем запустите strace -s99999 ./server, чтобы мы могли увидеть всю строку, прочитанную read(), в выводе strace. В общем, для потоковых сокетов общее количество байтов, записанных функциями write(), в конечном итоге должно быть таким же, как общее количество байтов, прочитанных функциями read(). Здесь много недостающих функций write().

pts 30.09.2023 12:43

Интересно, что сервер читает новое сообщение только через несколько секунд. Что происходит между ними? Я могу только представить, что другой клиент работает и отправляет сообщения в цикле. Чтобы исключить это, добавьте getpid() и gettimeofday(), как указано выше, а также переименуйте имя файла сокета как в client.c, так и в server.c. Это сломает старых клиентов.

pts 30.09.2023 12:49

@pts Добавлены getpid() и gettimeofday(), и это, по крайней мере, выявило проблему, в которой она заключается, я обновил свой вопрос с результатами. Спасибо за ваше драгоценное время и предложения.

RootPhoenix 02.10.2023 12:34

Используйте SCM_CREDENTIALS на сервере, чтобы получить PID, UID и GID клиента. Это работает для сокетов Unix (AF_UNIX), но может быть, а может и не быть лучшим решением для вашего случая использования.

pts 02.10.2023 19:00
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
15
125
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вышеупомянутая функция send_event компилируется как часть библиотеки libabc.so. Поместив getpid() и gettimeofday() в свое сообщение, я обнаружил, что другой демон использовал эту библиотеку и вызывал функцию-оболочку, которая вызывает функцию send_event, поэтому я вижу, что сервер периодически получает сообщение, хотя клиент не отправляет никаких сообщений. Сейчас я создаю файл .pid, в котором хранится pid клиента, а затем в send_event я сравниваю текущий pid процесса с pid клиента.

Спасибо @pts за предложения.

Другие вопросы по теме