古月不傲 发表于 2021-1-31 17:10

socket编程--EMFILE错误

#### EMFILE

该错误表示该进程可使用的文件描述符超出了上限,默认为1024

在select、poll、epoll的LT模式如果不处理这种错误,会导致处于busy loop状态

##### 如何处理EMFILE?

1、修改上限值、治标不至本

2、死等

3、关闭listen_fd

4、epoll改用ET模式

5、退出程序

6、准备一个空闲的描述符,先把位置站好,该描述符最开始一定是3,0 1 2 属于标准输入、输出、错误

​                然后连接到来,close掉空闲描述符,腾出3号,再次accept,再次close这个连接,再open获取3号描述符

​                这样我们就优雅的关闭了该连接,要不只能退出程序,否则accept会返回-1 ,poll、epoll的LT模式会认为你没有处理该                事件 就会一直触发 处于busy loop状态

服务端测试

```98
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <signal.h>
#include <poll.h>
#include <vector>
#include <netinet/in.h>           // INADDR_ANY
#include <errno.h>
#include <arpa/inet.h>      // inet_ntoa
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(void)
{
        signal(SIGPIPE, SIG_IGN);
        signal(SIGCHLD, SIG_IGN);

        // 先提前占用一个文件描述符,是3对吧,0 1 2 = 标准输入、输出、错误
        int idleFd = open("/dev/null", O_RDONLY | O_CLOEXEC);

        int listen_fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);

        struct sockaddr_in server_addr {};
        server_addr.sin_family = AF_INET;
        server_addr.sin_port = htons(5578);
        server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

        int on = 1;
        setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on));
        bind(listen_fd, (const struct sockaddr*)&server_addr, sizeof(server_addr));
        listen(listen_fd, 1);

        struct pollfd read_pollfd;
        read_pollfd.fd = listen_fd;
        read_pollfd.events = POLLIN;
        read_pollfd.revents= 0;

        std::vector<pollfd> vec_read_poll;
        vec_read_poll.push_back(read_pollfd);

        int read_ready;
        int connected_fd;

        struct sockaddr_inclient_addr;
        socklen_t addr_len = sizeof(client_addr);

        int saveErrno = -1;

        while (1)
        {
                read_ready = poll(vec_read_poll.data(), vec_read_poll.size(), -1);
                printf("read_ready = %d\n", read_ready);
                // poll失败
                if (read_ready == -1)
                {
                        if (errno == EINTR)
                                continue;
                        return -1;
                }
                else if (read_ready == 0)
                        continue;

                else if (vec_read_poll.revents & POLLIN)
                {
                        --read_ready;

                        connected_fd = accept4(listen_fd, (sockaddr *)&client_addr, &addr_len, SOCK_NONBLOCK | SOCK_CLOEXEC);
                        printf("connected_fd = %d\n", connected_fd);
                        saveErrno = errno;

                        if (connected_fd == -1)
                        {
                                if (saveErrno == EINTR)
                                        continue;
                                else if (saveErrno == EMFILE)
                                {
                                        printf("该服务器连接上限,请过一会在连接\n");
                                        // 立即关闭3号文件描述符
                                        close(idleFd);
                                        // 腾出3号,再次accept
                                        idleFd = accept4(listen_fd, nullptr, nullptr,SOCK_NONBLOCK | SOCK_CLOEXEC);
                                        // 优雅的关闭该连接,要不只能退出程序,否则accept会返回-1
                                        // poll是lt模式会认为你没有处理该事件 就会一直触发 处于busy loop状态
                                        close(idleFd);
                                        // 再次占用3号
                                        idleFd = open("/dev/null", O_RDONLY | O_CLOEXEC);
                                        continue;
                                }
                                else if (saveErrno == ECONNABORTED)
                                {
                                        close(connected_fd);
                                        continue;
                                }
                                else
                                        return -1;
                        }   

                        std::cout << "client_ip = " << inet_ntoa(client_addr.sin_addr) << std::endl;
                        std::cout << "client_port = " << ntohs(client_addr.sin_port) << std::endl;

                        if (read_ready == 0)
                                continue;
                }
        }
        return 0;
}
```

客户端fork 1019个连接,再打开telnet连接就会触发EMFILE错误               

```
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <signal.h>
#include <poll.h>
#include <vector>
#include <netinet/in.h>   // INADDR_ANY
#include <errno.h>
#include <arpa/inet.h>    // inet_ntoa
#include <cstring>
#include <unistd.h>

int main(void)
{
        int ret = -1;
        // 0 1 2 3 4 输入、输出、错误、idleFd、listen_fd
        for (int i = 0; i < 1019; i++)
        {
                ret = fork();

                usleep(100);

                if (ret == 0)
                {
                        // 创建socket 非阻塞 | 子进程中关闭该套接字
                        int listen_fd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0);

                        // 初始化socket信息
                        struct sockaddr_in server_addr {};
                        server_addr.sin_family = AF_INET;
                        server_addr.sin_port = htons(5578);
                        server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

                        socklen_t len = sizeof(server_addr);

                        int conn = connect(listen_fd, (struct sockaddr *)&server_addr, len);
                        getchar();
                }
        }

        getchar();
        return 0;
}
```

这样就限制死了客户端的连接数量,当然可以创建一个等待队列,监测其他客户端关闭,就可以accpet了。
页: [1]
查看完整版本: socket编程--EMFILE错误