epoll在包含数据的可用fds上设置EPOLLHUP

时间:2019-12-13 22:01:55

标签: c sockets epoll

多年以来,我一直在使用不同的语言(Java,PHP,ASP,JS ...),但从未接触过C。总是想着它,但总有明天。现在,我终于做到了,并且已经玩了一段时间,并想尝试使用套接字。因此,我制作了一个小型测试程序,该程序将读取文件并将其发送给客户端。使用epoll来处理fd状态,效果很好。然后,我想我将把这个测试扩展到包括更多有趣的东西,并决定将fork()添加到混合中。然后,与其正常地读取文件,不如简单地“捕获”文件内容并从设置的管道中读取输出。

这本来很简单,然后我也可以添加更多或其他内容,但是事实证明,这比我想象的要困难得多。用流程管道更改普通文件描述符并不是那么容易。如果您希望它正常运行,至少不会。

我测试过的文件包含60000+字节,但是在关闭连接之前,我得到了大约8000到14000的随机字节数。到进程的两个管道以及客户端套接字本身都接收到“ EPOLLHUP”,同时仍有数据要从进程输出中读取,并且服务器与客户端之间的连接已完全建立。我已经调试了S ***,并多次重写了整个内容,以尝试不同的方法。但是,当涉及从进程中提取数据并通过套接字将其发送到客户端时,我总是会失败。

这次是epoll事件标志,一直困扰着我。如果我删除了对“ EPOLLRDHUP | EPOLLHUP | EPOLLERR”标志的任何管理,我将获得完整的60000字节传输。

服务器代码

typedef struct stream_fd_info {
    int fd;
    uint32_t events;

} stream_fd_info_t;

typedef struct stream_info {
    char buffer[1024];
    ssize_t rc;
    ssize_t wc;
    ssize_t ofs;
    stream_fd_info_t output;
    stream_fd_info_t input;

} stream_info_t;

#define EPOLLDIED (EPOLLRDHUP|EPOLLHUP|EPOLLERR)

