【问题标题】:Why does a client not close its socket?为什么客户端不关闭其套接字?
【发布时间】:2019-05-31 20:59:46
【问题描述】:

我正在阅读来自 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);
}

【问题讨论】:

  • 进程退出时关闭打开的描述符。
  • 但是,在程序中不再使用套接字后,最好始终明确地关闭它们。因此,您的示例在这方面有点草率。
  • @JamesKPolk 但是服务器关闭了它的套接字。所以我猜这可能是客户不这样做的原因。
  • 原因是代码错误。没有什么神秘的。

标签: c linux sockets


【解决方案1】:

一般来说客户端不必关闭其套接字文件描述符是真的吗?

不,这不是真的。

这种信念的一个变体导致了早期 Microsoft Internet Explorer 浏览器(版本 1 到 5)中的许多保活问题,必须在服务器端解决这些问题。 (本质上,操作系统并不能确保正确、完整的TCP connection termination。)

但是,如果进程即将退出,不关闭所有套接字并不是错误,因为 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 中,您可以使用 socket(domain, type | SOCK_CLOEXEC, protocol) 创建 close-on-exec 套接字描述符,并使用 socketpair(domain, type | SOCK_CLOEXEC, protocol, sfd) 创建套接字对。

    Close-on-exec 描述符在exec 调用成功时关闭,用正在执行的任何其他进程替换该进程。因此,如果 fork 后跟 exec 或 _Exit,并且所有套接字描述符都是 close-on-exec,则重复的套接字会“自动”关闭,您无需担心。

    请注意,如果您的代码使用popen(),您最好让您的套接字描述符在执行时关闭,否则您运行的命令可能可以访问连接。可惜目前(2019 年初)完全不标准。

    还要注意,如果子进程不执行另一个二进制文件,但例如放弃特权(对于客户端来说很少见),则 close-on-exec 不会做任何事情。因此,明确地“手动”关闭(在子进程中)不需要的套接字描述符副本对于正确的权限分离仍然很重要。但这很少是客户端应用程序的问题,更多的是服务等。

换句话说,无论何时你想终止套接字连接,或者当你有一个无关的套接字连接副本时,你close()他们。

【讨论】:

    【解决方案2】:

    总是要关闭socket,反正是资源泄露。

    有很多方法可以关闭它,但在关闭之前,请确保套接字数据为空。所以你必须在关闭它之后调用shutdown,anf。

    【讨论】:

      猜你喜欢
      • 2014-07-23
      • 1970-01-01
      • 1970-01-01
      • 2015-01-03
      • 1970-01-01
      • 2012-04-23
      • 2020-09-30
      • 1970-01-01
      • 2021-02-13
      相关资源
      最近更新 更多