2010-04-25 23 views
7

Estaba tratando de medir la velocidad de un servidor TCP que estoy escribiendo, y he notado que podría haber un problema fundamental para medir la velocidad de las llamadas a connect(): si me conecto de forma no bloqueante , las operaciones connect() se vuelven muy lentas después de unos segundos. Aquí está el código de ejemplo en Python:¿Por qué una conexión TCP no bloqueante() ocasionalmente es tan lenta en 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, la mayoría se comunica() las operaciones son rápidos, pero en cada pocos segundos no pasa a ser una operación connect() que toma por lo menos 2 segundos (según lo indicado por 5 cartas consecutivas P en la salida). Al usar sock.connect en lugar de NonBlockingConnect, todas las operaciones de conexión parecen ser rápidas.

¿Cómo es posible deshacerse de estos slow connect() s?

estoy corriendo escritorio Ubuntu Karmic con el núcleo estándar PAE:

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

Es extraño que no hay retrasos con strace -f ./conn.py.

Es extraño que no haya retrasos si descomiento la muy rápida time.sleep.

Es extraño que no hay retrasos en mi sistema Ubuntu Hardy:

Todos estos sistemas se ven afectados (que se ejecuta 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 extraño que los siguientes El sistema Debian Lenny no se ve afectado:

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

FYI No hay retrasos si utilizo una toma AF_UNIX.

FYI consigo el mismo comportamiento si puedo implementar el cliente en 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; 
} 

Respuesta

1

Dado que sleep y strace hacen que el problema desaparezca, parece que hay un problema de programación en el que el proceso del servidor no se programa para aceptar la conexión. Aunque no programar el servidor en un período de 2 segundos es un tiempo terriblemente largo.

Quizás una herramienta como latencytop pueda ayudar a revelar lo que está sucediendo. Probablemente solo puedas ejecutar eso en Karmic (2.6.31), ya que los otros kernels son demasiado viejos, creo.

+1

De hecho, el proceso del servidor se programa. Cuando hago un accept() + a no select() en el proceso del servidor, el select() regresa con un tiempo de espera. Entonces 1. el servidor no acepta accept(); 2. servidor selecciona (tiempo de espera = 3) 3.el cliente no conecta connect(); 4. servidor selecciona (tiempo de espera = 3); 5. ambos select() regresan con un tiempo de espera excedido. Entonces, el servidor quiere aceptar(), el cliente quiere conectarse(), entonces ¿por qué la conexión no ocurre en cada caso número 500? – pts

1

¿Estás seguro de que es la llamada connect() el lento? en la mayoría de las bibliotecas, la resolución de DNS siempre está bloqueando. compruebe si siempre usando direcciones IP hace alguna diferencia.

+0

Estoy ejecutando el código que he incluido en la pregunta. No hay resolución de DNS allí. – pts

+0

tenga en cuenta que 'sock.connect ((host, puerto))' gustosamente resolverá 'host' si no se parece a un número de IP. – Javier

+1

Sé que 'sock.connect ((host, puerto))' resolverá 'host'. Pero eso es completamente irrelevante en mi caso, en el código de ejemplo de la pregunta utilizo direcciones IP, y todavía es lento. También analicé el programa con 'strace', y no intenta ninguna resolución DNS, o cualquier otra cosa que obviamente es lenta. – pts

Cuestiones relacionadas