int main(int argc, char **argv) {

    struct epoll_event event, events[6];
    int epoll = epoll_create1(0);

    int sockfd = socket(AF_UNIX, SOCK_STREAM, 0), clifd;
    struct sockaddr_un sockaddr, cliaddr;
    socklen_t clilen;

    sockaddr.sun_path[0] = '\0';
    char* sockuri = "/tmp/ctest.sock";
    sockaddr.sun_family = AF_UNIX;

    memcpy(&sockaddr.sun_path[1], sockuri, strlen(sockuri));
    socklen_t socklen = sizeof(sockaddr.sun_family) + strlen(sockuri) + 1;

    if (bind(sockfd, (struct sockaddr *) &sockaddr, socklen) < 0 || listen(sockfd, 1) < 0) {
        perror("Socket"); exit(1);
    }

    clifd = accept(sockfd, (struct sockaddr *) &cliaddr, &clilen);

    if (clifd < 0) {
        perror("Client"); exit(1);
    }

    int pipeout[2];
    int pipein[2];

    // Create the pipe FD's
    pipe(pipeout);
    pipe(pipein);

    pid_t pid = fork();

    if (pid < 0) {
        perror("Fork"); exit(1);

    } else if(!pid) {
        fprintf(stderr, "Starting client process shell\n");

        dup2(pipeout[0], 0);
        dup2(pipein[1], 1);
        dup2(1, 2);

        close(pipein[0]);
        close(pipein[1]);
        close(pipeout[0]);
        close(pipeout[1]);

        char *args[] = {"cat", {"/opt/php/cli/php.ini"}};

        execvp(args[0], args);

    } else {
        sleep(1);

        close(pipein[1]);
        close(pipeout[0]);

        stream_info_t streams[2];

        streams[0].input.fd = clifd;
        streams[0].input.events = EPOLLDIED|EPOLLIN;
        streams[0].output = streams[0].input;

        streams[1].input.fd = pipein[0];
        streams[1].output.fd = pipeout[1];
        streams[1].input.events = EPOLLDIED|EPOLLIN;
        streams[1].output.events = EPOLLDIED;

        fprintf(stderr, "Monitoring uni-directional client descriptor %d\n", streams[0].output.fd);
        fcntl(streams[0].input.fd, F_SETFL, fcntl(streams[0].input.fd, F_GETFL, 0) | O_NONBLOCK);
        event.events = streams[0].input.events;
        event.data.fd = streams[0].input.fd;
        epoll_ctl(epoll, EPOLL_CTL_ADD, streams[0].input.fd, &event);

        fprintf(stderr, "Monitoring process output descriptor %d\n", streams[1].output.fd);
        fcntl(streams[1].output.fd, F_SETFL, fcntl(streams[1].output.fd, F_GETFL, 0) | O_NONBLOCK);
        event.events = streams[1].output.events;
        event.data.fd = streams[1].output.fd;
        epoll_ctl(epoll, EPOLL_CTL_ADD, streams[1].output.fd, &event);

        fprintf(stderr, "Monitoring process input descriptor %d\n", streams[1].input.fd);
        fcntl(streams[1].input.fd, F_SETFL, fcntl(streams[1].input.fd, F_GETFL, 0) | O_NONBLOCK);
        event.events = streams[1].input.events;
        event.data.fd = streams[1].input.fd;
        epoll_ctl(epoll, EPOLL_CTL_ADD, streams[1].input.fd, &event);

        int ready, i, x;
        int totalRead = 0, totalWrite = 0;

        fprintf(stderr, "Setting up client connection\n");

        int running = 1;
        while (running) {
            ready = epoll_wait(epoll, events, 6, 30000);

            // fprintf(stderr, "There are %d descriptor(s) ready\n", ready);

            for(i = 0; i < ready; i++) {
                // fprintf(stderr, "Checking flags %d on descriptor %d\n", events[i].events, events[i].data.fd);

                if ((events[i].events & EPOLLOUT) != 0) {
                    x = events[i].data.fd == streams[0].input.fd ? 0 : 1;

                    if (streams[x].wc > 0) {
                        streams[x].wc = write(events[i].data.fd, &streams[x].buffer[ streams[x].ofs ], (size_t) streams[x].rc);

                        if (streams[x].wc < 0) {
                            perror("Write"); exit(1);

                        } else if (streams[x].wc > 0) {
                            fprintf(stderr, "Wrote %d byte(s) of %d to descriptor %d\n", streams[x].wc, streams[x].rc, events[i].data.fd);

                            streams[x].rc -= streams[x].wc;
                            streams[x].ofs += streams[x].wc;
                            totalWrite += streams[x].wc;
                        }

                        if (streams[x].rc <= 0) {
                            streams[x].output.events &= ~EPOLLOUT;
                            event.events = streams[x].output.events;
                            event.data.fd = streams[x].output.fd;

                            fprintf(stderr, "Removed EPOLLOUT from descriptor %d\n", event.data.fd);
                            epoll_ctl(epoll, EPOLL_CTL_MOD, event.data.fd, &event);
                        }
                    }
                }

                if ((events[i].events & EPOLLIN) != 0) {
                    x = events[i].data.fd == streams[0].input.fd ? 1 : 0;

                    if (streams[x].rc <= 0) {
                        streams[x].rc = read(events[i].data.fd, streams[x].buffer, sizeof streams[x].buffer);
                        streams[x].ofs = 0;

                        if (streams[x].rc < 0) {
                            perror("Read"); exit(1);

                        } else if (streams[x].rc > 0) {
                            fprintf(stderr, "Read %d byte(s) from descriptor %d into %d bytes cache\n", streams[x].rc, events[i].data.fd, sizeof streams[x].buffer);

                            totalRead += streams[x].rc;

                            streams[x].output.events |= EPOLLOUT;
                            event.events = streams[x].output.events;
                            event.data.fd = streams[x].output.fd;

                            fprintf(stderr, "Added EPOLLOUT to descriptor %d\n", event.data.fd);
                            epoll_ctl(epoll, EPOLL_CTL_MOD, event.data.fd, &event);
                        }
                    }
                }

                if ((events[i].events & EPOLLDIED) != 0) {
                    // fprintf(stderr, "Descriptor %d has closed. Flags %d\n", events[i].data.fd, events[i].events);

                    /*x = events[i].data.fd == streams[0].input.fd ? 1 : 0;

                    if ((events[i].events & (EPOLLHUP|EPOLLERR)) != 0
                            && streams[x].rc <= 0) {

                        running = 0;

                    } else {
                        fprintf(stderr, "Removing descriptor %d from the poll\n", events[i].data.fd);
                        epoll_ctl(epoll, EPOLL_CTL_DEL, events[i].data.fd, NULL);
                    }*/
                }
            }
        }

        int pidres;

        if (waitpid(pid, &pidres, WNOHANG) == 0) {
            // TODO: Cleaner shutdown
            fprintf(stderr, "Terminating child process %d\n", pid);
            kill(pid, SIGKILL);
        }

        fprintf(stderr, "Connection was close with total read count of %d bytes and write count of %d bytes\n", totalRead, totalWrite);

        close(streams[1].input.fd);
        close(streams[1].output.fd);
    }

    close(clifd);
    close(sockfd);
    close(epoll);
}

