справочник по Python. мм isbn 9785932861578 9 785932 861578
Скачать 4.21 Mb.
|
d.accept() Принимает соединение. Возвращает кортеж (client, addr), где в поле cli- ent возвращается объект сокета, используемый для обмена данными через соединение, а в поле addr – адрес клиента. d.bind(address) Присваивает сокету указанный адрес address. В аргументе address обычно передается кортеж (host, port), однако точное представление адреса зави- сит от используемого семейства адресов. d.close() Закрывает сокет. d.connect(address) Устанавливает соединение. В аргументе address передается кортеж (host, port) 570 Глава 21. Работа с сетью и сокеты d.create_socket(family, type) Создает новый сокет. Аргументы имеют тот же смысл, что и в функции socket.socket() d.listen([backlog]) Принимает входящие соединения. В аргументе backlog передается целое число, которое передается функции socket.listen(), на основе которой реа- лизован этот метод. d.recv(size) Принимает до size байтов. Пустая строка служит признаком того, что кли- байтов. Пустая строка служит признаком того, что кли- . Пустая строка служит признаком того, что кли- Пустая строка служит признаком того, что кли- ент закрыл канал. d.send(data) Отправляет данные data. В аргументе data передается строка байтов. Следующая функция используется для запуска цикла приема и обработки событий: loop([timeout [, use_poll [, map [, count]]]]) Запускает бесконечный цикл опроса. Если в аргументе use_poll передается значение False, опрос выполняется с помощью функции select(), в против- ном случае используется функция poll(). Аргумент timeout определяет пре- дельное время ожидания и по умолчанию принимает значение 30 секунд. В аргументе map передается словарь со всеми каналами для мониторинга. Аргумент count определяет количество операций опроса, которые должны быть выполнены перед тем, как функция вернет управление. Если в аргу- менте count передать None (по умолчанию), функция loop() выполняет беско- нечный цикл опроса, пока все каналы не будут закрыты. Если в аргументе count передать 1, функция выполнит одну проверку на наличие событий и вернет управление. Пример Следующий пример реализует веб-сервер с минимальными возможностя- ми на основе модуля asyncore. В примере определяются два класса: asynhttp, принимающий соединения, и asynclient, обрабатывающий запросы кли- ентов. Сравните этот пример с примером, который приводился в разде- ле с описанием модуля asynchat. Основное отличие этого примера состоит в том, что он реализован на более низком уровне, из-за чего потребовалось побеспокоиться о таких проблемах, как разбиение потока входных данных на строки, буферизация данных и идентификация пустых строк, заверша- ющих заголовки запросов. # Асинхронный сервер HTTP import asyncore, socket import os import mimetypes import collections try: from http.client import responses # Python 3 Модуль asyncore 571 except ImportError: from httplib import responses # Python 2 ёё # Следующий класс просто принимает входящие соединения class async_http(asyncore.dispatcher): def __init__(self,port): asyncore.dispatcher.__init__(self) self.create_socket(socket.AF_INET,socket.SOCK_STREAM) self.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.bind((‘’,port)) self.listen(5) ёё def handle_accept(self): client,addr = self.accept() return async_http_handler(client) ёё # Класс, обслуживающий клиентов class async_http_handler(asyncore.dispatcher): def __init__(self, sock = None): asyncore.dispatcher.__init__(self,sock) self.got_request = False # Запрос HTTP прочитан? self.request_data = b”” self.write_queue = collections.deque() self.responding = False ёё # Может использоваться для чтения, только если запрос еще не был прочитан def readable(self): return not self.got_request ёё # Читает входящие данные запроса def handle_read(self): chunk = self.recv(8192) self.request_data += chunk if b’\r\n\r\n’ in self.request_data: self.handle_request() ёё # Обрабатывает входящий запрос def handle_request(self): self.got_request = True header_data = self.request_data[:self.request_data.find(b’\r\n\r\n’)] header_text = header_data.decode(‘latin-1’) header_lines = header_text.splitlines() request = header_lines[0].split() op = request[0] url = request[1][1:] self.process_request(op,url) ёё # Обрабатывает запрос def process_request(self,op,url): self.responding = True if op == “GET”: if not os.path.exists(url): self.send_error(404,”File %s not found\r\n” % url) else: type, encoding = mimetypes.guess_type(url) 572 Глава 21. Работа с сетью и сокеты size = os.path.getsize(url) self.push_text(‘HTTP/1.0 200 OK\r\n’) self.push_text(‘Content-length: %d\r\n’ % size) self.push_text(‘Content-type: %s\r\n’ % type) self.push_text(‘\r\n’) self.push(open(url,”rb”).read()) else: self.send_error(501,”%s method not implemented” % self.op) ёё # Обработка ошибок def send_error(self,code,message): self.push_text(‘HTTP/1.0 %s %s\r\n’ % (code, responses[code])) self.push_text(‘Content-type: text/plain\r\n’) self.push_text(‘\r\n’) self.push_text(message) ёё # Добавляет двоичные данные в выходную очередь def push(self,data): self.write_queue.append(data) ёё # Добавляет текстовые данные в выходную очередь def push_text(self,text): self.push(text.encode(‘latin-1’)) ёё # Готов для записи, только если ответ готов def writable(self): return self.responding and self.write_queue ёё # Записывает данные ответа def handle_write(self): chunk = self.write_queue.popleft() bytes_sent = self.send(chunk) if bytes_sent != len(chunk): self.write_queue.appendleft(chunk[bytes_sent:]) if not self.write_queue: self.close() ёё # Создать сервер a = async_http(8080) # Запустить бесконечный цикл опроса asyncore.loop() См. также Описание модулей socket (стр. 586), select (стр. 572), SocketServer (стр. 611), пакета http (стр. 623). Модуль select М одуль select предоставляет доступ к системным вызовам select() и poll(). Системный вызов select() обычно используется для реализации опроса, или мультиплексирования, обработки нескольких потоков ввода-вывода, без использования потоков управления или дочерних процессов. В систе- мах UNIX эти вызовы можно использовать для работы с файлами, сокета- UNIX эти вызовы можно использовать для работы с файлами, сокета- эти вызовы можно использовать для работы с файлами, сокета- Модуль select 573 ми, каналами и со многими другими типами файлов. В Windows их можно использовать только для работы с сокетами. select(iwtd, owtd, ewtd [, timeout]) Запрашивает информацию о готовности к вводу, выводу и о наличии ис- ключений для группы дескрипторов файлов. В первых трех аргументах передаются списки с целочисленными дескрипторами файлов или с объ- ектами, обладающими методом fileno(), который возвращает дескриптор файла. Аргумент iwtd определяет список объектов, которые проверяются на готовность к вводу, owtd – список объектов, которые проверяются на готовность к выводу, и ewtd – список объектов, которые проверяются на наличие исключительных ситуаций. В любом из аргументов допускается передавать пустой список. В аргументе timeout передается число с плаваю- щей точкой, определяющее предельное время ожидания в секундах. При вызове без аргумента timeout функция ожидает, пока хотя бы один из де- скрипторов не окажется в требуемом состоянии. Если в этом аргументе пе- редать число 0, функция просто выполнит опрос и тут же вернет управле- ние. Возвращает кортеж списков с объектами, находящимися в требуемом состоянии. Эти списки включают подмножества объектов в первых трех аргументах. Если к моменту истечения предельного времени ожидания ни один из дескрипторов не находится в требуемом состоянии, возвращается три пустых списка. В случае ошибки возбуждается исключение select.er- .er- er- ror . В качестве значения исключения возвращается та же информация, что и в исключениях IOError и OSError. poll() Создает объект, выполняющий опрос с помощью системного вызова poll(). Эта функция доступна только в системах, поддерживающих системный вызов poll(). Объект p, возвращаемый функцией poll(), поддерживает следующие мето- ды: p.register(fd [, eventmask]) Регистрирует новый дескриптор файла fd. В аргументе fd может переда- ваться целочисленный дескриптор или объект, обладающий методом file- no() , с помощью которого можно получить дескриптор. В аргументе event- mask передается битная маска, составленная с помощью битовой операции ИЛИ из следующих флагов, которая определяет интересующие события: Константа Описание POLLIN Имеются данные, доступные для чтения. POLLPRI Имеются срочные данные, доступные для чтения. POLLOUT Готов к записи. POLLERR Ошибка. POLLHUP Разрыв соединения. POLLNVAL Недопустимый запрос. 574 Глава 21. Работа с сетью и сокеты При вызове функции без аргумента eventmask проверяются события POLLIN, POLLPRI и POLLOUT. p.unregister(fd) Удаляет дескриптор файла fd из объекта, выполняющего опрос. Возбуж- дает исключение KeyError, если дескриптор не был зарегистрирован ранее. p.poll([timeout]) Проверяет наступление событий для всех зарегистрированных дескрипто- ров. Необязательный аргумент timeout определяет предельное время ожи- дания в миллисекундах. Возвращает список кортежей (fd, event), где поле fd является дескриптором файла, а поле event – битной маской, определяю- щей события. Поля этой битовой маски соответствуют константам POLLIN, POLLOUT и так далее. Например, чтобы проверить наличие события POLLIN, достаточно просто проверить значение выражение event & POLLIN на нера- венство нулю. Если возвращается пустой список, это означает, что в тече- ние указанного времени ожидания не возникло ни одного события. Дополнительные возможности модуля Функции select() и poll(), объявленные в этом модуле, совместимы со мно- гими операционными системами. Кроме того, в системах Linux модуль select предоставляет интерфейс к механизму определения состояния по перепаду и по значению (epoll), который может обеспечить значительно более высокую производительность. В системах BSD предоставляется воз- можность доступа к очереди ядра и к объектам событий. Описание этих программных интерфейсов можно найти в электронной документации к модулю select, по адресу http://docs.python.org/library/select. Усложненный пример использования асинхронного ввода-вывода Иногда модуль select используется для реализации серверов, основанных на тасклетах и сопрограммах – механизмах многозадачности, в которых не используются потоки управления или процессы. Следующий пример иллюстрирует данную концепцию, реализуя диспетчер задач для сопро- грамм, основанный на операциях ввода-вывода. Предупреждаю, что это самый сложный пример в книге и вам может потребоваться внимательно исследовать его, чтобы понять, как он работает. Возможно, вам также по- требуется ознакомиться с моим учебным руководством «A Curious Course on Coroutines and Concurrency» (http://www.dabeaz.com/coroutines), где можно найти дополнительный справочный материал. import select import types import collections ёё # Объект, представляющий запущенную задачу class Task(object): def __init__(self,target): self.target = target # Сопрограмма Модуль select 575 self.sendval = None # Значение, которое передается при возобновлении self.stack = [] # Стек вызовов ёё def run(self): try: result = self.target.send(self.sendval) if isinstance(result,SystemCall): return result if isinstance(result,types.GeneratorType): self.stack.append(self.target) self.sendval = None self.target = result else: if not self.stack: return self.sendval = result self.target = self.stack.pop() except StopIteration: if not self.stack: raise self.sendval = None self.target = self.stack.pop() ёё # Объект, представляющий “системный вызов” class SystemCall(object): def handle(self,sched,task): pass ёё # Объект диспетчера задач class Scheduler(object): def __init__(self): self.task_queue = collections.deque() self.read_waiting = {} self.write_waiting = {} self.numtasks = 0 ёё # Создает новую задачу из сопрограммы def new(self,target): newtask = Task(target) self.schedule(newtask) self.numtasks += 1 ёё # Добавляет задачу в очередь задач def schedule(self,task): self.task_queue.append(task) ёё # Приостанавливает задачу, пока дескриптор файла не станет # доступным для чтения def readwait(self,task,fd): self.read_waiting[fd] = task ёё # Приостанавливает задачу, пока дескриптор файла не станет # доступным для записи def writewait(self,task,fd): self.write_waiting[fd] = task ёё # Главный цикл диспетчера задач def mainloop(self,count=-1,timeout=None): 576 Глава 21. Работа с сетью и сокеты while self.numtasks: # Проверить наличие событий ввода-вывода if self.read_waiting or self.write_waiting: wait = 0 if self.task_queue else timeout r,w,e = select.select(self.read_waiting, self.write_waiting, [], wait) for fileno in r: self.schedule(self.read_waiting.pop(fileno)) for fileno in w: self.schedule(self.write_waiting.pop(fileno)) ёё # Запустить все задачи, имеющиеся в очереди, # которые готовы к запуску while self.task_queue: task = self.task_queue.popleft() try: result = task.run() if isinstance(result,SystemCall): result.handle(self,task) else: self.schedule(task) except StopIteration: self.numtasks -= 1 ёё # Если нет задач, готовых для запуска, # требуется решить – продолжать или выйти else: if count > 0: count -= 1 if count == 0: return ёё # Реализация различных системных вызовов class ReadWait(SystemCall): def __init__(self,f): self.f = f def handle(self,sched,task): fileno = self.f.fileno() sched.readwait(task,fileno) ёё class WriteWait(SystemCall): def __init__(self,f): self.f = f def handle(self,sched,task): fileno = self.f.fileno() sched.writewait(task,fileno) ёё class NewTask(SystemCall): def __init__(self,target): self.target = target def handle(self,sched,task): sched.new(self.target) sched.schedule(task) Программный код этого примера реализует «операционную систему» в ми- ниатюре. Ниже коротко описывается, как он действует: Модуль select 577 • Вся основная работа выполняется сопрограммами. Сопрограммы, как и генераторы, используют инструкцию yield, но в отличие от генерато- ров, в сопрограммах она используется не только чтобы возвращать зна- чения, но и чтобы принимать значения, которые передаются с помощью метода send(value). • Класс Task представляет готовую к запуску задачу и является всего лишь тонкой оберткой вокруг сопрограммы. Объект task класса Task может выполнять единственную операцию – task.run(). Этот метод воз- обновляет выполнение задачи, которая продолжает работать, пока не встретит следующую инструкцию yield, после чего выполнение задачи приостанавливается. После запуска задачи атрибут task.sendval содер- жит значение, которое должно быть послано соответствующему выра- жению yield в задаче. Задачи продолжают выполняться, пока не будет встречена следующая инструкция yield. Значение, которое воспроиз- водится этой инструкцией, определяет, что будет происходить дальше внутри задачи: • Если возвращаемым значением является другая сопрограмма (type. GeneratorType ), это означает, что задача временно передает управле- ние другой сопрограмме. Атрибут stack объекта класса Task представ- ляет стек вызовов сопрограмм, в котором сохраняется прежняя со- программа. При следующем запуске задачи управление будет пере- дано новой сопрограмме. • Если возвращаемым значением является экземпляр класса System- Call , это означает, что задаче требуется, чтобы диспетчер выполнил некоторую операцию от своего имени (например, запустил бы новую задачу, приостановил бы выполнение задачи, пока не появится воз- можность выполнить операцию ввода-вывода, и так далее). Назначе- ние этого объекта описывается чуть ниже. • Если возвращается какое-либо другое значение, это может означать одно из двух: либо управление вернула сопрограмма, запущенная из задачи, либо управление вернула сама задача. Если стек вызовов не пуст, это означает, что произошел возврат из сопрограммы, за- пущенной из задачи, поэтому вызывающая задача выталкивается из стека вызовов, а возвращаемое значение сохраняется, чтобы его можно было передать вызывающей сопрограмме. Вызывающая со- программа получит это значение, когда будет запущена в следую- щий раз. Если стек вызовов пуст, возвращаемое значение просто уничтожается. • Исключение StopIteration означает, что сопрограмма завершила свою работу. Когда появляется это исключение, управление переда- работу. Когда появляется это исключение, управление переда- работу. Когда появляется это исключение, управление переда- . Когда появляется это исключение, управление переда- Когда появляется это исключение, управление переда- ется предыдущей сопрограмме, сохраненной в стеке вызовов (если таковая имеется), либо исключение будет передано диспетчеру и в этом случае оно будет интерпретироваться, как признак завершения работы сопрограммы. • Объект класса SystemCall представляет системный вызов. Когда работа- ющей задаче требуется сообщить диспетчеру, что он должен выполнить операцию от своего имени, она с помощью инструкции yield возвращает |