2012-02-06 3 views
6

Tôi mới làm quen với lập trình socket và tôi gặp sự cố khi hiểu cách hoạt động của select()FD_SET().Làm thế nào để sử dụng lựa chọn và FD_SET trong lập trình socket?

Tôi sửa đổi một ví dụ từ hướng dẫn của Beej để tìm hiểu. Những gì tôi muốn làm trong vòng lặp for là ở mỗi lần lặp lại, tôi đợi trong 4 giây. Nếu đọc có sẵn, tôi sẽ in "Một phím đã được nhấn" và nếu nó hết thời gian chờ, sau đó nó sẽ in "Hết giờ." Sau đó, tôi sẽ xóa các thiết lập và lặp lại quá trình 9 lần. Nhưng có vẻ như một khi bộ mô tả tập tin 0 được thiết lập, nó không bao giờ bị bỏ đặt ngay cả sau khi gọi tới FD_ZERO() và/hoặc FD_CLR(). Nói cách khác sau khi tôi nhấn một phím trong vòng lặp đầu tiên của vòng lặp, bộ mô tả tập tin được thiết lập cho phần còn lại của các lần lặp lại và không còn chờ đợi nữa được thực hiện. Vì vậy, phải có một cái gì đó tôi đang mất tích, nhưng tôi không biết những gì.

#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; 
} 

Trả lời

3

Bạn đang sử dụng FD_SET chính xác. Bạn đang yêu cầu select() thông báo cho bạn khi bộ mô tả tập tin 0 (đầu vào tiêu chuẩn) sẵn sàng để đọc. Nó làm điều này. Vấn đề là bạn không đọc đầu vào tiêu chuẩn để tiêu thụ đầu vào có sẵn. Vì vậy, khi bạn lặp lại và gọi lại select(), đầu vào chuẩn vẫn sẵn sàng để đọc và trả về ngay lập tức.

Các cách chính xác để sử dụng select() (hoặc poll(), mà thường là một lựa chọn tốt hơn là) là:

  • Đặt tất cả các file descriptor tham gia sang chế độ nonblocking. Đối với hầu hết các trường hợp sử dụng, bạn muốn điều này vì bạn muốn thực hiện tất cả chặn của mình bên trong select() (hoặc poll()), không phải trong khi phục vụ các mô tả tệp riêng lẻ.
  • Thiết lập các đối số select() hoặc poll() đăng ký mà mô tả tập tin mà bạn quan tâm.
  • Gọi select() hoặc poll().
  • Phản hồi các thông báo mà nó cung cấp cho bạn bằng cách tiêu thụ đầu vào và đầu ra ghi.
  • Vòng lại bước 2.

P.S .: Điều gì làm cổng UDP của bạn sockfd phải làm với bất cứ điều gì? Bạn mở nó nhưng nó không được sử dụng cho bất cứ điều gì.

+0

Nó không phải làm gì cả. Xin lỗi, tôi đã làm một công việc tồi tệ khi xóa các phần không cần thiết cho câu hỏi của tôi. – user1161604

3

Vấn đề là bạn không bao giờ đọc dữ liệu từ bộ mô tả tệp.

chọn() báo cáo, không phải sự kiện. Vì vậy, sau lần đầu tiên chọn() trả về, luôn có dữ liệu để đọc, vì vậy hãy chọn() báo cáo ngay lập tức.

PS. Bạn sẽ nhận được mã từ đó, có vẻ như khoảng 15 tuổi. poll() thường thuận tiện hơn select(), và getaddrinfo() thuận tiện hơn gethostbyname(). [Và họ cũng làm việc tốt hơn].