如何处理套接字服务器中select()引起的“坏文件描述符”?

时间:2016-12-13 21:48:32

标签: c sockets unix select

我使用

编译我的程序
all:
    gcc server.c -o server
    gcc file_reader.c -o file_reader

编译完成后,我在终端输入“./server [port_num]”。

初始化服务器后,我可以在浏览器上输入一些内容:“http://127.0.0.1:[port_num]/cgi_program?filename=[filename]

然后我的CGI(file_reader)会正确地将名为“filename”的文件内容转储到我输入的浏览器中。

我在这里发布了我的所有代码,抱歉很长,因为有很多函数可以调用,你可以跳过并假设以下函数是正确的。

问题仍然存在:错误消息“select:Bad file descriptor”,只能从一个人那里读取。

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

#define TIMEOUT_SEC 5       // timeout in seconds for wait for a connection
#define MAXBUFSIZE  1024    // timeout in seconds for wait for a connection
#define NO_USE      0       // status of a http request
#define ERROR       -1
#define READING     1
#define WRITING     2
#define ERR_EXIT(a) { perror(a); exit(1); }

typedef struct {
    char hostname[512];     // hostname
    unsigned short port;    // port to listen
    int listen_fd;          // fd to wait for a new connection
} http_server;

typedef struct {
    int conn_fd;               // fd to talk with client
    int status;                // not used, error, reading (from client), writing (to client)

    char file[MAXBUFSIZE];     // requested file
    char query[MAXBUFSIZE];    // requested query
    char host[MAXBUFSIZE];     // client host
    char* buf;                 // data sent by/to client
    size_t buf_len;            // bytes used by buf
    size_t buf_size;           // bytes allocated for buf
    size_t buf_idx;            // offset for reading and writing
} http_request;

static char* logfilenameP;  // log file name

static void init_http_server(http_server *svrP, unsigned short port);   // initailize a http_request instance, exit for error
static void init_request(http_request* reqP);                           // initailize a http_request instance
static void free_request(http_request* reqP);                           // free resources used by a http_request instance
static int read_header_and_file(http_request* reqP, int *errP);
// return 0: success, file is buffered in retP->buf with retP->buf_len bytes
// return -1: error, check error code (*errP)
// return 1: continue to it until return -1 or 0
// error code:
// 1: client connection error
// 2: bad request, cannot parse request
// 3: method not implemented
// 4: illegal filename
// 5: illegal query
// 6: file not found
// 7: file is protected

static void set_ndelay(int fd);
// Set NDELAY mode on a socket.

