Introducción
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.socket_echo_server.py
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.socket_echo_client.py
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.socket_echo_client_easy.py
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.socket_echo_server_explicit.py
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 probadosocket_echo_client_explicit.py
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.socket_echo_server_any.py
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 ...