如何管理大量套接字

How to manage huge amount of socket

我开发了一个C软件,它使用不同的循环在IP和端口(例如192.168.1.10端口= 80)上打开套接字,并检查是否有人打开; 这是代码:

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



int main(void)
{
    int sockfd = 0,n = 0;
    char recvBuff[1024];
    struct sockaddr_in serv_addr;

    memset(recvBuff, '0' ,sizeof(recvBuff));
    if((sockfd = socket(AF_INET, SOCK_STREAM, 0))< 0)
    {
        printf("\n Error : Could not create socket \n");

    }

    serv_addr.sin_family = AF_INET;
    int i;
    char buf[15];

    for (i = 1; i < 255; i++) {
        sprintf(buf, "192.168.1.%d", i); // puts string into buffer
        for (int port = 0; port<=1024; port++) {
//            Problem
//            int sockfd = 0,n = 0;
//            char recvBuff[1024];
//            struct sockaddr_in serv_addr;
//            
//            memset(recvBuff, '0' ,sizeof(recvBuff));
//            if((sockfd = socket(AF_INET, SOCK_STREAM, 0))< 0)
//            {
//                printf("\n Error : Could not create socket \n");
//                return 1;
//            }
            serv_addr.sin_port = htons(port);
            serv_addr.sin_addr.s_addr = inet_addr(buf);
            if(connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr))<0)
            {
               // printf("The port %d of host %s is not open \n",port,buf);

            }else{
                printf("The port %d of host %s is open \n",port,buf);
            }
        }

    }
    return 0;
}

在第二个版本中,我添加了 connection_nonblocking 方法,它减少了可能出现的问题。 这是使用 OpenMP 指令来提高性能的版本;你有什么改进的想法吗?

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


int connect_nonblock (int sockfd, const struct sockaddr *saptr, socklen_t salen)
{
    int n, valopt;
    socklen_t len;
    fd_set rset;
    struct timeval tv;
    long int arg;

    arg = fcntl(sockfd, F_GETFL, NULL);
    arg |= O_NONBLOCK;
    fcntl(sockfd, F_SETFL, arg);

    n = connect(sockfd, saptr, salen);

    if (n == 0) {
        // completed immediately
        arg &= (~O_NONBLOCK);
        fcntl(sockfd, F_SETFL, arg);
        close(sockfd);

        return 0;
    }

    if (n < 0) {
        if (errno != EINPROGRESS) {
            // fail somehow...
            arg &= (~O_NONBLOCK);
            fcntl(sockfd, F_SETFL, arg);
            close(sockfd);
            return -1;
        }
        else {
            tv.tv_sec = 0;
            tv.tv_usec = 10000;
            FD_ZERO(&rset);
            FD_SET(sockfd, &rset);
            n = select(sockfd + 1, NULL, &rset, NULL, &tv);
            if (n < 0 && errno != EINTR) {
                arg &= (~O_NONBLOCK);
                fcntl(sockfd, F_SETFL, arg);
                close(sockfd);
                return -1;
            }
            else if (n > 0) {
                len = sizeof(int);
                getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (void*)(&valopt), &len);
                if (valopt != 0) {
                    arg &= (~O_NONBLOCK);
                    fcntl(sockfd, F_SETFL, arg);
                    close(sockfd);
                    return -1;
                }
            }
            else {
                arg &= (~O_NONBLOCK);
                fcntl(sockfd, F_SETFL, arg);
                close(sockfd);
                return -1;
            }
        }
    }

    arg &= (~O_NONBLOCK);
    fcntl(sockfd, F_SETFL, arg);
    close(sockfd);

    return 0;
}

