2010-04-25 69 views
7

我試圖測量我正在編寫的TCP服務器的速度,並且我注意到可能存在測量connect()調用速度的基本問題:如果以非阻塞方式連接,幾秒鐘後connect()操作變得非常慢。這裏是在Python示例代碼:爲什麼Linux上的非阻塞TCP連接()偶爾會這麼慢?

#! /usr/bin/python2.4 
import errno 
import os 
import select 
import socket 
import sys 
import time 

def NonBlockingConnect(sock, addr): 
    #time.sleep(0.0001) # Fixes the problem. 
    while True: 
    try: 
     return sock.connect(addr) 
    except socket.error, e: 
     if e.args[0] not in (errno.EINPROGRESS, errno.EALREADY): 
     raise 
     os.write(2, '^') 
     if not select.select((), (sock,),(), 0.5)[1]: 
     os.write(2, 'P') 

def InfiniteClient(addr): 
    while True: 
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) 
    sock.setblocking(0) 
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 
    # sock.connect(addr) 
    NonBlockingConnect(sock, addr) 
    sock.close() 
    os.write(2, '.') 

def InfiniteServer(server_socket): 
    while True: 
    sock, addr = server_socket.accept() 
    sock.close() 

server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) 
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 
server_socket.bind(('127.0.0.1', 45454)) 
server_socket.listen(128) 

if os.fork(): # Parent. 
    InfiniteServer(server_socket) 
else: 
    addr = server_socket.getsockname() 
    server_socket.close() 
    InfiniteClient(addr) 

隨着NonBlockingConnect,大多數連接()操作的速度快,但在每幾秒鐘那裏恰好是一個connect()操作這需要至少2秒(由所指示的輸出上連續輸出5個字符P)。通過使用sock.connect而不是NonBlockingConnect,所有連接操作看起來都很快。

如何才能擺脫這些慢連接()?

我運行Ubuntu桌面業報與標準PAE內核:

Linux narancs 2.6.31-20-generic-pae #57-Ubuntu SMP Mon Feb 8 10:23:59 UTC 2010 i686 GNU/Linux 

有與strace -f ./conn.py沒有延誤很奇怪。

奇怪的是,如果我取消註釋非常快的time.sleep沒有延遲。

,有我的Ubuntu哈代的系統上沒有延誤很奇怪:

所有這些系統都受到影響(運行Ubuntu的業報,Ubuntu的哈代的是Debian Etch):

Linux narancs 2.6.31-20-generic-pae #57-Ubuntu SMP Mon Feb 8 10:23:59 UTC 2010 i686 GNU/Linux 
Linux t 2.6.24-grseC#1 SMP Thu Apr 24 14:15:58 CEST 2008 x86_64 GNU/Linux 
Linux geekpad 2.6.24-24-generiC#1 SMP Fri Sep 18 16:49:39 UTC 2009 i686 GNU/Linux 

很奇怪的是,以下Debian的萊尼系統不會受到影響:

Linux t 2.6.31.5 #2 SMP Thu Nov 5 15:33:05 CET 2009 i686 GNU/Linux 

FYI有沒有延遲,如果我使用AF_UNIX插座。

FYI我得到同樣的行爲,如果我實現客戶端在C:

/* by [email protected] at Sun Apr 25 20:47:24 CEST 2010 */ 
#include <arpa/inet.h> 
#include <errno.h> 
#include <fcntl.h> 
#include <netinet/in.h> 
#include <stdio.h> 
#include <string.h> 
#include <sys/select.h> 
#include <sys/socket.h> 
#include <unistd.h> 

