1、许可证服务器版本1.0
运行服务器和客户端,首先客户端进入get_ticket函数:client将HELO pid发给server,server再传回消息,如果传回来TICK %d.%d,将%d. %d保存至ticket_buf,获得ticket槽位,如果传回来FAIL,获取ticket失败。然后客户端进入do_regular_work函数。最后客户端进入release_ticket函数:client睡眠10秒后,发送GBYE %d. %d给server,server再传回消息,如果传回来THNX,server收回了ticket,如果传回来FAIL,release ticket失败。
服务器不断循环等待recvfrom接收消息,当服务器接收到消息:如果client发送HELO pid过来,server占位ticket_array同时num_tickets_out++并且发送回TICK %d.%d,如果client发送GBYE %d.%d过来,server释放ticket_array同时num_tickets_out--并且发送回THNX,否则client发送其他消息过来,发送回FAIL。
2、许可证服务器版本2.0
许可证服务器能很好的工作,前提是所有的进程是正常工作的,现实世界上的程序必须能够处理异常崩溃,这里考虑两种情形:客户端崩溃和服务器崩溃。
(1)处理客户端崩溃
许可证服务器可以定期检查票据数组,确定其中的每个进程是否还活着,如果某个进程已经不存在了,服务器可以把该进程从数组中去除,释放占用的票据资源。可以使用alarm和signal技术来周期地调用一个函数,那如何判断进程是否还活着呢?
- 一种方法是使用popen运行ps,然后从ps的输出中查找pid以确定持有票据的pid是否存在;
- 另一种简单快速的方法是使用kill系统调用,通过使用kill给进程发送0的信号以确定它是否存在,如果进程不存在,内核将不会发送信号而是返回错误并设置errno为ESRCH。
(2)处理服务器崩溃
服务器崩溃处理的最简单的方式就是重启服务器,重启后服务器的票据数组为空,这种可能导致崩溃前的票据数据已满但是重启后服务器仍然发送票据,另外持有旧的服务器票据的客户归还票据时,将被认为是伪造票据。最简单的解决方式是票据验证,每个客户周期性地向服务器发送票据副本,验证是否合法。
- 一种方法服务器检查票据列表,如果该项为空,服务器可以认为该票据是崩溃前发送出去的,将该票据加到列表中。但是这种方法可能导致如果一个新客户在重启后请求票据,服务器可能分发一个已经给予其他客户的票据给该客户,当旧客户归还票据的时候对票据进行验证,服务器将会拒绝。
- 另一种方法是服务器拒绝表中没有的所有票据,持有被拒票据的客户尝试再重新申请新的,也就是客户端周期性票据验证,如果验证成功则没事,如果验证的时候该票据所在位置为空就重新申请该位置的票据。
3、分布式许可证服务器
上诉许可证服务器的客户端和服务器位于同一个主机,如果运行在不同主机就会出现的一些问题:
- 重复的进程id:位于不同主机可能导致pid相同,但可以通过扩展票据表项格式加入识别主机的字段就可以解决这个问题
- 回收票据:服务器不能给其他机器上的进程发送信号,kill(pid,0)命令失效,因为服务器和客户端位于不同主机,这就不能收回票据了,可以在每台机器本地运行一个服务器用来监控丢失的票据
- 主机崩溃:如果其中的一台机器停止运行如何工作?可以通过建立一个分布式许可证服务器来同时支持多台机器解决
三种分布式许可证服务器方案:
- 客户端服务器和中央服务器通信:每台机器都有一个本地服务器,每个客户跟本地服务器通信,本地服务器把请求转发给中央服务器。
- 每个客户都和中央服务器通信:客户直接给中央服务器主机发送请求,本地服务器不和本地客户通信,本地服务器在重新声明票据的时候作为中央服务器的代理。
- 客户服务器和客户服务器通信:每台机器都有客户服务器,每个客户跟本地服务器通信,没有中央服务器,所有的本地服务器间互相通信。
4、Unix域socket
使用Unix域socket仅限于本地主机内部通信,socket地址有两种地址:Internet地址和本地地址,Internet地址包含主机id和端口号,本地地址通常叫做Unix域地址,它是一个文件名。编写日志服务器使用Unix域socket地址,只有同一台主机上的客户才能发送消息给它。
5、编写程序代码
lserv1.c
许可证服务器版本1.0的服务器
#include<stdio.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<netdb.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/errno.h>
#include<signal.h>
#include<sys/socket.h>
#include<stdbool.h>
#include<netinet/in.h>
#define MSGLEN 128
#define SERVER_PORTNUM 2020
#define HOSTLEN 512
#define MAXUSERS 3
#define TICKET_AVAIL 0
#define oops(p) {perror(p);exit(1);}
int ticket_array[MAXUSERS];
int sd=-1;
int num_tickets_out=0;
int make_dgram_server_socket(int portnum)
{
struct sockaddr_in saddr;
char hostname[HOSTLEN];
struct hostent *hp;
int sock_id;
sock_id=socket(PF_INET,SOCK_DGRAM,0);
if(sock_id==-1)
oops("socket");
bzero((void*)&saddr,sizeof(saddr));
gethostname(hostname,HOSTLEN);
hp=gethostbyname(hostname);
bcopy((void*)hp->h_addr,(void*)&saddr.sin_addr,hp->h_length);
saddr.sin_port=htons(portnum);
saddr.sin_family=AF_INET;
if(bind(sock_id,(struct sockaddr *)&saddr,sizeof(saddr))!=0)
oops("bind");
return sock_id;
}
void free_all_tickets()
{
int i;
for(i=0;i<MAXUSERS;i++)
ticket_array[i]=TICKET_AVAIL;
}
int setup()
{
sd=make_dgram_server_socket(SERVER_PORTNUM);
if(sd==-1)
oops("make socket");
free_all_tickets();
return sd;
}
void shut_down()
{
close(sd);
}
void narrate(char *msg1,char *msg2,struct sockaddr_in *clientp)
{
fprintf(stderr,"\t\tSERVER:%s %s",msg1,msg2);
if(clientp)
fprintf(stderr,"(%s:%d)",inet_ntoa(clientp->sin_addr),ntohs(clientp->sin_port));
putc('\n',stderr);
}
char *do_hello(char *msg_p)
{
int x;
static char replybuf[MSGLEN];
if(num_tickets_out>=MAXUSERS)
return "FAIL,no tickets available";
for(x=0;x<MAXUSERS&&ticket_array[x]!=TICKET_AVAIL;x++);
if(x==MAXUSERS)
{
narrate("database corrupt","",NULL);
return "FAIL,database corrupt";
}
ticket_array[x]=atoi(msg_p+5);
sprintf(replybuf,"TICK %d.%d",ticket_array[x],x);
num_tickets_out++;
return replybuf;
}
char *do_goodbye(char *msg_p)
{
int pid,slot;
if((sscanf((msg_p+5),"%d.%d",&pid,&slot)!=2)||(ticket_array[slot]!=pid))
{
narrate("bogus ticket",msg_p+5,NULL);
return "FAIL,invalid ticket";
}
ticket_array[slot]=TICKET_AVAIL;
num_tickets_out--;
return "THNX see you!";
}
void handle_request(char *req,struct sockaddr_in *client,socklen_t addlen)
{
char *response;
int ret;
//client发送HELO pid过来,server占位ticket_array同时num_tickets_out++并且发送回TICK %d.%d
if(strncmp(req,"HELO",4)==0)
response=do_hello(req);
//client发送GBYE %d.%d过来,server释放ticket_array同时num_tickets_out--并且发送回THNX
else if(strncmp(req,"GBYE",4)==0)
response=do_goodbye(req);
else
response="FAIL,invalid request";
narrate("SAID:",response,client);
ret=sendto(sd,response,strlen(response),0,client,addlen);
if(ret==-1)
oops("SERVER sendto failed");
}
int main()
{
struct sockaddr_in client_addr;
socklen_t addrlen;
char buf[MSGLEN];
int sock,ret;
sock=setup();
while(1)
{
addrlen=sizeof(client_addr);
ret=recvfrom(sock,buf,MSGLEN,0,&client_addr,&addrlen);
if(ret!=-1)
{
buf[ret]='\0';
narrate("GOT:",buf,&client_addr);
handle_request(buf,&client_addr,addrlen);
}
else if(errno!=EINTR)
oops("recvfrom");
}
return 0;
}
lclnt1.c
许可证服务器版本1.0的客户端
#include<stdio.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<netdb.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<stdbool.h>
#include<netinet/in.h>
#define MSGLEN 128
#define SERVER_PORTNUM 2020
#define HOSTLEN 512
#define oops(p) {perror(p);exit(1);}
static int pid=-1;
static int sd=-1;
static struct sockaddr serv_addr;
static socklen_t serv_alen;
static char ticket_buf[128];
static bool have_ticket=false;
int make_dgram_client_socket(char *host,int portnum,struct sockaddr_in *servadd)
{
struct hostent *hp;
int sock_id;
sock_id=socket(PF_INET,SOCK_DGRAM,0);
if(sock_id==-1)
oops("socket");
bzero((void *)servadd,sizeof(struct sockaddr_in));
hp=gethostbyname(host);
if(hp==NULL)
oops(host);
bcopy((void*)hp->h_addr,(void*)&servadd->sin_addr,hp->h_length);
servadd->sin_port=htons(portnum);
servadd->sin_family=AF_INET;
return sock_id;
}
void setup()
{
char hostname[BUFSIZ];
pid=getpid();
gethostname(hostname,HOSTLEN);
sd=make_dgram_client_socket(hostname,SERVER_PORTNUM,&serv_addr);
if(sd==-1)
oops("cannot make socket");
serv_alen=sizeof(serv_addr);
}
void shut_down()
{
close(sd);
}
char *do_transaction(char *msg)
{
static char buf[MSGLEN];
int ret=sendto(sd,msg,strlen(msg),0,&serv_addr,serv_alen);
if(ret==-1)
oops("sendto");
ret=recvfrom(sd,buf,MSGLEN,0,NULL,NULL);
buf[ret]='\0';
if(ret==-1)
oops("recvfrom");
return buf;
}
void narrate(char *msg1,char *msg2)
{
fprintf(stderr,"CLIENT[%d]:%s %s\n",pid,msg1,msg2);
}
syserr(char *msg1)
{
char buf[MSGLEN];
sprintf(buf,"CLIENT[%d]:%s\n",pid,msg1);
perror(buf);
}
int get_ticket()
{
char *response;
char buf[MSGLEN];
if(have_ticket)
return 0;
sprintf(buf,"HELO %d",pid);
//client将HELO pid发给server,server再传回消息
if((response=do_transaction(buf))==NULL)
return -1;
//如果传回来TICK %d.%d,将%d. %d保存至ticket_buf,获得ticket槽位
if(strncmp(response,"TICK",4)==0)
{
strcpy(ticket_buf,response+5);
have_ticket=true;
narrate("got a ticket",ticket_buf);
return 0;
}
//如果传回来FAIL,获取ticket失败
if(strncmp(response,"FAIL",4)==0)
narrate("could not get ticket",response);
else
narrate("unknown message:",response);
return -1;
}
void do_regular_work()
{
printf("SuperSleep version 1.0 Running-Licensed Software\n");
sleep(10);
}
int release_ticket()
{
char buf[MSGLEN];
char *response;
if(!have_ticket)
return 0;
sprintf(buf,"GBYE %s",ticket_buf);
//client睡眠10秒后,发送GBYE %d. %d给server,server再传回消息
if((response=do_transaction(buf))==NULL)
return -1;
//如果传回来THNX,server收回了ticket
if(strncmp(response,"THNX",4)==0)
{
have_ticket=false;
narrate("release ticket OK","");
return 0;
}
//如果传回来FAIL,release ticket失败
else if(strncmp(response,"FAIL",4)==0)
narrate("release failed",response);
else
narrate("unknown message:",response);
return -1;
}
int main()
{
setup();
if(get_ticket()!=0)
exit(0);
do_regular_work();
release_ticket();
shut_down();
return 0;
}
lserv2.c
许可证服务器版本2.0的服务器
#include<stdio.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<netdb.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/errno.h>
#include<signal.h>
#include<sys/socket.h>
#include<stdbool.h>
#include<netinet/in.h>
#define MSGLEN 128
#define SERVER_PORTNUM 2020
#define HOSTLEN 512
#define MAXUSERS 3
#define TICKET_AVAIL 0
#define RECLAIM_INTERVAL 30
#define oops(p) {perror(p);exit(1);}
int ticket_array[MAXUSERS];
int sd=-1;
int num_tickets_out=0;
int make_dgram_server_socket(int portnum)
{
struct sockaddr_in saddr;
char hostname[HOSTLEN];
struct hostent *hp;
int sock_id;
sock_id=socket(PF_INET,SOCK_DGRAM,0);
if(sock_id==-1)
oops("socket");
bzero((void*)&saddr,sizeof(saddr));
gethostname(hostname,HOSTLEN);
hp=gethostbyname(hostname);
bcopy((void*)hp->h_addr,(void*)&saddr.sin_addr,hp->h_length);
saddr.sin_port=htons(portnum);
saddr.sin_family=AF_INET;
if(bind(sock_id,(struct sockaddr *)&saddr,sizeof(saddr))!=0)
oops("bind");
return sock_id;
}
void free_all_tickets()
{
int i;
for(i=0;i<MAXUSERS;i++)
ticket_array[i]=TICKET_AVAIL;
}
int setup()
{
sd=make_dgram_server_socket(SERVER_PORTNUM);
if(sd==-1)
oops("make socket");
free_all_tickets();
return sd;
}
void shut_down()
{
close(sd);
}
void narrate(char *msg1,char *msg2,struct sockaddr_in *clientp)
{
fprintf(stderr,"\t\tSERVER:%s %s",msg1,msg2);
if(clientp)
fprintf(stderr,"(%s:%d)",inet_ntoa(clientp->sin_addr),ntohs(clientp->sin_port));
putc('\n',stderr);
}
//处理客户机崩溃,收回ticket
void ticket_reclaim()
{
int i;
char tick[BUFSIZ];
for(i=0;i<MAXUSERS;i++)
{
if((ticket_array[i]!=TICKET_AVAIL)&&(kill(ticket_array[i],0)==-1)&&(errno==ESRCH))
{
sprintf(tick,"%d.%d",ticket_array[i],i);
narrate("freeing",tick,NULL);
ticket_array[i]=TICKET_AVAIL;
num_tickets_out--;
}
}
alarm(RECLAIM_INTERVAL);
}
char *do_hello(char *msg_p)
{
int x;
static char replybuf[MSGLEN];
if(num_tickets_out>=MAXUSERS)
return "FAIL,no tickets available";
for(x=0;x<MAXUSERS&&ticket_array[x]!=TICKET_AVAIL;x++);
if(x==MAXUSERS)
{
narrate("database corrupt","",NULL);
return "FAIL,database corrupt";
}
ticket_array[x]=atoi(msg_p+5);
sprintf(replybuf,"TICK %d.%d",ticket_array[x],x);
num_tickets_out++;
return replybuf;
}
char *do_goodbye(char *msg_p)
{
int pid,slot;
if((sscanf((msg_p+5),"%d.%d",&pid,&slot)!=2)||(ticket_array[slot]!=pid))
{
narrate("bogus ticket",msg_p+5,NULL);
return "FAIL,invalid ticket";
}
ticket_array[slot]=TICKET_AVAIL;
num_tickets_out--;
return "THNX see you!";
}
char *do_validate(char *msg)
{
int pid,slot;
if((sscanf((msg+5),"%d.%d",&pid,&slot)==2) && (ticket_array[slot]==pid))
{
return "GOOD Valid ticket";
}
narrate("Bogus ticket",msg+5,NULL);
return "FAIL invalid ticket";
}
void handle_request(char *req,struct sockaddr_in *client,socklen_t addlen)
{
char *response;
int ret;
//client发送HELO pid过来,server占位ticket_array同时num_tickets_out++并且发送回TICK %d.%d
if(strncmp(req,"HELO",4)==0)
response=do_hello(req);
//client发送GBYE %d.%d过来,server释放ticket_array同时num_tickets_out--并且发送回THNX
else if(strncmp(req,"GBYE",4)==0)
response=do_goodbye(req);
//client发送VALD %d.%d过来,server验证是否存在并且发送回GOOD或者FAIL
else if(strncmp(req,"VALD",4)==0)
response=do_validate(req);
else
response="FAIL,invalid request";
narrate("SAID:",response,client);
ret=sendto(sd,response,strlen(response),0,client,addlen);
if(ret==-1)
oops("SERVER sendto failed");
}
int main()
{
struct sockaddr_in client_addr;
socklen_t addrlen;
char buf[MSGLEN];
int sock,ret;
unsigned time_left;
sock=setup();
signal(SIGALRM,ticket_reclaim);
alarm(RECLAIM_INTERVAL);
while(1)
{
addrlen=sizeof(client_addr);
ret=recvfrom(sock,buf,MSGLEN,0,&client_addr,&addrlen);
if(ret!=-1)
{
buf[ret]='\0';
narrate("GOT:",buf,&client_addr);
time_left=alarm(0);
handle_request(buf,&client_addr,addrlen);
alarm(time_left);
}
else if(errno!=EINTR)
oops("recvfrom");
}
return 0;
}
llcnt2.c
许可证服务器版本2.0的客户端
#include<stdio.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<netdb.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<stdbool.h>
#include<netinet/in.h>
#define MSGLEN 128
#define SERVER_PORTNUM 2020
#define HOSTLEN 512
#define oops(p) {perror(p);exit(1);}
static int pid=-1;
static int sd=-1;
static struct sockaddr serv_addr;
static socklen_t serv_alen;
static char ticket_buf[128];
static bool have_ticket=false;
int make_dgram_client_socket(char *host,int portnum,struct sockaddr_in *servadd)
{
struct hostent *hp;
int sock_id;
sock_id=socket(PF_INET,SOCK_DGRAM,0);
if(sock_id==-1)
oops("socket");
bzero((void *)servadd,sizeof(struct sockaddr_in));
hp=gethostbyname(host);
if(hp==NULL)
oops(host);
bcopy((void*)hp->h_addr,(void*)&servadd->sin_addr,hp->h_length);
servadd->sin_port=htons(portnum);
servadd->sin_family=AF_INET;
return sock_id;
}
void setup()
{
char hostname[BUFSIZ];
pid=getpid();
gethostname(hostname,HOSTLEN);
sd=make_dgram_client_socket(hostname,SERVER_PORTNUM,&serv_addr);
if(sd==-1)
oops("cannot make socket");
serv_alen=sizeof(serv_addr);
}
void shut_down()
{
close(sd);
}
char *do_transaction(char *msg)
{
static char buf[MSGLEN];
int ret=sendto(sd,msg,strlen(msg),0,&serv_addr,serv_alen);
if(ret==-1)
oops("sendto");
ret=recvfrom(sd,buf,MSGLEN,0,NULL,NULL);
buf[ret]='\0';
if(ret==-1)
oops("recvfrom");
return buf;
}
void narrate(char *msg1,char *msg2)
{
fprintf(stderr,"CLIENT[%d]:%s %s\n",pid,msg1,msg2);
}
syserr(char *msg1)
{
char buf[MSGLEN];
sprintf(buf,"CLIENT[%d]:%s\n",pid,msg1);
perror(buf);
}
int get_ticket()
{
char *response;
char buf[MSGLEN];
if(have_ticket)
return 0;
sprintf(buf,"HELO %d",pid);
//client将HELO pid发给server,server再传回消息
if((response=do_transaction(buf))==NULL)
return -1;
//如果传回来TICK %d.%d,将%d. %d保存至ticket_buf,获得ticket槽位
if(strncmp(response,"TICK",4)==0)
{
strcpy(ticket_buf,response+5);
have_ticket=true;
narrate("got a ticket",ticket_buf);
return 0;
}
if(strncmp(response,"FAIL",4)==0)
narrate("could not get ticket",response);
else
narrate("unknown message:",response);
return -1;
}
void do_regular_work()
{
printf("SuperSleep version 1.0 Running-Licensed Software\n");
sleep(10);
}
int do_validate()
{
char *response;
char buf[MSGLEN];
if(!have_ticket)
return 0;
sprintf(buf,"VALD %s",ticket_buf);
if((response=do_transaction(buf))==NULL)
return -1;
//如果传回来GOOD,验证成功说明服务器没有崩溃
if(strncmp(response,"GOOD",4)==0)
{
narrate("validate success:",response);
return 0;
}
if(strncmp(response,"FAIL",4)==0)
narrate("validate failed:",response);
else
narrate("unknown message:",response);
return -1;
}
int release_ticket()
{
char buf[MSGLEN];
char *response;
if(!have_ticket)
return 0;
sprintf(buf,"GBYE %s",ticket_buf);
//client睡眠10秒后,发送GBYE %d. %d给server,server再传回消息
if((response=do_transaction(buf))==NULL)
return -1;
//如果传回来THNX,server收回了ticket
if(strncmp(response,"THNX",4)==0)
{
have_ticket=false;
narrate("release ticket OK","");
return 0;
}
else if(strncmp(response,"FAIL",4)==0)
narrate("release failed",response);
else
narrate("unknown message:",response);
return -1;
}
int main()
{
setup();
if(get_ticket()!=0)
exit(0);
do_regular_work();
do_validate();
release_ticket();
shut_down();
return 0;
}
logfileserv.c
Unix域socket日志服务器
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/un.h>
#include<string.h>
#include<stdlib.h>
#include<time.h>
#define MSGLEN 512
#define oops(m) {perror(m);exit(1);}
#define SOCKNAME "/tmp/logfilesock"
int main()
{
int sock;
struct sockaddr_un addr;
socklen_t addrlen;
char msg[MSGLEN];
int l;
char sockname[]=SOCKNAME;
time_t now;
int msgnum=0;
char *timestr;
addr.sun_family=AF_UNIX;
strcpy(addr.sun_path,sockname);
addrlen=strlen(sockname)+sizeof(addr.sun_family);
sock=socket(PF_UNIX,SOCK_DGRAM,0);
if(sock==-1)
oops("socket");
if(bind(sock,(struct sockaddr *)&addr,addrlen)==-1)
oops("bind");
while(1)
{
l=read(sock,msg,MSGLEN);
msg[l]='\0';
time(&now);
timestr=ctime(&now);
timestr[strlen(timestr)-1]='\0';
printf("[%d]%s %s\n",msgnum++,timestr,msg);
fflush(stdout);
}
close(sock);
return 0;
}
logfileclnt.c
Unix域socket日志客户端
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/un.h>
#include<string.h>
#include<stdlib.h>
#define oops(m) {perror(m);exit(1);}
#define SOCKNAME "/tmp/logfilesock"
int main(int argc,char *argv[])
{
int sock;
struct sockaddr_un addr;
socklen_t addrlen;
char *msg=argv[1];
char sockname[]=SOCKNAME;
if(argc!=2)
oops("usage:logfileclnt massege\n");
sock=socket(PF_UNIX,SOCK_DGRAM,0);
addr.sun_family=AF_UNIX;
strcpy(addr.sun_path,sockname);
addrlen=strlen(sockname)+sizeof(addr.sun_family);
if(sock==-1)
oops("socket");
if(sendto(sock,msg,strlen(msg),0,(const struct sockaddr *)&addr,addrlen)==-1)
oops("sendto");
close(sock);
return 0;
}