<?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=AndrewDjVu</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=AndrewDjVu"/>
		<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/AndrewDjVu"/>
		<updated>2026-05-13T02:11:03Z</updated>
		<subtitle>Вклад участника</subtitle>
		<generator>MediaWiki 1.19.20+dfsg-0+deb7u3</generator>

	<entry>
		<id>http://wiki.linuxformat.ru/wiki/LXF82:Python</id>
		<title>LXF82:Python</title>
		<link rel="alternate" type="text/html" href="http://wiki.linuxformat.ru/wiki/LXF82:Python"/>
				<updated>2009-02-08T13:53:33Z</updated>
		
		<summary type="html">&lt;p&gt;AndrewDjVu: /* HTTP */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{цикл/Python}}&lt;br /&gt;
&lt;br /&gt;
== РАЗРАБОТКА клиент-серверных приложений ==&lt;br /&gt;
''ЧАСТЬ 2 Вознамерились написать открытую альтернативу Skype или собственный клиент BitTorrent? '''Сергей Супрунов''' научит всему необходимому – от основ архитектуры «клиент-сервер» до готовых библиотек для работы с существующими интернет-протоколами.''&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;
|Заголовок=UDP-CLIENT.PY&lt;br /&gt;
|Содержание=&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&lt;br /&gt;
from socket import *&lt;br /&gt;
hostname = sys.argv[1]&lt;br /&gt;
HEADER = '\x00\x01\x00\x00\x00\x01'&lt;br /&gt;
HEADER += '\x00\x00\x00\x00\x00\x00'&lt;br /&gt;
QUESTION = ''&lt;br /&gt;
parts = hostname.split('.')&lt;br /&gt;
for p in parts:&lt;br /&gt;
    QUESTION += '%c%s' % (chr(len(p)), p)&lt;br /&gt;
    QUESTION += '\x00\x00\x01\x00\x01'&lt;br /&gt;
QUERY = HEADER + QUESTION&lt;br /&gt;
cs = socket(AF_INET, SOCK_DGRAM)&lt;br /&gt;
cs.sendto(QUERY, ('127.0.0.1', 53))&lt;br /&gt;
rsp = cs.recv(1024)&lt;br /&gt;
start = len(QUERY) + 12&lt;br /&gt;
print '%s.%s.%s.%s' % (ord(rsp[start]),&lt;br /&gt;
    ord(rsp[start+1]),&lt;br /&gt;
    ord(rsp[start+2]),&lt;br /&gt;
    ord(rsp[start+3]))&amp;lt;/source&amp;gt;&lt;br /&gt;
|Ширина=310px}}&lt;br /&gt;
Наиболее распространенным способом взаимодействия двух приложений через сеть являются уже знакомые нам сокеты. Модуль&lt;br /&gt;
socket, входящий в стандартную поставку Python, помимо рассмотренных в прошлый раз Unix-сокетов поддерживает также сокеты&lt;br /&gt;
домена Internet. Методология использования мало чем отличается от&lt;br /&gt;
Unix-сокетов, за исключением того, что вместо параметра AF_UNIX&lt;br /&gt;
используется AF_INET, а вместо имени файла указываются имя хоста&lt;br /&gt;
и номер порта, которые будут обслуживаться создаваемым сокетом.&lt;br /&gt;
&lt;br /&gt;
В качестве второго параметра в конструкторе сокета можно указать&lt;br /&gt;
его тип: с установлением соединения, соответствующий протоколу TCP,&lt;br /&gt;
или без соединения – протокол UDP. Допустимые значения – SOCK_STREAM и SOCK_DGRAM соответственно. По умолчанию подразумевается SOCK_STREAM.&lt;br /&gt;
&lt;br /&gt;
Как пример, рассмотрим работу приложения, выполняющего роль&lt;br /&gt;
примитивного (и весьма ограниченного функционально) клиента DNS,&lt;br /&gt;
работающего по протоколу UDP (см. листинг udp-client.py).&lt;br /&gt;
&lt;br /&gt;
Некоторую сложность здесь представляет то, что DNS относится к&lt;br /&gt;
так называемым «двоичным» протоколам, в отличие от «текстовых»,&lt;br /&gt;
таких как HTTP или SMTP, где обмен идет обычными текстовыми строками. В случае с DNS оперировать приходится «сырыми» байтами.&lt;br /&gt;
Сведения по формату сообщений можно почерпнуть из RFC 1035, раздел «4. MESSAGES».&lt;br /&gt;
&lt;br /&gt;
В 6-й и 7-й строках рассматриваемого кода формируется заголовок&lt;br /&gt;
(12 байт). Пренебрегая всем богатством возможностей протокола DNS,&lt;br /&gt;
мы ограничиваемся простым запросом (OPCODE=0) одного доменного&lt;br /&gt;
имени (QDCOUNT=1). Конструкции вида «\x00» позволяют задать в&lt;br /&gt;
строке произвольный шестнадцатиричный код.&lt;br /&gt;
&lt;br /&gt;
В строках 8–12 формируется поле запроса. Оно состоит из частей&lt;br /&gt;
доменного имени (в оригинале разделенных точками), перед которыми&lt;br /&gt;
указывается число символов в этой части. Например, имя mail.ru состоит из двух частей (mail – 4 символа, ru – 2 символа) и в запросе должно выглядеть так: \x04mail\x02ru\x00. Завершающий ноль, а также&lt;br /&gt;
два двухбайтовых поля (QTYPE и QCLASS) добавляются к переменной&lt;br /&gt;
QUESTION в строке 12.&lt;br /&gt;
&lt;br /&gt;
Наконец, строки 14 и 15 – создание сокета (обратите внимание на&lt;br /&gt;
второй параметр, SOCK_DGRAM, указывающий тип транспортного протокола UDP) и отправка запроса. Поскольку UDP работает без установки&lt;br /&gt;
соединения, то вместо знакомой нам пары методов «connect – send» мы&lt;br /&gt;
используем один «sendto», в котором указывается сразу и отправляемая информация, и адрес получателя. В данном примере предполагается, что DNS-сервер работает на локальной машине. Вы можете указать&lt;br /&gt;
здесь свой DNS-сервер или передавать его имя в качестве параметра.&lt;br /&gt;
&lt;br /&gt;
И в строках 17–21 из всей полезной информации, возвращаемой&lt;br /&gt;
сервером, мы, игнорируя любые возможные ошибки, выбираем только&lt;br /&gt;
IP-адрес, размещаемый по смещению, которое формируется в переменной start. Результат работы:&lt;br /&gt;
 serg$ ./udp-client.py donpac.ru&lt;br /&gt;
 80.254.111.2&lt;br /&gt;
&lt;br /&gt;
=== Всегда к вашим услугам ===&lt;br /&gt;
{{Врезка&lt;br /&gt;
|Заголовок=IAMOK.PY&lt;br /&gt;
|Содержание=&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, re&lt;br /&gt;
from socket import *&lt;br /&gt;
class IamOK:&lt;br /&gt;
    def __init__(self, host='localhost', port=12345):&lt;br /&gt;
        self.socket = socket(AF_INET, SOCK_STREAM)&lt;br /&gt;
        self.socket.bind((host, port))&lt;br /&gt;
        self.socket.listen(5)&lt;br /&gt;
    def process(self):&lt;br /&gt;
        while 1:&lt;br /&gt;
            csocket, caddress = self.socket.accept()&lt;br /&gt;
            csocket.send('IamOK server v.0.0. Ready to serve.\n')&lt;br /&gt;
            csocket.send('You are from %s, port %s...\n' % caddress)&lt;br /&gt;
            while 1:&lt;br /&gt;
                request = csocket.recv(64)&lt;br /&gt;
                if re.match('get\s+uptime', request, re.IGNORECASE):&lt;br /&gt;
                    csocket.send(os.popen('/usr/bin/uptime').read())&lt;br /&gt;
                elif re.match('quit|bye|exit', request, re.IGNORECASE):&lt;br /&gt;
                    break&lt;br /&gt;
                else:&lt;br /&gt;
                    csocket.send('Unknown command.\n')&lt;br /&gt;
            csocket.close()&lt;br /&gt;
if __name__ == '__main__':&lt;br /&gt;
    serv = IamOK()&lt;br /&gt;
    serv.process()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
|Ширина=550px}}&lt;br /&gt;
В качестве примера сервера, на этот раз работающего по протоколу TCP,&lt;br /&gt;
рассмотрим такой код (см Листинг iamok.py).&lt;br /&gt;
&lt;br /&gt;
Думаю, вы уже поняли, что он прослушивает указанный порт&lt;br /&gt;
(12345), и при поступлении на него запроса возвращает клиенту вывод утилиты uptime, из которого можно почерпнуть время непрерывной&lt;br /&gt;
работы сервера, число подключенных в данный момент пользователей&lt;br /&gt;
и среднюю загрузку системы.&lt;br /&gt;
&lt;br /&gt;
Здесь все должно быть понятно по прошлому уроку. Два отличия – в конструкторе socket.socket() указывается второй параметр –&lt;br /&gt;
SOCK_STREAM (в данном случае его можно было бы и опустить, т.к.&lt;br /&gt;
для Internet-домена и так по умолчанию используется протокол TCP).&lt;br /&gt;
И метод bind() осуществляет привязку сокета не к файлу, а к имени&lt;br /&gt;
хоста и номеру порта, на котором будут ожидаться входящие соединения. Кстати, выбирая номер порта, не забывайте, что порты до 1024-го&lt;br /&gt;
относятся к привилегированным и могут быть задействованы только&lt;br /&gt;
пользователем root.&lt;br /&gt;
&lt;br /&gt;
Наш сервер понимает две команды: «get uptime», по которой возвращается информация о времени работы сервера, и «quit» (с двумя&lt;br /&gt;
синонимами – «bye» и «exit»), по которой сеанс завершается.&lt;br /&gt;
&lt;br /&gt;
Проверить работу сервера можно с помощью обычной&lt;br /&gt;
telnet-сессии:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
admin@dom:~/lxf/propy/l2$ telnet localhost 12345&lt;br /&gt;
Trying 127.0.0.1...&lt;br /&gt;
Connected to localhost.localdomain.&lt;br /&gt;
Escape character is ‘^]’.&lt;br /&gt;
IamOK server v.0.0. Ready to serve.&lt;br /&gt;
You are from 127.0.0.1, port 2650...&lt;br /&gt;
helo&lt;br /&gt;
Unknown command.&lt;br /&gt;
get uptime&lt;br /&gt;
23:09:26 up 58 min, 3 users, load average: 0.04, 0.15, 0.63&lt;br /&gt;
quit&lt;br /&gt;
Connection closed by foreign host.&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Какая от этого может быть польза – решайте сами.&lt;br /&gt;
&lt;br /&gt;
=== «Гуртом и батьку бить веселей» ===&lt;br /&gt;
{{Врезка&lt;br /&gt;
|Заголовок=THREAD-TEST.PY&lt;br /&gt;
|Содержание=&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 threading as t&lt;br /&gt;
import time, re&lt;br /&gt;
diskbusy = t.Lock()&lt;br /&gt;
def parseit(lognum):&lt;br /&gt;
    global errors, total&lt;br /&gt;
    diskbusy.acquire()&lt;br /&gt;
    log = open('logs/syslog.%d' % lognum)&lt;br /&gt;
    lines = log.readlines()&lt;br /&gt;
    diskbusy.release()&lt;br /&gt;
    for line in lines:&lt;br /&gt;
        if re.search('failed|error', line):&lt;br /&gt;
            errors += 1&lt;br /&gt;
            total += 1&lt;br /&gt;
class ParseLog(t.Thread):&lt;br /&gt;
    def __init__(self, num):&lt;br /&gt;
        self.lognum = num&lt;br /&gt;
        t.Thread.__init__(self)&lt;br /&gt;
    def run(self):&lt;br /&gt;
        parseit(self.lognum)&lt;br /&gt;
#------------------ 1&lt;br /&gt;
def test1():&lt;br /&gt;
    global errors, total&lt;br /&gt;
    errors = total = 0&lt;br /&gt;
    for i in range(10):&lt;br /&gt;
        parseit(i)&lt;br /&gt;
    print errors, total&lt;br /&gt;
#------------------ 2&lt;br /&gt;
def test2():&lt;br /&gt;
    global errors, total&lt;br /&gt;
    errors = total = 0&lt;br /&gt;
    running = []&lt;br /&gt;
    for i in range(10):&lt;br /&gt;
        tr = ParseLog(i)&lt;br /&gt;
        tr.start()&lt;br /&gt;
        running.append(tr)&lt;br /&gt;
    for tr in running:&lt;br /&gt;
        tr.join()&lt;br /&gt;
    print errors, total&lt;br /&gt;
&lt;br /&gt;
start = time.time()&lt;br /&gt;
test1()&lt;br /&gt;
print 'Послед.: %f' % (time.time() - start)&lt;br /&gt;
start = time.time()&lt;br /&gt;
test2()&lt;br /&gt;
print 'Потоки: %f' % (time.time() - start)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
|Ширина=350px}}&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;
&lt;br /&gt;
В стандартной поставке Python для работы с потоками есть два&lt;br /&gt;
модуля – thread и threading. Первый позволяет управлять потоками на&lt;br /&gt;
достаточно низком уровне, второй – использует средства первого для&lt;br /&gt;
предоставления более удобного объектно-ориентированного интерфейса. Класс threading.Thread предоставляет «шаблон» потока. Для его&lt;br /&gt;
использования в своей программе вам нужно переопределить метод&lt;br /&gt;
run(), описав в нем действия, которые должны выполняться этим потоком. Ниже приведен пример, из которого все должно стать понятно.&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;
Вот если бы нам удалось организовать работу потоков таким образом, чтобы, пока один из них выполняет чтение файла, другие не обращались к диску... Для решения этой задачи в языке Python доступно&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;
if DISKBUSY:&lt;br /&gt;
# ожидание&lt;br /&gt;
else:&lt;br /&gt;
DISKBUSY = 1&lt;br /&gt;
# чтение файла&lt;br /&gt;
DISKBUSY = 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
То есть первый поток выставит истинное значение глобальной переменной DISKBUSY и приступит к чтению файла. «Опоздавшие» потоки&lt;br /&gt;
будут ждать, пока переменная вновь не примет значение «ложь».&lt;br /&gt;
&lt;br /&gt;
В такой реализации программисту предстоит решить не такую уж&lt;br /&gt;
простую, как может показаться на первый взгляд, задачу – грамотно&lt;br /&gt;
обеспечить ожидание. Бесконечный цикл проверки значения переменной слишком сильно нагружает процессор (не даром такие циклы&lt;br /&gt;
называют напряженными). Напрашивающееся time.sleep(1) очень&lt;br /&gt;
не эффективно – если ресурс освободится до того, как истечет время «спячки», то он будет простаивать. [Кроме того, подобный метод&lt;br /&gt;
синхронизации сам по себе не атомарен – подробности ищите в статье&lt;br /&gt;
«[[LXF82:Unix API|Очереди сообщений и семафоры]]»]&lt;br /&gt;
&lt;br /&gt;
Однако в модуле threading есть готовая реализация описанной&lt;br /&gt;
выше идеи – класс Lock, который предоставляет программисту так&lt;br /&gt;
называемые блокировки, иногда именуемые «мьютексами» (mutex).&lt;br /&gt;
Идя проста – создается объект данного класса (threading.Lock()),&lt;br /&gt;
который имеет два метода: acquire() позволяет захватить объект,&lt;br /&gt;
release() – освободить его. Метод aquire() является блокирующим:&lt;br /&gt;
очередной поток, вызвавший его, будет ждать до тех пор, пока мьютекс&lt;br /&gt;
не освободится.&lt;br /&gt;
&lt;br /&gt;
Дальнейшим развитием идеи блокировок являются семафоры.&lt;br /&gt;
Фактически, семафор – это тот же мьютекс, но позволяющий захватить себя несколько раз. Если вы создадите семафор командой&lt;br /&gt;
semaphore = threading.Semaphore(3), то его смогут захватить (тем&lt;br /&gt;
же методом semaphore.acquire()) одновременно три потока (каждый раз отнимая по единице из указанного при инициализации числа).&lt;br /&gt;
Четвертый поток сможет захватить семафор только после того, как он&lt;br /&gt;
будет высвобожден (semaphore.release()) одним из тех, которые&lt;br /&gt;
удерживают его в настоящее время.&lt;br /&gt;
&lt;br /&gt;
Впрочем, для нашей задачи лучше всего подходят мьютексы – диск&lt;br /&gt;
объявим неразделяемым ресурсом, и посмотрим, какой выигрыш по&lt;br /&gt;
времени это нам даст (см. листинг thread-test.py).&lt;br /&gt;
&lt;br /&gt;
Здесь мы проводим два теста – последовательная обработка (1) и&lt;br /&gt;
использование потоков с эксклюзивным доступом к диску (2). В строках&lt;br /&gt;
16–21 мы создаем подкласс класса Thread, в котором переопределяем&lt;br /&gt;
метод run(). Запуск потока выполняется в строке 36. Обратите внимание&lt;br /&gt;
на список running (строки 33, 37–39). С его помощью мы отслеживаем активность потоков – метод join() заставляет ждать, пока поток не&lt;br /&gt;
завершит свою работу. Дальнейшая работа основного сценария продолжится только после того, как отработают все порожденные потоки.&lt;br /&gt;
&lt;br /&gt;
В строке 5 мы создаем мьютекс, с помощью которого в строках 8 и 11&lt;br /&gt;
будет регулироваться доступ потоков к диску. В глобальной переменной&lt;br /&gt;
errors ведется подсчет числа строк, в которых есть подстрока «failed» или&lt;br /&gt;
«error», в total – общее число обработанных строк. Результат работы:&lt;br /&gt;
 21931 2302755 Послед.: 33.271387&lt;br /&gt;
 21931 2302755 Потоки: 22.867245&lt;br /&gt;
