<?xml version="1.0"?>
<?xml-stylesheet type="text/css" href="http://wiki.linuxformat.ru/wiki/skins/common/feed.css?303"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ru">
		<id>http://wiki.linuxformat.ru/wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Mim</id>
		<title>Linuxformat - Вклад участника [ru]</title>
		<link rel="self" type="application/atom+xml" href="http://wiki.linuxformat.ru/wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Mim"/>
		<link rel="alternate" type="text/html" href="http://wiki.linuxformat.ru/wiki/%D0%A1%D0%BB%D1%83%D0%B6%D0%B5%D0%B1%D0%BD%D0%B0%D1%8F:Contributions/Mim"/>
		<updated>2026-05-13T13:13:53Z</updated>
		<subtitle>Вклад участника</subtitle>
		<generator>MediaWiki 1.19.20+dfsg-0+deb7u3</generator>

	<entry>
		<id>http://wiki.linuxformat.ru/wiki/LXF81:Python</id>
		<title>LXF81:Python</title>
		<link rel="alternate" type="text/html" href="http://wiki.linuxformat.ru/wiki/LXF81:Python"/>
				<updated>2008-12-10T11:43:24Z</updated>
		
		<summary type="html">&lt;p&gt;Mim: /* Сокеты */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Цикл/Python}}&lt;br /&gt;
