Глава 1. Сокеты, IPv4 и примеры программирования клиент/ сервер

Содержание

Глава 1. Сокеты, IPv4 и примеры программирования клиент/ сервер
Введение
Вывод названия вашей машины и её IPv4 адреса
Приготовление
Как это сделать
Как это работает
Получение IP адреса удалённой машины
Как это сделать
Как это работает
Преобразование IP адреса в различные форматы
Как это сделать
Как это работает
Поиск названия службы, приданных ей порта и протокола
Приготовление
Как это сделать
Как это работает
Преобразование целых в- и из- сетевого порядка передачи байт хоста
Как это сделать
Как это работает
Установка и получение значения таймаута сокета по умолчанию
Как это сделать
Как это работает
Надлежащая обработка ошибок сокета
Как это сделать
Как это работает
Изменение размеров буферов приёма/ отправки сокета
Как это сделать
Как это работает
Изменение режима сокета в блокируемое/ неблокируемое состояние
Как это сделать
Как это работает
Повторное использование адресов сокета
Как это сделать
Как это работает
Вывод текущего времени из сервера времени Интернет
Приготовление
Как это сделать
Как это работает
Написание клиента SNTP
Как это сделать
Как это работает
Написание простого приложения клиент/ сервер echo TCP
Как это сделать
Как это работает
Написание простого приложения клиент/ сервер echo UDP
Как это сделать
Как это работает

В данной главе мы охватим следующие рецепты:

  • Вывод имени вашей машины и её IPv4 адреса

  • Выборка IPv4 адреса удалёной машины

  • Преобразование адреса IPv4 в различные форматы

  • Поиск назвния службы, приданного ей порта и протокола

  • Преобразование целых значений в- и из- порядка байт сети в порядок байт хоста

  • Установка и получение значения таймаута сокета по умолчанию

  • Аккуратная обработка ошибок сокета

  • Изменение размера буфера приёма/ передачи для некоторого сокета

  • Повторное использование адреса сокета

  • Вывод текущего времени с определённого сервера времени Интернета

  • Написание какого- то клиента SNTP

  • Написание простого приложения клиент/ сервер echo TCP

  • Написание простого приложения клиент/ сервер echo UDP

  • Вывод имени вашей машины и её IPv4 адреса

Введение

Даная глава представляет сетевые библиотеки ядра Python посредством некоторых простых примеров. Модуль Python сокета имеет утилиты как на основе класса, так и на основе экземпляра. Основная разница между методом на основе класса и метода, базирующегося на экземпляре состоит в том, что первому не требуется некий ъкземпляр какого- то объекта сокета. Это очень интуитивно понятный подход. Например, чтобы вывести адрес IP вашей машины, вам не нужен некий объект сокета. Вместо этого вы можете просто вызывать определённые основанные на классе методы сокета. С другой стороны, если вам требуется отсылать некие данные какому- то серверному приложению, интуитивно более понятно когда вы создаёте некий объект сокета для выполнения исключительно этой операции. Все представленные в данной главе рецепты могут быть разбиты на категории по трём группам следующим образом:

  • В нескольких самых первых рецептах применяются определённые утилиты на основе класса чтобы извлекать какую- то полезную информацию о хосте, сетевой среде и любых службах назначения.

  • После этого представлены некоторые дополнительные рецепты, применяющие имеющиеся утилиты на базе экземпляра. Демонстрируются некоторые распространённые задачи, включающие в себя манипулюцию имеющимся у сокета таймаутом, размером буфера, а также режимом блокирования.

  • Наконец, и основанные на классе, и базирующиеся на экземпляре утилиты используются для построения неких клиентов, которые могут осуществлять какие- то практические задачи, например, синхронизацию установленного в машине времени с неким Интернет сервером или писать некие общие сценарии клиент/ сервер.

Вы можете применить эти продемонстрированные подходы для написания своего собственного приложения клиент/ сервер.

Вывод названия вашей машины и её IPv4 адреса

Порой вам требуется быстро изучить некоторую информацию о своей машине, например, имя данного хоста, адрес IP, номер сетевого интерфейса и тому подобное. Это очень просто осуществить применяя сценарии Python.

Приготовление

Перед началом написания кода вам необходимо установить на свою машину Python. Python является предварительно установленным в большинстве имеющихся дистрибутивов Linux. Для операционных систем Windows вы можете выгрузить исполняемые файлы с самого вебсайта Python: http://www.python.org/download/.

В настоящее время, помио Python 2.x выпущен Python 3.x. Многие из имеющихся в настоящее время дистрибутивов Linux и версий macOS всё ещё снабжаются по умолчанию Python 2.x. Однако некоторые поставляются с обеими версиями.

Выгрузите отвечающий вашей операционной системе установщик, который соответствует версии вашей операционной системы и тому является ли она 32- битной или 64- битной.

Вы можете проконсультироваться с имеющейся документацией своей операционной системы чтобы проверить и пересмотреть установку своего Python. После установки Python на своей машине вы можете попробовать открыть интерпретатор командной строки набрав python. Это высветит приглашение имеющегося интерпретатора, >>>, которое должно выглядеть примерно следующим образом:


~$ python
Python 2.7.12 (default, Nov 19 2016, 06:48:10)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>>
		

Как это сделать

В имеющихся последних версиях Ubuntu, начиная с Ubuntu 14.04, Python 3 может исполняться путём набора python3:


~$ python3
Python 3.5.2 (default, Nov 17 2016, 17:05:23)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>
		

Аналогично, чтобы определить какую версию вы предпочитаете применить, вы можете набирать python2 также для исполнения Python2:


~$ python2
Python 2.7.12 (default, Nov 19 2016, 06:48:10)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>>
		

Имеется ряд изменений в Python 3, которые делают написанный на Python 2 код несовместимым с Python 3. Когда вы пишите сетевое приложение, попробуйте придерживаться практическиого опыта Python 3, так как эти изменения и улучшения портируются обратно в самые последние версии Python 2. Таким образом вы сможете спокойно исполнять самые последние версии Python 2, например, Python 2.7. Онако, некоторый код, разработанный специально под Python 2 может не исполняться в Python 3.

Последующие рецепты в данной главе написаны под Python 3. Однако, имейте в виду, пожалуйста, что некоторые сетевые проекты и модули могут быть разработаны под Python 2. В этом случае вам придётся либо портировать такое приложение под Python 3, либо применять Python 2 в зависимости от ваших требований.

Так как данный рецепт очень короток, вы можете попробовать это в своём интерпретаторе Python интерактивно.