Как видите, мы получили выигрыш по времени более чем на 30%.&lt;br /&gt;
Но нужно заметить, что распараллеливание подобных скриптов даст&lt;br /&gt;
заметный эффект только в том случае, если нагрузка на дисковую систему сопоставима с нагрузкой на процессор. Если какой-то из ресурсов&lt;br /&gt;
будет востребован намного больше второго, то потокам все равно придется ждать его высвобождения, а с учетом дополнительных затрат на&lt;br /&gt;
обслуживание самих потоков, суммарный результат может оказаться&lt;br /&gt;
даже хуже, чем при последовательной обработке.&lt;br /&gt;
&lt;br /&gt;
=== Все включено ===&lt;br /&gt;
В поставку Python входит несколько готовых модулей, позволяющих&lt;br /&gt;
легко и быстро разработать сетевую программу, например, HTTP-сервер или FTP-клиент. Более детально вы сможете познакомиться с ними&lt;br /&gt;
в документации или в хорошо прокомментированных исходных кодах&lt;br /&gt;
самих модулей. Здесь же рассмотрим их возможности обзорно.&lt;br /&gt;
&lt;br /&gt;
=== HTTP ===&lt;br /&gt;
{{Врезка&lt;br /&gt;
|Заголовок=HTTP-SERVER.PY&lt;br /&gt;
|Содержание=&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: utf8 -*-&lt;br /&gt;
import SimpleHTTPServer as http&lt;br /&gt;
handler = http.SimpleHTTPRequestHandler&lt;br /&gt;
server = http.BaseHTTPServer.HTTPServer((‘localhost’, 8080), handler)&lt;br /&gt;
server.serve_forever()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
|Ширина=310px}}&lt;br /&gt;
{{Врезка&lt;br /&gt;
|Заголовок=HTTP-CLIENT.PY&lt;br /&gt;
|Содержание=&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: utf8 -*-&lt;br /&gt;
import httplib&lt;br /&gt;
host = httplib.HTTP(‘localhost:8080’)&lt;br /&gt;
host.putrequest(‘GET’, ‘/testpage.html’)&lt;br /&gt;
host.putheader(‘accept’, ‘text/html’)&lt;br /&gt;
host.endheaders()&lt;br /&gt;
code, msg, headers = host.getreply()&lt;br /&gt;
print code, msg&lt;br /&gt;
if code == 200:&lt;br /&gt;
    print host.getfile().read()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
|Ширина=310px}}&lt;br /&gt;
Для работы с HTTP Python предоставляет четыре основных модуля:&lt;br /&gt;
BaseHTTPServer, SimpleHTTPServer, CGIHTTPServer и httplib.&lt;br /&gt;
Первые три реализуют простейшие серверы, причем второй и третий&lt;br /&gt;
модули используют возможности первого, предоставляя программисту&lt;br /&gt;
более высокоуровневый интерфейс к его методам. Модуль httplib служит для разработки HTTP-клиентов.&lt;br /&gt;
&lt;br /&gt;
Например, простейший HTTP-сервер может выглядеть таким образом (см. листинг http-server.py).&lt;br /&gt;
&lt;br /&gt;
Как видите – всего четыре «рабочих» строчки, и то строка под номером 4 служит лишь для присвоения столь длинного имени метода-обработчика более короткой и удобной переменной. Вести себя этот сервер&lt;br /&gt;
будет как «самый настоящий»: он будет возвращаться запрошенные&lt;br /&gt;
html-страницы или файлы из текущего и вложенных в него каталогов,&lt;br /&gt;
при наличии файлов index.html или index.htm в каталоге, из которого сервер запущен, клиенту по умолчанию (когда указано только имя&lt;br /&gt;
каталога) будут отдаваться они. Если индексные файлы отсутствуют,&lt;br /&gt;
автоматически будет строиться страница-содержание каталога (аналогично работает Apache с включенным модулем mod_autoindex). В&lt;br /&gt;
ответ на запрос несуществующего ресурса будут возвращаться сообщения об ошибке, и т.д.&lt;br /&gt;
&lt;br /&gt;
Клиентское приложение будет не намного сложнее (см Листинг&lt;br /&gt;
http-client.py).&lt;br /&gt;
&lt;br /&gt;
В строках 4–7 формируется нужный HTTP-заголовок, затем получаем и распечатываем ответ сервера:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;html4strict&amp;quot;&amp;gt;&lt;br /&gt;
admin@dom:~/lxf/propy/l2$ ./http-client.py&lt;br /&gt;
200 OK&lt;br /&gt;
&amp;lt;HTML&amp;gt;&amp;lt;HEAD&amp;gt;&lt;br /&gt;
&amp;lt;TITLE&amp;gt;Test page&amp;lt;/TITLE&amp;gt;&lt;br /&gt;
&amp;lt;/HEAD&amp;gt;&amp;lt;BASE&amp;gt;&lt;br /&gt;
&amp;lt;H2&amp;gt;It is a test page&amp;lt;/H2&amp;gt;&lt;br /&gt;
&amp;lt;/BASE&amp;gt;&amp;lt;/HTML&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Конечно, чтобы представить эту страницу в графическом отформатированном виде, придется приложить еще немало усилий. Но это, как&lt;br /&gt;
говорится, уже дело техники.&lt;br /&gt;
&lt;br /&gt;
=== Электронная почта ===&lt;br /&gt;
Модули smtplib, poplib, imaplib предоставляют клиентские интерфейсы к соответствующим протоколам. Их использование не намного&lt;br /&gt;
сложнее рассмотренного выше httplib, и, думаю, вы без труда в них&lt;br /&gt;
разберетесь. Для более тонкой обработки содержимого почтовых сообщений (выделения заголовков, вложений и т.д.) вам помогут модули&lt;br /&gt;
rfc822, mimetools, multifile, base64, mailbox и другие. Все они&lt;br /&gt;
очень хорошо прокомментированы и снабжены достаточно подробной&lt;br /&gt;
документацией. Простейший способ получить к ней доступ – функция&lt;br /&gt;
help(). Например:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
&amp;gt;&amp;gt;&amp;gt; import mailbox&lt;br /&gt;
&amp;gt;&amp;gt;&amp;gt; help(mailbox)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Этот код выведет встроенную справку по работе с модулем mailbox&lt;br /&gt;
прямо в окне интерактивного терминала.&lt;br /&gt;
&lt;br /&gt;
=== FTP ===&lt;br /&gt;
{{Врезка&lt;br /&gt;
|Заголовок=FTP-CLIENT.PY&lt;br /&gt;
|Содержание=&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: uft-8 -*-&lt;br /&gt;
import ftplib&lt;br /&gt;
ftp = ftplib.FTP(‘ftp.freebsd.org’)&lt;br /&gt;
ftp.login(‘ftp’, ‘my@mail.ru’)&lt;br /&gt;
ftp.cwd(‘pub/FreeBSD’)&lt;br /&gt;
retfile = ‘README.TXT’&lt;br /&gt;
ftp.retrbinary(‘RETR %s’ % retfile,&lt;br /&gt;
open(retfile, ‘w+’).write, 1024)&lt;br /&gt;
ftp.quit()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
|Ширина=310px}}&lt;br /&gt;
Для работы с протоколом FTP к вашим услугам модуль ftplib. Работа с&lt;br /&gt;
ним ведется на достаточно низком уровне, и порой напоминает обычный&lt;br /&gt;
сеанс FTP, выполняемый вручную (см. листинг ftp-client.py).&lt;br /&gt;
&lt;br /&gt;
В итоге выполнения этого скрипта в текущем каталоге должен появиться файл README.TXT, скачанный с ftp-сервера ftp://ftp.freebsd.org.&lt;br /&gt;
&lt;br /&gt;
=== Заключение ===&lt;br /&gt;
Итак, на этом мы завершим знакомство с основными сетевыми возможностями языка Python. Хочу заметить, что они выходят далеко за рамки&lt;br /&gt;
простейших сценариев, пригодных для тестирования «больших» серверов или встраивания некоторых сетевых возможностей в ваши приложения. Приведу лишь несколько примеров. Так, в 90-х годах большой популярностью пользовался web-браузер Grail, разработанный на Python и&lt;br /&gt;
предоставляющий весьма широкие для того времени возможности по&lt;br /&gt;
обработке интернет-страниц – полная поддержка стандарта HTML 2.0&lt;br /&gt;
и, в значительной мере, HTML 3.2, поддержка различных форматов&lt;br /&gt;
изображений и звука, способность работать с языком разметки SGML,&lt;br /&gt;
поддержка FTP, и т.д.&lt;br /&gt;
&lt;br /&gt;
Менеджер почтовых рассылок Mailman обеспечивает широкие возможности по управлению списками рассылок, включая web-интерфейс. Интернет-сервер Medusa обладает достаточно хорошими&lt;br /&gt;
характеристиками, позволяя использовать его как для тестовых&lt;br /&gt;
целей, так и для промышленной эксплуатации. Популярный&lt;br /&gt;
сервер web-приложений Zope также полностью разработан&lt;br /&gt;
на языке Python.&lt;br /&gt;
&lt;br /&gt;
Таким образом, этот язык способен решать весьма серьезные сетевые задачи, причем эти решения, как правило,&lt;br /&gt;
обладают весьма высокой переносимостью между различными системами и платформами.&lt;br /&gt;
&lt;br /&gt;
В следующий раз мы рассмотрим способы взаимодействия с&lt;br /&gt;
базами данных, а также убедимся, что Python очень хорош и для разработки динамических web-сайтов.&lt;/div&gt;</summary>
		<author><name>AndrewDjVu</name></author>	</entry>

	<entry>
		<id>http://wiki.linuxformat.ru/wiki/LXF82:Python</id>
		<title>LXF82:Python</title>
		<link rel="alternate" type="text/html" href="http://wiki.linuxformat.ru/wiki/LXF82:Python"/>
				<updated>2009-02-08T13:52:16Z</updated>
		
		<summary type="html">&lt;p&gt;AndrewDjVu: /* «Гуртом и батьку бить веселей» */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{цикл/Python}}&lt;br /&gt;