&lt;br /&gt;
==Задачи многозадачности==&lt;br /&gt;
''часть 1 '''Сергей Супрунов''' открывает новый цикл статей, в котором будут более полно освещены некоторые практические моменты разработки приложений на языке Python. Начнём, пожалуй, с вопросов параллельных вычислений...''&lt;br /&gt;
&lt;br /&gt;
Практически любая программа, особенно если она в процессе своей работы осуществляет взаимодействие с пользователем или удалённым клиентом, довольно много времени тратит впустую, ожидая ответного хода своего «партнёра».&lt;br /&gt;
Неэффективность проявляется и в других вопросах: процессор простаивает, пока программа работает с жёстким диском; жёсткий диск,&lt;br /&gt;
напротив, бездействует, пока программа занята вычислительными&lt;br /&gt;
задачами, например, обработкой только что считанных с диска данных.&lt;br /&gt;
Поэтому рано или поздно разработчики операционных систем должны&lt;br /&gt;
были прийти к идее распараллеливания работы.&lt;br /&gt;
&lt;br /&gt;
В большинстве современных ОС эта идея имеет две реализации: процессы и потоки (причем в Linux одно практически неотличимо от другого).&lt;br /&gt;
Процесс, если говорить упрощённо, представляет собой некоторый набор&lt;br /&gt;
ресурсов (область памяти, значения процессорных регистров, открытые&lt;br /&gt;
дескрипторы файлов и т.д.), принадлежащих какой-то задаче. На однопроцессорных машинах одновременно может обрабатываться только&lt;br /&gt;
один процесс, остальные в это время находятся в очереди. Ядро системы,&lt;br /&gt;
точнее, его планировщик, в соответствии с заданным алгоритмом предоставляет доступ к процессору ожидающим процессам в соответствии с&lt;br /&gt;
их приоритетом. Если текущий процесс переходит в состояние ожидания&lt;br /&gt;
ввода-вывода, то доступ к процессору передаётся следующему процессу&lt;br /&gt;
в очереди. Благодаря этому, во-первых, реализуется более эффективное&lt;br /&gt;
использование ресурсов системы, а во-вторых, несколько задач могут&lt;br /&gt;
выполняться в одно и то же (с точки зрения пользователя) время.&lt;br /&gt;
&lt;br /&gt;
Потоки (threads, их также называют нитями или облегчёнными процессами) решают аналогичную задачу, но в рамках одного процесса.&lt;br /&gt;
При управлении как потоками, так и процессами операционная система&lt;br /&gt;
вынуждена «непроизводительно» расходовать некоторые ресурсы на&lt;br /&gt;
так называемое переключение контекста (т.е. на выполнение «подготовительных» мероприятий, таких как восстановление значения регистров и адресного пространства). Благодаря тому, что потоки разделяют&lt;br /&gt;
некоторые ресурсы (например, память процесса, в рамках которого они&lt;br /&gt;
исполняются), переключение их контекста происходит заметно быстрее,&lt;br /&gt;
чем контекста процесса. Благодаря этому можно распараллеливать&lt;br /&gt;
задачи с заметно меньшими затратами. Хорошим примером может служить производительность Apache 2.x (см. обзор в [[LXF77:Apache 2.2|LXF77]]).&lt;br /&gt;
&lt;br /&gt;
Хватит, пожалуй, теории. Посмотрим, какие средства предоставляет&lt;br /&gt;
язык Python программисту, желающему воспользоваться многозадачностью операционной системы, а заодно рассмотрим такие вещи как&lt;br /&gt;
сокеты и межпроцессорное взаимодействие.&lt;br /&gt;
===Сокеты===&lt;br /&gt;
Сокет (конечная точка сетевых коммуникаций) – это основа клиент-серверных приложений. Фактически, это интерфейс, с помощью которого процессы могут осуществлять обмен информацией между собой.&lt;br /&gt;
Конкретная реализация определяется так называемым коммуникационным доменом, наиболее распространённые из них – Internet-домен и&lt;br /&gt;
Unix-домен. Internet-сокеты позволяют реализовать взаимодействие на&lt;br /&gt;
базе протоколов сети Интернет, таких как TCP или UDP. О них мы подробнее поговорим в одной из следующих статей цикла.&lt;br /&gt;
&lt;br /&gt;
Unix-сокеты представляют собой файловые объекты, куда процессы&lt;br /&gt;
могут записывать поток данных и считывать его. Процесс, прослушивающий сокет в ожидании входящих сообщений, по традиции именуется сервером, а подключающийся к сокету для обмена данными – клиентом.&lt;br /&gt;
&lt;br /&gt;
В языке Python работа с сокетами реализована в модуле socket. Со стороны сервера создание сокета&lt;br /&gt;
(рассмотрим пример для домена Unix) выглядит следующим образом:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
import socket                     # импортируем модуль&lt;br /&gt;
s = socket.socket(socket.AF_UNIX) # создаём сокет домена AF_UNIX&lt;br /&gt;
s.bind('/tmp/test.sock')          # привязываем его к файлу&lt;br /&gt;
s.listen(1)                       # начинаем прослушивать&lt;br /&gt;
conn, addr = s.accept()           # ждём подключения&lt;br /&gt;
conn.send('HELO')                 # дождавшись, отправляем клиенту строку&lt;br /&gt;
data = conn.recv(1024)            # получаем от клиента данные&lt;br /&gt;
s.close()                         # закрываем сокет&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Обратите внимание на то, что методы accept(), send(), recv() являются по умолчанию блокирующими,&lt;br /&gt;
т.е. работа программы приостанавливается до тех пор, пока не будет выполнено необходимое действие. Это&lt;br /&gt;
означает, что клиент и сервер должны (по крайней мере, в нашей простейшей реализации) строго придерживаться определённой последовательности действий (протокола). В нашем случае после установки соединения&lt;br /&gt;
сервер посылает строку приветствия. Если клиент, вместо того чтобы принять эту информацию, сам начнёт&lt;br /&gt;
что-то передавать, то мы получим взаимную блокировку – клиент будет ждать, пока сервер примет его данные,&lt;br /&gt;
сервер же будет ждать аналогичных действий со стороны клиента.&lt;br /&gt;
&lt;br /&gt;
Для нормальной работы клиентская реализация должна выглядеть примерно так:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
import socket                     # импортируем модуль&lt;br /&gt;
c = socket.socket(socket.AF_UNIX) # создаём сокет такого-же домена&lt;br /&gt;
c.connect('/tmp/test.sock')       # подключаемся к сокету сервера&lt;br /&gt;
greeting = c.recv(1024)           # принимаем строку приветсвия&lt;br /&gt;
c.send('Hello, server!')          # отправляем свои данные&lt;br /&gt;
c.close()                         # закрываем соединение&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Понятно, что в данном примере мы получили «одноразовый» сервер, который, дождавшись соединения и&lt;br /&gt;
приняв данные, завершает свою работу. Для постоянной работы фрагмент, начиная с метода accept(), нужно&lt;br /&gt;
поместить в бесконечный цикл (см. пример ниже).&lt;br /&gt;
&lt;br /&gt;
===Модуль select===&lt;br /&gt;
В Python доступен ещё один способ повысить эффективность работы за счёт параллельного выполнения некоторых операций – модуль select. Он использует системный вызов select для мультиплексирования соединений клиентов в одном цикле событий – метод select этого модуля позволяет отслеживать одновременно&lt;br /&gt;
несколько сокетов или других файловых объектов (только на Unix-подобных системах) в ожидании готовности&lt;br /&gt;
одного из них, после чего управление возвращается основной программе. Благодаря этому программа может&lt;br /&gt;
обрабатывать сразу несколько сокетов по мере их готовности к взаимодействию. Познакомимся с этим модулем поближе.&lt;br /&gt;
&lt;br /&gt;
Чтобы не замусоривать статью множеством фрагментов кода, приступим сразу к рассмотрению серьёзного примера, к которому будем обращаться по мере необходимости. Данный пример – простейший аналог&lt;br /&gt;
демона syslog, задача которого – получать через Unix-сокет информацию от клиентов и записывать её в файл&lt;br /&gt;
журнала. Если что-то не совсем понятно сразу, не обращайте на это внимание – всё прояснится к концу изложения. Код представлен на врезке logserver.py.&lt;br /&gt;
&lt;br /&gt;
Чтобы в дальнейшем было проще модифицировать код, реализуем его в виде класса. В его конструкторе (метод __init__()) решаются три задачи:&lt;br /&gt;
открытие файла (стр. 8-9), удаление файла-сокета,&lt;br /&gt;
который может остаться в случае аварийного завершения сценария (стр. 10-15) и собственно создание&lt;br /&gt;
сокета (стр. 16-18). На строки 19-21 пока не обращайте внимания.&lt;br /&gt;
&lt;br /&gt;
Методы openlog() и writelog() соответственно открывают лог-файл и записывают в него строку, предваряя текущей датой. Ну и метод start() –&lt;br /&gt;
основной, в котором и осуществляется обработка&lt;br /&gt;
входящих соединений.&lt;br /&gt;
&lt;br /&gt;
Для того, чтобы сервер постоянно обслуживал&lt;br /&gt;
вверенный ему сокет, создаётся бесконечный цикл&lt;br /&gt;
(строка 37). Однако здесь есть небольшая проблема.&lt;br /&gt;
Вы же ещё помните, что методы accept(), recv() и&lt;br /&gt;
send() являются по умолчанию блокирующими? То&lt;br /&gt;
есть при «последовательной» обработке первый клиент, достучавшийся до сервера, полностью завладеет вниманием последнего, пока не завершит работу&lt;br /&gt;
согласно заданному протоколу. Остальные же будут&lt;br /&gt;
либо поставлены в очередь, либо вообще отброшены,&lt;br /&gt;
если размер очереди превысит установленное значение (задаётся параметром метода listen()). Только&lt;br /&gt;
полностью обслужив первого клиента, сервер сможет&lt;br /&gt;
вернуться к строке 44 и снова вызвать accept().&lt;br /&gt;
&lt;br /&gt;
Если обмен с клиентом происходит быстро, то&lt;br /&gt;
такая схема работы вполне приемлема. Однако если&lt;br /&gt;
протокол требует ведения «диалога», в ходе которого возможны задержки, то это может стать серьёзной&lt;br /&gt;
проблемой. В рассматриваемом примере мы специально усложнили протокол, сделав его двухэтапным –&lt;br /&gt;
сначала клиент должен представиться, дождаться от&lt;br /&gt;
сервера подтверждения, затем отправить данные и&lt;br /&gt;
снова дождаться подтверждения.&lt;br /&gt;
&lt;br /&gt;
Если между первой и второй отправкой данных возникнет пауза (в коде клиента она искусственно реализована функцией time.sleep()), то&lt;br /&gt;
сервер будет понапрасну простаивать, хотя вполне мог бы заняться обслуживанием других клиентов. Собственно, для этого и используется метод&lt;br /&gt;
select() одноимённого модуля (строка 39).&lt;br /&gt;
&lt;br /&gt;
Принцип действия его следующий – он берёт на&lt;br /&gt;
себя ожидание данных в сокетах (массивы обслуживаемых сокетов передаются ему в виде параметров),&lt;br /&gt;
передавая управление основной программе, если&lt;br /&gt;
один из сокетов будет готов к обслуживанию.&lt;br /&gt;
&lt;br /&gt;
Чтобы было понятнее, рассмотрим, что происходит&lt;br /&gt;
в нашем примере. В строке 39 мы запускаем метод&lt;br /&gt;
select(). Как только один (или несколько) из обслуживаемых сокетов (первоначально такой сокет только&lt;br /&gt;
один, созданный при инициализации объекта в строке 16) будет готов к обслуживанию, select() передаёт&lt;br /&gt;
основной программе массив сокетов, готовых к работе,&lt;br /&gt;
который обрабатывается в цикле (строка 42). Так, если&lt;br /&gt;
к работе готов «родительский» сокет, для него вызывается метод accept(). Поскольку метод select()&lt;br /&gt;
гарантирует, что запрос на соединение уже есть, то&lt;br /&gt;
основной программе не придётся тратить время на&lt;br /&gt;
ожидание – accept() будет обработан сразу, вернув&lt;br /&gt;
объект – новый сокет, предназначенный для работы с&lt;br /&gt;
данным клиентом. Но мы не начинаем сразу же выполнять установленный протокол, а просто помещаем этот&lt;br /&gt;
новый сокет в список rsocks, обслуживаемый методом&lt;br /&gt;
select() (строка 45).&lt;br /&gt;
&lt;br /&gt;
Когда на этот сокет поступят данные от клиента, select() вновь сообщит о готовности. На этот&lt;br /&gt;
раз обработка пойдёт по ветке «else» (строка 46).&lt;br /&gt;
Поскольку наш протокол двухэтапный, то считывать&lt;br /&gt;
данные мы должны два раза, что и реализуется&lt;br /&gt;
дополнительной конструкцией «if – else» (строки 47-56): при первом «подходе» словарь senders не&lt;br /&gt;
будет содержать упоминания данного сокета (обратные кавычки позволяют работать не с самим сокетом,&lt;br /&gt;
а с его «строковым представлением»); при втором&lt;br /&gt;
же этот словарь уже будет содержать имя отправителя, ассоциированное с сокетом. На втором этапе&lt;br /&gt;
выполняется запись строки в лог-файл (строка 54),&lt;br /&gt;
удаление сокета из массива rsocks (строка 55), что-бы select() уже не занимался его обслуживанием, и&lt;br /&gt;
удаление записи из словаря (строка 56).&lt;br /&gt;
&lt;br /&gt;
Обратите внимание, что мы не можем просто&lt;br /&gt;
взять и последовательно вызвать два метода recv()&lt;br /&gt;
для получения всех данных, поскольку второй вызов&lt;br /&gt;
окажется уже блокирующим – ведь на первый recv()&lt;br /&gt;
мы попадаем, только когда select() обнаружит готовые для обработки данные; во втором же случае&lt;br /&gt;
готовности придётся ждать самостоятельно.&lt;br /&gt;
&lt;br /&gt;
По большому счёту, вызовы send() в нашем&lt;br /&gt;
примере получились блокирующие – если клиент&lt;br /&gt;
не сможет сразу принять переданное ему подтверждение, то сервер будет простаивать. Решается это&lt;br /&gt;
аналогичным путём, но уже с помощью массива&lt;br /&gt;
wsocks, однако из боязни сделать код чрезмерно&lt;br /&gt;
сложным и нечитаемым, в данном примере мы проигнорируем эту проблему, оставив её решение вам в&lt;br /&gt;
качестве упражнения.&lt;br /&gt;
===Сигналы===&lt;br /&gt;
Остались ещё две проблемы. Во-первых, хотелось&lt;br /&gt;
бы, чтобы сервер перед завершением своей работы (поскольку используется бесконечный цикл, то&lt;br /&gt;
это придётся делать «грубыми» методами вроде&lt;br /&gt;
команды kill или Ctrl+C) успевал выполнить некоторые полезные действия (например, закрыть файл&lt;br /&gt;
журнала, удалить файл сокета). Во-вторых, если в&lt;br /&gt;
процессе работы сервера удалить или переименовать лог-файл и создать новый с таким же именем&lt;br /&gt;
(например, это может происходить при ротации журнала утилитами типа logrotate), то дескриптор открытого файла (self.log в нашем примере) не изменится, продолжая указывать на прежнее расположение&lt;br /&gt;
файла в файловой системе. Так что запись будет&lt;br /&gt;
вестись по этому дескриптору, в уже переименованный или удалённый файл (поскольку на файл будет&lt;br /&gt;
оставаться ссылка, «привязанная» к дескриптору, то&lt;br /&gt;
при удалении из каталога он физически будет оставаться на месте, пока не будет удалён этот дескриптор) То есть нужно предусмотреть переинициализацию файла журнала.&lt;br /&gt;
&lt;br /&gt;
Как команда kill, так и комбинация [Ctrl+C] реализуют метод межпроцессорного взаимодействия,&lt;br /&gt;
именуемый сигналами. Например, kill 3942 отправит&lt;br /&gt;
процессу номер 3942 сигнал 15 (SIGTERM), дающий&lt;br /&gt;
указание завершить работу. Ctrl+C отправляет сигнал 2 (SIGINT). Большинство сигналов процесс может&lt;br /&gt;
перехватить и обработать по собственному желанию,&lt;br /&gt;
чем мы и воспользуемся.&lt;br /&gt;
&lt;br /&gt;
В Python для этого предназначен модуль signal.&lt;br /&gt;
Собственно, его мы и используем в строках 19–21,&lt;br /&gt;
назначая на некоторые сигналы в качестве обработчика метод stop(). Для сигнала 1 (SIGHUP) в качестве обработчика&lt;br /&gt;
назначается метод reinit(), который решает задачу переинициализации&lt;br /&gt;
открытого файла журнала.&lt;br /&gt;
===Тестирование===&lt;br /&gt;
Чтобы проверить работу нашего сервера, нам нужен клиент. Его код&lt;br /&gt;
представлен во врезке logclient.py. Никаких сложностей здесь нет.&lt;br /&gt;
Поясню лишь, что конструкции time.sleep(5) (стр. 15 и 21) искусственно создают задержку между первым и вторым этапами диалога.&lt;br /&gt;
&lt;br /&gt;
Чтобы убедиться в том, что все клиенты обслуживаются сервером&lt;br /&gt;
параллельно, нужно запустить их в нескольких экземплярах (например, с разных консолей). В результате в файле журнала появятся такие&lt;br /&gt;
записи:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Sun Jun 4 15:30:17 2006: ===&amp;gt; LogServer started&lt;br /&gt;
Sun Jun 4 15:30:55 2006: [test2] Test message&lt;br /&gt;
Sun Jun 4 15:30:56 2006: [test3] Test message&lt;br /&gt;
Sun Jun 4 15:31:00 2006: ===&amp;gt; LogServer stopped [signal 2]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Как видите, между записью сообщений от test2 и test3 прошла&lt;br /&gt;
одна секунда, хотя каждый клиент требует для своей обработки как&lt;br /&gt;
минимум 10. Значит, ожидание ответа от обоих клиентов выполняется&lt;br /&gt;
одновременно, чего мы и добивались.&lt;br /&gt;
===Ветвления===&lt;br /&gt;
Впрочем, select – это не единственный способ организовать параллельную работу в Python. Модуль os предоставляет функцию fork(),&lt;br /&gt;
которая использует одноимённый системный вызов, порождающий&lt;br /&gt;
копию текущего процесса. Чтобы посмотреть, как это работает на практике, напишем небольшой сценарий, который будет автоматически&lt;br /&gt;
запускать скрипты-клиенты для тестирования нашего сервера (действительно, негоже делать вручную то, что можно поручить программе). Код&lt;br /&gt;
представлен на врезке logclient2.py.&lt;br /&gt;
&lt;br /&gt;
Здесь всё до безобразия просто – функция fork() (строка 6) порождает копию текущего процесса. В каждой копии выполнение кода будет&lt;br /&gt;
продолжено как ни в чём не бывало со следующей команды. Чтобы код&lt;br /&gt;
мог понять, где он выполняется – в родительском процессе или в дочернем, используется значение, возвращаемое функцией fork(). Дочерний&lt;br /&gt;
процесс получает значение 0, родительский – идентификатор порождённого дочернего процесса (PID).&lt;br /&gt;
&lt;br /&gt;
Кстати, функция os._exit(0) в строке 10 позволяет завершить&lt;br /&gt;
дочерний процесс. Если этого не сделать, то он пойдёт на выполнение&lt;br /&gt;
цикла for (строка 5), уже сам выступая в качестве родительского и&lt;br /&gt;
порождая, таким образом, настоящую лавину новых процессов.&lt;br /&gt;
&lt;br /&gt;
Естественно, таким образом можно было бы реализовать и наш&lt;br /&gt;
сервер – после метода accept() ответвлять дочерний процесс, который&lt;br /&gt;
занимался бы обслуживанием конкретного клиента, в то время как родительский продолжал бы «висеть» на методе accept(), ожидая входящие&lt;br /&gt;
соединения. Именно так и работают многие серверы, например, Apache&lt;br /&gt;
(версия 1.х – только так и никак иначе, а в 2.х появились потоки).&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== БЛОКИРОВАТЬ НЕОБЯЗАТЕЛЬНО ===&lt;br /&gt;
Модуль '''socket''' также предоставляет возможность работы с неблокирующими вызовами ''accept()'', ''send()'' и ''recv()''. Для&lt;br /&gt;
этого следует предварительно установить значение соответствующего атрибута объекта-сокета с помощью следующего&lt;br /&gt;
метода:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
socket.setblocking(0)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Значение '''0''' переключает сокет в неблокирующий режим работы (по умолчанию используется блокирующий –&lt;br /&gt;
значение '''1'''). При этом методы ''accept()'', ''send()'' и ''recv()'' при отсутствии данных для обработки не останавливают&lt;br /&gt;
выполнение программы до их появления, а генерируют исключение ''socket.error''. Что с ним делать дальше – решать&lt;br /&gt;
вам. Например, можно просто игнорировать:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
while(1):&lt;br /&gt;
    try:&lt;br /&gt;
        data = sock.recv()&lt;br /&gt;
    except socket.error, errcode:&lt;br /&gt;
        if errcode[0] == 35:&lt;br /&gt;
            pass&lt;br /&gt;
        else:&lt;br /&gt;
            raise(socket.error)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Второй параметр оператора ''except'' – переменная, в которую будет занесён код ошибки. Этот код представляет собой&lt;br /&gt;