Вначале нам необходимо импортировать нужную библиотеку Python socket воспользовавшись следующей командой:


>>> import socket
		

Затем мы вызовем из этой библиотеки socket метод gethostname() и сохраним результат в каой- то переменной следующим образом:


>>> host_name = socket.gethostname()
>>> print "Host name: %s" %host_name
Host name: llovizna
>>> print "IP address: %s"
%socket.gethostbyname(host_name)
IP address: 127.0.1.1
		

Все эти действия могут быть обёрнуты в некую отдельную функцию, print_machine_info(), которая применяет встроенные методы класса сокета.

Вы вызываем свою функцию из обычного блока Python __main__. В процессе времени исполнения Python назначает значения некоторым внутренним переменным, таким как __name__. В нашем случае __name__ ссылается на само название вызывающего процесса. При исполнении данного сценария из командной строки, как это показывается в приводимой ниже команде, таким именем будет __main__. Однако, оно будет отличаться если данный модуль импортируется из другого сценария. Это означает, что когда такой модуль вызывается из данной командной строки, он будет автоматически исполнять нашу функцию print_machine_info; однако, при отдельном импортировании, такому пользователю понадобится в явном виде вызвать данную функцию.

Листинг 1.1 отображает ниже как получить информацию о нашей машине:

 

#!/usr/bin/env python
    
	   

#!/usr/bin/env python
# Книга рецептов сетевого программирования Python, второе издание -- Глава - 1
# Данная программа оптимизирована под Python 2.7.12 и Python 3.5.2.
# Она может исполняться во всех прочих версиях с изменениями и/или без них.

import socket
def print_machine_info():
    host_name = socket.gethostname()
    ip_address = socket.gethostbyname(host_name)
    print ("Host name: %s" %host_name)
    print ("IP address: %s" %ip_address)
if __name__ == '__main__':
    print_machine_info()
 	   

Чтобы выполнить данный рецепт вы можете воспользоваться предоставленным файлом исходного кода из своей строки команд следующим образом:


$ python 1_1_local_machine_info.py
		

На моей машине был отображён следующий вывод:


Host name: llovizna
IP address: 127.0.1.1
		

Данное имя хоста представляет собой то имя вашего компьютера, которое назначается ему при настройке вашей операционной системы. Данный вывод будет различаться на вашей машине в зависимости от настройки хоста данной системы. В нашем случае интерпретатор указывает где в настоящее время исполнялся данный интерпретатор Python.

Отметьте, пожалуйста, что все программы из данной книги исполняются под обеими версиями 2 и 3. Мы избегаем упоминать python3 и python2 в командах, поскольку они слишком специфичны для ряда дистрибутивов и полагаются на особенности установленной версии. Вы можете исполнять любую из приводимых программ в любой из версий применяя соответственно python2 или python3.

Как это работает

Оператор import socket выполняет импортирование одной из центральных сетевых библиотек Python. Затем мы применяем две функции утилит, gethostbyname() и gethostbyname(host_name). Вы можете набрать help(socket.gethostname) чтобы просмотреть в реальном режиме информацию из своей командной строки. В качестве альтернативы вы можете набрать следующий адрес в своём веб браузере http://docs.python.org/3/library/socket.html. Вы можете получить следующий код:


gethostname(...)
    gethostname() -> string
    Return the current host name.
gethostbyname(...)
    gethostbyname(host) -> address
    Return the IP address (a string of the form '255.255.255.255') for a host.
 	   

Наша первая функция не принимает никакие параметры и возвращает текущее имя или название локального хоста. Вторая функция принимает некий отдельный параметр hostname и возвращает его IP адрес.

Получение IP адреса удалённой машины

Иногда нам требуется перевести имя хоста какой- то машины в соответствующий адрес IP, например, быстро отыскав доменное имя. Данные рецепт представляет некий пример функции чтобы сделать это.

Как это сделать

Если вам нужно знать определённый IP адрес какой- то удалённой машины, вы можете воспользоваться встроенной библиотечной функцией, gethostbyname(). В данном случае вам необходимо передать в качестве её параметра её имя хоста.

В этом примере нам необходимо вызвать функцию имеющегося класса gethostbyname(). Давайте взглянем на этот фрагмент кода.

Листинг 1.2 показывает далее как получить адрес IP какой- то удалённой машины:

 

#!/usr/bin/env python
# Книга рецептов сетевого программирования Python, второе издание -- Глава - 1
# Данная программа оптимизирована под Python 2.7.12 и Python 3.5.2.
# Она может исполняться во всех прочих версиях с изменениями и/или без них.

import socket
def get_remote_machine_info():
    remote_host = 'www.python.org'
    try
        print ("IP address of %s: %s" %(remote_host, socket.gethostbyname(remote_host)))
        except socket.error as err_msg:
        print ("%s: %s" %(remote_host, err_msg))
if __name__ == '__main__':
    get_remote_machine_info()
 	   

Если вы примените приведённый выше код, он приведёт к следующему выводу:


$ python 1_2_remote_machine_info.py
IP address of www.python.org: 151.101.36.223
		

Как это работает

Данный рецепт обёртывает имеющийся метод gethostbyname() внутри некоторой определённой пользователем функции с названием get_remote_machine_info(). В этом рецепте мы вводим существенное понятие обработки исключения. Как вы можете видеть, мы обёртываем вызов главной функции внутри некоторого блока try-except. Это означает, что если произойдёт некоторая ошибка в процессе исполнения данной функции, эта ошибка будет иметь дело с данным блоком try-except.

Например, давайте изменим значение remote_host и заменим https://www.python.org/ чем- то не существующим, например, www.pytgo.org:

 

#!/usr/bin/env python
# Книга рецептов сетевого программирования Python, второе издание -- Глава - 1
# Данная программа оптимизирована под Python 2.7.12 и Python 3.5.2.
# Она может исполняться во всех прочих версиях с изменениями и/или без них.

import socket
def get_remote_machine_info():
    remote_host = 'www.pytgo.org'
    try:
        print ("IP address of %s: %s" %(remote_host, socket.gethostbyname(remote_host)))
    except socket.error as err_msg:
        print ("%s: %s" %(remote_host, err_msg))
if __name__ == '__main__':
    get_remote_machine_info()
 	   

Теперь мы исполним следующую команду:


$ python 1_2_remote_machine_info.py
www.pytgo.org: [Errno -2] Name or service not known
		

Наш блок try-except перехватывает полученную ошибку и отображает своему пользователю некое сообщение об ошибке которое обусловлено тем, что не существует IP адреса, связанного с данным именем хоста, www.pytgo.org.

