csocketsnetwork-programmingapplication-layer

Select function behavior - Multi Client Quiz


I have to build a quiz application.

Details about the application:
1. Each client has to register to server before participating in Quiz. The server will ask username from each user and generate temporary id for each user.
2. After Registration process clients, who are successfully connected to server will get Question from server.
3. The client will reply with answer.
4. Server will receive answer from different clients with time stamps, and it will calculate time difference of each client which is called ∆t.

    Define such as:  
    ∆t = (Time Question sent - Time answer received) - RTT  
    Where RTT is Round Trip Time
  1. Server will select client, whose ∆t is minimum to all and reply with whatever score client will gain remains will not gain any score.
  2. After sending Question server will wait Answer for a particular time periods called (T). If client did not reply within ‘T’ time period Server will skip that Question and goes to next Question.

Pseudocode of main loop in my server code

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.

Problem:
When I connect only to a single client my code works fine for any number of question.
But when I test this code on multiple client with the same client code:

Why for multi-client my code is not working. (According to me the error is somewhere in getting answer).

My Code:
The structure of the packets send can be understand easily from the variable names.

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;
}

Solution

  • The problem is that the call to select in the answer getting loop is modifying read_fds to hold just the file descriptor of the first client(s) to respond. Since you don't reset read_fds before calling select again, it will not recognize the other clients' response.