кортеж вида ('''35''', ‘Resource temporarily unavailable’), где первый элемент – числовой код ошибки, а второй –&lt;br /&gt;
текстовая строка-пояснение. При отсутствии данных генерируется ошибка '''35''', которую мы и игнорируем (''pass''). Здесь&lt;br /&gt;
мы получаем то же ожидание данных, но уже реализованное самим кодом Python. Но преимущество здесь в том, что&lt;br /&gt;
вместо оператора ''pass'' можно реализовать любую обработку. Например, переходить к опросу другого сокета.&lt;br /&gt;
&lt;br /&gt;
===logserver.py===&lt;br /&gt;
&amp;lt;source lang=&amp;quot;python&amp;quot; line=&amp;quot;GESHI_NORMAL_LINE_NUMBERS&amp;quot;&amp;gt;&lt;br /&gt;
#!/usr/bin/python&lt;br /&gt;
# -*- coding: utf-8 -*-&lt;br /&gt;
&lt;br /&gt;
import os, socket, time, signal, select&lt;br /&gt;
&lt;br /&gt;
class LogServer:&lt;br /&gt;
    def __init__(self, sockfile='./lserv.sock',&lt;br /&gt;
                 logfile='./lserv.log',&lt;br /&gt;
                 maxqueue=5):&lt;br /&gt;
        self.logfilename = logfile&lt;br /&gt;
        self.openlog()&lt;br /&gt;
        self.sockfilename = sockfile&lt;br /&gt;
        try:&lt;br /&gt;
            if os.path.exists(sockfile):&lt;br /&gt;
                os.unlink(sockfile)&lt;br /&gt;
        except:&lt;br /&gt;
            raise 'error'&lt;br /&gt;
&lt;br /&gt;
        self.socket = socket.socket(socket.AF_UNIX)&lt;br /&gt;
        self.socket.bind(sockfile)&lt;br /&gt;
        self.socket.listen(maxqueue)&lt;br /&gt;
&lt;br /&gt;
        signal.signal(signal.SIGHUP, self.reinit)&lt;br /&gt;
        signal.signal(signal.SIGINT, self.stop)&lt;br /&gt;
        signal.signal(signal.SIGTERM, self.stop)&lt;br /&gt;
&lt;br /&gt;
        self.writelog('===&amp;gt; LogServer started')&lt;br /&gt;
&lt;br /&gt;
    def openlog(self):&lt;br /&gt;
        self.log = open(self.logfilename, 'a+')&lt;br /&gt;
&lt;br /&gt;
    def writelog(self, message):&lt;br /&gt;
        self.log.write('%s: %s\n' % (time.asctime(), message))&lt;br /&gt;
&lt;br /&gt;
    def reinit(self, signum, frame):&lt;br /&gt;
        self.log.close()&lt;br /&gt;
        self.openlog()&lt;br /&gt;
        self.start()&lt;br /&gt;
&lt;br /&gt;
    def start(self):&lt;br /&gt;
        rsocks = []&lt;br /&gt;
        wsocks = []&lt;br /&gt;
        rsocks.append(self.socket)&lt;br /&gt;
        senders = {}&lt;br /&gt;
        while 1:&lt;br /&gt;
            try:&lt;br /&gt;
                reads, writes, errs = select.select(rsocks, wsocks, [])&lt;br /&gt;
            except:&lt;br /&gt;
                return&lt;br /&gt;
&lt;br /&gt;
            for sock in reads:&lt;br /&gt;
                if sock == self.socket:&lt;br /&gt;
                    client, name = sock.accept()&lt;br /&gt;
                    rsocks.append(client)&lt;br /&gt;
                elif not `sock` in senders.keys():&lt;br /&gt;
                    sender = sock.recv(1024)&lt;br /&gt;
                    sock.send('Sender OK')&lt;br /&gt;
                    senders['sock'] = sender&lt;br /&gt;
                else:&lt;br /&gt;
                    message = sock.recv(1024)&lt;br /&gt;
                    sock.send('Message OK')&lt;br /&gt;
                    self.writelog('[%s] %s' % (senders['sock'], message))&lt;br /&gt;
                    rsocks.remove(sock)&lt;br /&gt;
                    del senders['sock']&lt;br /&gt;
&lt;br /&gt;
    def stop(self, signum, frame):&lt;br /&gt;
        self.writelog('===&amp;gt; LogServer stopped [signal %s]' % (signum))&lt;br /&gt;
        self.log.close()&lt;br /&gt;
        os.unlink(self.sockfilename)&lt;br /&gt;
&lt;br /&gt;
if __name__ == '__main__':&lt;br /&gt;
    serv = LogServer(maxqueue=3)&lt;br /&gt;
    serv.start()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===logclient.py===&lt;br /&gt;
&amp;lt;source lang=&amp;quot;python&amp;quot; line=&amp;quot;GESHI_NORMAL_LINE_NUMBERS&amp;quot;&amp;gt;&lt;br /&gt;
#!/usr/bin/python&lt;br /&gt;
# -*- coding: utf-8 -*-&lt;br /&gt;
import sys, socket, time&lt;br /&gt;
class LogClient:&lt;br /&gt;
def __init__(self, sender='generic client',&lt;br /&gt;
sockfile='./lserv.sock',&lt;br /&gt;
buffersize=1024,&lt;br /&gt;
testmode=0):&lt;br /&gt;
self.sender = sender&lt;br /&gt;
self.sockfile = sockfile&lt;br /&gt;
self.buffersize = buffersize&lt;br /&gt;
self.testmode = testmode&lt;br /&gt;
def writelog(self, message):&lt;br /&gt;
if self.testmode:&lt;br /&gt;
time.sleep(5)&lt;br /&gt;
self.socket = socket.socket(socket.AF_UNIX)&lt;br /&gt;
self.socket.connect(self.sockfile)&lt;br /&gt;
self.socket.send(self.sender)&lt;br /&gt;
if self.socket.recv(self.buffersize) == 'Sender OK':&lt;br /&gt;
if self.testmode:&lt;br /&gt;
time.sleep(5)&lt;br /&gt;
self.socket.send(message)&lt;br /&gt;
if not self.socket.recv(self.buffersize) == 'Message OK':&lt;br /&gt;
print 'Ошибка: нет подтверждения Message'&lt;br /&gt;
else:&lt;br /&gt;
print 'Ошибка: нет подтверждения Sender'&lt;br /&gt;
self.socket.close()&lt;br /&gt;
if __name__ == '__main__':&lt;br /&gt;
sendername = sys.argv[1]&lt;br /&gt;
client = LogClient(sender=sendername, testmode=1)&lt;br /&gt;
client.writelog('Test message')&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===logclient2.py===&lt;br /&gt;
&amp;lt;source lang=&amp;quot;python&amp;quot; line=&amp;quot;GESHI_NORMAL_LINE_NUMBERS&amp;quot;&amp;gt;&lt;br /&gt;
#!/usr/bin/python&lt;br /&gt;
# -*- coding: utf-8 -*-&lt;br /&gt;
import os&lt;br /&gt;
from logclient import LogClient&lt;br /&gt;
for i in xrange(25):&lt;br /&gt;
pid = os.fork()&lt;br /&gt;
if pid == 0:&lt;br /&gt;
client = LogClient(sender='client%d' % i, testmode=1)&lt;br /&gt;
client.writelog('Test from client%d' % i)&lt;br /&gt;
os._exit(0)&lt;br /&gt;
else:&lt;br /&gt;
print 'Start child[%d]' % pid&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mim</name></author>	</entry>

	<entry>
		<id>http://wiki.linuxformat.ru/wiki/LXF81:Python</id>
		<title>LXF81:Python</title>
		<link rel="alternate" type="text/html" href="http://wiki.linuxformat.ru/wiki/LXF81:Python"/>
				<updated>2008-11-20T00:10:33Z</updated>
		
		<summary type="html">&lt;p&gt;Mim: подправаил отступы, сделал выделение кода и чисел в тексте&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Цикл/Python}}&lt;br /&gt;
