2010-04-25 70 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