举例:单向连接(客户服务端模式的常见问题)
服务进程可以使用标准的bind,listen和accept函数来管理unix域到客户进程的单一连接。客户进程使用connect来和服务进程进行连接;在服务进程接收到了connect请求之后,在客户和服务进程之间就存在了一条单一的连接。这样的操作和前面我们在因特网套接字中的两个例子类似。
unix域套接字的serv_listen函数
下面给出一个使用unix域套接字的serv_listen函数:
#include "apue.h"
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#define QLEN 10
/*
* Create a server endpoint of a connection.
* Returns fd if all OK, <0 on error.
*/
int serv_listen(const char *name)
{
int fd, len, err, rval;
struct sockaddr_un un;
/* create a UNIX domain stream socket */
if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
return(-1);
unlink(name); /* in case it already exists */
/* fill in socket address structure */
memset(&un, 0, sizeof(un));
un.sun_family = AF_UNIX;
strcpy(un.sun_path, name);
len = offsetof(struct sockaddr_un, sun_path) + strlen(name);
/* bind the name to the descriptor */
if (bind(fd, (struct sockaddr *)&un, len) < 0) {
rval = -2;
goto errout;
}
if (listen(fd, QLEN) < 0) { /* tell kernel we're a server */
rval = -3;
goto errout;
}
return(fd);
errout:
err = errno;
close(fd);
errno = err;
return(rval);
}
首先,我们调用socket创建一个unix域的套接字。
然后我们用一个已知的名字填充sockaddr_un结构(这个结构做为bind的参数)以便绑定给套接字。注意在一些平台上面我们不需要设定sun_len成员,因为操作系统通过传给bind函数的地址长度会为我们设置这个成员。
最后,我们调用listen函数来告诉内核,进程将作为一个服务进程,等待来自客户进程的连接。
unix域套接字的serv_accept函数
当客户的连接请求到达的时候,服务进程再调用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, len, err, rval;
time_t staletime;
struct sockaddr_un un;
struct stat statbuf;
len = sizeof(un);
if ((clifd = accept(listenfd, (struct sockaddr *)&un, &len)) < 0)
return(-1); /* 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 */
un.sun_path[len] = 0; /* null terminate */
if (stat(un.sun_path, &statbuf) < 0) {
rval = -2;
goto errout;
}
#ifdef S_ISSOCK /* not defined for SVR4 */
if (S_ISSOCK(statbuf.st_mode) == 0) {
rval = -3; /* not a socket */
goto errout;
}
#endif
if ((statbuf.st_mode & (S_IRWXG | S_IRWXO)) ||
(statbuf.st_mode & S_IRWXU) != S_IRWXU) {
rval = -4; /* is not rwx------ */
goto errout;
}
staletime = time(NULL) - STALE;
if (statbuf.st_atime < staletime ||
statbuf.st_ctime < staletime ||
statbuf.st_mtime < staletime) {
rval = -5; /* i-node is too old */
goto errout;
}
if (uidptr != NULL)
*uidptr = statbuf.st_uid; /* return uid of caller */
unlink(un.sun_path); /* we're done with pathname now */
return(clifd);
errout:
err = errno;
close(clifd);
errno = err;
return(rval);
}
服务进程阻塞在accept调用上面,等待客户进程调用cli_conn。当accept返回的时候,它的返回值是连接到客户进程的一个新的文件描述符号(这个和connld模块对STREAMS子系统所做的一样)。另外,客户端赋值到它的套接字上面的路径名称(这个名称包含客户进程的进程ID)也会在accept中,通过第二个参数(指向sockaddr_un结构的指针)被返回(参见下面的cli_conn)。我们给这个路径名称赋值一个null结束符号,然后调用stat。这样我们检测路径名称确实是一个套接字并且权限只允许用户读,写,执行。我们也会检查和套接字相关的时间不会超过30秒。
如果所有这三项检测成功,我们假定客户进程的标识(它的有效用户ID)就是套接字的属主。尽管检测不是很完美,但是这也是我们在目前的系统上面可以做的最好的了(若内核返回有效用户ID给accept,就像ioctl的I_RECVFD命令那样,这会更好)。
网友评论