&lt;br /&gt;
==Задачи многозадачности==&lt;br /&gt;
''часть 1 '''Сергей Супрунов''' открывает новый цикл статей, в котором будут более полно освещены некоторые практические моменты разработки приложений на языке Python. Начнём, пожалуй, с вопросов параллельных вычислений...''&lt;br /&gt;
&lt;br /&gt;
Практически любая программа, особенно если она в процессе своей работы осуществляет взаимодействие с пользователем или удалённым клиентом, довольно много времени тратит впустую, ожидая ответного хода своего «партнёра».&lt;br /&gt;
Неэффективность проявляется и в других вопросах: процессор простаивает, пока программа работает с жёстким диском; жёсткий диск,&lt;br /&gt;
напротив, бездействует, пока программа занята вычислительными&lt;br /&gt;
задачами, например, обработкой только что считанных с диска данных.&lt;br /&gt;
Поэтому рано или поздно разработчики операционных систем должны&lt;br /&gt;
были прийти к идее распараллеливания работы.&lt;br /&gt;
&lt;br /&gt;
В большинстве современных ОС эта идея имеет две реализации: процессы и потоки (причем в Linux одно практически неотличимо от другого).&lt;br /&gt;
Процесс, если говорить упрощённо, представляет собой некоторый набор&lt;br /&gt;
ресурсов (область памяти, значения процессорных регистров, открытые&lt;br /&gt;
дескрипторы файлов и т.д.), принадлежащих какой-то задаче. На однопроцессорных машинах одновременно может обрабатываться только&lt;br /&gt;
один процесс, остальные в это время находятся в очереди. Ядро системы,&lt;br /&gt;
точнее, его планировщик, в соответствии с заданным алгоритмом предоставляет доступ к процессору ожидающим процессам в соответствии с&lt;br /&gt;
их приоритетом. Если текущий процесс переходит в состояние ожидания&lt;br /&gt;
ввода-вывода, то доступ к процессору передаётся следующему процессу&lt;br /&gt;
в очереди. Благодаря этому, во-первых, реализуется более эффективное&lt;br /&gt;
использование ресурсов системы, а во-вторых, несколько задач могут&lt;br /&gt;
выполняться в одно и то же (с точки зрения пользователя) время.&lt;br /&gt;
&lt;br /&gt;
Потоки (threads, их также называют нитями или облегчёнными процессами) решают аналогичную задачу, но в рамках одного процесса.&lt;br /&gt;
При управлении как потоками, так и процессами операционная система&lt;br /&gt;
вынуждена «непроизводительно» расходовать некоторые ресурсы на&lt;br /&gt;
так называемое переключение контекста (т.е. на выполнение «подготовительных» мероприятий, таких как восстановление значения регистров и адресного пространства). Благодаря тому, что потоки разделяют&lt;br /&gt;
некоторые ресурсы (например, память процесса, в рамках которого они&lt;br /&gt;
исполняются), переключение их контекста происходит заметно быстрее,&lt;br /&gt;
чем контекста процесса. Благодаря этому можно распараллеливать&lt;br /&gt;
задачи с заметно меньшими затратами. Хорошим примером может служить производительность Apache 2.x (см. обзор в [[LXF77:Apache 2.2|LXF77]]).&lt;br /&gt;
&lt;br /&gt;
Хватит, пожалуй, теории. Посмотрим, какие средства предоставляет&lt;br /&gt;
язык Python программисту, желающему воспользоваться многозадачностью операционной системы, а заодно рассмотрим такие вещи как&lt;br /&gt;
сокеты и межпроцессорное взаимодействие.&lt;br /&gt;
===Сокеты===&lt;br /&gt;
Сокет (конечная точка сетевых коммуникаций) – это основа клиент-серверных приложений. Фактически, это интерфейс, с помощью которого процессы могут осуществлять обмен информацией между собой.&lt;br /&gt;
Конкретная реализация определяется так называемым коммуникационным доменом, наиболее распространённые из них – Internet-домен и&lt;br /&gt;
Unix-домен. Internet-сокеты позволяют реализовать взаимодействие на&lt;br /&gt;
базе протоколов сети Интернет, таких как TCP или UDP. О них мы подробнее поговорим в одной из следующих статей цикла.&lt;br /&gt;
&lt;br /&gt;
Unix-сокеты представляют собой файловые объекты, куда процессы&lt;br /&gt;
могут записывать поток данных и считывать его. Процесс, прослушивающий сокет в ожидании входящих сообщений, по традиции именуется сервером, а подключающийся к сокету для обмена данными – клиентом.&lt;br /&gt;
&lt;br /&gt;
В языке Python работа с сокетами реализована в модуле socket. Со стороны сервера создание сокета&lt;br /&gt;
(рассмотрим пример для домена Unix) выглядит следующим образом:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
import socket                     # импортируем модуль&lt;br /&gt;
s = socket.socket(socket.AF_UNIX) # создаём сокет домена AF_UNIX&lt;br /&gt;
s.bind('/tmp/test.sock')          # привязываем его к файлу&lt;br /&gt;
s.listen(1)                       # начинаем прослушивать&lt;br /&gt;
conn = s.accept()                 # ждём подключения&lt;br /&gt;
conn.send('HELO')                 # дождавшись, отправляем клиенту строку&lt;br /&gt;
data = conn.recv(1024)            # получаем от клиента данные&lt;br /&gt;
s.close()                         # закрываем сокет&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Обратите внимание на то, что методы accept(), send(), recv() являются по умолчанию блокирующими,&lt;br /&gt;
т.е. работа программы приостанавливается до тех пор, пока не будет выполнено необходимое действие. Это&lt;br /&gt;
означает, что клиент и сервер должны (по крайней мере, в нашей простейшей реализации) строго придерживаться определённой последовательности действий (протокола). В нашем случае после установки соединения&lt;br /&gt;
сервер посылает строку приветствия. Если клиент, вместо того чтобы принять эту информацию, сам начнёт&lt;br /&gt;
что-то передавать, то мы получим взаимную блокировку – клиент будет ждать, пока сервер примет его данные,&lt;br /&gt;
сервер же будет ждать аналогичных действий со стороны клиента.&lt;br /&gt;
&lt;br /&gt;
Для нормальной работы клиентская реализация должна выглядеть примерно так:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
import socket                     # импортируем модуль&lt;br /&gt;
c = socket.socket(socket.AF_UNIX) # создаём сокет такого-же домена&lt;br /&gt;
c.connect('/tmp/test.sock')       # подключаемся к сокету сервера&lt;br /&gt;
greeting = c.recv(1024)           # принимаем строку приветсвия&lt;br /&gt;
c.send('Hello, server!')          # отправляем свои данные&lt;br /&gt;
c.close()                         # закрываем соединение&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Понятно, что в данном примере мы получили «одноразовый» сервер, который, дождавшись соединения и&lt;br /&gt;
приняв данные, завершает свою работу. Для постоянной работы фрагмент, начиная с метода accept(), нужно&lt;br /&gt;
поместить в бесконечный цикл (см. пример ниже).&lt;br /&gt;
===Модуль select===&lt;br /&gt;
В Python доступен ещё один способ повысить эффективность работы за счёт параллельного выполнения некоторых операций – модуль select. Он использует системный вызов select для мультиплексирования соединений клиентов в одном цикле событий – метод select этого модуля позволяет отслеживать одновременно&lt;br /&gt;
несколько сокетов или других файловых объектов (только на Unix-подобных системах) в ожидании готовности&lt;br /&gt;
одного из них, после чего управление возвращается основной программе. Благодаря этому программа может&lt;br /&gt;
обрабатывать сразу несколько сокетов по мере их готовности к взаимодействию. Познакомимся с этим модулем поближе.&lt;br /&gt;
&lt;br /&gt;
Чтобы не замусоривать статью множеством фрагментов кода, приступим сразу к рассмотрению серьёзного примера, к которому будем обращаться по мере необходимости. Данный пример – простейший аналог&lt;br /&gt;
демона syslog, задача которого – получать через Unix-сокет информацию от клиентов и записывать её в файл&lt;br /&gt;
журнала. Если что-то не совсем понятно сразу, не обращайте на это внимание – всё прояснится к концу изложения. Код представлен на врезке logserver.py.&lt;br /&gt;
&lt;br /&gt;
Чтобы в дальнейшем было проще модифицировать код, реализуем его в виде класса. В его конструкторе (метод __init__()) решаются три задачи:&lt;br /&gt;
открытие файла (стр. 8-9), удаление файла-сокета,&lt;br /&gt;
который может остаться в случае аварийного завершения сценария (стр. 10-15) и собственно создание&lt;br /&gt;
сокета (стр. 16-18). На строки 19-21 пока не обращайте внимания.&lt;br /&gt;
&lt;br /&gt;
Методы openlog() и writelog() соответственно открывают лог-файл и записывают в него строку, предваряя текущей датой. Ну и метод start() –&lt;br /&gt;
основной, в котором и осуществляется обработка&lt;br /&gt;
входящих соединений.&lt;br /&gt;
&lt;br /&gt;
Для того, чтобы сервер постоянно обслуживал&lt;br /&gt;
вверенный ему сокет, создаётся бесконечный цикл&lt;br /&gt;
(строка 37). Однако здесь есть небольшая проблема.&lt;br /&gt;
Вы же ещё помните, что методы accept(), recv() и&lt;br /&gt;
send() являются по умолчанию блокирующими? То&lt;br /&gt;
есть при «последовательной» обработке первый клиент, достучавшийся до сервера, полностью завладеет вниманием последнего, пока не завершит работу&lt;br /&gt;
согласно заданному протоколу. Остальные же будут&lt;br /&gt;
либо поставлены в очередь, либо вообще отброшены,&lt;br /&gt;
если размер очереди превысит установленное значение (задаётся параметром метода listen()). Только&lt;br /&gt;
полностью обслужив первого клиента, сервер сможет&lt;br /&gt;
вернуться к строке 44 и снова вызвать accept().&lt;br /&gt;
&lt;br /&gt;
Если обмен с клиентом происходит быстро, то&lt;br /&gt;
такая схема работы вполне приемлема. Однако если&lt;br /&gt;
протокол требует ведения «диалога», в ходе которого возможны задержки, то это может стать серьёзной&lt;br /&gt;
проблемой. В рассматриваемом примере мы специально усложнили протокол, сделав его двухэтапным –&lt;br /&gt;
сначала клиент должен представиться, дождаться от&lt;br /&gt;
сервера подтверждения, затем отправить данные и&lt;br /&gt;
снова дождаться подтверждения.&lt;br /&gt;
&lt;br /&gt;
Если между первой и второй отправкой данных возникнет пауза (в коде клиента она искусственно реализована функцией time.sleep()), то&lt;br /&gt;
сервер будет понапрасну простаивать, хотя вполне мог бы заняться обслуживанием других клиентов. Собственно, для этого и используется метод&lt;br /&gt;
select() одноимённого модуля (строка 39).&lt;br /&gt;
&lt;br /&gt;
Принцип действия его следующий – он берёт на&lt;br /&gt;
себя ожидание данных в сокетах (массивы обслуживаемых сокетов передаются ему в виде параметров),&lt;br /&gt;
передавая управление основной программе, если&lt;br /&gt;
один из сокетов будет готов к обслуживанию.&lt;br /&gt;
&lt;br /&gt;
Чтобы было понятнее, рассмотрим, что происходит&lt;br /&gt;
в нашем примере. В строке 39 мы запускаем метод&lt;br /&gt;
select(). Как только один (или несколько) из обслуживаемых сокетов (первоначально такой сокет только&lt;br /&gt;
один, созданный при инициализации объекта в строке 16) будет готов к обслуживанию, select() передаёт&lt;br /&gt;
основной программе массив сокетов, готовых к работе,&lt;br /&gt;
который обрабатывается в цикле (строка 42). Так, если&lt;br /&gt;
к работе готов «родительский» сокет, для него вызывается метод accept(). Поскольку метод select()&lt;br /&gt;
гарантирует, что запрос на соединение уже есть, то&lt;br /&gt;
основной программе не придётся тратить время на&lt;br /&gt;
ожидание – accept() будет обработан сразу, вернув&lt;br /&gt;
объект – новый сокет, предназначенный для работы с&lt;br /&gt;
данным клиентом. Но мы не начинаем сразу же выполнять установленный протокол, а просто помещаем этот&lt;br /&gt;
новый сокет в список rsocks, обслуживаемый методом&lt;br /&gt;
select() (строка 45).&lt;br /&gt;
&lt;br /&gt;
Когда на этот сокет поступят данные от клиента, select() вновь сообщит о готовности. На этот&lt;br /&gt;
раз обработка пойдёт по ветке «else» (строка 46).&lt;br /&gt;
Поскольку наш протокол двухэтапный, то считывать&lt;br /&gt;
данные мы должны два раза, что и реализуется&lt;br /&gt;
дополнительной конструкцией «if – else» (строки 47-56): при первом «подходе» словарь senders не&lt;br /&gt;
будет содержать упоминания данного сокета (обратные кавычки позволяют работать не с самим сокетом,&lt;br /&gt;
а с его «строковым представлением»); при втором&lt;br /&gt;
же этот словарь уже будет содержать имя отправителя, ассоциированное с сокетом. На втором этапе&lt;br /&gt;
выполняется запись строки в лог-файл (строка 54),&lt;br /&gt;
удаление сокета из массива rsocks (строка 55), что-бы select() уже не занимался его обслуживанием, и&lt;br /&gt;
удаление записи из словаря (строка 56).&lt;br /&gt;
&lt;br /&gt;
Обратите внимание, что мы не можем просто&lt;br /&gt;
взять и последовательно вызвать два метода recv()&lt;br /&gt;
для получения всех данных, поскольку второй вызов&lt;br /&gt;
окажется уже блокирующим – ведь на первый recv()&lt;br /&gt;
мы попадаем, только когда select() обнаружит готовые для обработки данные; во втором же случае&lt;br /&gt;
готовности придётся ждать самостоятельно.&lt;br /&gt;
&lt;br /&gt;
По большому счёту, вызовы send() в нашем&lt;br /&gt;
примере получились блокирующие – если клиент&lt;br /&gt;
не сможет сразу принять переданное ему подтверждение, то сервер будет простаивать. Решается это&lt;br /&gt;
аналогичным путём, но уже с помощью массива&lt;br /&gt;
wsocks, однако из боязни сделать код чрезмерно&lt;br /&gt;
сложным и нечитаемым, в данном примере мы проигнорируем эту проблему, оставив её решение вам в&lt;br /&gt;
качестве упражнения.&lt;br /&gt;
===Сигналы===&lt;br /&gt;
Остались ещё две проблемы. Во-первых, хотелось&lt;br /&gt;
бы, чтобы сервер перед завершением своей работы (поскольку используется бесконечный цикл, то&lt;br /&gt;
это придётся делать «грубыми» методами вроде&lt;br /&gt;
команды kill или Ctrl+C) успевал выполнить некоторые полезные действия (например, закрыть файл&lt;br /&gt;
журнала, удалить файл сокета). Во-вторых, если в&lt;br /&gt;
процессе работы сервера удалить или переименовать лог-файл и создать новый с таким же именем&lt;br /&gt;
(например, это может происходить при ротации журнала утилитами типа logrotate), то дескриптор открытого файла (self.log в нашем примере) не изменится, продолжая указывать на прежнее расположение&lt;br /&gt;
файла в файловой системе. Так что запись будет&lt;br /&gt;
вестись по этому дескриптору, в уже переименованный или удалённый файл (поскольку на файл будет&lt;br /&gt;
оставаться ссылка, «привязанная» к дескриптору, то&lt;br /&gt;
при удалении из каталога он физически будет оставаться на месте, пока не будет удалён этот дескриптор) То есть нужно предусмотреть переинициализацию файла журнала.&lt;br /&gt;
&lt;br /&gt;
Как команда kill, так и комбинация [Ctrl+C] реализуют метод межпроцессорного взаимодействия,&lt;br /&gt;
именуемый сигналами. Например, kill 3942 отправит&lt;br /&gt;
процессу номер 3942 сигнал 15 (SIGTERM), дающий&lt;br /&gt;
указание завершить работу. Ctrl+C отправляет сигнал 2 (SIGINT). Большинство сигналов процесс может&lt;br /&gt;
перехватить и обработать по собственному желанию,&lt;br /&gt;
чем мы и воспользуемся.&lt;br /&gt;
&lt;br /&gt;
В Python для этого предназначен модуль signal.&lt;br /&gt;
Собственно, его мы и используем в строках 19–21,&lt;br /&gt;
назначая на некоторые сигналы в качестве обработчика метод stop(). Для сигнала 1 (SIGHUP) в качестве обработчика&lt;br /&gt;
назначается метод reinit(), который решает задачу переинициализации&lt;br /&gt;
открытого файла журнала.&lt;br /&gt;
===Тестирование===&lt;br /&gt;
Чтобы проверить работу нашего сервера, нам нужен клиент. Его код&lt;br /&gt;
представлен во врезке logclient.py. Никаких сложностей здесь нет.&lt;br /&gt;
Поясню лишь, что конструкции time.sleep(5) (стр. 15 и 21) искусственно создают задержку между первым и вторым этапами диалога.&lt;br /&gt;
&lt;br /&gt;
Чтобы убедиться в том, что все клиенты обслуживаются сервером&lt;br /&gt;
параллельно, нужно запустить их в нескольких экземплярах (например, с разных консолей). В результате в файле журнала появятся такие&lt;br /&gt;
записи:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Sun Jun 4 15:30:17 2006: ===&amp;gt; LogServer started&lt;br /&gt;
Sun Jun 4 15:30:55 2006: [test2] Test message&lt;br /&gt;
Sun Jun 4 15:30:56 2006: [test3] Test message&lt;br /&gt;
Sun Jun 4 15:31:00 2006: ===&amp;gt; LogServer stopped [signal 2]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Как видите, между записью сообщений от test2 и test3 прошла&lt;br /&gt;
одна секунда, хотя каждый клиент требует для своей обработки как&lt;br /&gt;
минимум 10. Значит, ожидание ответа от обоих клиентов выполняется&lt;br /&gt;
одновременно, чего мы и добивались.&lt;br /&gt;
===Ветвления===&lt;br /&gt;
Впрочем, select – это не единственный способ организовать параллельную работу в Python. Модуль os предоставляет функцию fork(),&lt;br /&gt;
которая использует одноимённый системный вызов, порождающий&lt;br /&gt;
копию текущего процесса. Чтобы посмотреть, как это работает на практике, напишем небольшой сценарий, который будет автоматически&lt;br /&gt;
запускать скрипты-клиенты для тестирования нашего сервера (действительно, негоже делать вручную то, что можно поручить программе). Код&lt;br /&gt;
представлен на врезке logclient2.py.&lt;br /&gt;
&lt;br /&gt;
Здесь всё до безобразия просто – функция fork() (строка 6) порождает копию текущего процесса. В каждой копии выполнение кода будет&lt;br /&gt;
продолжено как ни в чём не бывало со следующей команды. Чтобы код&lt;br /&gt;
мог понять, где он выполняется – в родительском процессе или в дочернем, используется значение, возвращаемое функцией fork(). Дочерний&lt;br /&gt;
процесс получает значение 0, родительский – идентификатор порождённого дочернего процесса (PID).&lt;br /&gt;
&lt;br /&gt;
Кстати, функция os._exit(0) в строке 10 позволяет завершить&lt;br /&gt;
дочерний процесс. Если этого не сделать, то он пойдёт на выполнение&lt;br /&gt;
цикла for (строка 5), уже сам выступая в качестве родительского и&lt;br /&gt;
порождая, таким образом, настоящую лавину новых процессов.&lt;br /&gt;
&lt;br /&gt;
Естественно, таким образом можно было бы реализовать и наш&lt;br /&gt;
сервер – после метода accept() ответвлять дочерний процесс, который&lt;br /&gt;
занимался бы обслуживанием конкретного клиента, в то время как родительский продолжал бы «висеть» на методе accept(), ожидая входящие&lt;br /&gt;
соединения. Именно так и работают многие серверы, например, Apache&lt;br /&gt;
(версия 1.х – только так и никак иначе, а в 2.х появились потоки).&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== БЛОКИРОВАТЬ НЕОБЯЗАТЕЛЬНО ===&lt;br /&gt;
Модуль '''socket''' также предоставляет возможность работы с неблокирующими вызовами ''accept()'', ''send()'' и ''recv()''. Для&lt;br /&gt;
этого следует предварительно установить значение соответствующего атрибута объекта-сокета с помощью следующего&lt;br /&gt;
метода:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
socket.setblocking(0)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Значение '''0''' переключает сокет в неблокирующий режим работы (по умолчанию используется блокирующий –&lt;br /&gt;
значение '''1'''). При этом методы ''accept()'', ''send()'' и ''recv()'' при отсутствии данных для обработки не останавливают&lt;br /&gt;
выполнение программы до их появления, а генерируют исключение ''socket.error''. Что с ним делать дальше – решать&lt;br /&gt;
вам. Например, можно просто игнорировать:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
while(1):&lt;br /&gt;
    try:&lt;br /&gt;
        data = sock.recv()&lt;br /&gt;
    except socket.error, errcode:&lt;br /&gt;
        if errcode[0] == 35:&lt;br /&gt;
            pass&lt;br /&gt;
        else:&lt;br /&gt;
            raise(socket.error)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Второй параметр оператора ''except'' – переменная, в которую будет занесён код ошибки. Этот код представляет собой&lt;br /&gt;
