Почему клиент не закрывает сокет? - PullRequest
0 голосов
/ 05 января 2019

Я читаю пример клиента сокета из APUE в https://github.com/hayatoito/apue-2e/blob/master/sockets/ruptime.c. Я не нахожу его закрытым или выключить его сокет. В целом верно ли, что клиенту не нужно закрывать свой дескриптор файла сокета? Когда клиент должен закрывать свой дескриптор файла сокета, а когда нет?

Для сравнения, его сервер закрывает сокет в конце: https://github.com/hayatoito/apue-2e/blob/master/sockets/ruptimed.c

Клиент:

#include "apue.h"
#include <netdb.h>
#include <errno.h>
#include <sys/socket.h>

#define MAXADDRLEN  256
#define BUFLEN      128

extern int connect_retry(int, const struct sockaddr *, socklen_t);

void
print_uptime(int sockfd)
{
    int n;
    char buf[BUFLEN];

    while ((n = recv(sockfd, buf, BUFLEN, 0)) > 0)
        write(STDOUT_FILENO, buf, n);
    if (n < 0)
        err_sys("recv error");
}

int
main(int argc, char *argv[])
{
    struct addrinfo *ailist, *aip;
    struct addrinfo hint;
    int sockfd, err;

    if (argc != 2)
        err_quit("usage: ruptime hostname");
    hint.ai_flags = 0;
    hint.ai_family = 0;
    hint.ai_socktype = SOCK_STREAM;
    hint.ai_protocol = 0;
    hint.ai_addrlen = 0;
    hint.ai_canonname = NULL;
    hint.ai_addr = NULL;
    hint.ai_next = NULL;
    if ((err = getaddrinfo(argv[1], "ruptime", &hint, &ailist)) != 0)
        err_quit("getaddrinfo error: %s", gai_strerror(err));
    for (aip = ailist; aip != NULL; aip = aip->ai_next) {
        if ((sockfd = socket(aip->ai_family, SOCK_STREAM, 0)) < 0)
            err = errno;
        if (connect_retry(sockfd, aip->ai_addr, aip->ai_addrlen) < 0) {
            err = errno;
        } else {
            print_uptime(sockfd);
            exit(0);
        }
    }
    fprintf(stderr, "can't connect to %s: %s\n", argv[1], strerror(err));
    exit(1);
}

Сервер:

#include "apue.h"
#include <netdb.h>
#include <errno.h>
#include <syslog.h>
#include <sys/socket.h>

#define BUFLEN  128
#define QLEN 10

#ifndef HOST_NAME_MAX
#define HOST_NAME_MAX 256
#endif

extern int initserver(int, struct sockaddr *, socklen_t, int);

void
serve(int sockfd)
{
    int clfd;
    FILE *fp;
    char buf[BUFLEN];

    for (;;) {
        clfd = accept(sockfd, NULL, NULL);
        if (clfd < 0) {
            syslog(LOG_ERR, "ruptimed: accept error: %s", strerror(errno));
            exit(1);
        }
        if ((fp = popen("/usr/bin/uptime", "r")) == NULL) {
            sprintf(buf, "error: %s\n", strerror(errno));
            send(clfd, buf, strlen(buf), 0);
        } else {
            while (fgets(buf, BUFLEN, fp) != NULL)
                send(clfd, buf, strlen(buf), 0);
            pclose(fp);
        }
        close(clfd);
    }
}

int
main(int argc, char *argv[])
{
    struct addrinfo *ailist, *aip;
    struct addrinfo hint;
    int sockfd, err, n;
    char *host;

    if (argc != 1)
        err_quit("usage: ruptimed");
#ifdef _SC_HOST_NAME_MAX
    n = sysconf(_SC_HOST_NAME_MAX);
    if (n < 0)                  /* best guess */
#endif
        n = HOST_NAME_MAX;
    host = malloc(n);
    if (host == NULL)
        err_sys("malloc error");
    if (gethostname(host, n) < 0)
        err_sys("gethostname error");
    daemonize("ruptimed");
    hint.ai_flags = AI_CANONNAME;
    hint.ai_family = 0;
    hint.ai_socktype = SOCK_STREAM;
    hint.ai_protocol = 0;
    hint.ai_addrlen = 0;
    hint.ai_canonname = NULL;
    hint.ai_addr = NULL;
    hint.ai_next = NULL;
    if ((err = getaddrinfo(host, "ruptime", &hint, &ailist)) != 0) {
        syslog(LOG_ERR, "ruptimed: getaddrinfo error: %s",
               gai_strerror(err));
        exit(1);
    }
    for (aip = ailist; aip != NULL; aip = aip->ai_next) {
        if ((sockfd = initserver(SOCK_STREAM, aip->ai_addr,
                                 aip->ai_addrlen, QLEN)) >= 0) {
            serve(sockfd);
            exit(0);
        }
    }
    exit(1);
}

