2010-04-25 3 views
7

Ich habe versucht, die Geschwindigkeit eines TCP-Servers zu messen, den ich schreibe, und ich habe bemerkt, dass es ein grundlegendes Problem der Messung der Geschwindigkeit der connect() -Aufrufe geben könnte: wenn ich in einer nicht-blockierenden Weise verbinde Die Vorgänge von connect() werden nach einigen Sekunden sehr langsam. Hier ist der Beispielcode in Python:Warum ist ein non-blocking TCP connect() gelegentlich unter Linux so langsam?

#! /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) 

Mit NonBlockingConnect meist connect() Operationen sind schnell, aber alle paar Sekunden dort geschehen ein connect() Betrieb zu sein, die mindestens 2 Sekunden in Anspruch nimmt (wie durch 5 aufeinanderfolgende P Buchstaben auf dem Ausgang). Durch Verwendung von sock.connect anstelle von NonBlockingConnect scheinen alle Verbindungsoperationen schnell zu sein.

Wie ist es möglich, diese langsamen connect() s loszuwerden?

Ich bin mit Ubuntu Karmic Desktop mit dem Standard-PAE-Kernel:

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

Es ist seltsam, dass es mit strace -f ./conn.py keine Verzögerungen gibt.

Es ist seltsam, dass es keine Verzögerungen gibt, wenn ich die sehr schnelle time.sleep auskommentiere.

Es ist seltsam, dass es keine Verzögerungen auf meinem Ubuntu Hardy Systems sind:

Alle diese Systeme sind betroffen (Ubuntu Karmic, Ubuntu Hardy, 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 

Es ist seltsam, dass die folgende Debian Lenny-System ist nicht betroffen:

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

FYI Es gibt keine Verzögerungen, wenn ich einen AF_UNIX-Socket verwenden.

FYI bekomme ich das gleiche Verhalten, wenn ich den Client in C implementieren:

/* 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; 
} 

Antwort

1

Vorausgesetzt, dass Schlaf und Strace das Problem weggehen, sieht es wie ein Zeitplanproblem aus, bei dem der Serverprozess nicht geplant wird, die Verbindung zu akzeptieren. Obwohl das Planen des Servers in einer 2-Sekunden-Periode nicht sehr lange dauert.

Vielleicht kann ein Werkzeug wie laventytop helfen zu enthüllen, was vor sich geht. Sie können das wahrscheinlich nur auf Karmic (2.6.31) ausführen, da die anderen Kernel zu alt sind, denke ich.

+1

Der Serverprozess wird tatsächlich geplant. Wenn ich im Server-Prozess eine nicht-blockierende accept() + a select() -Methode mache, wird select() mit einem Timeout zurückgegeben. Also 1. Server nicht blockierend accept(); 2. Server wählt (Timeout = 3) 3.Client führt nicht blockierende Verbindung() durch; 4. Server wählt (Timeout = 3); 5. Beide select() s kehren mit einem Timeout zurück. Also will der Server akzeptieren(), der Client will sich verbinden(), warum passiert dann nicht die Verbindung in jedem 500. Fall? – pts

1

Sind Sie sicher, dass es der connect() Anruf der langsamen? In den meisten Bibliotheken blockiert die DNS-Auflösung immer. Überprüfen Sie, ob die Verwendung von IP-Adressen einen Unterschied macht.

+0

Ich führe den Code, den ich in die Frage aufgenommen habe. Dort gibt es keine DNS-Auflösung. – pts

+0

Beachten Sie, dass 'sock.connect ((host, port))' 'host' gerne auflöst, wenn es nicht wie eine IP-Nummer aussieht. – Javier

+1

Ich weiß, dass 'sock.connect ((host, port))' 'Host' auflösen wird. Aber das ist in meinem Fall völlig irrelevant, im Beispielcode in der Frage verwende ich IP-Adressen, und es ist immer noch langsam. Außerdem habe ich das Programm mit "strace" analysiert, und es wird keine DNS-Auflösung versucht, oder irgendetwas anderes, was offensichtlich langsam ist. – pts