客户代码

#!/usr/bin/env php
<?php

echo "Establishing connection to the server...\n";
$socket = socket_create(AF_UNIX, SOCK_STREAM, 0);

if (!$socket) {
    die("Could not connect to the server socket\n");

} else {
    socket_connect($socket, "\0/tmp/ctest.sock");
}

$totalRead = 0;

for (;;) {
    echo "Reading from server...\n";
    $buffer = socket_read($socket, 1024);

    if ($socket !== FALSE && strcmp($buffer, '') != 0) {
        $totalRead += strlen($buffer);
        //echo $buffer;
        echo "Total read count $totalRead bytes\n";

        sleep(1);

    } else {
        break;
    }
}

echo "Connection was closed with a total read of $totalRead bytes\n";

socket_close($socket);

C对我来说就像是一门外语,我仍在学习我的ABC。因此,如果那里的C大师能够发现我的新手错误并通过提示进行提示,那就太好了。下面的东西对您来说似乎很简单,但是我花了数周的时间解决了这个问题,但是却无济于事:o

谢谢

编辑: 在关闭任何内容之前添加'EPOLLIN | EPOLLOUT'检查可以解决某些问题。

                if ((events[i].events & EPOLLDIED) != 0
                        && (events[i].events & (EPOLLIN|EPOLLOUT)) == 0) {

这使进程fds按其应有的方式工作。输入流在开头关闭,这很好,因为“ cat”在输出数据时不使用它,而在读取最后一个字节时关闭输出流。

但是现在它无法检测到套接字连接丢失,因为“ EPOLLIN”始终处于打开状态,没有其他问题。我可以找到解决方法,但这真的是这些标志应如何起作用的吗?误报?如果插座是关闭的,则无法读取。

编辑2: 因此,我对其进行了重新组织,以使其更加可见,并修复了在清理过程中偶然发现的一些错误。在这里和那里添加了很多检查,现在终于可以在我测试的所有场景中正常工作了。为了安全起见,还建议对零读/写进行处理。

/*
 *
 */
typedef struct stream_fd_info {
    uint32_t events;
    int fd;
    int epoll;

} stream_fd_info_t;

/*
 *
 */
typedef struct stream_info {
    char buffer[4096];
    ssize_t rc;
    ssize_t wc;
    ssize_t ofs;
    stream_fd_info_t output;
    stream_fd_info_t input;

} stream_info_t;

/*
 * 
 */
#define EPOLLDIED (EPOLLRDHUP|EPOLLHUP|EPOLLERR)

/**
 *
 */
int process_validator(void* args) {
    pid_t*  pid = (pid_t*) args;

    return waitpid(*pid, NULL, WNOHANG) == 0 ? 1 : 0;
}

/**
 *
 */
void* stream_handler(stream_info_t* streams, int (*validator)(void*), void* args) {  // stream_info_t[2]

    stream_fd_info_t        fdinfo;
    struct epoll_event      event, events[4];
    int                     epoll = epoll_create1(0);
    int                     i, x, y;
    int                     ready, running = 1;

    /*
     * Start epoll monitoring on all FDs
     */
    for (i=0; i < 2; i++) {
        for (x=0; x < 2; x++) {
            fdinfo = x == 0 ? streams[i].input : streams[i].output;
            fprintf(stderr, "Monitoring %s descriptor %d\n", x == 0 ? "input" : "output", fdinfo.fd);
            event.events = fdinfo.events;
            event.data.fd = fdinfo.fd;
            epoll_ctl(epoll, EPOLL_CTL_ADD, fdinfo.fd, &event);
            fcntl(fdinfo.fd, F_SETFL, fcntl(fdinfo.fd, F_GETFL, 0) | O_NONBLOCK);

            fdinfo.epoll = 1;
        }
    }

    while (running) {
        ready = epoll_wait(epoll, events, 4, 20000);

        // fprintf(stderr, "There are %d descriptor(s) ready\n", ready);

        /*
         * If both sets of FD's can only read or only write, then there is no
         * way for them to communicate. As such we can consider this connection closed/broken.
         */
        if (ready == 0
                && (( (!streams[0].input.epoll || !streams[1].output.epoll) && (!streams[0].output.epoll || !streams[1].input.epoll) )
                        || !validator(args))) {

            fprintf(stderr, "The connection has timed-out\n");
            running = 0;

        } else if (ready < 0) {
            fprintf(stderr, "Epoll crashed on error code %d\n", errno);
            running = 0;
        }

        for(i = 0; i < ready; i++) {
            // fprintf(stderr, "Checking flags %d on descriptor %d\n", events[i].events, events[i].data.fd);

            if ((events[i].events & EPOLLOUT) != 0) {
                x = events[i].data.fd == streams[0].output.fd ? 0 : 1;

                if (streams[x].rc > 0) {
                    streams[x].wc = write(events[i].data.fd, &streams[x].buffer[ streams[x].ofs ], (size_t) streams[x].rc);

                    if (streams[x].wc < 0
                            && errno != EWOULDBLOCK && errno != EAGAIN) {

                        running = 0;

                    } else if (streams[x].wc > 0) {
                        fprintf(stderr, "Wrote %d byte(s) of %d to descriptor %d\n", streams[x].wc, streams[x].rc, events[i].data.fd);

                        streams[x].rc -= streams[x].wc;
                        streams[x].ofs += streams[x].wc;

                    } else if (streams[x].wc == 0) {
                        // Lets deal with this at the end
                        if ((events[i].events & EPOLLDIED) == 0) {
                            events[i].events |= EPOLLDIED;
                        }

                        events[i].events &= ~EPOLLOUT;
                    }

                    if (streams[x].rc <= 0) {
                        streams[x].output.events &= ~EPOLLOUT;
                        event.events = streams[x].output.events;
                        event.data.fd = streams[x].output.fd;

                        fprintf(stderr, "Removed EPOLLOUT from descriptor %d\n", event.data.fd);
                        epoll_ctl(epoll, EPOLL_CTL_MOD, event.data.fd, &event);
                    }
                }
            }

            if ((events[i].events & EPOLLIN) != 0) {
                x = events[i].data.fd == streams[0].input.fd ? 1 : 0;

                if (streams[x].rc <= 0) {
                    streams[x].rc = read(events[i].data.fd, streams[x].buffer, sizeof streams[x].buffer);
                    streams[x].ofs = 0;

                    if (streams[x].rc < 0
                            && errno != EWOULDBLOCK && errno != EAGAIN) {

                        running = 0;

                    } else if (streams[x].rc > 0) {
                        fprintf(stderr, "Read %d byte(s) from descriptor %d into %d bytes cache\n", streams[x].rc, events[i].data.fd, sizeof streams[x].buffer);

                        streams[x].output.events |= EPOLLOUT;
                        event.events = streams[x].output.events;
                        event.data.fd = streams[x].output.fd;

                        fprintf(stderr, "Added EPOLLOUT to descriptor %d\n", event.data.fd);
                        epoll_ctl(epoll, EPOLL_CTL_MOD, event.data.fd, &event);

                    } else if (streams[x].rc == 0) {
                        // Lets deal with this at the end
                        if ((events[i].events & EPOLLDIED) == 0) {
                            events[i].events |= EPOLLDIED;
                        }

                        events[i].events &= ~EPOLLIN;
                    }
                }
            }

            if ((events[i].events & EPOLLDIED) != 0
                    && (events[i].events & (EPOLLIN|EPOLLOUT)) == 0) {

                fprintf(stderr, "Descriptor %d has closed. Flags %d\n", events[i].data.fd, events[i].events);

                x = events[i].data.fd == streams[0].input.fd
                        || events[i].data.fd == streams[0].output.fd ? 1 : 0;

                y = x == 0 ? 1 : 0;

                if ((events[i].events & (EPOLLHUP|EPOLLERR)) != 0
                        && streams[x].rc <= 0) {

                    running = 0;

                } else {
                    fprintf(stderr, "Removing descriptor %d from the poll\n", events[i].data.fd);
                    epoll_ctl(epoll, EPOLL_CTL_DEL, events[i].data.fd, NULL);

                    fdinfo = streams[y].input.fd == events[i].data.fd
                                ? streams[y].input : streams[y].output;

                    fdinfo.epoll = 0;
                }
            }
        }
    }

    close(epoll);
}

/**
 *
 */
int main(int argc, char **argv) {

    int                     sockfd = socket(AF_UNIX, SOCK_STREAM, 0), clifd;
    struct sockaddr_un      sockaddr, cliaddr;
    socklen_t               socklen, clilen;
    char*                   sockuri = "/tmp/ctest.sock";
    int                     pipeout[2], pipein[2];
    stream_info_t           streams[2];
    pid_t                   pid;
    int                     pidres;

    sockaddr.sun_path[0] = '\0';
    sockaddr.sun_family = AF_UNIX;
    memcpy(&sockaddr.sun_path[1], sockuri, strlen(sockuri));
    socklen = sizeof(sockaddr.sun_family) + strlen(sockuri) + 1;

    if (bind(sockfd, (struct sockaddr *) &sockaddr, socklen) < 0 || listen(sockfd, 1) < 0) {
        perror("Socket"); exit(1);
    }

    clifd = accept(sockfd, (struct sockaddr *) &cliaddr, &clilen);

    if (clifd < 0) {
        perror("Client"); exit(1);
    }

    fprintf(stderr, "Setting up client environment\n");

    pipe(pipeout);
    pipe(pipein);

    pid = fork();

    if (pid < 0) {
        perror("Fork"); exit(1);

    } else if(!pid) {
        fprintf(stderr, "Starting client sub-process\n");

        dup2(pipeout[0], 0);
        dup2(pipein[1], 1);
        dup2(1, 2);

        close(pipein[0]);
        close(pipein[1]);
        close(pipeout[0]);
        close(pipeout[1]);

        char *args[] = {"cat", {"/opt/php/cli/php.ini"}};

        execvp(args[0], args);

    } else {
        close(pipein[1]);
        close(pipeout[0]);

        streams[0].input.fd = clifd;
        streams[0].input.events = EPOLLDIED|EPOLLIN;
        streams[0].input.epoll = 0;
        streams[0].output = streams[0].input;
        streams[0].rc = 0;
        streams[0].wc = 0;
        streams[0].ofs = 0;

        streams[1].input.fd = pipein[0];
        streams[1].output.fd = pipeout[1];
        streams[1].input.events = EPOLLDIED|EPOLLIN;
        streams[1].output.events = EPOLLDIED;
        streams[1].input.epoll = 0;
        streams[1].output.epoll = 0;
        streams[1].rc = 0;
        streams[1].wc = 0;
        streams[1].ofs = 0;

        /*
         * Now let the stream handler deal with I/O
         */
        stream_handler(streams, process_validator, &pid);

        if (waitpid(pid, &pidres, WNOHANG) == 0) {
            fprintf(stderr, "Terminating child process %d\n", pid);
            kill(pid, SIGKILL);
        }

        close(streams[1].input.fd);
        close(streams[1].output.fd);
    }

    close(clifd);
    close(sockfd);

    fprintf(stderr, "Connection has been closed\n");
}

不确定这是正确的还是我遗漏了一些东西,但是它现在可以正常工作了。

0 个答案:

没有答案