кортеж вида ('''35''', ‘Resource temporarily unavailable’), где первый элемент – числовой код ошибки, а второй –&lt;br /&gt;
текстовая строка-пояснение. При отсутствии данных генерируется ошибка '''35''', которую мы и игнорируем (''pass''). Здесь&lt;br /&gt;
мы получаем то же ожидание данных, но уже реализованное самим кодом [[Python]]. Но преимущество здесь в том, что&lt;br /&gt;
вместо оператора ''pass'' можно реализовать любую обработку. Например, переходить к опросу другого сокета.&lt;br /&gt;
&lt;br /&gt;
===logserver.py===&lt;br /&gt;
&amp;lt;source lang=&amp;quot;python&amp;quot; line=&amp;quot;GESHI_NORMAL_LINE_NUMBERS&amp;quot;&amp;gt;&lt;br /&gt;
#!/usr/bin/python&lt;br /&gt;
# -*- coding: utf-8 -*-&lt;br /&gt;
&lt;br /&gt;
import os, socket, time, signal, select&lt;br /&gt;
&lt;br /&gt;
class LogServer:&lt;br /&gt;
    def __init__(self, sockfile='./lserv.sock',&lt;br /&gt;
                 logfile='./lserv.log',&lt;br /&gt;
                 maxqueue=5):&lt;br /&gt;
        self.logfilename = logfile&lt;br /&gt;
        self.openlog()&lt;br /&gt;
        self.sockfilename = sockfile&lt;br /&gt;
        try:&lt;br /&gt;
            if os.path.exists(sockfile):&lt;br /&gt;
                os.unlink(sockfile)&lt;br /&gt;
        except:&lt;br /&gt;
            raise 'error'&lt;br /&gt;
&lt;br /&gt;
        self.socket = socket.socket(socket.AF_UNIX)&lt;br /&gt;
        self.socket.bind(sockfile)&lt;br /&gt;
        self.socket.listen(maxqueue)&lt;br /&gt;
&lt;br /&gt;
        signal.signal(signal.SIGHUP, self.reinit)&lt;br /&gt;
        signal.signal(signal.SIGINT, self.stop)&lt;br /&gt;
        signal.signal(signal.SIGTERM, self.stop)&lt;br /&gt;
&lt;br /&gt;
        self.writelog('===&amp;gt; LogServer started')&lt;br /&gt;
&lt;br /&gt;
    def openlog(self):&lt;br /&gt;
        self.log = open(self.logfilename, 'a+')&lt;br /&gt;
&lt;br /&gt;
    def writelog(self, message):&lt;br /&gt;
        self.log.write('%s: %s\n' % (time.asctime(), message))&lt;br /&gt;
&lt;br /&gt;
    def reinit(self, signum, frame):&lt;br /&gt;
        self.log.close()&lt;br /&gt;
        self.openlog()&lt;br /&gt;
        self.start()&lt;br /&gt;
&lt;br /&gt;
    def start(self):&lt;br /&gt;
        rsocks = []&lt;br /&gt;
        wsocks = []&lt;br /&gt;
        rsocks.append(self.socket)&lt;br /&gt;
        senders = {}&lt;br /&gt;
        while 1:&lt;br /&gt;
            try:&lt;br /&gt;
                reads, writes, errs = select.select(rsocks, wsocks, [])&lt;br /&gt;
            except:&lt;br /&gt;
                return&lt;br /&gt;
