Автор оригинала: Doug Hellmann.
Сокеты можно настроить для работы в качестве сервера и прослушивания входящих сообщений или для подключения к другим приложениям в качестве клиента . После подключения обоих концов сокета TCP/IP обмен данными становится двунаправленным.
Эхо-сервер
Эта программа-пример, основанная на той, что приведена в документации стандартной библиотеки, принимает входящие сообщения и возвращает их отправителю. Он начинается с создания сокета TCP/IP, затем используется bind ()
для связывания сокета с адресом сервера. В этом случае это адрес localhost
, относящийся к текущему серверу, а номер порта – 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()
Вызов listen ()
переводит сокет в режим сервера, а accept ()
ожидает входящего соединения. Целочисленный аргумент – это количество подключений, которые система должна поставить в очередь в фоновом режиме, прежде чем отклонять новых клиентов. В этом примере предполагается, что одновременно будет работать только одно соединение.
accept ()
возвращает открытое соединение между сервером и клиентом вместе с адресом клиента. На самом деле соединение представляет собой другой сокет на другом порту (назначенный ядром). Данные считываются из соединения с помощью recv ()
и передаются с помощью sendall ()
.
Когда связь с клиентом завершена, необходимо очистить соединение с помощью close ()
. В этом примере используется блок try: finally
, чтобы гарантировать, что close ()
всегда вызывается, даже в случае ошибки.
Эхо-клиент
Клиентская программа настраивает свой сокет
иначе, чем сервер. Вместо привязки к порту и прослушивания он использует connect ()
для присоединения сокета непосредственно к удаленному адресу.
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()
После установления соединения данные могут быть отправлены через socket
с помощью sendall ()
и получены с помощью recv ()
, как и на сервере. Когда все сообщение отправлено и получена копия, сокет закрывается, чтобы освободить порт.
Клиент и сервер вместе
Клиент и сервер должны запускаться в отдельных окнах терминала, чтобы они могли взаимодействовать друг с другом. Выходные данные сервера показывают входящее соединение и данные, а также ответ, отправленный обратно клиенту.
$ 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
Выходные данные клиента показывают исходящее сообщение и ответ сервера.
$ 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
Простые клиентские подключения
Клиенты TCP/IP могут сэкономить несколько шагов, используя удобную функцию create_connection ()
для подключения к серверу. Функция принимает один аргумент, двухзначный кортеж, содержащий адрес сервера, и получает лучший адрес для использования для соединения.
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 ()
использует getaddrinfo ()
для поиска параметров соединения-кандидата и возвращает socket
, открытый с первой конфигурацией, которая создает успешное соединение. Атрибуты family
, type
и proto
можно проверить, чтобы определить тип возвращаемого socket
.
$ 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
Выбор адреса для прослушивания
Важно привязать сервер к правильному адресу, чтобы клиенты могли общаться с ним. Во всех предыдущих примерах в качестве IP-адреса использовался 'localhost'
, что ограничивает подключения клиентов, работающих на одном сервере. Используйте общедоступный адрес сервера, например значение, возвращаемое функцией gethostname ()
, чтобы разрешить другим хостам подключаться. Этот пример изменяет эхо-сервер для прослушивания адреса, указанного в аргументе командной строки.
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()
Аналогичная модификация клиентской программы необходима до тестирования сервера.
socket_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()
После запуска сервера с аргументом hubert
команда netstat
показывает, что сервер прослушивает адрес указанного хоста.
$ 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 ...
Запуск клиента на другом хосте с передачей hubert.hellfly.net
в качестве хоста, на котором работает сервер, дает:
$ 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.'
И вывод сервера:
$ 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
Многие серверы имеют более одного сетевого интерфейса и, следовательно, более одного IP-адреса. Вместо того, чтобы запускать отдельные копии службы, привязанной к каждому IP-адресу, используйте специальный адрес INADDR_ANY
для одновременного прослушивания всех адресов. Хотя socket
определяет константу для INADDR_ANY
, это целочисленное значение, и его необходимо преобразовать в адрес строки с точечной нотацией, прежде чем его можно будет передать в bind ()
. В качестве ярлыка используйте « 0.0.0.0
» или пустую строку ( ''
) вместо выполнения преобразования.
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()
Чтобы увидеть фактический адрес, используемый сокетом, вызовите его метод getsockname ()
. После запуска службы запуск netstat
снова показывает, что она прослушивает входящие соединения по любому адресу.
$ netstat -an Active Internet connections (including servers) Proto Recv-Q Send-Q Local Address Foreign Address (state) ... tcp4 0 0 *.10000 *.* LISTEN ...