&lt;br /&gt;
== РАЗРАБОТКА клиент-серверных приложений ==&lt;br /&gt;
''ЧАСТЬ 2 Вознамерились написать открытую альтернативу Skype или собственный клиент BitTorrent? '''Сергей Супрунов''' научит всему необходимому – от основ архитектуры «клиент-сервер» до готовых библиотек для работы с существующими интернет-протоколами.''&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;
|Заголовок=UDP-CLIENT.PY&lt;br /&gt;
|Содержание=&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&lt;br /&gt;
from socket import *&lt;br /&gt;
hostname = sys.argv[1]&lt;br /&gt;
HEADER = '\x00\x01\x00\x00\x00\x01'&lt;br /&gt;
HEADER += '\x00\x00\x00\x00\x00\x00'&lt;br /&gt;
QUESTION = ''&lt;br /&gt;
parts = hostname.split('.')&lt;br /&gt;
for p in parts:&lt;br /&gt;
    QUESTION += '%c%s' % (chr(len(p)), p)&lt;br /&gt;
    QUESTION += '\x00\x00\x01\x00\x01'&lt;br /&gt;
QUERY = HEADER + QUESTION&lt;br /&gt;
cs = socket(AF_INET, SOCK_DGRAM)&lt;br /&gt;
cs.sendto(QUERY, ('127.0.0.1', 53))&lt;br /&gt;
rsp = cs.recv(1024)&lt;br /&gt;
start = len(QUERY) + 12&lt;br /&gt;
print '%s.%s.%s.%s' % (ord(rsp[start]),&lt;br /&gt;
    ord(rsp[start+1]),&lt;br /&gt;
    ord(rsp[start+2]),&lt;br /&gt;
    ord(rsp[start+3]))&amp;lt;/source&amp;gt;&lt;br /&gt;
|Ширина=310px}}&lt;br /&gt;
Наиболее распространенным способом взаимодействия двух приложений через сеть являются уже знакомые нам сокеты. Модуль&lt;br /&gt;
socket, входящий в стандартную поставку Python, помимо рассмотренных в прошлый раз Unix-сокетов поддерживает также сокеты&lt;br /&gt;
домена Internet. Методология использования мало чем отличается от&lt;br /&gt;
Unix-сокетов, за исключением того, что вместо параметра AF_UNIX&lt;br /&gt;
используется AF_INET, а вместо имени файла указываются имя хоста&lt;br /&gt;
и номер порта, которые будут обслуживаться создаваемым сокетом.&lt;br /&gt;
&lt;br /&gt;
В качестве второго параметра в конструкторе сокета можно указать&lt;br /&gt;
его тип: с установлением соединения, соответствующий протоколу TCP,&lt;br /&gt;
или без соединения – протокол UDP. Допустимые значения – SOCK_STREAM и SOCK_DGRAM соответственно. По умолчанию подразумевается SOCK_STREAM.&lt;br /&gt;
&lt;br /&gt;
Как пример, рассмотрим работу приложения, выполняющего роль&lt;br /&gt;
примитивного (и весьма ограниченного функционально) клиента DNS,&lt;br /&gt;
работающего по протоколу UDP (см. листинг udp-client.py).&lt;br /&gt;
&lt;br /&gt;
Некоторую сложность здесь представляет то, что DNS относится к&lt;br /&gt;
так называемым «двоичным» протоколам, в отличие от «текстовых»,&lt;br /&gt;
таких как HTTP или SMTP, где обмен идет обычными текстовыми строками. В случае с DNS оперировать приходится «сырыми» байтами.&lt;br /&gt;
Сведения по формату сообщений можно почерпнуть из RFC 1035, раздел «4. MESSAGES».&lt;br /&gt;
&lt;br /&gt;
В 6-й и 7-й строках рассматриваемого кода формируется заголовок&lt;br /&gt;
(12 байт). Пренебрегая всем богатством возможностей протокола DNS,&lt;br /&gt;
мы ограничиваемся простым запросом (OPCODE=0) одного доменного&lt;br /&gt;
имени (QDCOUNT=1). Конструкции вида «\x00» позволяют задать в&lt;br /&gt;
строке произвольный шестнадцатиричный код.&lt;br /&gt;
&lt;br /&gt;
В строках 8–12 формируется поле запроса. Оно состоит из частей&lt;br /&gt;
доменного имени (в оригинале разделенных точками), перед которыми&lt;br /&gt;
указывается число символов в этой части. Например, имя mail.ru состоит из двух частей (mail – 4 символа, ru – 2 символа) и в запросе должно выглядеть так: \x04mail\x02ru\x00. Завершающий ноль, а также&lt;br /&gt;
два двухбайтовых поля (QTYPE и QCLASS) добавляются к переменной&lt;br /&gt;
QUESTION в строке 12.&lt;br /&gt;
&lt;br /&gt;
Наконец, строки 14 и 15 – создание сокета (обратите внимание на&lt;br /&gt;
второй параметр, SOCK_DGRAM, указывающий тип транспортного протокола UDP) и отправка запроса. Поскольку UDP работает без установки&lt;br /&gt;
соединения, то вместо знакомой нам пары методов «connect – send» мы&lt;br /&gt;
используем один «sendto», в котором указывается сразу и отправляемая информация, и адрес получателя. В данном примере предполагается, что DNS-сервер работает на локальной машине. Вы можете указать&lt;br /&gt;
здесь свой DNS-сервер или передавать его имя в качестве параметра.&lt;br /&gt;
&lt;br /&gt;
И в строках 17–21 из всей полезной информации, возвращаемой&lt;br /&gt;
сервером, мы, игнорируя любые возможные ошибки, выбираем только&lt;br /&gt;
IP-адрес, размещаемый по смещению, которое формируется в переменной start. Результат работы:&lt;br /&gt;
 serg$ ./udp-client.py donpac.ru&lt;br /&gt;
 80.254.111.2&lt;br /&gt;
&lt;br /&gt;
=== Всегда к вашим услугам ===&lt;br /&gt;
{{Врезка&lt;br /&gt;
|Заголовок=IAMOK.PY&lt;br /&gt;
|Содержание=&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, re&lt;br /&gt;
from socket import *&lt;br /&gt;
class IamOK:&lt;br /&gt;
    def __init__(self, host='localhost', port=12345):&lt;br /&gt;
        self.socket = socket(AF_INET, SOCK_STREAM)&lt;br /&gt;
        self.socket.bind((host, port))&lt;br /&gt;
        self.socket.listen(5)&lt;br /&gt;
    def process(self):&lt;br /&gt;
        while 1:&lt;br /&gt;
            csocket, caddress = self.socket.accept()&lt;br /&gt;
            csocket.send('IamOK server v.0.0. Ready to serve.\n')&lt;br /&gt;
            csocket.send('You are from %s, port %s...\n' % caddress)&lt;br /&gt;
            while 1:&lt;br /&gt;
                request = csocket.recv(64)&lt;br /&gt;
                if re.match('get\s+uptime', request, re.IGNORECASE):&lt;br /&gt;
                    csocket.send(os.popen('/usr/bin/uptime').read())&lt;br /&gt;
                elif re.match('quit|bye|exit', request, re.IGNORECASE):&lt;br /&gt;
                    break&lt;br /&gt;
                else:&lt;br /&gt;
                    csocket.send('Unknown command.\n')&lt;br /&gt;
            csocket.close()&lt;br /&gt;
if __name__ == '__main__':&lt;br /&gt;
    serv = IamOK()&lt;br /&gt;
    serv.process()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
|Ширина=550px}}&lt;br /&gt;
В качестве примера сервера, на этот раз работающего по протоколу TCP,&lt;br /&gt;
рассмотрим такой код (см Листинг iamok.py).&lt;br /&gt;
&lt;br /&gt;
Думаю, вы уже поняли, что он прослушивает указанный порт&lt;br /&gt;
(12345), и при поступлении на него запроса возвращает клиенту вывод утилиты uptime, из которого можно почерпнуть время непрерывной&lt;br /&gt;
работы сервера, число подключенных в данный момент пользователей&lt;br /&gt;
и среднюю загрузку системы.&lt;br /&gt;
&lt;br /&gt;
Здесь все должно быть понятно по прошлому уроку. Два отличия – в конструкторе socket.socket() указывается второй параметр –&lt;br /&gt;
SOCK_STREAM (в данном случае его можно было бы и опустить, т.к.&lt;br /&gt;
для Internet-домена и так по умолчанию используется протокол TCP).&lt;br /&gt;
И метод bind() осуществляет привязку сокета не к файлу, а к имени&lt;br /&gt;
хоста и номеру порта, на котором будут ожидаться входящие соединения. Кстати, выбирая номер порта, не забывайте, что порты до 1024-го&lt;br /&gt;
относятся к привилегированным и могут быть задействованы только&lt;br /&gt;
пользователем root.&lt;br /&gt;
&lt;br /&gt;
Наш сервер понимает две команды: «get uptime», по которой возвращается информация о времени работы сервера, и «quit» (с двумя&lt;br /&gt;
синонимами – «bye» и «exit»), по которой сеанс завершается.&lt;br /&gt;
&lt;br /&gt;
Проверить работу сервера можно с помощью обычной&lt;br /&gt;
telnet-сессии:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
admin@dom:~/lxf/propy/l2$ telnet localhost 12345&lt;br /&gt;
Trying 127.0.0.1...&lt;br /&gt;
Connected to localhost.localdomain.&lt;br /&gt;
Escape character is ‘^]’.&lt;br /&gt;
IamOK server v.0.0. Ready to serve.&lt;br /&gt;
You are from 127.0.0.1, port 2650...&lt;br /&gt;
helo&lt;br /&gt;
Unknown command.&lt;br /&gt;
get uptime&lt;br /&gt;
23:09:26 up 58 min, 3 users, load average: 0.04, 0.15, 0.63&lt;br /&gt;
quit&lt;br /&gt;
Connection closed by foreign host.&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Какая от этого может быть польза – решайте сами.&lt;br /&gt;
&lt;br /&gt;
=== «Гуртом и батьку бить веселей» ===&lt;br /&gt;
{{Врезка&lt;br /&gt;
|Заголовок=THREAD-TEST.PY&lt;br /&gt;
|Содержание=&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 threading as t&lt;br /&gt;
import time, re&lt;br /&gt;
diskbusy = t.Lock()&lt;br /&gt;
def parseit(lognum):&lt;br /&gt;
    global errors, total&lt;br /&gt;
    diskbusy.acquire()&lt;br /&gt;
    log = open('logs/syslog.%d' % lognum)&lt;br /&gt;
    lines = log.readlines()&lt;br /&gt;
    diskbusy.release()&lt;br /&gt;
    for line in lines:&lt;br /&gt;
        if re.search('failed|error', line):&lt;br /&gt;
            errors += 1&lt;br /&gt;
            total += 1&lt;br /&gt;
class ParseLog(t.Thread):&lt;br /&gt;
    def __init__(self, num):&lt;br /&gt;
        self.lognum = num&lt;br /&gt;
        t.Thread.__init__(self)&lt;br /&gt;
    def run(self):&lt;br /&gt;
        parseit(self.lognum)&lt;br /&gt;
#------------------ 1&lt;br /&gt;
def test1():&lt;br /&gt;
    global errors, total&lt;br /&gt;
    errors = total = 0&lt;br /&gt;
    for i in range(10):&lt;br /&gt;
        parseit(i)&lt;br /&gt;
    print errors, total&lt;br /&gt;
#------------------ 2&lt;br /&gt;
def test2():&lt;br /&gt;
    global errors, total&lt;br /&gt;
    errors = total = 0&lt;br /&gt;
    running = []&lt;br /&gt;
    for i in range(10):&lt;br /&gt;
        tr = ParseLog(i)&lt;br /&gt;
        tr.start()&lt;br /&gt;
        running.append(tr)&lt;br /&gt;
    for tr in running:&lt;br /&gt;
        tr.join()&lt;br /&gt;
    print errors, total&lt;br /&gt;
&lt;br /&gt;
start = time.time()&lt;br /&gt;
test1()&lt;br /&gt;
print 'Послед.: %f' % (time.time() - start)&lt;br /&gt;
start = time.time()&lt;br /&gt;
test2()&lt;br /&gt;
print 'Потоки: %f' % (time.time() - start)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
|Ширина=350px}}&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;
&lt;br /&gt;
В стандартной поставке Python для работы с потоками есть два&lt;br /&gt;
модуля – thread и threading. Первый позволяет управлять потоками на&lt;br /&gt;
достаточно низком уровне, второй – использует средства первого для&lt;br /&gt;
предоставления более удобного объектно-ориентированного интерфейса. Класс threading.Thread предоставляет «шаблон» потока. Для его&lt;br /&gt;
использования в своей программе вам нужно переопределить метод&lt;br /&gt;
run(), описав в нем действия, которые должны выполняться этим потоком. Ниже приведен пример, из которого все должно стать понятно.&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;
Вот если бы нам удалось организовать работу потоков таким образом, чтобы, пока один из них выполняет чтение файла, другие не обращались к диску... Для решения этой задачи в языке Python доступно&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;
if DISKBUSY:&lt;br /&gt;
# ожидание&lt;br /&gt;
else:&lt;br /&gt;
DISKBUSY = 1&lt;br /&gt;
# чтение файла&lt;br /&gt;
DISKBUSY = 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
То есть первый поток выставит истинное значение глобальной переменной DISKBUSY и приступит к чтению файла. «Опоздавшие» потоки&lt;br /&gt;
будут ждать, пока переменная вновь не примет значение «ложь».&lt;br /&gt;
&lt;br /&gt;
В такой реализации программисту предстоит решить не такую уж&lt;br /&gt;
простую, как может показаться на первый взгляд, задачу – грамотно&lt;br /&gt;
обеспечить ожидание. Бесконечный цикл проверки значения переменной слишком сильно нагружает процессор (не даром такие циклы&lt;br /&gt;
называют напряженными). Напрашивающееся time.sleep(1) очень&lt;br /&gt;
не эффективно – если ресурс освободится до того, как истечет время «спячки», то он будет простаивать. [Кроме того, подобный метод&lt;br /&gt;
синхронизации сам по себе не атомарен – подробности ищите в статье&lt;br /&gt;
«[[LXF82:Unix API|Очереди сообщений и семафоры]]»]&lt;br /&gt;
&lt;br /&gt;
Однако в модуле threading есть готовая реализация описанной&lt;br /&gt;
выше идеи – класс Lock, который предоставляет программисту так&lt;br /&gt;
называемые блокировки, иногда именуемые «мьютексами» (mutex).&lt;br /&gt;
Идя проста – создается объект данного класса (threading.Lock()),&lt;br /&gt;
который имеет два метода: acquire() позволяет захватить объект,&lt;br /&gt;
release() – освободить его. Метод aquire() является блокирующим:&lt;br /&gt;
очередной поток, вызвавший его, будет ждать до тех пор, пока мьютекс&lt;br /&gt;
не освободится.&lt;br /&gt;
&lt;br /&gt;
Дальнейшим развитием идеи блокировок являются семафоры.&lt;br /&gt;
Фактически, семафор – это тот же мьютекс, но позволяющий захватить себя несколько раз. Если вы создадите семафор командой&lt;br /&gt;
semaphore = threading.Semaphore(3), то его смогут захватить (тем&lt;br /&gt;
же методом semaphore.acquire()) одновременно три потока (каждый раз отнимая по единице из указанного при инициализации числа).&lt;br /&gt;
Четвертый поток сможет захватить семафор только после того, как он&lt;br /&gt;
будет высвобожден (semaphore.release()) одним из тех, которые&lt;br /&gt;
удерживают его в настоящее время.&lt;br /&gt;
&lt;br /&gt;
Впрочем, для нашей задачи лучше всего подходят мьютексы – диск&lt;br /&gt;
объявим неразделяемым ресурсом, и посмотрим, какой выигрыш по&lt;br /&gt;
времени это нам даст (см. листинг thread-test.py).&lt;br /&gt;
&lt;br /&gt;
Здесь мы проводим два теста – последовательная обработка (1) и&lt;br /&gt;
использование потоков с эксклюзивным доступом к диску (2). В строках&lt;br /&gt;
16–21 мы создаем подкласс класса Thread, в котором переопределяем&lt;br /&gt;
метод run(). Запуск потока выполняется в строке 36. Обратите внимание&lt;br /&gt;
на список running (строки 33, 37–39). С его помощью мы отслеживаем активность потоков – метод join() заставляет ждать, пока поток не&lt;br /&gt;
завершит свою работу. Дальнейшая работа основного сценария продолжится только после того, как отработают все порожденные потоки.&lt;br /&gt;
&lt;br /&gt;
В строке 5 мы создаем мьютекс, с помощью которого в строках 8 и 11&lt;br /&gt;
будет регулироваться доступ потоков к диску. В глобальной переменной&lt;br /&gt;
errors ведется подсчет числа строк, в которых есть подстрока «failed» или&lt;br /&gt;
«error», в total – общее число обработанных строк. Результат работы:&lt;br /&gt;
 21931 2302755 Послед.: 33.271387&lt;br /&gt;
 21931 2302755 Потоки: 22.867245&lt;br /&gt;
