【问题标题】:Using select() for non-blocking sockets对非阻塞套接字使用 select()
【发布时间】:2011-07-16 06:17:40
【问题描述】:

我正在尝试使用 select 函数在服务器和 1 个客户端(不再)之间进行非阻塞 i/o,其中通信顺畅(可以随时发送,另一个将接收而无需等待发送) .我找到了一个包含一些代码的教程,并试图将其改编为我的。这就是我所拥有的 -

服务器

#define PORT "4950"
#define STDIN 0

struct sockaddr name;

void set_nonblock(int socket) {
    int flags;
    flags = fcntl(socket,F_GETFL,0);
    assert(flags != -1);
    fcntl(socket, F_SETFL, flags | O_NONBLOCK);
}


// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa) {
    if (sa->sa_family == AF_INET)
        return &(((struct sockaddr_in*)sa)->sin_addr);

    return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

int main(int agrc, char** argv) {
    int status, sock, adrlen, new_sd;

    struct addrinfo hints;
    struct addrinfo *servinfo;  //will point to the results

    //store the connecting address and size
    struct sockaddr_storage their_addr;
    socklen_t their_addr_size;

    fd_set read_flags,write_flags; // the flag sets to be used
    struct timeval waitd;          // the max wait time for an event
    int sel;                      // holds return value for select();

    //socket infoS
    memset(&hints, 0, sizeof hints); //make sure the struct is empty
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM; //tcp
    hints.ai_flags = AI_PASSIVE;     //use local-host address

    //get server info, put into servinfo
    if ((status = getaddrinfo("127.0.0.1", PORT, &hints, &servinfo)) != 0) {
        fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(status));
        exit(1);
    }

    //make socket
    sock = socket(servinfo->ai_family, servinfo->ai_socktype, servinfo->ai_protocol);
    if (sock < 0) {
        printf("\nserver socket failure %m", errno);
        exit(1);
    }

    //allow reuse of port
    int yes=1;
    if (setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int)) == -1) {
        perror("setsockopt");
        exit(1);
    }

    //unlink and bind
    unlink("127.0.0.1");
    if(bind (sock, servinfo->ai_addr, servinfo->ai_addrlen) < 0) {
        printf("\nBind error %m", errno);
        exit(1);
    }

    freeaddrinfo(servinfo);

    //listen
    if(listen(sock, 5) < 0) {
        printf("\nListen error %m", errno);
        exit(1);
    }
    their_addr_size = sizeof(their_addr);

    //accept
    new_sd = accept(sock, (struct sockaddr*)&their_addr, &their_addr_size);
    if( new_sd < 0) {
        printf("\nAccept error %m", errno);
        exit(1);
    }

    set_nonblock(new_sd);
    cout<<"\nSuccessful Connection!";

    char* in = new char[255];
    char* out = new char[255];
    int numSent;
    int numRead;


    while(1) {

        waitd.tv_sec = 10;
        FD_ZERO(&read_flags);
        FD_ZERO(&write_flags);
        FD_SET(new_sd, &read_flags);

        if(strlen(out) != 0)
            FD_SET(new_sd, &write_flags);

        sel = select(new_sd+1, &read_flags, &write_flags, (fd_set*)0, &waitd);
        if(sel < 0)
            continue;

        //socket ready for reading
        if(FD_ISSET(new_sd, &read_flags)) {
            FD_CLR(new_sd, &read_flags);

            memset(&in, 0, sizeof(in));

            if(recv(new_sd, in, sizeof(in), 0) <= 0) {
                close(new_sd);
                break;
            }
            else
                cout<<"\n"<<in;
        }   //end if ready for read

        //socket ready for writing
        if(FD_ISSET(new_sd, &write_flags)) {

            FD_CLR(new_sd, &write_flags);
            send(new_sd, out, strlen(out), 0);
            memset(&out, 0, sizeof(out));
        }
    }   //end while

    cout<<"\n\nExiting normally\n";
    return 0;
}

客户端(基本相同,只是减去了一个接受调用) -

#define PORT "4950"

struct sockaddr name;

void set_nonblock(int socket) {
    int flags;
    flags = fcntl(socket,F_GETFL,0);
    assert(flags != -1);
    fcntl(socket, F_SETFL, flags | O_NONBLOCK);
}

// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa) {
    if (sa->sa_family == AF_INET)
        return &(((struct sockaddr_in*)sa)->sin_addr);

    return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