int main(int argc, char **argv) {
    http_server server;            // http server
    http_request* requestP = NULL; // pointer to http requests from client

    int maxfd;                     // size of open file descriptor table

    struct sockaddr_in cliaddr;    // used by accept()
    int clilen;

    int conn_fd;        // fd for a new connection with client
    int err;            // used by read_header_and_file()
    int i, ret, nwritten;

    // Initialize http server
    init_http_server(&server, (unsigned short) atoi(argv[1]));

    maxfd = getdtablesize();
    requestP = (http_request*) malloc(sizeof(http_request) * maxfd);
    if (requestP == (http_request*) 0) {
        fprintf(stderr, "out of memory allocating all http requests\n");
        exit(1);
    }
    for (i = 0; i < maxfd; i ++)
        init_request(&requestP[i]);
    requestP[server.listen_fd].conn_fd = server.listen_fd;
    requestP[server.listen_fd].status = READING;

    fprintf(stderr, "\nstarting on %.80s, port %d, fd %d, maxconn %d, logfile %s...\n", server.hostname, server.port, server.listen_fd, maxfd, logfilenameP);

    fd_set master;          /* master file descriptor list */
    fd_set read_fds;        /* temp file descriptor list for select() */
    FD_SET(server.listen_fd, &master);

    int fdmax = server.listen_fd;
    while (1) {                             /* Main loop */
        read_fds = master;

        if (select(fdmax + 1, &read_fds, NULL, NULL, NULL) == -1)
            ERR_EXIT("select")
            printf("server select() is OK!\n");

        for (i = 0; i < fdmax + 1; i++) {
            if (FD_ISSET(i, &read_fds)) {
                if (i == server.listen_fd) {
                    clilen = sizeof(cliaddr);
                    conn_fd = accept(server.listen_fd, (struct sockaddr *) &cliaddr, (socklen_t *) &clilen);
                    if (conn_fd < 0) {
                        if (errno == EINTR || errno == EAGAIN) continue; // try again
                        if (errno == ENFILE) {
                            (void) fprintf(stderr, "out of file descriptor table ... (maxconn %d)\n", maxfd);
                            continue;
                        }
                        ERR_EXIT("accept")
                    }
                    requestP[conn_fd].conn_fd = conn_fd;
                    requestP[conn_fd].status = READING;
                    strcpy(requestP[conn_fd].host, inet_ntoa(cliaddr.sin_addr));
                    set_ndelay(conn_fd);
                    FD_SET(conn_fd, &master);
                    if (conn_fd > fdmax)
                        fdmax = conn_fd;
                    fprintf(stderr, "getting a new request... fd %d from %s\n", conn_fd, requestP[conn_fd].host);
                }
                else {      /* Handle data from a client */
                    ret = read_header_and_file(&requestP[i], &err);
                    if (ret == 1) continue;
                    else if (ret < 0) {
                        fprintf(stderr, "error on fd %d, code %d\n", requestP[i].conn_fd, err);
                        requestP[i].status = ERROR;
                        close(requestP[i].conn_fd);
                        free_request(&requestP[i]);
                        break;
                    }
                    else if (ret == 0) {
                        fprintf(stderr, "writing (buf %s, idx %d) %d bytes to request fd %d\n", requestP[conn_fd].buf, (int) requestP[conn_fd].buf_idx, (int) requestP[conn_fd].buf_len, requestP[conn_fd].conn_fd);
                        nwritten = write(requestP[conn_fd].conn_fd, requestP[conn_fd].buf, requestP[conn_fd].buf_len);
                        fprintf(stderr, "complete writing %d bytes on fd %d\n", nwritten, requestP[conn_fd].conn_fd);
                        fprintf(stderr, "=============================================\n");

                        // char *m = strchr(requestP[conn_fd].query, '=') + 1;
                        // char *filename = strncpy(requestP[conn_fd].query, m, sizeof(requestP[conn_fd].query));

                        int fd[2];
                        if (pipe(fd) == -1)
                            ERR_EXIT("pipe")

                            pid_t pid;
                        if ((pid = fork()) < 0) {
                            ERR_EXIT("fork")
                        }
                        else if (pid == 0) {       /* In Child Process */
                            close(fd[0]);
                            dup2(fd[1], STDOUT_FILENO);
                            close(fd[1]);

                            execl("file_reader", "./file_reader", requestP[i].query, (char *)0);
                            fprintf(stderr, "Error: Unexpect flow of control.\n");
                            exit(EXIT_FAILURE);
                        }
                        else {      /* In Parent Process */
                            close(fd[1]);
                            char recv[1024];
                            read(fd[0], recv, sizeof(recv));
                            printf("The file content is:\n%s\n", recv);
                        }
                        close(conn_fd);              // I forgot to close the listen conn_fd!
                        free_request(&requestP[i]);
                        FD_CLR(conn_fd, &master);    // I forgot to FD_CLR the conn_fd!
                    }
                }
            }
        }
    }
    free(requestP);
    return 0;
}
//=========================
//The following are some APIs

#include <time.h>
#include <fcntl.h>
#include <ctype.h>
#include <sys/stat.h>
#include <sys/mman.h>

