C服务器客户端坏文件描述符

时间:2016-08-22 09:17:43

标签: c server client 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正在打印出子文件描述符的返回值。这意味着循环运行一次,然后返回错误。我的预期输出只是继续监听客户端的输入,服务器应该不断吐出客户输入的内容。 我是这个服务器的新手,我真的很头疼让这个工作。任何帮助将不胜感激。

以下是客户的代码,如果您有兴趣的话。

#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, '\0', 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] = '\0'; //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()

2 个答案:

答案 0 :(得分:3)

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

这就是为什么我总是将子代码放在它自己的函数中,并且总是紧跟着_exit()。请注意,我使用_exit()代替exit()来确保不会执行父atexit个处理程序。

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

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

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

答案 1 :(得分:1)

服务器端的子进程将尝试接受相同的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);
        }

        }
    }