选择功能行为 - 多客户测验

时间:2012-09-13 20:17:48

标签: c sockets networking network-programming application-layer

我必须构建一个测验应用程序。

有关申请的详情:
1.每个客户必须在参加测验之前注册到服务器。服务器会询问 每个用户的用户名,并为每个用户生成临时ID 2.注册过程成功连接到服务器的客户端将获得 来自服务器的问题。
3.客户将回答答案 4.服务器将收到来自不同客户的带时间戳的答案,并将进行计算 每个客户的时差,称为Δt。

    Define such as:  
    ∆t = (Time Question sent - Time answer received) - RTT  
    Where RTT is Round Trip Time
  1. 服务器将选择客户端,其Δt对所有人来说是最小的,并且回答客户端将获得的任何得分,将无法获得任何分数。
  2. 发送问题后,服务器将等待一段特定的时间段(T)。如果客户在“T”时间段内未回复,则服务器将跳过该问题并转到下一个问题。
  3. 我的服务器代码中主循环的伪代码

    A. A main while loop which runs once for each question.  
    B. Inside this first I am accepting login for 10 seconds.  
           Here I am assigning user Id and all other initialization stuff.  
    C. Then using `select` to check which are available for writing.
           To available connections I am checking `RTT` and then sending question to each user.
    D. Then I am waiting for some time to get answers.
           Here I am using `select` to determine where the answer is available to read.
    E. Then I am repeating steps C. and D.
    

    问题
    当我只连接到一个客户端时,我的代码适用于任何数量的问题 但是,当我使用相同的客户端代码在多个客户端上测试此代码时:

    • 登录所有人都可以。
    • 向大家发送第一个问题的工作正常。
    • 然后在等待答案时我只收到一位客户的答复。每个客户端都显示已发送答案。对于第二个客户端,第二个select函数不会返回可读数据可用性。

    为什么多客户端我的代码无效。 (据我说,错误在某处得到答案)。

    我的代码
    可以从变量名称中轻松理解数据包发送的结构。

    Server.c

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <error.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netdb.h>
    #include <arpa/inet.h>
    #include <sys/wait.h>
    #include <signal.h>
    #include <time.h>
    
    #define PORT "3490" //the port user will be connecting to
    #define BACKLOG 10  //how many pending connection queue will hold
    #define maxUser 10
    #define LOGIN_OK "OK"
    #define LOGIN_WrongPassword "NP"
    #define LOGIN_WrongUsername "NU"
    #define MAX_USERS 10
    #define MAX_ANSWER_TIME 10
    #define LOGIN_WAIT 10
    #define TOTAL_QUES "3"
    
    int users[MAX_USERS][3] = {};  //index is userID, 0 is no user
    
    void sigchld_handler(int s)
    {
        while(waitpid(-1, NULL, WNOHANG) > 0);
    }
    
    //get sockaddr, IPv4 or IPv6
    
    int timer;
    void alarm_handler(int s) {
        timer = 0;
    }
    
    wrongRecv(ssize_t recvd, ssize_t expctd)
    {
    
        if(recvd != expctd)
        {
            printf("Recvd(%zd) bytes not equal to expected(%zd) bytes\n",recvd,expctd);
            //getchar();
        }
    }
    
    //void nextQues(char* quesMsg, char* ques, char* optA, char* optB, char* optC, char* optD)
    int nextQues(char* quesMsg, int QID)
    {
        char ques[40], optA[10], optB[10], optC[10], optD[10], quesId[5];
    
        sprintf(quesId,"%d",QID);
        strncpy(ques, "This is the question?",22);
        strncpy(optA, "OptionA", 7);    strncpy(optB, "OptionB", 7);    strncpy(optC, "OptionC", 7);    strncpy(optD, "OptionD", 7);
        strncpy(quesMsg,quesId,5);
        strncpy(quesMsg + 05,ques,40);
        strncpy(quesMsg + 45,optA,10);
        strncpy(quesMsg + 55,optB,10);
        strncpy(quesMsg + 65,optC,10);
        strncpy(quesMsg + 75,optD,10);
    
        return 0;
    }
    
    //void answerCheck(char* ques, char* optA, char* optB, char* optC, char* optD, char* usrResponse, int rtt, int timeTaken)
    void answerCheck(int fd, char usrResponse[6], int rtt, int timeTaken)
    {
        int responseTime, i;
        char actualAnswer[1];
        char quesId[5];
        printf("fd(%d) quesid(%s) response(%c) rtt(%d) timeTaken(%d)\n", fd, usrResponse, usrResponse[5], rtt, timeTaken );
        strncpy(quesId, usrResponse, 5);
        actualAnswer[0] = 'B';//we have quesId we can find actual answer on basis of it
    
        if(actualAnswer[0] == usrResponse[5])
        {
            //printf("%s\n","+++++" );
            responseTime = timeTaken - rtt;
            //printf("Response Time(%d)\n",responseTime);
    
            //save it with user id
    
            //finding userid
            for(i = 0; i < MAX_USERS; i++) {
                if(users[i][1] == fd) {
                    users[i][2] = responseTime;//saving it
                    //printf("%d\n",i );
                }
            }
        }
    }
    
    int compareAnswer() {
        int i, min = 2 * MAX_ANSWER_TIME, userIndex;
        for(i = 0; i < MAX_USERS; i++) {
            if(users[i][2] < min) {
                min = users[i][2];
                userIndex = i;
            }
        }
        //Increasing Score
        users[userIndex][0]++;
    
        //returning fd
        return users[userIndex][1];
    }
    
    void users_deleteFd(int fd) {
        int i;
        for (i = 0; i < MAX_USERS; ++i)
        {
            if(users[i][1] == fd) {
                users[i][1] =0;
                return;
            }
        }
    }
    
    int rtt_check(int new_fd)
    {
        ssize_t send_ret, recv_ret;
        char rtt_check[1];
        time_t rtt1, rtt2;
    
        rtt1 = time(NULL);
        send_ret = send(new_fd, "r", 1, 0);
        if(send_ret == 0)
        {
            return -2;
        }
        wrongRecv(send_ret, 1);
        //printf("%s\n","Between two phase of rttCheck" );
        recv_ret = recv(new_fd, rtt_check, 1,0);
        rtt2 = time(NULL);
        if(recv_ret == 0)
        {
            return -2;
        }
        wrongRecv(recv_ret,1);
        //printf("diff(%d)\n",(int) difftime(rtt2,rtt1));
    
        return  (int) difftime(rtt2,rtt1);
    }
    
    int login(char user[], char pass[])
    {
        //for user
        static int Id = 0; //when have function getUserID, make it not static and also remove Id++;
        if(!strcmp(user,"abhishek") && !strcmp(pass,"abhishek")) {
            //Id = getUserID(user);
            return ++Id;
        }else if(!strcmp(user,"abhishek")){
            return 0; //wrong password
        }
        return -1; //wrong username
    }
    
    int totalQues;
    
    int login_setup(int new_fd)
    {
        //login inititalizations
        char login_det[16];
        char username[9],password[9], login_statMsg[7], totalQuesMsg[5] = TOTAL_QUES;
        totalQues = atoi(totalQuesMsg);
        //for user
        int userId;
    
        //for wrongRecv
        ssize_t send_ret,recv_ret;
    
        //getting username and password
        recv_ret = recv(new_fd,login_det,16,0);
        if(recv_ret == 0)
        {
            return -2;
        }
        wrongRecv(recv_ret,16);
    
        //extracting username nad password
        strncpy(username,login_det,8);  
        strncpy(password,login_det+8,8);
        username[8]='\0'; password[8]='\0';
        //printf("username(%s) and password(%s)\n",username,password);
    
        if( (userId = login(username,password)) > 0) {
            //printf("%d\n",userId);
    
            //sending status
            strncpy(login_statMsg, LOGIN_OK, 2);
            strncpy(login_statMsg + 2, totalQuesMsg , 5);
            send_ret = send(new_fd, login_statMsg,7,0);
            if(send_ret == 0)
            {
                return -2;
            }
            wrongRecv(send_ret,7);
    
            //TODO error checking then handling if error
    
            //users[userId][0] = 0; //score
            users[userId][1] = new_fd; //file descriptor associated with this user
            //users[userId][2] = 0; //answer time
            return 1;
        }
        else if(userId == -1) { //wrong username
            strncpy(login_statMsg, LOGIN_WrongUsername, 2);
            strncpy(login_statMsg + 2, totalQuesMsg , 5);
            send_ret = send(new_fd, login_statMsg,7,0);
            if(send_ret == 0)
            {
                return -2;
            }
            wrongRecv(send_ret,7);
            return 0;
        }
        else{
            strncpy(login_statMsg, LOGIN_WrongPassword, 2);
            strncpy(login_statMsg + 2, totalQuesMsg , 5);
            send_ret = send(new_fd, login_statMsg,7,0);
            if(send_ret == 0)
            {
                return -2;
            }
            wrongRecv(send_ret,7);      
            return 0;
        }
        //TODO erorr handling of above two case
        //TODO make login a loop
    }
    
    
    void *get_in_addr(struct sockaddr *sa)
    {
        if (sa->sa_family == AF_INET) {
            return &(((struct sockaddr_in*)sa)->sin_addr);
        }
    
        return &(((struct sockaddr_in6*)sa)->sin6_addr);
    }
    
    
    int main(void)
    {
        int listen_fd, new_fd; // listen on sock_fd, new connection on new_fd
        struct addrinfo hints, *servinfo, *p;
        struct sockaddr_storage their_addr;//connection's address info
        socklen_t sin_size;
        int yes=1;
        char s[INET6_ADDRSTRLEN];
        int rv;
    
        memset(&hints, 0, sizeof hints);
        hints.ai_family = AF_UNSPEC;//IPv4 or IPv6
        hints.ai_socktype = SOCK_STREAM;
        hints.ai_flags = AI_PASSIVE; // use my IP
    
        if((rv = getaddrinfo(NULL,PORT, &hints, &servinfo)) != 0){ //getting which IPv server supports
            fprintf(stderr, "getaddrinfo: %s\n",gai_strerror(rv));
            return 1;
        }
    
        //loop through all the result and bind to the first we can
        for(p = servinfo; p != NULL; p  = p->ai_next){
            if((listen_fd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1){
                perror("server : socket");
                continue;
            }
    
            if(setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1){
                perror("set sockopt");
                exit(1);
            }
    
            if(bind(listen_fd, p->ai_addr, p->ai_addrlen) == -1){
                close(listen_fd);
                perror("server: bind");
                continue;
            }
    
            break;
        }
    
        if(p == NULL) {
            fprintf(stderr, "server:failed to bind\n");
            return 2;
        }
    
        freeaddrinfo(servinfo);//all done with this structure
    
        if(listen(listen_fd, BACKLOG) == -1){
            perror("listen");
            exit(1);
        }
        //printf("listen_fd(%d)\n",listen_fd );
    
    //  sa.sa_handler = sigchld_handler; // reap all dead processes
    //  sigemptyset(&sa.sa_mask);
    //  sa.sa_flags = SA_RESTART;
    //  if(sigaction(SIGCHLD, &sa, NULL) == -1){
    //      perror("sigaction");
    //      exit(1);
    //  }
    
        printf("server waiting for connections.....\n");
    
        fd_set master; //master file descriptor list
        fd_set read_fds; //temp file descriptor list for select()
        int fdmax;
        FD_ZERO(&master); //clear the master and temp sets
        FD_ZERO(&read_fds);
    
        FD_SET(listen_fd, &master);
    
        //keep track of the bigge file descriptor
        fdmax = listen_fd; // so far it is this one
    
        ssize_t recv_ret, send_ret;
    
    
        //for login
        int loginStatus;
        struct sigaction sa;
        sa.sa_handler = alarm_handler;
        sigemptyset(&sa.sa_mask);
        //sa.sa_flags = SA_RESTART;
        if(sigaction(SIGALRM, &sa, NULL) == -1){
            perror("sigaction");
            exit(1);
        }
    
    
        //login while
        alarm(LOGIN_WAIT);//accepting login only for 10 seconds
        timer = 1;
        printf("\n-----------------------------Waiting for users to login for %d seconds.-----------------------------\n",LOGIN_WAIT);
        while(timer) {
            sin_size = sizeof their_addr;
            new_fd = accept(listen_fd, (struct sockaddr *)&their_addr, &sin_size);
            if(new_fd == -1){
                //perror("accept");
                break;// this break is very important , as we are using alarm(Signals) and accept is a blocking function
                        //If accept is in blocked sate and our signal comes then accept will exit returning error. So
                        //if error then we have to break else next satements will run on falsy values.
                        //In reality we dont need this as I alredy set the SA_RESTART flag in sigaction which means
                        //after returning from the signal handler restart the activity on which you are previously
                        //instead of starting execution from next line.
            }else {
    
                inet_ntop(their_addr.ss_family, get_in_addr((struct sockaddr *)&their_addr), s, sizeof s);
                printf("server : got connection from %s\n", s);
    
                //LOGIN     //need to call login function via thread because this 
                                //may stop the function if user doesnot respond
                loginStatus = login_setup(new_fd);
    
                //adding to select checkup
                if(loginStatus) {
                    printf("User Loginned Succesfully\n");
                }
            }
        }
        printf("-----------------------------Login Closed. Now starting the QUIZ.-----------------------------\n");
    
        //for randome seek
        srand(time(NULL));
    
        //for main loop counter
        int i, win_fd;
    
        //for questions
        int QID = 0;
        int maxQues_Len = 40, maxOpt_len = 10, maxQuesId_len = 5;//including '\0' this time
        char quesMsg[80], answer[6];//score doesnot include \0
        //char ques[40], optA[10], optB[10], optC[10], optD[10];
    
        //for time calculation of each answer
        ssize_t time_ques, time_ans;
    
        //getting all avialable participants
        fdmax = 0;
        FD_ZERO(&master);
        for(i = 0; i < MAX_USERS; i++) {
            if( (new_fd = users[i][1]) != 0){
                FD_SET(new_fd, &master);
                if(new_fd > fdmax)
                    fdmax = new_fd;
                //printf("%d\n",new_fd);
            }
        }
    
        int current_rtt;
        //while for main quiz
        while(totalQues--) {
    
            //checking who are ready for witing
            if(select(fdmax+1, NULL, &master, NULL, NULL) == -1){//here select will return withh all the descriptors which are 
                                                                    //ready to write , all others have to miss this question
                perror("select");
                exit(1);
            }
    
            //setting which question to send
            QID++;
    
            //for sending questions to all
            for(i = 0; i <= fdmax; i++) {
                if(FD_ISSET(i, &master)) {
                    //rtt check
                    current_rtt = rtt_check(i);
                    if(current_rtt == -2) {//connection closed
                        FD_CLR(i, &master);
                        users_deleteFd(i);
                        continue;
                    }
                    //setting question
                    //nextQues(quesMsg, ques, optA, optB, optC, optD);
                    nextQues(quesMsg, QID);
                    printf("Sending Question QID(%s) fd(%d)\n",quesMsg,i);
                    //send a question
                    time_ques = time(NULL);
                    send_ret = send(i, quesMsg, maxQues_Len + 4 * maxOpt_len + maxQuesId_len, 0);
                    if(send_ret == 0) {//connection closed
                        FD_CLR(i, &master);
                        users_deleteFd(i);
                        continue;
                    }
                    wrongRecv(send_ret, maxQues_Len + 4 * maxOpt_len + maxQuesId_len);  
                }
            }
    
            //ASSUMING Question is send ot all the users at same time       
            //receiving and waiting for answers
            alarm(MAX_ANSWER_TIME);
            timer = 1;
            FD_ZERO(&read_fds);
            read_fds = master;
            // unsigned int qq = read_fds.fd_count;
            // for (int ii = 0; ii < qq; ++ii)
            // {
            //  printf("%d\n",read_fds.fd_array[i] );
            // }
            while(timer) {
                //printf("HURRAY\n");
                if(select(fdmax+1, &read_fds, NULL, NULL, NULL) <=0){
                    perror("select");
                    //exit(4);
                    break;//break is important. Explained above
                }
    
                for(i = 0; i <= fdmax; i++) {
                    //printf("Recving answer I(%d)\n",i);
                    if(FD_ISSET(i, &read_fds)) {
                        //receiving answer
                        //TODO if we get answer to wrong ques
                        printf("Recving answer I(%d) fdmax (%d)\n",i,fdmax);
                        recv_ret = recv(i,answer,6,0);
                        time_ans = time(NULL);
                        wrongRecv(recv_ret,6);
                        printf("%s\n",answer );
                        if(recv_ret == 0)//connection closed
                        {
                            FD_CLR(i, &read_fds);
                            FD_CLR(i, &master);
                            users_deleteFd(i);
                            continue;
                        }else if(recv_ret > 0){
                            if(QID == atoi(answer)) { //we have received the answer to this question so remove the user from wait answer loop
                                FD_CLR(i, &read_fds);
                                //printf("%s i(%d)\n","#######",i );
                                answerCheck(i ,answer, current_rtt, (int) difftime(time_ans,time_ques));
                                //printf("Answer(%c)\n",answer[0]);
                            }
                            else{//we have recvd something unexpectable so ignore for NOW
    
                            }
                        }
    
                        //time_t cccc = time(NULL);
                        //printf("%s I(%d)\n",ctime(&cccc),i);
                    }
                }
            }
            //comparing answers
            win_fd = compareAnswer();
            //sending score
        }
        return 0;
    }
    

    Client.c

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <errno.h>
    #include <string.h>
    #include <netdb.h>
    #include <sys/types.h>
    #include <netinet/in.h>
    #include <sys/socket.h>
    #include <arpa/inet.h>
    
    #define PORT "3490" //the port client will be connecting to
    
    #define MAXDATASIZE 100 // max number of bytes we can get at once
    
    //get sockaddr ,IPv4 or IPv6:
    void *get_in_addr(struct sockaddr *sa)
    {
        if(sa->sa_family ==AF_INET) {
            return &(((struct sockaddr_in*)sa)->sin_addr);
        }
    
        return &(((struct sockaddr_in6*)sa)->sin6_addr);
    }
    
    wrongRecv(ssize_t recvd, ssize_t expctd)
    {
    
        if(recvd != expctd)
        {
            printf("Recvd(%zd) bytes not equal to expected(%zd) bytes\n",recvd,expctd);
            getchar();
        }
    }
    
    void rtt_check(int sockfd)
    {
        ssize_t send_ret, recv_ret;
        char rtt_check[1];
        recv_ret = recv(sockfd, rtt_check, 1,0);
        wrongRecv(recv_ret,1);
        sleep(1);//to check
        send_ret = send(sockfd, "r", 1, 0);
        wrongRecv(send_ret, 1);
    
        return;
    }
    
    int main(int argc, char *argv[])
    {
        int sockfd, numbytes;
        char buf[MAXDATASIZE];
        struct addrinfo hints, *servinfo, *p;
        int rv;
        char s[INET6_ADDRSTRLEN];
    
        if(argc != 2) {
            fprintf(stderr,"usage: client hostname\n");
            exit(1);
        }
    
        memset(&hints, 0, sizeof hints);
        hints.ai_family = AF_UNSPEC;
        hints.ai_socktype = SOCK_STREAM;
    
        if((rv = getaddrinfo(argv[1], PORT, &hints, &servinfo)) != 0) {
            fprintf(stderr,"getaddrinfo: %s\n",gai_strerror(rv));
            return 1;
        }
    
        //lopp through all the results and connect to the first we can
        for(p = servinfo; p != NULL; p = p->ai_next) {
            if((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1){
                perror("client: socket");
                continue;
            }
    
            if(connect(sockfd, p->ai_addr, p->ai_addrlen) == -1){
                close(sockfd);
                perror("client: connect");
                continue;
            }
    
            break;
        }
    
        if(p ==NULL) {
            fprintf(stderr,"client: failed to connect\n");
            return 2;
        }
    
        inet_ntop(p->ai_family, get_in_addr((struct sockaddr *)p->ai_addr), s, sizeof s);
        printf("client : connecting to %s\n", s);
    
        freeaddrinfo(servinfo); // all done with this structure
        char login_det[17] = "abhishekabhishek";
        char login_retMsg[7], login_stat[3], totalQuesMsg[5];
        int totalQues;
    
        //sending login details
        ssize_t send_ret,recv_ret;
        send_ret = send(sockfd, login_det,16,0);
        wrongRecv(send_ret,16);
    
        //receiving login status
        recv_ret = recv(sockfd,login_retMsg,7,0);
        wrongRecv(recv_ret,7);
    
        strncpy(login_stat, login_retMsg, 2);
        login_stat[2] = '\0';
        printf("Login Status(%s)\n",login_stat);
        strncpy(totalQuesMsg, login_retMsg + 2, 5);
        totalQues = atoi(totalQuesMsg);
        printf("totalQues(%d)\n",totalQues);
    
        if(!strcmp(login_stat,"OK")) {  //login ok
            char quesId[5];
            int maxQues_Len = 40, maxOpt_len = 10, maxQuesId_len = 5;//including '\0' this time
            char quesMsg[80], scoreMsg[1];//score doesnot include \0
            char ques[40], optA[10], optB[10], optC[10], optD[10];
            char answer[6];
    
            while(totalQues--) {
                //checking rtt
                rtt_check(sockfd);
                //receving question
                recv_ret = recv(sockfd, quesMsg,  maxQues_Len + 4 * maxOpt_len + maxQuesId_len ,0);
                wrongRecv(recv_ret,  maxQues_Len + 4 * maxOpt_len + maxQuesId_len);
                strncpy(quesId,quesMsg,5);
                strncpy(ques, quesMsg + 05, 40);
                strncpy(optA, quesMsg + 45, 10);
                strncpy(optB, quesMsg + 55, 10);
                strncpy(optC, quesMsg + 65, 10);
                strncpy(optD, quesMsg + 75, 10);
                printf("QUESID(%s) Question(%s), A(%s) , B(%s) , C(%s) , D(%s)\n", quesId, ques, optA, optB, optC, optD);
    
                //choose answer
                scoreMsg[0] = 'B';
    
                strncpy(answer,quesId, 5);
                answer[5] = scoreMsg[0];
                sleep(5);
    
                //sending answer
                send_ret = send(sockfd, answer,6,0);
                wrongRecv(send_ret,6);
                printf("%s\n","Answer Message Sent" );
    
    
                // if((numbytes = recv(sockfd, buf, MAXDATASIZE-1, 0)) == -1) {
                //  perror("recv");
                //  exit(1);
                // }
    
                // buf[numbytes] = '\0';
    
                // printf("client: received '%s'\n",buf);
            }
        } 
        //TODO wrong login
        close(sockfd);
        return 0;
    }
    

1 个答案:

答案 0 :(得分:2)

问题是在答案获取循环中调用select正在修改read_fds以仅保留要响应的第一个客户端的文件描述符。由于您在再次致电read_fds之前未重置select,因此无法识别其他客户的回复。