static void add_to_buf(http_request *reqP, char* str, size_t len);
static void strdecode(char* to, char* from);
static int hexit(char c);
static char* get_request_line(http_request *reqP);
static void* e_malloc(size_t size);
static void* e_realloc(void* optr, size_t size);

static void init_request(http_request* reqP) {
    reqP->conn_fd = -1;
    reqP->status = 0;       // not used
    reqP->file[0] = (char) 0;
    reqP->query[0] = (char) 0;
    reqP->host[0] = (char) 0;
    reqP->buf = NULL;
    reqP->buf_size = 0;
    reqP->buf_len = 0;
    reqP->buf_idx = 0;
}

static void free_request(http_request* reqP) {
    if (reqP->buf != NULL) {
        free(reqP->buf);
        reqP->buf = NULL;
    }
    init_request(reqP);
}


#define ERR_RET(error) { *errP = error; return -1; }
// return 0: success, file is buffered in retP->buf with retP->buf_len bytes
// return -1: error, check error code (*errP)
// return 1: read more, continue until return -1 or 0
// error code:
// 1: client connection error
// 2: bad request, cannot parse request
// 3: method not implemented
// 4: illegal filename
// 5: illegal query
// 6: file not found
// 7: file is protected
//
static int read_header_and_file(http_request* reqP, int *errP) {
    // Request variables
    char* file = (char *) 0;
    char* path = (char *) 0;
    char* query = (char *) 0;
    char* protocol = (char *) 0;
    char* method_str = (char *) 0;
    int r, fd;
    struct stat sb;
    char timebuf[100];
    int buflen;
    char buf[10000];
    time_t now;
    void *ptr;

    // Read in request from client
    while (1) {
        r = read(reqP->conn_fd, buf, sizeof(buf));
        if (r < 0 && (errno == EINTR || errno == EAGAIN)) return 1;
        if (r <= 0) ERR_RET(1)
            add_to_buf(reqP, buf, r);
        if (strstr(reqP->buf, "\015\012\015\012") != (char*) 0 ||
            strstr(reqP->buf, "\012\012") != (char*) 0) break;
    }

    fprintf(stderr, "=============================================\n");
    fprintf(stderr, "header: %s", reqP->buf);
    fprintf(stderr, "=============================================\n");

    // Parse the first line of the request.
    method_str = get_request_line(reqP);
    if (method_str == (char*) 0) ERR_RET(2)
        path = strpbrk(method_str, " \t\012\015");
    if (path == (char*) 0) ERR_RET(2)
        *path++ = '\0';
    path += strspn(path, " \t\012\015");
    protocol = strpbrk(path, " \t\012\015");
    if (protocol == (char*) 0) ERR_RET(2)
        *protocol++ = '\0';
    protocol += strspn(protocol, " \t\012\015");
    query = strchr(path, '?');
    if (query == (char*) 0)
        query = "";
    else
        *query++ = '\0';

    if (strcasecmp(method_str, "GET") != 0) ERR_RET(3)
        else {
            strdecode(path, path);
            if (path[0] != '/') ERR_RET(4)
                else file = &(path[1]);
        }

    if (strlen(file) >= MAXBUFSIZE-1) ERR_RET(4)
        if (strlen(query) >= MAXBUFSIZE-1) ERR_RET(5)
            strcpy(reqP->file, file);
    strcpy(reqP->query, query);

    char *m = strchr(reqP->query, '=') + 1;
    char *filename = strncpy(reqP->query, m, sizeof(reqP->query));

    fprintf(stderr, "filename = %s\n", filename);
    fprintf(stderr, "reqP.conn_fd = %d\n", reqP->conn_fd);
    fprintf(stderr, "reqP.status = %d\n", reqP->status);
    fprintf(stderr, "reqP.file = %s\n", reqP->file);
    fprintf(stderr, "reqP.query = %s\n", reqP->query);
    fprintf(stderr, "reqP.host = %s\n", reqP->host);
    fprintf(stderr, "reqP.buf = %s\n", reqP->buf);
    fprintf(stderr, "reqP.buf_len = %zu\n", reqP->buf_len);
    fprintf(stderr, "reqP.buf_size = %zu\n", reqP->buf_size);
    fprintf(stderr, "reqP.buf_idx = %zu\n", reqP->buf_idx);
    fprintf(stderr, "=============================================\n");

    // if (query[0] == (char) 0) {
    if (query[0] == 'f') {
        fprintf(stderr, "query[0] = %c\n", query[0]);
        // for file request, read it in buf

        r = stat(filename, &sb);
        // r = stat(reqP->file, &sb);
        if (r < 0) ERR_RET(6)
            fd = open(filename, O_RDONLY);
        // fd = open(reqP->file, O_RDONLY);
        if (fd < 0) ERR_RET(7)
            reqP->buf_len = 0;

        buflen = snprintf(buf, sizeof(buf), "HTTP/1.1 200 OK\015\012Server: SP TOY\015\012");
        add_to_buf(reqP, buf, buflen);
        now = time((time_t*) 0);
        (void) strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&now));
        buflen = snprintf(buf, sizeof(buf), "Date: %s\015\012", timebuf);
        add_to_buf(reqP, buf, buflen);
        buflen = snprintf(
                          buf, sizeof(buf), "Content-Length: %lld\015\012", (int64_t) sb.st_size);
        add_to_buf(reqP, buf, buflen);
        buflen = snprintf(buf, sizeof(buf), "Connection: close\015\012\015\012");
        add_to_buf(reqP, buf, buflen);

        ptr = mmap(0, (size_t) sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
        if (ptr == (void*) -1) ERR_RET(8)
            add_to_buf(reqP, ptr, sb.st_size);
        (void) munmap(ptr, sb.st_size);
        close(fd);
        // printf("%s\n", reqP->buf);
        // fflush(stdout);
        reqP->buf_idx = 0; // writing from offset 0
        return 0;
    }
    return 0;
}

