2010-04-25 10 views
7

Stavo provando a misurare la velocità di un server TCP che sto scrivendo, e ho notato che potrebbe esserci un problema fondamentale nel misurare la velocità delle chiamate connect(): se mi collego in modo non bloccante , le operazioni connect() diventano molto lente dopo alcuni secondi. Ecco il codice di esempio in Python:Perché TCP() non bloccante è occasionalmente così lento su Linux?

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

Con NonBlockingConnect, più connect() le operazioni sono veloci, ma in ogni pochi secondi ci sembra essere una connect operazione() che richiede almeno 2 secondi (come indicato dalla 5 lettere consecutive P sull'output). Utilizzando sock.connect anziché NonBlockingConnect tutte le operazioni di connessione sembrano essere veloci.

Come è possibile sbarazzarsi di questi collegamenti lenti() s?

Io corro Ubuntu Karmic desktop con il kernel standard PAE:

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

E 'strano che non ci siano ritardi con strace -f ./conn.py.

È strano che non ci siano ritardi se interrompo il molto veloce time.sleep.

E 'strano che non ci siano ritardi sul mio sistema Ubuntu Hardy:

Tutti questi sistemi sono influenzati (con 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 

E' strano che il seguito Il sistema Debian Lenny non è interessato:

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

FYI Non ci sono ritardi se utilizzo un socket AF_UNIX.

FYI ottengo lo stesso comportamento se a implementare il cliente in 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; 
} 

risposta

1

Dato che sleep e strace fanno sì che il problema si risolva, sembra un problema di pianificazione in cui il processo del server non viene programmato per accettare la connessione. Anche se non programmare il server in un periodo di 2 secondi è un tempo terribilmente lungo.

Forse uno strumento come latencytop può aiutare a rivelare cosa sta succedendo. Probabilmente lo puoi eseguire solo su Karmic (2.6.31), poiché gli altri kernel sono troppo vecchi, penso.

+1

Il processo del server viene effettivamente pianificato. Quando eseguo un accept non blocking() + select() nel processo del server, select() restituisce un timeout. Quindi 1. il server non accetta il blocco(); 2. il server seleziona (timeout = 3) 3.client non connect-connecting(); 4. il server seleziona (timeout = 3); 5. entrambi select() s restituiscono con un timeout. Quindi il server vuole accettare(), il client vuole connettersi(), quindi perché la connessione non avviene in ogni caso 500? – pts

1

Sei sicuro che sia la chiamata connect() il lento? nella maggior parte delle librerie, la risoluzione DNS è sempre bloccante. controlla se l'uso di indirizzi IP fa sempre la differenza.

+0

Sto eseguendo il codice che ho incluso nella domanda. Non c'è una risoluzione DNS lì. – pts

+0

nota che 'sock.connect ((host, porta))' risolverà volentieri 'host' se non assomiglia ad un numero IP. – Javier

+1

So che 'sock.connect ((host, porta))' risolverà 'host'. Ma questo è del tutto irrilevante nel mio caso, nel codice di esempio nella domanda che uso gli indirizzi IP, ed è ancora lento. Inoltre ho analizzato il programma con 'strace', e non tenta alcuna risoluzione DNS, o qualsiasi altra cosa che sia ovviamente lenta. – pts

Problemi correlati