Преобразование IP адреса в различные форматы

Когда вы пожелаете иметь дело с сетевыми функциями нижнего уровня, иногда, обычная строчная нотация адреса IP не является очень полезной. Её необходимо преобразовывать в упакованные определённым образом 32- битные двоичные форматы.

Как это сделать

Библиотека Python socket имеет утилиты для работы с различными форматами IP адреса. В дааном случае мы воспользуемся двумя из них: inet_aton() и inet_ntoa().

Давайте создадим некую функцию convert_ip4_address(), в которой inet_aton() и inet_ntoa() будут применяться для преобразования необходимого IP адреса. Мы воспользуемся двумя примерами IP адреса, 192.168.0.1 и 127.0.0.1.

Листинг 1.3 отображает ip4_address_conversion следующим образом:

 

#!/usr/bin/env python
# Книга рецептов сетевого программирования Python, второе издание -- Глава - 1
# Данная программа оптимизирована под Python 2.7.12 и Python 3.5.2.
# Она может исполняться во всех прочих версиях с изменениями и/или без них.

import socket
from binascii import hexlify
def convert_ip4_address():
    for ip_addr in ['127.0.0.1', '192.168.0.1']:
        packed_ip_addr = socket.inet_aton(ip_addr)
        unpacked_ip_addr = socket.inet_ntoa(packed_ip_addr)
        print ("IP Address: %s => Packed: %s, Unpacked: %s" %(ip_addr, hexlify(packed_ip_addr), unpacked_ip_addr))
if __name__ == '__main__':
    convert_ip4_address()
       

Теперь, если мы исполним данный рецепт, мы увидим следующий результат:


$ python 1_3_ip4_address_conversion.py
IP Address: 127.0.0.1 => Packed: 7f000001, Unpacked: 127.0.0.1
IP Address: 192.168.0.1 => Packed: c0a80001, Unpacked: 192.168.0.1
		

Как это работает

В данном рецепте были преобразованы два IP адреса из строкового значения в некий упакованный в 32 бита формат с применением некого оператора for-in. Кроме того, из имеющегося у нас модуля binascii вызывается функция hexlify. Это помогает представлять наши двоичные данные в некотором шестнадцатеричном виде.

Поиск названия службы, приданных ей порта и протокола

Если вы желаете исследовать сетевые службы, может быть полезным определять по каким портам работают эти службы и применяют ли они протокол TCP или UDP.

Приготовление

Если вы знаете номер порта некоторой сетевой службы, вы можете определить её название воспользовавшись имеющейся функцией класса сокета getservbyport() из своей библиотеки сокета. В качестве необязательного параметра вы можете получить название применяемого протокола при вызове данной функции.

Как это сделать

Давайте определим некую функцию find_service_name(), в которой будет вызываться функция класса сокета getservbyport() с некоторыми портами, например, 80, 25. Мы можем применить конструкцию цикла Python for-in.

Листинг 1.4 отображает finding_service_name следующим образом:

 

#!/usr/bin/env python
# Книга рецептов сетевого программирования Python, второе издание -- Глава - 1
# Данная программа оптимизирована под Python 2.7.12 и Python 3.5.2.
# Она может исполняться во всех прочих версиях с изменениями и/или без них.

import socket

def find_service_name():
    protocolname = 'tcp'
    for port in [80, 25]:
        print ("Port: %s => service name: %s" %(port, socket.getservbyport(port, protocolname)))
        print ("Port: %s => service name: %s" %(53, socket.getservbyport(53, 'udp')))
if __name__ == '__main__':
    find_service_name()
       

Если вы исполните этот сценарий, вы получите следующий вывод:


$ python 1_4_finding_service_name.py
Port: 80 => service name: http
Port: 25 => service name: smtp
Port: 53 => service name: domain
		

Он указывает, что службы http, smtp и domain работают, соответственно по портам 80, 25 и 53.

Как это работает

В данном рецепте применяется оператор for-in для итераций по некоторой последовательности переменных. Поэтому, для каждой итерации мы применяем один IP адрес для преобразования его в его упакованный и распакованный формат.

Преобразование целых в- и из- сетевого порядка передачи байт хоста

Если вам приходится писать некое низкоуровневое приложение, может оказаться необходимым обрабатывать также низкоуровневый обмен данными через проводную связь между двумя машинами. Данная операция требует некоторого вида преобразования данных из естественного представления хоста операционной системой в имеющийся сетевой формат и наоборот. Это обусловлено тем, что каждый из них имеет своё собственное определённое представление данных.

Как это сделать

Библиотека Python socket имеет утилиты для преобразования из сетевого порядка последовательности байт в порядок байт хоста и наоборот. Вы можете захотеть ознакомиться с ними, например с ntohl()/htonl().

Давайте определим свою функцию convert_integer(), в которой функции ntohl()/htonl() класса socket применяются для преобразования формата адреса IP.

Листинг 1.5 отображает convert_integer следующим образом:

 

#!/usr/bin/env python
# Книга рецептов сетевого программирования Python, второе издание -- Глава - 1
# Данная программа оптимизирована под Python 2.7.12 и Python 3.5.2.
# Она может исполняться во всех прочих версиях с изменениями и/или без них.

import socket

def convert_integer():
    data = 1234
    # 32-bit
    print ("Original: %s => Long host byte order: %s, Network byte order: %s" %(data, socket.ntohl(data), socket.htonl(data)))
    # 16-bit
    print ("Original: %s => Short host byte order: %s, Network byte order: %s" %(data, socket.ntohs(data), socket.htons(data)))
if __name__ == '__main__':
    convert_integer()
       

Если вы исполните этот рецепт, вы получите следующий вывод:


$ python 1_5_integer_conversion.py
Original: 1234 => Long host byte order: 3523477504,
Network byte order: 3523477504
Original: 1234 => Short host byte order: 53764,
Network byte order: 53764
		

Как это работает

Здесь мы берём некое целое значение и показываем как преобразовывать его между сетевым порядком байт и порядком хоста. Функция ntohl() клсаас socket преобразовывает из сетевого порядка байт в порядок байт хоста в неком фор мате long. В нашем случае n представляет сетевой порядок, h порядок хоста; l представляет long, а s short, то есть 16- бит.

Установка и получение значения таймаута сокета по умолчанию

Иногда вам требуется обращаться со значениями по умолчанию определённых свойств библиотеки socket, например, значением таймаута сокета.

Как это сделать