static void add_to_buf(http_request *reqP, char* str, size_t len) {
    char** bufP = &(reqP->buf);
    size_t* bufsizeP = &(reqP->buf_size);
    size_t* buflenP = &(reqP->buf_len);

    if (*bufsizeP == 0) {
        *bufsizeP = len + 500;
        *buflenP = 0;
        *bufP = (char*) e_malloc(*bufsizeP);
    } else if (*buflenP + len >= *bufsizeP) {
        *bufsizeP = *buflenP + len + 500;
        *bufP = (char*) e_realloc((void*) *bufP, *bufsizeP);
    }
    (void) memmove(&((*bufP)[*buflenP]), str, len);
    *buflenP += len;
    (*bufP)[*buflenP] = '\0';
}

static char* get_request_line(http_request *reqP) {
    int begin;
    char c;

    char *bufP = reqP->buf;
    int buf_len = reqP->buf_len;

    for (begin = reqP->buf_idx ; reqP->buf_idx < buf_len; ++reqP->buf_idx) {
        c = bufP[reqP->buf_idx];
        if (c == '\012' || c == '\015') {
            bufP[reqP->buf_idx] = '\0';
            ++reqP->buf_idx;
            if (c == '\015' && reqP->buf_idx < buf_len &&
                bufP[reqP->buf_idx] == '\012') {
                bufP[reqP->buf_idx] = '\0';
                ++reqP->buf_idx;
            }

            fprintf(stderr, "bufP = %s\n", bufP);
            fprintf(stderr, "=============================================\n");
            return &(bufP[begin]);
        }
    }
    fprintf(stderr, "http request format error\n");
    exit(1);
}

