/*
*两个线程一个负责监听客户端,一个负责读客户端请求。 服务器模型,
*主控线程负责accept监听链接的客户端,
*把客户端fd放入任务队列中(),分离子线程则从任务队列取出所有的
*客户端描述加入select并处理客户端读请求。
*13:06:22
*/
#include<stdio.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/select.h>
#include<sys/time.h>
#include<pthread.h>
#include<memory.h>
#include<errno.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<signal.h>
#include<semaphore.h>
#include<malloc.h>
#include<fcntl.h>
typedef unsigned int uint32;
pthread_mutex_t lock;//同步主线程跟select线程操作任务队列
int* queue=NULL;
uint32 qcount=0;//任务队列有多少个任务
uint32 qcapty=4;//任务队列容量
/*
 *
 *MYLOCK 用于同步accept select两个线程,如果select线程先主线程准备好  *socket启动,那么select 就会报段错误。所以必须在accept准备好后select  *在启动,否则一直阻塞。
 *
 */
typedef struct{
    int flag;//如果select子线程先执行到select函数会报错,所以必须等到主控线程accept监听。当主线程执行到accept通知子线程否则子线程阻塞。信号灯
    sem_t sem;
}MYLOCK;
struct sockaddr_in server;
void deleQueue(int*);
void printerror()
{
    printf("%d:%s\n",errno,strerror(errno));
    deleQueue(queue);
    exit(-1);
}
void addSet(fd_set* set,char* p){
    FD_ZERO(set);
    int i=0;
    for(;i<qcapty;i++){//必须全部检索,因为队列fd不是顺序排列,可能有空位。
        int tem=*(queue+i);
        if(tem!=0){
            FD_SET(tem,set);
    //        if(p==NULL)
    //        printf("addset: fd=%d\n",tem);
        }
    }
}
void initQueue(int **p,int n){
    *p=(int*)malloc(sizeof(int)*n);
    memset(*p,0,sizeof(int)*n);
    pthread_mutex_init(&lock,0);
}
void deleQueue(int* p){
    free(p);
}
int getMax(int* p,int num){//取得队列的最大描述符
    pthread_mutex_lock(&lock);
    int i=0;
    int tem=0;
    for(;i<num;i++){
        if(tem<*(p+i)){
            tem=*(p+i);
        }
    }
    pthread_mutex_unlock(&lock);
    return tem;
}
void enlargeQueue(int **pqueue,uint32* oldcapty){//当任务队列不能容下任务的话,要扩容,释放掉以前的队列,新建队列并考数据。初始16每次左移1位,扩大一倍。
        uint32 old=*oldcapty;
        *oldcapty=(*oldcapty)<<1;
    int* nice=(int*)malloc((*oldcapty)*sizeof(int));
    memset(nice,0,sizeof(int)*(*oldcapty));
    memcpy(nice,*pqueue,sizeof(int)*old);
    free(*pqueue);
    *pqueue=nice;
}
void addQueue(int value){
    pthread_mutex_lock(&lock);
    int i=0;
    if(qcapty-qcount<2){
        enlargeQueue(&queue,&qcapty);
        printf("kong rong  le have %d client , capty=%d\n",qcount,qcapty);
    }
    for(;i<qcapty;i++){
        if(*(queue+i)==0){
            *(queue+i)=value;
            qcount++;
//            printf("have fd num = %u\n",qcount);
            break;
        }
    }
    pthread_mutex_unlock(&lock);
}
int getQueue(int dex){
    pthread_mutex_lock(&lock);
    int tem= *(queue+(dex-1));
    pthread_mutex_unlock(&lock);
    return tem;
}
void removeQueue(int value){
    pthread_mutex_lock(&lock);
     int i=0;
        for(;i<qcapty;i++){
            if(*(queue+i)==value){
                *(queue+i)=0;
                qcount--;
            }
        }
    pthread_mutex_unlock(&lock);
}
    fd_set set;
    MYLOCK mlock;
void* th_hand(void* p){
    sem_wait(&mlock.sem);
    while(!mlock.flag){//等主控线程执行到accept并通知子线程执行。
        sem_post(&mlock.sem);
        sleep(1);
        sem_wait(&mlock.sem);
    }
    sem_post(&mlock.sem);
    printf("sub before select\n");    
    while(1){
        addSet(&set,NULL);
        struct timeval timeout={1,0};
        int s=select(getMax(queue,qcapty)+1,&set,NULL,NULL,&timeout);
        if(s<0){
            printerror();
        }else if(s==0){
            continue;    
        }else{
            int i=0;
            for(;i<qcapty;i++){
                int readfd=*(queue+i);
                if(FD_ISSET(readfd,&set)){
                    char buff[40]={0};
                    printf("go read fd %d\n",readfd);
                    int rn=read(readfd,buff,sizeof(buff)-1);
                    if(rn==0){
                        struct sockaddr_in client;
                        memset(&client,0,sizeof(client));
                        int len=sizeof(client);
                        getpeername(readfd,(struct sockaddr*)&client,&len);
//                        printf("%s is closed\n",inet_ntoa(client.sin_addr));
                        removeQueue(readfd);
//                        printf("move fd %d\n",readfd);
                        
                /*        int i=0;
                        for(;i<qcapty;i++){
                            int tem=*(queue+i);
                                printf("close : fd=%d\n",tem);
                            }
                */
                        close(readfd);
                    }else if(rn==-1){
                        printf("read  fd error%d %d\n",readfd,rn);
                        printerror();
                    }else if(rn>0){
                        int wr=write(STDOUT_FILENO,buff,rn);
                        }
                    }
                }
            }
        }        
    }
int initSocket(int port){
    memset(&server,0,sizeof(server));
    server.sin_family=AF_INET;
    server.sin_addr.s_addr=htonl(INADDR_ANY);
    server.sin_port=htons(port);
    int sockfd=socket(AF_INET,SOCK_STREAM,0);
    if(sockfd==-1){
        printerror();
    }
    int res=bind(sockfd,(struct sockaddr*)&server,sizeof(struct sockaddr));
    if(res==-1){
        printerror();
    }
    if(-1==listen(sockfd,10)){
        printerror();    
    }
   
    
    sem_wait(&mlock.sem);
        mlock.flag=1;//通知子线程可以从队列取任务添加到select
    sem_post(&mlock.sem);
    printf("main before accept\n");    
    while(1){
        int fd;
        if((fd=accept(sockfd,NULL,NULL))==-1){
            printerror();
        }
        printf("%d non is unblock\n",O_NONBLOCK);
        addQueue(fd);//当有客户端连接,加入到任务队列中。
    }
}
void sig_hand(int signo){
    if(signo==SIGINT){
        printf("have %d client\n",qcount);
        deleQueue(queue);
        exit(0);
    }
}
pthread_t pid;
int main(int argc,char** argv){
    if(argc<2){
        puts("please input port\n");
        exit(-1);
    }
    int port=atoi(argv[1]);
    signal(SIGINT,sig_hand);
    memset(&mlock,0,sizeof(mlock));
    sem_init(&mlock.sem,0,1);
    mlock.flag=0;
    initQueue(&queue,qcapty);
    pthread_create(&pid,NULL,th_hand,(void*)0);
    pthread_detach(pid);
    initSocket(port);        
}