Вы можете сделать некий экземпляр какого- то объекта socket и вызвать метод gettimeout() для получения установленного по умолчанию значения таймаута и метод settimeout() для установки определённого значения таймаута. Это очень полезно при разработке индивидуальных серверных приложений.

Вначале мы создадим некий объект socket внутри своей функции test_socket_timeout(). Затем мы можем применять методы экземпляра getter/setter для обработки значений таймаута.

Листинг 1.6 показывает socket_timeout следующим образом:

 

#!/usr/bin/env python
# Книга рецептов сетевого программирования Python, второе издание -- Глава - 1
# Данная программа оптимизирована под Python 2.7.12 и Python 3.5.2.
# Она может исполняться во всех прочих версиях с изменениями и/или без них.

import socket

def test_socket_timeout():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    print ("Default socket timeout: %s" %s.gettimeout())
    s.settimeout(100)
    print ("Current socket timeout: %s" %s.gettimeout())
if __name__ == '__main__':
    test_socket_timeout()
       

После выполнения предыдущего сценария, вы вы поймёте как это изменяет значение таймаута сокета по умолчанию:


$ python 1_6_socket_timeout.py
Default socket timeout: None
Current socket timeout: 100.0
		

Как это работает

В данном фрагменте кода мы вначале создаём некий объект socket передавая имеющееся семейство сокета и тип сокета в качестве первого и второго параметров своего конструктора сокета. Затем вы можете получить значение таймаута сокета вызвав gettimeout() и изменить это значение путём вызова метода settimeout(). Передаваемое в метод settimeout() значение может быть в секундах (неотрицательное значение с плавающей запятой) или None. Данный метод применяется для работы с операциями, блокирующими сокет. Установка некоторого таймаута в значение None отключает таймауты в операциях с сокетом.

Надлежащая обработка ошибок сокета

В любом сетевом приложении очень часто бывает когда одна сторона пытается выполнить соединение, но другая не отвечает либо из- за отказов сетевой среды, либо ещё по какой- то другой причине. Имеющаяся библиотека Python socket располагает неким элегантным методом обработки таких ошибок посредством исключения socket.error. В данном рецепте представлен ряд примеров.

Как это сделать

Давайте создадим ряд блоков кода try-except и поместим по одной потенциальной ошибке в каждый блок. Для получения некоторого пользовательского ввода может применяться модуль argparse. Этот модуль намного мощнее чем простой разбор синтаксиса аргументов командной строки с помощью sys.argv. В свои блоки try-except поместите обычные операции сокета, например, создайте некий объект socket, подключитесь к какому- то серверу, отправьте данные и дождитесь некого ответа.

Следующий рецепт иллюстрирует данные концепции в нескольких строках кода.

Листинг 1.7 демонстрирует socket_errors так:

 

#!/usr/bin/env python
# Книга рецептов сетевого программирования Python, второе издание -- Глава - 1
# Данная программа оптимизирована под Python 2.7.12 и Python 3.5.2.
# Она может исполняться во всех прочих версиях с изменениями и/или без них.

import sys
import socket
import argparse

def main():
    # установка синтаксического анализа аргументов
    parser = argparse.ArgumentParser(description='Socket Error Examples')
    parser.add_argument('--host', action="store", dest="host", required=False)
    parser.add_argument('--port', action="store", dest="port", type=int, required=False)
    parser.add_argument('--file', action="store", dest="file", required=False)
    given_args = parser.parse_args()
    host = given_args.host
    port = given_args.port
    filename = given_args.file
    # Первый блок try-except -- создаём сокет
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    except socket.error as e:
        print ("Error creating socket: %s" % e)
        sys.exit(1)
    # Второй блок try-except -- выполняем соединение с определёнными хостом/портом
    try:
        s.connect((host, port))
    except socket.gaierror as e:
        print ("Address-related error connecting to server: %s" % e)
        sys.exit(1)
    except socket.error as e:
        print ("Connection error: %s" % e)
        sys.exit(1)
    # Третий блок try-except -- отправка данных
    try:
        msg = "GET %s HTTP/1.0\r\n\r\n" % filename
        s.sendall(msg.encode('utf-8'))
    except socket.error as e:
        print ("Error sending data: %s" % e)
        sys.exit(1)
    while 1:
        # Четвёртый блок try-except -- ожидание получения данных от удалённого хоста
    try:
        buf = s.recv(2048)
    except socket.error as e:
        print ("Error receiving data: %s" % e)
        sys.exit(1)
    if not len(buf):
        break
        # запись принятых данных
        sys.stdout.write(buf.decode('utf-8'))

if __name__ == '__main__':
    main()
       

Как это работает

В Python передача аргументов командной строки и их синтаксический разбор в вашем сценарии может выполняться при помощи модуля argparse. Он доступен в Python 2.7. Для более ранних версий Python этот модуль доступен отдельно в Python Package Index (PyPI). Вы можете установить его через easy_install или pip.

В данном рецепте устанавливаются три параметра - имя хоста, номер порта и имя файла. Применение данного сценария выглядит следующим образом:


$ python 1_7_socket_errors.py --host=<HOST> --port=<PORT> --file=<FILE>
В предыдущем рецепте, msg.encode('utf-8') кодировал сообщение в UTF-8, а buf.decode('utf-8') декодировал получаемый формат UTF-8.
		

Если вы попробуете применить предыдущий рецепт с каким- то несуществующим хостом, данный сценарий распечатает в ответ ошибку неким следующим образом:


$ python 1_7_socket_errors.py --host=www.pytgo.org --port=8080 --file=1_7_socket_errors.py
Address-related error connecting to server: [Errno -2] Name or service not known
		

Если по определённому порту отсутствует служба и если вы попробуете соединиться с этим портом, тогда это пройдёт через некую ошибку соединения по таймауту так:


$ python 1_7_socket_errors.py --host=www.python.org --port=8080 --file=1_7_socket_errors.py
		

Данный оператор вернёт приводимую ниже ошибку по той причине, что данный хост, www.python.org, не прослушивает порт 8080:


Connection error: [Errno 110] Connection timed out
		

Однако, если вы отправите некий произвольный запрос как корректный запрос на правильный порт, данная ошибка не может быть перехвачена на уровне данного приложения. Например, исполнение следующего сценария не возвращает никакой ошибки, в то время как HTML вывод сообщает нам что было неверно в данном сценарии:


$ python 1_7_socket_errors.py --host=www.python.org --port=80 --file=1_7_socket_errors.py
HTTP/1.1 500 Domain Not Found
Server: Varnish
Retry-After: 0
content-type: text/html
Cache-Control: private, no-cache
connection: keep-alive
Content-Length: 179
Accept-Ranges: bytes
Date: Thu, 01 Jun 2017 22:02:24 GMT
Via: 1.1 varnish
Connection: close
<html>
<head>
<title>Fastly error: unknown domain </title>
</head>
<body>
Fastly error: unknown domain: . Please check that this domain has been
added to a service.</body></html>
		

В своём предыдущем примере было применено четыре блока try-except. Все блоки применяли исключение socket.error за исключением второго, который применял socket.gaierror. Оно используется для ошибок, относящихся к адресации. Имеются два других типа исключений - socket.herror применяется для наследуемого API C, а если вы применяете в некотором сокете метод settimeout(), будет распространена ошибка socket.timeout по достижению некоторого таймаута в данном сокете.

Изменение размеров буферов приёма/ отправки сокета

Установленный по умолчанию размер буфера сокета может не подходить по какой- либо причине. При таких обстоятельствах вы можете изменить такой установленный по умолчанию размер буфера на более подходящее значение.

Как это сделать

Давайте выполним манипуляции с установленным по умолчанию значением размера буфера с использованием некоторого метода объекта сокета setsockopt().

Вначале определим два постоянных значения: SEND_BUF_SIZE/RECV_BUF_SIZE и затем обернём некий вызов экземпляра сокета методом setsockopt() в какую- то функцию. Будет также неплохой мыслью перед изменением установленного значения буфера проверить его текущее значение. Отметим, что нам необходимо устанавливать размеры буфера отправки и приёма по отдельности.

Листинг 1.8 отображает далее как изменять размеры буферов приёма/ передачи:

 

#!/usr/bin/env python
# Книга рецептов сетевого программирования Python, второе издание -- Глава - 1
# Данная программа оптимизирована под Python 2.7.12 и Python 3.5.2.
# Она может исполняться во всех прочих версиях с изменениями и/или без них.

import socket

SEND_BUF_SIZE = 4096
RECV_BUF_SIZE = 4096

def modify_buff_size():
    sock = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
    # Получить размер буфера для буфера отправки определённого сокета
    bufsize = sock.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF)
    print ("Buffer size [Before]:%d" %bufsize)
    sock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, SEND_BUF_SIZE)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, RECV_BUF_SIZE)
    bufsize = sock.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF)
    print ("Buffer size [After]:%d" %bufsize)

if __name__ == '__main__':
    modify_buff_size()
       

Если вы исполните предыдущий сценарий, он отобразит все изменения имеющихся размеров буферов. Приводимый ниже вывод может отличаться на вашей машине в зависимости от локальных установок операционной системы:


$ python 1_8_modify_buff_size.py
Buffer size [Before]:16384
Buffer size [After]:8192
		

Как это работает

Вы можете вызывать имеющиеся в некотором объекте сокета методы getsockopt() и setsockopt() для соответствующих выборки и изменения свойств данного объекта сокета. Метод setsockopt() получает три аргумента: level, optname и value. Здесь optname получает наименовании опции, а value является соответствующим значением этой опции. Для нашего первого аргумента требуются символические константы, которые вы можете обнаружить в имеющемся модуле сокета (SO_* и т.д.).

Изменение режима сокета в блокируемое/ неблокируемое состояние

По умолчанию сокеты TCP помещаются в некий блокируемый режим. Это означает, что управление не возвращается в вашу программу пока не выполнится некая определённая операция. Если вы вызываете API connect(), данное соединение блокирует вашу программу пока не завершится данная операция. Во многим случаях вы не желаете чтобы ваша программа выполняла вечное ожидание, либо некоторого отклика от своего сервера или некоторой ошибки для завершения данной операции. Например, когда вы пишите какой- то клиент веб браузера, который подключается к некоторому веб серверу, вы обязаны рассматривать возможный останов работы который может прекратить имеющийся процесс соединения посреди данной операции. Этого можно достичь помещением данного сокета в определённый режим без блокирования.

Как это сделать

Давайте рассмотрим доступные в Python варианты. В Python сокет может быть помещён как в блокируемый, так и в не блокируемый режим. При не блокируемом режиме если вы ваш некий вызов API, скажем, send() или recv(), сталкивается с любой проблемой, будет распространена какая- то ошибка. Однако, если это происходит при режиме с блокировкой, это не остановит данную операцию. Мы можем создать некий обычный сокет TCP и поэксперементировать как с блокируемыми, так и с не блокируемыми операциями.

Для манипуляций с природой блокируемого сокета нам следует вначале создать некий объект сокета.

Затем мы можем вызвать setblocking(1) для установки блокировки, или setblocking(0) для её снятия. В конце концов, мы прикрепляем данный сокет к некоторому определённому порту и прослушиваем входящие соединения.

Листинг 1.9 показывает как сокет изменяет режим на блокируемый или без блокирования следующим образом:

 

#!/usr/bin/env python
# Книга рецептов сетевого программирования Python, второе издание -- Глава - 1
# Данная программа оптимизирована под Python 2.7.12 и Python 3.5.2.
# Она может исполняться во всех прочих версиях с изменениями и/или без них.

import socket

def test_socket_modes():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.setblocking(1)
    s.settimeout(0.5)
    s.bind(("127.0.0.1", 0))
    socket_address = s.getsockname()
    print ("Trivial Server launched on socket: %s" %str(socket_address))
    while(1):
        s.listen(1)

if __name__ == '__main__':
    test_socket_modes()
       

Если вы исполните предыдущий рецепт, он запустит некий простейший сервер, который имеет включённым блокируемый режим, что показывает следующая команда:


$ python 1_9_socket_modes.py
Trivial Server launched on socket: ('127.0.0.1', 51410)
		

Как это работает

В данном рецепте мы включаем блокирование некоторого сокета устанавливая значение 1 в своём методе setblocking(). Аналогично мы можем сбросить значение в 0 в данном методе чтобы сделать сокет не блокированным.

Данная функциональность будет повторно применяться в последующих рецептах, в которых будет разработано её истинное предназначение.

Повторное использование адресов сокета

Вы хотите чтобы некий сервер сокета всегда работал с неким определённым портом, даже после того как он намеренно или неожиданно закрылся. Это полезно в некоторых случаях когда ваша клиентская программа всегда соединяется с этим определённым портом сервера. Поэтому вам нет необходимости изменять данный порт сервера.

Как это сделать

Если вы исполняете некий сервер сокета Python с каким- то определённым портом и пытаетесь вернуть его впоследствии, после того как закрыли его однажды, у вас не будет возможности использовать тот же самый порт. Обычно это будет сопровождаться некоторой ошибкой как в приводимой ниже команде:


Traceback (most recent call last):
   File "1_10_reuse_socket_address.py",
   line 40, in <module>
        reuse_socket_addr()
   File "1_10_reuse_socket_address.py",
   line 25, in reuse_socket_addr
        srv.bind( ('', local_port) )
   File "<string>", line 1, in bind
    socket.error: [Errno 98] Address
    already in use
		

Лекарством для данной проблемы является включение опции повторного применения данного сокета, SO_REUSEADDR.

После создания некоторого объекта socket, мы можем запросить установку состояния повторного использования алреса, допустим, старого состояния. Затем мы вызываем метод setsockopt() для изменения имеющегося значения его состояния повторного использования адреса. Потом мы следуем обычными этапами связывания некоторого адреса и прослушивания входящих соединений клиента.

В предыдущем примере, когда мы закрываем своё соединение с помощью Ctrl + C, вы получаете уведомление о некоторой исключительной ситуации:


^CTraceback (most recent call last):File "1_9_socket_modes.py", line 20, in 
<module> 
test_socket_modes()
File "1_9_socket_modes.py", line 17, in test_socket_modes
s.listen(1)
KeyboardInterrupt
		

Это указывает что при данном исполнении произошло прерывание с клавиатуры.

В данном примере мы перехватываем исключительную ситуацию KeyboardInterrupt, поэтому если вы применяете Ctrl + C, тогда ваш сценарий Python прекращается без отображения какого- бы то ни было сообщения.

Листинг 1.10 показывает далее как повторно применять адреса сокета:

 

#!/usr/bin/env python
# Книга рецептов сетевого программирования Python, второе издание -- Глава - 1
# Данная программа оптимизирована под Python 2.7.12 и Python 3.5.2.
# Она может исполняться во всех прочих версиях с изменениями и/или без них.

import socket
import sys

def reuse_socket_addr():
    sock = socket.socket( socket.AF_INET, socket.SOCK_STREAM )

    # Получаем старое состояние данной опции SO_REUSEADDR
    old_state = sock.getsockopt(socket.SOL_SOCKET,
    socket.SO_REUSEADDR )
    print ("Old sock state: %s" %old_state)
    # Включаем опцию SO_REUSEADDR
    sock.setsockopt( socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 )
    new_state = sock.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR )
    print ("New sock state: %s" %new_state)

    local_port = 8282
    srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    srv.bind( ('', local_port) )
    srv.listen(1)
    print ("Listening on port: %s " %local_port)
    while True:
        try:
            connection, addr = srv.accept()
            print ('Connected by %s:%s' % (addr[0], addr[1]))
        except KeyboardInterrupt:
break
except socket.error as msg:
print ('%s' % (msg,))

if __name__ == '__main__':
    reuse_socket_addr()
       

Вывод для данного рецепта будет похож на представленный здесь вывод исполнения нашей программы:


$ python 1_10_reuse_socket_address.py
Old sock state: 0
New sock state: 1
Listening on port: 8282
		

Как это работает

Вы можете исполнить данный сценарий в одном окне консоли и попытаться соединиться с этим сервером из другого консольного окна набрав telnet localhost 8282.

Когда ваш telnet подключится к нему, вы увидите некий вывод, выдаваемый данной программой:


Connected by 127.0.0.1:46584
		

Здесь номера хоста и порта могут отличаться в зависимости от того экземпляра telnet, с которого вы отправляете данный запрос.

Когда вы закроете данную программу сервера, вы может вернуть её вновь с тем же самым портом. Однако, если вы скроете комментарием ту строку, которая устанавливает SO_REUSEADDR, данный сервер не исполнится во второй раз.

Вывод текущего времени из сервера времени Интернет

Многие программы полагаются на точность машинного времени, например, ваша команда make в UNIX. Ваше машинное время может отличаться и требует синхронизации с другим сервером времени в вашей сетевой среде.

Приготовление

Чтобы выполнить синхронизацию вашего машинного времени с одним из имеющихся серверов времени Интернет, вы можете написать для этого некого клиента Python. С этой целью будет применена ntplib. В данном случае все переговоры клиент/ сервер будут выполняться с использованием Network Time Protocol (NTP). Если в вашей машине не установлена ntplib, вы можете получить её из PyPI следующей командой с применением pip или easy_install:


$ pip install ntplib
		

Если и pip не установлен в вашем компьютере, вначале установите его, прежде чем выполнить данную команду. В дистрибутивах Linux на основе Debian, таких как Ubuntu, его можно установить так:


$ sudo apt install python-pip
		

Отметим, что вам потребуется установить pip для Python 3 отдельно если вы исполняете его совместно с Python 2, поскольку обычно Python 2 установлен как применяемая по умолчанию версия:


$ sudo apt-get install python3-pip
		

Аналогично, ntplib требует отдельной установки для python3-pip (также имеющего название pip3):


$ pip install --upgrade pip
		

или:


$ pip3 install --upgrade pip
		

Если на вашем компьютере установлены и Python 2, и Python 3, применяйте pip3.

Я использую pip с версией 9.0.1 и для Python 2, и для Python 3. Это самая последняя версия на момент написания книги.

Как это сделать

Мы создадим некий экземпляр NTPClient и затем вызовем метод request() в нём передав необходимый адрес сервера NTP.

Листинг 1.11 показывает как вывести текущее время имеющегося сервера времени Интернет следующим образом:

 

#!/usr/bin/env python
# Книга рецептов сетевого программирования Python, второе издание -- Глава - 1
# Данная программа оптимизирована под Python 2.7.12 и Python 3.5.2.
# Она может исполняться во всех прочих версиях с изменениями и/или без них.

import ntplib 
from time import ctime

def print_time():
    ntp_client = ntplib.NTPClient()
    response = ntp_client.request('pool.ntp.org')
    print (ctime(response.tx_time))

if __name__ == '__main__':
    print_time()
       

На моей машине данный рецепт отобразил следующий вывод:


$ python 1_11_print_machine_time.py
Fri Jun 2 16:01:35 2017
		

Как это работает

В данном случае был создан некий клиент NTP и какой- то запрос NTP был отправлен к одному из имеющихся серверов времени Интернет, pool.ntp.org. Наша функция ctime() применяется для печати данного отклика.

Написание клиента SNTP

В отличии от предыдущего рецепта, иногда вам не требуется получать совсем точное время от сервера NTP. Вы можете применять более простую версию NTP, вызывая простой сетевой протокол времени.

Как это сделать

Давайте создадим некого простейшего клиента SNTP без применения какой бы то ни было сторонней библиотеки.

Вначале давайте определим две константы: NTP_SERVER и TIME1970. NTP_SERVER является определённым адресом сервера, к которому наш клиент будет подключаться, а TIME1970 является временной ссылкой на 1 января 1970 (также называемое Эпохой). Вы можете обнаружить необходимое значение данного времени Epoch или преобразовать во время Epoch по адресу http://www.epochconverter.com/. Действующий клиент создаёт некий сокет UDP (SOCK_DGRAM) для соединения с данным сервером по протоколу UDP. Такой клиент затем требует отправки данных протокола SNTP ('\x1b' + 47 * '\0') в каком- то пакете. Наш клиент UDP отправляет и получает данные с применением методов sendto() и recvfrom().

Когда намш сервер возвращает полученную информацию о времени в некотором упакованном массиве, данному клиенту требуется некий специализированный модуль struct чтобы распаковать эти данные. Все интересующие нас данные расположены в 11м элементе данного массива. Наконец, нам требуется вычесть имеющееся эталонное значение, TIME1970, из своего упакованного значения чтобы получить действительное текущее время.

Листинг 1.12 показывает как написать некий клиент SNTP ниже:

 

#!/usr/bin/env python
# Книга рецептов сетевого программирования Python, второе издание -- Глава - 1
# Данная программа оптимизирована под Python 2.7.12 и Python 3.5.2.
# Она может исполняться во всех прочих версиях с изменениями и/или без них.

import socket
import struct
import sys
import time

NTP_SERVER = "0.uk.pool.ntp.org"
TIME1970 = 2208988800

def sntp_client():
    client = socket.socket( socket.AF_INET, socket.SOCK_DGRAM )
    data = '\x1b' + 47 * '\0'
    client.sendto( data.encode('utf-8'), ( NTP_SERVER, 123 )) 
	data, address = client.recvfrom( 1024 )
    if data:
        print ('Response received from:', address)
    t = struct.unpack( '!12I', data )[10]
    t -= TIME1970
    print ('\tTime=%s' % time.ctime(t))

if __name__ == '__main__':
    sntp_client()
       

Данный рецепт выводит текущее время сервера времени Интернет, получаемое с применением протокола SNTP таким образом:


$ python 1_12_sntp_client.py
('Response received from:',
('192.146.137.13', 123))
      Time=Sat Jun 3 14:45:45 2017
		

Как это работает

Данный клиент SNTP создаёт некое соединение сокета и отправляет все данные протокла. После получения соответствующего овета от определённого сервера NTP (в нашем случае, 0.uk.pool.ntp.org), он распаковывает полученные данные с помощью struct. Наконец, он вычитает эталонное время, которым является 1 января 1970, и печатате полученное время с применением встроенного метода ctime() в модуле time Python.

Написание простого приложения клиент/ сервер echo TCP

После проверки основных API сокета в Python, давайте теперь создадим некий сервер и клиент сокета TCP. Здесь вы получите возможность применить свои основные знания, полученные во всех предыдущих рецептах.

Как это сделать

В данном примере некий сервер будет отвечать эхом на всё, что он получает от клиента. Мы воспользуемся модулем argparse Python для определения необходимого порта TCP из командной строки. И сценарий нашего сервера, и сценарий клиента будут иметь данный аргумент.

Вначале мы создадим свой сервер. Мы начнём созданием некоторого объекта сокета TCP. Затем мы установим повторное использование адреса с тем, чтобы мы могли исполнять данный сервер столько раз, сколько нам потребуется. Мы привязываем определённый сокет к заданному порту на своей локальной машине. На данном этапе прослушивания мы убеждаемся что мы ожидаем множество клиентов в некоторой очереди с применением аргумента откладывания обработки (backlog) в своём методе listen(). Наконец, мы ждём соединений со стороны клиентов и отправляем некоторые данные своему серверу. После получения этих данных, данный сервер возвращает обратно эти данные эхом своему клиенту.

 

Листинг 1.13a показывает как написать некое простое приложение клиент/ сервер echo TCP следующим образом:


#!/usr/bin/env python
# Книга рецептов сетевого программирования Python, второе издание -- Глава - 2
# Данная программа оптимизирована под Python 2.7.12 и Python 3.5.2.
# Она может исполняться во всех прочих версиях с изменениями и/или без них.

import socket
import sys
import argparse

host = 'localhost'
data_payload = 2048
backlog = 5