Как видите, мы получили выигрыш по времени более чем на 30%.&lt;br /&gt;
Но нужно заметить, что распараллеливание подобных скриптов даст&lt;br /&gt;
заметный эффект только в том случае, если нагрузка на дисковую систему сопоставима с нагрузкой на процессор. Если какой-то из ресурсов&lt;br /&gt;
будет востребован намного больше второго, то потокам все равно придется ждать его высвобождения, а с учетом дополнительных затрат на&lt;br /&gt;
обслуживание самих потоков, суммарный результат может оказаться&lt;br /&gt;
даже хуже, чем при последовательной обработке.&lt;br /&gt;
&lt;br /&gt;
=== Все включено ===&lt;br /&gt;
В поставку Python входит несколько готовых модулей, позволяющих&lt;br /&gt;
легко и быстро разработать сетевую программу, например, HTTP-сервер или FTP-клиент. Более детально вы сможете познакомиться с ними&lt;br /&gt;
в документации или в хорошо прокомментированных исходных кодах&lt;br /&gt;
самих модулей. Здесь же рассмотрим их возможности обзорно.&lt;br /&gt;
&lt;br /&gt;
=== HTTP ===&lt;br /&gt;
{{Врезка&lt;br /&gt;
|Заголовок=HTTP-SERVER.PY&lt;br /&gt;
|Содержание=&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: utf8 -*-&lt;br /&gt;
import SimpleHTTPServer as http&lt;br /&gt;
handler = http.SimpleHTTPRequestHandler&lt;br /&gt;
server = http.BaseHTTPServer.HTTPServer((‘localhost’, 8080), handler)&lt;br /&gt;
server.serve_forever()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
|Ширина=310px}}&lt;br /&gt;
{{Врезка&lt;br /&gt;
|Заголовок=HTTP-CLIENT.PY&lt;br /&gt;
|Содержание=&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: utf8 -*-&lt;br /&gt;
import httplib&lt;br /&gt;
host = httplib.HTTP(‘localhost:8080’)&lt;br /&gt;
host.putrequest(‘GET’, ‘/testpage.html’)&lt;br /&gt;
host.putheader(‘accept’, ‘text/html’)&lt;br /&gt;
host.endheaders()&lt;br /&gt;
code, msg, headers = host.getreply()&lt;br /&gt;
print code, msg&lt;br /&gt;
if code == 200:&lt;br /&gt;
print host.getfile().read()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
|Ширина=310px}}&lt;br /&gt;
Для работы с HTTP Python предоставляет четыре основных модуля:&lt;br /&gt;
BaseHTTPServer, SimpleHTTPServer, CGIHTTPServer и httplib.&lt;br /&gt;
Первые три реализуют простейшие серверы, причем второй и третий&lt;br /&gt;
модули используют возможности первого, предоставляя программисту&lt;br /&gt;
более высокоуровневый интерфейс к его методам. Модуль httplib служит для разработки HTTP-клиентов.&lt;br /&gt;
&lt;br /&gt;
Например, простейший HTTP-сервер может выглядеть таким образом (см. листинг http-server.py).&lt;br /&gt;
&lt;br /&gt;
Как видите – всего четыре «рабочих» строчки, и то строка под номером 4 служит лишь для присвоения столь длинного имени метода-обработчика более короткой и удобной переменной. Вести себя этот сервер&lt;br /&gt;
будет как «самый настоящий»: он будет возвращаться запрошенные&lt;br /&gt;
html-страницы или файлы из текущего и вложенных в него каталогов,&lt;br /&gt;
при наличии файлов index.html или index.htm в каталоге, из которого сервер запущен, клиенту по умолчанию (когда указано только имя&lt;br /&gt;
каталога) будут отдаваться они. Если индексные файлы отсутствуют,&lt;br /&gt;
автоматически будет строиться страница-содержание каталога (аналогично работает Apache с включенным модулем mod_autoindex). В&lt;br /&gt;
ответ на запрос несуществующего ресурса будут возвращаться сообщения об ошибке, и т.д.&lt;br /&gt;
&lt;br /&gt;
Клиентское приложение будет не намного сложнее (см Листинг&lt;br /&gt;
http-client.py).&lt;br /&gt;
&lt;br /&gt;
В строках 4–7 формируется нужный HTTP-заголовок, затем получаем и распечатываем ответ сервера:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;html4strict&amp;quot;&amp;gt;&lt;br /&gt;
admin@dom:~/lxf/propy/l2$ ./http-client.py&lt;br /&gt;
200 OK&lt;br /&gt;
&amp;lt;HTML&amp;gt;&amp;lt;HEAD&amp;gt;&lt;br /&gt;
&amp;lt;TITLE&amp;gt;Test page&amp;lt;/TITLE&amp;gt;&lt;br /&gt;
&amp;lt;/HEAD&amp;gt;&amp;lt;BASE&amp;gt;&lt;br /&gt;
&amp;lt;H2&amp;gt;It is a test page&amp;lt;/H2&amp;gt;&lt;br /&gt;
&amp;lt;/BASE&amp;gt;&amp;lt;/HTML&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Конечно, чтобы представить эту страницу в графическом отформатированном виде, придется приложить еще немало усилий. Но это, как&lt;br /&gt;
говорится, уже дело техники.&lt;br /&gt;
&lt;br /&gt;
=== Электронная почта ===&lt;br /&gt;
Модули smtplib, poplib, imaplib предоставляют клиентские интерфейсы к соответствующим протоколам. Их использование не намного&lt;br /&gt;
сложнее рассмотренного выше httplib, и, думаю, вы без труда в них&lt;br /&gt;
разберетесь. Для более тонкой обработки содержимого почтовых сообщений (выделения заголовков, вложений и т.д.) вам помогут модули&lt;br /&gt;
rfc822, mimetools, multifile, base64, mailbox и другие. Все они&lt;br /&gt;
очень хорошо прокомментированы и снабжены достаточно подробной&lt;br /&gt;
документацией. Простейший способ получить к ней доступ – функция&lt;br /&gt;
help(). Например:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
&amp;gt;&amp;gt;&amp;gt; import mailbox&lt;br /&gt;
&amp;gt;&amp;gt;&amp;gt; help(mailbox)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Этот код выведет встроенную справку по работе с модулем mailbox&lt;br /&gt;
прямо в окне интерактивного терминала.&lt;br /&gt;
&lt;br /&gt;
=== FTP ===&lt;br /&gt;
{{Врезка&lt;br /&gt;
|Заголовок=FTP-CLIENT.PY&lt;br /&gt;
|Содержание=&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: uft-8 -*-&lt;br /&gt;
import ftplib&lt;br /&gt;
ftp = ftplib.FTP(‘ftp.freebsd.org’)&lt;br /&gt;
ftp.login(‘ftp’, ‘my@mail.ru’)&lt;br /&gt;
ftp.cwd(‘pub/FreeBSD’)&lt;br /&gt;
retfile = ‘README.TXT’&lt;br /&gt;
ftp.retrbinary(‘RETR %s’ % retfile,&lt;br /&gt;
open(retfile, ‘w+’).write, 1024)&lt;br /&gt;
ftp.quit()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
|Ширина=310px}}&lt;br /&gt;
Для работы с протоколом FTP к вашим услугам модуль ftplib. Работа с&lt;br /&gt;
ним ведется на достаточно низком уровне, и порой напоминает обычный&lt;br /&gt;
сеанс FTP, выполняемый вручную (см. листинг ftp-client.py).&lt;br /&gt;
&lt;br /&gt;
В итоге выполнения этого скрипта в текущем каталоге должен появиться файл README.TXT, скачанный с ftp-сервера ftp://ftp.freebsd.org.&lt;br /&gt;
&lt;br /&gt;
=== Заключение ===&lt;br /&gt;
Итак, на этом мы завершим знакомство с основными сетевыми возможностями языка Python. Хочу заметить, что они выходят далеко за рамки&lt;br /&gt;
простейших сценариев, пригодных для тестирования «больших» серверов или встраивания некоторых сетевых возможностей в ваши приложения. Приведу лишь несколько примеров. Так, в 90-х годах большой популярностью пользовался web-браузер Grail, разработанный на Python и&lt;br /&gt;
предоставляющий весьма широкие для того времени возможности по&lt;br /&gt;
обработке интернет-страниц – полная поддержка стандарта HTML 2.0&lt;br /&gt;
и, в значительной мере, HTML 3.2, поддержка различных форматов&lt;br /&gt;
изображений и звука, способность работать с языком разметки SGML,&lt;br /&gt;
поддержка FTP, и т.д.&lt;br /&gt;
&lt;br /&gt;
Менеджер почтовых рассылок Mailman обеспечивает широкие возможности по управлению списками рассылок, включая web-интерфейс. Интернет-сервер Medusa обладает достаточно хорошими&lt;br /&gt;
характеристиками, позволяя использовать его как для тестовых&lt;br /&gt;
целей, так и для промышленной эксплуатации. Популярный&lt;br /&gt;
сервер web-приложений Zope также полностью разработан&lt;br /&gt;
на языке Python.&lt;br /&gt;
&lt;br /&gt;
Таким образом, этот язык способен решать весьма серьезные сетевые задачи, причем эти решения, как правило,&lt;br /&gt;
обладают весьма высокой переносимостью между различными системами и платформами.&lt;br /&gt;
&lt;br /&gt;
В следующий раз мы рассмотрим способы взаимодействия с&lt;br /&gt;
базами данных, а также убедимся, что Python очень хорош и для разработки динамических web-сайтов.&lt;/div&gt;</summary>
		<author><name>AndrewDjVu</name></author>	</entry>

	<entry>
		<id>http://wiki.linuxformat.ru/wiki/LXF82:Python</id>
		<title>LXF82:Python</title>
		<link rel="alternate" type="text/html" href="http://wiki.linuxformat.ru/wiki/LXF82:Python"/>
				<updated>2009-02-08T10:58:36Z</updated>
		
		<summary type="html">&lt;p&gt;AndrewDjVu: /* Всегда к вашим услугам */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{цикл/Python}}&lt;br /&gt;
