Herr Groucho, el 25 de agosto a las 11:35 me escribiste:
> Hola.
> Para resolver un problema, se me ha planteado la necesidad de hacer un
> programa que acepte conexiones TCP desde equipos embebidos y les lea datos
> que dichos equipos están recabando en campo.
>
> Actualmente el programa de esos equipos embebidos establece la conexión
> TCP y la mantiene conectada todo el tiempo. Entonces el servidor que
> acepta esas conexiones va a tener un socket conectado con cada equipo todo
> el tiempo. Si hay 10 equipos, tendrá 10 conexiones abiertas. Pero qué pasa
> cuando hay 10000 equipos y por lo tanto 10000 conexiones?
Nada, no debería pasar nada.
> El programa que va a aceptar las conexiones actualmente lanza un proceso
> hijo por cada conexión que acepta. Eso tiene la ventaja de que el
> tratamiento de TCP/IP y la implementación del protocolo de aplicación se
> simplifica (sólo hay un socket del cual preocuparse y si el protocolo de
> aplicación tiene que esperar un evento, directamente se puede bloquear en
> un read() o write() o irse a dormir con timeout en un select()).
> Con 10000 conexiones, tendría 10001 procesos. Es esta una estrategia
> cuerda para manejar 10000 conexiones?
No. Para qué querés tener 10001 procesos si con uno alcanza y sobre.
> - El sistema operativo soportará 10000 procesos (hace unos años el tamaño
> de la tabla de procesos era elegido en tiempo de compilación del kernel,
> en valores miserables como 200)?
No los necesitás.
> - El stack TCP/IP soportará 10000 conexiones?
Sí
> - Qué le pasará al consumo de memoria?
Bah... no debería ser muy alto.
> - Qué le pasará al multitasking si el scheduler tiene que mirar una tabla
> de procesos tan grande cada unas pocas decenas de milisegundos?
No debería pasar nada grave con un scheduler O(1) como el de Linux, pero
los context switches probablemente sí consuman mucho más procesador del
que quisieras.
> Qué otras estrategias son concebibles?
1 solo proceso, un solo thread + epoll[1] (o más simple, libevent[2] o
libev[3]).
> - Un único proceso que atienda y maneje todas las conexiones, usando
> arreglos para guardar los file descriptors de los sockets, select() para
> esperar que pase algo interesante en alguno de ellos e iteraciones por
> todos lados?
Es la versión ineficiente de lo que propongo =)
> - Un proceso que lance hijos, cada uno de los cuales maneje unos pocos
> threads (un híbrido entre los 2)
No creo que tener threads te beneficie en algo, a menos que tengas más de
un core, por supuesto.
> - Varios sistemas operativos independientes, corriendo en máquinas (reales
> o virtuales) separadas ejecutando cada uno alguno de los programas
> anteriores.
Creo que solo te serviría para agregar overhead si usás máquinas
virtuales.
> Sugerencias?
Usá, libev. En la página hay benchmarks[4] (contra la libevent,
para benchmarks de select/poll vs epoll mirá la página de libevent)
que llegan a los 100.000 fds abiertos con tiempos razonables.
Para que veas que es totalmente viable esta opción, te adjunto una
implementación pedorra en Python hecha en 15 minutos. Solo tené en
cuenta que tenés que levantar la cantidad máxima de fds abiertos que
podés tener (ulimit -n) porque por default viene en 1024.
Anda bien con los 10000 sockets, usando poll (NO epoll) y en un lenguaje
interpretado. En mi Athlon XP 2200 (1.8GHz) está el CPU entre 80% y 85% y
el load average entre 2 y 8 (con el X abierto con varias aplicaciones,
incluido el puto Firefox que está usando 8% del CPU vaya uno a saber en
qué). Les puse que mande un mensaje cada 10ms y completa una "ronda" de
los 10000 fds en unos 2 minutos.
Como verás, lo tuyo es un no-problema...
[1] http://linux.die.net/man/4/epoll
[2] http://monkey.org/~provos/libevent/
[3] http://libev.schmorp.de/
[4] http://libev.schmorp.de/bench.html
--
Leandro Lucarella (luca) | Blog colectivo: http://www.mazziblog.com.ar/blog/
----------------------------------------------------------------------------
GPG Key: 5F5A8D05 (F8CD F9A7 BF00 5431 4145 104C 949E BFB6 5F5A 8D05)
----------------------------------------------------------------------------
El techo de mi cuarto lleno de galaxias
import sys
import socket
import time
N = 10000
sockets = list()
print 'creating', N, 'sockets'
for i in range(N):
if i and not (i % 500):
print i, 'created so far'
s = socket.socket()
try:
s.connect(('localhost', 9998))
except Exception, e:
print 'at socket', i, '-> error:', e
sockets.append(s)
while True:
print 'sending...'
for i, s in enumerate(sockets):
time.sleep(0.01)
s.send(str(i))
time.sleep(0.01)
import sys
import socket
from select import poll, POLLIN, POLLPRI, POLLERR
N = 10000
server_socket = socket.socket()
print 'server socket fd =', server_socket.fileno()
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(('localhost', 9998))
server_socket.listen(5)
pollobj = poll()
flags = POLLIN | POLLPRI | POLLERR
clients = dict()
for i in range(N):
(client_socket, client_addr) = server_socket.accept()
pollobj.register(client_socket, flags)
clients[client_socket.fileno()] = client_socket
event_type = {
POLLIN: 'POLLIN',
POLLPRI: 'POLLPRI',
POLLERR: 'POLLERR',
}
#pollobj.register(server_socket.fileno(), flags)
def unreg(fd):
pollobj.unregister(fd)
del clients[fd]
while True:
fd_list = pollobj.poll()
if not fd_list:
continue
for (fd, event) in fd_list:
print 'new event', event_type[event], 'on fd', fd
if fd == server_socket.fileno():
if event == POLLERR:
sys.exit(1)
(client_socket, client_addr) = server_socket.accept()
pollobj.register(client_socket, flags)
clients[client_socket.fileno()] = client_socket
else:
if event == POLLERR:
print 'error, unregistering'
unreg(fd)
else:
client_socket = clients[fd]
data = client_socket.recv(1024)
if data:
print 'received', repr(data)
else:
print 'disconnected, unregistering'
unreg(fd)