#include    <stdio.h>
#include    <malloc.h>
#include    <string.h>
#include    <errno.h>
#include    <signal.h>
#include    <sys/types.h>
#include    <sys/socket.h>
#include    <netinet/in.h>
#include    <netdb.h>

#define DB(x)   x

int     Soc=NULL;
int     Acc=NULL;
void    MainLoop();
void    CloseSocket();
void    CloseChild();

void main(int argc,char *argv[])
{
    InitSocket();
    InitSignal();
    MainLoop();
}

int InitSocket()
{
char    hostname[MAXHOSTNAMELEN];
struct hostent  *myhost;
struct servent  *se;
struct sockaddr_in      me;
int     opt;

    DB(fprintf(stderr,"InitSocket\n"));

    /* ホスト名の取得 */
    if(gethostname(hostname,MAXHOSTNAMELEN)== -1){
        perror("gethostname");
        exit(-1);
    }
    DB(fprintf(stderr,"hostname=%s\n",hostname));

    /* サービス情報取得 */
    if((se=getservbyname("TestServer","tcp"))==NULL){
        perror("getservbyname");
        exit(-1);
    }
    DB(fprintf(stderr,"getservbyname:OK\n"));

    /* ホスト情報取得 */
    if((myhost=gethostbyname(hostname))==NULL){
        perror("gethostbyname");
        exit(-1);
    }
    DB(fprintf(stderr,"gethostbyname:OK\n"));

    /* ソケット生成 */
    if((Soc=socket(AF_INET,SOCK_STREAM,0))<0){
        perror("socket");
        exit(-1);
    }
    DB(fprintf(stderr,"socket:OK\n"));

    /* ソケットオプション設定 */
    opt=1;
    if(setsockopt(Soc,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(int))!=0){
        perror("setsockopt");
        exit(-1);
    }
    DB(fprintf(stderr,"setsockopt:OK\n"));

    /* バインド */
    memset((char *)&me,0,sizeof(me));
    me.sin_family=AF_INET;
    me.sin_port=se->s_port;
    if(bind(Soc,&me,sizeof(me))== -1){
        perror("bind");
        exit(-1);
    }
    DB(fprintf(stderr,"bind:OK\n"));

    return(0);
}

void CloseSocket()
{
    DB(fprintf(stderr,"CloseSocket\n"));

    if(Soc!=NULL){
        close(Soc);
        DB(fprintf(stderr,"close(Soc)\n"));
        Soc=NULL;
    }

    if(Acc!=NULL){
        close(Acc);
        DB(fprintf(stderr,"close(Acc)\n"));
        Acc=NULL;
    }

    DB(fprintf(stderr,"CloseSocket:exit\n"));

    exit(0);
}

void CloseChild()
{
int    pid;

    DB(fprintf(stderr,"CloseChild\n"));
    pid=wait(0);
    DB(fprintf(stderr,"wait:pid=%d:end\n",pid));
    signal(SIGCLD,CloseChild);
}

int InitSignal()
{
    DB(fprintf(stderr,"InitSignal\n"));
    signal(SIGINT,CloseSocket);
    signal(SIGTERM,CloseSocket);
    signal(SIGCLD,CloseChild);
    return(0);
}

void MainLoop()
{
struct sockaddr_in      from;
int     len,width,pid;
fd_set  readOk,Mask;
struct timeval  timeout;
char    client[64];

    DB(fprintf(stderr,"MainLoop\n"));

    width=0;
    /* マスクの設定 */
    FD_ZERO(&Mask);
    FD_SET(Soc,&Mask);
    FD_SET(0,&Mask);
    width=Soc+1;

    /* 接続要求受付準備 */
    listen(Soc,SOMAXCONN);
    DB(fprintf(stderr,"losten:OK\n"));

    /* 処理ループ */
    while(1){
        readOk=Mask;
        timeout.tv_sec=0;
        timeout.tv_usec=100000;
        switch(select(width,(fd_set *)&readOk,NULL,NULL,&timeout)){
            case    -1:
                perror("select");
                break;
            case    0:
                break;
            default:
                if(FD_ISSET(Soc,&readOk)){    /* ソケットレディ */
                    len=sizeof(from);
                    Acc=accept(Soc,&from,&len);    /* 接続受付 */
                    if(Acc<0){
                        if(errno==EINTR){
                            continue;
                        }
                        perror("accept");
                        continue;
                    }
                    sprintf(client,"%d.%d.%d.%d",
                        from.sin_addr.S_un.S_un_b.s_b1,
                        from.sin_addr.S_un.S_un_b.s_b2,
                        from.sin_addr.S_un.S_un_b.s_b3,
                        from.sin_addr.S_un.S_un_b.s_b4);
                    DB(fprintf(stderr,"accept:%s\n",client));
                    if((pid=fork())==0){    /* 子プロセス生成 */
                        close(Soc);
                        Soc=NULL;
                        StartChild(client);
                        DB(fprintf(stderr,"MainLoop:exit\n"));
                        exit(0);
                    }
                    DB(fprintf(stderr,"pid=%d\n",pid));
                    setpgrp(pid,getpid());
                    close(Acc);
                    Acc=NULL;
                }
                if(FD_ISSET(0,&readOk)){    /* 標準入力レディ */
                    KeyIn();
                }
                break;
        }
    }
}

int KeyIn()
{
char    buf[512];

    fgets(buf,sizeof(buf)-1,stdin);
    DoKeyInCommand(buf);
    return(0);
}

int DoKeyInCommand(char *str)
{
    DB(fprintf(stderr,"(%s)\n",str));
    if(strcmp(str,"end")==0){
        killpg(getpid(),SIGTERM);
        CloseSocket();
    }
    return(0);
}

int StartChild(char *client)
{
char    buf[8192],name[80],passwd[80];
int     ret,level;

    DB(fprintf(stderr,"StartChild\n"));
    /* 初期メッセージ */
    sprintf(buf,"Server Ready.\n");
    send(Acc,buf,strlen(buf),0);

    while(1){
        ret=recv(Acc,buf,sizeof(buf),0);    /* 受信 */
        if(ret== -1){
            perror("recv");
            break;
        }
        buf[ret]='\0';
        if(DoCommand(buf)== -1){
            /* end */
            break;
        }
    }

    DB(fprintf(stderr,"StartChild:end\n"));
    return(0);
}

int DoCommand(char *com)
{
int     ret;
char    buf[512];

    DB(fprintf(stderr,"[%s]\n",com));

    if(strncmp(com,"end",strlen("end"))==0){
        ret= -1;
    }
    else{
        sprintf(buf,"受け取ったメッセージ:[%s]\n",com);
        send(Acc,buf,strlen(buf),0);    /* 送信 */
        ret=0;
    }

    return(ret);
}