&lt;br /&gt;
== РАЗРАБОТКА клиент-серверных приложений ==&lt;br /&gt;
''ЧАСТЬ 2 Вознамерились написать открытую альтернативу Skype или собственный клиент BitTorrent? '''Сергей Супрунов''' научит всему необходимому – от основ архитектуры «клиент-сервер» до готовых библиотек для работы с существующими интернет-протоколами.''&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;
|Заголовок=UDP-CLIENT.PY&lt;br /&gt;
|Содержание=&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&lt;br /&gt;
from socket import *&lt;br /&gt;
hostname = sys.argv[1]&lt;br /&gt;
HEADER = '\x00\x01\x00\x00\x00\x01'&lt;br /&gt;
HEADER += '\x00\x00\x00\x00\x00\x00'&lt;br /&gt;
QUESTION = ''&lt;br /&gt;
parts = hostname.split('.')&lt;br /&gt;
for p in parts:&lt;br /&gt;
    QUESTION += '%c%s' % (chr(len(p)), p)&lt;br /&gt;
    QUESTION += '\x00\x00\x01\x00\x01'&lt;br /&gt;
QUERY = HEADER + QUESTION&lt;br /&gt;
cs = socket(AF_INET, SOCK_DGRAM)&lt;br /&gt;
cs.sendto(QUERY, ('127.0.0.1', 53))&lt;br /&gt;
rsp = cs.recv(1024)&lt;br /&gt;
start = len(QUERY) + 12&lt;br /&gt;
print '%s.%s.%s.%s' % (ord(rsp[start]),&lt;br /&gt;
    ord(rsp[start+1]),&lt;br /&gt;
    ord(rsp[start+2]),&lt;br /&gt;
    ord(rsp[start+3]))&amp;lt;/source&amp;gt;&lt;br /&gt;
|Ширина=310px}}&lt;br /&gt;
Наиболее распространенным способом взаимодействия двух приложений через сеть являются уже знакомые нам сокеты. Модуль&lt;br /&gt;
socket, входящий в стандартную поставку Python, помимо рассмотренных в прошлый раз Unix-сокетов поддерживает также сокеты&lt;br /&gt;
домена Internet. Методология использования мало чем отличается от&lt;br /&gt;
Unix-сокетов, за исключением того, что вместо параметра AF_UNIX&lt;br /&gt;
используется AF_INET, а вместо имени файла указываются имя хоста&lt;br /&gt;
и номер порта, которые будут обслуживаться создаваемым сокетом.&lt;br /&gt;
&lt;br /&gt;
В качестве второго параметра в конструкторе сокета можно указать&lt;br /&gt;
его тип: с установлением соединения, соответствующий протоколу TCP,&lt;br /&gt;
или без соединения – протокол UDP. Допустимые значения – SOCK_STREAM и SOCK_DGRAM соответственно. По умолчанию подразумевается SOCK_STREAM.&lt;br /&gt;
&lt;br /&gt;
Как пример, рассмотрим работу приложения, выполняющего роль&lt;br /&gt;
примитивного (и весьма ограниченного функционально) клиента DNS,&lt;br /&gt;
работающего по протоколу UDP (см. листинг udp-client.py).&lt;br /&gt;
&lt;br /&gt;
Некоторую сложность здесь представляет то, что DNS относится к&lt;br /&gt;
так называемым «двоичным» протоколам, в отличие от «текстовых»,&lt;br /&gt;
таких как HTTP или SMTP, где обмен идет обычными текстовыми строками. В случае с DNS оперировать приходится «сырыми» байтами.&lt;br /&gt;
Сведения по формату сообщений можно почерпнуть из RFC 1035, раздел «4. MESSAGES».&lt;br /&gt;
&lt;br /&gt;
В 6-й и 7-й строках рассматриваемого кода формируется заголовок&lt;br /&gt;
(12 байт). Пренебрегая всем богатством возможностей протокола DNS,&lt;br /&gt;
мы ограничиваемся простым запросом (OPCODE=0) одного доменного&lt;br /&gt;
имени (QDCOUNT=1). Конструкции вида «\x00» позволяют задать в&lt;br /&gt;
строке произвольный шестнадцатиричный код.&lt;br /&gt;
&lt;br /&gt;
В строках 8–12 формируется поле запроса. Оно состоит из частей&lt;br /&gt;
доменного имени (в оригинале разделенных точками), перед которыми&lt;br /&gt;
указывается число символов в этой части. Например, имя mail.ru состоит из двух частей (mail – 4 символа, ru – 2 символа) и в запросе должно выглядеть так: \x04mail\x02ru\x00. Завершающий ноль, а также&lt;br /&gt;
два двухбайтовых поля (QTYPE и QCLASS) добавляются к переменной&lt;br /&gt;
QUESTION в строке 12.&lt;br /&gt;
&lt;br /&gt;
Наконец, строки 14 и 15 – создание сокета (обратите внимание на&lt;br /&gt;
второй параметр, SOCK_DGRAM, указывающий тип транспортного протокола UDP) и отправка запроса. Поскольку UDP работает без установки&lt;br /&gt;
соединения, то вместо знакомой нам пары методов «connect – send» мы&lt;br /&gt;
используем один «sendto», в котором указывается сразу и отправляемая информация, и адрес получателя. В данном примере предполагается, что DNS-сервер работает на локальной машине. Вы можете указать&lt;br /&gt;
здесь свой DNS-сервер или передавать его имя в качестве параметра.&lt;br /&gt;
&lt;br /&gt;
И в строках 17–21 из всей полезной информации, возвращаемой&lt;br /&gt;
сервером, мы, игнорируя любые возможные ошибки, выбираем только&lt;br /&gt;
IP-адрес, размещаемый по смещению, которое формируется в переменной start. Результат работы:&lt;br /&gt;
 serg$ ./udp-client.py donpac.ru&lt;br /&gt;
 80.254.111.2&lt;br /&gt;
&lt;br /&gt;
=== Всегда к вашим услугам ===&lt;br /&gt;
{{Врезка&lt;br /&gt;
|Заголовок=IAMOK.PY&lt;br /&gt;
|Содержание=&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, re&lt;br /&gt;
from socket import *&lt;br /&gt;
class IamOK:&lt;br /&gt;
    def __init__(self, host='localhost', port=12345):&lt;br /&gt;
        self.socket = socket(AF_INET, SOCK_STREAM)&lt;br /&gt;
        self.socket.bind((host, port))&lt;br /&gt;
        self.socket.listen(5)&lt;br /&gt;
    def process(self):&lt;br /&gt;
        while 1:&lt;br /&gt;
            csocket, caddress = self.socket.accept()&lt;br /&gt;
            csocket.send('IamOK server v.0.0. Ready to serve.\n')&lt;br /&gt;
            csocket.send('You are from %s, port %s...\n' % caddress)&lt;br /&gt;
            while 1:&lt;br /&gt;
                request = csocket.recv(64)&lt;br /&gt;
                if re.match('get\s+uptime', request, re.IGNORECASE):&lt;br /&gt;
                    csocket.send(os.popen('/usr/bin/uptime').read())&lt;br /&gt;
                elif re.match('quit|bye|exit', request, re.IGNORECASE):&lt;br /&gt;
                    break&lt;br /&gt;
                else:&lt;br /&gt;
                    csocket.send('Unknown command.\n')&lt;br /&gt;
            csocket.close()&lt;br /&gt;
if __name__ == '__main__':&lt;br /&gt;
    serv = IamOK()&lt;br /&gt;
    serv.process()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
|Ширина=550px}}&lt;br /&gt;
В качестве примера сервера, на этот раз работающего по протоколу TCP,&lt;br /&gt;
рассмотрим такой код (см Листинг iamok.py).&lt;br /&gt;
&lt;br /&gt;
Думаю, вы уже поняли, что он прослушивает указанный порт&lt;br /&gt;
(12345), и при поступлении на него запроса возвращает клиенту вывод утилиты uptime, из которого можно почерпнуть время непрерывной&lt;br /&gt;
работы сервера, число подключенных в данный момент пользователей&lt;br /&gt;
и среднюю загрузку системы.&lt;br /&gt;
&lt;br /&gt;
Здесь все должно быть понятно по прошлому уроку. Два отличия – в конструкторе socket.socket() указывается второй параметр –&lt;br /&gt;
SOCK_STREAM (в данном случае его можно было бы и опустить, т.к.&lt;br /&gt;
для Internet-домена и так по умолчанию используется протокол TCP).&lt;br /&gt;
И метод bind() осуществляет привязку сокета не к файлу, а к имени&lt;br /&gt;
хоста и номеру порта, на котором будут ожидаться входящие соединения. Кстати, выбирая номер порта, не забывайте, что порты до 1024-го&lt;br /&gt;
относятся к привилегированным и могут быть задействованы только&lt;br /&gt;
пользователем root.&lt;br /&gt;
&lt;br /&gt;
Наш сервер понимает две команды: «get uptime», по которой возвращается информация о времени работы сервера, и «quit» (с двумя&lt;br /&gt;
синонимами – «bye» и «exit»), по которой сеанс завершается.&lt;br /&gt;
&lt;br /&gt;
Проверить работу сервера можно с помощью обычной&lt;br /&gt;
telnet-сессии:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
admin@dom:~/lxf/propy/l2$ telnet localhost 12345&lt;br /&gt;
Trying 127.0.0.1...&lt;br /&gt;
Connected to localhost.localdomain.&lt;br /&gt;
Escape character is ‘^]’.&lt;br /&gt;
IamOK server v.0.0. Ready to serve.&lt;br /&gt;
You are from 127.0.0.1, port 2650...&lt;br /&gt;
helo&lt;br /&gt;
Unknown command.&lt;br /&gt;
get uptime&lt;br /&gt;
23:09:26 up 58 min, 3 users, load average: 0.04, 0.15, 0.63&lt;br /&gt;
quit&lt;br /&gt;
Connection closed by foreign host.&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Какая от этого может быть польза – решайте сами.&lt;br /&gt;
&lt;br /&gt;
=== «Гуртом и батьку бить веселей» ===&lt;br /&gt;
{{Врезка&lt;br /&gt;
|Заголовок=THREAD-TEST.PY&lt;br /&gt;
|Содержание=&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: utf8 -*-&lt;br /&gt;
import threading as t&lt;br /&gt;
import time, re&lt;br /&gt;
diskbusy = t.Lock()&lt;br /&gt;
def parseit(lognum):&lt;br /&gt;
global errors, total&lt;br /&gt;
diskbusy.acquire()&lt;br /&gt;
log = open(‘logs/syslog.%d’ % lognum)&lt;br /&gt;
lines = log.readlines()&lt;br /&gt;
diskbusy.release()&lt;br /&gt;
for line in lines:&lt;br /&gt;
if re.search(‘failed|error’, line):&lt;br /&gt;
errors += 1&lt;br /&gt;
total += 1&lt;br /&gt;
class ParseLog(t.Thread):&lt;br /&gt;
def __init__(self, num):&lt;br /&gt;
self.lognum = num&lt;br /&gt;
t.Thread.__init__(self)&lt;br /&gt;
&lt;br /&gt;
def run(self):&lt;br /&gt;
parseit(self.lognum)&lt;br /&gt;
#------------------ 1&lt;br /&gt;
def test1():&lt;br /&gt;
global errors, total&lt;br /&gt;
errors = total = 0&lt;br /&gt;
for i in range(10):&lt;br /&gt;
parseit(i)&lt;br /&gt;
print errors, total,&lt;br /&gt;
#------------------ 2&lt;br /&gt;
def test2():&lt;br /&gt;
global errors, total&lt;br /&gt;
errors = total = 0&lt;br /&gt;
running = []&lt;br /&gt;
for i in range(10):&lt;br /&gt;
tr = ParseLog(i)&lt;br /&gt;
tr.start()&lt;br /&gt;
running.append(tr)&lt;br /&gt;
for tr in running:&lt;br /&gt;
tr.join()&lt;br /&gt;
&lt;br /&gt;
print errors, total,&lt;br /&gt;
start = time.time()&lt;br /&gt;
test1()&lt;br /&gt;
print ‘Послед.: %f’ % (time.time() - start)&lt;br /&gt;
start = time.time()&lt;br /&gt;
test2()&lt;br /&gt;
print ‘Потоки: %f’ % (time.time() - start)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
|Ширина=310px}}&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;
&lt;br /&gt;
В стандартной поставке Python для работы с потоками есть два&lt;br /&gt;
модуля – thread и threading. Первый позволяет управлять потоками на&lt;br /&gt;
достаточно низком уровне, второй – использует средства первого для&lt;br /&gt;
предоставления более удобного объектно-ориентированного интерфейса. Класс threading.Thread предоставляет «шаблон» потока. Для его&lt;br /&gt;
использования в своей программе вам нужно переопределить метод&lt;br /&gt;
run(), описав в нем действия, которые должны выполняться этим потоком. Ниже приведен пример, из которого все должно стать понятно.&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;
Вот если бы нам удалось организовать работу потоков таким образом, чтобы, пока один из них выполняет чтение файла, другие не обращались к диску... Для решения этой задачи в языке Python доступно&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;
if DISKBUSY:&lt;br /&gt;
# ожидание&lt;br /&gt;
else:&lt;br /&gt;
DISKBUSY = 1&lt;br /&gt;
# чтение файла&lt;br /&gt;
DISKBUSY = 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
То есть первый поток выставит истинное значение глобальной переменной DISKBUSY и приступит к чтению файла. «Опоздавшие» потоки&lt;br /&gt;
будут ждать, пока переменная вновь не примет значение «ложь».&lt;br /&gt;
&lt;br /&gt;
В такой реализации программисту предстоит решить не такую уж&lt;br /&gt;
простую, как может показаться на первый взгляд, задачу – грамотно&lt;br /&gt;
обеспечить ожидание. Бесконечный цикл проверки значения переменной слишком сильно нагружает процессор (не даром такие циклы&lt;br /&gt;
называют напряженными). Напрашивающееся time.sleep(1) очень&lt;br /&gt;
не эффективно – если ресурс освободится до того, как истечет время «спячки», то он будет простаивать. [Кроме того, подобный метод&lt;br /&gt;
синхронизации сам по себе не атомарен – подробности ищите в статье&lt;br /&gt;
«[[LXF82:Unix API|Очереди сообщений и семафоры]]»]&lt;br /&gt;
&lt;br /&gt;
Однако в модуле threading есть готовая реализация описанной&lt;br /&gt;
выше идеи – класс Lock, который предоставляет программисту так&lt;br /&gt;
называемые блокировки, иногда именуемые «мьютексами» (mutex).&lt;br /&gt;
Идя проста – создается объект данного класса (threading.Lock()),&lt;br /&gt;
который имеет два метода: acquire() позволяет захватить объект,&lt;br /&gt;
release() – освободить его. Метод aquire() является блокирующим:&lt;br /&gt;
очередной поток, вызвавший его, будет ждать до тех пор, пока мьютекс&lt;br /&gt;
не освободится.&lt;br /&gt;
&lt;br /&gt;
Дальнейшим развитием идеи блокировок являются семафоры.&lt;br /&gt;
Фактически, семафор – это тот же мьютекс, но позволяющий захватить себя несколько раз. Если вы создадите семафор командой&lt;br /&gt;
semaphore = threading.Semaphore(3), то его смогут захватить (тем&lt;br /&gt;
же методом semaphore.acquire()) одновременно три потока (каждый раз отнимая по единице из указанного при инициализации числа).&lt;br /&gt;
Четвертый поток сможет захватить семафор только после того, как он&lt;br /&gt;
будет высвобожден (semaphore.release()) одним из тех, которые&lt;br /&gt;
удерживают его в настоящее время.&lt;br /&gt;
&lt;br /&gt;
Впрочем, для нашей задачи лучше всего подходят мьютексы – диск&lt;br /&gt;
объявим неразделяемым ресурсом, и посмотрим, какой выигрыш по&lt;br /&gt;
времени это нам даст (см. листинг thread-test.py).&lt;br /&gt;
&lt;br /&gt;
Здесь мы проводим два теста – последовательная обработка (1) и&lt;br /&gt;
использование потоков с эксклюзивным доступом к диску (2). В строках&lt;br /&gt;
16–22 мы создаем подкласс класса Thread, в котором переопределяем&lt;br /&gt;
метод run(). Запуск потока выполняется в строке 37. Обратите внимание&lt;br /&gt;
на список running (строки 34, 38, 39–40). С его помощью мы отслеживаем активность потоков – метод join() заставляет ждать, пока поток не&lt;br /&gt;
завершит свою работу. Дальнейшая работа основного сценария продолжится только после того, как отработают все порожденные потоки.&lt;br /&gt;
&lt;br /&gt;
В строке 5 мы создаем мьютекс, с помощью которого в строках 8 и 11&lt;br /&gt;
будет регулироваться доступ потоков к диску. В глобальной переменной&lt;br /&gt;
errors ведется подсчет числа строк, в которых есть подстрока «failed» или&lt;br /&gt;
«error», в total – общее число обработанных строк. Результат работы:&lt;br /&gt;
 21931 2302755 Послед.: 33.271387&lt;br /&gt;
 21931 2302755 Потоки: 22.867245&lt;br /&gt;