int main(void)
{
    int sockfd = 0;
    struct sockaddr_in serv_addr;
    int i, port;
    char buf[15];
    double end, start = omp_get_wtime();
    for (i = 1; i <= 255; i++) {
        sprintf(buf, "192.168.1.%d", i); // puts string into buffer
        fprintf(stdout, "Checking address: %s\n", buf);
        //omp_set_num_threads(1);
        #pragma omp parallel for private(sockfd,serv_addr,port)

        for (port = 0; port <= 1024; port++) {
            if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
            {
                perror("socket");
            }

            memset(&serv_addr, 0, sizeof(serv_addr));
            serv_addr.sin_family = AF_INET;
            serv_addr.sin_port = htons(port);
            serv_addr.sin_addr.s_addr = inet_addr(buf);

            if (connect_nonblock(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
                /* uncoment this but if you want */
                //fprintf(stdout, "The port %d of host %s is not open.\n", port, buf);
                ;
            }
            else {
                fprintf(stdout, "The port %d of host %s is open.\n", port, buf);
            }
        }

    }
    end = omp_get_wtime();
    printf("Elapsed time = %f sec\n", end-start);
    return 0;
}

如评论中所述,您没有关闭套接字。您可以使用 close,因为这是一个文件描述符。但是,如果您扫描很多地址,如果连接失败并超时,将会非常耗时。 我正在添加您的代码,稍微修改一下:

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

 int connect_nonblock (int sockfd, const struct sockaddr *saptr, socklen_t salen)
    {
        int n, valopt;
        socklen_t len;
        fd_set rset;
        struct timeval tv;
        long int arg;

        arg = fcntl(sockfd, F_GETFL, NULL);
        arg |= O_NONBLOCK;
        fcntl(sockfd, F_SETFL, arg);

        n = connect(sockfd, saptr, salen);

        if (n == 0) {
            /* completed immediately */
            arg &= (~O_NONBLOCK);
            fcntl(sockfd, F_SETFL, arg);
            close(sockfd);

            return 0;
        }

        if (n < 0) {
            if (errno != EINPROGRESS) {
                /* fail somehow... */
                arg &= (~O_NONBLOCK);
                fcntl(sockfd, F_SETFL, arg);
                close(sockfd);
                return -1;
            }
            else {
                tv.tv_sec = 0;
                tv.tv_usec = 10000;
                FD_ZERO(&rset);
                FD_SET(sockfd, &rset);
                n = select(sockfd + 1, NULL, &rset, NULL, &tv);
                if (n < 0 && errno != EINTR) {
                    arg &= (~O_NONBLOCK);
                    fcntl(sockfd, F_SETFL, arg);
                    close(sockfd);
                    return -1;
                }
                else if (n > 0) {
                    len = sizeof(int);
                    getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (void*)(&valopt), &len);
                    if (valopt != 0) {
                        arg &= (~O_NONBLOCK);
                        fcntl(sockfd, F_SETFL, arg);
                        close(sockfd);
                        return -1;
                    }
                }
                else {
                    arg &= (~O_NONBLOCK);
                    fcntl(sockfd, F_SETFL, arg);
                    close(sockfd);
                    return -1;
                }
            }
        }

        arg &= (~O_NONBLOCK);
        fcntl(sockfd, F_SETFL, arg);
        close(sockfd);

        return 0;
    }

    int main(void)
    {
        int sockfd = 0, n = 0;
        char recv_buff[1024];
        struct sockaddr_in serv_addr;
        int i, port;
        char buf[15];

        memset(recv_buff, '0', sizeof(recv_buff));

        for (i = 1; i < 255; i++) {
            sprintf(buf, "192.168.88.%d", i); // puts string into buffer
            for (port = 0; port <= 1024; port++) {
                if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
                {
                    perror("socket");
                    return EXIT_FAILURE;
                }

                memset(&serv_addr, 0, sizeof(serv_addr));
                serv_addr.sin_family = AF_INET;
                serv_addr.sin_port = htons(port);
                serv_addr.sin_addr.s_addr = inet_addr(buf);

                if (connect_nonblock(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
                {
                    printf("The port %d of host %s is not open \n", port, buf);
                } else {
                    printf("The port %d of host %s is open \n", port, buf);
                }

                // you missed this
                close(sockfd);
             }
         }
         return 0;
     }

我正在使用 select 添加一种非阻塞方法,超时和线程很少。应该重做,因为很多错误检查没有以正确的方式完成。但希望你能明白。

这里是我的建议:

  • 使用非阻塞操作。
  • 运行 一次有多个套接字,超时受控。

当 运行 流套接字时存在几个问题:它们的数量是有限的。旧实现不能为每个接口打开超过 65K 个套接字。较新的可以做得更好,但这仍然需要大量资源(因为您计划总共扫描 260+K 个套接字)。

流套接字没有同步操作(默认情况下)。打开和关闭操作都会启动一系列数据包,以便两端都知道。当然,您可以强制关闭它们,但这也可能会给您的算法带来问题,因为内核仍会接收那些未正确终止的连接的数据包。

即使您扫描本地网络,尝试连接的时间也可能非常长,因为远程主机可能禁用了 ICMP 响应。

因此,该算法可能如下所示:

while (true)
{
    while (size_of_socket_table < N && has_more_addr())
    {
        Create socket entry
            Create socket
            Mark operation expiration time
        Initiate connect operation
        if fail
        {
            continue
        }
        Add entry to table
        Register entry for `pollfd`
     }

    If entry table is empty
       break
    Compute timeout from socket entries
    Execute EPOLL with timeout

    Process connected sockets:
       close
       release socket entries
    Process timed out socket entries
       close
       release socket entries
}

注意:当文件描述符准备好写入时(EPOLLOUT),流套接字已连接。

为什么 epoll vs poll vs select

  • epoll允许用户数据与文件描述符(socket)关联,非常方便编程。
  • select 不太好,因为你将不得不处理 FD_SET 大小限制,而且它无论如何都在里面使用 poll
  • poll很好也很稳定,但这是你的选择。

有用的链接: