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 familytype, 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
...

By sduro