Ответы [ 2 ]

0 голосов
/ 05 января 2019

Всегда нужно закрывать сокет, в любом случае это утечка ресурса.

Существует много способов закрыть его, но перед закрытием убедитесь, что данные сокета пусты. Таким образом, вы должны вызвать завершение работы после закрытия.

0 голосов
/ 05 января 2019

В целом верно ли, что клиенту не нужно закрывать свой дескриптор файла сокета?

Нет, это не так.

Один из вариантов этой веры привел к появлению ряда проблем с поддержкой активности в ранних браузерах Microsoft Internet Explorer (версии 1–5), которые пришлось обойти на стороне сервера. (По сути, ОС не обеспечивала правильного полного прекращения TCP-соединения .)

Однако, если процесс собирается завершиться, не является ошибкой не закрывать все сокеты, потому что POSIX.1 (стандарт, который определяет эту функциональность и интерфейс C, используемый здесь) говорит прямо (например, exit()) что все открытые потоки закрываются при выходе из процесса. Теоретически, это аналогично динамическому распределению памяти: для процесса не обязательно free() вся динамически выделенная память при его выходе, потому что вся (не разделяемая) динамически распределенная память автоматически освобождается при выходе из процесса.

На практике гораздо надежнее явно закрыть все дескрипторы сокетов. Это особенно верно для соединений TCP, потому что завершение соединения включает в себя обмен пакетами FIN и ACK. В то время как один мог бы доверять ОС, чтобы она всегда понимала это правильно, пример MSIE показывает, что реальность гораздо менее заслуживает доверия, а тщательность делает для лучшего взаимодействия с пользователем.

Когда клиент должен закрывать свой дескриптор файла сокета, а когда нет?

На практике есть два случая:

  1. Когда соединение разорвано.

    Дескрипторы являются конечным ресурсом, и закрытие их, как только они больше не нужны, гарантирует, что ресурсы не будут потрачены впустую. На самом деле нет веских оснований для того, чтобы держать сокетное соединение открытым дольше, чем это необходимо. Некоторые вещи, такие как обход иерархии файловой системы с использованием nftw(), гораздо эффективнее, когда они могут использовать потенциально большое количество дескрипторов, поэтому заботиться о том, чтобы процесс не заканчивался из-за лени программиста, хорошая идея.

  2. При создании дочернего процесса через fork(), у него не должно быть доступа к этому сокетному соединению.

    Текущие Linux, MacOS, FreeBSD и OpenBSD по крайней мере поддерживают флаг close-on-exec (часто через fcntl(sfd, F_SETFD, FD_CLOEXEC)). В Linux вы можете создавать дескрипторы сокетов close-on-exec, используя socket(domain, type | SOCK_CLOEXEC, protocol), и пары сокетов, используя socketpair(domain, type | SOCK_CLOEXEC, protocol, sfd).

    Дескрипторы close-on-exec закрываются, когда вызов exec успешен, заменяя этот процесс на то, что еще выполняется. Таким образом, если за форком следует exec или _Exit, а все дескрипторы сокетов близки к exec, дубликаты сокетов закрываются «автоматически», и вам не нужно об этом беспокоиться.

    Обратите внимание, что если в вашем коде используется popen(), лучше, чтобы дескрипторы сокетов были закрыты при запуске, или выполняемая вами команда может иметь доступ к соединениям. Жаль, что это совершенно нестандартно на данный момент (начало 2019 года).

    Также обратите внимание, что если дочерний процесс не выполняет другой двоичный файл, но, например, отбрасывает привилегии (редко для клиента), close-on-exec ничего не сделает. Таким образом, закрытие (в дочернем процессе) ненужных дубликатов дескрипторов сокетов, явно «вручную», все еще важно для правильного разделения привилегий. Но это редко проблема с клиентскими приложениями, больше со службами и т. Д.

Другими словами, всякий раз, когда вы хотите разорвать соединение с сокетом, или когда у вас есть посторонняя копия сокета, вы close() их.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...