static void init_http_server(http_server *svrP, unsigned short port) {
    struct sockaddr_in servaddr;
    int tmp;

    gethostname(svrP->hostname, sizeof(svrP->hostname));
    svrP->port = port;

    svrP->listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (svrP->listen_fd < 0) ERR_EXIT("socket")

        bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(port);
    tmp = 1;
    if (setsockopt(svrP->listen_fd, SOL_SOCKET, SO_REUSEADDR, (void*) &tmp, sizeof(tmp)) < 0) ERR_EXIT ("setsockopt ")
        if (bind(svrP->listen_fd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) ERR_EXIT("bind")
            if (listen(svrP->listen_fd, 1024) < 0) ERR_EXIT("listen")
                }

// Set NDELAY mode on a socket.
static void set_ndelay(int fd) {
    int flags, newflags;

    flags = fcntl(fd, F_GETFL, 0);
    if (flags != -1) {
        newflags = flags | (int) O_NDELAY; // nonblocking mode
        if (newflags != flags)
            (void) fcntl(fd, F_SETFL, newflags);
    }
}

static void strdecode(char* to, char* from) {
    for (; *from != '\0'; ++to, ++from) {
        if (from[0] == '%' && isxdigit(from[1]) && isxdigit(from[2])) {
            *to = hexit(from[1]) * 16 + hexit(from[2]);
            from += 2;
        } else {
            *to = *from;
        }
    }
    *to = '\0';
}

static int hexit(char c) {
    if (c >= '0' && c <= '9')
        return c - '0';
    if (c >= 'a' && c <= 'f')
        return c - 'a' + 10;
    if (c >= 'A' && c <= 'F')
        return c - 'A' + 10;
    return 0;           // shouldn't happen
}

static void* e_malloc(size_t size) {
    void* ptr;

    ptr = malloc(size);
    if (ptr == (void*) 0) {
        (void) fprintf(stderr, "out of memory\n");
        exit(1);
    }
    return ptr;
}

static void* e_realloc(void* optr, size_t size) {
    void* ptr;

    ptr = realloc(optr, size);
    if (ptr == (void*) 0) {
        (void) fprintf(stderr, "out of memory\n");
        exit(1);
    }
    return ptr;
}

这是file_reader.c

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

#define ERR_EXIT(a) { perror(a); exit(1); }

int main(int argc, char **argv) {
    FILE *fp = fopen(argv[1], "r");
    char c;
    char buf[1024];
    int i = 0;
    fprintf(stderr, "filename = %s\n", argv[1]);

    if (fp == NULL) 
        ERR_EXIT("ERROR open!\n")
    while ((c = fgetc(fp)) != EOF) {
        buf[i] = c;
        i++;
    }
    buf[i] = '\0';
    write(STDOUT_FILENO, buf, sizeof(buf));
    fclose(fp);
    return 0;
}

2 个答案:

答案 0 :(得分:1)

您正在同一个requestP插槽中存储多个连接的客户端,因为您在调用accept()后使用侦听套接字描述符作为数组索引。如果一次只连接一个客户端,那么这将起作用,否则当一次连接多个客户端时,您将丢弃该插槽。

但是,您的读/写代码使用客户端套接字描述符作为数组索引。由于客户端套接字描述符的值与侦听套接字描述符的值不同,因此在准备新的requestP[i]客户端时,需要将requestP[conn_fd]更改为accept()

这也假设requestP是一个固定长度的数组,并且当用作索引时,各种套接字描述符永远不会超过数组的边界。

每当从maxfd列表中删除套接字描述符时,您也不会重新计算master。这也可能导致您的错误。

您也没有处理write()在非阻塞套接字上返回阻塞错误的情况。在这种情况下,您需要使用write_fds的{​​{1}}部分来检测套接字何时可以接受更多数据,然后再次调用select()

更好的解决方案是将write()更改为动态数组或链接列表,并完全停止使用套接字描述符作为数组索引,例如:

requestP

答案 1 :(得分:0)

关闭readFDSwriteFDs中的FD后,您需要将其从那里删除。