C Don';我不理解为什么来自APUE的代码片段会取消连接到客户端unix域套接字的文件的链接

C Don';我不理解为什么来自APUE的代码片段会取消连接到客户端unix域套接字的文件的链接,c,unix-socket,unlink,C,Unix Socket,Unlink,本书定义了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 ch

本书定义了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\u 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);
}
#包括“apue.h”
#包括
#包括
#包括
#包括
#define STALE 30/*客户端名称不能早于此(秒)*/
/*
*等待客户端连接到达,然后接受它。
*我们还从路径名获取客户端的用户ID
*在召唤我们之前,它必须绑定。
*如果一切正常,则返回新fd,
为什么服务器代码
取消链接(名称)
附加到客户端套接字的文件

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

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

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

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

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


当然,给出的代码并不完美。有一些执行路径会导致某些名称未被取消链接(例如,如果服务器进程在错误时间崩溃)。但这些应该是罕见的。经验表明,客户端崩溃的频率远远高于服务器。

我刚刚意识到unix套接字域可以命名,也可以不命名。服务器unix域套接字需要命名,因为客户端需要一种方法来知道您在哪里。Internet套接字通过端口号来实现这一点。看

然后我可以理解
serv\u accept
uname(name)
的行为


首先,检查客户端连接功能
cli\u conn
code:

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

#define CLI_PATH    "/var/tmp/"
#define CLI_PERM    S_IRWXU         /* rwx for user only */

/*
 * Create a client endpoint and connect to a server.
 * Returns fd if all OK, <0 on error.
 */
int
cli_conn(const char *name)
{
    int                 fd, len, err, rval;
    struct sockaddr_un  un, sun;
    int                 do_unlink = 0;

    if (strlen(name) >= sizeof(un.sun_path)) {
        errno = ENAMETOOLONG;
        return(-1);
    }

    /* create a UNIX domain stream socket */
    if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
        return(-1);

    /* fill socket address structure with our address */
    memset(&un, 0, sizeof(un));
    un.sun_family = AF_UNIX;
    sprintf(un.sun_path, "%s%05ld", CLI_PATH, (long)getpid());
printf("file is %s\n", un.sun_path);
    len = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);

    unlink(un.sun_path);        /* in case it already exists */
    if (bind(fd, (struct sockaddr *)&un, len) < 0) {
        rval = -2;
        goto errout;
    }
    if (chmod(un.sun_path, CLI_PERM) < 0) {
        rval = -3;
        do_unlink = 1;
        goto errout;
    }

    /* fill socket address structure with server's address */
    memset(&sun, 0, sizeof(sun));
    sun.sun_family = AF_UNIX;
    strcpy(sun.sun_path, name);
    len = offsetof(struct sockaddr_un, sun_path) + strlen(name);
    if (connect(fd, (struct sockaddr *)&sun, len) < 0) {
        rval = -4;
        do_unlink = 1;
        goto errout;
    }
    return(fd);

errout:
    err = errno;
    close(fd);
    if (do_unlink)
        unlink(un.sun_path);
    errno = err;
    return(rval);
}
#包括“apue.h”
#包括
#包括
#包括
#定义CLI\u路径“/var/tmp/”
#仅为用户定义CLI_PERM S_IRWXU/*rwx*/
/*
*创建客户端端点并连接到服务器。
*如果一切正常,则返回fd,=sizeof(un.sun\u路径)){
errno=搪瓷;
返回(-1);
}
/*创建UNIX域流套接字*/
if((fd=socket(AF\u UNIX,SOCK\u STREAM,0))<0)
返回(-1);
/*用我们的地址填充套接字地址结构*/
memset(&un,0,sizeof(un));
un.sun_family=AF_UNIX;
sprintf(un.sun_路径,“%s%05ld”,CLI_路径,(长)getpid();
printf(“文件是%s\n”,un.sun\u路径);
len=偏移量(结构sockaddr\u un,sun\u path)+strlen(un.sun\u path);
取消链接(un.sun_路径);/*以防它已经存在*/
if(绑定(fd,(结构sockaddr*)&un,len)<0){
rval=-2;
后藤埃罗特;
}
如果(chmod(un.sun\u路径,CLI\u PERM)<0){
rval=-3;
do_unlink=1;
后藤埃罗特;
}
/*用服务器地址填充套接字地址结构*/
memset(&sun,0,sizeof(sun));
sun.sun_family=AF_UNIX;
strcpy(sun.sun\u路径,名称);
len=偏移量(结构sockaddr\u un,太阳路径)+strlen(名称);
if(连接(fd,(结构sockaddr*)和sun,len)<0){
rval=-4;
do_unlink=1;
后藤埃罗特;
}
返回(fd);
错误:
err=errno;
关闭(fd);
如果(不要取消链接)
取消链接(联合国新路);
errno=err;
返回(rval);
}
书中写道:

我们调用套接字来创建UNIX域套接字的客户端。我们 然后用特定于客户端的名称填写
sockaddr\u un
结构。 我们不允许系统为我们选择默认地址,因为服务器将无法区分一个客户端和另一个客户端(如果我们 不要显式地将名称绑定到UNIX域套接字(内核) 代表我们隐式地将地址绑定到它,并且不会创建任何文件 在文件系统中表示套接字)。相反,我们绑定 自有地址-开发客户时通常不采取的步骤 使用套接字的程序

因此,我认为这一切都是为了使服务器能够重新格式化一组类似的客户端

例如:

使用客户端附加文件路径
/tmp/cat
->客户端连接到服务器->服务器检查文件路径并知道它是“cat”客户端->服务器释放该文件路径的占用->可以创建另一个“cat”客户端

通过这种方式,服务器可以例如区分具有不同底层文件路径
/tmp/cat
/tmp/dog
的“猫”客户端和“狗”客户端