1.xinetd提供被动式的超级服务,也就是服务程序是被使用端所启动,平时则无须存在。例如,ftp, telnetd, pop3,imap, auth等等,这些服务没有人使用时,无须启动。此外,xinetd将socket转换成stdin/stdout,因而使得网路服务程式设计大大简化,您可以只用printf及fgets便可完成处理很复杂的网路协定。
2.一个简单的服务程序 goodie
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
char *cmds[]={
"help",
"say",
"hello",
"bye",
"exit",
NULL
};
int getcmd(char *cmd)
{
int n=0;
while (cmds[n]!=NULL) {
if (strncasecmp(cmd,cmds[n],strlen(cmds[n]))==0) return n;
n++;
}
return -1;
}
void main(void)
{
char buf[1024];
int ok;
printf("Welcome to goodie service! ");
fflush(stdout);
ok=0;
do {
while (fgets(buf,1023,stdin)==NULL);
switch (getcmd(buf)) {
case -1: printf("Unknown command! "); break;
case 0:
printf("How may I help you, sir? ");
int j=0;
while(cmds[j++]!=NULL)printf(” %s\t\t“,cmds[j-1]);
break;
case 1: printf("I will say %s",&buf[3]); break;
case 2: printf("How're you doing today? "); break;
case 3: printf("Si ya, mate! "); ok=1; break;
case 4: printf("Go ahead! "); ok=1; break;
}
fflush(stdout);
} while (!ok);
}
3.配置文件
在/etc/services 中加入如下项
goodie 12345/tcp
服务名是 goodie 服务端口是 12345 服务类型是 TCP
在/etc/xinetd.d目录下新检服务文件 goodie
输入内容
service goodie
{
socket_type=stream
protocal =tcp
wait =no
server = /{goodie dir}/goodir
disable =no #初始化为开启服务监听
}
4.启动服务
先停止服务
killall xinetd
启动服务
/usr/sbin/xinetd -f /etc/xinetd.conf
5.开启终端连接
telnet localhost 12345
------------------------------------------------------------------------------------------------------------------
6.xinetd 的工作原理
通过配置xinetd,然后查看相应的套接字和进程,可以看出,xinetd是这样工作的(针对tcp服务)
A.启动时读取/etc/xinetd.conf文件并为文件中指定的所有服务创建相应的套接字(流或数据报),xinetd能处理的服务的数目依赖于所创建的套接字数目。每个新创建的套接字都被加入到select调用所用到的描述符集中。
B.对每一个套接字调用bind,绑定服务端口(/etc/services中定义),端口号通过调用getservbyname获得。
C.所有套接字建立后,调用select等待它们变为可读,当tcp套接字上有数据报到来时变为可读。xinetd在大部分时间阻塞在select的调用处;
D.xinetd守护进程fork,由子进程处理服务请求;子进程关闭除了要处理的套接字之外的所有描述字,子进程三次调用dup2,把套接字描述字复制到0、1、2,然后关闭原套接字;以后程序对套接字操作就是对0、1、2进行操作;子进程exec执行相应的服务器程序,并将配置文件中的参数传递。
E. 因为tcp服务器通常设置nowait标记,表示xinetd在该套接字上再次选择之前,必须等待在该套接字上服务的子进程终止。所以,父进程中的fork返回时,把子进程的进程号记录下来,这样,在子进程终止时,父进程可以用waitpid的返回值查知是那一个子进程;父进程用FD_CLR宏关闭select使用的描述字集中与这个套接字对应的位,以便不对该套接字select;当子进程终止时,父进程收到一个SIGCHLD信号,父进程的信号处理程序得到终止子进程的进程号,父进程通过打开描述字集中对应的位恢复对该套接字的select。
7.重新实现的XINETD超级守护程序
A.程序利用上面的原理在思路上重新实现了超级守护进程。
B.程序在一些地方只是概念性的代码,完全实现还有待补充。
C.程序能演示XINETD 的功能,但是在注释有BUG 和NOTE 的地方还需改进
D.这是一个总结性的资料,参考了“利用INETD实现UDP守护进程“和“www.douzhe.com“上的文章。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<string.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<errno.h>
#include <sys/ioctl.h>
//为每一个服务分配一个包含socket,及路径的信息结构。
struct param{
int sock; //BIND的套接字
char path[256]; //服务程序 路径
pid_t pid ; //CHILD 进程ID
struct param *next;
}
fd_set readfds;
struct param *phead,*ptail,*p;
/*
响应子进程结束的信号函数;
如果服务中wait=yes;
则bind socket 必须等到子进程结束才能监听下一个请求
*/
void signal_handler(int signum)
{
pid_t pid;
pid= waitpid(-1,NULL,WUNTRACED);
for(p=phead;p!=NULL;p=p->next)
if(p->pid==pid)
{
//FD_SET(p->sock,&readfds);
printf("child exit pid= %d\n",pid);
break;
}
signal(SIGCHLD,signal_handler);
}
int main(int argc,char *argv[])
{
int sock,ss;
int nret;
int sock_len;
struct sockaddr_in addr;
struct sockaddr_in addr_clt;
FILE *file;
fd_set testfds;
sock_len=sizeof(addr);
p=(struct param*)malloc(sizeof(struct param));
ptail=p;
phead=NULL;
p->sock=socket(AF_INET,SOCK_STREAM,0);
// file=fopen("/etc/xinetd.conf","r");
// while(1)//省略从"/etc/xinetd.conf"文件中读每一个服务并启动
{
addr.sin_family=AF_INET;
addr.sin_addr.s_addr=htonl(INADDR_ANY);
addr.sin_port=htons(9734);//从“/etc/service”文件中读服务的端口号 getservbyname;
bind(p->sock,(struct sockaddr *)&addr,sock_len);
listen(p->sock,5);
FD_ZERO(&readfds);
FD_SET(p->sock,&readfds);
strcpy(p->path,"~/socket/");//省略从"/etc/xinetd.conf"文件中读每一个服务的路径
if(phead==NULL)
{
p->next=NULL;
phead=ptail=p;
printf("phead==null\n");
}
else
{
ptail->next=p;
ptail=p;
ptail->next=NULL;
}
printf("path=%s,sock=%d\n",phead->path,phead->sock);
}
while(1)
{
int fd;
int clientfd;
int n;
pid_t pid;
int flag=0;
testfds=readfds;
nret=select(FD_SETSIZE,&testfds,NULL,NULL,NULL);
//if(nret<0){perror(strerror(errno));exit(5);}// bug 1:如果子进程END,会显示“interrupt system call”错误。
for(fd=0;fd<FD_SETSIZE;fd++)
if(FD_ISSET(fd,&readfds))
{ //bug 2:第一次接收到串“sock addr=3.190.0.64,client sock=4”,以后用LOOPBACK 连接返回为"127.0.0.1"
clientfd=accept(fd,(struct sockaddr*)&addr_clt,&n);
printf("sock addr=%s,client sock=%d\n",inet_ntoa(addr_clt.sin_addr),clientfd);
pid=fork();
switch(pid)
{
case -1:
perror("error");
exit(5);
case 0:
//dup2(clientfd,stdin);
//dup2(clientfd,stdout);
//dup2(clientfd,stderr);
//close(clientfd);
for(p=phead;p!=NULL;p=p->next)
if(p->sock==fd)
{ printf("child runing \n");
//execve(); //note 1:未调用SERVER PATH(FILE)
}
sleep(1);
exit(5);
default:// >0
close(clientfd);
if(flag) //note 2: 由service name 的wait 标志决定
FD_CLR(fd,&readfds);
for(p=phead;p!=NULL;p=p->next)
if(p->sock==fd)
{
p->pid=pid;
printf("sock:%d, child pid=%d\n",p->sock,p->pid);
}
signal(SIGCHLD,signal_handler);//note 3:放到开始,还是这儿呢?
break;
}
}
}
}