&lt;br /&gt;
            for sock in reads:&lt;br /&gt;
                if sock == self.socket:&lt;br /&gt;
                    client, name = sock.accept()&lt;br /&gt;
                    rsocks.append(client)&lt;br /&gt;
                elif not `sock` in senders.keys():&lt;br /&gt;
                    sender = sock.recv(1024)&lt;br /&gt;
                    sock.send('Sender OK')&lt;br /&gt;
                    senders['sock'] = sender&lt;br /&gt;
                else:&lt;br /&gt;
                    message = sock.recv(1024)&lt;br /&gt;
                    sock.send('Message OK')&lt;br /&gt;
                    self.writelog('[%s] %s' % (senders['sock'], message))&lt;br /&gt;
                    rsocks.remove(sock)&lt;br /&gt;
                    del senders['sock']&lt;br /&gt;
&lt;br /&gt;
    def stop(self, signum, frame):&lt;br /&gt;
        self.writelog('===&amp;gt; LogServer stopped [signal %s]' % (signum))&lt;br /&gt;
        self.log.close()&lt;br /&gt;
        os.unlink(self.sockfilename)&lt;br /&gt;
&lt;br /&gt;
if __name__ == '__main__':&lt;br /&gt;
    serv = LogServer(maxqueue=3)&lt;br /&gt;
    serv.start()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===logclient.py===&lt;br /&gt;
&amp;lt;source lang=&amp;quot;python&amp;quot; line=&amp;quot;GESHI_NORMAL_LINE_NUMBERS&amp;quot;&amp;gt;&lt;br /&gt;
#!/usr/bin/python&lt;br /&gt;
# -*- coding: utf-8 -*-&lt;br /&gt;
import sys, socket, time&lt;br /&gt;
class LogClient:&lt;br /&gt;
def __init__(self, sender='generic client',&lt;br /&gt;
sockfile='./lserv.sock',&lt;br /&gt;
buffersize=1024,&lt;br /&gt;
testmode=0):&lt;br /&gt;
self.sender = sender&lt;br /&gt;
self.sockfile = sockfile&lt;br /&gt;
self.buffersize = buffersize&lt;br /&gt;
self.testmode = testmode&lt;br /&gt;
def writelog(self, message):&lt;br /&gt;
if self.testmode:&lt;br /&gt;
time.sleep(5)&lt;br /&gt;
self.socket = socket.socket(socket.AF_UNIX)&lt;br /&gt;
self.socket.connect(self.sockfile)&lt;br /&gt;
self.socket.send(self.sender)&lt;br /&gt;
if self.socket.recv(self.buffersize) == 'Sender OK':&lt;br /&gt;
if self.testmode:&lt;br /&gt;
time.sleep(5)&lt;br /&gt;
self.socket.send(message)&lt;br /&gt;
if not self.socket.recv(self.buffersize) == 'Message OK':&lt;br /&gt;
print 'Ошибка: нет подтверждения Message'&lt;br /&gt;
else:&lt;br /&gt;
print 'Ошибка: нет подтверждения Sender'&lt;br /&gt;
self.socket.close()&lt;br /&gt;
if __name__ == '__main__':&lt;br /&gt;
sendername = sys.argv[1]&lt;br /&gt;
client = LogClient(sender=sendername, testmode=1)&lt;br /&gt;
client.writelog('Test message')&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===logclient2.py===&lt;br /&gt;
&amp;lt;source lang=&amp;quot;python&amp;quot; line=&amp;quot;GESHI_NORMAL_LINE_NUMBERS&amp;quot;&amp;gt;&lt;br /&gt;
#!/usr/bin/python&lt;br /&gt;
# -*- coding: utf-8 -*-&lt;br /&gt;
import os&lt;br /&gt;
from logclient import LogClient&lt;br /&gt;
for i in xrange(25):&lt;br /&gt;
pid = os.fork()&lt;br /&gt;
if pid == 0:&lt;br /&gt;
client = LogClient(sender='client%d' % i, testmode=1)&lt;br /&gt;
client.writelog('Test from client%d' % i)&lt;br /&gt;
os._exit(0)&lt;br /&gt;
else:&lt;br /&gt;
print 'Start child[%d]' % pid&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mim</name></author>	</entry>

	<entry>
		<id>http://wiki.linuxformat.ru/wiki/LXF81:Python</id>
		<title>LXF81:Python</title>
		<link rel="alternate" type="text/html" href="http://wiki.linuxformat.ru/wiki/LXF81:Python"/>
				<updated>2008-11-19T23:52:02Z</updated>
		
		<summary type="html">&lt;p&gt;Mim: добавил отступы, заменил &amp;quot;else: if&amp;quot; на &amp;quot;elif&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Цикл/Python}}&lt;br /&gt;
