C 服务器客户端 错误的文件描述符

C server client Bad file descriptor

我一直在弄清楚为什么下面的代码给出了一整天的错误描述符。下面是服务端代码,大部分参考了Beej的攻略

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.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 "CountryData.c"

//This function determine if address is IPv4 or IPv6 IP address
void *getAddr_Type(struct sockaddr *sa) {
    if (sa->sa_family == AF_INET) { //If IPv4
        return &(((struct sockaddr_in*)sa)->sin_addr);
    }
    else //if IPv6
    return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

void sigchld_handler(int s)
{
    // waitpid() might overwrite errno, so it is stored in a variable first:
    int saved_errno = errno;

    while(waitpid(-1, NULL, WNOHANG) > 0);

    errno = saved_errno;
}


int main(void){

    readData(); //Read from CountryData.c

    int status, sockfd, client_sockfd;
    int pid; //fork return value

    char buffer[1000];
    int bytecount;

    struct addrinfo hints, *res, *serverInfo; //res points to linked list of "struct addrinfo"; serverInfo also points to linked list of "struct addrinfo" for use in for loop.
    struct sockaddr_storage client_addr; //Address information of the client
    struct sigaction sa;

    socklen_t address_size; //Initialize size of address

    char i[INET6_ADDRSTRLEN]; //INET6_ADDRSTRLEN macro is used to store maximum length of IPv6. Since IPv4 is definitely shorter than IPv6, "INET_ADDRSTRLEN" is not used.


    memset(&hints, 0, sizeof(hints));  //emptying the structure

    //Pass in value into "addrinfo" struct
    hints.ai_family = AF_INET; //Using IPv4
    hints.ai_socktype = SOCK_STREAM; //Using TCP
    hints.ai_flags = AI_PASSIVE; //AI_PASSIVE = Own IP address

    status = getaddrinfo(NULL, "8888", &hints, &serverInfo); //Initialising status return value and also passing in values to getaddrinfo().
//IP address is set to null. This will be filled in automatically by AI_PASSIVE.

    if (status != 0) {
        fprintf(stderr, "Error: %s\n", gai_strerror(status));//gai_strerror to print human readable error
        exit(1); 
    }


    //Loop through all results and bind to the first
    for (res = serverInfo; res != NULL; res = res->ai_next) {

         //(1)Initializing socket 
        if ((sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) == -1) { //Show error message if initializing socket file descriptor fails
            perror("Socket"); 
            continue;
        }

        int optValue=1;

        if ((setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optValue, sizeof(int))) == -1) {
            perror("Socket options");
            exit(1);
        } //(2)setting socket options. "SO_REUSEADDR" to prevent "Address already in use" and to allow reuse of the port

        if ((bind(sockfd, res->ai_addr, res->ai_addrlen)) == -1) { //(3) Binding to local address and port
            close(sockfd);
            perror("Bind");
            continue;
        }

    break;
    }
    freeaddrinfo(serverInfo); //Freeing "serverInfo" linked list


    //However, if linked list is still empty, print error.
    if (res == NULL) {
        fprintf(stderr, "Error! Server is unable to bind.\n");
        exit(1);
    }

    //If unable to listen, print error.
    if ((listen(sockfd, 8)) == -1) { //(4) Listen for client connections, maximum of 8 waiting in queue
        perror("Listen");
    }

    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("Running server program 'server' ... \n\n\nCountry Directory Server Started! PID: %d\n", getpid());



    for(;;) //infinite loop for server to wait for client requests
    { 
        memset(buffer, 0, 1000);
        address_size = sizeof(client_addr);
        client_sockfd = accept(sockfd, (struct sockaddr *)&client_addr, &address_size);

        if (client_sockfd == -1) {
            perror("Accept");
            close(client_sockfd);
            exit(1);
        }

        inet_ntop(client_addr.ss_family, getAddr_Type((struct sockaddr *)&client_addr), i, sizeof(i));//retrieving IP address. "inet_ntop" is used for IPv6 compatibility.  


        printf("-------------------------------------------------------\n");
        printf("Connection received from:       %s\n\n", i);


        if ((pid = fork()) == -1){ //Starts forking
            perror("Failed to fork");
            close(sockfd);
        }

        else if (pid == 0){ //child process

            close(sockfd);//Child doesn't need this socket
            memset(buffer, 0, 1000); //clear the buffer

            if ((bytecount = recv(client_sockfd, buffer, 1000, 0)) == -1){//Receiving Client's input
                perror("Server unable to receive");
                close(client_sockfd);
                exit(0);
            }
            else if ((strcasecmp(buffer, "END")) == 0){ //Nested If-statement; If client sends "end"
                close(client_sockfd);
                exit(0);
                break;
            }

            else if (bytecount == 0) { //If "recv" returns 0, client has closed the connection
                printf("Client (%d) has closed the connection.\n", getpid());
                close(client_sockfd);
                exit(0);
                break;
            }else {
            printf("%s", buffer);
            printf("%d", client_sockfd);
            }
        }

    close(client_sockfd);

    }   //end of infinite while loop

}//End of main function

它成功读取了客户端的输入并在第一个 for(;;) 循环中将其打印在屏幕上。第二次迭代后,它显示 Bad file descriptor 以下是在客户端输入 Hi 后服务器终端的输出。

Johnny$ server
Running server program 'server' ... 


Country Directory Server Started! PID: 18386
-------------------------------------------------------
Connection received from:       127.0.0.1