Как видите, мы получили выигрыш по времени более чем на 30%.&lt;br /&gt;
Но нужно заметить, что распараллеливание подобных скриптов даст&lt;br /&gt;
заметный эффект только в том случае, если нагрузка на дисковую систему сопоставима с нагрузкой на процессор. Если какой-то из ресурсов&lt;br /&gt;
будет востребован намного больше второго, то потокам все равно придется ждать его высвобождения, а с учетом дополнительных затрат на&lt;br /&gt;
обслуживание самих потоков, суммарный результат может оказаться&lt;br /&gt;
даже хуже, чем при последовательной обработке.&lt;br /&gt;
&lt;br /&gt;
=== Все включено ===&lt;br /&gt;
В поставку Python входит несколько готовых модулей, позволяющих&lt;br /&gt;
легко и быстро разработать сетевую программу, например, HTTP-сервер или FTP-клиент. Более детально вы сможете познакомиться с ними&lt;br /&gt;
в документации или в хорошо прокомментированных исходных кодах&lt;br /&gt;
самих модулей. Здесь же рассмотрим их возможности обзорно.&lt;br /&gt;
&lt;br /&gt;
=== HTTP ===&lt;br /&gt;
{{Врезка&lt;br /&gt;
|Заголовок=HTTP-SERVER.PY&lt;br /&gt;
|Содержание=&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: utf8 -*-&lt;br /&gt;
import SimpleHTTPServer as http&lt;br /&gt;
handler = http.SimpleHTTPRequestHandler&lt;br /&gt;
server = http.BaseHTTPServer.HTTPServer((‘localhost’, 8080), handler)&lt;br /&gt;
server.serve_forever()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
|Ширина=310px}}&lt;br /&gt;
{{Врезка&lt;br /&gt;
|Заголовок=HTTP-CLIENT.PY&lt;br /&gt;
|Содержание=&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: utf8 -*-&lt;br /&gt;
import httplib&lt;br /&gt;
host = httplib.HTTP(‘localhost:8080’)&lt;br /&gt;
host.putrequest(‘GET’, ‘/testpage.html’)&lt;br /&gt;
host.putheader(‘accept’, ‘text/html’)&lt;br /&gt;
host.endheaders()&lt;br /&gt;
code, msg, headers = host.getreply()&lt;br /&gt;
print code, msg&lt;br /&gt;
if code == 200:&lt;br /&gt;
print host.getfile().read()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
|Ширина=310px}}&lt;br /&gt;
Для работы с HTTP Python предоставляет четыре основных модуля:&lt;br /&gt;
BaseHTTPServer, SimpleHTTPServer, CGIHTTPServer и httplib.&lt;br /&gt;
Первые три реализуют простейшие серверы, причем второй и третий&lt;br /&gt;
модули используют возможности первого, предоставляя программисту&lt;br /&gt;
более высокоуровневый интерфейс к его методам. Модуль httplib служит для разработки HTTP-клиентов.&lt;br /&gt;
&lt;br /&gt;
Например, простейший HTTP-сервер может выглядеть таким образом (см. листинг http-server.py).&lt;br /&gt;
&lt;br /&gt;
Как видите – всего четыре «рабочих» строчки, и то строка под номером 4 служит лишь для присвоения столь длинного имени метода-обработчика более короткой и удобной переменной. Вести себя этот сервер&lt;br /&gt;
будет как «самый настоящий»: он будет возвращаться запрошенные&lt;br /&gt;
html-страницы или файлы из текущего и вложенных в него каталогов,&lt;br /&gt;
при наличии файлов index.html или index.htm в каталоге, из которого сервер запущен, клиенту по умолчанию (когда указано только имя&lt;br /&gt;
каталога) будут отдаваться они. Если индексные файлы отсутствуют,&lt;br /&gt;
автоматически будет строиться страница-содержание каталога (аналогично работает Apache с включенным модулем mod_autoindex). В&lt;br /&gt;
ответ на запрос несуществующего ресурса будут возвращаться сообщения об ошибке, и т.д.&lt;br /&gt;
&lt;br /&gt;
Клиентское приложение будет не намного сложнее (см Листинг&lt;br /&gt;
http-client.py).&lt;br /&gt;
&lt;br /&gt;
В строках 4–7 формируется нужный HTTP-заголовок, затем получаем и распечатываем ответ сервера:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;html4strict&amp;quot;&amp;gt;&lt;br /&gt;
admin@dom:~/lxf/propy/l2$ ./http-client.py&lt;br /&gt;
200 OK&lt;br /&gt;
&amp;lt;HTML&amp;gt;&amp;lt;HEAD&amp;gt;&lt;br /&gt;
&amp;lt;TITLE&amp;gt;Test page&amp;lt;/TITLE&amp;gt;&lt;br /&gt;
&amp;lt;/HEAD&amp;gt;&amp;lt;BASE&amp;gt;&lt;br /&gt;
&amp;lt;H2&amp;gt;It is a test page&amp;lt;/H2&amp;gt;&lt;br /&gt;
&amp;lt;/BASE&amp;gt;&amp;lt;/HTML&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Конечно, чтобы представить эту страницу в графическом отформатированном виде, придется приложить еще немало усилий. Но это, как&lt;br /&gt;
говорится, уже дело техники.&lt;br /&gt;
&lt;br /&gt;
=== Электронная почта ===&lt;br /&gt;
Модули smtplib, poplib, imaplib предоставляют клиентские интерфейсы к соответствующим протоколам. Их использование не намного&lt;br /&gt;
сложнее рассмотренного выше httplib, и, думаю, вы без труда в них&lt;br /&gt;
разберетесь. Для более тонкой обработки содержимого почтовых сообщений (выделения заголовков, вложений и т.д.) вам помогут модули&lt;br /&gt;
rfc822, mimetools, multifile, base64, mailbox и другие. Все они&lt;br /&gt;
очень хорошо прокомментированы и снабжены достаточно подробной&lt;br /&gt;
документацией. Простейший способ получить к ней доступ – функция&lt;br /&gt;
help(). Например:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
&amp;gt;&amp;gt;&amp;gt; import mailbox&lt;br /&gt;
&amp;gt;&amp;gt;&amp;gt; help(mailbox)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Этот код выведет встроенную справку по работе с модулем mailbox&lt;br /&gt;
прямо в окне интерактивного терминала.&lt;br /&gt;
&lt;br /&gt;
=== FTP ===&lt;br /&gt;
{{Врезка&lt;br /&gt;
|Заголовок=FTP-CLIENT.PY&lt;br /&gt;
|Содержание=&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: uft-8 -*-&lt;br /&gt;
import ftplib&lt;br /&gt;
ftp = ftplib.FTP(‘ftp.freebsd.org’)&lt;br /&gt;
ftp.login(‘ftp’, ‘my@mail.ru’)&lt;br /&gt;
ftp.cwd(‘pub/FreeBSD’)&lt;br /&gt;
retfile = ‘README.TXT’&lt;br /&gt;
ftp.retrbinary(‘RETR %s’ % retfile,&lt;br /&gt;
open(retfile, ‘w+’).write, 1024)&lt;br /&gt;
ftp.quit()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
|Ширина=310px}}&lt;br /&gt;
Для работы с протоколом FTP к вашим услугам модуль ftplib. Работа с&lt;br /&gt;
ним ведется на достаточно низком уровне, и порой напоминает обычный&lt;br /&gt;
сеанс FTP, выполняемый вручную (см. листинг ftp-client.py).&lt;br /&gt;
&lt;br /&gt;
В итоге выполнения этого скрипта в текущем каталоге должен появиться файл README.TXT, скачанный с ftp-сервера ftp://ftp.freebsd.org.&lt;br /&gt;
&lt;br /&gt;
=== Заключение ===&lt;br /&gt;
Итак, на этом мы завершим знакомство с основными сетевыми возможностями языка Python. Хочу заметить, что они выходят далеко за рамки&lt;br /&gt;
простейших сценариев, пригодных для тестирования «больших» серверов или встраивания некоторых сетевых возможностей в ваши приложения. Приведу лишь несколько примеров. Так, в 90-х годах большой популярностью пользовался web-браузер Grail, разработанный на Python и&lt;br /&gt;
предоставляющий весьма широкие для того времени возможности по&lt;br /&gt;
обработке интернет-страниц – полная поддержка стандарта HTML 2.0&lt;br /&gt;
и, в значительной мере, HTML 3.2, поддержка различных форматов&lt;br /&gt;
изображений и звука, способность работать с языком разметки SGML,&lt;br /&gt;
поддержка FTP, и т.д.&lt;br /&gt;
&lt;br /&gt;
Менеджер почтовых рассылок Mailman обеспечивает широкие возможности по управлению списками рассылок, включая web-интерфейс. Интернет-сервер Medusa обладает достаточно хорошими&lt;br /&gt;
характеристиками, позволяя использовать его как для тестовых&lt;br /&gt;
целей, так и для промышленной эксплуатации. Популярный&lt;br /&gt;
сервер web-приложений Zope также полностью разработан&lt;br /&gt;
на языке Python.&lt;br /&gt;
&lt;br /&gt;
Таким образом, этот язык способен решать весьма серьезные сетевые задачи, причем эти решения, как правило,&lt;br /&gt;
обладают весьма высокой переносимостью между различными системами и платформами.&lt;br /&gt;
&lt;br /&gt;
В следующий раз мы рассмотрим способы взаимодействия с&lt;br /&gt;
базами данных, а также убедимся, что Python очень хорош и для разработки динамических web-сайтов.&lt;/div&gt;</summary>
		<author><name>AndrewDjVu</name></author>	</entry>

	<entry>
		<id>http://wiki.linuxformat.ru/wiki/LXF82:Python</id>
		<title>LXF82:Python</title>
		<link rel="alternate" type="text/html" href="http://wiki.linuxformat.ru/wiki/LXF82:Python"/>
				<updated>2009-02-05T19:45:44Z</updated>
		
		<summary type="html">&lt;p&gt;AndrewDjVu: /* Клиент всегда прав */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{цикл/Python}}&lt;br /&gt;
