2016-03-01 82 views
0

Linux manpage SO_RCVTIMEO說:SO_RCVTIMEO醒來得早

指定接收或這段時間發送超時,直到報告錯誤......如果輸入或輸出功能塊...... [和]沒有數據被傳輸和達到超時,則返回-1並將errno設置爲EAGAIN或EWOULDBLOCK,或EINPROGRESS(用於連接(2))

這聽起來好像所述I/O應該至少在之前將SO_RCVTIMEO至少返回給調用者。同時,at the Open Group,他們的文件相反:

設置指定的時間的輸入功能等待,直到它完成最大量的超時值。

那麼,最小阻塞時間還是最大阻塞時間?答案似乎是:是的。這是當我問一個.500s超時Linux系統上會發生什麼:

time: 0.497054 result: 0 
time: 0.495352 result: 0 
time: 0.504948 result: 0 
time: 0.495119 result: 0 
time: 0.507884 result: 0 
time: 0.491892 result: 0 
time: 0.500764 result: 0 

我們看到的時間是錯誤的,通常多達7毫秒左右,這是一個很長一段時間是錯誤的。錯誤發生在兩個方向。與此同時,在達爾文:

time: 0.500426 result: -1 
time: 0.501144 result: -1 
time: 0.500507 result: -1 
time: 0.501119 result: -1 
time: 0.501016 result: -1 
time: 0.500540 result: -1 
time: 0.500127 result: -1 
time: 0.500815 result: -1 
time: 0.500341 result: -1 
time: 0.500871 result: -1 
time: 0.500835 result: -1 
time: 0.501138 result: -1 
time: 0.501087 result: -1 
time: 0.501153 result: -1 
time: 0.501149 result: -1 

錯誤要低得多(〜1毫秒),但依然存在,他們清楚地解釋爲500ms的最短時間,而不是最大化。

現在的一些問題:

  • 是SO_RCVTIMEO應該是最小或阻止呼叫者的最大持續時間?
  • 如果是最長持續時間,最小值是多少?當要求500ms超時時,一個實現是否可以自由選擇非阻塞讀取?
  • 如果這是最短的持續時間,達爾文是錯誤的嗎?
  • 如果我想保證我試圖讀取至少 500毫秒,我應該繼續嘗試循環,直到500毫秒消逝?實施「至少X毫秒」行爲的「正確方法」是什麼?
  • 爲什麼Linux上的呼叫有很多變化?錯誤的來源是什麼?
  • 是否有更好的API我應該使用從套接字讀取?

代碼我用來衡量這一點:

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <unistd.h> 
#include <sys/types.h> 
#include <sys/socket.h> 
#include <netinet/in.h> 
#include <time.h> 
#include <fcntl.h> 

#ifdef __MACH__ 
#include <mach/clock.h> 
#include <mach/mach.h> 
#endif 
void error(const char *msg) 
{ 
    perror(msg); 
    exit(1); 
} 

struct timespec os_time() { 
    struct timespec ts; 
    #ifdef __MACH__ // OS X does not have clock_gettime, use clock_get_time 
    clock_serv_t cclock; 
    mach_timespec_t mts; 
    host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock); 
    clock_get_time(cclock, &mts); 
    mach_port_deallocate(mach_task_self(), cclock); 
    ts.tv_sec = mts.tv_sec; 
    ts.tv_nsec = mts.tv_nsec; 

    #else 
    clock_gettime(CLOCK_REALTIME, &ts); 
    #endif 
    return ts; 
} 