&lt;br /&gt;
==Задачи многозадачности==&lt;br /&gt;
''часть 1 '''Сергей Супрунов''' открывает новый цикл статей, в котором будут более полно освещены некоторые практические моменты разработки приложений на языке Python. Начнём, пожалуй, с вопросов параллельных вычислений...''&lt;br /&gt;
&lt;br /&gt;
Практически любая программа, особенно если она в процессе своей работы осуществляет взаимодействие с пользователем или удалённым клиентом, довольно много времени тратит впустую, ожидая ответного хода своего «партнёра».&lt;br /&gt;
Неэффективность проявляется и в других вопросах: процессор простаивает, пока программа работает с жёстким диском; жёсткий диск,&lt;br /&gt;
напротив, бездействует, пока программа занята вычислительными&lt;br /&gt;
задачами, например, обработкой только что считанных с диска данных.&lt;br /&gt;
Поэтому рано или поздно разработчики операционных систем должны&lt;br /&gt;
были прийти к идее распараллеливания работы.&lt;br /&gt;
&lt;br /&gt;
В большинстве современных ОС эта идея имеет две реализации: процессы и потоки (причем в Linux одно практически неотличимо от другого).&lt;br /&gt;
Процесс, если говорить упрощённо, представляет собой некоторый набор&lt;br /&gt;
ресурсов (область памяти, значения процессорных регистров, открытые&lt;br /&gt;
дескрипторы файлов и т.д.), принадлежащих какой-то задаче. На однопроцессорных машинах одновременно может обрабатываться только&lt;br /&gt;
один процесс, остальные в это время находятся в очереди. Ядро системы,&lt;br /&gt;
точнее, его планировщик, в соответствии с заданным алгоритмом предоставляет доступ к процессору ожидающим процессам в соответствии с&lt;br /&gt;
их приоритетом. Если текущий процесс переходит в состояние ожидания&lt;br /&gt;
ввода-вывода, то доступ к процессору передаётся следующему процессу&lt;br /&gt;
в очереди. Благодаря этому, во-первых, реализуется более эффективное&lt;br /&gt;
использование ресурсов системы, а во-вторых, несколько задач могут&lt;br /&gt;
выполняться в одно и то же (с точки зрения пользователя) время.&lt;br /&gt;
&lt;br /&gt;
Потоки (threads, их также называют нитями или облегчёнными процессами) решают аналогичную задачу, но в рамках одного процесса.&lt;br /&gt;
При управлении как потоками, так и процессами операционная система&lt;br /&gt;
вынуждена «непроизводительно» расходовать некоторые ресурсы на&lt;br /&gt;
так называемое переключение контекста (т.е. на выполнение «подготовительных» мероприятий, таких как восстановление значения регистров и адресного пространства). Благодаря тому, что потоки разделяют&lt;br /&gt;
некоторые ресурсы (например, память процесса, в рамках которого они&lt;br /&gt;
исполняются), переключение их контекста происходит заметно быстрее,&lt;br /&gt;
чем контекста процесса. Благодаря этому можно распараллеливать&lt;br /&gt;
задачи с заметно меньшими затратами. Хорошим примером может служить производительность Apache 2.x (см. обзор в [[LXF77:Apache 2.2|LXF77]]).&lt;br /&gt;
&lt;br /&gt;
Хватит, пожалуй, теории. Посмотрим, какие средства предоставляет&lt;br /&gt;
язык Python программисту, желающему воспользоваться многозадачностью операционной системы, а заодно рассмотрим такие вещи как&lt;br /&gt;
сокеты и межпроцессорное взаимодействие.&lt;br /&gt;
===Сокеты===&lt;br /&gt;
Сокет (конечная точка сетевых коммуникаций) – это основа клиент-серверных приложений. Фактически, это интерфейс, с помощью которого процессы могут осуществлять обмен информацией между собой.&lt;br /&gt;
Конкретная реализация определяется так называемым коммуникационным доменом, наиболее распространённые из них – Internet-домен и&lt;br /&gt;
Unix-домен. Internet-сокеты позволяют реализовать взаимодействие на&lt;br /&gt;
базе протоколов сети Интернет, таких как TCP или UDP. О них мы подробнее поговорим в одной из следующих статей цикла.&lt;br /&gt;
&lt;br /&gt;
Unix-сокеты представляют собой файловые объекты, куда процессы&lt;br /&gt;
могут записывать поток данных и считывать его. Процесс, прослушивающий сокет в ожидании входящих сообщений, по традиции именуется сервером, а подключающийся к сокету для обмена данными – клиентом.&lt;br /&gt;
&lt;br /&gt;
В языке Python работа с сокетами реализована в модуле socket. Со стороны сервера создание сокета&lt;br /&gt;
(рассмотрим пример для домена Unix) выглядит следующим образом:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
import socket                     # импортируем модуль&lt;br /&gt;
s = socket.socket(socket.AF_UNIX) # создаём сокет домена AF_UNIX&lt;br /&gt;
s.bind('/tmp/test.sock')          # привязываем его к файлу&lt;br /&gt;
s.listen(1)                       # начинаем прослушивать&lt;br /&gt;
conn = s.accept()                 # ждём подключения&lt;br /&gt;
conn.send('HELO')                 # дождавшись, отправляем клиенту строку&lt;br /&gt;
data = conn.recv(1024)            # получаем от клиента данные&lt;br /&gt;
s.close()                         # закрываем сокет&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Обратите внимание на то, что методы accept(), send(), recv() являются по умолчанию блокирующими,&lt;br /&gt;
т.е. работа программы приостанавливается до тех пор, пока не будет выполнено необходимое действие. Это&lt;br /&gt;
означает, что клиент и сервер должны (по крайней мере, в нашей простейшей реализации) строго придерживаться определённой последовательности действий (протокола). В нашем случае после установки соединения&lt;br /&gt;
сервер посылает строку приветствия. Если клиент, вместо того чтобы принять эту информацию, сам начнёт&lt;br /&gt;
что-то передавать, то мы получим взаимную блокировку – клиент будет ждать, пока сервер примет его данные,&lt;br /&gt;
сервер же будет ждать аналогичных действий со стороны клиента.&lt;br /&gt;
&lt;br /&gt;
Для нормальной работы клиентская реализация должна выглядеть примерно так:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
import socket                     # импортируем модуль&lt;br /&gt;
c = socket.socket(socket.AF_UNIX) # создаём сокет такого-же домена&lt;br /&gt;
c.connect('/tmp/test.sock')       # подключаемся к сокету сервера&lt;br /&gt;
greeting = c.recv(1024)           # принимаем строку приветсвия&lt;br /&gt;
c.send('Hello, server!')          # отправляем свои данные&lt;br /&gt;
c.close()                         # закрываем соединение&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Понятно, что в данном примере мы получили «одноразовый» сервер, который, дождавшись соединения и&lt;br /&gt;
приняв данные, завершает свою работу. Для постоянной работы фрагмент, начиная с метода accept(), нужно&lt;br /&gt;
поместить в бесконечный цикл (см. пример ниже).&lt;br /&gt;
===Модуль select===&lt;br /&gt;
В Python доступен ещё один способ повысить эффективность работы за счёт параллельного выполнения некоторых операций – модуль select. Он использует системный вызов select для мультиплексирования соединений клиентов в одном цикле событий – метод select этого модуля позволяет отслеживать одновременно&lt;br /&gt;
несколько сокетов или других файловых объектов (только на Unix-подобных системах) в ожидании готовности&lt;br /&gt;
одного из них, после чего управление возвращается основной программе. Благодаря этому программа может&lt;br /&gt;
обрабатывать сразу несколько сокетов по мере их готовности к взаимодействию. Познакомимся с этим модулем поближе.&lt;br /&gt;
&lt;br /&gt;
Чтобы не замусоривать статью множеством фрагментов кода, приступим сразу к рассмотрению серьёзного примера, к которому будем обращаться по мере необходимости. Данный пример – простейший аналог&lt;br /&gt;
демона syslog, задача которого – получать через Unix-сокет информацию от клиентов и записывать её в файл&lt;br /&gt;
журнала. Если что-то не совсем понятно сразу, не обращайте на это внимание – всё прояснится к концу изложения. Код представлен на врезке logserver.py.&lt;br /&gt;
&lt;br /&gt;
Чтобы в дальнейшем было проще модифицировать код, реализуем его в виде класса. В его конструкторе (метод __init__()) решаются три задачи:&lt;br /&gt;
открытие файла (стр. 8-9), удаление файла-сокета,&lt;br /&gt;
который может остаться в случае аварийного завершения сценария (стр. 10-15) и собственно создание&lt;br /&gt;
сокета (стр. 16-18). На строки 19-21 пока не обращайте внимания.&lt;br /&gt;
&lt;br /&gt;
Методы openlog() и writelog() соответственно открывают лог-файл и записывают в него строку, предваряя текущей датой. Ну и метод start() –&lt;br /&gt;
основной, в котором и осуществляется обработка&lt;br /&gt;
входящих соединений.&lt;br /&gt;
&lt;br /&gt;
Для того, чтобы сервер постоянно обслуживал&lt;br /&gt;
вверенный ему сокет, создаётся бесконечный цикл&lt;br /&gt;
(строка 37). Однако здесь есть небольшая проблема.&lt;br /&gt;
Вы же ещё помните, что методы accept(), recv() и&lt;br /&gt;
send() являются по умолчанию блокирующими? То&lt;br /&gt;
есть при «последовательной» обработке первый клиент, достучавшийся до сервера, полностью завладеет вниманием последнего, пока не завершит работу&lt;br /&gt;
согласно заданному протоколу. Остальные же будут&lt;br /&gt;
либо поставлены в очередь, либо вообще отброшены,&lt;br /&gt;
если размер очереди превысит установленное значение (задаётся параметром метода listen()). Только&lt;br /&gt;
полностью обслужив первого клиента, сервер сможет&lt;br /&gt;
вернуться к строке 44 и снова вызвать accept().&lt;br /&gt;
&lt;br /&gt;
Если обмен с клиентом происходит быстро, то&lt;br /&gt;
такая схема работы вполне приемлема. Однако если&lt;br /&gt;
протокол требует ведения «диалога», в ходе которого возможны задержки, то это может стать серьёзной&lt;br /&gt;
проблемой. В рассматриваемом примере мы специально усложнили протокол, сделав его двухэтапным –&lt;br /&gt;
сначала клиент должен представиться, дождаться от&lt;br /&gt;
сервера подтверждения, затем отправить данные и&lt;br /&gt;
снова дождаться подтверждения.&lt;br /&gt;
&lt;br /&gt;
Если между первой и второй отправкой данных возникнет пауза (в коде клиента она искусственно реализована функцией time.sleep()), то&lt;br /&gt;
сервер будет понапрасну простаивать, хотя вполне мог бы заняться обслуживанием других клиентов. Собственно, для этого и используется метод&lt;br /&gt;
select() одноимённого модуля (строка 39).&lt;br /&gt;
&lt;br /&gt;
Принцип действия его следующий – он берёт на&lt;br /&gt;
себя ожидание данных в сокетах (массивы обслуживаемых сокетов передаются ему в виде параметров),&lt;br /&gt;
передавая управление основной программе, если&lt;br /&gt;
один из сокетов будет готов к обслуживанию.&lt;br /&gt;
&lt;br /&gt;
Чтобы было понятнее, рассмотрим, что происходит&lt;br /&gt;
в нашем примере. В строке 39 мы запускаем метод&lt;br /&gt;
select(). Как только один (или несколько) из обслуживаемых сокетов (первоначально такой сокет только&lt;br /&gt;
один, созданный при инициализации объекта в строке 16) будет готов к обслуживанию, select() передаёт&lt;br /&gt;
основной программе массив сокетов, готовых к работе,&lt;br /&gt;
который обрабатывается в цикле (строка 42). Так, если&lt;br /&gt;
к работе готов «родительский» сокет, для него вызывается метод accept(). Поскольку метод select()&lt;br /&gt;
гарантирует, что запрос на соединение уже есть, то&lt;br /&gt;
основной программе не придётся тратить время на&lt;br /&gt;
ожидание – accept() будет обработан сразу, вернув&lt;br /&gt;
объект – новый сокет, предназначенный для работы с&lt;br /&gt;
данным клиентом. Но мы не начинаем сразу же выполнять установленный протокол, а просто помещаем этот&lt;br /&gt;
новый сокет в список rsocks, обслуживаемый методом&lt;br /&gt;
select() (строка 45).&lt;br /&gt;
&lt;br /&gt;
Когда на этот сокет поступят данные от клиента, select() вновь сообщит о готовности. На этот&lt;br /&gt;
раз обработка пойдёт по ветке «else» (строка 46).&lt;br /&gt;
Поскольку наш протокол двухэтапный, то считывать&lt;br /&gt;
данные мы должны два раза, что и реализуется&lt;br /&gt;
дополнительной конструкцией «if – else» (строки 47-56): при первом «подходе» словарь senders не&lt;br /&gt;
будет содержать упоминания данного сокета (обратные кавычки позволяют работать не с самим сокетом,&lt;br /&gt;
а с его «строковым представлением»); при втором&lt;br /&gt;
же этот словарь уже будет содержать имя отправителя, ассоциированное с сокетом. На втором этапе&lt;br /&gt;
выполняется запись строки в лог-файл (строка 54),&lt;br /&gt;
удаление сокета из массива rsocks (строка 55), что-бы select() уже не занимался его обслуживанием, и&lt;br /&gt;
удаление записи из словаря (строка 56).&lt;br /&gt;
&lt;br /&gt;
Обратите внимание, что мы не можем просто&lt;br /&gt;
взять и последовательно вызвать два метода recv()&lt;br /&gt;
для получения всех данных, поскольку второй вызов&lt;br /&gt;
окажется уже блокирующим – ведь на первый recv()&lt;br /&gt;
мы попадаем, только когда select() обнаружит готовые для обработки данные; во втором же случае&lt;br /&gt;
готовности придётся ждать самостоятельно.&lt;br /&gt;
&lt;br /&gt;
По большому счёту, вызовы send() в нашем&lt;br /&gt;
примере получились блокирующие – если клиент&lt;br /&gt;
не сможет сразу принять переданное ему подтверждение, то сервер будет простаивать. Решается это&lt;br /&gt;
аналогичным путём, но уже с помощью массива&lt;br /&gt;
wsocks, однако из боязни сделать код чрезмерно&lt;br /&gt;
сложным и нечитаемым, в данном примере мы проигнорируем эту проблему, оставив её решение вам в&lt;br /&gt;
качестве упражнения.&lt;br /&gt;
===Сигналы===&lt;br /&gt;
Остались ещё две проблемы. Во-первых, хотелось&lt;br /&gt;
бы, чтобы сервер перед завершением своей работы (поскольку используется бесконечный цикл, то&lt;br /&gt;
это придётся делать «грубыми» методами вроде&lt;br /&gt;
команды kill или Ctrl+C) успевал выполнить некоторые полезные действия (например, закрыть файл&lt;br /&gt;
журнала, удалить файл сокета). Во-вторых, если в&lt;br /&gt;
процессе работы сервера удалить или переименовать лог-файл и создать новый с таким же именем&lt;br /&gt;
(например, это может происходить при ротации журнала утилитами типа logrotate), то дескриптор открытого файла (self.log в нашем примере) не изменится, продолжая указывать на прежнее расположение&lt;br /&gt;
файла в файловой системе. Так что запись будет&lt;br /&gt;
вестись по этому дескриптору, в уже переименованный или удалённый файл (поскольку на файл будет&lt;br /&gt;
оставаться ссылка, «привязанная» к дескриптору, то&lt;br /&gt;
при удалении из каталога он физически будет оставаться на месте, пока не будет удалён этот дескриптор) То есть нужно предусмотреть переинициализацию файла журнала.&lt;br /&gt;
&lt;br /&gt;
Как команда kill, так и комбинация [Ctrl+C] реализуют метод межпроцессорного взаимодействия,&lt;br /&gt;
именуемый сигналами. Например, kill 3942 отправит&lt;br /&gt;
процессу номер 3942 сигнал 15 (SIGTERM), дающий&lt;br /&gt;
указание завершить работу. Ctrl+C отправляет сигнал 2 (SIGINT). Большинство сигналов процесс может&lt;br /&gt;
перехватить и обработать по собственному желанию,&lt;br /&gt;
чем мы и воспользуемся.&lt;br /&gt;
&lt;br /&gt;
В Python для этого предназначен модуль signal.&lt;br /&gt;
Собственно, его мы и используем в строках 19–21,&lt;br /&gt;
назначая на некоторые сигналы в качестве обработчика метод stop(). Для сигнала 1 (SIGHUP) в качестве обработчика&lt;br /&gt;
назначается метод reinit(), который решает задачу переинициализации&lt;br /&gt;
открытого файла журнала.&lt;br /&gt;
===Тестирование===&lt;br /&gt;
Чтобы проверить работу нашего сервера, нам нужен клиент. Его код&lt;br /&gt;
представлен во врезке logclient.py. Никаких сложностей здесь нет.&lt;br /&gt;
Поясню лишь, что конструкции time.sleep(5) (стр. 15 и 21) искусственно создают задержку между первым и вторым этапами диалога.&lt;br /&gt;
&lt;br /&gt;
Чтобы убедиться в том, что все клиенты обслуживаются сервером&lt;br /&gt;
параллельно, нужно запустить их в нескольких экземплярах (например, с разных консолей). В результате в файле журнала появятся такие&lt;br /&gt;
записи:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Sun Jun 4 15:30:17 2006: ===&amp;gt; LogServer started&lt;br /&gt;
Sun Jun 4 15:30:55 2006: [test2] Test message&lt;br /&gt;
Sun Jun 4 15:30:56 2006: [test3] Test message&lt;br /&gt;
Sun Jun 4 15:31:00 2006: ===&amp;gt; LogServer stopped [signal 2]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Как видите, между записью сообщений от test2 и test3 прошла&lt;br /&gt;
одна секунда, хотя каждый клиент требует для своей обработки как&lt;br /&gt;
минимум 10. Значит, ожидание ответа от обоих клиентов выполняется&lt;br /&gt;
одновременно, чего мы и добивались.&lt;br /&gt;
===Ветвления===&lt;br /&gt;
Впрочем, select – это не единственный способ организовать параллельную работу в Python. Модуль os предоставляет функцию fork(),&lt;br /&gt;
которая использует одноимённый системный вызов, порождающий&lt;br /&gt;
копию текущего процесса. Чтобы посмотреть, как это работает на практике, напишем небольшой сценарий, который будет автоматически&lt;br /&gt;
запускать скрипты-клиенты для тестирования нашего сервера (действительно, негоже делать вручную то, что можно поручить программе). Код&lt;br /&gt;
представлен на врезке logclient2.py.&lt;br /&gt;
&lt;br /&gt;
Здесь всё до безобразия просто – функция fork() (строка 6) порождает копию текущего процесса. В каждой копии выполнение кода будет&lt;br /&gt;
продолжено как ни в чём не бывало со следующей команды. Чтобы код&lt;br /&gt;
мог понять, где он выполняется – в родительском процессе или в дочернем, используется значение, возвращаемое функцией fork(). Дочерний&lt;br /&gt;
процесс получает значение 0, родительский – идентификатор порождённого дочернего процесса (PID).&lt;br /&gt;
&lt;br /&gt;
Кстати, функция os._exit(0) в строке 10 позволяет завершить&lt;br /&gt;
дочерний процесс. Если этого не сделать, то он пойдёт на выполнение&lt;br /&gt;
цикла for (строка 5), уже сам выступая в качестве родительского и&lt;br /&gt;
порождая, таким образом, настоящую лавину новых процессов.&lt;br /&gt;
&lt;br /&gt;
Естественно, таким образом можно было бы реализовать и наш&lt;br /&gt;
сервер – после метода accept() ответвлять дочерний процесс, который&lt;br /&gt;
занимался бы обслуживанием конкретного клиента, в то время как родительский продолжал бы «висеть» на методе accept(), ожидая входящие&lt;br /&gt;
соединения. Именно так и работают многие серверы, например, Apache&lt;br /&gt;
(версия 1.х – только так и никак иначе, а в 2.х появились потоки).&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
=== БЛОКИРОВАТЬ НЕОБЯЗАТЕЛЬНО ===&lt;br /&gt;
Модуль socket также предоставляет возможность работы с неблокирующими вызовами accept(), send() и recv(). Для&lt;br /&gt;
этого следует предварительно установить значение соответствующего атрибута объекта-сокета с помощью следующего&lt;br /&gt;
метода:&lt;br /&gt;
 socket.setblocking(0)&lt;br /&gt;