int main(int agrc, char** argv) {
    int status, sock, adrlen;

    struct addrinfo hints;
    struct addrinfo *servinfo;  //will point to the results

    fd_set read_flags,write_flags; // the flag sets to be used
    struct timeval waitd;          // the max wait time for an event
    int sel;                      // holds return value for select();

    memset(&hints, 0, sizeof hints); //make sure the struct is empty
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM; //tcp
    hints.ai_flags = AI_PASSIVE;     //use local-host address

    //get server info, put into servinfo
    if ((status = getaddrinfo("127.0.0.1", PORT, &hints, &servinfo)) != 0) {
        fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(status));
        exit(1);
    }

    //make socket
    sock = socket(servinfo->ai_family, servinfo->ai_socktype, servinfo->ai_protocol);
    if (sock < 0) {
        printf("\nserver socket failure %m", errno);
        exit(1);
    }

    if(connect(sock, servinfo->ai_addr, servinfo->ai_addrlen) < 0) {
        printf("\nclient connection failure %m", errno);
        exit(1);
    }

    cout<<"\nSuccessful connection!";

    set_nonblock(sock);

    char* out = new char[255];
    char* in = new char[255];
    int numRead;
    int numSent;

    while(1) {

        waitd.tv_sec = 10;
        FD_ZERO(&read_flags);
        FD_ZERO(&write_flags);
        FD_SET(sock, &read_flags);

        if(strlen(out) != 0)
            FD_SET(sock, &write_flags);


        sel = select(sock+1, &read_flags, &write_flags, (fd_set*)0, &waitd);
        if(sel < 0)
            continue;

        //socket ready for reading
        if(FD_ISSET(sock, &read_flags)) {
            FD_CLR(sock, &read_flags);

            memset(&in, 0, sizeof(in));

            if(recv(sock, in, sizeof(in), 0) <= 0) {
                close(sock);
                break;
            }
            else
                cout<<"\n"<<in;
        }   //end if ready for read

        //socket ready for writing
        if(FD_ISSET(sock, &write_flags)) {
            FD_CLR(sock, &write_flags);
            send(sock, out, strlen(out), 0);
            memset(&out, 0, sizeof(out));
        }
    }   //end while

    cout<<"\n\nExiting normally\n";
    return 0;
}

问题是当我运行它们时,什么也没有发生。我可以输入一个并按回车键,而另一个屏幕上什么也没有显示(反之亦然)。这不是我要调试的全部信息,这是我第一次真正尝试使用 select,所以我想也许我只是不知道一些简单的事情。如果有任何发现错误或奇怪的地方,请指出,感谢任何帮助。

