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

标签 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”时间内回复,服务器将跳过该问题并转到下一个问题。

我的服务器代码中主循环的伪代码

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 函数不会返回可读数据可用性。

为什么对于多客户端我的代码不起作用。 (根据我的说法,错误是在获取答案的某个地方)。

我的代码:
从变量名称中可以很容易地理解发送的数据包的结构。

服务器.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;
}

最佳答案

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

关于c - 选择功能行为 - 多客户端测验,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12414138/

相关文章:

iphone - 如何在 VoIP 通话开始前确定其质量?

保存宏定义的C字符串

c - 使用 **ptr 将多维数组传递给函数

c 套接字文件传输

javascript - Socket.io 如何使函数 io() 匿名?

linux - 有什么方法可以区分具有相同 MAC 地址的两台 Linux 机器吗?

c - 使用一维数组对 C 中的矩阵进行转置

c - 在下面的代码中, "average/=5.0"是什么意思?

c - 为什么Tracker服务器发送 "\x03\0\0\0r.opConnection ID missmatch."作为响应消息? (比特流协议(protocol))

python - 在 Python 中 HTTP GET 的最快方法是什么?