Linux socket编程中如何使用select和FD_SET?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9164848/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 04:25:17  来源:igfitidea点击:

How to use select and FD_SET in socket programming?

linuxsocketsselect

提问by user1161604

I am new to socket programming, and I am having trouble understanding how select()and FD_SET()works.

我是新来的socket编程,而我无法理解如何select()FD_SET()作品。

I modify an example from Beej's tutorial in an attempt to figure it out. What I want to do in the for loop is at each iterations I wait for 4 seconds. If a read is available, I would print "A key was pressed" and if it timeout, then it would print "Timed out." Then I would clear the set and repeat the process 9 more times. But it seems that once file descriptor 0 is set, it never gets unset even after a call to FD_ZERO()and/or FD_CLR(). In other words after I press a key in the first iteration of the loop the file descriptor is set for the rest of the iterations and no more waiting is done. So there must be something I am missing, but I don't know what.

我修改了 Beej 教程中的一个例子,试图弄清楚。我想在 for 循环中做的是在每次迭代时等待 4 秒。如果读取可用,我会打印“按下了一个键”,如果超时,则会打印“超时”。然后我会清除设置并再重复这个过程 9 次。但似乎一旦文件描述符 0 被设置,即使在调用FD_ZERO()和/或之后它也永远不会被取消设置FD_CLR()。换句话说,在循环的第一次迭代中按下一个键后,文件描述符被设置为其余的迭代并且不再等待。所以一定有我想念的东西,但我不知道是什么。

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

#define SERVERPORT 4950

int main(int argc, char *argv[]) {
    struct sockaddr_in their_addr; // connector's address information
    struct hostent *he;
    int numbytes;
    int broadcast = 1;

    if ((he=gethostbyname(argv[1])) == NULL) {  // get the host info
        perror("gethostbyname");
        exit(1);
    }

    // this call is what allows broadcast packets to be sent:
    if (setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &broadcast,
        sizeof broadcast) == -1) {
        perror("setsockopt (SO_BROADCAST)");
        exit(1);
    }
    their_addr.sin_family = AF_INET;     // host byte order
    their_addr.sin_port = htons(SERVERPORT); // short, network byte order
    their_addr.sin_addr = *((struct in_addr *)he->h_addr);
    memset(their_addr.sin_zero, '##代码##', sizeof their_addr.sin_zero);

    struct timeval tv;
    fd_set broadcastfds;

    int i;
    for(i=0; i < 10; i++) {
        tv.tv_sec = 4;
        tv.tv_usec = 500000;

        FD_ZERO(&broadcastfds);
        FD_CLR(0, &broadcastfds);
        FD_SET(0, &broadcastfds);
        if(select(0+1, &broadcastfds, NULL, NULL, &tv) == -1) perror("select");

        if (FD_ISSET(0, &broadcastfds)) printf("A key was pressed!\n");
        else printf("Timed out.\n");
        fflush(stdout); 
    }   
    close(sockfd);
    return 0;
}

采纳答案by Celada

You are using FD_SETcorrectly. You are asking select()to notify you when file descriptor 0 (standard input) is ready for reading. It does this. The problem is that you are not reading standard input to consume the input that is available. So when you loop back and call select()again, standard input is still ready for reading and it returns immediately.

您使用FD_SET正确。您要求select()在文件描述符 0(标准输入)准备好读取时通知您。它这样做。问题是您没有读取标准输入来使用可用的输入。因此,当您循环返回并select()再次调用时,标准输入仍然可以读取并立即返回。

The correct way to use select()(or poll(), which is usually a better option) is:

正确的使用方法select()(或poll(),通常是更好的选择)是:

  • Set all of the file descriptors involved to nonblocking mode. For most use cases you want this because you want to do all your blocking inside select()(or poll()), not while servicing individual file descriptors.
  • Set up the arguments to select()or poll()to register which file descriptors you are interested in.
  • Call select()or poll().
  • React to the notifications it gives you by consuming input and writing output.
  • Loop back to step 2.
  • 将所有涉及的文件描述符设置为非阻塞模式。对于大多数用例,您希望这样做是因为您希望在select()(或poll())内部进行所有阻塞,而不是在为单个文件描述符提供服务时。
  • 设置参数select()poll()注册您感兴趣的文件描述符。
  • 打电话select()poll()
  • 通过使用输入和写入输出来响应它给你的通知。
  • 循环回到步骤 2。

P.S.: What does your UDP socket sockfdhave to do with anything? You open it but it doesn't get used for anything.

PS:你的UDP套接字sockfd与什么有什么关系?你打开它,但它不会被用来做任何事情。

回答by Rcl

The problem is that you never read the data from the file descriptor.

问题是你从来没有从文件描述符中读取数据。

select() reports state, not events.

select() 报告状态,而不是事件。

So after the first time select() returns, there is always data available for reading, so select() reports that immediately.

所以在第一次 select() 返回后,总是有数据可供读取,所以 select() 立即报告。

PS. Whereever you're getting that code from, it looks about 15 years old. poll() is generally more convenient than select(), and getaddrinfo() is more convenient than gethostbyname(). [And they work better, too].

附注。无论您从何处获取该代码,它看起来都有大约 15 年的历史。poll() 一般比 select() 方便,getaddrinfo() 比 gethostbyname() 方便。[而且它们也能更好地工作]。