【问题讨论】:

    标签: c sockets select bsd


    【解决方案1】:

    问题是当我运行它们时,什么都没有发生。

    真正的问题是,人们多年来一直在粘贴 Beej 的东西,却不理解它。这就是为什么我不太喜欢那个指南。它给出了大量的代码块而没有真正详细地解释它们。

    您没有阅读任何内容,也没有发送任何内容;没有 fgets、scanf、cin 等。这就是我要做的:

    FD_SET(sock, &read_flags);
    FD_SET(STDIN_FILENO, &read_flags);
    
    /* .. snip .. */
    
    if(FD_ISSET(STDIN_FILENO, &read_flags)) {
        fgets(out, len, stdin);
    }
    

    这将监视stdin 并在输入可用时从中读取;然后,当套接字可写时(FD_ISSET(sock, &amp;write_flags)),它将发送缓冲区。

    【讨论】:

    • 感谢您的回复。是的,最近几天我一直在阅读 Beej 的教程,但几乎没有得到解决。我尝试将其添加到我的代码中,但是现在每当我在该终端上的任一终端中键入内容时都会出现分段错误(在服务器中键入“hi”,服务器段错误,客户端关闭正常,反之亦然)。我尝试了 getline 以确保它不仅仅是 fgets。 GDB 并没有真正帮助 - 告诉我回溯只是 main () 中的 #0 0x08048c93。抱歉问(此时有点绝望),但你知道是什么原因造成的吗?
    • @Sterling 使用 -g 编译。此外,对于初学者来说,声明 outin 如下:char out[255];
    • 奇怪...当我只是更改这些声明时,程序不会出现段错误,但它会直接通过 while 循环并退出。
    • @Sterling 尝试调试它。如果需要,请添加 printf 行。在“选择”printf("Select fired\n"); 等之后添加一个 printf 行。
    • 会的。另一个奇怪的事情可能值得注意...... select 会为这两个程序触发不同的次数,并且每次运行它时触发的次数也不同。有时 10,有时 20,有时 5,然后关闭套接字。不知何故,recv 调用
    【解决方案2】:

    我的程序现在可以正常工作了。

    服务器 -

    #define PORT "4950"
    #define STDIN 0
    
    struct sockaddr name;
    
    void set_nonblock(int socket) {
        int flags;
        flags = fcntl(socket,F_GETFL,0);
        assert(flags != -1);
        fcntl(socket, F_SETFL, flags | O_NONBLOCK);
    }
    
    
    // get sockaddr, IPv4 or IPv6:
    void *get_in_addr(struct sockaddr *sa) {
        if (sa->sa_family == AF_INET)
            return &(((struct sockaddr_in*)sa)->sin_addr);
    
        return &(((struct sockaddr_in6*)sa)->sin6_addr);
    }
    
    
    int main(int agrc, char** argv) {
        int status, sock, adrlen, new_sd;
    
        struct addrinfo hints;
        struct addrinfo *servinfo;  //will point to the results
    
        //store the connecting address and size
        struct sockaddr_storage their_addr;
        socklen_t their_addr_size;
    
        fd_set read_flags,write_flags; // the flag sets to be used
        struct timeval waitd = {10, 0};          // the max wait time for an event
        int sel;                      // holds return value for select();
    
    
        //socket infoS
        memset(&hints, 0, sizeof hints); //make sure the struct is empty
        hints.ai_family = AF_INET;
        hints.ai_socktype = SOCK_STREAM; //tcp
        hints.ai_flags = AI_PASSIVE;     //use local-host address
    
        //get server info, put into servinfo
        if ((status = getaddrinfo("127.0.0.1", PORT, &hints, &servinfo)) != 0) {
            fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(status));
            exit(1);
        }
    
        //make socket
        sock = socket(servinfo->ai_family, servinfo->ai_socktype, servinfo->ai_protocol);
        if (sock < 0) {
            printf("\nserver socket failure %m", errno);
            exit(1);
        }
    
        //allow reuse of port
        int yes=1;
        if (setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int)) == -1) {
            perror("setsockopt");
            exit(1);
        }
    
        //unlink and bind
        unlink("127.0.0.1");
        if(bind (sock, servinfo->ai_addr, servinfo->ai_addrlen) < 0) {
            printf("\nBind error %m", errno);
            exit(1);
        }
    
        freeaddrinfo(servinfo);
    
        //listen
        if(listen(sock, 5) < 0) {
            printf("\nListen error %m", errno);
            exit(1);
        }
        their_addr_size = sizeof(their_addr);
    
        //accept
        new_sd = accept(sock, (struct sockaddr*)&their_addr, &their_addr_size);
        if( new_sd < 0) {
            printf("\nAccept error %m", errno);
            exit(1);
        }
    
        //set non blocking
        set_nonblock(new_sd);
        cout<<"\nSuccessful Connection!\n";
    
        char in[255];
        char out[255];
        memset(&in, 0, 255);
        memset(&out, 0, 255);
        int numSent;
        int numRead;
    
        while(1) {
    
            FD_ZERO(&read_flags);
            FD_ZERO(&write_flags);
            FD_SET(new_sd, &read_flags);
            FD_SET(new_sd, &write_flags);
            FD_SET(STDIN_FILENO, &read_flags);
            FD_SET(STDIN_FILENO, &write_flags);
    
            sel = select(new_sd+1, &read_flags, &write_flags, (fd_set*)0, &waitd);
    
            //if an error with select
            if(sel < 0)
                continue;
    
            //socket ready for reading
            if(FD_ISSET(new_sd, &read_flags)) {
    
                //clear set
                FD_CLR(new_sd, &read_flags);
    
                memset(&in, 0, 255);
    
                numRead = recv(new_sd, in, 255, 0);
                if(numRead <= 0) {
                    printf("\nClosing socket");
                    close(new_sd);
                    break;
                }
                else if(in[0] != '\0')
                    cout<<"\nClient: "<<in;
    
            }   //end if ready for read
    
            //if stdin is ready to be read
            if(FD_ISSET(STDIN_FILENO, &read_flags))
                fgets(out, 255, stdin);
    
    
            //socket ready for writing
            if(FD_ISSET(new_sd, &write_flags)) {
                //printf("\nSocket ready for write");
                FD_CLR(new_sd, &write_flags);
                send(new_sd, out, 255, 0);
                memset(&out, 0, 255);
            }   //end if
        }   //end while
    
       cout<<"\n\nExiting normally\n";
        return 0;
    }
    

    客户端基本上是一样的……唯一的区别就是没有倾听和接受。

    【讨论】:

    • “我解释太累了,所以我只是发布代码来与上面的代码进行比较,如果将来有人需要信息” 那不是写有用答案的方法-1
    • 在本节中:FD_SET(new_sd, &amp;read_flags); FD_SET(new_sd, &amp;write_flags); FD_SET(STDIN_FILENO, &amp;read_flags); FD_SET(STDIN_FILENO, &amp;write_flags); 前 2 个 FD_SET 被后两个覆盖。为什么要打扰?
    • @EntangledLoops 我不同意——这四行会将 new_sd 和 STDIN_FILENO 添加到 read_flags 和 write_flags 集。来自fd_set man pagesFD_SET() 和 FD_CLR() 分别从集合中添加和删除给定的文件描述符。
    • @pepan 你显然是正确的,因为我现在看它;不记得十月读它时我在想什么。 +1 一个完整的例子
    【解决方案3】:

    我只想补充一点,上面的示例在 Linux 上可能无法按预期工作。在 Linux 上,waitd 可以通过 select 进行修改。所以对于 Linux,waitd 应该在 select 之前重写。

    请参阅手册页中的“超时”部分进行选择。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-31
      • 2013-10-15
      相关资源
      最近更新 更多