&lt;br /&gt;
== РАЗРАБОТКА клиент-серверных приложений ==&lt;br /&gt;
''ЧАСТЬ 2 Вознамерились написать открытую альтернативу Skype или собственный клиент BitTorrent? '''Сергей Супрунов''' научит всему необходимому – от основ архитектуры «клиент-сервер» до готовых библиотек для работы с существующими интернет-протоколами.''&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;
|Заголовок=UDP-CLIENT.PY&lt;br /&gt;
|Содержание=&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&lt;br /&gt;
from socket import *&lt;br /&gt;
hostname = sys.argv[1]&lt;br /&gt;
HEADER = '\x00\x01\x00\x00\x00\x01'&lt;br /&gt;
HEADER += '\x00\x00\x00\x00\x00\x00'&lt;br /&gt;
QUESTION = ''&lt;br /&gt;
parts = hostname.split('.')&lt;br /&gt;
for p in parts:&lt;br /&gt;
    QUESTION += '%c%s' % (chr(len(p)), p)&lt;br /&gt;
    QUESTION += '\x00\x00\x01\x00\x01'&lt;br /&gt;
QUERY = HEADER + QUESTION&lt;br /&gt;
cs = socket(AF_INET, SOCK_DGRAM)&lt;br /&gt;
cs.sendto(QUERY, ('127.0.0.1', 53))&lt;br /&gt;
rsp = cs.recv(1024)&lt;br /&gt;
start = len(QUERY) + 12&lt;br /&gt;
print '%s.%s.%s.%s' % (ord(rsp[start]),&lt;br /&gt;
    ord(rsp[start+1]),&lt;br /&gt;
    ord(rsp[start+2]),&lt;br /&gt;
    ord(rsp[start+3]))&amp;lt;/source&amp;gt;&lt;br /&gt;
|Ширина=310px}}&lt;br /&gt;
Наиболее распространенным способом взаимодействия двух приложений через сеть являются уже знакомые нам сокеты. Модуль&lt;br /&gt;
socket, входящий в стандартную поставку Python, помимо рассмотренных в прошлый раз Unix-сокетов поддерживает также сокеты&lt;br /&gt;
домена Internet. Методология использования мало чем отличается от&lt;br /&gt;
Unix-сокетов, за исключением того, что вместо параметра AF_UNIX&lt;br /&gt;
используется AF_INET, а вместо имени файла указываются имя хоста&lt;br /&gt;
и номер порта, которые будут обслуживаться создаваемым сокетом.&lt;br /&gt;
&lt;br /&gt;
В качестве второго параметра в конструкторе сокета можно указать&lt;br /&gt;
его тип: с установлением соединения, соответствующий протоколу TCP,&lt;br /&gt;
или без соединения – протокол UDP. Допустимые значения – SOCK_STREAM и SOCK_DGRAM соответственно. По умолчанию подразумевается SOCK_STREAM.&lt;br /&gt;
&lt;br /&gt;
Как пример, рассмотрим работу приложения, выполняющего роль&lt;br /&gt;
примитивного (и весьма ограниченного функционально) клиента DNS,&lt;br /&gt;
работающего по протоколу UDP (см. листинг udp-client.py).&lt;br /&gt;
&lt;br /&gt;
Некоторую сложность здесь представляет то, что DNS относится к&lt;br /&gt;
так называемым «двоичным» протоколам, в отличие от «текстовых»,&lt;br /&gt;
таких как HTTP или SMTP, где обмен идет обычными текстовыми строками. В случае с DNS оперировать приходится «сырыми» байтами.&lt;br /&gt;
Сведения по формату сообщений можно почерпнуть из RFC 1035, раздел «4. MESSAGES».&lt;br /&gt;
&lt;br /&gt;
В 6-й и 7-й строках рассматриваемого кода формируется заголовок&lt;br /&gt;
(12 байт). Пренебрегая всем богатством возможностей протокола DNS,&lt;br /&gt;
мы ограничиваемся простым запросом (OPCODE=0) одного доменного&lt;br /&gt;
имени (QDCOUNT=1). Конструкции вида «\x00» позволяют задать в&lt;br /&gt;
строке произвольный шестнадцатиричный код.&lt;br /&gt;
&lt;br /&gt;
В строках 8–12 формируется поле запроса. Оно состоит из частей&lt;br /&gt;
доменного имени (в оригинале разделенных точками), перед которыми&lt;br /&gt;
указывается число символов в этой части. Например, имя mail.ru состоит из двух частей (mail – 4 символа, ru – 2 символа) и в запросе должно выглядеть так: \x04mail\x02ru\x00. Завершающий ноль, а также&lt;br /&gt;
два двухбайтовых поля (QTYPE и QCLASS) добавляются к переменной&lt;br /&gt;
QUESTION в строке 12.&lt;br /&gt;
&lt;br /&gt;
Наконец, строки 14 и 15 – создание сокета (обратите внимание на&lt;br /&gt;
второй параметр, SOCK_DGRAM, указывающий тип транспортного протокола UDP) и отправка запроса. Поскольку UDP работает без установки&lt;br /&gt;
соединения, то вместо знакомой нам пары методов «connect – send» мы&lt;br /&gt;
используем один «sendto», в котором указывается сразу и отправляемая информация, и адрес получателя. В данном примере предполагается, что DNS-сервер работает на локальной машине. Вы можете указать&lt;br /&gt;
здесь свой DNS-сервер или передавать его имя в качестве параметра.&lt;br /&gt;
&lt;br /&gt;
И в строках 17–21 из всей полезной информации, возвращаемой&lt;br /&gt;
сервером, мы, игнорируя любые возможные ошибки, выбираем только&lt;br /&gt;
IP-адрес, размещаемый по смещению, которое формируется в переменной start. Результат работы:&lt;br /&gt;
 serg$ ./udp-client.py donpac.ru&lt;br /&gt;
 80.254.111.2&lt;br /&gt;
&lt;br /&gt;
=== Всегда к вашим услугам ===&lt;br /&gt;
{{Врезка&lt;br /&gt;
|Заголовок=IAMOK.PY&lt;br /&gt;
|Содержание=&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, re&lt;br /&gt;
from socket import *&lt;br /&gt;
class IamOK:&lt;br /&gt;
def __init__(self, host=’localhost’, port=12345):&lt;br /&gt;
self.socket = socket(AF_INET, SOCK_STREAM)&lt;br /&gt;
self.socket.bind((host, port))&lt;br /&gt;
self.socket.listen(5)&lt;br /&gt;
def process(self):&lt;br /&gt;
while 1:&lt;br /&gt;
csocket, caddress = self.socket.accept()&lt;br /&gt;
csocket.send(‘IamOK server v.0.0. Ready to serve.\n’)&lt;br /&gt;
csocket.send(‘You are from %s, port %s...\n’ % caddress)&lt;br /&gt;
while 1:&lt;br /&gt;
request = csocket.recv(64)&lt;br /&gt;
if re.match(‘get\s+uptime’, request, re.IGNORECASE):&lt;br /&gt;
csocket.send(os.popen(‘/usr/bin/uptime’).read())&lt;br /&gt;
elif re.match(‘quit|bye|exit’, request, re.IGNORECASE):&lt;br /&gt;
break&lt;br /&gt;
else:&lt;br /&gt;
csocket.send(‘Unknown command.\n’)&lt;br /&gt;
csocket.close()&lt;br /&gt;
if __name__ == ‘__main__’:&lt;br /&gt;
serv = IamOK()&lt;br /&gt;
serv.process()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
|Ширина=310px}}&lt;br /&gt;
В качестве примера сервера, на этот раз работающего по протоколу TCP,&lt;br /&gt;
рассмотрим такой код (см Листинг iamok.py).&lt;br /&gt;
&lt;br /&gt;
Думаю, вы уже поняли, что он прослушивает указанный порт&lt;br /&gt;
(12345), и при поступлении на него запроса возвращает клиенту вывод утилиты uptime, из которого можно почерпнуть время непрерывной&lt;br /&gt;
работы сервера, число подключенных в данный момент пользователей&lt;br /&gt;
и среднюю загрузку системы.&lt;br /&gt;
&lt;br /&gt;
Здесь все должно быть понятно по прошлому уроку. Два отличия – в конструкторе socket.socket() указывается второй параметр –&lt;br /&gt;
SOCK_STREAM (в данном случае его можно было бы и опустить, т.к.&lt;br /&gt;
для Internet-домена и так по умолчанию используется протокол TCP).&lt;br /&gt;
И метод bind() осуществляет привязку сокета не к файлу, а к имени&lt;br /&gt;
хоста и номеру порта, на котором будут ожидаться входящие соединения. Кстати, выбирая номер порта, не забывайте, что порты до 1024-го&lt;br /&gt;
относятся к привилегированным и могут быть задействованы только&lt;br /&gt;
пользователем root.&lt;br /&gt;
&lt;br /&gt;
Наш сервер понимает две команды: «get uptime», по которой возвращается информация о времени работы сервера, и «quit» (с двумя&lt;br /&gt;
синонимами – «bye» и «exit»), по которой сеанс завершается.&lt;br /&gt;
&lt;br /&gt;
Проверить работу сервера можно с помощью обычной&lt;br /&gt;
telnet-сессии:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
admin@dom:~/lxf/propy/l2$ telnet localhost 12345&lt;br /&gt;
Trying 127.0.0.1...&lt;br /&gt;
Connected to localhost.localdomain.&lt;br /&gt;
Escape character is ‘^]’.&lt;br /&gt;
IamOK server v.0.0. Ready to serve.&lt;br /&gt;
You are from 127.0.0.1, port 2650...&lt;br /&gt;
helo&lt;br /&gt;
Unknown command.&lt;br /&gt;
get uptime&lt;br /&gt;
23:09:26 up 58 min, 3 users, load average: 0.04, 0.15, 0.63&lt;br /&gt;
quit&lt;br /&gt;
Connection closed by foreign host.&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Какая от этого может быть польза – решайте сами.&lt;br /&gt;
&lt;br /&gt;
=== «Гуртом и батьку бить веселей» ===&lt;br /&gt;
{{Врезка&lt;br /&gt;
|Заголовок=THREAD-TEST.PY&lt;br /&gt;
|Содержание=&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: utf8 -*-&lt;br /&gt;
import threading as t&lt;br /&gt;
import time, re&lt;br /&gt;
diskbusy = t.Lock()&lt;br /&gt;
def parseit(lognum):&lt;br /&gt;
global errors, total&lt;br /&gt;
diskbusy.acquire()&lt;br /&gt;
log = open(‘logs/syslog.%d’ % lognum)&lt;br /&gt;
lines = log.readlines()&lt;br /&gt;
diskbusy.release()&lt;br /&gt;
for line in lines:&lt;br /&gt;
if re.search(‘failed|error’, line):&lt;br /&gt;
errors += 1&lt;br /&gt;
total += 1&lt;br /&gt;
class ParseLog(t.Thread):&lt;br /&gt;
def __init__(self, num):&lt;br /&gt;
self.lognum = num&lt;br /&gt;
t.Thread.__init__(self)&lt;br /&gt;
&lt;br /&gt;
def run(self):&lt;br /&gt;
parseit(self.lognum)&lt;br /&gt;
#------------------ 1&lt;br /&gt;
def test1():&lt;br /&gt;
global errors, total&lt;br /&gt;
errors = total = 0&lt;br /&gt;
for i in range(10):&lt;br /&gt;
parseit(i)&lt;br /&gt;
print errors, total,&lt;br /&gt;
#------------------ 2&lt;br /&gt;
def test2():&lt;br /&gt;
global errors, total&lt;br /&gt;
errors = total = 0&lt;br /&gt;
running = []&lt;br /&gt;
for i in range(10):&lt;br /&gt;
tr = ParseLog(i)&lt;br /&gt;
tr.start()&lt;br /&gt;
running.append(tr)&lt;br /&gt;
for tr in running:&lt;br /&gt;
tr.join()&lt;br /&gt;
&lt;br /&gt;
print errors, total,&lt;br /&gt;
start = time.time()&lt;br /&gt;
test1()&lt;br /&gt;
print ‘Послед.: %f’ % (time.time() - start)&lt;br /&gt;
start = time.time()&lt;br /&gt;
test2()&lt;br /&gt;
print ‘Потоки: %f’ % (time.time() - start)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
|Ширина=310px}}&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;
&lt;br /&gt;
В стандартной поставке Python для работы с потоками есть два&lt;br /&gt;
модуля – thread и threading. Первый позволяет управлять потоками на&lt;br /&gt;
достаточно низком уровне, второй – использует средства первого для&lt;br /&gt;
предоставления более удобного объектно-ориентированного интерфейса. Класс threading.Thread предоставляет «шаблон» потока. Для его&lt;br /&gt;
использования в своей программе вам нужно переопределить метод&lt;br /&gt;
run(), описав в нем действия, которые должны выполняться этим потоком. Ниже приведен пример, из которого все должно стать понятно.&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;
Вот если бы нам удалось организовать работу потоков таким образом, чтобы, пока один из них выполняет чтение файла, другие не обращались к диску... Для решения этой задачи в языке Python доступно&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;
if DISKBUSY:&lt;br /&gt;
# ожидание&lt;br /&gt;
else:&lt;br /&gt;
DISKBUSY = 1&lt;br /&gt;
# чтение файла&lt;br /&gt;
DISKBUSY = 0&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
То есть первый поток выставит истинное значение глобальной переменной DISKBUSY и приступит к чтению файла. «Опоздавшие» потоки&lt;br /&gt;
будут ждать, пока переменная вновь не примет значение «ложь».&lt;br /&gt;
&lt;br /&gt;
В такой реализации программисту предстоит решить не такую уж&lt;br /&gt;
простую, как может показаться на первый взгляд, задачу – грамотно&lt;br /&gt;
обеспечить ожидание. Бесконечный цикл проверки значения переменной слишком сильно нагружает процессор (не даром такие циклы&lt;br /&gt;
называют напряженными). Напрашивающееся time.sleep(1) очень&lt;br /&gt;
не эффективно – если ресурс освободится до того, как истечет время «спячки», то он будет простаивать. [Кроме того, подобный метод&lt;br /&gt;
синхронизации сам по себе не атомарен – подробности ищите в статье&lt;br /&gt;
«[[LXF82:Unix API|Очереди сообщений и семафоры]]»]&lt;br /&gt;
&lt;br /&gt;
Однако в модуле threading есть готовая реализация описанной&lt;br /&gt;
выше идеи – класс Lock, который предоставляет программисту так&lt;br /&gt;
называемые блокировки, иногда именуемые «мьютексами» (mutex).&lt;br /&gt;
Идя проста – создается объект данного класса (threading.Lock()),&lt;br /&gt;
который имеет два метода: acquire() позволяет захватить объект,&lt;br /&gt;
release() – освободить его. Метод aquire() является блокирующим:&lt;br /&gt;
очередной поток, вызвавший его, будет ждать до тех пор, пока мьютекс&lt;br /&gt;
не освободится.&lt;br /&gt;
&lt;br /&gt;
Дальнейшим развитием идеи блокировок являются семафоры.&lt;br /&gt;
Фактически, семафор – это тот же мьютекс, но позволяющий захватить себя несколько раз. Если вы создадите семафор командой&lt;br /&gt;
semaphore = threading.Semaphore(3), то его смогут захватить (тем&lt;br /&gt;
же методом semaphore.acquire()) одновременно три потока (каждый раз отнимая по единице из указанного при инициализации числа).&lt;br /&gt;
Четвертый поток сможет захватить семафор только после того, как он&lt;br /&gt;
будет высвобожден (semaphore.release()) одним из тех, которые&lt;br /&gt;
удерживают его в настоящее время.&lt;br /&gt;
&lt;br /&gt;
Впрочем, для нашей задачи лучше всего подходят мьютексы – диск&lt;br /&gt;
объявим неразделяемым ресурсом, и посмотрим, какой выигрыш по&lt;br /&gt;
времени это нам даст (см. листинг thread-test.py).&lt;br /&gt;
&lt;br /&gt;
Здесь мы проводим два теста – последовательная обработка (1) и&lt;br /&gt;
использование потоков с эксклюзивным доступом к диску (2). В строках&lt;br /&gt;
16–22 мы создаем подкласс класса Thread, в котором переопределяем&lt;br /&gt;
метод run(). Запуск потока выполняется в строке 37. Обратите внимание&lt;br /&gt;
на список running (строки 34, 38, 39–40). С его помощью мы отслеживаем активность потоков – метод join() заставляет ждать, пока поток не&lt;br /&gt;
завершит свою работу. Дальнейшая работа основного сценария продолжится только после того, как отработают все порожденные потоки.&lt;br /&gt;
&lt;br /&gt;
В строке 5 мы создаем мьютекс, с помощью которого в строках 8 и 11&lt;br /&gt;
будет регулироваться доступ потоков к диску. В глобальной переменной&lt;br /&gt;
errors ведется подсчет числа строк, в которых есть подстрока «failed» или&lt;br /&gt;
«error», в total – общее число обработанных строк. Результат работы:&lt;br /&gt;
 21931 2302755 Послед.: 33.271387&lt;br /&gt;
 21931 2302755 Потоки: 22.867245&lt;br /&gt;