def echo_server(port):
    """ A simple echo server """
    # Сосдаём некий сокет TCP
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # разрешаем повторное использование адреса/порта
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    # Привязываем данный сокет к определённому порту
    server_address = (host, port)
    print ("Starting up echo server on %s
    port %s" % server_address)
    sock.bind(server_address)
    # Ожидаем клиентов, отлаженные (backlog) аргументы, 
	# определяющие максимальные значения помещаемых в очередь соединений
    sock.listen(backlog)
    while True:
        print ("Waiting to receive message from client")
        client, address = sock.accept()
        data = client.recv(data_payload)
        if data:
            print ("Data: %s" %data)
            client.send(data)
            print ("sent %s bytes back
            to %s" % (data, address))
        # завершаем соединение
        client.close()

if __name__ == '__main__':
    parser = argparse.ArgumentParser
    (description='Socket Server Example')
    parser.add_argument('--port', action="store", dest="port", type=int, required=True)
    given_args = parser.parse_args()
    port = given_args.port
    echo_server(port)
 	   

В своём коде стороны клиента мы создаём некий сокет клиента, применяя полученный аргумент порта и подключаемся к своему серверу. Затем данный клиент отправляет определённое сообщение, Test message. This will be echoed (Тестовое сообщение. Оно подлежит отправке эхом), к своему серверу и данный клиент немедленно получает данное сообщение обратно в некоторых сегментах. В данном случае для отлавливания всех исключений в процессе такого сеанса взаимодействия построены два блока try-except.

 

Листинг 1.13b показывает как написать некое простое приложение клиент/ сервер echo TCP следующим образом:


#!/usr/bin/env python
# Книга рецептов сетевого программирования Python, второе издание -- Глава - 2
# Данная программа оптимизирована под Python 2.7.12 и Python 3.5.2.
# Она может исполняться во всех прочих версиях с изменениями и/или без них.

import socket
import sys
import argparse

host = 'localhost'

def echo_client(port):
    """ A simple echo client """
    # Создаём некий сокет TCP/IP
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # Присоединяем данный сокет к его серверу
    server_address = (host, port)
    print ("Connecting to %s port %s" % server_address)
    sock.connect(server_address)
    # Отправляем данные
    try:
        # Отправляем данные
        message = "Test message. This will be echoed"
        print ("Sending %s" % message)
        sock.sendall(message.encode('utf-8'))
        # Просматриваем полученный отклик
        amount_received = 0
        amount_expected = len(message)
        while amount_received < amount_expected:
            data = sock.recv(16)
            amount_received += len(data)
            print ("Received: %s" % data)
        except socket.error as e:
            print ("Socket error: %s" %str(e))
        except Exception as e:
            print ("Other exception: %s" %str(e))
        finally:
            print ("Closing connection to the server")
            sock.close()

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Socket Server Example')
    parser.add_argument('--port', action="store", dest="port", type=int, required=True)
    given_args = parser.parse_args()
    port = given_args.port
    echo_client(port)
 	   

Как это работает

Чтобы увидеть получающееся взаимодействие клиент/ сервер, в одной консоли запустите следующий сценарий сервера:


$ python 1_13a_echo_server.py --port=9900
Starting up echo server on localhost port 9900
Waiting to receive message from client
		

Теперь выполните в другом терминале своего клиента следующим образом:


$ python 1_13b_echo_client.py --port=9900
Connecting to localhost port 9900
Sending Test message. This will be echoed
Received: Test message. Th
Received: is will be echoe
Received: d
Closing connection to the server
		

В то время когда получено определённое сообщение от имеющегося клиента, наш сервер также выводит нечто подобное приводимому далее сообщению:


Data: Test message. This will be echoed
sent Test message. This will be echoed
bytes back to ('127.0.0.1', 42961)
Waiting to receive message from client
		

Написание простого приложения клиент/ сервер echo UDP

раз мы разработали некий пример сервера и клиента TCP в предыдущем рецепте, мы теперь взглянем как разработать то же самое с применением UDP.

Как это сделать

Данный рецепт аналогичен предыдущему, за исключением того, что он применяет UDP. Метод recvfrom() считывает все сообщения из имеющегося сокета и возвращает все данные и сам адрес клиента.

 

Листинг 1.14a показывает как написать некое простое приложение клиент/ сервер echo TCP следующим образом:


#!/usr/bin/env python
# Книга рецептов сетевого программирования Python, второе издание -- Глава - 2
# Данная программа оптимизирована под Python 2.7.12 и Python 3.5.2.
# Она может исполняться во всех прочих версиях с изменениями и/или без них.

import socket
import sys
import argparse

host = 'localhost'
data_payload = 2048
def echo_server(port):
    """ A simple echo server """
    # Создаём некий сокет UDP
    sock = socket.socket(socket.AF_INET,
    socket.SOCK_DGRAM)
    # Bind the socket to the port
    server_address = (host, port)
    print ("Starting up echo server on %s port %s" % server_address)
    sock.bind(server_address)
    while True:
        print ("Waiting to receive message from client")
        data, address = sock.recvfrom(data_payload)
        print ("received %s bytes from %s" % (len(data), address))
        print ("Data: %s" %data)
        if data:
            sent = sock.sendto(data, address)
            print ("sent %s bytes back to %s" % (sent, address))

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Socket Server Example')
    parser.add_argument('--port', action="store", dest="port", type=int, required=True)
    given_args = parser.parse_args()
    port = given_args.port
    echo_server(port)
 	   

В своём коде клиентской стороны мы создаём некий сокет клиента при помощи полученного аргумента порта и соединяемся с сервером, как мы делали это в своём предыдущем рецепте. Затем данный клиент отправляет сообщение, Test message. This will be echoed (Тестовое сообщение. Оно подлежит отправке эхом), к своему серверу и данный клиент немедленно получает данное сообщение обратно в некоторых сегментах.

 

Листинг 1.14b показывает как написать некое простое приложение клиент/ сервер echo TCP следующим образом:


#!/usr/bin/env python
# Книга рецептов сетевого программирования Python, второе издание -- Глава - 2
# Данная программа оптимизирована под Python 2.7.12 и Python 3.5.2.
# Она может исполняться во всех прочих версиях с изменениями и/или без них.

import socket
import sys
import argparse

host = 'localhost'
data_payload = 2048

def echo_client(port):
    """ A simple echo client """
    # Создаём некий сокет UDP
    sock = socket.socket(socket.AF_INET,
    socket.SOCK_DGRAM)
    server_address = (host, port)
    print ("Connecting to %s port %s" % server_address)
    message = 'This is the message. It will be repeated.'
    try:
        # Send data
        message = "Test message. This will be echoed"
        print ("Sending %s" % message)
        sent = sock.sendto(message.encode('utf-8'), server_address)
        # Receive response
        data, server = sock.recvfrom(data_payload)
        print ("received %s" % data)
    finally:
        print ("Closing connection to the server")
        sock.close()

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Socket Server Example')
    parser.add_argument('--port', action="store", dest="port", type=int, required=True)
    given_args = parser.parse_args()
    port = given_args.port
    echo_client(port)
 	   
[Совет]Выгрузка примера кода

Подробные шаги по выгрузке всего пакета кода приводятся во введении данной книги. Весь пакет кода для данной книги также размещён на GitHub по адресу: https://github.com/PacktPublishing/Python-Network-Programming-Cookbook-Second-Edition. У нас также имеются прочие пакеты кода из нашего богатого каталога книг и видео, которы доступны по адресу: https://github.com/PacktPublishing/. Следите за ними!

Как это работает

Чтобы увидеть получающееся взаимодействие клиент/ сервер, в одной консоли запустите следующий сценарий сервера:


$ python 1_14a_echo_server_udp.py --port=9900
Starting up echo server on localhost port 9900
Waiting to receive message from client
		

Теперь выполните в другом терминале своего клиента следующим образом:


$ python 1_14b_echo_client_udp.py --port=9900
Connecting to localhost port 9900
Sending Test message. This will be echoed
received Test message. This will be echoed
Closing connection to the server
		

В то время когда получено определённое сообщение от имеющегося клиента, наш сервер также выводит нечто подобное приводимому далее сообщению:


received 33 bytes from ('127.0.0.1', 43542)
Data: Test message. This will be echoed
sent 33 bytes back to ('127.0.0.1', 43542)
Waiting to receive message from client