static int work(void) { 
    fd_set rset; 
    fd_set wset; 
    fd_set eset; 
    socklen_t sl; 
    struct timeval timeout; 
    struct sockaddr_in sa; 
    int sd, i, j; 
    long l; 
    sd = socket(AF_INET, SOCK_STREAM, 0); 
    if (sd < 0) { 
    perror("socket"); 
    return 2; 
    } 
    l = fcntl(sd, F_GETFL, 0); 
    if (l < 0) { 
    perror("fcntl-getfl"); 
    close(sd); 
    return 2; 
    } 
    if (0 != fcntl(sd, F_SETFL, l | O_NONBLOCK)) { 
    perror("fcntl-setfl"); 
    close(sd); 
    return 2; 
    } 
    memset(&sa, '\0', sizeof(sa)); 
    sa.sin_family = AF_INET; 
    sa.sin_port = htons(45454); 
    sa.sin_addr.s_addr = inet_addr("127.0.0.1"); 
    while (0 != connect(sd, (struct sockaddr*)&sa, sizeof sa)) { 
    if (errno != EAGAIN && errno != EINPROGRESS && errno != EALREADY) { 
     perror("connect"); 
     close(sd); 
     return 2; 
    } 
    FD_ZERO(&rset); 
    FD_ZERO(&wset); 
    FD_ZERO(&eset); 

    j = 0; 
    do { 
     timeout.tv_sec = 0; 
     timeout.tv_usec = 100 * 1000; /* 0.1 sec */ 
     FD_SET(sd, &wset); 
     FD_SET(sd, &eset); 
     i = select(sd + 1, &rset, &wset, &eset, &timeout); 
     if (i < 0) { 
     perror("select"); 
     close(sd); 
     return 2; 
     } 
     if (++j == 5) { 
     (void)write(2, "P", 1); 
     j = 0; 
     } 
    } while (i == 0); 
    sl = sizeof i; 
    if (0 != getsockopt(sd, SOL_SOCKET, SO_ERROR, &i, &sl)) { 
     perror("getsockopt"); 
     close(sd); 
     return 2; 
    } 
    if (i != 0) { 
     if (i == ECONNRESET) { 
     (void)write(2, "R", 1); 
     close(sd); 
     return -3; 
     } 
     fprintf(stderr, "connect-SO_ERROR: %s\n", strerror(i)); 
     close(sd); 
     return 2; 
    } 
    } 
    close(sd); 
    return 0; 
} 

int main(int argc, char**argv) { 
    int i; 
    (void)argc; 
    (void)argv; 
    while ((i = work()) <= 0) (void)write(2, ".", 1); 
    return i; 
} 

回答

1

考慮到睡眠和strace導致問題消失,它看起來像一些調度問題,其中服務器進程沒有計劃接受連接。雖然不在2秒內安排服務器是非常長的時間。

也許像latencytop這樣的工具可能有助於揭示發生了什麼。你可能只能在Karmic(2.6.31)上運行它,因爲其他內核太舊我認爲。

+1

服務器進程確實得到安排。當我在服務器進程中執行非阻塞accept()+ select()時,select()會返回超時。所以1.服務器做非阻塞accept(); 2.服務器確實選擇(超時= 3)3。客戶端不阻塞connect(); 4.服務器選擇(超時= 3); 5. select()返回一個超時值。所以服務器想要接受(),客戶端想要連接(),那麼爲什麼連接不會發生在每個第500個案例中? – pts 2010-04-27 15:23:14

1

你確定它是connect()調用慢呢?在大多數圖書館中,DNS解析始終受阻。檢查是否始終使用IP地址有什麼不同。

+0

我正在運行包含在問題中的代碼。那裏沒有DNS解析。 – pts 2010-04-25 16:01:24

+0

請注意,如果sock.connect((host,port))看起來不像IP號碼,它很樂意解析host。 – Javier 2010-04-26 02:35:26

+1

我知道'sock.connect((host,port))'會解析'host'。但對我而言,這與我在使用IP地址的問題中的示例代碼完全無關,而且它仍然很慢。我還用'strace'分析了程序,它不會嘗試任何DNS解析,或者其他任何顯然很慢的事情。 – pts 2010-04-26 08:49:03