Cliente y Servidor TCP/IP¶
Los conectores se pueden configurar para que actúen como un servidor y escuchen mensajes entrantes, o se conecten a otras aplicaciones como un cliente. Después de que ambos extremos de un conector TCP/IP estén conectados, la comunicación es bidireccional.
Servidor de eco¶
Este programa de ejemplo, basado en la documentación de la biblioteca estándar,
recibe los mensajes entrantes y los devuelve al remitente. Comienza creando un
conector TCP/IP, luego bind()
se utiliza para asociar el conector con la
dirección del servidor. En este caso, la dirección es localhost
,
refiriéndose al servidor actual, y número de puerto es 10000.
import socket
import sys
# Create a TCP/IP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Bind the socket to the port
server_address = ('localhost', 10000)
print('starting up on {} port {}'.format(*server_address))
sock.bind(server_address)
# Listen for incoming connections
sock.listen(1)
while True:
# Wait for a connection
print('waiting for a connection')
connection, client_address = sock.accept()
try:
print('connection from', client_address)
# Receive the data in small chunks and retransmit it
while True:
data = connection.recv(16)
print('received {!r}'.format(data))
if data:
print('sending data back to the client')
connection.sendall(data)
else:
print('no data from', client_address)
break
finally:
# Clean up the connection
connection.close()
Al llamar a listen()
pone al conector en modo servidor, y accept()
espera
una conexión entrante. El argumento entero es el número de conexiones que el
sistema debe hacer cola en segundo plano antes de rechazar nuevos clientes.
Este ejemplo solo espera trabajar con una conexión a la vez.
accept()
devuelve una conexión abierta entre el servidor y cliente, junto
con la dirección del cliente. La conexión es en realidad un conector diferente
en otro puerto (asignado por el núcleo). Los datos se leen de la conexión con
recv()
y se transmiten con sendall()
.
Cuando finaliza la comunicación con un cliente, la conexión debe ser limpiada
usando close()
. Este ejemplo usa un bloque try:finally
para asegurar
que close()
siempre se llame, incluso en caso de error.
Cliente de eco¶
El programa cliente configura su socket
de forma diferente a como lo hace
un servidor. En lugar de unirse a un puerto y escuchar, usa connect()
para
conectar el conector directamente a la dirección de control remoto.
import socket
import sys
# Create a TCP/IP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Connect the socket to the port where the server is listening
server_address = ('localhost', 10000)
print('connecting to {} port {}'.format(*server_address))
sock.connect(server_address)
try:
# Send data
message = b'This is the message. It will be repeated.'
print('sending {!r}'.format(message))
sock.sendall(message)
# Look for the response
amount_received = 0
amount_expected = len(message)
while amount_received < amount_expected:
data = sock.recv(16)
amount_received += len(data)
print('received {!r}'.format(data))
finally:
print('closing socket')
sock.close()
Una vez establecida la conexión, los datos pueden ser enviados a través del
socket
con sendall()
y recibidos con recv()
, al igual que en el
servidor. Cuando se envía todo el mensaje y se recibe una copia, el conector
es cerrado para liberar el puerto.
Cliente y servidor juntos¶
El cliente y el servidor deben ejecutarse en ventanas de terminal separadas, para que puedan comunicarse entre ellos. La salida del servidor muestra la conexión y datos entrantes, así como la respuesta enviada al cliente.
$ python3 socket_echo_server.py
starting up on localhost port 10000
waiting for a connection
connection from ('127.0.0.1', 65141)
received b'This is the mess'
sending data back to the client
received b'age. It will be'
sending data back to the client
received b' repeated.'
sending data back to the client
received b''
no data from ('127.0.0.1', 65141)
waiting for a connection
La salida del cliente muestra el mensaje saliente y la respuesta del servidor.
$ python3 socket_echo_client.py
connecting to localhost port 10000
sending b'This is the message. It will be repeated.'
received b'This is the mess'
received b'age. It will be'
received b' repeated.'
closing socket
Conexiones de cliente fáciles¶
Los clientes TCP/IP pueden ahorrar algunos pasos utilizando la función de
conveniencia create_connection()
para conectarse a un servidor. La función
toma un argumento, una tupla de dos valores que contiene la dirección del
servidor, y deduce la mejor dirección para usar para la conexión.
import socket
import sys
def get_constants(prefix):
"""Create a dictionary mapping socket module
constants to their names.
"""
return {
getattr(socket, n): n
for n in dir(socket)
if n.startswith(prefix)
}
families = get_constants('AF_')
types = get_constants('SOCK_')
protocols = get_constants('IPPROTO_')
# Create a TCP/IP socket
sock = socket.create_connection(('localhost', 10000))
print('Family :', families[sock.family])
print('Type :', types[sock.type])
print('Protocol:', protocols[sock.proto])
print()
try:
# Send data
message = b'This is the message. It will be repeated.'
print('sending {!r}'.format(message))
sock.sendall(message)
amount_received = 0
amount_expected = len(message)
while amount_received < amount_expected:
data = sock.recv(16)
amount_received += len(data)
print('received {!r}'.format(data))
finally:
print('closing socket')
sock.close()
create_connection()
usa getaddrinfo()
para encontrar candidatos para
los parámetros de conexión, y devuelve un socket
abierto con el primera
configuración que crea una conexión exitosa. Los atributos family
,
type
, y proto
pueden ser examinados para determinar el tipo de
socket
que se devuelve.
$ python3 socket_echo_client_easy.py
Family : AF_INET
Type : SOCK_STREAM
Protocol: IPPROTO_TCP
sending b'This is the message. It will be repeated.'
received b'This is the mess'
received b'age. It will be'
received b' repeated.'
closing socket
Elegir una dirección para escuchar¶
Es importante vincular un servidor a la dirección correcta, para que los
clientes pueden comunicarse con él. Los ejemplos anteriores todos usaron
'localhost'
como la dirección IP, que limita las conexiones a los clientes
ejecutándose en el mismo servidor. Utiliza una dirección pública del servidor,
como el valor devuelto por gethostname()
, para permitir que otros hosts se
conecten. Este ejemplo modifica el servidor de eco para escuchar en una
dirección especificada a través de un argumento de línea de comando.
import socket
import sys
# Create a TCP/IP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Bind the socket to the address given on the command line
server_name = sys.argv[1]
server_address = (server_name, 10000)
print('starting up on {} port {}'.format(*server_address))
sock.bind(server_address)
sock.listen(1)
while True:
print('waiting for a connection')
connection, client_address = sock.accept()
try:
print('client connected:', client_address)
while True:
data = connection.recv(16)
print('received {!r}'.format(data))
if data:
connection.sendall(data)
else:
break
finally:
connection.close()
Se necesita una modificación similar al programa cliente antes de que el servidor puede ser probado
import socket
import sys
# Create a TCP/IP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Connect the socket to the port on the server
# given by the caller
server_address = (sys.argv[1], 10000)
print('connecting to {} port {}'.format(*server_address))
sock.connect(server_address)
try:
message = b'This is the message. It will be repeated.'
print('sending {!r}'.format(message))
sock.sendall(message)
amount_received = 0
amount_expected = len(message)
while amount_received < amount_expected:
data = sock.recv(16)
amount_received += len(data)
print('received {!r}'.format(data))
finally:
sock.close()
Después de iniciar el servidor con el argumento hubert
, el comando
netstat
lo muestra escuchando en la dirección de host nombrada.
$ host hubert.hellfly.net
hubert.hellfly.net has address 10.9.0.6
$ netstat -an | grep 10000
Active Internet connections (including servers)
Proto Recv-Q Send-Q Local Address Foreign Address (state)
...
tcp4 0 0 10.9.0.6.10000 *.* LISTEN
...
Ejecutar el cliente en otro host, pasando hubert.hellfly.net
como host
donde se ejecuta el servidor, produce:
$ hostname
apu
$ python3 ./socket_echo_client_explicit.py hubert.hellfly.net
connecting to hubert.hellfly.net port 10000
sending b'This is the message. It will be repeated.'
received b'This is the mess'
received b'age. It will be'
received b' repeated.'
Y la salida del servidor es:
$ python3 socket_echo_server_explicit.py hubert.hellfly.net
starting up on hubert.hellfly.net port 10000
waiting for a connection
client connected: ('10.9.0.10', 33139)
received b''
waiting for a connection
client connected: ('10.9.0.10', 33140)
received b'This is the mess'
received b'age. It will be'
received b' repeated.'
received b''
waiting for a connection
Muchos servidores tienen más de una interfaz de red, y por lo tanto más de una
dirección IP. En lugar de ejecutar copias separadas de un servicio enlazada a
cada dirección IP, usa la dirección especial INADDR_ANY
para escuchar en
todas las direcciones al mismo tiempo. Aunque socket
define una constante
para INADDR_ANY
, es un valor entero y debe convertirse en una cadena de
dirección en notación de puntos antes de que pueda ser pasada a bind()
.
Como atajo, usa «0.0.0.0
» o una cadena vacía (''
) en lugar de hacer la
conversión.
import socket
import sys
# Create a TCP/IP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Bind the socket to the address given on the command line
server_address = ('', 10000)
sock.bind(server_address)
print('starting up on {} port {}'.format(*sock.getsockname()))
sock.listen(1)
while True:
print('waiting for a connection')
connection, client_address = sock.accept()
try:
print('client connected:', client_address)
while True:
data = connection.recv(16)
print('received {!r}'.format(data))
if data:
connection.sendall(data)
else:
break
finally:
connection.close()
Para ver la dirección utilizada por un socket, llama a su método
getsockname()
. Tras iniciar el servicio, ejecutar netstat
nuevamente
lo muestra escuchando las conexiones entrantes en cualquier dirección.
$ netstat -an
Active Internet connections (including servers)
Proto Recv-Q Send-Q Local Address Foreign Address (state)
...
tcp4 0 0 *.10000 *.* LISTEN
...