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

12

我刚开始学习socket编程,但是我不太理解 select()FD_SET() 是如何工作的。

为了理解它们,我修改了Beej's教程中的一个例子。我的想法是在每次迭代中等待 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, '\0', 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;
}
2个回答

7
您正确使用了FD_SET。您要求select()在文件描述符0(标准输入)准备好读取时通知您。它确实这样做了。问题是,您没有读取标准输入以消耗可用的输入。因此,当您循环回调用select()时,标准输入仍然准备好读取,并立即返回。
使用select()(或通常更好的选项poll())的正确方法如下:
  • 将涉及的所有文件描述符设置为非阻塞模式。对于大多数用例,您希望这样做,因为您希望在select()(或poll())内执行所有阻塞操作,而不是在服务单个文件描述符时执行。
  • 设置select()poll()的参数以注册您感兴趣的文件描述符。
  • 调用select()poll()
  • 通过消耗输入和写入输出来响应其给出的通知。
  • 回到步骤2。
附:您的UDP套接字sockfd与任何事情有关吗?您打开它但未用于任何内容。

这与任何事情都没有关系。抱歉,我在删除问题中的不必要部分时做得不好。 - user1161604

5
问题在于你从文件描述符中并没有读取数据。
select() 报告状态而不是事件。
因此,第一次 select() 返回后,总是有可供读取的数据,所以 select() 会立即报告。
顺便说一下,无论你从哪里得到这个代码,它看起来已经有15年的历史了。poll() 通常比 select() 更方便使用,getaddrinfo() 比 gethostbyname() 更方便使用。而且它们的效果更好。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接