int main(int argc, char *argv[]) 
{ 
    int sockfd, newsockfd, portno; 
    socklen_t clilen; 
    char buffer[256]; 
    struct sockaddr_in serv_addr, cli_addr; 
    int n; 
    if (argc < 2) { 
     fprintf(stderr,"ERROR, no port provided\n"); 
     exit(1); 
    } 
    sockfd = socket(AF_INET, SOCK_STREAM, 0); 
    if (sockfd < 0) 
     error("ERROR opening socket"); 
    bzero((char *) &serv_addr, sizeof(serv_addr)); 
    portno = atoi(argv[1]); 
    serv_addr.sin_family = AF_INET; 
    serv_addr.sin_addr.s_addr = INADDR_ANY; 
    serv_addr.sin_port = htons(portno); 
    if (bind(sockfd, (struct sockaddr *) &serv_addr, 
       sizeof(serv_addr)) < 0) 
       error("ERROR on binding"); 
    listen(sockfd,5); 
    clilen = sizeof(cli_addr); 
    newsockfd = accept(sockfd, 
       (struct sockaddr *) &cli_addr, 
       &clilen); 
    if (newsockfd < 0) 
      error("ERROR on accept"); 
    for (int i = 0; i < 100; i++) { 
     struct timeval tv; 

     tv.tv_sec = 0; 
     tv.tv_usec = 500000; 
     char buf[1]; 
     if (setsockopt(newsockfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv,sizeof(struct timeval)) != 0){ 
      error("setsockopt error"); 
     } 
     struct timespec start = os_time(); 
     int result = recv(newsockfd,buf,1,0); 
     struct timespec end = os_time(); 

     double end_time = (double)end.tv_sec + ((double)end.tv_nsec)/1.0E9; 
     double start_time = (double)start.tv_sec + ((double)start.tv_nsec)/1.0E9; 
     printf("time: %f result: %d\n",end_time-start_time, result); 
    } 
    return 0; 
} 

繁殖:

clang test.c && ./a.out 5551 & 
telnet localhost 5551 
time: 0.497839 result: 0 
time: 0.501052 result: 0 
time: 0.498565 result: 0 
time: 0.500741 result: 0 
time: 0.500108 result: 0 
time: 0.500244 result: 0 
time: 0.499040 result: 0 
time: 0.500212 result: 0 
time: 0.500137 result: 0 
time: 0.499920 result: 0 
time: 0.500758 result: 0 
time: 0.498068 result: 0 

回答

3

這聽起來好像在I/O應返回執行之前等待至少SO_RCVTIMEO呼叫者,召集者。

編號它應該等待最多的超時時間。如果數據已經存在,或者在超時之前到達,那麼該方法將在此時返回,而不等待超時過期。

同時,在公開組,他們的文件相反:

設置指定的時間的輸入功能等待,直到它完成的最大量的超時值。

那麼,最小阻塞時間還是最大阻塞時間?

最大阻塞時間。

他們明確地將500ms解釋爲最小時間,而不是最大值。

在這裏,您要問及測試兩個不同的問題:定時器的解析度和操作系統在超時後重新安排線程的速度。沒有指定。

SO_RCVTIMEO應該是阻止呼叫者的最小或最大持續時間?

在其(即操作系統)分辨率內的最大值,並且受到進一步的調度延遲的影響。

如果是最長持續時間,最小值是多少?

零。

當要求500ms超時時,一個實現是不是可以自由選擇非阻塞讀取?

當然是了。如果數據已經存在於套接字接收緩衝區中,recv()將立即傳輸該數據並返回。它爲什麼要等待?

如果是最短持續時間,達爾文是錯的嗎?

不,它只是有不同的分辨率和重新安排延遲。

如果我想保證我試圖讀取至少500ms,我應該繼續嘗試循環,直到500ms過去?實施「至少X毫秒」行爲的「正確方法」是什麼?

你必須用你自己的計時器做到這一點,但我看不到這一點。如果數據已經存在,或者提前到達,您爲什麼要延遲?

爲什麼Linux上的呼叫有很多變化?錯誤的來源是什麼?

定時器抖動;重新調度抖動。這不是一個實時操作系統

是否有更好的API我應該使用從套接字讀取而不是?

定義'更好'。你的期望看起來很奇怪。這個API對於其他人來說已經足夠好了30多年了。

+0

「如果數據已經存在,或者在超時之前到達,則該方法在該點返回,而不等待超時過期。」 我明白這一點。我所問的情況是數據沒有到達。在這種情況下,Linux在500ms超時時間內放棄490ms。這是否合理?或者什麼是阻止Linux在500毫秒超時時間內以0毫秒放棄? 還有另一種方式:如果我知道某些數據從現在起500毫秒可讀,那麼SO_RCVTIMEO的值將保證讀取成功? – Drew

+0

什麼是防止它是規範,但你需要了解定時器具有粒度。例如在Windows上它是16ms。你無法獲得更多的粒度。 – EJP

+0

規格在哪裏?我可以在哪裏找到Linux上的超時或超時時間? – Drew