首页 > 解决方案 > 不明白为什么来自 APUE 的代码片段会取消链接到客户端 unix 域套接字的文件

问题描述

本书定义了 3 个自定义函数:

int serv_listen(const char *name);
//Returns: file descriptor to listen on if OK, negative value on error

int serv_accept(int listenfd, uid_t *uidptr);
//Returns: new file descriptor if OK, negative value on error

int cli_conn(const char *name);
//Returns: file descriptor if OK, negative value on error

服务器使用该serv_accept函数(图 17.9)等待客户端的连接请求到达。当一个到达时,系统自动创建一个新的 UNIX 域套接字,将它连接到客户端的套接字,并将新的套接字返回给服务器。另外,客户端的有效用户ID存储在所 uidptr指向的内存中。


serv_accept功能代码及说明:

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

#define STALE   30  /* client's name can't be older than this (sec) */

/*
 * Wait for a client connection to arrive, and accept it.
 * We also obtain the client's user ID from the pathname
 * that it must bind before calling us.
 * Returns new fd if all OK, <0 on error
 */
int
serv_accept(int listenfd, uid_t *uidptr)
{
    int                 clifd, err, rval;
    socklen_t           len;
    time_t              staletime;
    struct sockaddr_un  un;
    struct stat         statbuf;
    char                *name;

    /* allocate enough space for longest name plus terminating null */
    if ((name = malloc(sizeof(un.sun_path + 1))) == NULL)
        return(-1);
    len = sizeof(un);
    if ((clifd = accept(listenfd, (struct sockaddr *)&un, &len)) < 0) {
        free(name);
        return(-2);     /* often errno=EINTR, if signal caught */
    }

    /* obtain the client's uid from its calling address */
    len -= offsetof(struct sockaddr_un, sun_path); /* len of pathname */
    memcpy(name, un.sun_path, len);
    name[len] = 0;          /* null terminate */
    if (stat(name, &statbuf) < 0) {
        rval = -3;
        goto errout;
    }

#ifdef  S_ISSOCK    /* not defined for SVR4 */
    if (S_ISSOCK(statbuf.st_mode) == 0) {
        rval = -4;      /* not a socket */
        goto errout;
    }
#endif

    if ((statbuf.st_mode & (S_IRWXG | S_IRWXO)) ||
        (statbuf.st_mode & S_IRWXU) != S_IRWXU) {
          rval = -5;    /* is not rwx------ */
          goto errout;
    }

    staletime = time(NULL) - STALE;
    if (statbuf.st_atime < staletime ||
        statbuf.st_ctime < staletime ||
        statbuf.st_mtime < staletime) {
          rval = -6;    /* i-node is too old */
          goto errout;
    }

    if (uidptr != NULL)
        *uidptr = statbuf.st_uid;   /* return uid of caller */
    unlink(name);       /* we're done with pathname now */
    free(name);
    return(clifd);

errout:
    err = errno;
    close(clifd);
    free(name);
    errno = err;
    return(rval);
}

...然后我们调用stat以验证路径名确实是一个套接字,并且权限只允许用户读取、用户写入和用户执行。我们还验证与套接字关联的三个时间不超过 30 秒。

如果所有这些检查都OK,我们假设客户端的身份(它的有效用户ID)是套接字的所有者。

为什么服务器编码unlink(name)附加到客户端套接字的文件?

其他 2 个功能代码通过链接提供:

https://wandbox.org/permlink/jq5BajJYLgoh4yO6

标签: cunix-socketunlink

解决方案


为什么服务器编码unlink(name)附加到客户端套接字的文件?

更准确地说,服务器正在删除附加到客户端套接字的文件路径。或者更通俗地说,是客户端的套接字名称。

回想一下,unlink()不会删除当前在某些进程中打开的命名对象;客户端的套接字可能仍在客户端中打开,因此unlink(name)尚未删除套接字。相反,它确保套接字不再被正在运行的进程使用时将被删除。

它立即做的是释放名称,以便名称可以与不同的套接字重用。

那么为什么要这样做呢?主要是为了使文件系统不会被僵尸套接字名称填满。它无助于当前客户端重用名称(例如连接到不同的服务),因为客户端无论如何都会在尝试使用名称之前取消链接名称。但是对于具有不同 uid 且恰好被分配了相同 pid 的不同未来客户端进程来说,僵尸名称可能是一个问题。未来的进程可能没有足够的权限来取消链接名称,在这种情况下,它将最终无法使用此 IPC 机制(至少与​​此库一起使用)。

好的,那为什么它被服务器取消链接?服务器使用文件路径进行stat调用,客户端无法知道何时发生。由于尽快取消链接名称基本上是一个好主意,因此在这种情况下最好让服务器取消链接名称;它知道何时不再需要该名称。

当然,呈现的代码并不完美。有一些执行路径会导致某些名称没有被取消链接(例如,如果服务器进程在错误的时间崩溃)。但这些应该很少见。经验表明,客户端比服务器更频繁地崩溃。


推荐阅读