&lt;br /&gt;
Значение 0 переключает сокет в неблокирующий режим работы (по умолчанию используется блокирующий –&lt;br /&gt;
значение 1). При этом методы accept(), send() и recv() при отсутствии данных для обработки не останавливают&lt;br /&gt;
выполнение программы до их появления, а генерируют исключение socket.error. Что с ним делать дальше – решать&lt;br /&gt;
вам. Например, можно просто игнорировать:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
while(1):&lt;br /&gt;
try:&lt;br /&gt;
 data = sock.recv()&lt;br /&gt;
 except socket.error, errcode:&lt;br /&gt;
if errcode[0] == 35:&lt;br /&gt;
 pass&lt;br /&gt;
else:&lt;br /&gt;
 raise(socket.error)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Второй параметр оператора except – переменная, в которую будет занесён код ошибки. Этот код представляет собой&lt;br /&gt;
кортеж вида (35, ‘Resource temporarily unavailable’), где первый элемент – числовой код ошибки, а второй –&lt;br /&gt;
текстовая строка-пояснение. При отсутствии данных генерируется ошибка 35, которую мы и игнорируем (pass). Здесь&lt;br /&gt;
мы получаем то же ожидание данных, но уже реализованное самим кодом Python. Но преимущество здесь в том, что&lt;br /&gt;
вместо оператора pass можно реализовать любую обработку. Например, переходить к опросу другого сокета.&lt;br /&gt;
&lt;br /&gt;
===logserver.py===&lt;br /&gt;
&amp;lt;source lang=&amp;quot;python&amp;quot; line=&amp;quot;GESHI_NORMAL_LINE_NUMBERS&amp;quot;&amp;gt;&lt;br /&gt;
#!/usr/bin/python&lt;br /&gt;
# -*- coding: utf-8 -*-&lt;br /&gt;
&lt;br /&gt;
import os, socket, time, signal, select&lt;br /&gt;
&lt;br /&gt;
class LogServer:&lt;br /&gt;
    def __init__(self, sockfile='./lserv.sock',&lt;br /&gt;
                 logfile='./lserv.log',&lt;br /&gt;
                 maxqueue=5):&lt;br /&gt;
        self.logfilename = logfile&lt;br /&gt;
        self.openlog()&lt;br /&gt;
        self.sockfilename = sockfile&lt;br /&gt;
        try:&lt;br /&gt;
            if os.path.exists(sockfile):&lt;br /&gt;
                os.unlink(sockfile)&lt;br /&gt;
        except:&lt;br /&gt;
            raise 'error'&lt;br /&gt;
&lt;br /&gt;
        self.socket = socket.socket(socket.AF_UNIX)&lt;br /&gt;
        self.socket.bind(sockfile)&lt;br /&gt;
        self.socket.listen(maxqueue)&lt;br /&gt;
&lt;br /&gt;
        signal.signal(signal.SIGHUP, self.reinit)&lt;br /&gt;
        signal.signal(signal.SIGINT, self.stop)&lt;br /&gt;
        signal.signal(signal.SIGTERM, self.stop)&lt;br /&gt;
&lt;br /&gt;
        self.writelog('===&amp;gt; LogServer started')&lt;br /&gt;
&lt;br /&gt;
    def openlog(self):&lt;br /&gt;
        self.log = open(self.logfilename, 'a+')&lt;br /&gt;
&lt;br /&gt;
    def writelog(self, message):&lt;br /&gt;
        self.log.write('%s: %s\n' % (time.asctime(), message))&lt;br /&gt;
&lt;br /&gt;
    def reinit(self, signum, frame):&lt;br /&gt;
        self.log.close()&lt;br /&gt;
        self.openlog()&lt;br /&gt;
        self.start()&lt;br /&gt;
&lt;br /&gt;
    def start(self):&lt;br /&gt;
        rsocks = []&lt;br /&gt;
        wsocks = []&lt;br /&gt;
        rsocks.append(self.socket)&lt;br /&gt;
        senders = {}&lt;br /&gt;
        while 1:&lt;br /&gt;
            try:&lt;br /&gt;
                reads, writes, errs = select.select(rsocks, wsocks, [])&lt;br /&gt;
            except:&lt;br /&gt;
                return&lt;br /&gt;
&lt;br /&gt;
            for sock in reads:&lt;br /&gt;
                if sock == self.socket:&lt;br /&gt;
                    client, name = sock.accept()&lt;br /&gt;
                    rsocks.append(client)&lt;br /&gt;
                elif not `sock` in senders.keys():&lt;br /&gt;
                    sender = sock.recv(1024)&lt;br /&gt;
                    sock.send('Sender OK')&lt;br /&gt;
                    senders['sock'] = sender&lt;br /&gt;
                else:&lt;br /&gt;
                    message = sock.recv(1024)&lt;br /&gt;
                    sock.send('Message OK')&lt;br /&gt;
                    self.writelog('[%s] %s' % (senders['sock'], message))&lt;br /&gt;
                    rsocks.remove(sock)&lt;br /&gt;
                    del senders['sock']&lt;br /&gt;
&lt;br /&gt;
    def stop(self, signum, frame):&lt;br /&gt;
        self.writelog('===&amp;gt; LogServer stopped [signal %s]' % (signum))&lt;br /&gt;
        self.log.close()&lt;br /&gt;
        os.unlink(self.sockfilename)&lt;br /&gt;
&lt;br /&gt;
if __name__ == '__main__':&lt;br /&gt;
    serv = LogServer(maxqueue=3)&lt;br /&gt;
    serv.start()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===logclient.py===&lt;br /&gt;
&amp;lt;source lang=&amp;quot;python&amp;quot; line=&amp;quot;GESHI_NORMAL_LINE_NUMBERS&amp;quot;&amp;gt;&lt;br /&gt;
#!/usr/bin/python&lt;br /&gt;
# -*- coding: utf-8 -*-&lt;br /&gt;
import sys, socket, time&lt;br /&gt;
class LogClient:&lt;br /&gt;
def __init__(self, sender='generic client',&lt;br /&gt;
sockfile='./lserv.sock',&lt;br /&gt;
buffersize=1024,&lt;br /&gt;
testmode=0):&lt;br /&gt;
self.sender = sender&lt;br /&gt;
self.sockfile = sockfile&lt;br /&gt;
self.buffersize = buffersize&lt;br /&gt;
self.testmode = testmode&lt;br /&gt;
def writelog(self, message):&lt;br /&gt;
if self.testmode:&lt;br /&gt;
time.sleep(5)&lt;br /&gt;
self.socket = socket.socket(socket.AF_UNIX)&lt;br /&gt;
self.socket.connect(self.sockfile)&lt;br /&gt;
self.socket.send(self.sender)&lt;br /&gt;
if self.socket.recv(self.buffersize) == 'Sender OK':&lt;br /&gt;
if self.testmode:&lt;br /&gt;
time.sleep(5)&lt;br /&gt;
self.socket.send(message)&lt;br /&gt;
if not self.socket.recv(self.buffersize) == 'Message OK':&lt;br /&gt;
print 'Ошибка: нет подтверждения Message'&lt;br /&gt;
else:&lt;br /&gt;
print 'Ошибка: нет подтверждения Sender'&lt;br /&gt;
self.socket.close()&lt;br /&gt;
if __name__ == '__main__':&lt;br /&gt;
sendername = sys.argv[1]&lt;br /&gt;
client = LogClient(sender=sendername, testmode=1)&lt;br /&gt;
client.writelog('Test message')&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===logclient2.py===&lt;br /&gt;
&amp;lt;source lang=&amp;quot;python&amp;quot; line=&amp;quot;GESHI_NORMAL_LINE_NUMBERS&amp;quot;&amp;gt;&lt;br /&gt;
#!/usr/bin/python&lt;br /&gt;
# -*- coding: utf-8 -*-&lt;br /&gt;
import os&lt;br /&gt;
from logclient import LogClient&lt;br /&gt;
for i in xrange(25):&lt;br /&gt;
pid = os.fork()&lt;br /&gt;
if pid == 0:&lt;br /&gt;
client = LogClient(sender='client%d' % i, testmode=1)&lt;br /&gt;
client.writelog('Test from client%d' % i)&lt;br /&gt;
os._exit(0)&lt;br /&gt;
else:&lt;br /&gt;
print 'Start child[%d]' % pid&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;/div&gt;</summary>
		<author><name>Mim</name></author>	</entry>

	</feed>