本书定义了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秒。如果所有这些检查都可以,我们假设客户的身份(其有效的用户ID)是套接字的所有者。
为什么服务器代码unlink(name)
附加到客户端套接字的文件?
其他2个功能代码通过链接提供:
为什么服务器代码
unlink(name)
附加到客户端套接字的文件?
更准确地说,服务器正在删除附加到客户端套接字的filepath。或更通俗地说,是客户端的套接字名称。
回想一下unlink()
不会删除某些进程中当前打开的命名对象;客户端的套接字可能仍在客户端中打开,因此unlink(name)
尚未删除该套接字。相反,它确保套接字在运行中的进程不再使用时将被删除。
它立即执行的操作是释放名称,以便可以使用其他套接字重用该名称。
那为什么呢?通常是为了使文件系统不会充满僵尸套接字名称。这无助于当前客户端重用该名称(例如,连接到其他服务),因为无论如何,客户端都会在尝试使用该名称之前取消链接。但是对于具有不同uid(恰好被分配了相同pid)的另一个将来的客户端进程,僵尸名称可能是一个问题。将来的过程可能没有足够的权限来取消名称的链接,在这种情况下,它最终将无法使用此IPC机制(至少对于此库而言)。
好吧,为什么它不链接由服务器?服务器使用stat
调用的文件路径,而客户端无法知道何时发生。由于基本上最好尽快取消链接名称,因此在这种情况下,服务器最好取消链接名称。它知道何时不再需要该名称。
当然,所提供的代码并不完美。有执行路径将导致某些名称未取消链接(例如,如果服务器进程在错误的时间崩溃)。但是这些应该很少见。经验表明,客户端崩溃多于服务器。