远程调用-门
一、概述
客户-服务器情形和过程调用时,存在三种不同类型的过程调用,如下所示:
- 本地过程调用:被调用的过程(函数)与调用过程处于同同一个进程中。
- 远程过程调用:被调用过程和调用过程处于不同的进程中。
RPC
允许一台主机上的每个客户调用另一台主机上的每个服务器过程,只要这两台以某种形式的网络连接着。
本地调用是同步的,门调用也是同步的。在进程内部,门是用描述符标识的,在进程以外门可能是用文件系统中的路径名标识的。
二、door_call函数
此函由客户调用,调用在服务器进程的地址空间中执行一个服务器过程。
#include<door.h>
int door_call(int fd,door_arg_t *argp);
//fd:由open返回的。
//由客户打开并将返回的描述符作为第一个参数传递给door_call的路径名标识了该函数所调用的服务器过程。
//arg:指向如下结构:描述了调用参数和用于容纳返回值的缓冲区。
typedef struct door_arg{
char *data_ptr;//数据参数
size_t data_size;//
door_desc_t *desc_ptr;
size_t desc_num;
char *rbuf;
size_t rszie;
size_t rsize;
}door_arg_t;
//返回时也由该结构描述返回值。
无论是参数还是结果都存在两种数据类型:数据和描述符。
-
数据参数:是由
data_ptr
指向的一系列总共data_size
个字节。 -
描述符参数:是一个
door_desc_t
结构的数组,每个元素含有一个从客户往服务器过程传递的描述符。所传递的door_desc_t
结构数为desc_num
,没有描述符参数,desc_ptr
指定成一个空指针,把desc_num
指定为0。 -
返回时
data_ptr
指向数据结果,data_szie
表示结果大小,没有数据数据结果,data_size
为0,这时应该忽略data_ptr
。 -
返回时也可能有描述符结果:
desc_ptr
指向一个door_desc_t
结构的数组,每个元素含有一个由服务器过程传递回客户的描述符。返回的door_desc_t
结构数存放在desc_num
中。若没有描述符结果,desc_num
为0,这时应该忽略desc_ptr
。 -
给参数和结果使用同样的缓冲区是可行的。表示调用
door_call
时,data_ptr和desc_ptr
都可指向由rbuf
指定的缓冲区中。 -
调用
door_call
前,客户把rbuf
设置成指向存放结果的缓冲区,把rsize
设置成该缓冲区的大小。返回时data_ptr和desc_ptr
通常都指向这个结果缓冲区中。 -
若该缓冲区太小而容纳不了服务器的结果,门函数库就会在调用者的地址空间中使用
mmap
自动分配一个新的缓冲区,然后相应地更新rbuf和rsize
。data_ptr和desc_ptr
将指向这个新分配的缓冲区中。
三、door_create函数
服务器进程通过调用此函数建立一个服务器过程。
#include<door.h>
//声明函数原型
typedef void Door_server_proc(void *cookie,char *dataptr,size_t datasize,door_desc_t *descptr,size_t ndesc);
int door_create(Door_server_proc *proc,void *cookie,u_int attr);
-
服务器调用此函数时,传递给函数第一个
参数proc
是一个服务器过程地址,该服务器过程由该函数返回的门描述符相关联。-
第一个
参数cookie
是作为door_create
的第二个参数传递进去的值,后面4个参数描述来自服务器的数据参数和描述符参数。-
参数attr描述所创建服务器过程的特殊值:或为0,或以下两个常值的按位或:
-
DOOR_PRIVATE
:随着客户请求的到达,门函数库在服务器进程中自动创建必要的新线程以调用服务器过程。认设置,这些线程放在进程范围的线程池中,可用于给服务器进程中的任何门提供客户请求的服务。指定DOOR_PRIVATE
属性就是告诉门函数库该门得有自己的服务器线程池,与进程范围的池分开。 -
DOOR_UNREF
:当指代该门的描述符数从2降为1时,将第二个参数(dataptr)
指定为DOOR_UNREF_DATA
再次调用该服务器过程。这次调用的descptr
参数是一个空指针,datasize和ndesc
则均为0。
-
-
-
-
服务器过程的返回值声明为void,因为不通过调用return或掉出函数尾部返回。会调用
door_return
返回。 -
door_create
创建的门描述符在其文件描述符标志中设置了FD_CLOEXEC
位。意味着创建一个门描述符的进程如果调用了一个exec
函数,该描述符就被内核关闭。 -
至于fork,尽管父进程中已经打开的所有描述符随后为子进程共享,却只有父进程会收到来自客户的门激活请求,不会递交给子进程。即使
door_create
返回的描述符在子进程在也是打开的。
四、door_return函数
服务器过程完成工作时通过调用door_return
返回。这会使客户中关联的door_call
调用返回。
#include<door.h>
int door_return(char *dataptr,size_t datasize,door_desc_t *descptr,size_t *ndesc);
//数据结果由dataptr和datasize指定。
//描述符结果由descptr和ndesc指定。
五、door_cred函数
此函数能够获取每个调用相应的客户凭证。
#include<door.h>
int door_cred(door_cred_t *cred);
//cred指向door_cred_t结果:
typedef struct door_cred{
uid_t dc_euid;
gid_t dc_egid;
uid_t dc_ruid;
gid_t dc_rgid;
pid_t dc_pid;
}door_cred_t;
本函数没有描述符参数。返回发起当前门激活请求的客户的有关信息,因而必须在服务器过程或由该过程调用的某个函数中调用它。
六、door_info函数
此函数用于找出有关服务器信息。
#include<door.h>
int door_info(int fd,door_info_t *info);
//fd:已打开的门。
//info指向如下结构:
typedef struct door_info{
pid_t di_target;//服务器进程ID
door_ptr_t di_proc;//指定服务器过程在服务器进程中的地址
door_ptr_t di_data;//返回cookie指针。
door_attr_t di_attributes;//存放门的当前属性
door_id_t di_uniquifier;//刚创建时赋予一个系统范围内唯一的数值。
}door_info_t;
此函数通常由客户端调用,以获取相关服务器信息,也可以由一个服务器过程调用,这时第一个参数指定DOOR_QUERY
,所返回的信息是关于调用线程的这时服务器过程的地址(di_proc)
和cookie(di_data)
也许有用。
七、结果缓冲区太小
客户端向服务器传递一个长整数,服务器以长整数结果返回该值的平方。若结果缓冲区太小而容纳不了服务器的结果,门函数库就会自动分配一个新的缓冲区。
客户端
int main(int argc, char **argv)
{
int fd;
long ival, oval;
door_arg_t arg;
if (argc != 3)
err_quit("usage: client2 <server-pathname> <integer-value>");
fd = open(argv[1], O_RDWR); /* 打开门 */
/* 设置参数和指向结果的指针 */
ival = atol(argv[2]);
arg.data_ptr = (char *) &ival; /* 数据参数 */
arg.data_size = sizeof(long); /* 数据参数的大小 */
arg.desc_ptr = NULL;
arg.desc_num = 0;
arg.rbuf = (char *) &oval; /*数据结果*/
//arg.rsize = sizeof(long);
//把客户的结果缓冲区大小减少1字节,将会分配新的缓冲区
//解决结果缓冲区太小!
arg.rsize = sizeof(long)-1; /* 数据结果的大小 */
/* 调用服务器过程和打印结果*/
door_call(fd, &arg);
printf("&oval = %p, data_ptr = %p, rbuf = %p, rsize = %d\n",
&oval, arg.data_ptr, arg.rbuf, arg.rsize);
printf("result: %ld\n", *((long *) arg.data_ptr));
exit(0);
}
服务器程序:
void servproc(void *cookie, char *dataptr, size_t datasize,
door_desc_t *descptr, size_t ndesc)
{
long arg, result;
arg = *((long *) dataptr);
result = arg * arg;
door_return((char *) &result, sizeof(result), NULL, 0);
}
void Fattach(int fd, const char *path)
{
if (fattach(fd, path) == -1)
err_sys("fattach error");
}
int main(int argc, char **argv)
{
int fd;
if (argc != 2)
err_quit("usage: server1 <server-pathname>");
/* 创建门描述符并附加到路径名*/
fd = door_create(servproc, NULL, 0);
unlink(argv[1]);
close(open(argv[1], O_CREAT | O_RDWR, FILE_MODE));
fattach(fd, argv[1]);
/* servproc()处理所有客户端请求 */
for ( ; ; )
pause();
}
八、door_cred函数和客户凭证
服务器程序:
void servproc(void *cookie, char *dataptr, size_t datasize,
door_desc_t *descptr, size_t ndesc)
{
long arg, result;
door_cred_t info;
//获取并打印客户端凭据
door_cred(&info);
printf("euid=%d,ruid=%ld,pid=%ld\n".(long)info.dc_euid,(long)info.dc_ruid,(long)info.dc_pid);
arg = *((long *)dataptr;
result = arg *arg;
door_return((char *)&result,,sizeof(result),NULL,0);
}
void Fattach(int fd, const char *path)
{
if (fattach(fd, path) == -1)
err_sys("fattach error");
}
int main(int argc, char **argv)
{
int fd;
if (argc != 2)
err_quit("usage: server1 <server-pathname>");
/* 创建门描述符并附加到路径名 */
fd = door_create(servproc, NULL, 0);
unlink(argv[1]);
close(Open(argv[1], O_CREAT | O_RDWR, FILE_MODE));
fattach(fd, argv[1]);
/* servproc()处理所有客户端请求 */
for ( ; ; )
pause();
}
客户端程序:
int main(int argc, char **argv)
{
int fd;
long ival, oval;
door_arg_t arg;
if (argc != 3)
err_quit("usage: client4 <server-pathname> <integer-value>");
fd = open(argv[1], O_RDWR); /*打开门 */
/*设置参数和指向结果的指针*/
ival = atol(argv[2]);
arg.data_ptr = (char *) &ival; /* 数据参数 */
arg.data_size = sizeof(long); /* 数据参数的大小 */
arg.desc_ptr = NULL;
arg.desc_num = 0;
arg.rbuf = (char *) &oval; /*数据结果 */
arg.rsize = sizeof(long); /* 数据结果的大小*/
/* 调用服务器过程和打印结果*/
door_call(fd, &arg);
printf("result: %ld\n", *((long *) arg.data_ptr));
exit(0);
}
九、服务器的自动线程管理
查看服务器执行的线程管理,使服务器过程在一开始执行时输出自己所在线程的线程ID,然后睡眠5秒钟。
long pr_thread_id(pthread_t *ptr)
{
#if defined(sun)
return((ptr == NULL) ? pthread_self() : *ptr); /* Solaris */
#elif defined(__osf__) && defined(__alpha)
pthread_t tid;
tid = (ptr == NULL) ? pthread_self() : *ptr; /* Digital Unix */
return(pthread_getsequence_np(tid));
#else
/* 其他一切 */
return((ptr == NULL) ? pthread_self() : *ptr);
#endif
}
void servproc(void *cookie, char *dataptr, size_t datasize,
door_desc_t *descptr, size_t ndesc)
{
long arg, result;
arg = *((long *) dataptr);
printf("thread id %ld, arg = %ld\n", pr_thread_id(NULL), arg);
sleep(5);
result = arg * arg;
door_return((char *) &result, sizeof(result), NULL, 0);
}
void fattach(int fd, const char *path)
{
if (fattach(fd, path) == -1)
err_sys("fattach error");
}
int main(int argc, char **argv)
{
int fd;
if (argc != 2)
err_quit("usage: server5 <server-pathname>");
/*创建门描述符并附加到路径名 */
fd = door_create(servproc, NULL, 0);
unlink(argv[1]);
close(open(argv[1], O_CREAT | O_RDWR, FILE_MODE));
fattach(fd, argv[1]);
/*servproc()处理所有客户端请求 */
for ( ; ; )
pause();
}
客户端程序:
int main(int argc, char **argv)
{
int fd;
long ival, oval;
door_arg_t arg;
if (argc != 3)
err_quit("usage: client5 <server-pathname> <integer-value>");
fd = open(argv[1], O_RDWR); /* 打开门*/
/* 设置参数和指向结果的指针*/
ival = atol(argv[2]);
arg.data_ptr = (char *) &ival; /* 数据参数 */
arg.data_size = sizeof(long); /*数据参数的大小*/
arg.desc_ptr = NULL;
arg.desc_num = 0;
arg.rbuf = (char *) &oval; /* 数据结果 */
arg.rsize = sizeof(long); /* 数据结果的大小 */
/*调用服务器过程和打印结果 */
door_call(fd, &arg);
printf("result: %ld\n", *((long *) arg.data_ptr));
exit(0);
}
服务器进程根据需要自动创建服务器线程,若一个应用程序希望亲自处理线程的管理,可以这样做。
十、服务器的自动线程管理:多个服务器过程
上面的服务器进程只有一个服务器过程,问题:同一个服务器进程中的多个服务器过程是否可以使用同一个线程池。下列程序给服务器进程增加了另外一个服务器过程,表现出在不同进程间处理参数和结果。
头文件:
//给平方函数定义了一个输入参数的数据类型和一个输出参数的数据类型。
#define PATH_SQRT_DOOR "/tmp/sqrtproc_door"
typedef struct { /* 输入到sqltproc()*/
long arg1;
} sqrtproc_in_t;
typedef struct { /*sqrtproc()的输出*/
double res1;
} sqrtproc_out_t;
//新服务器过程接受一个长整数,返回一个double类型的值。
#define PATH_SQUARE_DOOR "/tmp/squareproc_door"
typedef struct { /* 输入到 squareproc() */
long arg1;
} squareproc_in_t;
typedef struct { /* 输出 squareproc() */
long res1;
} squareproc_out_t;
客户端程序:一先一后调用那两个服务器过程,然后输出结果。
int main(int argc, char **argv)
{
int fdsquare, fdsqrt;
door_arg_t arg;
squareproc_in_t square_in;
squareproc_out_t square_out;
sqrtproc_in_t sqrt_in;
sqrtproc_out_t sqrt_out;
if (argc != 2)
err_quit("usage: client7 <integer-value>");
fdsquare = open(PATH_SQUARE_DOOR, O_RDWR);
fdsqrt = open(PATH_SQRT_DOOR, O_RDWR);
/* 设置参数并调用squareproc() */
square_in.arg1 = atol(argv[1]);
arg.data_ptr = (char *) &square_in;
arg.data_size = sizeof(square_in);
arg.desc_ptr = NULL;
arg.desc_num = 0;
arg.rbuf = (char *) &square_out;
arg.rsize = sizeof(square_out);
door_call(fdsquare, &arg);
/* 设置参数并调用sqrtproc() */
sqrt_in.arg1 = atol(argv[1]);
arg.data_ptr = (char *) &sqrt_in;
arg.data_size = sizeof(sqrt_in);
arg.desc_ptr = NULL;
arg.desc_num = 0;
arg.rbuf = (char *) &sqrt_out;
arg.rsize = sizeof(sqrt_out);
door_call(fdsqrt, &arg);
printf("result: %ld %g\n", square_out.res1, sqrt_out.res1);
exit(0);
}
服务器程序:给出了两个服务器过程。每个过程输出其所在线程的线程ID和输入参数,睡眠5秒钟,计算结果,然后返回。
#include <math.h>
void squareproc(void *cookie, char *dataptr, size_t datasize,
door_desc_t *descptr, size_t ndesc)
{
squareproc_in_t in;
squareproc_out_t out;
memcpy(&in, dataptr, min(sizeof(in), datasize));
printf("squareproc: thread id %ld, arg = %ld\n",
pr_thread_id(NULL), in.arg1);
sleep(5);
out.res1 = in.arg1 * in.arg1;
door_return((char *) &out, sizeof(out), NULL, 0);
}
void sqrtproc(void *cookie, char *dataptr, size_t datasize,
door_desc_t *descptr, size_t ndesc)
{
sqrtproc_in_t in;
sqrtproc_out_t out;
memcpy(&in, dataptr, min(sizeof(in), datasize));
printf("sqrtproc: thread id %ld, arg = %ld\n",
pr_thread_id(NULL), in.arg1);
sleep(5);
out.res1 = sqrt((double) in.arg1);
door_return((char *) &out, sizeof(out), NULL, 0);
}
//打开两个门描述符,然后给每个门描述符关联一个服务器过程。
int main(int argc, char **argv)
{
int fd;
if (argc != 1)
err_quit("usage: server7");
fd = Door_create(squareproc, NULL, 0);
unlink(PATH_SQUARE_DOOR);
close(Open(PATH_SQUARE_DOOR, O_CREAT | O_RDWR, FILE_MODE));
Fattach(fd, PATH_SQUARE_DOOR);
fd = door_create(sqrtproc, NULL, 0);
unlink(PATH_SQRT_DOOR);
close(Open(PATH_SQRT_DOOR, O_CREAT | O_RDWR, FILE_MODE));
fattach(fd, PATH_SQRT_DOOR);
for ( ; ; )
pause();
}
对一个给定进程,其服务器线程池中的任意线程都能够处理针对任意服务器过程的客户请求。
十一、服务器DOOR_UNREF属性
DOOR_UNREF
可作为一个新创建的门的属性之一指定给door_create函数,当指代某个具备该属性的门的描述符数降为1时,该门的服务器过程将有一次特殊的激活。特殊指给服务器的第二个参数是常值DOOR_UNREF_DATA
。
引用该门的三种方法:
-
服务器中由
door_create
返回的描述符算作一个引用。 -
附接到该门上的文件系统中的路径名也算作一个引用。可以删除这个引用:调用fdetach函数,运行fdetach程序,或者从文件系统中删除该路径名(既可调用unlink函数,也可运行rm命令)。
-
客户中由open返回的描述符算作 一 个打开的引用,直到该描述符关闭为止,这种关闭既可以显式地调用close完成,也可以隐式(本章)地由客户进程的终止完成。
十二、描述符传递
把一个打开着的描述符从一个进程传递到另一个进程时的方法:
- 调用fork之后,子进程与父进程共享所有打开着的描述符。
- 调用exec之后,所有描述符通常仍保持打开。
现在提供了把任何打开着的描述符从一个进程传递给任何其他进程的能力,这两个进程间有无亲缘关系即可。门提供了从客户端到服务器以及从服务器到客户的一个描述符传递API。
例:使服务器打开文件并把打开着的描述符传递给客户,然后由客户把文件的内容复制到标准输出,如下图所示:
客户端程序:
int main(int argc, char **argv)
{
int door, fd;
char argbuf[BUFFSIZE], resbuf[BUFFSIZE], buff[BUFFSIZE];
size_t len, n;
door_arg_t arg;
if (argc != 2)
err_quit("usage: clientfd1 <server-pathname>");
door = Open(argv[1], O_RDWR); /* 打开门 */
Fgets(argbuf, BUFFSIZE, stdin); /* 读取要打开的文件的路径名 */
len = strlen(argbuf);
if (argbuf[len-1] == '\n')
len--; /*从fgets()中删除换行符 */
/* 设置参数和指向结果的指针*/
arg.data_ptr = argbuf; /* 数据自变量 */
arg.data_size = len + 1; /* 数据参数的大小 */
arg.desc_ptr = NULL;
arg.desc_num = 0;
arg.rbuf = resbuf; /*数据结果/
arg.rsize = BUFFSIZE; /* 数据结果的大小 */
door_call(door, &arg); /*呼叫服务器过程 */
if (arg.data_size != 0)
err_quit("%.*s", arg.data_size, arg.data_ptr);
else if (arg.desc_ptr == NULL)
err_quit("desc_ptr is NULL");
else if (arg.desc_num != 1)
err_quit("desc_num = %d", arg.desc_num);
else if (arg.desc_ptr->d_attributes != DOOR_DESCRIPTOR)
err_quit("d_attributes = %d", arg.desc_ptr->d_attributes);
fd = arg.desc_ptr->d_data.d_desc.d_descriptor;
while ( (n = read(fd, buff, BUFFSIZE)) > 0)
write(STDOUT_FILENO, buff, n);
exit(0);
}
服务器程序:
void servproc(void *cookie, char *dataptr, size_t datasize,
door_desc_t *descptr, size_t ndesc)
{
int fd;
char resbuf[BUFFSIZE];
door_desc_t desc;
dataptr[datasize-1] = 0; /* null终止 */
if ( (fd = open(dataptr, O_RDONLY)) == -1) {
/*错误:必须告诉客户*/
snprintf(resbuf, BUFFSIZE, "%s: can't open, %s",
dataptr, strerror(errno));
Door_return(resbuf, strlen(resbuf), NULL, 0);
} else {
/* open成功:返回描述符 */
desc.d_data.d_desc.d_descriptor = fd;
desc.d_attributes = DOOR_DESCRIPTOR;
Door_return(NULL, 0, &desc, 1);
}
}
int main(int argc, char **argv)
{
int fd;
if (argc != 2)
err_quit("usage: serverfd1 <server-pathname>");
/* 创建门描述符并附加到路径名 */
fd = Door_create(servproc, NULL, 0);
unlink(argv[1]);
Close(Open(argv[1], O_CREAT | O_RDWR, FILE_MODE));
Fattach(fd, argv[1]);
/* servproc()处理所有客户端请求*/
for ( ; ; )
pause();
}
十三、door_server_create函数
当客户请求到达时,门函数库会按照处理它们的需要自动地创建新线程。这些线程由该函数库作为脱离的线程创建,具有默认的线程栈大小,禁止了线程取消功能,并具有从调用door_create的线程继承来的信号掩码和调度类。
若想修改这任何特性,或者亲自管理服务器线程池,可以调用door_server_create
以指定我们自己的服务器创建过程。
#include<door.h>
typedef void Door_create_proc(door_info_t *);
//使用typedef将服务器创建定义为接受单个参数,不返回任何值。
//调用此函数时,参数指向服务器创建过程的指针,返回值是指向上一个服务器创建过程的指针。
Door_create_proc *door_sever_create(Door_create_proc *proc);
每当需要一个新线程来给某个客户请求提供服务时,服务器创建过程被调用,至于哪个服务器过程需要这个新线程的信息则存放在其地址作为参数传递进本创建过程的door_info_t
结构中。结构di_proc成员含有服务器过程的地址,di_data
成员含有该服务器过程每次被调用时所传递进去cookie
指针。
例:客户向服务器传递一个长整数,服务器以长整数结果返回该值平方。
//服务器过程
void servproc(void *cookie, char *dataptr, size_t datasize,
door_desc_t *descptr, size_t ndesc)
{
long arg, result;
arg = *((long *) dataptr);
printf("thread id %ld, arg = %ld\n", pr_thread_id(NULL), arg);
sleep(5);
result = arg * arg;
door_return((char *) &result, sizeof(result), NULL, 0);
}
pthread_mutex_t fdlock = PTHREAD_MUTEX_INITIALIZER;
static int fd = -1; /*门描述符 */
//线程启动
void * my_thread(void *arg)
{
int oldstate;
door_info_t *iptr = arg;
//等待描述符变为有效
if ((Door_server_proc *) iptr->di_proc == servproc) {
pthread_mutex_lock(&fdlock);
pthread_mutex_unlock(&fdlock);
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate);
//把本线程捆绑到一个门
door_bind(fd);
door_return(NULL, 0, NULL, 0);
} else
err_quit("my_thread: unknown function: %p", arg);
return(NULL); /* 从未执行过*/
}
//服务器创建过程,每被调用就创建一个新的线程。
void my_create(door_info_t *iptr)
{
pthread_t tid;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);//设置脱离线程范围
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&tid, &attr, my_thread, (void *) iptr);
pthread_attr_destroy(&attr);
printf("my_thread: created server thread %ld\n", pr_thread_id(&tid));
}
void Fattach(int fd, const char *path)
{
if (fattach(fd, path) == -1)
err_sys("fattach error");
}
int main(int argc, char **argv)
{
if (argc != 2)
err_quit("usage: server6 <server-pathname>");
door_server_create(my_create);
/* 创建门描述符并附加到路径名*/
//互斥锁包含door_create调用
pthread_mutex_lock(&fdlock);
///DOOR_PRIVATE称为私用服务器池的线程池。
fd = door_create(servproc, NULL, DOOR_PRIVATE);
pthread_mutex_unlock(&fdlock);
unlink(argv[1]);
close(open(argv[1], O_CREAT | O_RDWR, FILE_MODE));
Fattach(fd, argv[1]);
/* servproc()处理所有客户端请求 */
for ( ; ; )
pause();
}
使用DOOR_PRIVATE
指定一个私有服务器池和使用door_server_create
指定一个服务器创建过程是互相独立的。有以下四种情形:
-
默认情形:没有私用服务器池,也没有服务器创建过程。系统根据需要创建线程,它们都进入进程范围的线程池。
-
指定
OOOR_PRIVATER
, 不过没有服务器创建过程。系统根据需要创建线程,对于创建时没有指定DOOR_PRIVATE
属性的门,新创建的线程将进入进程范围的池,对于创建时指定了DOOR_PRIVATE
属性的门,新创建的线程将进入该门的私用服务器池。 -
没有私用服务器,但是指定了一个服务器创建过程。每当需要一个新线程时,该服务器创建过程就被调用,所创建的线程都进入进程范围的线程池。
-
指定
DOOR_ PRIVATE
, 同时指定了 — 个服务器创建过程。每当需要一个新线程时,该服务器创建过程就被调用。一个线程创建出来后,必须调用door_ bind
把自己赋给合适的私用服务器池,否则它将被赋给进程范围的线程池。
客户端程序:
int main(int argc, char **argv)
{
int fd;
long ival, oval;
door_arg_t arg;
if (argc != 3)
err_quit("usage: client6 <server-pathname> <integer-value>");
fd = Open(argv[1], O_RDWR); /* 打开门 */
/*设置参数和指向结果的指针 */
ival = atol(argv[2]);
arg.data_ptr = (char *) &ival; /*数据参数 */
arg.data_size = sizeof(long); /*数据参数的大小 */
arg.desc_ptr = NULL;
arg.desc_num = 0;
arg.rbuf = (char *) &oval; /* 数据结果*/
arg.rsize = sizeof(long); /* 数据结果的大小 */
/*调用服务器过程和打印结果 */
door_call(fd, &arg);
printf("result: %ld\n", *((long *) arg.data_ptr));
exit(0);
}
十四、door_bind、door_unbind和door_revoke函数
#include<door.h>
int door_bind(int fd);//调用线程捆绑到与描述符为fd的门关联的私用服务器池中。
int door_unbind(void);//松绑
int door_revoke(int fd);//撤销对于由fd标识的门的访问。
十五、door_call系统调用的不可中断性
door_call系统调用的不可中断性,通过将服务器程序修改成其中的服务器过程在返回前睡眠6秒钟,能看出此特性。
服务器程序:
void servproc(void *cookie, char *dataptr, size_t datasize,
door_desc_t *descptr, size_t ndesc)
{
long arg, result;
sleep(6); /*让客户端捕获SIGCHLD */
arg = *((long *) dataptr);
result = arg * arg;
door_return((char *) &result, sizeof(result), NULL, 0);
}
int main(int argc, char **argv)
{
int fd;
if (argc != 2)
err_quit("usage: serverintr2 <server-pathname>");
/* 创建门描述符并附加到路径名*/
fd = door_create(servproc, NULL, 0);
unlink(argv[1]);
close(open(argv[1], O_CREAT | O_RDWR, FILE_MODE));
Fattach(fd, argv[1]);
/*servproc()处理所有客户端请求 */
for ( ; ; )
pause();
}
客户端程序:
void sig_chld(int signo)
{
return; /* 只需中断door_call()*/
}
int main(int argc, char **argv)
{
int fd;
long ival, oval;
door_arg_t arg;
if (argc != 3)
err_quit("usage: clientintr2 <server-pathname> <integer-value>");
fd = open(argv[1], O_RDWR); /* 打开门 */
/* 设置参数和指向结果的指针 */
ival = atol(argv[2]);
arg.data_ptr = (char *) &ival; /*数据参数 */
arg.data_size = sizeof(long); /* 数据参数的大小 */
arg.desc_ptr = NULL;
arg.desc_num = 0;
arg.rbuf = (char *) &oval; /* 数据结果*/
arg.rsize = sizeof(long); /* 数据结果的大小 */
signal(SIGCHLD, sig_chld);
if (fork() == 0) {
sleep(2); /* child */
exit(0); /* 生成SIGCHLD*/
}
/* 父进程:调用服务器过程并打印结果 */
//两秒父进程调用door_call,父进程捕捉信号,接着信号处理程序返回,从而中断door_call系统调用。
door_call(fd, &arg);
printf("result: %ld\n", oval);
exit(0);
}
意味着必须阻塞调用door_call
区间可能产生的任何信号,防止它们被递交给进程,因为这些信号会中断door_call
。
十六、等势过程和非等势过程
上个客户端捕获一个信号,当检查到由door_call
返回的EINTR
错误后接着再次调用同一个服务器过程将发生错误,错误来自被捕捉的信号,而不是过早的来自服务器的终止。
void servproc(void *cookie, char *dataptr, size_t datasize,
door_desc_t *descptr, size_t ndesc)
{
long arg, result;
//输出当前线程ID
printf("thread id %ld called\n", pr_thread_id(NULL));
sleep(6); /* 让客户端捕获SIGCHLD */
arg = *((long *) dataptr);
result = arg * arg;
printf("thread id %ld returning\n", pr_thread_id(NULL));
door_return((char *) &result, sizeof(result), NULL, 0);
}
int main(int argc, char **argv)
{
int fd;
if (argc != 2)
err_quit("usage: serverintr3 <server-pathname>");
/* 4create a door descriptor and attach to pathname */
fd = door_create(servproc, NULL, 0);
unlink(argv[1]);
close(open(argv[1], O_CREAT | O_RDWR, FILE_MODE));
Fattach(fd, argv[1]);
/* 4servproc() handles all client requests */
for ( ; ; )
pause();
}
客户端程序:接收到EINTR
错误后再次调用door_call
客户端
//声明全局变量
volatile sig_atomic_t caught_sigchld;
void sig_chld(int signo)
{
caught_sigchld = 1;
return; /* 只需中断door_call() */
}
int main(int argc, char **argv)
{
int fd, rc;
long ival, oval;
door_arg_t arg;
if (argc != 3)
err_quit("usage: clientintr3 <server-pathname> <integer-value>");
fd = open(argv[1], O_RDWR); /* 打开门 */
/* 设置参数和指向结果的指针 */
ival = atol(argv[2]);
arg.data_ptr = (char *) &ival; /* 数据参数*/
arg.data_size = sizeof(long); /* 数据参数的大小*/
arg.desc_ptr = NULL;
arg.desc_num = 0;
arg.rbuf = (char *) &oval; /* 数据结果 */
arg.rsize = sizeof(long); /* 数据结果的大小 */
signal(SIGCHLD, sig_chld);
if (Fork() == 0) {
sleep(2); /* 子进程 */
exit(0); /* 生成SIGCHLD */
}
/* 父进程:调用服务器过程并打印结果 */
for ( ; ; ) {
printf("calling door_call\n");
if ( (rc = door_call(fd, &arg)) == 0)
break; /* 成功 */
if (errno == EINTR && caught_sigchld) {
caught_sigchld = 0;
continue; /* 再次调用door_call() */
}
err_sys("door_call error");
}
printf("result: %ld\n", oval);
exit(0);
}
第一次调用door_call后约22秒时,信号处理被激活,caught_sigchld设置为1,该信号处理程序返回导致第一次door_call调用返回EINTR错误,再次调用door_call。第二次调用服务器过程运行完毕,从而返回预期结果。
查看服务器输出,发现服务器过程调用了两次:
客户的第 一 次door_ call
调 用 被所捕获的信号中断后 ,它的第 二 次door_ call
调用启动了再次调用服务器过程的另一个线程。如果该服务器过程是等势的 , 那是没有问题。但是如果该服务器过程是非等势的,那就有问题了。
-
等势:描述一个过程时,意思是该过程可调用任意多次而不出问题。例:a.计算平方值的服务器过程是等势的:不论调用次还一次是两次 ,都得到正确的结果 。b.返回当前时间和日期的过程 。尽 管该过程每次可能返回不同的信 息(譬如说它被调用了两次,彼此相差1秒,于是导致返回时间也相差1秒),仍然是正确的。
-
非等势过程的经典例子是从某个银行账户减去笔 费用 的过 程 : 除非 该过程 只 调用 了一 次 ,否则最终结果是错误的。
十七、客户过早终止
int main(int argc, char **argv)
{
int fd;
long ival, oval;
door_arg_t arg;
if (argc != 3)
err_quit("usage: clientintr4 <server-pathname> <integer-value>");
fd = open(argv[1], O_RDWR); /* 打开门*/
/* 设置参数和指向结果的指针 */
ival = atol(argv[2]);
arg.data_ptr = (char *) &ival; /* 数据参数 */
arg.data_size = sizeof(long); /* 数据参数的大小 */
arg.desc_ptr = NULL;
arg.desc_num = 0;
arg.rbuf = (char *) &oval; /* 数据结果*/
arg.rsize = sizeof(long); /*数据结果的大小 */
/*调用服务器过程和打印结果 */
//alarm调度了一个3秒后发出的SIGALAM信号,由于没有捕捉这个信号。默认信号终止客户进程。导致客户在door_call返回前终止,因为服务器过程中放置一个6秒钟的睡眠。
alarm(3);
door_call(fd, &arg);
printf("result: %ld\n", oval);
exit(0);
}
当系统检测到客户在其door_call
调用进展期间即终止时,就会向处理该调用的服务器线程发送一个取消请求。
服务器程序:检测客户过早终止
void servproc_cleanup(void *arg)
{
printf("servproc cancelled, thread id %ld\n", pr_thread_id(NULL));
}
void servproc(void *cookie, char *dataptr, size_t datasize,
door_desc_t *descptr, size_t ndesc)
{
int oldstate, junk;
long arg, result;
//启用线程取消功能
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldstate);
//把函数servpro_clenup注册为取消处理程序
pthread_cleanup_push(servproc_cleanup, NULL);
sleep(6);
arg = *((long *) dataptr);
result = arg * arg;
pthread_cleanup_pop(0);
Pthread_setcancelstate(oldstate, &junk);
door_return((char *) &result, sizeof(result), NULL, 0);
}
int main(int argc, char **argv)
{
int fd;
if (argc != 2)
err_quit("usage: serverintr4 <server-pathname>");
/* 创建门描述符并附加到路径名 */
fd = door_create(servproc, NULL, 0);
unlink(argv[1]);
close(open(argv[1], O_CREAT | O_RDWR, FILE_MODE));
Fattach(fd, argv[1]);
/* servproc()处理所有客户端请求 */
for ( ; ; )
pause();
}
运行客户程序两次,进程被SIGALRM
信号所杀灭时,输出Alarm Clock
警告。
查看服务器输出,看到每次有客户过早终止时,服务器线程确实被取消,清理处理程序也被调用。
十八、服务器过早终止
让客户阻塞在door_call调用中等待结果期间,必须知道服务器线程是否因某种原因而终止。下列让服务器过程调用pthread_exit
以终止所在的线程。这样仅仅终止该线程本身而不是终止整个服务器进程。
服务器程序:
void servproc(void *cookie, char *dataptr, size_t datasize,
door_desc_t *descptr, size_t ndesc)
{
long arg, result;
pthread_exit(NULL); /* 终止所在的线程*/
arg = *((long *) dataptr);
result = arg * arg;
door_return((char *) &result, sizeof(result), NULL, 0);
}
int main(int argc, char **argv)
{
int fd;
if (argc != 2)
err_quit("usage: serverintr1 <server-pathname>");
/* 创建门描述符并附加到路径名 */
fd = door_create(servproc, NULL, 0);
unlink(argv[1]);
close(open(argv[1], O_CREAT | O_RDWR, FILE_MODE));
Fattach(fd, argv[1]);
/* servproc()处理所有客户端请求 */
for ( ; ; )
pause();
}
客户端程序:
int main(int argc, char **argv)
{
int fd;
long ival, oval;
door_arg_t arg;
if (argc != 3)
err_quit("usage: clientintr1 <server-pathname> <integer-value>");
fd = open(argv[1], O_RDWR); /*打开门 */
/* 设置参数和指向结果的指针 */
ival = atol(argv[2]);
arg.data_ptr = (char *) &ival; /* 数据参数 */
arg.data_size = sizeof(long); /*数据参数的大小 */
arg.desc_ptr = NULL;
arg.desc_num = 0;
arg.rbuf = (char *) &oval; /* 数据结果 */
arg.rsize = sizeof(long); /* 数据结果的大小 */
/* 调用服务器过程和打印结果 */
door_call(fd, &arg);
printf("result: %ld\n", oval);
exit(0);
}
运行客户程序,发现若服务器过程在返回前终止了,那么客户的door_call
将返回一个EINNTR
错误。