Как видите, мы получили выигрыш по времени более чем на 30%.&lt;br /&gt;
Но нужно заметить, что распараллеливание подобных скриптов даст&lt;br /&gt;
заметный эффект только в том случае, если нагрузка на дисковую систему сопоставима с нагрузкой на процессор. Если какой-то из ресурсов&lt;br /&gt;
будет востребован намного больше второго, то потокам все равно придется ждать его высвобождения, а с учетом дополнительных затрат на&lt;br /&gt;
обслуживание самих потоков, суммарный результат может оказаться&lt;br /&gt;
даже хуже, чем при последовательной обработке.&lt;br /&gt;
&lt;br /&gt;
=== Все включено ===&lt;br /&gt;
В поставку Python входит несколько готовых модулей, позволяющих&lt;br /&gt;
легко и быстро разработать сетевую программу, например, HTTP-сервер или FTP-клиент. Более детально вы сможете познакомиться с ними&lt;br /&gt;
в документации или в хорошо прокомментированных исходных кодах&lt;br /&gt;
самих модулей. Здесь же рассмотрим их возможности обзорно.&lt;br /&gt;
&lt;br /&gt;
=== HTTP ===&lt;br /&gt;
{{Врезка&lt;br /&gt;
|Заголовок=HTTP-SERVER.PY&lt;br /&gt;
|Содержание=&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: utf8 -*-&lt;br /&gt;
import SimpleHTTPServer as http&lt;br /&gt;
handler = http.SimpleHTTPRequestHandler&lt;br /&gt;
server = http.BaseHTTPServer.HTTPServer((‘localhost’, 8080), handler)&lt;br /&gt;
server.serve_forever()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
|Ширина=310px}}&lt;br /&gt;
{{Врезка&lt;br /&gt;
|Заголовок=HTTP-CLIENT.PY&lt;br /&gt;
|Содержание=&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: utf8 -*-&lt;br /&gt;
import httplib&lt;br /&gt;
host = httplib.HTTP(‘localhost:8080’)&lt;br /&gt;
host.putrequest(‘GET’, ‘/testpage.html’)&lt;br /&gt;
host.putheader(‘accept’, ‘text/html’)&lt;br /&gt;
host.endheaders()&lt;br /&gt;
code, msg, headers = host.getreply()&lt;br /&gt;
print code, msg&lt;br /&gt;
if code == 200:&lt;br /&gt;
print host.getfile().read()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
|Ширина=310px}}&lt;br /&gt;
Для работы с HTTP Python предоставляет четыре основных модуля:&lt;br /&gt;
BaseHTTPServer, SimpleHTTPServer, CGIHTTPServer и httplib.&lt;br /&gt;
Первые три реализуют простейшие серверы, причем второй и третий&lt;br /&gt;
модули используют возможности первого, предоставляя программисту&lt;br /&gt;
более высокоуровневый интерфейс к его методам. Модуль httplib служит для разработки HTTP-клиентов.&lt;br /&gt;
&lt;br /&gt;
Например, простейший HTTP-сервер может выглядеть таким образом (см. листинг http-server.py).&lt;br /&gt;
&lt;br /&gt;
Как видите – всего четыре «рабочих» строчки, и то строка под номером 4 служит лишь для присвоения столь длинного имени метода-обработчика более короткой и удобной переменной. Вести себя этот сервер&lt;br /&gt;
будет как «самый настоящий»: он будет возвращаться запрошенные&lt;br /&gt;
html-страницы или файлы из текущего и вложенных в него каталогов,&lt;br /&gt;
при наличии файлов index.html или index.htm в каталоге, из которого сервер запущен, клиенту по умолчанию (когда указано только имя&lt;br /&gt;
каталога) будут отдаваться они. Если индексные файлы отсутствуют,&lt;br /&gt;
автоматически будет строиться страница-содержание каталога (аналогично работает Apache с включенным модулем mod_autoindex). В&lt;br /&gt;
ответ на запрос несуществующего ресурса будут возвращаться сообщения об ошибке, и т.д.&lt;br /&gt;
&lt;br /&gt;
Клиентское приложение будет не намного сложнее (см Листинг&lt;br /&gt;
http-client.py).&lt;br /&gt;
&lt;br /&gt;
В строках 4–7 формируется нужный HTTP-заголовок, затем получаем и распечатываем ответ сервера:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;html4strict&amp;quot;&amp;gt;&lt;br /&gt;
admin@dom:~/lxf/propy/l2$ ./http-client.py&lt;br /&gt;
200 OK&lt;br /&gt;
&amp;lt;HTML&amp;gt;&amp;lt;HEAD&amp;gt;&lt;br /&gt;
&amp;lt;TITLE&amp;gt;Test page&amp;lt;/TITLE&amp;gt;&lt;br /&gt;
&amp;lt;/HEAD&amp;gt;&amp;lt;BASE&amp;gt;&lt;br /&gt;
&amp;lt;H2&amp;gt;It is a test page&amp;lt;/H2&amp;gt;&lt;br /&gt;
&amp;lt;/BASE&amp;gt;&amp;lt;/HTML&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Конечно, чтобы представить эту страницу в графическом отформатированном виде, придется приложить еще немало усилий. Но это, как&lt;br /&gt;
говорится, уже дело техники.&lt;br /&gt;
&lt;br /&gt;
=== Электронная почта ===&lt;br /&gt;
Модули smtplib, poplib, imaplib предоставляют клиентские интерфейсы к соответствующим протоколам. Их использование не намного&lt;br /&gt;
сложнее рассмотренного выше httplib, и, думаю, вы без труда в них&lt;br /&gt;
разберетесь. Для более тонкой обработки содержимого почтовых сообщений (выделения заголовков, вложений и т.д.) вам помогут модули&lt;br /&gt;
rfc822, mimetools, multifile, base64, mailbox и другие. Все они&lt;br /&gt;
очень хорошо прокомментированы и снабжены достаточно подробной&lt;br /&gt;
документацией. Простейший способ получить к ней доступ – функция&lt;br /&gt;
help(). Например:&lt;br /&gt;
&amp;lt;source lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
&amp;gt;&amp;gt;&amp;gt; import mailbox&lt;br /&gt;
&amp;gt;&amp;gt;&amp;gt; help(mailbox)&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
Этот код выведет встроенную справку по работе с модулем mailbox&lt;br /&gt;
прямо в окне интерактивного терминала.&lt;br /&gt;
&lt;br /&gt;
=== FTP ===&lt;br /&gt;
{{Врезка&lt;br /&gt;
|Заголовок=FTP-CLIENT.PY&lt;br /&gt;
|Содержание=&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: uft-8 -*-&lt;br /&gt;
import ftplib&lt;br /&gt;
ftp = ftplib.FTP(‘ftp.freebsd.org’)&lt;br /&gt;
ftp.login(‘ftp’, ‘my@mail.ru’)&lt;br /&gt;
ftp.cwd(‘pub/FreeBSD’)&lt;br /&gt;
retfile = ‘README.TXT’&lt;br /&gt;
ftp.retrbinary(‘RETR %s’ % retfile,&lt;br /&gt;
open(retfile, ‘w+’).write, 1024)&lt;br /&gt;
ftp.quit()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
|Ширина=310px}}&lt;br /&gt;
Для работы с протоколом FTP к вашим услугам модуль ftplib. Работа с&lt;br /&gt;
ним ведется на достаточно низком уровне, и порой напоминает обычный&lt;br /&gt;
сеанс FTP, выполняемый вручную (см. листинг ftp-client.py).&lt;br /&gt;
&lt;br /&gt;
В итоге выполнения этого скрипта в текущем каталоге должен появиться файл README.TXT, скачанный с ftp-сервера ftp://ftp.freebsd.org.&lt;br /&gt;
&lt;br /&gt;
=== Заключение ===&lt;br /&gt;
Итак, на этом мы завершим знакомство с основными сетевыми возможностями языка Python. Хочу заметить, что они выходят далеко за рамки&lt;br /&gt;
простейших сценариев, пригодных для тестирования «больших» серверов или встраивания некоторых сетевых возможностей в ваши приложения. Приведу лишь несколько примеров. Так, в 90-х годах большой популярностью пользовался web-браузер Grail, разработанный на Python и&lt;br /&gt;
предоставляющий весьма широкие для того времени возможности по&lt;br /&gt;
обработке интернет-страниц – полная поддержка стандарта HTML 2.0&lt;br /&gt;
и, в значительной мере, HTML 3.2, поддержка различных форматов&lt;br /&gt;
изображений и звука, способность работать с языком разметки SGML,&lt;br /&gt;
поддержка FTP, и т.д.&lt;br /&gt;
&lt;br /&gt;
Менеджер почтовых рассылок Mailman обеспечивает широкие возможности по управлению списками рассылок, включая web-интерфейс. Интернет-сервер Medusa обладает достаточно хорошими&lt;br /&gt;
характеристиками, позволяя использовать его как для тестовых&lt;br /&gt;
целей, так и для промышленной эксплуатации. Популярный&lt;br /&gt;
сервер web-приложений Zope также полностью разработан&lt;br /&gt;
на языке Python.&lt;br /&gt;
&lt;br /&gt;
Таким образом, этот язык способен решать весьма серьезные сетевые задачи, причем эти решения, как правило,&lt;br /&gt;
обладают весьма высокой переносимостью между различными системами и платформами.&lt;br /&gt;
&lt;br /&gt;
В следующий раз мы рассмотрим способы взаимодействия с&lt;br /&gt;
базами данных, а также убедимся, что Python очень хорош и для разработки динамических web-сайтов.&lt;/div&gt;</summary>
		<author><name>AndrewDjVu</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>2009-02-03T19:29:26Z</updated>
		
		<summary type="html">&lt;p&gt;AndrewDjVu: /* logclient2.py */&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;
&lt;br /&gt;
import sys, socket, time&lt;br /&gt;
&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;
&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;
&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;
&lt;br /&gt;
import os&lt;br /&gt;
from logclient import LogClient&lt;br /&gt;
&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>AndrewDjVu</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>2009-02-03T19:23:26Z</updated>
		
		<summary type="html">&lt;p&gt;AndrewDjVu: /* logclient.py */&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;
&lt;br /&gt;
import sys, socket, time&lt;br /&gt;
&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;
&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;
&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>AndrewDjVu</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>2009-02-03T19:18:43Z</updated>
		
		<summary type="html">&lt;p&gt;AndrewDjVu: /* logserver.py */&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>AndrewDjVu</name></author>	</entry>

	</feed>