Accept: Bad file descriptor
Hi4

数字4正在打印出子文件描述符的return值。这意味着循环 运行 一次,然后 return 出错。我的预期输出只是继续监听客户端的输入,服务器应该不断吐出客户端输入的内容。 我是这个服务器的新手,真的很头疼让它工作。任何帮助将不胜感激。

下面是客户端的代码,如果你感兴趣的话。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h> 
#include <arpa/inet.h>
#include <errno.h>

void welcome()
{
    printf("\n+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n");
    printf("        Welcome to the Country Info Directory Service!         \n");
    printf("       ----------------------------------------------          \n");
    printf("Usage :\n\n");
    printf("1) At the '>' prompt, type in the name of the country\n");
    printf("   you wish to search\n\n");
    printf("2) To end program, type in 'end'\n");
    printf("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n\n");
}

//This function determine if address is IPv4 or IPv6 IP address
void *getAddr_Type(struct sockaddr *sa) {
    if (sa->sa_family == AF_INET) { //If IPv4
        return &(((struct sockaddr_in*)sa)->sin_addr);
    }
    else //if IPv6
    return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

int main(int argc, char *argv[]) {
    int sockfd, numOfBytes;
    int retrieveInfo;
    char buf[100];
    struct addrinfo hints, *res, *serverInfo;

    char i[INET6_ADDRSTRLEN]; //INET6_ADDRSTRLEN macro is used to store maximum length of IPv6. Since IPv4 is definitely shorter than IPv6, "INET_ADDRSTRLEN" is not used.

    if (argc != 2){
        printf("Please enter in this format:\n'client':server-host-name\nFor example, if your hostname is vmwubuntu, please type:\nclient vmwubuntu [Enter]\n\n");
        exit(1);
    }

    welcome();

    memset(&hints, 0, sizeof(hints));  //emptying the structure

    //Pass in value into "addrinfo" struct
    hints.ai_family = AF_INET; //Using IPv4
    hints.ai_socktype = SOCK_STREAM; //Using TCP

    retrieveInfo = getaddrinfo(argv[1], "8888", &hints, &serverInfo); 

    if(retrieveInfo != 0) {
        printf("Fail to retrieve address!");
    }

    //Loop through all results and bind to the first
    for (res = serverInfo; res != NULL; res = res->ai_next) {

        sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); //(1)Initializing socket 

        if (sockfd == -1) { //Show error message if initializing socket file descriptor fails
            perror("Socket"); 
            continue;
        }

        if (connect(sockfd, res->ai_addr, res->ai_addrlen) == -1) { //Retrieving values from "struct addrinfo" through "res" pointer
            //close(sockfd);
            perror("Connection");
            continue;
        }
        break;
    }

    if (res == NULL) {
    printf("Failed to connect to server\n");
    exit(1);
    }

    char input[1000];
    char receive[1000];

    for(;;)
    {   
        memset(input, '[=12=]', 1000); //Initialize buffer size to store user input
        printf("Enter Country > ");
        fgets(input, 1000, stdin); //Take in user input with 1000 as the buffer size
        input[strlen(input) - 1] = '[=12=]'; //Stripping the null terminator away

        if (strcasecmp(input, "END") == 0){ //If user enters "end"(case is ignored), close the file descriptor and exit
            close(sockfd);
            exit(0);
        }

        else {//SEND
            if((numOfBytes = send(sockfd, input, strlen(input), 0)) == -1){ //start of nested if statement
                perror("Unable to send");
                exit(1);
            }
            else if (numOfBytes != strlen(input)){ //If string is not sent in full
                perror("Send");
                close(sockfd);
                exit(1);
            }else{//for testing purposes
            printf("%d\n",strlen(input));//for testing purpose
            printf("%d\n", numOfBytes); //for testing purpose
            }//End of nested if statement

        }

    }//End of for infinite loop
} //End of main()

您的子进程似乎没有退出,而是继续执行与父进程相同的代码。然后,您尝试使用已关闭的文件描述符调用 accept

这就是为什么我总是将子代码放在它自己的函数中,并且总是紧随其后调用 _exit()。请注意,我使用 _exit() 而不是 exit() 来确保没有父 atexit 处理程序被执行。

此外,在您的日志消息中包含 PID 也很有帮助。尝试使用这样的东西:

#define INFO(fmt, ...)    fprintf(stderr, "[%d] %s" fmt, getpid(), __FUNCTION__, __VA_ARGS__)

...
INFO("x=%d\n", x);

服务器端的子进程会尝试接受同一个fd。 如何在

之前添加一个无限循环
 else if (pid == 0){ //child process

        close(sockfd);//Child doesn't need this socket
        memset(buffer, 0, 1000); //clear the buffer

        for (;;) {
        if ((bytecount = recv(client_sockfd, buffer, 1000, 0)) == -1){//Receiving Client's input
            perror("Server unable to receive");
            close(client_sockfd);
            exit(0);
        }
        else if ((strcasecmp(buffer, "END")) == 0){ //Nested If-statement; If client sends "end"
            close(client_sockfd);
            exit(0);
            break;
        }

        else if (bytecount == 0) { //If "recv" returns 0, client has closed the connection
            printf("Client (%d) has closed the connection.\n", getpid());
            close(client_sockfd);
            exit(0);
            break;
        }else {
        printf("%s", buffer);
        printf("%d", client_sockfd);
        }

        }
    }